@alliance-droid/status-feedback-system 4.2.4 → 4.3.0
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/CHANGELOG.md +13 -5
- package/README.md +367 -365
- package/dist/api.d.ts +9 -2
- package/dist/api.js +4 -2
- package/dist/components/board/FeedbackItem.svelte +26 -5
- package/dist/components/board/FeedbackList.svelte +2 -2
- package/dist/components/board/FeedbackSidebar.svelte +11 -5
- package/dist/components/board/StatusBadgeMenu.svelte +32 -14
- package/dist/components/board/capabilities.d.ts +4 -0
- package/dist/components/board/capabilities.js +10 -0
- package/dist/components/board/state.svelte.d.ts +3 -3
- package/dist/components/board/state.svelte.js +32 -59
- package/dist/components/board/status-state.svelte.d.ts +10 -0
- package/dist/components/board/status-state.svelte.js +41 -0
- package/dist/sveltekit/server.js +9 -4
- package/package.json +70 -70
package/README.md
CHANGED
|
@@ -1,365 +1,367 @@
|
|
|
1
|
-
# @alliance-droid/status-feedback-system
|
|
2
|
-
|
|
3
|
-
Drop-in system status pages and user feedback components for SvelteKit apps.
|
|
4
|
-
|
|
5
|
-
This package provides UI components plus an optional SvelteKit server proxy for talking to a [status-feedback-admin](https://alliance.ghe.com/alliance/status-feedback-admin) backend. The admin app handles data storage, API key management, and the admin dashboard.
|
|
6
|
-
|
|
7
|
-
## Architecture
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
┌─────────────────────────┐ ┌──────────────────────────┐
|
|
11
|
-
│ Your SvelteKit App │ │ status-feedback-admin │
|
|
12
|
-
│ │ │ │
|
|
13
|
-
│ ┌───────────────────┐ │ API │ ┌────────────────────┐ │
|
|
14
|
-
│ │ StatusPage │──┼───────┼──│ /api/status │ │
|
|
15
|
-
│ │ FeedbackButton │ │ │ │ /api/feedback │ │
|
|
16
|
-
│ │ DevFeedback │ │ │ │ /api/incidents │ │
|
|
17
|
-
│ │ Feedback board │ │ │ │ /api/board │ │
|
|
18
|
-
│ │ (composable) │ │ │ │ │ │
|
|
19
|
-
│ └───────────────────┘ │ │ └────────────────────┘ │
|
|
20
|
-
│ │ │ │ │
|
|
21
|
-
│ npm package │ │ MSSQL + Azure Blob │
|
|
22
|
-
└─────────────────────────┘ └──────────────────────────┘
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Setup
|
|
26
|
-
|
|
27
|
-
### 1. Deploy the admin backend
|
|
28
|
-
|
|
29
|
-
The admin app ([status-feedback-admin](https://alliance.ghe.com/alliance/status-feedback-admin)) provides the API, database, and admin dashboard. Deploy it first.
|
|
30
|
-
|
|
31
|
-
Required env vars on the admin app:
|
|
32
|
-
```bash
|
|
33
|
-
MSSQL_CONNECTION_STRING=Server=...;Initial Catalog=StatusFeedback;...
|
|
34
|
-
SESSION_SECRET=your-secret
|
|
35
|
-
|
|
36
|
-
# Optional — stores screenshots in Azure Blob Storage instead of base64 in DB
|
|
37
|
-
AZURE_FEEDBACK_BLOB_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=...
|
|
38
|
-
AZURE_FEEDBACK_BLOB_CONTAINER=screenshots
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### 2. Create a project and keep the API key server-side
|
|
42
|
-
|
|
43
|
-
Log into the admin dashboard, create a project, and copy its API key (prefixed `sf_`). Store it in a private server-side environment variable, not a `PUBLIC_` / browser-delivered variable.
|
|
44
|
-
|
|
45
|
-
### 3. Install the client package
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
npm install @alliance-droid/status-feedback-system
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
**Peer dependencies:** `svelte ^5.48.0`, `@alliance-droid/svelte-component-library >=1.3.2`
|
|
52
|
-
|
|
53
|
-
> `@alliance-droid/svelte-component-library` must be `>=1.3.2` — earlier versions are incompatible with Svelte `5.56+` and throw `target.exclude.has is not a function` when board components render.
|
|
54
|
-
|
|
55
|
-
### 4. Add the recommended SvelteKit proxy
|
|
56
|
-
|
|
57
|
-
Use one catch-all route in the consuming app. The package owns the admin API path mapping, request validation, key injection, and `key` query stripping; the app owns auth and app-specific policy.
|
|
58
|
-
|
|
59
|
-
```ts
|
|
60
|
-
// src/routes/svc/feedback/[...path]/+server.ts
|
|
61
|
-
import { env } from '$env/dynamic/private';
|
|
62
|
-
import { createSvelteKitFeedbackProxy } from '@alliance-droid/status-feedback-system/sveltekit/server';
|
|
63
|
-
|
|
64
|
-
export const { GET, POST, PATCH } = createSvelteKitFeedbackProxy({
|
|
65
|
-
apiUrl: env.SF_API_URL,
|
|
66
|
-
apiKey: env.SF_API_KEY,
|
|
67
|
-
resolveUser: async (event) => {
|
|
68
|
-
const user = event.locals.user;
|
|
69
|
-
return user ? { email: user.email, roles: user.roles } : null;
|
|
70
|
-
},
|
|
71
|
-
requireUser: true,
|
|
72
|
-
canSubmitInAppFeedback: ({ user }) =>
|
|
73
|
-
user.roles.includes('QA') || user.roles.includes('ProductOwner'),
|
|
74
|
-
});
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Then point components at the same-origin route and omit `apiKey`:
|
|
78
|
-
|
|
79
|
-
```svelte
|
|
80
|
-
<DevFeedback apiUrl="/svc/feedback/dev" enabled={canSubmitFeedback} userEmail={user.email} />
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
Direct admin calls with `apiKey="sf_..."` remain supported for existing integrations, but the proxy is the hardened default.
|
|
84
|
-
|
|
85
|
-
## Components
|
|
86
|
-
|
|
87
|
-
### StatusPage
|
|
88
|
-
|
|
89
|
-
Full status page with systems, incidents, and uptime history bars. Supports **self-fetching** (provide `apiUrl`; add `apiKey` only for direct admin calls) or **pass-through** (provide data directly).
|
|
90
|
-
|
|
91
|
-
```svelte
|
|
92
|
-
<script>
|
|
93
|
-
import { StatusPage } from '@alliance-droid/status-feedback-system';
|
|
94
|
-
</script>
|
|
95
|
-
|
|
96
|
-
<!-- Self-fetching through the same-origin proxy -->
|
|
97
|
-
<StatusPage
|
|
98
|
-
projectName="My App"
|
|
99
|
-
apiUrl="/svc/feedback"
|
|
100
|
-
/>
|
|
101
|
-
|
|
102
|
-
<!-- Pass-through mode -->
|
|
103
|
-
<StatusPage
|
|
104
|
-
projectName="My App"
|
|
105
|
-
systems={data.systems}
|
|
106
|
-
incidents={data.incidents}
|
|
107
|
-
statusHistory={data.statusHistory}
|
|
108
|
-
showUptime={true}
|
|
109
|
-
/>
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
| Prop | Type | Default | Description |
|
|
113
|
-
|------|------|---------|-------------|
|
|
114
|
-
| `projectName` | `string` | required | Display name in the header |
|
|
115
|
-
| `apiUrl` | `string` | — | Proxy URL or direct admin backend URL (self-fetching mode) |
|
|
116
|
-
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
117
|
-
| `systems` | `System[]` | — | Pass-through: system data |
|
|
118
|
-
| `incidents` | `Incident[]` | — | Pass-through: incident data |
|
|
119
|
-
| `statusHistory` | `Record<string, StatusHistoryEntry[]>` | — | Pass-through: uptime data |
|
|
120
|
-
| `showUptime` | `boolean` | `true` | Show 90-day uptime bars |
|
|
121
|
-
|
|
122
|
-
### StatusBanner
|
|
123
|
-
|
|
124
|
-
Compact inline status indicator — good for footers or nav bars.
|
|
125
|
-
|
|
126
|
-
```svelte
|
|
127
|
-
<StatusBanner
|
|
128
|
-
apiUrl="/svc/feedback"
|
|
129
|
-
statusUrl="/status"
|
|
130
|
-
/>
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
| Prop | Type | Default | Description |
|
|
134
|
-
|------|------|---------|-------------|
|
|
135
|
-
| `systems` | `System[]` | — | Pass-through mode |
|
|
136
|
-
| `apiUrl` | `string` | — | Proxy URL or direct admin backend URL |
|
|
137
|
-
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
138
|
-
| `statusUrl` | `string` | — | Links to full status page (renders as `<a>`) |
|
|
139
|
-
|
|
140
|
-
### FeedbackButton
|
|
141
|
-
|
|
142
|
-
Floating action button with an embedded feedback form. Fixed position, bottom corner.
|
|
143
|
-
|
|
144
|
-
```svelte
|
|
145
|
-
<FeedbackButton
|
|
146
|
-
apiUrl="/svc/feedback"
|
|
147
|
-
position="bottom-right"
|
|
148
|
-
categories={['Bug', 'Feature Request', 'General']}
|
|
149
|
-
userEmail={currentUser?.email}
|
|
150
|
-
/>
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
| Prop | Type | Default | Description |
|
|
154
|
-
|------|------|---------|-------------|
|
|
155
|
-
| `apiUrl` | `string` | required | Proxy URL or direct admin backend URL |
|
|
156
|
-
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
157
|
-
| `categories` | `string[]` | `['Bug', 'Feature Request', 'General']` | Category options |
|
|
158
|
-
| `userEmail` | `string` | — | Pre-fill email field |
|
|
159
|
-
| `position` | `'bottom-right' \| 'bottom-left'` | `'bottom-right'` | Button position |
|
|
160
|
-
|
|
161
|
-
### FeedbackForm
|
|
162
|
-
|
|
163
|
-
Standalone feedback form — embed it wherever you need it.
|
|
164
|
-
|
|
165
|
-
```svelte
|
|
166
|
-
<FeedbackForm
|
|
167
|
-
apiUrl="/svc/feedback"
|
|
168
|
-
categories={['Bug', 'Feature Request']}
|
|
169
|
-
userEmail={currentUser?.email}
|
|
170
|
-
onSuccess={() => toast('Thanks!')}
|
|
171
|
-
onError={(msg) => toast(msg, 'error')}
|
|
172
|
-
/>
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
| Prop | Type | Default | Description |
|
|
176
|
-
|------|------|---------|-------------|
|
|
177
|
-
| `apiUrl` | `string` | required | Proxy URL or direct admin backend URL |
|
|
178
|
-
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
179
|
-
| `categories` | `string[]` | `['Bug', 'Feature Request', 'General']` | Category options |
|
|
180
|
-
| `userEmail` | `string` | — | Pre-fill email field |
|
|
181
|
-
| `onSuccess` | `() => void` | — | Called after successful submission |
|
|
182
|
-
| `onError` | `(error: string) => void` | — | Called on submission error |
|
|
183
|
-
|
|
184
|
-
### Feedback board (composable)
|
|
185
|
-
|
|
186
|
-
> 📖 **Full integration guide:** [docs/custom-feedback-integration.md](docs/custom-feedback-integration.md) — composing the parts into your own shell, status management via `getAuthToken`, list restyling, and the not-configured fallback.
|
|
187
|
-
|
|
188
|
-
The board is composed from parts around **one shared state** (`createFeedbackBoard`), so it drops into your own layout/shell — app rails, breadcrumb, a right-hand detail panel, whatever you have.
|
|
189
|
-
|
|
190
|
-
```svelte
|
|
191
|
-
<script lang="ts">
|
|
192
|
-
import {
|
|
193
|
-
createFeedbackBoard,
|
|
194
|
-
createAuthTokenGetter,
|
|
195
|
-
FeedbackSidebar,
|
|
196
|
-
FeedbackList,
|
|
197
|
-
FeedbackDetail,
|
|
198
|
-
FeedbackSubmitForm,
|
|
199
|
-
type BoardItem,
|
|
200
|
-
} from '@alliance-droid/status-feedback-system';
|
|
201
|
-
|
|
202
|
-
let { data } = $props();
|
|
203
|
-
const board = createFeedbackBoard({
|
|
204
|
-
apiUrl: '/svc/feedback',
|
|
205
|
-
userEmail: data.user?.email,
|
|
206
|
-
getAuthToken: createAuthTokenGetter(), // optional — enables status management
|
|
207
|
-
categoryConfig: {
|
|
208
|
-
Bug: { icon: 'fa-solid fa-bug', label: 'Bugs' },
|
|
209
|
-
'Feature Request': { icon: 'fa-solid fa-lightbulb', label: 'Ideas' },
|
|
210
|
-
},
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
let selected = $state<BoardItem | null>(null);
|
|
214
|
-
</script>
|
|
215
|
-
|
|
216
|
-
<YourSidebar><FeedbackSidebar {board} /></YourSidebar>
|
|
217
|
-
<YourMain><FeedbackList {board} onSelect={(i) => (selected = i)} /></YourMain>
|
|
218
|
-
<YourRail>
|
|
219
|
-
{#if selected}<FeedbackDetail {board} item={selected} />{/if}
|
|
220
|
-
</YourRail>
|
|
221
|
-
<!-- Render <FeedbackSubmitForm {board} /> wherever you want submission. -->
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
`createFeedbackBoard(options)` returns a reactive `FeedbackBoardState` (items, categories, filters, voting, `loadTransitions`/`setStatus`, …) shared by every sub-component.
|
|
225
|
-
|
|
226
|
-
| Option | Type | Default | Description |
|
|
227
|
-
|--------|------|---------|-------------|
|
|
228
|
-
| `apiUrl` | `string` | required | Proxy URL or direct admin backend URL |
|
|
229
|
-
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
230
|
-
| `userEmail` | `string` | — | Authenticated user (required to submit feedback/replies) |
|
|
231
|
-
| `categoryConfig` | `Record<string, { label?, icon?, description? }>` | `{}` | Relabel / add icons to sidebar categories |
|
|
232
|
-
| `getAuthToken` | `() => string \| Promise<string \| null>` | — | Bearer token so privileged users can set status (see below) |
|
|
233
|
-
| `pageSize` | `number` | `50` | Items per page |
|
|
234
|
-
|
|
235
|
-
Exported parts: `createFeedbackBoard`, `FeedbackSidebar`, `FeedbackList`, `FeedbackItem`, `FeedbackDetail`, `FeedbackSubmitForm`.
|
|
236
|
-
|
|
237
|
-
Categories are fixed by the system (**Bug**, **Feature Request**) — `categoryConfig` only relabels or adds icons.
|
|
238
|
-
|
|
239
|
-
#### Setting status (
|
|
240
|
-
|
|
241
|
-
When you pass `getAuthToken`,
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
-
|
|
248
|
-
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
|
277
|
-
|
|
278
|
-
| `
|
|
279
|
-
| `
|
|
280
|
-
| `
|
|
281
|
-
| `
|
|
282
|
-
| `
|
|
283
|
-
| `
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const {
|
|
302
|
-
const
|
|
303
|
-
const {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
1
|
+
# @alliance-droid/status-feedback-system
|
|
2
|
+
|
|
3
|
+
Drop-in system status pages and user feedback components for SvelteKit apps.
|
|
4
|
+
|
|
5
|
+
This package provides UI components plus an optional SvelteKit server proxy for talking to a [status-feedback-admin](https://alliance.ghe.com/alliance/status-feedback-admin) backend. The admin app handles data storage, API key management, and the admin dashboard.
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
┌─────────────────────────┐ ┌──────────────────────────┐
|
|
11
|
+
│ Your SvelteKit App │ │ status-feedback-admin │
|
|
12
|
+
│ │ │ │
|
|
13
|
+
│ ┌───────────────────┐ │ API │ ┌────────────────────┐ │
|
|
14
|
+
│ │ StatusPage │──┼───────┼──│ /api/status │ │
|
|
15
|
+
│ │ FeedbackButton │ │ │ │ /api/feedback │ │
|
|
16
|
+
│ │ DevFeedback │ │ │ │ /api/incidents │ │
|
|
17
|
+
│ │ Feedback board │ │ │ │ /api/board │ │
|
|
18
|
+
│ │ (composable) │ │ │ │ │ │
|
|
19
|
+
│ └───────────────────┘ │ │ └────────────────────┘ │
|
|
20
|
+
│ │ │ │ │
|
|
21
|
+
│ npm package │ │ MSSQL + Azure Blob │
|
|
22
|
+
└─────────────────────────┘ └──────────────────────────┘
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Setup
|
|
26
|
+
|
|
27
|
+
### 1. Deploy the admin backend
|
|
28
|
+
|
|
29
|
+
The admin app ([status-feedback-admin](https://alliance.ghe.com/alliance/status-feedback-admin)) provides the API, database, and admin dashboard. Deploy it first.
|
|
30
|
+
|
|
31
|
+
Required env vars on the admin app:
|
|
32
|
+
```bash
|
|
33
|
+
MSSQL_CONNECTION_STRING=Server=...;Initial Catalog=StatusFeedback;...
|
|
34
|
+
SESSION_SECRET=your-secret
|
|
35
|
+
|
|
36
|
+
# Optional — stores screenshots in Azure Blob Storage instead of base64 in DB
|
|
37
|
+
AZURE_FEEDBACK_BLOB_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=...
|
|
38
|
+
AZURE_FEEDBACK_BLOB_CONTAINER=screenshots
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. Create a project and keep the API key server-side
|
|
42
|
+
|
|
43
|
+
Log into the admin dashboard, create a project, and copy its API key (prefixed `sf_`). Store it in a private server-side environment variable, not a `PUBLIC_` / browser-delivered variable.
|
|
44
|
+
|
|
45
|
+
### 3. Install the client package
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm install @alliance-droid/status-feedback-system
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Peer dependencies:** `svelte ^5.48.0`, `@alliance-droid/svelte-component-library >=1.3.2`
|
|
52
|
+
|
|
53
|
+
> `@alliance-droid/svelte-component-library` must be `>=1.3.2` — earlier versions are incompatible with Svelte `5.56+` and throw `target.exclude.has is not a function` when board components render.
|
|
54
|
+
|
|
55
|
+
### 4. Add the recommended SvelteKit proxy
|
|
56
|
+
|
|
57
|
+
Use one catch-all route in the consuming app. The package owns the admin API path mapping, request validation, key injection, and `key` query stripping; the app owns auth and app-specific policy.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
// src/routes/svc/feedback/[...path]/+server.ts
|
|
61
|
+
import { env } from '$env/dynamic/private';
|
|
62
|
+
import { createSvelteKitFeedbackProxy } from '@alliance-droid/status-feedback-system/sveltekit/server';
|
|
63
|
+
|
|
64
|
+
export const { GET, POST, PATCH } = createSvelteKitFeedbackProxy({
|
|
65
|
+
apiUrl: env.SF_API_URL,
|
|
66
|
+
apiKey: env.SF_API_KEY,
|
|
67
|
+
resolveUser: async (event) => {
|
|
68
|
+
const user = event.locals.user;
|
|
69
|
+
return user ? { email: user.email, roles: user.roles } : null;
|
|
70
|
+
},
|
|
71
|
+
requireUser: true,
|
|
72
|
+
canSubmitInAppFeedback: ({ user }) =>
|
|
73
|
+
user.roles.includes('QA') || user.roles.includes('ProductOwner'),
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Then point components at the same-origin route and omit `apiKey`:
|
|
78
|
+
|
|
79
|
+
```svelte
|
|
80
|
+
<DevFeedback apiUrl="/svc/feedback/dev" enabled={canSubmitFeedback} userEmail={user.email} />
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Direct admin calls with `apiKey="sf_..."` remain supported for existing integrations, but the proxy is the hardened default.
|
|
84
|
+
|
|
85
|
+
## Components
|
|
86
|
+
|
|
87
|
+
### StatusPage
|
|
88
|
+
|
|
89
|
+
Full status page with systems, incidents, and uptime history bars. Supports **self-fetching** (provide `apiUrl`; add `apiKey` only for direct admin calls) or **pass-through** (provide data directly).
|
|
90
|
+
|
|
91
|
+
```svelte
|
|
92
|
+
<script>
|
|
93
|
+
import { StatusPage } from '@alliance-droid/status-feedback-system';
|
|
94
|
+
</script>
|
|
95
|
+
|
|
96
|
+
<!-- Self-fetching through the same-origin proxy -->
|
|
97
|
+
<StatusPage
|
|
98
|
+
projectName="My App"
|
|
99
|
+
apiUrl="/svc/feedback"
|
|
100
|
+
/>
|
|
101
|
+
|
|
102
|
+
<!-- Pass-through mode -->
|
|
103
|
+
<StatusPage
|
|
104
|
+
projectName="My App"
|
|
105
|
+
systems={data.systems}
|
|
106
|
+
incidents={data.incidents}
|
|
107
|
+
statusHistory={data.statusHistory}
|
|
108
|
+
showUptime={true}
|
|
109
|
+
/>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
| Prop | Type | Default | Description |
|
|
113
|
+
|------|------|---------|-------------|
|
|
114
|
+
| `projectName` | `string` | required | Display name in the header |
|
|
115
|
+
| `apiUrl` | `string` | — | Proxy URL or direct admin backend URL (self-fetching mode) |
|
|
116
|
+
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
117
|
+
| `systems` | `System[]` | — | Pass-through: system data |
|
|
118
|
+
| `incidents` | `Incident[]` | — | Pass-through: incident data |
|
|
119
|
+
| `statusHistory` | `Record<string, StatusHistoryEntry[]>` | — | Pass-through: uptime data |
|
|
120
|
+
| `showUptime` | `boolean` | `true` | Show 90-day uptime bars |
|
|
121
|
+
|
|
122
|
+
### StatusBanner
|
|
123
|
+
|
|
124
|
+
Compact inline status indicator — good for footers or nav bars.
|
|
125
|
+
|
|
126
|
+
```svelte
|
|
127
|
+
<StatusBanner
|
|
128
|
+
apiUrl="/svc/feedback"
|
|
129
|
+
statusUrl="/status"
|
|
130
|
+
/>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
| Prop | Type | Default | Description |
|
|
134
|
+
|------|------|---------|-------------|
|
|
135
|
+
| `systems` | `System[]` | — | Pass-through mode |
|
|
136
|
+
| `apiUrl` | `string` | — | Proxy URL or direct admin backend URL |
|
|
137
|
+
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
138
|
+
| `statusUrl` | `string` | — | Links to full status page (renders as `<a>`) |
|
|
139
|
+
|
|
140
|
+
### FeedbackButton
|
|
141
|
+
|
|
142
|
+
Floating action button with an embedded feedback form. Fixed position, bottom corner.
|
|
143
|
+
|
|
144
|
+
```svelte
|
|
145
|
+
<FeedbackButton
|
|
146
|
+
apiUrl="/svc/feedback"
|
|
147
|
+
position="bottom-right"
|
|
148
|
+
categories={['Bug', 'Feature Request', 'General']}
|
|
149
|
+
userEmail={currentUser?.email}
|
|
150
|
+
/>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
| Prop | Type | Default | Description |
|
|
154
|
+
|------|------|---------|-------------|
|
|
155
|
+
| `apiUrl` | `string` | required | Proxy URL or direct admin backend URL |
|
|
156
|
+
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
157
|
+
| `categories` | `string[]` | `['Bug', 'Feature Request', 'General']` | Category options |
|
|
158
|
+
| `userEmail` | `string` | — | Pre-fill email field |
|
|
159
|
+
| `position` | `'bottom-right' \| 'bottom-left'` | `'bottom-right'` | Button position |
|
|
160
|
+
|
|
161
|
+
### FeedbackForm
|
|
162
|
+
|
|
163
|
+
Standalone feedback form — embed it wherever you need it.
|
|
164
|
+
|
|
165
|
+
```svelte
|
|
166
|
+
<FeedbackForm
|
|
167
|
+
apiUrl="/svc/feedback"
|
|
168
|
+
categories={['Bug', 'Feature Request']}
|
|
169
|
+
userEmail={currentUser?.email}
|
|
170
|
+
onSuccess={() => toast('Thanks!')}
|
|
171
|
+
onError={(msg) => toast(msg, 'error')}
|
|
172
|
+
/>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
| Prop | Type | Default | Description |
|
|
176
|
+
|------|------|---------|-------------|
|
|
177
|
+
| `apiUrl` | `string` | required | Proxy URL or direct admin backend URL |
|
|
178
|
+
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
179
|
+
| `categories` | `string[]` | `['Bug', 'Feature Request', 'General']` | Category options |
|
|
180
|
+
| `userEmail` | `string` | — | Pre-fill email field |
|
|
181
|
+
| `onSuccess` | `() => void` | — | Called after successful submission |
|
|
182
|
+
| `onError` | `(error: string) => void` | — | Called on submission error |
|
|
183
|
+
|
|
184
|
+
### Feedback board (composable)
|
|
185
|
+
|
|
186
|
+
> 📖 **Full integration guide:** [docs/custom-feedback-integration.md](docs/custom-feedback-integration.md) — composing the parts into your own shell, status management via `getAuthToken`, list restyling, and the not-configured fallback.
|
|
187
|
+
|
|
188
|
+
The board is composed from parts around **one shared state** (`createFeedbackBoard`), so it drops into your own layout/shell — app rails, breadcrumb, a right-hand detail panel, whatever you have.
|
|
189
|
+
|
|
190
|
+
```svelte
|
|
191
|
+
<script lang="ts">
|
|
192
|
+
import {
|
|
193
|
+
createFeedbackBoard,
|
|
194
|
+
createAuthTokenGetter,
|
|
195
|
+
FeedbackSidebar,
|
|
196
|
+
FeedbackList,
|
|
197
|
+
FeedbackDetail,
|
|
198
|
+
FeedbackSubmitForm,
|
|
199
|
+
type BoardItem,
|
|
200
|
+
} from '@alliance-droid/status-feedback-system';
|
|
201
|
+
|
|
202
|
+
let { data } = $props();
|
|
203
|
+
const board = createFeedbackBoard({
|
|
204
|
+
apiUrl: '/svc/feedback',
|
|
205
|
+
userEmail: data.user?.email,
|
|
206
|
+
getAuthToken: createAuthTokenGetter(), // optional — enables status management
|
|
207
|
+
categoryConfig: {
|
|
208
|
+
Bug: { icon: 'fa-solid fa-bug', label: 'Bugs' },
|
|
209
|
+
'Feature Request': { icon: 'fa-solid fa-lightbulb', label: 'Ideas' },
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
let selected = $state<BoardItem | null>(null);
|
|
214
|
+
</script>
|
|
215
|
+
|
|
216
|
+
<YourSidebar><FeedbackSidebar {board} /></YourSidebar>
|
|
217
|
+
<YourMain><FeedbackList {board} onSelect={(i) => (selected = i)} /></YourMain>
|
|
218
|
+
<YourRail>
|
|
219
|
+
{#if selected}<FeedbackDetail {board} item={selected} />{/if}
|
|
220
|
+
</YourRail>
|
|
221
|
+
<!-- Render <FeedbackSubmitForm {board} /> wherever you want submission. -->
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`createFeedbackBoard(options)` returns a reactive `FeedbackBoardState` (items, categories, filters, voting, `loadTransitions`/`setStatus`, …) shared by every sub-component.
|
|
225
|
+
|
|
226
|
+
| Option | Type | Default | Description |
|
|
227
|
+
|--------|------|---------|-------------|
|
|
228
|
+
| `apiUrl` | `string` | required | Proxy URL or direct admin backend URL |
|
|
229
|
+
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
230
|
+
| `userEmail` | `string` | — | Authenticated user (required to submit feedback/replies) |
|
|
231
|
+
| `categoryConfig` | `Record<string, { label?, icon?, description? }>` | `{}` | Relabel / add icons to sidebar categories |
|
|
232
|
+
| `getAuthToken` | `() => string \| Promise<string \| null>` | — | Bearer token so privileged users can set status (see below) |
|
|
233
|
+
| `pageSize` | `number` | `50` | Items per page |
|
|
234
|
+
|
|
235
|
+
Exported parts: `createFeedbackBoard`, `FeedbackSidebar`, `FeedbackList`, `FeedbackItem`, `FeedbackDetail`, `FeedbackSubmitForm`.
|
|
236
|
+
|
|
237
|
+
Categories are fixed by the system (**Bug**, **Feature Request**) — `categoryConfig` only relabels or adds icons.
|
|
238
|
+
|
|
239
|
+
#### Setting status (ProductOwners and Admins)
|
|
240
|
+
|
|
241
|
+
When you pass `getAuthToken`, **Admins** and **ProductOwners with project access** can accept, reopen, or decline feedback (Won't do, Duplicate, Can't reproduce), where the current status permits the decision. Reopens and declines require a reason. **Only acceptance starts automation**; engineering, QA approval, release, and completion transitions remain in the admin app.
|
|
242
|
+
|
|
243
|
+
Editable items have **purple status controls and row accents**, accompanied by a text cue. The sidebar's **Needs my input** filter shows Open, Considering, and Waiting on reply, filtered on the server across all pages. Everyone continues to see each item's granular status.
|
|
244
|
+
|
|
245
|
+
Deploy the matching admin-server capabilities update first. The packaged proxy forwards the optional board bearer token; custom proxies must also forward `Authorization` on board reads and preserve `status=needs_input`. Custom lists can use the backend's per-item `canChangeStatus` and `needsInput` fields with `board.canManageStatus`.
|
|
246
|
+
|
|
247
|
+
- `getAuthToken` must return the user's **Alliance OIDC access token** whose audience covers **`status-feedback-admin`** (an *id token*, whose `aud` is your app's client id, is rejected). It's called on demand; return `null` for anonymous / non-privileged users and the control simply won't appear — the badge stays a plain read-only badge.
|
|
248
|
+
- **Use the built-in `createAuthTokenGetter()`** for this — it fetches a same-origin token endpoint (`/auth/access-token` by default), caches until just before expiry, single-flights, and returns `null` on any failure. With `@alliance-droid/svelte-auth-core`, set `enableAccessTokenEndpoint: true` and request the `status-feedback-admin` scope so that endpoint exists. Create one instance at module level: `export const getAuthToken = createAuthTokenGetter();`
|
|
249
|
+
- The token travels as `Authorization: Bearer …` to the admin backend, which **verifies it against the IdP** and derives the user's roles. Authorization (Admin or ProductOwner role + per-project access + the restricted board transitions, including reason prompts on declines/reopens) is enforced **server-side** — the client never decides permissions. The backend returns a `canManage` flag (HTTP 200, not 403) so ordinary signed-in users get no console noise.
|
|
250
|
+
- The available transitions are returned by the backend per item, so the menu always reflects exactly what that user may do.
|
|
251
|
+
|
|
252
|
+
> Security note: use the SvelteKit proxy to keep the project `apiKey` out of browser-delivered code. Direct admin calls with an embedded key are still supported for existing integrations, but same-origin proxy mode is the recommended default.
|
|
253
|
+
|
|
254
|
+
### DevFeedback
|
|
255
|
+
|
|
256
|
+
Feedback overlay for authorized users. Activated via **Ctrl+right-click** (Cmd+right-click on macOS) anywhere on the page.
|
|
257
|
+
|
|
258
|
+
Auto-captures: screenshots (via html2canvas), console errors, viewport size, current route, clicked element selector, and deployment context (Vercel env vars).
|
|
259
|
+
|
|
260
|
+
**Gating:** the widget shows only when `enabled` is `true` **and** `userEmail` is non-empty. `enabled` is the single gate — wire it to your own rule, typically a role/permission check. There is no environment auto-detection. See [docs/role-based-access.md](docs/role-based-access.md) for the recommended role-based setup and the pitfalls.
|
|
261
|
+
|
|
262
|
+
```svelte
|
|
263
|
+
<script>
|
|
264
|
+
import { DevFeedback } from '@alliance-droid/status-feedback-system';
|
|
265
|
+
</script>
|
|
266
|
+
|
|
267
|
+
<!-- In your root +layout.svelte -->
|
|
268
|
+
<DevFeedback
|
|
269
|
+
apiUrl="/svc/feedback/dev"
|
|
270
|
+
boardUrl="/feedback"
|
|
271
|
+
userEmail={user?.email ?? ''}
|
|
272
|
+
enabled={userCanGiveFeedback}
|
|
273
|
+
/>
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
| Prop | Type | Default | Description |
|
|
277
|
+
|------|------|---------|-------------|
|
|
278
|
+
| `apiUrl` | `string` | required | Proxy URL or direct admin backend URL. Use `/svc/feedback/dev` with the package proxy for stricter widget policy. |
|
|
279
|
+
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
280
|
+
| `enabled` | `boolean` | required | The only gate — wire to your role/permission check |
|
|
281
|
+
| `userEmail` | `string` | required | Signed-in user's email. Empty string ⇒ renders nothing (anonymous feedback unsupported) |
|
|
282
|
+
| `boardUrl` | `string` | — | URL for "View Open Issues" link. Hidden if omitted. |
|
|
283
|
+
| `maxConsoleErrors` | `number` | `20` | Max console errors to capture |
|
|
284
|
+
| `showOnboarding` | `boolean` | `true` | Show "Ctrl+right-click" toast on first visit |
|
|
285
|
+
| `renderTokenEndpoint` | `string` | `/svc/internal/issue-render-token` | App endpoint that mints render tokens for screenshot capture |
|
|
286
|
+
|
|
287
|
+
**Screenshot storage:** If the admin backend has `AZURE_FEEDBACK_BLOB_CONNECTION_STRING` configured, screenshots are uploaded to Azure Blob Storage. Otherwise they're stored as base64 in the database.
|
|
288
|
+
|
|
289
|
+
## API Client
|
|
290
|
+
|
|
291
|
+
All components use a shared API client. You can also use it directly:
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
import { createStatusClient } from '@alliance-droid/status-feedback-system';
|
|
295
|
+
|
|
296
|
+
const client = createStatusClient({
|
|
297
|
+
apiUrl: '/svc/feedback',
|
|
298
|
+
fetch: event.fetch, // Optional: pass SvelteKit's fetch for SSR
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const { systems, overallStatus } = await client.getStatus();
|
|
302
|
+
const { incidents } = await client.getIncidents({ limit: 5, includeResolved: true });
|
|
303
|
+
const { history } = await client.getUptime({ days: 90 });
|
|
304
|
+
const feedback = await client.submitFeedback({ message: 'Great product!' });
|
|
305
|
+
const { items, total, categories } = await client.getBoard({ sort: 'votes', status: 'open' });
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Shared Components
|
|
309
|
+
|
|
310
|
+
Lower-level building blocks used by StatusPage — exported for custom layouts:
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
import {
|
|
314
|
+
StatusIndicator, // Status dot + label (operational/degraded/outage/maintenance)
|
|
315
|
+
UptimeBar, // 90-day uptime bar chart
|
|
316
|
+
IncidentTimeline, // Chronological incident updates
|
|
317
|
+
SystemCard, // Single system status card with uptime bar
|
|
318
|
+
} from '@alliance-droid/status-feedback-system';
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## All Exports
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
// Components
|
|
325
|
+
import {
|
|
326
|
+
StatusPage, StatusBanner, FeedbackForm, FeedbackButton, DevFeedback,
|
|
327
|
+
StatusIndicator, UptimeBar, IncidentTimeline, SystemCard, PasteImageInput,
|
|
328
|
+
} from '@alliance-droid/status-feedback-system';
|
|
329
|
+
|
|
330
|
+
// Feedback board (composable)
|
|
331
|
+
import {
|
|
332
|
+
createFeedbackBoard,
|
|
333
|
+
createAuthTokenGetter,
|
|
334
|
+
FeedbackSidebar, FeedbackList, FeedbackItem, FeedbackDetail, FeedbackSubmitForm,
|
|
335
|
+
} from '@alliance-droid/status-feedback-system';
|
|
336
|
+
import type {
|
|
337
|
+
FeedbackBoardState, FeedbackBoardOptions, CategoryConfig,
|
|
338
|
+
AuthTokenGetter, AuthTokenGetterOptions,
|
|
339
|
+
} from '@alliance-droid/status-feedback-system';
|
|
340
|
+
|
|
341
|
+
// API Client
|
|
342
|
+
import { createStatusClient } from '@alliance-droid/status-feedback-system';
|
|
343
|
+
import type {
|
|
344
|
+
StatusClient, StatusClientConfig,
|
|
345
|
+
StatusData, IncidentData, UptimeData, BoardData, BoardItem, BoardCategory,
|
|
346
|
+
} from '@alliance-droid/status-feedback-system';
|
|
347
|
+
|
|
348
|
+
// SvelteKit server proxy
|
|
349
|
+
import {
|
|
350
|
+
createSvelteKitFeedbackProxy,
|
|
351
|
+
isFeedbackProxyConfigured,
|
|
352
|
+
} from '@alliance-droid/status-feedback-system/sveltekit/server';
|
|
353
|
+
|
|
354
|
+
// Utilities
|
|
355
|
+
import { deriveOverallStatus } from '@alliance-droid/status-feedback-system';
|
|
356
|
+
|
|
357
|
+
// Types
|
|
358
|
+
import type {
|
|
359
|
+
Project, System, Incident, IncidentUpdate, Feedback, FeedbackResponse,
|
|
360
|
+
FeedbackVisibility, StatusHistoryEntry, ApiResponse,
|
|
361
|
+
SystemStatus, IncidentStatus, IncidentSeverity, FeedbackStatus,
|
|
362
|
+
} from '@alliance-droid/status-feedback-system';
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## License
|
|
366
|
+
|
|
367
|
+
Copyright © 2026 Alliance Technical Group. All rights reserved.
|