@adminforth/dashboard 1.4.2 → 1.5.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/custom/api/dashboardApi.ts +0 -4
- package/custom/skills/adminforth-dashboard/SKILL.md +111 -181
- package/dist/custom/api/dashboardApi.d.ts +0 -1
- package/dist/custom/api/dashboardApi.js +0 -5
- package/dist/custom/api/dashboardApi.ts +0 -4
- package/dist/custom/skills/adminforth-dashboard/SKILL.md +111 -181
- package/dist/endpoint/dashboard.d.ts +2 -4
- package/dist/endpoint/dashboard.js +1 -21
- package/dist/endpoint/groups.d.ts +1 -0
- package/dist/endpoint/groups.js +61 -48
- package/dist/endpoint/widgets.d.ts +1 -0
- package/dist/endpoint/widgets.js +80 -62
- package/dist/schema/api.d.ts +0 -867
- package/dist/schema/api.js +0 -5
- package/dist/services/dashboardConfigService.d.ts +4 -0
- package/dist/services/dashboardConfigService.js +46 -0
- package/endpoint/dashboard.ts +2 -33
- package/endpoint/groups.ts +91 -72
- package/endpoint/widgets.ts +114 -88
- package/package.json +1 -1
- package/schema/api.ts +0 -6
- package/services/dashboardConfigService.ts +73 -0
package/dist/schema/api.js
CHANGED
|
@@ -41,10 +41,6 @@ export const DashboardWidgetDataResponseZodSchema = z.union([
|
|
|
41
41
|
export const SlugRequestZodSchema = z.object({
|
|
42
42
|
slug: z.string(),
|
|
43
43
|
}).strict();
|
|
44
|
-
export const SetDashboardConfigRequestZodSchema = z.object({
|
|
45
|
-
slug: z.string(),
|
|
46
|
-
config: DashboardConfigZodSchema,
|
|
47
|
-
}).strict();
|
|
48
44
|
export const GroupIdRequestZodSchema = z.object({
|
|
49
45
|
slug: z.string(),
|
|
50
46
|
groupId: z.string(),
|
|
@@ -91,7 +87,6 @@ export const DashboardResponseSchema = toAdminForthJsonSchema(DashboardResponseZ
|
|
|
91
87
|
export const DashboardApiResponseSchema = toAdminForthJsonSchema(DashboardApiResponseZodSchema);
|
|
92
88
|
export const DashboardWidgetDataResponseSchema = toAdminForthJsonSchema(DashboardWidgetDataResponseZodSchema);
|
|
93
89
|
export const SlugRequestSchema = toAdminForthJsonSchema(SlugRequestZodSchema);
|
|
94
|
-
export const SetDashboardConfigRequestSchema = toAdminForthJsonSchema(SetDashboardConfigRequestZodSchema);
|
|
95
90
|
export const GroupIdRequestSchema = toAdminForthJsonSchema(GroupIdRequestZodSchema);
|
|
96
91
|
export const MoveGroupRequestSchema = toAdminForthJsonSchema(MoveGroupRequestZodSchema);
|
|
97
92
|
export const SetGroupConfigRequestSchema = toAdminForthJsonSchema(SetGroupConfigRequestZodSchema);
|
|
@@ -15,11 +15,15 @@ export type PersistedDashboardResponse = {
|
|
|
15
15
|
revision: number;
|
|
16
16
|
config: DashboardConfig;
|
|
17
17
|
};
|
|
18
|
+
type DashboardConfigMutator = (config: DashboardConfig, dashboard: DashboardRecord) => DashboardConfig | null | Promise<DashboardConfig | null>;
|
|
18
19
|
export declare function getDashboardRecord(adminforth: IAdminForth, dashboardConfigsResourceId: string, slug: string): Promise<DashboardRecord | null>;
|
|
19
20
|
export declare function persistDashboardConfig(adminforth: IAdminForth, dashboardConfigsResourceId: string, dashboard: DashboardRecord, config: DashboardConfig): Promise<PersistedDashboardResponse>;
|
|
21
|
+
export declare function updateDashboardConfig(adminforth: IAdminForth, dashboardConfigsResourceId: string, slug: string, mutateConfig: DashboardConfigMutator): Promise<PersistedDashboardResponse | null>;
|
|
20
22
|
export type DashboardConfigService = {
|
|
21
23
|
getDashboardRecord: (slug: string) => Promise<DashboardRecord | null>;
|
|
22
24
|
parseStoredDashboardConfig: typeof parseStoredDashboardConfig;
|
|
23
25
|
persistDashboardConfig: (dashboard: DashboardRecord, config: DashboardConfig) => Promise<PersistedDashboardResponse>;
|
|
26
|
+
updateDashboardConfig: (slug: string, mutateConfig: DashboardConfigMutator) => Promise<PersistedDashboardResponse | null>;
|
|
24
27
|
};
|
|
25
28
|
export declare function createDashboardConfigService(adminforth: IAdminForth, dashboardConfigsResourceId: string): DashboardConfigService;
|
|
29
|
+
export {};
|
|
@@ -14,6 +14,29 @@ export function parseStoredDashboardConfig(config) {
|
|
|
14
14
|
const parsedConfig = typeof config === 'string' ? JSON.parse(config) : config;
|
|
15
15
|
return DashboardConfigZodSchema.parse(parsedConfig);
|
|
16
16
|
}
|
|
17
|
+
const dashboardConfigUpdateQueues = new Map();
|
|
18
|
+
function runDashboardConfigUpdateQueued(dashboardSlug, callback) {
|
|
19
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
20
|
+
var _a;
|
|
21
|
+
const previousUpdate = (_a = dashboardConfigUpdateQueues.get(dashboardSlug)) !== null && _a !== void 0 ? _a : Promise.resolve();
|
|
22
|
+
let releaseCurrentUpdate;
|
|
23
|
+
const currentUpdate = new Promise((resolve) => {
|
|
24
|
+
releaseCurrentUpdate = resolve;
|
|
25
|
+
});
|
|
26
|
+
const queuedUpdate = previousUpdate.then(() => currentUpdate, () => currentUpdate);
|
|
27
|
+
dashboardConfigUpdateQueues.set(dashboardSlug, queuedUpdate);
|
|
28
|
+
yield previousUpdate.catch(() => undefined);
|
|
29
|
+
try {
|
|
30
|
+
return yield callback();
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
releaseCurrentUpdate();
|
|
34
|
+
if (dashboardConfigUpdateQueues.get(dashboardSlug) === queuedUpdate) {
|
|
35
|
+
dashboardConfigUpdateQueues.delete(dashboardSlug);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
17
40
|
function normalizeDashboardOrder(config) {
|
|
18
41
|
var _a;
|
|
19
42
|
const widgetsByGroupId = new Map();
|
|
@@ -55,10 +78,33 @@ export function persistDashboardConfig(adminforth, dashboardConfigsResourceId, d
|
|
|
55
78
|
};
|
|
56
79
|
});
|
|
57
80
|
}
|
|
81
|
+
export function updateDashboardConfig(adminforth, dashboardConfigsResourceId, slug, mutateConfig) {
|
|
82
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
83
|
+
return runDashboardConfigUpdateQueued(slug, () => __awaiter(this, void 0, void 0, function* () {
|
|
84
|
+
const dashboard = yield getDashboardRecord(adminforth, dashboardConfigsResourceId, slug);
|
|
85
|
+
if (!dashboard) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const config = parseStoredDashboardConfig(dashboard.config);
|
|
89
|
+
const nextConfig = yield mutateConfig(config, dashboard);
|
|
90
|
+
if (nextConfig === null) {
|
|
91
|
+
return {
|
|
92
|
+
id: dashboard.id,
|
|
93
|
+
slug: dashboard.slug,
|
|
94
|
+
label: dashboard.label,
|
|
95
|
+
revision: dashboard.revision,
|
|
96
|
+
config,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return persistDashboardConfig(adminforth, dashboardConfigsResourceId, dashboard, nextConfig);
|
|
100
|
+
}));
|
|
101
|
+
});
|
|
102
|
+
}
|
|
58
103
|
export function createDashboardConfigService(adminforth, dashboardConfigsResourceId) {
|
|
59
104
|
return {
|
|
60
105
|
getDashboardRecord: (slug) => getDashboardRecord(adminforth, dashboardConfigsResourceId, slug),
|
|
61
106
|
parseStoredDashboardConfig,
|
|
62
107
|
persistDashboardConfig: (dashboard, config) => persistDashboardConfig(adminforth, dashboardConfigsResourceId, dashboard, config),
|
|
108
|
+
updateDashboardConfig: (slug, mutateConfig) => updateDashboardConfig(adminforth, dashboardConfigsResourceId, slug, mutateConfig),
|
|
63
109
|
};
|
|
64
110
|
}
|
package/endpoint/dashboard.ts
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { IHttpServer } from 'adminforth';
|
|
2
2
|
import type { DashboardConfig } from '../custom/model/dashboard.types.js';
|
|
3
3
|
import {
|
|
4
4
|
DashboardApiResponseSchema,
|
|
5
|
-
SetDashboardConfigRequestSchema,
|
|
6
5
|
SlugRequestSchema,
|
|
7
6
|
} from '../schema/api.js';
|
|
8
|
-
import type { DashboardRecord
|
|
7
|
+
import type { DashboardRecord } from '../services/dashboardConfigService.js';
|
|
9
8
|
|
|
10
9
|
type DashboardEndpointsContext = {
|
|
11
|
-
canEditDashboard: (adminUser: AdminUser) => boolean;
|
|
12
10
|
getDashboardRecord: (slug: string) => Promise<DashboardRecord | null>;
|
|
13
11
|
parseStoredDashboardConfig: (config: unknown) => DashboardConfig;
|
|
14
|
-
persistDashboardConfig: (
|
|
15
|
-
dashboard: DashboardRecord,
|
|
16
|
-
config: DashboardConfig,
|
|
17
|
-
) => Promise<PersistedDashboardResponse>;
|
|
18
12
|
};
|
|
19
13
|
|
|
20
14
|
export function registerDashboardEndpoints(
|
|
@@ -44,29 +38,4 @@ export function registerDashboardEndpoints(
|
|
|
44
38
|
};
|
|
45
39
|
},
|
|
46
40
|
});
|
|
47
|
-
|
|
48
|
-
server.endpoint({
|
|
49
|
-
method: 'POST',
|
|
50
|
-
path: '/dashboard/set_dashboard_config',
|
|
51
|
-
description: 'Replaces one dashboard configuration, including groups and widgets. Superadmin only.',
|
|
52
|
-
request_schema: SetDashboardConfigRequestSchema,
|
|
53
|
-
response_schema: DashboardApiResponseSchema,
|
|
54
|
-
handler: async ({ body, adminUser, response }) => {
|
|
55
|
-
if (!ctx.canEditDashboard(adminUser)) {
|
|
56
|
-
response.setStatus(403);
|
|
57
|
-
return { error: 'Dashboard edit is not allowed' };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const dashboard = await ctx.getDashboardRecord(body.slug);
|
|
61
|
-
|
|
62
|
-
if (!dashboard) {
|
|
63
|
-
response.setStatus(404);
|
|
64
|
-
return { error: 'Dashboard not found' };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const config = body.config as DashboardConfig;
|
|
68
|
-
|
|
69
|
-
return ctx.persistDashboardConfig(dashboard, config);
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
41
|
}
|
package/endpoint/groups.ts
CHANGED
|
@@ -22,6 +22,10 @@ type GroupEndpointsContext = {
|
|
|
22
22
|
dashboard: DashboardRecord,
|
|
23
23
|
config: DashboardConfig,
|
|
24
24
|
) => Promise<PersistedDashboardResponse>;
|
|
25
|
+
updateDashboardConfig: (
|
|
26
|
+
slug: string,
|
|
27
|
+
mutateConfig: (config: DashboardConfig, dashboard: DashboardRecord) => DashboardConfig | null | Promise<DashboardConfig | null>,
|
|
28
|
+
) => Promise<PersistedDashboardResponse | null>;
|
|
25
29
|
};
|
|
26
30
|
|
|
27
31
|
export function registerGroupEndpoints(
|
|
@@ -40,26 +44,26 @@ export function registerGroupEndpoints(
|
|
|
40
44
|
return { error: 'Dashboard edit is not allowed' };
|
|
41
45
|
}
|
|
42
46
|
|
|
43
|
-
const
|
|
47
|
+
const updatedDashboard = await ctx.updateDashboardConfig(body.slug, (config) => {
|
|
48
|
+
const nextOrder = config.groups.length + 1;
|
|
49
|
+
const group: DashboardGroupConfig = {
|
|
50
|
+
id: `group_${randomUUID()}`,
|
|
51
|
+
label: 'New group',
|
|
52
|
+
order: nextOrder,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
...config,
|
|
57
|
+
groups: [...config.groups, group],
|
|
58
|
+
};
|
|
59
|
+
});
|
|
44
60
|
|
|
45
|
-
if (!
|
|
61
|
+
if (!updatedDashboard) {
|
|
46
62
|
response.setStatus(404);
|
|
47
63
|
return { error: 'Dashboard not found' };
|
|
48
64
|
}
|
|
49
65
|
|
|
50
|
-
|
|
51
|
-
const nextOrder = config.groups.length + 1;
|
|
52
|
-
|
|
53
|
-
const group: DashboardGroupConfig = {
|
|
54
|
-
id: `group_${randomUUID()}`,
|
|
55
|
-
label: 'New group',
|
|
56
|
-
order: nextOrder,
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
return ctx.persistDashboardConfig(dashboard, {
|
|
60
|
-
...config,
|
|
61
|
-
groups: [...config.groups, group],
|
|
62
|
-
});
|
|
66
|
+
return updatedDashboard;
|
|
63
67
|
},
|
|
64
68
|
});
|
|
65
69
|
|
|
@@ -76,33 +80,40 @@ export function registerGroupEndpoints(
|
|
|
76
80
|
}
|
|
77
81
|
|
|
78
82
|
const groupId = body.groupId;
|
|
79
|
-
|
|
83
|
+
let mutationError: string | null = null;
|
|
84
|
+
const updatedDashboard = await ctx.updateDashboardConfig(body.slug, (config) => {
|
|
85
|
+
const group = config.groups.find((item) => item.id === groupId);
|
|
86
|
+
|
|
87
|
+
if (!group) {
|
|
88
|
+
mutationError = 'Dashboard group not found';
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const nextGroup: DashboardGroupConfig = {
|
|
93
|
+
...(body.config as EditableDashboardGroupConfig),
|
|
94
|
+
id: group.id,
|
|
95
|
+
order: group.order,
|
|
96
|
+
};
|
|
80
97
|
|
|
81
|
-
|
|
98
|
+
return {
|
|
99
|
+
...config,
|
|
100
|
+
groups: config.groups.map((item) => item.id === groupId
|
|
101
|
+
? nextGroup
|
|
102
|
+
: item),
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (!updatedDashboard) {
|
|
82
107
|
response.setStatus(404);
|
|
83
108
|
return { error: 'Dashboard not found' };
|
|
84
109
|
}
|
|
85
110
|
|
|
86
|
-
|
|
87
|
-
const group = config.groups.find((item) => item.id === groupId);
|
|
88
|
-
|
|
89
|
-
if (!group) {
|
|
111
|
+
if (mutationError) {
|
|
90
112
|
response.setStatus(404);
|
|
91
|
-
return { error:
|
|
113
|
+
return { error: mutationError };
|
|
92
114
|
}
|
|
93
115
|
|
|
94
|
-
|
|
95
|
-
...(body.config as EditableDashboardGroupConfig),
|
|
96
|
-
id: group.id,
|
|
97
|
-
order: group.order,
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
return ctx.persistDashboardConfig(dashboard, {
|
|
101
|
-
...config,
|
|
102
|
-
groups: config.groups.map((item) => item.id === groupId
|
|
103
|
-
? nextGroup
|
|
104
|
-
: item),
|
|
105
|
-
});
|
|
116
|
+
return updatedDashboard;
|
|
106
117
|
},
|
|
107
118
|
});
|
|
108
119
|
|
|
@@ -118,42 +129,43 @@ export function registerGroupEndpoints(
|
|
|
118
129
|
return { error: 'Dashboard edit is not allowed' };
|
|
119
130
|
}
|
|
120
131
|
|
|
121
|
-
|
|
132
|
+
let mutationError: string | null = null;
|
|
133
|
+
const updatedDashboard = await ctx.updateDashboardConfig(body.slug, (config) => {
|
|
134
|
+
const sortedGroups = [...config.groups].sort((a, b) => a.order - b.order);
|
|
135
|
+
const currentIndex = sortedGroups.findIndex((group) => group.id === body.groupId);
|
|
122
136
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
137
|
+
if (currentIndex === -1) {
|
|
138
|
+
mutationError = 'Dashboard group not found';
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
127
141
|
|
|
128
|
-
|
|
129
|
-
const sortedGroups = [...config.groups].sort((a, b) => a.order - b.order);
|
|
130
|
-
const currentIndex = sortedGroups.findIndex((group) => group.id === body.groupId);
|
|
142
|
+
const targetIndex = body.direction === 'up' ? currentIndex - 1 : currentIndex + 1;
|
|
131
143
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
144
|
+
if (targetIndex < 0 || targetIndex >= sortedGroups.length) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
136
147
|
|
|
137
|
-
|
|
148
|
+
const reorderedGroups = [...sortedGroups];
|
|
149
|
+
const [group] = reorderedGroups.splice(currentIndex, 1);
|
|
150
|
+
reorderedGroups.splice(targetIndex, 0, group);
|
|
138
151
|
|
|
139
|
-
if (targetIndex < 0 || targetIndex >= sortedGroups.length) {
|
|
140
152
|
return {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
label: dashboard.label,
|
|
144
|
-
revision: dashboard.revision,
|
|
145
|
-
config: ctx.parseStoredDashboardConfig(dashboard.config),
|
|
153
|
+
...config,
|
|
154
|
+
groups: reorderedGroups,
|
|
146
155
|
};
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
if (!updatedDashboard) {
|
|
159
|
+
response.setStatus(404);
|
|
160
|
+
return { error: 'Dashboard not found' };
|
|
147
161
|
}
|
|
148
162
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
163
|
+
if (mutationError) {
|
|
164
|
+
response.setStatus(404);
|
|
165
|
+
return { error: mutationError };
|
|
166
|
+
}
|
|
152
167
|
|
|
153
|
-
return
|
|
154
|
-
...config,
|
|
155
|
-
groups: reorderedGroups,
|
|
156
|
-
});
|
|
168
|
+
return updatedDashboard;
|
|
157
169
|
},
|
|
158
170
|
});
|
|
159
171
|
|
|
@@ -170,26 +182,33 @@ export function registerGroupEndpoints(
|
|
|
170
182
|
}
|
|
171
183
|
|
|
172
184
|
const groupId = body.groupId;
|
|
173
|
-
|
|
185
|
+
let mutationError: string | null = null;
|
|
186
|
+
const updatedDashboard = await ctx.updateDashboardConfig(body.slug, (config) => {
|
|
187
|
+
const nextGroups = config.groups.filter((group) => group.id !== groupId);
|
|
188
|
+
|
|
189
|
+
if (nextGroups.length === config.groups.length) {
|
|
190
|
+
mutationError = 'Dashboard group not found';
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
...config,
|
|
196
|
+
groups: nextGroups,
|
|
197
|
+
widgets: config.widgets.filter((widget) => widget.group_id !== groupId),
|
|
198
|
+
};
|
|
199
|
+
});
|
|
174
200
|
|
|
175
|
-
if (!
|
|
201
|
+
if (!updatedDashboard) {
|
|
176
202
|
response.setStatus(404);
|
|
177
203
|
return { error: 'Dashboard not found' };
|
|
178
204
|
}
|
|
179
205
|
|
|
180
|
-
|
|
181
|
-
const nextGroups = config.groups.filter((group) => group.id !== groupId);
|
|
182
|
-
|
|
183
|
-
if (nextGroups.length === config.groups.length) {
|
|
206
|
+
if (mutationError) {
|
|
184
207
|
response.setStatus(404);
|
|
185
|
-
return { error:
|
|
208
|
+
return { error: mutationError };
|
|
186
209
|
}
|
|
187
210
|
|
|
188
|
-
return
|
|
189
|
-
...config,
|
|
190
|
-
groups: nextGroups,
|
|
191
|
-
widgets: config.widgets.filter((widget) => widget.group_id !== groupId),
|
|
192
|
-
});
|
|
211
|
+
return updatedDashboard;
|
|
193
212
|
},
|
|
194
213
|
});
|
|
195
214
|
|
package/endpoint/widgets.ts
CHANGED
|
@@ -25,6 +25,10 @@ type WidgetEndpointsContext = {
|
|
|
25
25
|
dashboard: DashboardRecord,
|
|
26
26
|
config: DashboardConfig,
|
|
27
27
|
) => Promise<PersistedDashboardResponse>;
|
|
28
|
+
updateDashboardConfig: (
|
|
29
|
+
slug: string,
|
|
30
|
+
mutateConfig: (config: DashboardConfig, dashboard: DashboardRecord) => DashboardConfig | null | Promise<DashboardConfig | null>,
|
|
31
|
+
) => Promise<PersistedDashboardResponse | null>;
|
|
28
32
|
getWidgetData: (
|
|
29
33
|
widget: DashboardWidgetConfig,
|
|
30
34
|
options?: {
|
|
@@ -50,35 +54,42 @@ export function registerWidgetEndpoints(
|
|
|
50
54
|
return { error: 'Dashboard edit is not allowed' };
|
|
51
55
|
}
|
|
52
56
|
|
|
53
|
-
|
|
57
|
+
let mutationError: string | null = null;
|
|
58
|
+
const updatedDashboard = await ctx.updateDashboardConfig(body.slug, (config) => {
|
|
59
|
+
const group = config.groups.find((item) => item.id === body.groupId);
|
|
60
|
+
|
|
61
|
+
if (!group) {
|
|
62
|
+
mutationError = 'Dashboard group not found';
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const nextOrder = config.widgets.filter((item) => item.group_id === body.groupId).length + 1;
|
|
67
|
+
const widget: DashboardWidgetConfig = {
|
|
68
|
+
id: `widget_${randomUUID()}`,
|
|
69
|
+
group_id: body.groupId,
|
|
70
|
+
label: 'New widget',
|
|
71
|
+
size: 'small',
|
|
72
|
+
order: nextOrder,
|
|
73
|
+
target: 'empty',
|
|
74
|
+
};
|
|
54
75
|
|
|
55
|
-
|
|
76
|
+
return {
|
|
77
|
+
...config,
|
|
78
|
+
widgets: [...config.widgets, widget],
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (!updatedDashboard) {
|
|
56
83
|
response.setStatus(404);
|
|
57
84
|
return { error: 'Dashboard not found' };
|
|
58
85
|
}
|
|
59
86
|
|
|
60
|
-
|
|
61
|
-
const group = config.groups.find((item) => item.id === body.groupId);
|
|
62
|
-
|
|
63
|
-
if (!group) {
|
|
87
|
+
if (mutationError) {
|
|
64
88
|
response.setStatus(404);
|
|
65
|
-
return { error:
|
|
89
|
+
return { error: mutationError };
|
|
66
90
|
}
|
|
67
91
|
|
|
68
|
-
|
|
69
|
-
const widget: DashboardWidgetConfig = {
|
|
70
|
-
id: `widget_${randomUUID()}`,
|
|
71
|
-
group_id: body.groupId,
|
|
72
|
-
label: 'New widget',
|
|
73
|
-
size: 'small',
|
|
74
|
-
order: nextOrder,
|
|
75
|
-
target: 'empty',
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
return ctx.persistDashboardConfig(dashboard, {
|
|
79
|
-
...config,
|
|
80
|
-
widgets: [...config.widgets, widget],
|
|
81
|
-
});
|
|
92
|
+
return updatedDashboard;
|
|
82
93
|
},
|
|
83
94
|
});
|
|
84
95
|
|
|
@@ -94,49 +105,50 @@ export function registerWidgetEndpoints(
|
|
|
94
105
|
return { error: 'Dashboard edit is not allowed' };
|
|
95
106
|
}
|
|
96
107
|
|
|
97
|
-
|
|
108
|
+
let mutationError: string | null = null;
|
|
109
|
+
const updatedDashboard = await ctx.updateDashboardConfig(body.slug, (config) => {
|
|
110
|
+
const widget = config.widgets.find((item) => item.id === body.widgetId);
|
|
98
111
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
112
|
+
if (!widget) {
|
|
113
|
+
mutationError = 'Dashboard widget not found';
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
103
116
|
|
|
104
|
-
|
|
105
|
-
|
|
117
|
+
const sortedWidgets = config.widgets
|
|
118
|
+
.filter((item) => item.group_id === widget.group_id)
|
|
119
|
+
.sort((a, b) => a.order - b.order);
|
|
120
|
+
const currentIndex = sortedWidgets.findIndex((item) => item.id === body.widgetId);
|
|
121
|
+
const targetIndex = body.direction === 'up' ? currentIndex - 1 : currentIndex + 1;
|
|
106
122
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
123
|
+
if (targetIndex < 0 || targetIndex >= sortedWidgets.length) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
111
126
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
.
|
|
115
|
-
|
|
116
|
-
const targetIndex = body.direction === 'up' ? currentIndex - 1 : currentIndex + 1;
|
|
127
|
+
const reorderedWidgets = [...sortedWidgets];
|
|
128
|
+
const [movedWidget] = reorderedWidgets.splice(currentIndex, 1);
|
|
129
|
+
reorderedWidgets.splice(targetIndex, 0, movedWidget);
|
|
130
|
+
const reorderedWidgetIds = new Map(reorderedWidgets.map((item, index) => [item.id, index + 1]));
|
|
117
131
|
|
|
118
|
-
if (targetIndex < 0 || targetIndex >= sortedWidgets.length) {
|
|
119
132
|
return {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
133
|
+
...config,
|
|
134
|
+
widgets: config.widgets.map((item) => ({
|
|
135
|
+
...item,
|
|
136
|
+
order: reorderedWidgetIds.get(item.id) ?? item.order,
|
|
137
|
+
})),
|
|
125
138
|
};
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
if (!updatedDashboard) {
|
|
142
|
+
response.setStatus(404);
|
|
143
|
+
return { error: 'Dashboard not found' };
|
|
126
144
|
}
|
|
127
145
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return
|
|
134
|
-
...config,
|
|
135
|
-
widgets: config.widgets.map((item) => ({
|
|
136
|
-
...item,
|
|
137
|
-
order: reorderedWidgetIds.get(item.id) ?? item.order,
|
|
138
|
-
})),
|
|
139
|
-
});
|
|
146
|
+
if (mutationError) {
|
|
147
|
+
response.setStatus(404);
|
|
148
|
+
return { error: mutationError };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return updatedDashboard;
|
|
140
152
|
},
|
|
141
153
|
});
|
|
142
154
|
|
|
@@ -152,25 +164,32 @@ export function registerWidgetEndpoints(
|
|
|
152
164
|
return { error: 'Dashboard edit is not allowed' };
|
|
153
165
|
}
|
|
154
166
|
|
|
155
|
-
|
|
167
|
+
let mutationError: string | null = null;
|
|
168
|
+
const updatedDashboard = await ctx.updateDashboardConfig(body.slug, (config) => {
|
|
169
|
+
const nextWidgets = config.widgets.filter((item) => item.id !== body.widgetId);
|
|
156
170
|
|
|
157
|
-
|
|
171
|
+
if (nextWidgets.length === config.widgets.length) {
|
|
172
|
+
mutationError = 'Dashboard widget not found';
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
...config,
|
|
178
|
+
widgets: nextWidgets,
|
|
179
|
+
};
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
if (!updatedDashboard) {
|
|
158
183
|
response.setStatus(404);
|
|
159
184
|
return { error: 'Dashboard not found' };
|
|
160
185
|
}
|
|
161
186
|
|
|
162
|
-
|
|
163
|
-
const nextWidgets = config.widgets.filter((item) => item.id !== body.widgetId);
|
|
164
|
-
|
|
165
|
-
if (nextWidgets.length === config.widgets.length) {
|
|
187
|
+
if (mutationError) {
|
|
166
188
|
response.setStatus(404);
|
|
167
|
-
return { error:
|
|
189
|
+
return { error: mutationError };
|
|
168
190
|
}
|
|
169
191
|
|
|
170
|
-
return
|
|
171
|
-
...config,
|
|
172
|
-
widgets: nextWidgets,
|
|
173
|
-
});
|
|
192
|
+
return updatedDashboard;
|
|
174
193
|
},
|
|
175
194
|
});
|
|
176
195
|
|
|
@@ -186,36 +205,43 @@ export function registerWidgetEndpoints(
|
|
|
186
205
|
return { error: 'Dashboard edit is not allowed' };
|
|
187
206
|
}
|
|
188
207
|
|
|
189
|
-
|
|
208
|
+
let mutationError: string | null = null;
|
|
209
|
+
const updatedDashboard = await ctx.updateDashboardConfig(body.slug, (config) => {
|
|
210
|
+
const widget = config.widgets.find((item) => item.id === body.widgetId);
|
|
190
211
|
|
|
191
|
-
|
|
212
|
+
if (!widget) {
|
|
213
|
+
mutationError = 'Dashboard widget not found';
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const typedWidgetConfig = body.config as EditableDashboardWidgetConfig;
|
|
218
|
+
|
|
219
|
+
const nextWidget: DashboardWidgetConfig = {
|
|
220
|
+
...typedWidgetConfig,
|
|
221
|
+
id: widget.id,
|
|
222
|
+
group_id: widget.group_id,
|
|
223
|
+
order: widget.order,
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
...config,
|
|
228
|
+
widgets: config.widgets.map((item) => item.id === body.widgetId
|
|
229
|
+
? nextWidget
|
|
230
|
+
: item),
|
|
231
|
+
};
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
if (!updatedDashboard) {
|
|
192
235
|
response.setStatus(404);
|
|
193
236
|
return { error: 'Dashboard not found' };
|
|
194
237
|
}
|
|
195
238
|
|
|
196
|
-
|
|
197
|
-
const widget = config.widgets.find((item) => item.id === body.widgetId);
|
|
198
|
-
|
|
199
|
-
if (!widget) {
|
|
239
|
+
if (mutationError) {
|
|
200
240
|
response.setStatus(404);
|
|
201
|
-
return { error:
|
|
241
|
+
return { error: mutationError };
|
|
202
242
|
}
|
|
203
243
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const nextWidget: DashboardWidgetConfig = {
|
|
207
|
-
...typedWidgetConfig,
|
|
208
|
-
id: widget.id,
|
|
209
|
-
group_id: widget.group_id,
|
|
210
|
-
order: widget.order,
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
return ctx.persistDashboardConfig(dashboard, {
|
|
214
|
-
...config,
|
|
215
|
-
widgets: config.widgets.map((item) => item.id === body.widgetId
|
|
216
|
-
? nextWidget
|
|
217
|
-
: item),
|
|
218
|
-
});
|
|
244
|
+
return updatedDashboard;
|
|
219
245
|
},
|
|
220
246
|
});
|
|
221
247
|
|
package/package.json
CHANGED
package/schema/api.ts
CHANGED
|
@@ -50,11 +50,6 @@ export const SlugRequestZodSchema = z.object({
|
|
|
50
50
|
slug: z.string(),
|
|
51
51
|
}).strict()
|
|
52
52
|
|
|
53
|
-
export const SetDashboardConfigRequestZodSchema = z.object({
|
|
54
|
-
slug: z.string(),
|
|
55
|
-
config: DashboardConfigZodSchema,
|
|
56
|
-
}).strict()
|
|
57
|
-
|
|
58
53
|
export const GroupIdRequestZodSchema = z.object({
|
|
59
54
|
slug: z.string(),
|
|
60
55
|
groupId: z.string(),
|
|
@@ -109,7 +104,6 @@ export const DashboardResponseSchema = toAdminForthJsonSchema(DashboardResponseZ
|
|
|
109
104
|
export const DashboardApiResponseSchema = toAdminForthJsonSchema(DashboardApiResponseZodSchema)
|
|
110
105
|
export const DashboardWidgetDataResponseSchema = toAdminForthJsonSchema(DashboardWidgetDataResponseZodSchema)
|
|
111
106
|
export const SlugRequestSchema = toAdminForthJsonSchema(SlugRequestZodSchema)
|
|
112
|
-
export const SetDashboardConfigRequestSchema = toAdminForthJsonSchema(SetDashboardConfigRequestZodSchema)
|
|
113
107
|
export const GroupIdRequestSchema = toAdminForthJsonSchema(GroupIdRequestZodSchema)
|
|
114
108
|
export const MoveGroupRequestSchema = toAdminForthJsonSchema(MoveGroupRequestZodSchema)
|
|
115
109
|
export const SetGroupConfigRequestSchema = toAdminForthJsonSchema(SetGroupConfigRequestZodSchema)
|