@alliance-droid/status-feedback-system 4.2.2 → 4.2.4
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 +34 -22
- package/README.md +101 -101
- package/dist/components/DevFeedback.svelte +7 -7
- package/dist/components/FeedbackButton.svelte +8 -8
- package/dist/components/FeedbackForm.svelte +8 -8
- package/dist/components/StatusBanner.svelte +21 -21
- package/dist/components/StatusPage.svelte +16 -16
- package/dist/components/board/FeedbackDetail.svelte +22 -6
- package/dist/components/board/StatusBadgeMenu.svelte +8 -11
- package/package.json +12 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,31 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 4.2.
|
|
3
|
+
## 4.2.4
|
|
4
4
|
|
|
5
5
|
### Fixed
|
|
6
6
|
|
|
7
|
-
-
|
|
7
|
+
- `<FeedbackDetail>` now reloads responses when consumers keep the detail panel mounted and select a different feedback item. The previous item's responses are cleared while the new thread loads, and stale in-flight requests can no longer overwrite the currently selected item's responses.
|
|
8
8
|
|
|
9
|
-
## 4.2.
|
|
10
|
-
|
|
11
|
-
###
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
## 4.2.
|
|
16
|
-
|
|
17
|
-
###
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
9
|
+
## 4.2.3
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Board status badges now show each item's **real, granular status to every viewer** — not just ProductOwners. Previously non-privileged viewers saw only the coarse public bucket ("In progress") for every internal status, and ProductOwners saw the real status but only after the privilege probe resolved. Each board item already carries its internal `status` from the API, so the badge now renders `internalStatusLabel(item.status)` for everyone on first paint (no privilege check, no per-item transitions fetch, no delay). **Changing** status remains ProductOwner-only. Note: this makes the granular internal statuses (e.g. "QA / UAT", "Technical review") visible to anyone who can see the board.
|
|
14
|
+
|
|
15
|
+
## 4.2.2
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- SvelteKit feedback proxy: read-only board status transition probes now return `200` with `canManage: false` when the browser has no usable management bearer token, avoiding noisy console `401` / `403` errors. Status mutations still require and forward a valid bearer token.
|
|
20
|
+
|
|
21
|
+
## 4.2.1
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- SvelteKit feedback proxy: private-app authentication/authorization now runs before missing proxy configuration is reported, so unauthenticated callers receive `401` instead of learning whether `SF_API_URL` / `SF_API_KEY` are configured.
|
|
26
|
+
|
|
27
|
+
## 4.2.0
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- Added `@alliance-droid/status-feedback-system/sveltekit/server`, a server-only SvelteKit proxy adapter that lets consuming apps keep the project API key in private server environment variables. The adapter maps the package's existing `/api/...` client paths to `status-feedback-admin`, injects the private `x-api-key`, strips browser `key` query params, validates request bodies, forwards bearer tokens for privileged status endpoints, and can derive feedback `email` / response `author` from the host app's authenticated user.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- `apiKey` is now optional for `createStatusClient`, `StatusPage`, `StatusBanner`, `FeedbackForm`, `FeedbackButton`, `DevFeedback`, and `createFeedbackBoard`. When omitted, the client no longer appends `?key=...`, which enables same-origin proxy mode with `apiUrl="/svc/feedback"`.
|
|
36
|
+
- Updated README and integration docs to make the SvelteKit proxy the recommended hardened default. Direct admin calls with an embedded project key remain supported for existing consumers.
|
|
37
|
+
|
|
38
|
+
## 4.1.6
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
29
41
|
|
|
30
42
|
- Board status badges: for privileged viewers (Admin / ProductOwner), **every** item now shows its real internal status (e.g. "QA / UAT"), not just the first one. Previously the list badge derived its label solely from the per-item transitions cache, which is only populated lazily (on menu open) plus a one-time privilege probe of `items[0]` — so exactly the first item showed its real status and the rest stayed on the coarse public projection ("In progress"). The badge now derives the privileged label from each item's own `status` (already present on every board item) via a new `internalStatusLabel`, so all rows resolve consistently once privilege is confirmed. Non-privileged viewers are unchanged (they still see the public status bucket).
|
|
31
43
|
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Drop-in system status pages and user feedback components for SvelteKit apps.
|
|
4
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.
|
|
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
6
|
|
|
7
7
|
## Architecture
|
|
8
8
|
|
|
@@ -38,66 +38,66 @@ AZURE_FEEDBACK_BLOB_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName
|
|
|
38
38
|
AZURE_FEEDBACK_BLOB_CONTAINER=screenshots
|
|
39
39
|
```
|
|
40
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.
|
|
41
|
+
### 2. Create a project and keep the API key server-side
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
```
|
|
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
50
|
|
|
51
51
|
**Peer dependencies:** `svelte ^5.48.0`, `@alliance-droid/svelte-component-library >=1.3.2`
|
|
52
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.
|
|
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
84
|
|
|
85
85
|
## Components
|
|
86
86
|
|
|
87
87
|
### StatusPage
|
|
88
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).
|
|
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
90
|
|
|
91
91
|
```svelte
|
|
92
92
|
<script>
|
|
93
93
|
import { StatusPage } from '@alliance-droid/status-feedback-system';
|
|
94
94
|
</script>
|
|
95
95
|
|
|
96
|
-
<!-- Self-fetching through the same-origin proxy -->
|
|
97
|
-
<StatusPage
|
|
98
|
-
projectName="My App"
|
|
99
|
-
apiUrl="/svc/feedback"
|
|
100
|
-
/>
|
|
96
|
+
<!-- Self-fetching through the same-origin proxy -->
|
|
97
|
+
<StatusPage
|
|
98
|
+
projectName="My App"
|
|
99
|
+
apiUrl="/svc/feedback"
|
|
100
|
+
/>
|
|
101
101
|
|
|
102
102
|
<!-- Pass-through mode -->
|
|
103
103
|
<StatusPage
|
|
@@ -112,8 +112,8 @@ Full status page with systems, incidents, and uptime history bars. Supports **se
|
|
|
112
112
|
| Prop | Type | Default | Description |
|
|
113
113
|
|------|------|---------|-------------|
|
|
114
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 |
|
|
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
117
|
| `systems` | `System[]` | — | Pass-through: system data |
|
|
118
118
|
| `incidents` | `Incident[]` | — | Pass-through: incident data |
|
|
119
119
|
| `statusHistory` | `Record<string, StatusHistoryEntry[]>` | — | Pass-through: uptime data |
|
|
@@ -124,17 +124,17 @@ Full status page with systems, incidents, and uptime history bars. Supports **se
|
|
|
124
124
|
Compact inline status indicator — good for footers or nav bars.
|
|
125
125
|
|
|
126
126
|
```svelte
|
|
127
|
-
<StatusBanner
|
|
128
|
-
apiUrl="/svc/feedback"
|
|
129
|
-
statusUrl="/status"
|
|
130
|
-
/>
|
|
127
|
+
<StatusBanner
|
|
128
|
+
apiUrl="/svc/feedback"
|
|
129
|
+
statusUrl="/status"
|
|
130
|
+
/>
|
|
131
131
|
```
|
|
132
132
|
|
|
133
133
|
| Prop | Type | Default | Description |
|
|
134
134
|
|------|------|---------|-------------|
|
|
135
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 |
|
|
136
|
+
| `apiUrl` | `string` | — | Proxy URL or direct admin backend URL |
|
|
137
|
+
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
138
138
|
| `statusUrl` | `string` | — | Links to full status page (renders as `<a>`) |
|
|
139
139
|
|
|
140
140
|
### FeedbackButton
|
|
@@ -142,18 +142,18 @@ Compact inline status indicator — good for footers or nav bars.
|
|
|
142
142
|
Floating action button with an embedded feedback form. Fixed position, bottom corner.
|
|
143
143
|
|
|
144
144
|
```svelte
|
|
145
|
-
<FeedbackButton
|
|
146
|
-
apiUrl="/svc/feedback"
|
|
147
|
-
position="bottom-right"
|
|
148
|
-
categories={['Bug', 'Feature Request', 'General']}
|
|
149
|
-
userEmail={currentUser?.email}
|
|
145
|
+
<FeedbackButton
|
|
146
|
+
apiUrl="/svc/feedback"
|
|
147
|
+
position="bottom-right"
|
|
148
|
+
categories={['Bug', 'Feature Request', 'General']}
|
|
149
|
+
userEmail={currentUser?.email}
|
|
150
150
|
/>
|
|
151
151
|
```
|
|
152
152
|
|
|
153
153
|
| Prop | Type | Default | Description |
|
|
154
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 |
|
|
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
157
|
| `categories` | `string[]` | `['Bug', 'Feature Request', 'General']` | Category options |
|
|
158
158
|
| `userEmail` | `string` | — | Pre-fill email field |
|
|
159
159
|
| `position` | `'bottom-right' \| 'bottom-left'` | `'bottom-right'` | Button position |
|
|
@@ -163,10 +163,10 @@ Floating action button with an embedded feedback form. Fixed position, bottom co
|
|
|
163
163
|
Standalone feedback form — embed it wherever you need it.
|
|
164
164
|
|
|
165
165
|
```svelte
|
|
166
|
-
<FeedbackForm
|
|
167
|
-
apiUrl="/svc/feedback"
|
|
168
|
-
categories={['Bug', 'Feature Request']}
|
|
169
|
-
userEmail={currentUser?.email}
|
|
166
|
+
<FeedbackForm
|
|
167
|
+
apiUrl="/svc/feedback"
|
|
168
|
+
categories={['Bug', 'Feature Request']}
|
|
169
|
+
userEmail={currentUser?.email}
|
|
170
170
|
onSuccess={() => toast('Thanks!')}
|
|
171
171
|
onError={(msg) => toast(msg, 'error')}
|
|
172
172
|
/>
|
|
@@ -174,8 +174,8 @@ Standalone feedback form — embed it wherever you need it.
|
|
|
174
174
|
|
|
175
175
|
| Prop | Type | Default | Description |
|
|
176
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 |
|
|
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
179
|
| `categories` | `string[]` | `['Bug', 'Feature Request', 'General']` | Category options |
|
|
180
180
|
| `userEmail` | `string` | — | Pre-fill email field |
|
|
181
181
|
| `onSuccess` | `() => void` | — | Called after successful submission |
|
|
@@ -199,11 +199,11 @@ The board is composed from parts around **one shared state** (`createFeedbackBoa
|
|
|
199
199
|
type BoardItem,
|
|
200
200
|
} from '@alliance-droid/status-feedback-system';
|
|
201
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
|
|
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
207
|
categoryConfig: {
|
|
208
208
|
Bug: { icon: 'fa-solid fa-bug', label: 'Bugs' },
|
|
209
209
|
'Feature Request': { icon: 'fa-solid fa-lightbulb', label: 'Ideas' },
|
|
@@ -225,8 +225,8 @@ The board is composed from parts around **one shared state** (`createFeedbackBoa
|
|
|
225
225
|
|
|
226
226
|
| Option | Type | Default | Description |
|
|
227
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 |
|
|
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
230
|
| `userEmail` | `string` | — | Authenticated user (required to submit feedback/replies) |
|
|
231
231
|
| `categoryConfig` | `Record<string, { label?, icon?, description? }>` | `{}` | Relabel / add icons to sidebar categories |
|
|
232
232
|
| `getAuthToken` | `() => string \| Promise<string \| null>` | — | Bearer token so privileged users can set status (see below) |
|
|
@@ -247,7 +247,7 @@ Privileged viewers also see each item's **real internal status** on the badge (e
|
|
|
247
247
|
- The token travels as `Authorization: Bearer …` to the admin backend, which **verifies it against the IdP** and derives the user's roles. Authorization (ProductOwner role + per-project access + which transitions are allowed, 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.
|
|
248
248
|
- The available transitions are returned by the backend per item, so the menu always reflects exactly what that user may do.
|
|
249
249
|
|
|
250
|
-
> 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.
|
|
250
|
+
> 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.
|
|
251
251
|
|
|
252
252
|
### DevFeedback
|
|
253
253
|
|
|
@@ -263,18 +263,18 @@ Auto-captures: screenshots (via html2canvas), console errors, viewport size, cur
|
|
|
263
263
|
</script>
|
|
264
264
|
|
|
265
265
|
<!-- In your root +layout.svelte -->
|
|
266
|
-
<DevFeedback
|
|
267
|
-
apiUrl="/svc/feedback/dev"
|
|
268
|
-
boardUrl="/feedback"
|
|
269
|
-
userEmail={user?.email ?? ''}
|
|
270
|
-
enabled={userCanGiveFeedback}
|
|
266
|
+
<DevFeedback
|
|
267
|
+
apiUrl="/svc/feedback/dev"
|
|
268
|
+
boardUrl="/feedback"
|
|
269
|
+
userEmail={user?.email ?? ''}
|
|
270
|
+
enabled={userCanGiveFeedback}
|
|
271
271
|
/>
|
|
272
272
|
```
|
|
273
273
|
|
|
274
274
|
| Prop | Type | Default | Description |
|
|
275
275
|
|------|------|---------|-------------|
|
|
276
|
-
| `apiUrl` | `string` | required | Proxy URL or direct admin backend URL. Use `/svc/feedback/dev` with the package proxy for stricter widget policy. |
|
|
277
|
-
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
276
|
+
| `apiUrl` | `string` | required | Proxy URL or direct admin backend URL. Use `/svc/feedback/dev` with the package proxy for stricter widget policy. |
|
|
277
|
+
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
278
278
|
| `enabled` | `boolean` | required | The only gate — wire to your role/permission check |
|
|
279
279
|
| `userEmail` | `string` | required | Signed-in user's email. Empty string ⇒ renders nothing (anonymous feedback unsupported) |
|
|
280
280
|
| `boardUrl` | `string` | — | URL for "View Open Issues" link. Hidden if omitted. |
|
|
@@ -291,10 +291,10 @@ All components use a shared API client. You can also use it directly:
|
|
|
291
291
|
```typescript
|
|
292
292
|
import { createStatusClient } from '@alliance-droid/status-feedback-system';
|
|
293
293
|
|
|
294
|
-
const client = createStatusClient({
|
|
295
|
-
apiUrl: '/svc/feedback',
|
|
296
|
-
fetch: event.fetch, // Optional: pass SvelteKit's fetch for SSR
|
|
297
|
-
});
|
|
294
|
+
const client = createStatusClient({
|
|
295
|
+
apiUrl: '/svc/feedback',
|
|
296
|
+
fetch: event.fetch, // Optional: pass SvelteKit's fetch for SSR
|
|
297
|
+
});
|
|
298
298
|
|
|
299
299
|
const { systems, overallStatus } = await client.getStatus();
|
|
300
300
|
const { incidents } = await client.getIncidents({ limit: 5, includeResolved: true });
|
|
@@ -336,21 +336,21 @@ import type {
|
|
|
336
336
|
AuthTokenGetter, AuthTokenGetterOptions,
|
|
337
337
|
} from '@alliance-droid/status-feedback-system';
|
|
338
338
|
|
|
339
|
-
// API Client
|
|
340
|
-
import { createStatusClient } from '@alliance-droid/status-feedback-system';
|
|
341
|
-
import type {
|
|
342
|
-
StatusClient, StatusClientConfig,
|
|
343
|
-
StatusData, IncidentData, UptimeData, BoardData, BoardItem, BoardCategory,
|
|
344
|
-
} from '@alliance-droid/status-feedback-system';
|
|
345
|
-
|
|
346
|
-
// SvelteKit server proxy
|
|
347
|
-
import {
|
|
348
|
-
createSvelteKitFeedbackProxy,
|
|
349
|
-
isFeedbackProxyConfigured,
|
|
350
|
-
} from '@alliance-droid/status-feedback-system/sveltekit/server';
|
|
351
|
-
|
|
352
|
-
// Utilities
|
|
353
|
-
import { deriveOverallStatus } from '@alliance-droid/status-feedback-system';
|
|
339
|
+
// API Client
|
|
340
|
+
import { createStatusClient } from '@alliance-droid/status-feedback-system';
|
|
341
|
+
import type {
|
|
342
|
+
StatusClient, StatusClientConfig,
|
|
343
|
+
StatusData, IncidentData, UptimeData, BoardData, BoardItem, BoardCategory,
|
|
344
|
+
} from '@alliance-droid/status-feedback-system';
|
|
345
|
+
|
|
346
|
+
// SvelteKit server proxy
|
|
347
|
+
import {
|
|
348
|
+
createSvelteKitFeedbackProxy,
|
|
349
|
+
isFeedbackProxyConfigured,
|
|
350
|
+
} from '@alliance-droid/status-feedback-system/sveltekit/server';
|
|
351
|
+
|
|
352
|
+
// Utilities
|
|
353
|
+
import { deriveOverallStatus } from '@alliance-droid/status-feedback-system';
|
|
354
354
|
|
|
355
355
|
// Types
|
|
356
356
|
import type {
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
getElementHtml,
|
|
21
21
|
} from './dev/environment.js';
|
|
22
22
|
|
|
23
|
-
interface Props {
|
|
24
|
-
apiUrl: string;
|
|
25
|
-
/** Omit when apiUrl points at a same-origin proxy. */
|
|
26
|
-
apiKey?: string;
|
|
23
|
+
interface Props {
|
|
24
|
+
apiUrl: string;
|
|
25
|
+
/** Omit when apiUrl points at a same-origin proxy. */
|
|
26
|
+
apiKey?: string;
|
|
27
27
|
/**
|
|
28
28
|
* The sole gate for the widget — wire it to your role/permission check
|
|
29
29
|
* (e.g. `enabled={userHasFeedbackRole}`). When false, nothing renders and
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
renderTokenEndpoint?: string;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
let {
|
|
56
|
-
apiUrl,
|
|
57
|
-
apiKey = '',
|
|
55
|
+
let {
|
|
56
|
+
apiUrl,
|
|
57
|
+
apiKey = '',
|
|
58
58
|
enabled,
|
|
59
59
|
maxConsoleErrors = 20,
|
|
60
60
|
showOnboarding = true,
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import FeedbackForm from './FeedbackForm.svelte';
|
|
3
|
-
|
|
4
|
-
interface Props {
|
|
5
|
-
/** Omit when apiUrl points at a same-origin proxy. */
|
|
6
|
-
apiKey?: string;
|
|
7
|
-
apiUrl: string;
|
|
2
|
+
import FeedbackForm from './FeedbackForm.svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
/** Omit when apiUrl points at a same-origin proxy. */
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
apiUrl: string;
|
|
8
8
|
categories?: string[];
|
|
9
9
|
userEmail?: string;
|
|
10
10
|
position?: 'bottom-right' | 'bottom-left';
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
let {
|
|
14
|
-
apiKey = '',
|
|
13
|
+
let {
|
|
14
|
+
apiKey = '',
|
|
15
15
|
apiUrl,
|
|
16
16
|
categories,
|
|
17
17
|
userEmail,
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { untrack } from 'svelte';
|
|
3
|
-
import { createStatusClient, type StatusClient } from '../api.js';
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
/** Omit when apiUrl points at a same-origin proxy. */
|
|
7
|
-
apiKey?: string;
|
|
8
|
-
apiUrl: string;
|
|
3
|
+
import { createStatusClient, type StatusClient } from '../api.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
/** Omit when apiUrl points at a same-origin proxy. */
|
|
7
|
+
apiKey?: string;
|
|
8
|
+
apiUrl: string;
|
|
9
9
|
categories?: string[];
|
|
10
10
|
userEmail?: string;
|
|
11
11
|
onSuccess?: () => void;
|
|
12
12
|
onError?: (error: string) => void;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
let {
|
|
16
|
-
apiKey = '',
|
|
15
|
+
let {
|
|
16
|
+
apiKey = '',
|
|
17
17
|
apiUrl,
|
|
18
18
|
categories = ['Bug', 'Feature Request', 'General'],
|
|
19
19
|
userEmail,
|
|
@@ -10,35 +10,35 @@
|
|
|
10
10
|
systems?: System[];
|
|
11
11
|
/** API URL for self-fetching mode */
|
|
12
12
|
apiUrl?: string;
|
|
13
|
-
/** API key for direct admin calls. Omit when using a same-origin proxy. */
|
|
14
|
-
apiKey?: string;
|
|
13
|
+
/** API key for direct admin calls. Omit when using a same-origin proxy. */
|
|
14
|
+
apiKey?: string;
|
|
15
15
|
/** Link to full status page */
|
|
16
16
|
statusUrl?: string;
|
|
17
17
|
/** Auto-refresh interval in seconds. 0 = disabled. Default: 60 */
|
|
18
18
|
refreshInterval?: number;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
let { systems: systemsProp, apiUrl, apiKey, statusUrl, refreshInterval = 60 }: Props = $props();
|
|
22
|
-
|
|
23
|
-
let systems = $derived(systemsProp ?? []);
|
|
24
|
-
let loading = $derived(!systemsProp && !!apiUrl);
|
|
25
|
-
let fetchFailed = $state(false);
|
|
26
|
-
|
|
27
|
-
let usesApi = $derived(!systemsProp && !!apiUrl);
|
|
21
|
+
let { systems: systemsProp, apiUrl, apiKey, statusUrl, refreshInterval = 60 }: Props = $props();
|
|
22
|
+
|
|
23
|
+
let systems = $derived(systemsProp ?? []);
|
|
24
|
+
let loading = $derived(!systemsProp && !!apiUrl);
|
|
25
|
+
let fetchFailed = $state(false);
|
|
26
|
+
|
|
27
|
+
let usesApi = $derived(!systemsProp && !!apiUrl);
|
|
28
28
|
|
|
29
29
|
let cachedClient: StatusClient | null = null;
|
|
30
|
-
let cachedApiUrl = '';
|
|
31
|
-
let cachedApiKey = '';
|
|
32
|
-
|
|
33
|
-
function getClient(): StatusClient {
|
|
34
|
-
const nextApiKey = apiKey ?? '';
|
|
35
|
-
if (!cachedClient || cachedApiUrl !== apiUrl || cachedApiKey !== nextApiKey) {
|
|
36
|
-
cachedClient = createStatusClient({ apiUrl: apiUrl!, apiKey: nextApiKey });
|
|
37
|
-
cachedApiUrl = apiUrl!;
|
|
38
|
-
cachedApiKey = nextApiKey;
|
|
39
|
-
}
|
|
40
|
-
return cachedClient;
|
|
41
|
-
}
|
|
30
|
+
let cachedApiUrl = '';
|
|
31
|
+
let cachedApiKey = '';
|
|
32
|
+
|
|
33
|
+
function getClient(): StatusClient {
|
|
34
|
+
const nextApiKey = apiKey ?? '';
|
|
35
|
+
if (!cachedClient || cachedApiUrl !== apiUrl || cachedApiKey !== nextApiKey) {
|
|
36
|
+
cachedClient = createStatusClient({ apiUrl: apiUrl!, apiKey: nextApiKey });
|
|
37
|
+
cachedApiUrl = apiUrl!;
|
|
38
|
+
cachedApiKey = nextApiKey;
|
|
39
|
+
}
|
|
40
|
+
return cachedClient;
|
|
41
|
+
}
|
|
42
42
|
|
|
43
43
|
async function fetchData() {
|
|
44
44
|
if (!usesApi) return;
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
projectName: string;
|
|
13
13
|
/** API URL for fetching data (self-fetching mode) */
|
|
14
14
|
apiUrl?: string;
|
|
15
|
-
/** API key for direct admin calls. Omit when using a same-origin proxy. */
|
|
16
|
-
apiKey?: string;
|
|
15
|
+
/** API key for direct admin calls. Omit when using a same-origin proxy. */
|
|
16
|
+
apiKey?: string;
|
|
17
17
|
/** Systems data (pass-through mode — overrides API fetching) */
|
|
18
18
|
systems?: System[];
|
|
19
19
|
/** Incidents data (pass-through mode) */
|
|
@@ -44,22 +44,22 @@
|
|
|
44
44
|
let error = $state('');
|
|
45
45
|
|
|
46
46
|
// ── Self-fetching mode ──
|
|
47
|
-
const usesApi = $derived(!!apiUrl && !systemsProp);
|
|
48
|
-
let refreshTimer: ReturnType<typeof setInterval> | null = null;
|
|
47
|
+
const usesApi = $derived(!!apiUrl && !systemsProp);
|
|
48
|
+
let refreshTimer: ReturnType<typeof setInterval> | null = null;
|
|
49
49
|
|
|
50
50
|
let cachedClient: StatusClient | null = null;
|
|
51
|
-
let cachedApiUrl = '';
|
|
52
|
-
let cachedApiKey = '';
|
|
53
|
-
|
|
54
|
-
function getClient(): StatusClient {
|
|
55
|
-
const nextApiKey = apiKey ?? '';
|
|
56
|
-
if (!cachedClient || cachedApiUrl !== apiUrl || cachedApiKey !== nextApiKey) {
|
|
57
|
-
cachedClient = createStatusClient({ apiUrl: apiUrl!, apiKey: nextApiKey });
|
|
58
|
-
cachedApiUrl = apiUrl!;
|
|
59
|
-
cachedApiKey = nextApiKey;
|
|
60
|
-
}
|
|
61
|
-
return cachedClient;
|
|
62
|
-
}
|
|
51
|
+
let cachedApiUrl = '';
|
|
52
|
+
let cachedApiKey = '';
|
|
53
|
+
|
|
54
|
+
function getClient(): StatusClient {
|
|
55
|
+
const nextApiKey = apiKey ?? '';
|
|
56
|
+
if (!cachedClient || cachedApiUrl !== apiUrl || cachedApiKey !== nextApiKey) {
|
|
57
|
+
cachedClient = createStatusClient({ apiUrl: apiUrl!, apiKey: nextApiKey });
|
|
58
|
+
cachedApiUrl = apiUrl!;
|
|
59
|
+
cachedApiKey = nextApiKey;
|
|
60
|
+
}
|
|
61
|
+
return cachedClient;
|
|
62
|
+
}
|
|
63
63
|
|
|
64
64
|
async function fetchData() {
|
|
65
65
|
if (!usesApi) return;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import { Spinner, Button, Textarea, Divider } from '@alliance-droid/svelte-component-library';
|
|
2
|
+
import { Spinner, Button, Textarea, Divider } from '@alliance-droid/svelte-component-library';
|
|
4
3
|
import type { BoardItem } from '../../api.js';
|
|
5
4
|
import type { FeedbackResponse } from '../../types.js';
|
|
6
5
|
import type { FeedbackBoardState } from './state.svelte.js';
|
|
@@ -102,10 +101,27 @@
|
|
|
102
101
|
|
|
103
102
|
let isAuthenticated = $derived(!!board.userEmail);
|
|
104
103
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
$effect(() => {
|
|
105
|
+
const itemId = item.id;
|
|
106
|
+
let cancelled = false;
|
|
107
|
+
|
|
108
|
+
// Consumers commonly keep the detail rail mounted and replace its item prop.
|
|
109
|
+
// Clear the old thread immediately so it is never shown under the new item.
|
|
110
|
+
responses = [];
|
|
111
|
+
loadingResponses = true;
|
|
112
|
+
|
|
113
|
+
void board.getResponses(itemId).then((nextResponses) => {
|
|
114
|
+
// A slower request for the previous selection must not overwrite the
|
|
115
|
+
// currently selected item's responses.
|
|
116
|
+
if (cancelled) return;
|
|
117
|
+
responses = nextResponses;
|
|
118
|
+
loadingResponses = false;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return () => {
|
|
122
|
+
cancelled = true;
|
|
123
|
+
};
|
|
124
|
+
});
|
|
109
125
|
|
|
110
126
|
async function handleSubmitReply() {
|
|
111
127
|
if (!replyMessage.trim() || !isAuthenticated) return;
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
import { onMount } from 'svelte';
|
|
22
22
|
import type { BoardItem, BoardTransition } from '../../api.js';
|
|
23
23
|
import type { FeedbackBoardState } from './state.svelte.js';
|
|
24
|
-
import { getStatusBadgeVariant,
|
|
24
|
+
import { getStatusBadgeVariant, internalStatusLabel } from './utils.js';
|
|
25
25
|
|
|
26
26
|
interface Props {
|
|
27
27
|
board: FeedbackBoardState;
|
|
@@ -45,15 +45,12 @@
|
|
|
45
45
|
|
|
46
46
|
// Read from the board's shared cache so detail + list reuse the same fetch.
|
|
47
47
|
let txns = $derived(board.transitions[item.id] ?? null);
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
let badgeLabel = $derived(
|
|
54
|
-
txns?.current.display ??
|
|
55
|
-
(board.canManageStatus ? internalStatusLabel(item.status) : publicStatusLabel(item.publicStatus))
|
|
56
|
-
);
|
|
48
|
+
// Everyone sees the item's real granular status — `item.status` is present on
|
|
49
|
+
// every board item (the board endpoint returns it), so all rows render their
|
|
50
|
+
// actual status on first paint, no privilege or transitions fetch required.
|
|
51
|
+
// Only *changing* status is gated (below, on canManageStatus). The loaded
|
|
52
|
+
// transitions display, when present, is the same value and stays authoritative.
|
|
53
|
+
let badgeLabel = $derived(txns?.current.display ?? internalStatusLabel(item.status));
|
|
57
54
|
// Changes whenever the menu's content (and thus its size) changes — drives a
|
|
58
55
|
// reposition so the teleported menu stays anchored after it loads/expands.
|
|
59
56
|
let posKey = $derived(`${menuOpen}:${loading}:${pendingTarget?.status ?? ''}:${txns?.targets.length ?? 0}`);
|
|
@@ -269,6 +266,6 @@
|
|
|
269
266
|
{/if}
|
|
270
267
|
{:else}
|
|
271
268
|
<Badge variant={getStatusBadgeVariant(item.publicStatus)} size="sm">
|
|
272
|
-
{
|
|
269
|
+
{badgeLabel}
|
|
273
270
|
</Badge>
|
|
274
271
|
{/if}
|
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "@alliance-droid/status-feedback-system",
|
|
3
|
-
"version": "4.2.
|
|
2
|
+
"name": "@alliance-droid/status-feedback-system",
|
|
3
|
+
"version": "4.2.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Drop-in system status pages and user feedback forms for SvelteKit apps",
|
|
6
6
|
"svelte": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"types": "./dist/index.d.ts",
|
|
11
|
-
"svelte": "./dist/index.js"
|
|
12
|
-
},
|
|
13
|
-
"./sveltekit/server": {
|
|
14
|
-
"types": "./dist/sveltekit/server.d.ts",
|
|
15
|
-
"default": "./dist/sveltekit/server.js"
|
|
16
|
-
}
|
|
17
|
-
},
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"svelte": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./sveltekit/server": {
|
|
14
|
+
"types": "./dist/sveltekit/server.d.ts",
|
|
15
|
+
"default": "./dist/sveltekit/server.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
18
|
"files": [
|
|
19
19
|
"dist",
|
|
20
20
|
"!dist/**/*.test.*",
|