lasal-mcp 0.1.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/LICENSE +21 -0
- package/README.md +198 -0
- package/dist/core/envelope.js +8 -0
- package/dist/core/errors.js +12 -0
- package/dist/core/http.js +19 -0
- package/dist/core/process.js +30 -0
- package/dist/core/response.js +37 -0
- package/dist/core/scratch.js +6 -0
- package/dist/core/staticServer.js +88 -0
- package/dist/server.js +230 -0
- package/dist/state.js +57 -0
- package/dist/tools/applyProjectChanges.js +371 -0
- package/dist/tools/deployAll.js +320 -0
- package/dist/tools/hmiBrowser.js +224 -0
- package/dist/tools/hmiRuntime.js +273 -0
- package/dist/tools/inspectProject.js +162 -0
- package/dist/tools/inspectVisuProject.js +474 -0
- package/dist/tools/larsRuntime.js +536 -0
- package/dist/tools/lasalApps.js +111 -0
- package/dist/tools/plcControl.js +530 -0
- package/dist/tools/plcDiagnostics.js +172 -0
- package/dist/tools/readClassSource.js +147 -0
- package/dist/tools/selectProject.js +47 -0
- package/dist/tools/setTargetIp.js +114 -0
- package/dist/tools/status.js +146 -0
- package/dist/tools/visuControl.js +447 -0
- package/dist/tools/visuDashboard.js +571 -0
- package/dist/utils/batchScript.js +257 -0
- package/dist/utils/config.js +34 -0
- package/dist/utils/editTransaction.js +39 -0
- package/dist/utils/engine.js +163 -0
- package/dist/utils/lars.js +471 -0
- package/dist/utils/lasalXml.js +758 -0
- package/dist/utils/preflight.js +194 -0
- package/dist/utils/projectScanner.js +212 -0
- package/dist/utils/resolvePaths.js +46 -0
- package/dist/utils/respond.js +14 -0
- package/dist/utils/scriptRunner.js +161 -0
- package/dist/utils/visuDashboardIO.js +198 -0
- package/dist/utils/visuPropertyEncoding.js +174 -0
- package/dist/utils/visuScript.js +262 -0
- package/package.json +64 -0
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, unlinkSync } from "fs";
|
|
2
|
+
import { join, dirname, basename } from "path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { resolveLvpPath } from "../utils/resolvePaths.js";
|
|
5
|
+
import { withEngineLock, killVisuDesigner } from "../utils/engine.js";
|
|
6
|
+
import { EditTransaction } from "../utils/editTransaction.js";
|
|
7
|
+
import { newDesignTimeId, newInstanceId, getFileEntryVersion, writeTabIndentedJson } from "../utils/visuDashboardIO.js";
|
|
8
|
+
import { loadControlManifest, encodeProperty } from "../utils/visuPropertyEncoding.js";
|
|
9
|
+
// Helper to clean JSON manifests
|
|
10
|
+
import { cleanJson } from "../utils/visuPropertyEncoding.js";
|
|
11
|
+
// ─── Zod Operation Schemas ───────────────────────────────────────────────────
|
|
12
|
+
const CreateDashboardOp = z.object({
|
|
13
|
+
op: z.literal("create_dashboard"),
|
|
14
|
+
kind: z.enum(["dashboard", "globalDashboard", "window", "controlTemplate"]),
|
|
15
|
+
name: z.string(),
|
|
16
|
+
width: z.string().optional(),
|
|
17
|
+
height: z.string().optional(),
|
|
18
|
+
});
|
|
19
|
+
const DeleteDashboardOp = z.object({
|
|
20
|
+
op: z.literal("delete_dashboard"),
|
|
21
|
+
kind: z.enum(["dashboard", "globalDashboard", "window", "controlTemplate"]),
|
|
22
|
+
name: z.string(),
|
|
23
|
+
});
|
|
24
|
+
const AddElementOp = z.object({
|
|
25
|
+
op: z.literal("add_element"),
|
|
26
|
+
dashboardName: z.string(),
|
|
27
|
+
controlId: z.string(),
|
|
28
|
+
name: z.string(),
|
|
29
|
+
left: z.string().optional(),
|
|
30
|
+
top: z.string().optional(),
|
|
31
|
+
width: z.string().optional(),
|
|
32
|
+
height: z.string().optional(),
|
|
33
|
+
});
|
|
34
|
+
const RemoveElementOp = z.object({
|
|
35
|
+
op: z.literal("remove_element"),
|
|
36
|
+
dashboardName: z.string(),
|
|
37
|
+
name: z.string(),
|
|
38
|
+
});
|
|
39
|
+
const PropertyBindInput = z.object({
|
|
40
|
+
name: z.string(),
|
|
41
|
+
sourceType: z.enum([
|
|
42
|
+
"constString",
|
|
43
|
+
"constNumber",
|
|
44
|
+
"constBool",
|
|
45
|
+
"datapoint",
|
|
46
|
+
"text",
|
|
47
|
+
"colorScheme",
|
|
48
|
+
"stateScheme",
|
|
49
|
+
"functionBlock",
|
|
50
|
+
"compositeControl",
|
|
51
|
+
"imageOrMedia",
|
|
52
|
+
"fontStyle",
|
|
53
|
+
"styleClass",
|
|
54
|
+
]),
|
|
55
|
+
value: z.any(),
|
|
56
|
+
});
|
|
57
|
+
const SetElementPropertiesOp = z.object({
|
|
58
|
+
op: z.literal("set_element_properties"),
|
|
59
|
+
dashboardName: z.string(),
|
|
60
|
+
name: z
|
|
61
|
+
.string()
|
|
62
|
+
.optional()
|
|
63
|
+
.describe("Element name to update. Omit or set to '' or '__root' to edit the root dashboard properties."),
|
|
64
|
+
properties: z.array(PropertyBindInput),
|
|
65
|
+
});
|
|
66
|
+
const MoveElementOp = z.object({
|
|
67
|
+
op: z.literal("move_element"),
|
|
68
|
+
dashboardName: z.string(),
|
|
69
|
+
name: z.string(),
|
|
70
|
+
left: z.string().optional(),
|
|
71
|
+
top: z.string().optional(),
|
|
72
|
+
});
|
|
73
|
+
const ResizeElementOp = z.object({
|
|
74
|
+
op: z.literal("resize_element"),
|
|
75
|
+
dashboardName: z.string(),
|
|
76
|
+
name: z.string(),
|
|
77
|
+
width: z.string().optional(),
|
|
78
|
+
height: z.string().optional(),
|
|
79
|
+
});
|
|
80
|
+
const DuplicateElementOp = z.object({
|
|
81
|
+
op: z.literal("duplicate_element"),
|
|
82
|
+
dashboardName: z.string(),
|
|
83
|
+
name: z.string(),
|
|
84
|
+
newName: z.string(),
|
|
85
|
+
});
|
|
86
|
+
const AddCompositeInstanceOp = z.object({
|
|
87
|
+
op: z.literal("add_composite_instance"),
|
|
88
|
+
dashboardName: z.string(),
|
|
89
|
+
templateName: z.string(),
|
|
90
|
+
name: z.string(),
|
|
91
|
+
left: z.string().optional(),
|
|
92
|
+
top: z.string().optional(),
|
|
93
|
+
width: z.string().optional(),
|
|
94
|
+
height: z.string().optional(),
|
|
95
|
+
});
|
|
96
|
+
const CreateCompositeTemplateOp = z.object({
|
|
97
|
+
op: z.literal("create_composite_template"),
|
|
98
|
+
name: z.string(),
|
|
99
|
+
width: z.string().optional(),
|
|
100
|
+
height: z.string().optional(),
|
|
101
|
+
});
|
|
102
|
+
const DescribeControlTypeOp = z.object({
|
|
103
|
+
op: z.literal("describe_control_type"),
|
|
104
|
+
controlId: z.string(),
|
|
105
|
+
});
|
|
106
|
+
const ListControlTypesOp = z.object({
|
|
107
|
+
op: z.literal("list_control_types"),
|
|
108
|
+
});
|
|
109
|
+
const VisuDashboardOperation = z.discriminatedUnion("op", [
|
|
110
|
+
CreateDashboardOp,
|
|
111
|
+
DeleteDashboardOp,
|
|
112
|
+
AddElementOp,
|
|
113
|
+
RemoveElementOp,
|
|
114
|
+
SetElementPropertiesOp,
|
|
115
|
+
MoveElementOp,
|
|
116
|
+
ResizeElementOp,
|
|
117
|
+
DuplicateElementOp,
|
|
118
|
+
AddCompositeInstanceOp,
|
|
119
|
+
CreateCompositeTemplateOp,
|
|
120
|
+
DescribeControlTypeOp,
|
|
121
|
+
ListControlTypesOp,
|
|
122
|
+
]);
|
|
123
|
+
export const visuDashboardSchema = {
|
|
124
|
+
lvp_path: z
|
|
125
|
+
.string()
|
|
126
|
+
.optional()
|
|
127
|
+
.describe("Full path to the .lvp file. Omit to auto-detect from the selected project."),
|
|
128
|
+
operations: z.array(VisuDashboardOperation).describe("List of dashboard and template operations to execute"),
|
|
129
|
+
};
|
|
130
|
+
// ─── Helper Functions ────────────────────────────────────────────────────────
|
|
131
|
+
function getDashboardFilePath(projectDir, kind, name) {
|
|
132
|
+
let sub = "Dashboards";
|
|
133
|
+
if (kind === "globalDashboard")
|
|
134
|
+
sub = "GlobalDashboards";
|
|
135
|
+
else if (kind === "window")
|
|
136
|
+
sub = "Window";
|
|
137
|
+
else if (kind === "controlTemplate")
|
|
138
|
+
sub = "ControlTemplate";
|
|
139
|
+
return join(projectDir, sub, `${name}.json`);
|
|
140
|
+
}
|
|
141
|
+
function findDashboardFile(projectDir, name) {
|
|
142
|
+
for (const kind of ["dashboard", "globalDashboard", "window", "controlTemplate"]) {
|
|
143
|
+
const p = getDashboardFilePath(projectDir, kind, name);
|
|
144
|
+
if (existsSync(p))
|
|
145
|
+
return { path: p, kind };
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
function updateLayoutProp(properties, name, value) {
|
|
150
|
+
if (value === undefined)
|
|
151
|
+
return;
|
|
152
|
+
let typeId = 0;
|
|
153
|
+
let propTypeId = 2;
|
|
154
|
+
let realVal = value;
|
|
155
|
+
if (name === "rotation") {
|
|
156
|
+
realVal = parseFloat(value) || 0.0;
|
|
157
|
+
typeId = 17;
|
|
158
|
+
propTypeId = 12;
|
|
159
|
+
}
|
|
160
|
+
else if (name === "--theme-sig-element-zindex") {
|
|
161
|
+
typeId = 0;
|
|
162
|
+
propTypeId = 2;
|
|
163
|
+
}
|
|
164
|
+
else if (name.startsWith("--theme-sig-element-")) {
|
|
165
|
+
typeId = 17;
|
|
166
|
+
propTypeId = 2;
|
|
167
|
+
}
|
|
168
|
+
const existing = properties.find((p) => p.name === name);
|
|
169
|
+
if (existing) {
|
|
170
|
+
existing.value = realVal;
|
|
171
|
+
existing.typeId = typeId;
|
|
172
|
+
existing.propTypeId = propTypeId;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
properties.push({
|
|
176
|
+
name,
|
|
177
|
+
value: realVal,
|
|
178
|
+
typeId,
|
|
179
|
+
propTypeId,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// ─── Handler ─────────────────────────────────────────────────────────────────
|
|
184
|
+
export async function visuDashboardHandler(args) {
|
|
185
|
+
const resolved = resolveLvpPath(args.lvp_path);
|
|
186
|
+
if ("error" in resolved) {
|
|
187
|
+
return { content: [{ type: "text", text: resolved.error }], isError: true };
|
|
188
|
+
}
|
|
189
|
+
const lvpPath = resolved.path;
|
|
190
|
+
const projectDir = dirname(lvpPath);
|
|
191
|
+
const results = [];
|
|
192
|
+
const backups = [];
|
|
193
|
+
let isError = false;
|
|
194
|
+
const tx = new EditTransaction();
|
|
195
|
+
const backup = (p) => {
|
|
196
|
+
tx.backup(p);
|
|
197
|
+
backups.push(p);
|
|
198
|
+
};
|
|
199
|
+
await withEngineLock(async () => {
|
|
200
|
+
// Safety: kill VISUDesigner before any direct file writes
|
|
201
|
+
killVisuDesigner();
|
|
202
|
+
for (const op of args.operations) {
|
|
203
|
+
const opResult = { op: op.op };
|
|
204
|
+
try {
|
|
205
|
+
switch (op.op) {
|
|
206
|
+
case "list_control_types": {
|
|
207
|
+
const list = [];
|
|
208
|
+
const roots = [
|
|
209
|
+
join(projectDir, "Runtime", "DesignerRuntime", "res", "components", "user"),
|
|
210
|
+
join(projectDir, "Runtime", "DesignerRuntime", "res", "components", "sigmatek"),
|
|
211
|
+
];
|
|
212
|
+
for (const r of roots) {
|
|
213
|
+
if (!existsSync(r))
|
|
214
|
+
continue;
|
|
215
|
+
for (const entry of readdirSync(r)) {
|
|
216
|
+
const path = join(r, entry, `${entry}.json`);
|
|
217
|
+
if (existsSync(path)) {
|
|
218
|
+
try {
|
|
219
|
+
const manifest = JSON.parse(cleanJson(readFileSync(path, "utf-8")));
|
|
220
|
+
list.push({
|
|
221
|
+
controlId: entry,
|
|
222
|
+
shortName: manifest.shortName?.en || entry,
|
|
223
|
+
version: manifest.version || "unknown",
|
|
224
|
+
description: manifest.description?.en || "",
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
catch { }
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
opResult.ok = true;
|
|
232
|
+
opResult.controlTypes = list;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
case "describe_control_type": {
|
|
236
|
+
const manifest = loadControlManifest(projectDir, op.controlId);
|
|
237
|
+
if (!manifest) {
|
|
238
|
+
throw new Error(`Control manifest for '${op.controlId}' not found.`);
|
|
239
|
+
}
|
|
240
|
+
opResult.ok = true;
|
|
241
|
+
opResult.controlType = {
|
|
242
|
+
controlId: manifest.name,
|
|
243
|
+
description: manifest.description?.en || "",
|
|
244
|
+
properties: (manifest.properties || []).map((p) => ({
|
|
245
|
+
name: p.name,
|
|
246
|
+
dataType: p.dataType || "string",
|
|
247
|
+
group: p.group?.en || "Custom",
|
|
248
|
+
description: p.description?.en || "",
|
|
249
|
+
valueSourceTypes: Array.isArray(p.valueSourceTypes)
|
|
250
|
+
? p.valueSourceTypes
|
|
251
|
+
: p.valueSourceTypes
|
|
252
|
+
? [p.valueSourceTypes]
|
|
253
|
+
: ["constString"],
|
|
254
|
+
})),
|
|
255
|
+
};
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
case "create_dashboard": {
|
|
259
|
+
const path = getDashboardFilePath(projectDir, op.kind, op.name);
|
|
260
|
+
if (existsSync(path)) {
|
|
261
|
+
throw new Error(`File already exists at ${path}`);
|
|
262
|
+
}
|
|
263
|
+
const verKey = op.kind === "controlTemplate"
|
|
264
|
+
? "controlTemplateVersion"
|
|
265
|
+
: op.kind === "globalDashboard"
|
|
266
|
+
? "globalDashboardVersion"
|
|
267
|
+
: op.kind === "window"
|
|
268
|
+
? "windowVersion"
|
|
269
|
+
: "dashboardVersion";
|
|
270
|
+
const version = getFileEntryVersion(lvpPath, verKey);
|
|
271
|
+
const content = {
|
|
272
|
+
type: op.kind === "controlTemplate" ? "controlTemplate" : "dashboard",
|
|
273
|
+
version,
|
|
274
|
+
gridWidth: "10px",
|
|
275
|
+
gridHeight: "10px",
|
|
276
|
+
gridColor: "#000000",
|
|
277
|
+
gridStyle: "dotgrid",
|
|
278
|
+
revision: { type: "Revision" },
|
|
279
|
+
designTimeId: newDesignTimeId(),
|
|
280
|
+
name: op.name,
|
|
281
|
+
instanceId: newInstanceId("design_"),
|
|
282
|
+
controlId: "sig-dashboard",
|
|
283
|
+
properties: [
|
|
284
|
+
{ name: "position", value: "absolute", typeId: 0, propTypeId: 2 },
|
|
285
|
+
{ name: "top", value: "0px", typeId: 0, propTypeId: 2 },
|
|
286
|
+
{ name: "left", value: "0px", typeId: 0, propTypeId: 2 },
|
|
287
|
+
{ name: "height", value: op.height || "800px", typeId: 0, propTypeId: 2 },
|
|
288
|
+
{ name: "width", value: op.width || "1024px", typeId: 0, propTypeId: 2 },
|
|
289
|
+
{ name: "overflow", value: "visible", typeId: 0, propTypeId: 2 },
|
|
290
|
+
{ name: "background", value: "transparent", typeId: 0, propTypeId: 2 },
|
|
291
|
+
],
|
|
292
|
+
dashboardelements: [],
|
|
293
|
+
};
|
|
294
|
+
writeTabIndentedJson(path, content);
|
|
295
|
+
opResult.ok = true;
|
|
296
|
+
opResult.path = path;
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
case "create_composite_template": {
|
|
300
|
+
const path = getDashboardFilePath(projectDir, "controlTemplate", op.name);
|
|
301
|
+
if (existsSync(path)) {
|
|
302
|
+
throw new Error(`File already exists at ${path}`);
|
|
303
|
+
}
|
|
304
|
+
const version = getFileEntryVersion(lvpPath, "controlTemplateVersion");
|
|
305
|
+
const content = {
|
|
306
|
+
type: "controlTemplate",
|
|
307
|
+
version,
|
|
308
|
+
gridWidth: "10px",
|
|
309
|
+
gridHeight: "10px",
|
|
310
|
+
gridColor: "#000000",
|
|
311
|
+
gridStyle: "dotgrid",
|
|
312
|
+
revision: { type: "Revision" },
|
|
313
|
+
designTimeId: newDesignTimeId(),
|
|
314
|
+
name: op.name,
|
|
315
|
+
instanceId: newInstanceId("design_"),
|
|
316
|
+
controlId: "sig-dashboard",
|
|
317
|
+
properties: [
|
|
318
|
+
{ name: "position", value: "absolute", typeId: 0, propTypeId: 2 },
|
|
319
|
+
{ name: "overflow", value: "visible", typeId: 0, propTypeId: 2 },
|
|
320
|
+
{ name: "background", value: "transparent", typeId: 0, propTypeId: 2 },
|
|
321
|
+
],
|
|
322
|
+
dashboardelements: [],
|
|
323
|
+
};
|
|
324
|
+
writeTabIndentedJson(path, content);
|
|
325
|
+
opResult.ok = true;
|
|
326
|
+
opResult.path = path;
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
case "delete_dashboard": {
|
|
330
|
+
const path = getDashboardFilePath(projectDir, op.kind, op.name);
|
|
331
|
+
if (!existsSync(path)) {
|
|
332
|
+
throw new Error(`File not found at ${path}`);
|
|
333
|
+
}
|
|
334
|
+
backup(path);
|
|
335
|
+
unlinkSync(path);
|
|
336
|
+
opResult.ok = true;
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
case "add_element": {
|
|
340
|
+
const found = findDashboardFile(projectDir, op.dashboardName);
|
|
341
|
+
if (!found) {
|
|
342
|
+
throw new Error(`Dashboard/template '${op.dashboardName}' not found.`);
|
|
343
|
+
}
|
|
344
|
+
backup(found.path);
|
|
345
|
+
const data = JSON.parse(readFileSync(found.path, "utf-8"));
|
|
346
|
+
if (!Array.isArray(data.dashboardelements)) {
|
|
347
|
+
data.dashboardelements = [];
|
|
348
|
+
}
|
|
349
|
+
if (data.dashboardelements.some((el) => el.name === op.name)) {
|
|
350
|
+
throw new Error(`Element with name '${op.name}' already exists in dashboard '${op.dashboardName}'.`);
|
|
351
|
+
}
|
|
352
|
+
const manifest = loadControlManifest(projectDir, op.controlId);
|
|
353
|
+
const defaultWidth = manifest?.defaultDimensions?.width || "100px";
|
|
354
|
+
const defaultHeight = manifest?.defaultDimensions?.height || "100px";
|
|
355
|
+
const elementProperties = [];
|
|
356
|
+
updateLayoutProp(elementProperties, "--theme-sig-element-left", op.left || "0px");
|
|
357
|
+
updateLayoutProp(elementProperties, "--theme-sig-element-top", op.top || "0px");
|
|
358
|
+
updateLayoutProp(elementProperties, "--theme-sig-element-width", op.width || defaultWidth);
|
|
359
|
+
updateLayoutProp(elementProperties, "--theme-sig-element-height", op.height || defaultHeight);
|
|
360
|
+
updateLayoutProp(elementProperties, "rotation", "0");
|
|
361
|
+
updateLayoutProp(elementProperties, "--theme-sig-element-zindex", "101");
|
|
362
|
+
const newEl = {
|
|
363
|
+
type: "control",
|
|
364
|
+
designTimeId: newDesignTimeId(),
|
|
365
|
+
name: op.name,
|
|
366
|
+
instanceId: newInstanceId("lvd"),
|
|
367
|
+
controlId: op.controlId,
|
|
368
|
+
properties: elementProperties,
|
|
369
|
+
};
|
|
370
|
+
data.dashboardelements.push(newEl);
|
|
371
|
+
writeTabIndentedJson(found.path, data);
|
|
372
|
+
opResult.ok = true;
|
|
373
|
+
opResult.designTimeId = newEl.designTimeId;
|
|
374
|
+
opResult.instanceId = newEl.instanceId;
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
case "remove_element": {
|
|
378
|
+
const found = findDashboardFile(projectDir, op.dashboardName);
|
|
379
|
+
if (!found) {
|
|
380
|
+
throw new Error(`Dashboard/template '${op.dashboardName}' not found.`);
|
|
381
|
+
}
|
|
382
|
+
backup(found.path);
|
|
383
|
+
const data = JSON.parse(readFileSync(found.path, "utf-8"));
|
|
384
|
+
const origCount = data.dashboardelements?.length || 0;
|
|
385
|
+
data.dashboardelements = (data.dashboardelements || []).filter((el) => el.name !== op.name);
|
|
386
|
+
if (data.dashboardelements.length === origCount) {
|
|
387
|
+
throw new Error(`Element '${op.name}' not found in dashboard '${op.dashboardName}'.`);
|
|
388
|
+
}
|
|
389
|
+
writeTabIndentedJson(found.path, data);
|
|
390
|
+
opResult.ok = true;
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
case "set_element_properties": {
|
|
394
|
+
const found = findDashboardFile(projectDir, op.dashboardName);
|
|
395
|
+
if (!found) {
|
|
396
|
+
throw new Error(`Dashboard/template '${op.dashboardName}' not found.`);
|
|
397
|
+
}
|
|
398
|
+
backup(found.path);
|
|
399
|
+
const data = JSON.parse(readFileSync(found.path, "utf-8"));
|
|
400
|
+
const targetName = op.name || "";
|
|
401
|
+
let targetProps = null;
|
|
402
|
+
let controlId = "sig-dashboard";
|
|
403
|
+
if (targetName === "" ||
|
|
404
|
+
targetName === "__root" ||
|
|
405
|
+
targetName.toLowerCase() === op.dashboardName.toLowerCase()) {
|
|
406
|
+
targetProps = data.properties;
|
|
407
|
+
controlId = data.controlId || "sig-dashboard";
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
const el = (data.dashboardelements || []).find((x) => x.name === targetName);
|
|
411
|
+
if (!el) {
|
|
412
|
+
throw new Error(`Element '${targetName}' not found in dashboard '${op.dashboardName}'.`);
|
|
413
|
+
}
|
|
414
|
+
targetProps = el.properties;
|
|
415
|
+
controlId = el.controlId;
|
|
416
|
+
}
|
|
417
|
+
if (!targetProps) {
|
|
418
|
+
throw new Error(`Properties list not found for target '${targetName}'.`);
|
|
419
|
+
}
|
|
420
|
+
const manifest = loadControlManifest(projectDir, controlId);
|
|
421
|
+
for (const propBind of op.properties) {
|
|
422
|
+
const manifestProp = manifest?.properties?.find((p) => p.name === propBind.name);
|
|
423
|
+
const encoded = encodeProperty(projectDir, propBind.name, manifestProp, propBind.sourceType, propBind.value);
|
|
424
|
+
const idx = targetProps.findIndex((p) => p.name === propBind.name);
|
|
425
|
+
if (idx >= 0) {
|
|
426
|
+
targetProps[idx] = encoded;
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
targetProps.push(encoded);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
writeTabIndentedJson(found.path, data);
|
|
433
|
+
opResult.ok = true;
|
|
434
|
+
break;
|
|
435
|
+
}
|
|
436
|
+
case "move_element": {
|
|
437
|
+
const found = findDashboardFile(projectDir, op.dashboardName);
|
|
438
|
+
if (!found) {
|
|
439
|
+
throw new Error(`Dashboard/template '${op.dashboardName}' not found.`);
|
|
440
|
+
}
|
|
441
|
+
backup(found.path);
|
|
442
|
+
const data = JSON.parse(readFileSync(found.path, "utf-8"));
|
|
443
|
+
const el = (data.dashboardelements || []).find((x) => x.name === op.name);
|
|
444
|
+
if (!el) {
|
|
445
|
+
throw new Error(`Element '${op.name}' not found in '${op.dashboardName}'.`);
|
|
446
|
+
}
|
|
447
|
+
updateLayoutProp(el.properties, "--theme-sig-element-left", op.left);
|
|
448
|
+
updateLayoutProp(el.properties, "--theme-sig-element-top", op.top);
|
|
449
|
+
writeTabIndentedJson(found.path, data);
|
|
450
|
+
opResult.ok = true;
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
case "resize_element": {
|
|
454
|
+
const found = findDashboardFile(projectDir, op.dashboardName);
|
|
455
|
+
if (!found) {
|
|
456
|
+
throw new Error(`Dashboard/template '${op.dashboardName}' not found.`);
|
|
457
|
+
}
|
|
458
|
+
backup(found.path);
|
|
459
|
+
const data = JSON.parse(readFileSync(found.path, "utf-8"));
|
|
460
|
+
const el = (data.dashboardelements || []).find((x) => x.name === op.name);
|
|
461
|
+
if (!el) {
|
|
462
|
+
throw new Error(`Element '${op.name}' not found in '${op.dashboardName}'.`);
|
|
463
|
+
}
|
|
464
|
+
updateLayoutProp(el.properties, "--theme-sig-element-width", op.width);
|
|
465
|
+
updateLayoutProp(el.properties, "--theme-sig-element-height", op.height);
|
|
466
|
+
writeTabIndentedJson(found.path, data);
|
|
467
|
+
opResult.ok = true;
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
case "duplicate_element": {
|
|
471
|
+
const found = findDashboardFile(projectDir, op.dashboardName);
|
|
472
|
+
if (!found) {
|
|
473
|
+
throw new Error(`Dashboard/template '${op.dashboardName}' not found.`);
|
|
474
|
+
}
|
|
475
|
+
backup(found.path);
|
|
476
|
+
const data = JSON.parse(readFileSync(found.path, "utf-8"));
|
|
477
|
+
const srcEl = (data.dashboardelements || []).find((x) => x.name === op.name);
|
|
478
|
+
if (!srcEl) {
|
|
479
|
+
throw new Error(`Element '${op.name}' to duplicate not found.`);
|
|
480
|
+
}
|
|
481
|
+
if (data.dashboardelements.some((x) => x.name === op.newName)) {
|
|
482
|
+
throw new Error(`An element with name '${op.newName}' already exists.`);
|
|
483
|
+
}
|
|
484
|
+
const duplicated = JSON.parse(JSON.stringify(srcEl));
|
|
485
|
+
duplicated.name = op.newName;
|
|
486
|
+
duplicated.designTimeId = newDesignTimeId();
|
|
487
|
+
duplicated.instanceId = newInstanceId(duplicated.type === "compositecontainer" ? "lvd" : "lvd");
|
|
488
|
+
// shift duplicate slightly so it's visible if stacked directly
|
|
489
|
+
const leftProp = duplicated.properties.find((p) => p.name === "--theme-sig-element-left");
|
|
490
|
+
if (leftProp && typeof leftProp.value === "string" && leftProp.value.endsWith("px")) {
|
|
491
|
+
const val = parseInt(leftProp.value) + 10;
|
|
492
|
+
leftProp.value = `${val}px`;
|
|
493
|
+
}
|
|
494
|
+
data.dashboardelements.push(duplicated);
|
|
495
|
+
writeTabIndentedJson(found.path, data);
|
|
496
|
+
opResult.ok = true;
|
|
497
|
+
opResult.designTimeId = duplicated.designTimeId;
|
|
498
|
+
opResult.instanceId = duplicated.instanceId;
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
case "add_composite_instance": {
|
|
502
|
+
const found = findDashboardFile(projectDir, op.dashboardName);
|
|
503
|
+
if (!found) {
|
|
504
|
+
throw new Error(`Dashboard/template '${op.dashboardName}' not found.`);
|
|
505
|
+
}
|
|
506
|
+
const templateFile = getDashboardFilePath(projectDir, "controlTemplate", op.templateName);
|
|
507
|
+
if (!existsSync(templateFile)) {
|
|
508
|
+
throw new Error(`Composite template '${op.templateName}' not found at ${templateFile}.`);
|
|
509
|
+
}
|
|
510
|
+
const templateData = JSON.parse(readFileSync(templateFile, "utf-8"));
|
|
511
|
+
const templateId = templateData.designTimeId;
|
|
512
|
+
backup(found.path);
|
|
513
|
+
const data = JSON.parse(readFileSync(found.path, "utf-8"));
|
|
514
|
+
if (data.dashboardelements?.some((x) => x.name === op.name)) {
|
|
515
|
+
throw new Error(`Element with name '${op.name}' already exists.`);
|
|
516
|
+
}
|
|
517
|
+
const elementProperties = [];
|
|
518
|
+
elementProperties.push({
|
|
519
|
+
name: "sigcompositectrl",
|
|
520
|
+
value: op.templateName,
|
|
521
|
+
typeId: 23,
|
|
522
|
+
propTypeId: 5,
|
|
523
|
+
refId: templateId,
|
|
524
|
+
});
|
|
525
|
+
updateLayoutProp(elementProperties, "--theme-sig-element-left", op.left || "0px");
|
|
526
|
+
updateLayoutProp(elementProperties, "--theme-sig-element-top", op.top || "0px");
|
|
527
|
+
updateLayoutProp(elementProperties, "--theme-sig-element-width", op.width || "200px");
|
|
528
|
+
updateLayoutProp(elementProperties, "--theme-sig-element-height", op.height || "150px");
|
|
529
|
+
updateLayoutProp(elementProperties, "rotation", "0");
|
|
530
|
+
updateLayoutProp(elementProperties, "--theme-sig-element-zindex", "101");
|
|
531
|
+
const newComp = {
|
|
532
|
+
type: "compositecontainer",
|
|
533
|
+
controlId: "sig-composite-container",
|
|
534
|
+
designTimeId: newDesignTimeId(),
|
|
535
|
+
name: op.name,
|
|
536
|
+
instanceId: newInstanceId("lvd"),
|
|
537
|
+
properties: elementProperties,
|
|
538
|
+
};
|
|
539
|
+
data.dashboardelements = data.dashboardelements || [];
|
|
540
|
+
data.dashboardelements.push(newComp);
|
|
541
|
+
writeTabIndentedJson(found.path, data);
|
|
542
|
+
opResult.ok = true;
|
|
543
|
+
opResult.designTimeId = newComp.designTimeId;
|
|
544
|
+
opResult.instanceId = newComp.instanceId;
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
catch (e) {
|
|
550
|
+
opResult.ok = false;
|
|
551
|
+
opResult.error = e.message;
|
|
552
|
+
isError = true;
|
|
553
|
+
}
|
|
554
|
+
results.push(opResult);
|
|
555
|
+
}
|
|
556
|
+
if (isError) {
|
|
557
|
+
tx.rollback();
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
tx.commit();
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
const responseBody = {
|
|
564
|
+
results,
|
|
565
|
+
backups: backups.map((b) => basename(b)),
|
|
566
|
+
};
|
|
567
|
+
return {
|
|
568
|
+
content: [{ type: "text", text: JSON.stringify(responseBody, null, 2) }],
|
|
569
|
+
isError,
|
|
570
|
+
};
|
|
571
|
+
}
|