@adminforth/dashboard 1.9.0 → 1.11.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.
@@ -22,26 +22,320 @@ Dashboard root, groups, and widgets are different entities.
22
22
 
23
23
  ## Tool routing
24
24
 
25
+ - Get dashboard slugs: dashboard_get_slugs
25
26
  - Read dashboard: dashboard_get_config
26
27
  - Add group: dashboard_add_dashboard_group
27
28
  - Rename group: dashboard_set_dashboard_group_config
28
29
  - Add widget slot: dashboard_add_dashboard_widget
29
- - Configure table widget: dashboard_configure_table_widget
30
- - Configure KPI card widget: dashboard_configure_kpi_card_widget
31
- - Configure gauge card widget: dashboard_configure_gauge_card_widget
32
- - Configure line chart widget: dashboard_configure_line_chart_widget
33
- - Configure bar chart widget: dashboard_configure_bar_chart_widget
34
- - Configure stacked bar chart widget: dashboard_configure_stacked_bar_chart_widget
35
- - Configure pie chart widget: dashboard_configure_pie_chart_widget
36
- - Configure histogram chart widget: dashboard_configure_histogram_chart_widget
37
- - Configure funnel chart widget: dashboard_configure_funnel_chart_widget
38
- - Configure pivot table widget: dashboard_configure_pivot_table_widget
30
+ - Configure widget:
31
+ - table: dashboard_configure_table_widget
32
+ - kpi_card: dashboard_configure_kpi_card_widget
33
+ - gauge_card: dashboard_configure_gauge_card_widget
34
+ - pivot_table: dashboard_configure_pivot_table_widget
35
+ - line_chart: dashboard_configure_line_chart_widget
36
+ - bar_chart: dashboard_configure_bar_chart_widget
37
+ - stacked_bar_chart: dashboard_configure_stacked_bar_chart_widget
38
+ - pie_chart: dashboard_configure_pie_chart_widget
39
+ - histogram_chart: dashboard_configure_histogram_chart_widget
40
+ - funnel_chart: dashboard_configure_funnel_chart_widget
39
41
  - Move/remove widget/group: matching move/remove tool
40
42
  - Load widget data: dashboard_get_dashboard_widget_data
41
43
 
42
44
  If a known dashboard tool schema is missing, call fetch_tool_schema for that exact tool.
43
45
  If fetch_tool_schema returns but the intended tool is still not callable, stop and report a tool-routing error. Do not substitute another mutation tool.
44
46
 
47
+ ## Configure schema examples
48
+
49
+ These examples show the expected shape only. Do not copy them one-to-one: adapt resource names, fields, aggregations, labels, filters, formats, and calculations to the actual dashboard request and available resource columns.
50
+
51
+ Important:
52
+ - `config.target` is the widget target, never a resource path.
53
+ - Never use values like `/resource/llm_usage` in `config.target`.
54
+ - Put the data resource in `config.query.resource`, for example `query.resource: "llm_usage"`.
55
+ - For chart widgets, `config.target` is always `chart`; the concrete chart kind is `config.chart.type`.
56
+ - `query.calcs[].calc` is an expression over already selected fields/aliases, not raw SQL. Do not use SQL syntax such as `CASE WHEN`.
57
+
58
+ Example `dashboard_configure_table_widget` config:
59
+
60
+ ```yaml
61
+ target: table
62
+ label: Recent usage
63
+ size: wide
64
+ table:
65
+ columns:
66
+ - field: used_at
67
+ label: Date
68
+ - field: model
69
+ label: Model
70
+ - field: total_tokens
71
+ label: Tokens
72
+ format: integer
73
+ pagination: true
74
+ page_size: 20
75
+ query:
76
+ resource: llm_usage
77
+ select:
78
+ - field: used_at
79
+ - field: model
80
+ - field: total_tokens
81
+ order_by:
82
+ - field: used_at
83
+ direction: desc
84
+ ```
85
+
86
+ Example `dashboard_configure_kpi_card_widget` config:
87
+
88
+ ```yaml
89
+ target: kpi_card
90
+ label: Total spend
91
+ size: medium
92
+ card:
93
+ title: Total spend
94
+ value:
95
+ field: spend
96
+ format: currency
97
+ query:
98
+ resource: llm_usage
99
+ select:
100
+ - agg: sum
101
+ field: cost
102
+ as: spend
103
+ ```
104
+
105
+ Example `dashboard_configure_gauge_card_widget` config:
106
+
107
+ ```yaml
108
+ target: gauge_card
109
+ label: Budget usage
110
+ size: medium
111
+ card:
112
+ title: Budget usage
113
+ value:
114
+ field: spend
115
+ format: currency
116
+ progress:
117
+ value_field: spend
118
+ target_value: 1000
119
+ format: percent
120
+ query:
121
+ resource: llm_usage
122
+ select:
123
+ - agg: sum
124
+ field: cost
125
+ as: spend
126
+ ```
127
+
128
+ Example `dashboard_configure_pivot_table_widget` config:
129
+
130
+ ```yaml
131
+ target: pivot_table
132
+ label: Spend by model and purpose
133
+ size: wide
134
+ pivot:
135
+ rows:
136
+ - field: model
137
+ label: Model
138
+ columns:
139
+ - field: purpose
140
+ label: Purpose
141
+ values:
142
+ - field: spend
143
+ label: Spend
144
+ format: currency
145
+ aggregation: sum
146
+ query:
147
+ resource: llm_usage
148
+ select:
149
+ - field: model
150
+ - field: purpose
151
+ - agg: sum
152
+ field: cost
153
+ as: spend
154
+ group_by:
155
+ - model
156
+ - purpose
157
+ ```
158
+
159
+ Example `dashboard_configure_line_chart_widget` config:
160
+
161
+ ```yaml
162
+ target: chart
163
+ label: Daily spend
164
+ size: wide
165
+ chart:
166
+ type: line
167
+ x:
168
+ field: day
169
+ label: Day
170
+ y:
171
+ - field: spend
172
+ label: Spend
173
+ format: currency
174
+ query:
175
+ resource: llm_usage
176
+ select:
177
+ - field: used_at
178
+ grain: day
179
+ as: day
180
+ - agg: sum
181
+ field: cost
182
+ as: spend
183
+ group_by:
184
+ - field: used_at
185
+ grain: day
186
+ as: day
187
+ order_by:
188
+ - field: day
189
+ direction: asc
190
+ ```
191
+
192
+ Example `dashboard_configure_bar_chart_widget` config:
193
+
194
+ ```yaml
195
+ target: chart
196
+ label: Spend by model
197
+ size: wide
198
+ chart:
199
+ type: bar
200
+ x:
201
+ field: model
202
+ label: Model
203
+ y:
204
+ field: spend
205
+ label: Spend
206
+ format: currency
207
+ query:
208
+ resource: llm_usage
209
+ select:
210
+ - field: model
211
+ - agg: sum
212
+ field: cost
213
+ as: spend
214
+ group_by:
215
+ - model
216
+ ```
217
+
218
+ Example `dashboard_configure_stacked_bar_chart_widget` config:
219
+
220
+ ```yaml
221
+ target: chart
222
+ label: Daily spend by purpose
223
+ size: wide
224
+ chart:
225
+ type: stacked_bar
226
+ x:
227
+ field: day
228
+ label: Day
229
+ y:
230
+ field: spend
231
+ label: Spend
232
+ format: currency
233
+ series:
234
+ field: purpose
235
+ label: Purpose
236
+ query:
237
+ resource: llm_usage
238
+ select:
239
+ - field: used_at
240
+ grain: day
241
+ as: day
242
+ - field: purpose
243
+ - agg: sum
244
+ field: cost
245
+ as: spend
246
+ group_by:
247
+ - field: used_at
248
+ grain: day
249
+ as: day
250
+ - purpose
251
+ order_by:
252
+ - field: day
253
+ direction: asc
254
+ ```
255
+
256
+ Example `dashboard_configure_pie_chart_widget` config:
257
+
258
+ ```yaml
259
+ target: chart
260
+ label: Spend share by model
261
+ size: medium
262
+ chart:
263
+ type: pie
264
+ label:
265
+ field: model
266
+ label: Model
267
+ value:
268
+ field: spend
269
+ label: Spend
270
+ format: currency
271
+ query:
272
+ resource: llm_usage
273
+ select:
274
+ - field: model
275
+ - agg: sum
276
+ field: cost
277
+ as: spend
278
+ group_by:
279
+ - model
280
+ ```
281
+
282
+ Example `dashboard_configure_histogram_chart_widget` config:
283
+
284
+ ```yaml
285
+ target: chart
286
+ label: Request size distribution
287
+ size: wide
288
+ chart:
289
+ type: histogram
290
+ x:
291
+ field: total_tokens
292
+ label: Tokens
293
+ y:
294
+ field: requests
295
+ label: Requests
296
+ query:
297
+ resource: llm_usage
298
+ select:
299
+ - field: total_tokens
300
+ - agg: count
301
+ as: requests
302
+ bucket:
303
+ field: total_tokens
304
+ buckets:
305
+ - label: Small
306
+ max: 1000
307
+ - label: Medium
308
+ min: 1000
309
+ max: 10000
310
+ - label: Large
311
+ min: 10000
312
+ ```
313
+
314
+ Example `dashboard_configure_funnel_chart_widget` config:
315
+
316
+ ```yaml
317
+ target: chart
318
+ label: Request funnel
319
+ size: wide
320
+ chart:
321
+ type: funnel
322
+ label:
323
+ field: stage
324
+ label: Stage
325
+ value:
326
+ field: count
327
+ label: Count
328
+ query:
329
+ resource: llm_usage
330
+ select:
331
+ - field: stage
332
+ - agg: count
333
+ as: count
334
+ group_by:
335
+ - stage
336
+ ```
337
+
338
+
45
339
  ## Group creation guard
46
340
 
47
341
  Before creating a group, call dashboard_get_config and check existing groups.
@@ -148,6 +442,7 @@ Use target, not type.
148
442
  For charts, use target: chart and chart.type for the concrete chart kind.
149
443
  Use query, not dataSource.
150
444
  Use resource, not resourceId.
445
+ Never use AdminForth routes such as /resource/llm_usage as resource or target values.
151
446
 
152
447
  ## Query shape rules
153
448
 
@@ -3,6 +3,7 @@ import type { DashboardConfig } from '../custom/model/dashboard.types.js';
3
3
  import type { DashboardRecord } from '../services/dashboardConfigService.js';
4
4
  type DashboardEndpointsContext = {
5
5
  getDashboardRecord: (slug: string) => Promise<DashboardRecord | null>;
6
+ getAllDashboardRecords: () => Promise<DashboardRecord[]>;
6
7
  parseStoredDashboardConfig: (config: unknown) => DashboardConfig;
7
8
  };
8
9
  export declare function registerDashboardEndpoints(server: IHttpServer, ctx: DashboardEndpointsContext): void;
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { DashboardApiResponseSchema, SlugRequestSchema, } from '../schema/api.js';
10
+ import { DashboardApiResponseSchema, GetSlugsResponseSchema, SlugRequestSchema, } from '../schema/api.js';
11
11
  export function registerDashboardEndpoints(server, ctx) {
12
12
  server.endpoint({
13
13
  method: 'POST',
@@ -30,4 +30,18 @@ export function registerDashboardEndpoints(server, ctx) {
30
30
  };
31
31
  }),
32
32
  });
33
+ server.endpoint({
34
+ method: 'GET',
35
+ path: '/dashboard/get-slugs',
36
+ description: 'Returns a list of all dashboard slugs and labels for listing purposes.',
37
+ request_schema: undefined,
38
+ response_schema: GetSlugsResponseSchema,
39
+ handler: () => __awaiter(this, void 0, void 0, function* () {
40
+ const dashboards = yield ctx.getAllDashboardRecords();
41
+ return dashboards.map((dashboard) => ({
42
+ slug: dashboard.slug,
43
+ label: dashboard.label,
44
+ }));
45
+ }),
46
+ });
33
47
  }
@@ -8,19 +8,20 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { randomUUID } from 'crypto';
11
- import { DashboardApiResponseSchema, GroupIdRequestSchema, MoveGroupRequestSchema, SetGroupConfigRequestSchema, SlugRequestSchema, } from '../schema/api.js';
11
+ import { DashboardMutationResponseSchema, GroupIdRequestSchema, MoveGroupRequestSchema, SetGroupConfigRequestSchema, SlugRequestSchema, } from '../schema/api.js';
12
12
  export function registerGroupEndpoints(server, ctx) {
13
13
  server.endpoint({
14
14
  method: 'POST',
15
15
  path: '/dashboard/add_dashboard_group',
16
16
  description: 'Adds a new group to a dashboard configuration. Superadmin only.',
17
17
  request_schema: SlugRequestSchema,
18
- response_schema: DashboardApiResponseSchema,
18
+ response_schema: DashboardMutationResponseSchema,
19
19
  handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
20
20
  if (!ctx.canEditDashboard(adminUser)) {
21
21
  response.setStatus(403);
22
- return { error: 'Dashboard edit is not allowed' };
22
+ return { ok: false, error: 'Dashboard edit is not allowed' };
23
23
  }
24
+ let groupId = null;
24
25
  const updatedDashboard = yield ctx.updateDashboardConfig(body.slug, (config) => {
25
26
  const nextOrder = config.groups.length + 1;
26
27
  const group = {
@@ -28,13 +29,14 @@ export function registerGroupEndpoints(server, ctx) {
28
29
  label: 'New group',
29
30
  order: nextOrder,
30
31
  };
32
+ groupId = group.id;
31
33
  return Object.assign(Object.assign({}, config), { groups: [...config.groups, group] });
32
34
  });
33
35
  if (!updatedDashboard) {
34
36
  response.setStatus(404);
35
- return { error: 'Dashboard not found' };
37
+ return { ok: false, error: 'Dashboard not found' };
36
38
  }
37
- return updatedDashboard;
39
+ return { ok: true, groupId };
38
40
  }),
39
41
  });
40
42
  server.endpoint({
@@ -42,11 +44,11 @@ export function registerGroupEndpoints(server, ctx) {
42
44
  path: '/dashboard/set_dashboard_group_config',
43
45
  description: 'Replaces editable JSON configuration for a dashboard group while preserving group id and order. Superadmin only.',
44
46
  request_schema: SetGroupConfigRequestSchema,
45
- response_schema: DashboardApiResponseSchema,
47
+ response_schema: DashboardMutationResponseSchema,
46
48
  handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
47
49
  if (!ctx.canEditDashboard(adminUser)) {
48
50
  response.setStatus(403);
49
- return { error: 'Dashboard edit is not allowed' };
51
+ return { ok: false, error: 'Dashboard edit is not allowed' };
50
52
  }
51
53
  const groupId = body.groupId;
52
54
  let mutationError = null;
@@ -63,13 +65,13 @@ export function registerGroupEndpoints(server, ctx) {
63
65
  });
64
66
  if (!updatedDashboard) {
65
67
  response.setStatus(404);
66
- return { error: 'Dashboard not found' };
68
+ return { ok: false, error: 'Dashboard not found' };
67
69
  }
68
70
  if (mutationError) {
69
71
  response.setStatus(404);
70
- return { error: mutationError };
72
+ return { ok: false, error: mutationError };
71
73
  }
72
- return updatedDashboard;
74
+ return { ok: true };
73
75
  }),
74
76
  });
75
77
  server.endpoint({
@@ -77,11 +79,11 @@ export function registerGroupEndpoints(server, ctx) {
77
79
  path: '/dashboard/move_dashboard_group',
78
80
  description: 'Moves a dashboard group up or down in its dashboard. Superadmin only.',
79
81
  request_schema: MoveGroupRequestSchema,
80
- response_schema: DashboardApiResponseSchema,
82
+ response_schema: DashboardMutationResponseSchema,
81
83
  handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
82
84
  if (!ctx.canEditDashboard(adminUser)) {
83
85
  response.setStatus(403);
84
- return { error: 'Dashboard edit is not allowed' };
86
+ return { ok: false, error: 'Dashboard edit is not allowed' };
85
87
  }
86
88
  let mutationError = null;
87
89
  const updatedDashboard = yield ctx.updateDashboardConfig(body.slug, (config) => {
@@ -102,13 +104,13 @@ export function registerGroupEndpoints(server, ctx) {
102
104
  });
103
105
  if (!updatedDashboard) {
104
106
  response.setStatus(404);
105
- return { error: 'Dashboard not found' };
107
+ return { ok: false, error: 'Dashboard not found' };
106
108
  }
107
109
  if (mutationError) {
108
110
  response.setStatus(404);
109
- return { error: mutationError };
111
+ return { ok: false, error: mutationError };
110
112
  }
111
- return updatedDashboard;
113
+ return { ok: true };
112
114
  }),
113
115
  });
114
116
  server.endpoint({
@@ -116,11 +118,11 @@ export function registerGroupEndpoints(server, ctx) {
116
118
  path: '/dashboard/remove_dashboard_group',
117
119
  description: 'Removes a dashboard group and all widgets inside it. Superadmin only.',
118
120
  request_schema: GroupIdRequestSchema,
119
- response_schema: DashboardApiResponseSchema,
121
+ response_schema: DashboardMutationResponseSchema,
120
122
  handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
121
123
  if (!ctx.canEditDashboard(adminUser)) {
122
124
  response.setStatus(403);
123
- return { error: 'Dashboard edit is not allowed' };
125
+ return { ok: false, error: 'Dashboard edit is not allowed' };
124
126
  }
125
127
  const groupId = body.groupId;
126
128
  let mutationError = null;
@@ -134,13 +136,13 @@ export function registerGroupEndpoints(server, ctx) {
134
136
  });
135
137
  if (!updatedDashboard) {
136
138
  response.setStatus(404);
137
- return { error: 'Dashboard not found' };
139
+ return { ok: false, error: 'Dashboard not found' };
138
140
  }
139
141
  if (mutationError) {
140
142
  response.setStatus(404);
141
- return { error: mutationError };
143
+ return { ok: false, error: mutationError };
142
144
  }
143
- return updatedDashboard;
145
+ return { ok: true };
144
146
  }),
145
147
  });
146
148
  }
@@ -8,7 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { randomUUID } from 'crypto';
11
- import { ConfigureBarChartWidgetRequestSchema, ConfigureFunnelChartWidgetRequestSchema, ConfigureGaugeCardWidgetRequestSchema, ConfigureHistogramChartWidgetRequestSchema, ConfigureKpiCardWidgetRequestSchema, ConfigureLineChartWidgetRequestSchema, ConfigurePieChartWidgetRequestSchema, ConfigurePivotTableWidgetRequestSchema, ConfigureStackedBarChartWidgetRequestSchema, ConfigureTableWidgetRequestSchema, DashboardApiResponseSchema, DashboardWidgetDataResponseSchema, GroupIdRequestSchema, MoveWidgetRequestSchema, SetWidgetConfigRequestSchema, WidgetDataRequestSchema, WidgetIdRequestSchema, } from '../schema/api.js';
11
+ import { ConfigureBarChartWidgetRequestSchema, ConfigureFunnelChartWidgetRequestSchema, ConfigureGaugeCardWidgetRequestSchema, ConfigureHistogramChartWidgetRequestSchema, ConfigureKpiCardWidgetRequestSchema, ConfigureLineChartWidgetRequestSchema, ConfigurePieChartWidgetRequestSchema, ConfigurePivotTableWidgetRequestSchema, ConfigureStackedBarChartWidgetRequestSchema, ConfigureTableWidgetRequestSchema, DashboardMutationResponseSchema, DashboardWidgetDataResponseSchema, GroupIdRequestSchema, MoveWidgetRequestSchema, SetWidgetConfigRequestSchema, WidgetDataRequestSchema, WidgetIdRequestSchema, } from '../schema/api.js';
12
12
  function replaceWidgetConfig(ctx, slug, widgetId, widgetConfig) {
13
13
  return __awaiter(this, void 0, void 0, function* () {
14
14
  let mutationError = null;
@@ -35,23 +35,23 @@ function registerConfigureWidgetEndpoint(server, ctx, options) {
35
35
  path: options.path,
36
36
  description: options.description,
37
37
  request_schema: options.requestSchema,
38
- response_schema: DashboardApiResponseSchema,
38
+ response_schema: DashboardMutationResponseSchema,
39
39
  handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
40
40
  if (!ctx.canEditDashboard(adminUser)) {
41
41
  response.setStatus(403);
42
- return { error: 'Dashboard edit is not allowed' };
42
+ return { ok: false, error: 'Dashboard edit is not allowed' };
43
43
  }
44
44
  const request = body;
45
45
  const { updatedDashboard, mutationError } = yield replaceWidgetConfig(ctx, request.slug, request.widgetId, request.config);
46
46
  if (!updatedDashboard) {
47
47
  response.setStatus(404);
48
- return { error: 'Dashboard not found' };
48
+ return { ok: false, error: 'Dashboard not found' };
49
49
  }
50
50
  if (mutationError) {
51
51
  response.setStatus(404);
52
- return { error: mutationError };
52
+ return { ok: false, error: mutationError };
53
53
  }
54
- return updatedDashboard;
54
+ return { ok: true };
55
55
  }),
56
56
  });
57
57
  }
@@ -61,13 +61,14 @@ export function registerWidgetEndpoints(server, ctx) {
61
61
  path: '/dashboard/add_dashboard_widget',
62
62
  description: 'Adds a new empty widget to a dashboard group. Superadmin only.',
63
63
  request_schema: GroupIdRequestSchema,
64
- response_schema: DashboardApiResponseSchema,
64
+ response_schema: DashboardMutationResponseSchema,
65
65
  handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
66
66
  if (!ctx.canEditDashboard(adminUser)) {
67
67
  response.setStatus(403);
68
- return { error: 'Dashboard edit is not allowed' };
68
+ return { ok: false, error: 'Dashboard edit is not allowed' };
69
69
  }
70
70
  let mutationError = null;
71
+ let widgetId = null;
71
72
  const updatedDashboard = yield ctx.updateDashboardConfig(body.slug, (config) => {
72
73
  const group = config.groups.find((item) => item.id === body.groupId);
73
74
  if (!group) {
@@ -83,17 +84,18 @@ export function registerWidgetEndpoints(server, ctx) {
83
84
  order: nextOrder,
84
85
  target: 'empty',
85
86
  };
87
+ widgetId = widget.id;
86
88
  return Object.assign(Object.assign({}, config), { widgets: [...config.widgets, widget] });
87
89
  });
88
90
  if (!updatedDashboard) {
89
91
  response.setStatus(404);
90
- return { error: 'Dashboard not found' };
92
+ return { ok: false, error: 'Dashboard not found' };
91
93
  }
92
94
  if (mutationError) {
93
95
  response.setStatus(404);
94
- return { error: mutationError };
96
+ return { ok: false, error: mutationError };
95
97
  }
96
- return updatedDashboard;
98
+ return { ok: true, widgetId };
97
99
  }),
98
100
  });
99
101
  server.endpoint({
@@ -101,11 +103,11 @@ export function registerWidgetEndpoints(server, ctx) {
101
103
  path: '/dashboard/move_dashboard_widget',
102
104
  description: 'Moves a dashboard widget up or down inside its group. Superadmin only.',
103
105
  request_schema: MoveWidgetRequestSchema,
104
- response_schema: DashboardApiResponseSchema,
106
+ response_schema: DashboardMutationResponseSchema,
105
107
  handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
106
108
  if (!ctx.canEditDashboard(adminUser)) {
107
109
  response.setStatus(403);
108
- return { error: 'Dashboard edit is not allowed' };
110
+ return { ok: false, error: 'Dashboard edit is not allowed' };
109
111
  }
110
112
  let mutationError = null;
111
113
  const updatedDashboard = yield ctx.updateDashboardConfig(body.slug, (config) => {
@@ -133,13 +135,13 @@ export function registerWidgetEndpoints(server, ctx) {
133
135
  });
134
136
  if (!updatedDashboard) {
135
137
  response.setStatus(404);
136
- return { error: 'Dashboard not found' };
138
+ return { ok: false, error: 'Dashboard not found' };
137
139
  }
138
140
  if (mutationError) {
139
141
  response.setStatus(404);
140
- return { error: mutationError };
142
+ return { ok: false, error: mutationError };
141
143
  }
142
- return updatedDashboard;
144
+ return { ok: true };
143
145
  }),
144
146
  });
145
147
  server.endpoint({
@@ -147,11 +149,11 @@ export function registerWidgetEndpoints(server, ctx) {
147
149
  path: '/dashboard/remove_dashboard_widget',
148
150
  description: 'Removes one dashboard widget by id. Superadmin only.',
149
151
  request_schema: WidgetIdRequestSchema,
150
- response_schema: DashboardApiResponseSchema,
152
+ response_schema: DashboardMutationResponseSchema,
151
153
  handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
152
154
  if (!ctx.canEditDashboard(adminUser)) {
153
155
  response.setStatus(403);
154
- return { error: 'Dashboard edit is not allowed' };
156
+ return { ok: false, error: 'Dashboard edit is not allowed' };
155
157
  }
156
158
  let mutationError = null;
157
159
  const updatedDashboard = yield ctx.updateDashboardConfig(body.slug, (config) => {
@@ -164,13 +166,13 @@ export function registerWidgetEndpoints(server, ctx) {
164
166
  });
165
167
  if (!updatedDashboard) {
166
168
  response.setStatus(404);
167
- return { error: 'Dashboard not found' };
169
+ return { ok: false, error: 'Dashboard not found' };
168
170
  }
169
171
  if (mutationError) {
170
172
  response.setStatus(404);
171
- return { error: mutationError };
173
+ return { ok: false, error: mutationError };
172
174
  }
173
- return updatedDashboard;
175
+ return { ok: true };
174
176
  }),
175
177
  });
176
178
  server.endpoint({
@@ -178,23 +180,23 @@ export function registerWidgetEndpoints(server, ctx) {
178
180
  path: '/dashboard/set_widget_config',
179
181
  description: 'Replaces editable JSON configuration for a dashboard widget while preserving widget id, group id, and order. Superadmin only.',
180
182
  request_schema: SetWidgetConfigRequestSchema,
181
- response_schema: DashboardApiResponseSchema,
183
+ response_schema: DashboardMutationResponseSchema,
182
184
  handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
183
185
  if (!ctx.canEditDashboard(adminUser)) {
184
186
  response.setStatus(403);
185
- return { error: 'Dashboard edit is not allowed' };
187
+ return { ok: false, error: 'Dashboard edit is not allowed' };
186
188
  }
187
189
  const request = body;
188
190
  const { updatedDashboard, mutationError } = yield replaceWidgetConfig(ctx, request.slug, request.widgetId, request.config);
189
191
  if (!updatedDashboard) {
190
192
  response.setStatus(404);
191
- return { error: 'Dashboard not found' };
193
+ return { ok: false, error: 'Dashboard not found' };
192
194
  }
193
195
  if (mutationError) {
194
196
  response.setStatus(404);
195
- return { error: mutationError };
197
+ return { ok: false, error: mutationError };
196
198
  }
197
- return updatedDashboard;
199
+ return { ok: true };
198
200
  }),
199
201
  });
200
202
  registerConfigureWidgetEndpoint(server, ctx, {