zinkee 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/AGENTS.md +46 -0
- package/README.md +395 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +7142 -0
- package/dist/index.js.map +1 -0
- package/package.json +32 -0
- package/src/api/automations.ts +403 -0
- package/src/api/comments.ts +51 -0
- package/src/api/displays.ts +276 -0
- package/src/api/files.ts +50 -0
- package/src/api/navigation.ts +71 -0
- package/src/api/records.ts +167 -0
- package/src/api/schemas.ts +130 -0
- package/src/api/teamspace.ts +110 -0
- package/src/cli-examples.ts +175 -0
- package/src/cli-runner.ts +73 -0
- package/src/client.test.ts +128 -0
- package/src/client.ts +243 -0
- package/src/command-registry.ts +749 -0
- package/src/commands/automations.test.ts +728 -0
- package/src/commands/automations.ts +2013 -0
- package/src/commands/comments.test.ts +214 -0
- package/src/commands/comments.ts +303 -0
- package/src/commands/config.test.ts +81 -0
- package/src/commands/config.ts +150 -0
- package/src/commands/displays.test.ts +266 -0
- package/src/commands/displays.ts +755 -0
- package/src/commands/files.test.ts +284 -0
- package/src/commands/files.ts +280 -0
- package/src/commands/navigation.test.ts +214 -0
- package/src/commands/navigation.ts +348 -0
- package/src/commands/profiles.test.ts +82 -0
- package/src/commands/profiles.ts +281 -0
- package/src/commands/records.test.ts +367 -0
- package/src/commands/records.ts +726 -0
- package/src/commands/schemas.test.ts +748 -0
- package/src/commands/schemas.ts +746 -0
- package/src/commands/teamspace.test.ts +229 -0
- package/src/commands/teamspace.ts +540 -0
- package/src/config.test.ts +165 -0
- package/src/config.ts +248 -0
- package/src/index.test.ts +140 -0
- package/src/index.ts +8 -0
- package/src/parsers/expressions.test.ts +50 -0
- package/src/parsers/expressions.ts +111 -0
- package/src/parsers/kv.test.ts +35 -0
- package/src/parsers/kv.ts +49 -0
- package/src/parsers/selectors.test.ts +23 -0
- package/src/parsers/selectors.ts +29 -0
- package/src/program.ts +56 -0
- package/src/runtime-context.ts +18 -0
- package/src/types.ts +71 -0
- package/src/utils/errors.test.ts +48 -0
- package/src/utils/errors.ts +183 -0
- package/src/utils/examples.test.ts +67 -0
- package/src/utils/examples.ts +855 -0
- package/src/utils/output.test.ts +39 -0
- package/src/utils/output.ts +124 -0
- package/src/utils/schema-fields.ts +529 -0
- package/tsconfig.json +20 -0
- package/tsup.config.ts +13 -0
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import type { Writable } from "node:stream";
|
|
3
|
+
|
|
4
|
+
import { Command, InvalidArgumentError } from "commander";
|
|
5
|
+
|
|
6
|
+
import { DisplaysApi, type DisplayResource, type DisplaySummary } from "../api/displays.js";
|
|
7
|
+
import { ApiClient } from "../client.js";
|
|
8
|
+
import { defaultConfigPath, readConfig } from "../config.js";
|
|
9
|
+
import { normalizeUuidOrSlug, type NormalizedUuidOrSlug } from "../parsers/selectors.js";
|
|
10
|
+
import { resolveRuntimeContext } from "../runtime-context.js";
|
|
11
|
+
import type { RuntimeOverrides } from "../types.js";
|
|
12
|
+
import { CliError } from "../utils/errors.js";
|
|
13
|
+
import { buildJsonSuccess, printTable } from "../utils/output.js";
|
|
14
|
+
|
|
15
|
+
interface CommandIo {
|
|
16
|
+
write(chunk: string): unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface RegisterDisplaysCommandsOptions {
|
|
20
|
+
configPath?: string;
|
|
21
|
+
stdout?: CommandIo;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface GlobalOptions extends RuntimeOverrides {}
|
|
25
|
+
|
|
26
|
+
interface DisplayCreateOptions {
|
|
27
|
+
name: string;
|
|
28
|
+
template: string;
|
|
29
|
+
freeformLayouts?: string;
|
|
30
|
+
freeformLayoutsFile?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface DisplayUpdateOptions {
|
|
34
|
+
name?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface LayoutUpdateOptions {
|
|
38
|
+
template: string;
|
|
39
|
+
freeformLayouts?: string;
|
|
40
|
+
freeformLayoutsFile?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface TabMutationOptions {
|
|
44
|
+
name?: string;
|
|
45
|
+
orientation?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface VariableMutationOptions {
|
|
49
|
+
type?: string;
|
|
50
|
+
name?: string;
|
|
51
|
+
allowMultiple?: boolean;
|
|
52
|
+
static?: boolean;
|
|
53
|
+
default?: string;
|
|
54
|
+
defaultValue?: string[];
|
|
55
|
+
sourceSchema?: string;
|
|
56
|
+
sourceField?: string;
|
|
57
|
+
filter?: string[];
|
|
58
|
+
dateMode?: string;
|
|
59
|
+
dateDynamicOption?: string;
|
|
60
|
+
dateSpecific?: string;
|
|
61
|
+
dateRangeStart?: string;
|
|
62
|
+
dateRangeEnd?: string;
|
|
63
|
+
raw?: string;
|
|
64
|
+
rawFile?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface WidgetMutationOptions {
|
|
68
|
+
type?: string;
|
|
69
|
+
name?: string;
|
|
70
|
+
description?: string;
|
|
71
|
+
icon?: string;
|
|
72
|
+
color?: string;
|
|
73
|
+
originResource?: string;
|
|
74
|
+
originSubschema?: boolean;
|
|
75
|
+
tab?: string;
|
|
76
|
+
slot?: string;
|
|
77
|
+
layout?: string;
|
|
78
|
+
layoutFile?: string;
|
|
79
|
+
field?: string[];
|
|
80
|
+
sort?: string[];
|
|
81
|
+
filter?: string[];
|
|
82
|
+
widgetReadOnly?: boolean;
|
|
83
|
+
hideAddButton?: boolean;
|
|
84
|
+
hideDeleteButton?: boolean;
|
|
85
|
+
hideSortButton?: boolean;
|
|
86
|
+
hideFilterButton?: boolean;
|
|
87
|
+
hideSeeButton?: boolean;
|
|
88
|
+
calc?: string;
|
|
89
|
+
calcField?: string;
|
|
90
|
+
kpiConfig?: string;
|
|
91
|
+
chartType?: string;
|
|
92
|
+
xAxisField?: string;
|
|
93
|
+
xAxisDateFormat?: string;
|
|
94
|
+
xAxisTimeSeries?: boolean;
|
|
95
|
+
yAxisSerie?: string[];
|
|
96
|
+
legend?: string;
|
|
97
|
+
raw?: string;
|
|
98
|
+
rawFile?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
interface NavigationTargetMutationOptions {
|
|
102
|
+
sourceWidget?: string;
|
|
103
|
+
targetDisplay?: string;
|
|
104
|
+
targetVariable?: string;
|
|
105
|
+
label?: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
interface CrossWidgetFilterSetOptions {
|
|
109
|
+
sourceWidget: string;
|
|
110
|
+
sourceField: string;
|
|
111
|
+
target: string[];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function getCommandIo(stdout: CommandIo | undefined): CommandIo {
|
|
115
|
+
return stdout ?? (process.stdout as Writable);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function writeLine(stdout: CommandIo, value: string): void {
|
|
119
|
+
stdout.write(`${value}\n`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function getGlobalOptions(command: Command): GlobalOptions {
|
|
123
|
+
return command.optsWithGlobals<GlobalOptions>();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function emitJson(stdout: CommandIo, commandName: string, data: unknown): void {
|
|
127
|
+
writeLine(stdout, JSON.stringify(buildJsonSuccess(data, { command: commandName })));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function toConfigurationError(filePath: string, error: unknown): never {
|
|
131
|
+
if (error instanceof CliError) {
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
136
|
+
throw new CliError("configuration_error", `Config file not found at ${filePath}.`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (error instanceof Error) {
|
|
140
|
+
throw new CliError("configuration_error", error.message);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function createDisplaysApi(
|
|
147
|
+
command: Command,
|
|
148
|
+
filePath: string,
|
|
149
|
+
): { api: DisplaysApi; json: boolean; readOnly: boolean } {
|
|
150
|
+
try {
|
|
151
|
+
const config = readConfig(filePath);
|
|
152
|
+
const runtime = resolveRuntimeContext(config, getGlobalOptions(command));
|
|
153
|
+
|
|
154
|
+
if (!runtime.baseUrl) {
|
|
155
|
+
throw new CliError("configuration_error", `Resolved profile "${runtime.profile}" is missing a base URL.`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
api: new DisplaysApi(
|
|
160
|
+
new ApiClient({
|
|
161
|
+
baseUrl: runtime.baseUrl,
|
|
162
|
+
apiKey: runtime.apiKey,
|
|
163
|
+
}),
|
|
164
|
+
),
|
|
165
|
+
json: runtime.json,
|
|
166
|
+
readOnly: runtime.readOnly,
|
|
167
|
+
};
|
|
168
|
+
} catch (error) {
|
|
169
|
+
toConfigurationError(filePath, error);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function ensureWritable(commandName: string, readOnly: boolean, mode: "write" | "destructive"): void {
|
|
174
|
+
if (readOnly) {
|
|
175
|
+
throw new CliError(
|
|
176
|
+
"command_not_allowed_in_read_only_mode",
|
|
177
|
+
`Command "${commandName}" is not allowed in read-only mode.`,
|
|
178
|
+
{ commandMode: mode, executionMode: "read-only" },
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function parseUuidOrSlugSelector(input: string): NormalizedUuidOrSlug {
|
|
184
|
+
try {
|
|
185
|
+
return normalizeUuidOrSlug(input);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
const message = error instanceof Error ? error.message : "Invalid selector.";
|
|
188
|
+
throw new InvalidArgumentError(message);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function parseBooleanFlag(input: string): boolean {
|
|
193
|
+
const normalized = input.trim().toLowerCase();
|
|
194
|
+
if (["true", "1", "yes", "y", "on"].includes(normalized)) return true;
|
|
195
|
+
if (["false", "0", "no", "n", "off"].includes(normalized)) return false;
|
|
196
|
+
throw new InvalidArgumentError(`Expected a boolean value, received "${input}".`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function parseNonNegativeInteger(input: string): number {
|
|
200
|
+
if (!/^\d+$/.test(input.trim())) {
|
|
201
|
+
throw new InvalidArgumentError(`Expected a non-negative integer, received "${input}".`);
|
|
202
|
+
}
|
|
203
|
+
return Number(input);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function readRawObject(raw?: string, rawFile?: string): Record<string, unknown> {
|
|
207
|
+
if (raw && rawFile) {
|
|
208
|
+
throw new CliError("invalid_cli_usage", 'Use either "--raw" or "--raw-file", not both.');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const source = rawFile ? readFileSync(rawFile, "utf8") : raw;
|
|
212
|
+
if (!source) {
|
|
213
|
+
return {};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const parsed = JSON.parse(source) as unknown;
|
|
218
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
219
|
+
throw new CliError("invalid_cli_usage", "Raw JSON payload must be a JSON object.");
|
|
220
|
+
}
|
|
221
|
+
return parsed as Record<string, unknown>;
|
|
222
|
+
} catch (error) {
|
|
223
|
+
if (error instanceof CliError) throw error;
|
|
224
|
+
throw new CliError("invalid_cli_usage", "Raw JSON payload is invalid.");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function readNamedJsonOption(raw?: string, rawFile?: string, inlineFlag = "--option", fileFlag = "--option-file"): unknown {
|
|
229
|
+
if (raw && rawFile) {
|
|
230
|
+
throw new CliError("invalid_cli_usage", `Use either "${inlineFlag}" or "${fileFlag}", not both.`);
|
|
231
|
+
}
|
|
232
|
+
const source = rawFile ? readFileSync(rawFile, "utf8") : raw;
|
|
233
|
+
if (!source) return undefined;
|
|
234
|
+
try {
|
|
235
|
+
return JSON.parse(source) as unknown;
|
|
236
|
+
} catch {
|
|
237
|
+
throw new CliError("invalid_cli_usage", `${inlineFlag} payload is invalid JSON.`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function isEmptyObject(value: object): boolean {
|
|
242
|
+
return Object.keys(value).length === 0;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function displayRows(displays: readonly DisplaySummary[]): Record<string, unknown>[] {
|
|
246
|
+
return displays.map((display) => ({
|
|
247
|
+
id: display.id,
|
|
248
|
+
name: display.name ?? "",
|
|
249
|
+
layoutTemplate: display.layoutTemplate ?? "",
|
|
250
|
+
variableCount: display.variableCount ?? "",
|
|
251
|
+
widgetCount: display.widgetCount ?? "",
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function getDisplaySubresource(value: unknown): Record<string, unknown>[] {
|
|
256
|
+
if (!Array.isArray(value)) {
|
|
257
|
+
return [];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return value.filter((entry): entry is Record<string, unknown> => Boolean(entry) && typeof entry === "object");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function buildVariableBody(options: VariableMutationOptions): Record<string, unknown> {
|
|
264
|
+
const body = {
|
|
265
|
+
...readRawObject(options.raw, options.rawFile),
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
if (options.type !== undefined) body.type = options.type;
|
|
269
|
+
if (options.name !== undefined) body.name = options.name;
|
|
270
|
+
if (options.allowMultiple !== undefined) body.allowMultiple = options.allowMultiple;
|
|
271
|
+
if (options.static !== undefined) body.staticVariable = options.static;
|
|
272
|
+
if (options.default !== undefined) body.defaultValue = options.default;
|
|
273
|
+
if (options.defaultValue && options.defaultValue.length > 0) body.defaultValues = options.defaultValue;
|
|
274
|
+
if (options.sourceSchema || options.sourceField) {
|
|
275
|
+
body.source = {
|
|
276
|
+
schemaId: options.sourceSchema,
|
|
277
|
+
fieldId: options.sourceField,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
if (options.filter && options.filter.length > 0) {
|
|
281
|
+
body.filters = options.filter.map((entry) => ({ raw: entry }));
|
|
282
|
+
}
|
|
283
|
+
if (
|
|
284
|
+
options.dateMode ||
|
|
285
|
+
options.dateDynamicOption ||
|
|
286
|
+
options.dateSpecific ||
|
|
287
|
+
options.dateRangeStart ||
|
|
288
|
+
options.dateRangeEnd
|
|
289
|
+
) {
|
|
290
|
+
body.date = {
|
|
291
|
+
mode: options.dateMode,
|
|
292
|
+
dynamicOption: options.dateDynamicOption,
|
|
293
|
+
specificDate: options.dateSpecific,
|
|
294
|
+
rangeStart: options.dateRangeStart,
|
|
295
|
+
rangeEnd: options.dateRangeEnd,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (isEmptyObject(body)) {
|
|
300
|
+
throw new CliError("invalid_cli_usage", "Nothing to send for variable mutation.");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return body;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function buildWidgetBody(options: WidgetMutationOptions): Record<string, unknown> {
|
|
307
|
+
const body = {
|
|
308
|
+
...readRawObject(options.raw, options.rawFile),
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
if (options.type !== undefined) {
|
|
312
|
+
const normalizedType = options.type.toLowerCase() === "table"
|
|
313
|
+
? "TABLE_OR_SUBTABLE"
|
|
314
|
+
: options.type.toUpperCase();
|
|
315
|
+
body.type = normalizedType;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (
|
|
319
|
+
options.name !== undefined ||
|
|
320
|
+
options.description !== undefined ||
|
|
321
|
+
options.icon !== undefined ||
|
|
322
|
+
options.color !== undefined
|
|
323
|
+
) {
|
|
324
|
+
body.presentation = {
|
|
325
|
+
...(typeof body.presentation === "object" && body.presentation ? body.presentation as Record<string, unknown> : {}),
|
|
326
|
+
name: options.name,
|
|
327
|
+
description: options.description,
|
|
328
|
+
icon: options.icon,
|
|
329
|
+
color: options.color,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (options.originResource !== undefined || options.originSubschema !== undefined) {
|
|
334
|
+
body.source = {
|
|
335
|
+
...(typeof body.source === "object" && body.source ? body.source as Record<string, unknown> : {}),
|
|
336
|
+
originResourceId: options.originResource,
|
|
337
|
+
isOriginSubSchema: options.originSubschema,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const layout = readNamedJsonOption(options.layout, options.layoutFile, "--layout", "--layout-file");
|
|
342
|
+
if (options.tab || options.slot || layout !== undefined) {
|
|
343
|
+
body.placement = {
|
|
344
|
+
...(typeof body.placement === "object" && body.placement ? body.placement as Record<string, unknown> : {}),
|
|
345
|
+
tabId: options.tab,
|
|
346
|
+
slotId: options.slot,
|
|
347
|
+
layout,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (options.field?.length || options.sort?.length || options.filter?.length) {
|
|
352
|
+
body.view = {
|
|
353
|
+
...(typeof body.view === "object" && body.view ? body.view as Record<string, unknown> : {}),
|
|
354
|
+
fields: options.field,
|
|
355
|
+
};
|
|
356
|
+
body.query = {
|
|
357
|
+
...(typeof body.query === "object" && body.query ? body.query as Record<string, unknown> : {}),
|
|
358
|
+
sorts: options.sort,
|
|
359
|
+
filters: options.filter?.map((entry) => ({ raw: entry })),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (
|
|
364
|
+
options.widgetReadOnly !== undefined ||
|
|
365
|
+
options.hideAddButton !== undefined ||
|
|
366
|
+
options.hideDeleteButton !== undefined ||
|
|
367
|
+
options.hideSortButton !== undefined ||
|
|
368
|
+
options.hideFilterButton !== undefined ||
|
|
369
|
+
options.hideSeeButton !== undefined
|
|
370
|
+
) {
|
|
371
|
+
body.behavior = {
|
|
372
|
+
...(typeof body.behavior === "object" && body.behavior ? body.behavior as Record<string, unknown> : {}),
|
|
373
|
+
readOnly: options.widgetReadOnly,
|
|
374
|
+
hideAddButton: options.hideAddButton,
|
|
375
|
+
hideDeleteButton: options.hideDeleteButton,
|
|
376
|
+
hideSortButton: options.hideSortButton,
|
|
377
|
+
hideFilterButton: options.hideFilterButton,
|
|
378
|
+
hideSeeButton: options.hideSeeButton,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (options.calc || options.calcField || options.kpiConfig) {
|
|
383
|
+
body.kpi = {
|
|
384
|
+
...(typeof body.kpi === "object" && body.kpi ? body.kpi as Record<string, unknown> : {}),
|
|
385
|
+
typeCalc: options.calc,
|
|
386
|
+
fieldCalc: options.calcField,
|
|
387
|
+
config: readNamedJsonOption(options.kpiConfig, undefined, "--kpi-config"),
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const hasChartFlags =
|
|
392
|
+
options.chartType !== undefined ||
|
|
393
|
+
options.xAxisField !== undefined ||
|
|
394
|
+
options.xAxisDateFormat !== undefined ||
|
|
395
|
+
options.xAxisTimeSeries !== undefined ||
|
|
396
|
+
(options.yAxisSerie?.length ?? 0) > 0 ||
|
|
397
|
+
options.legend !== undefined;
|
|
398
|
+
if (hasChartFlags) {
|
|
399
|
+
const legend = readNamedJsonOption(options.legend, undefined, "--legend");
|
|
400
|
+
body.chart = {
|
|
401
|
+
...(typeof body.chart === "object" && body.chart ? body.chart as Record<string, unknown> : {}),
|
|
402
|
+
...(options.chartType !== undefined ? { chartType: options.chartType } : {}),
|
|
403
|
+
...(options.xAxisField !== undefined ? { xAxisFieldId: options.xAxisField } : {}),
|
|
404
|
+
...(options.xAxisDateFormat !== undefined ? { xAxisDateFormat: options.xAxisDateFormat } : {}),
|
|
405
|
+
...(options.xAxisTimeSeries !== undefined ? { xAxisAsTimeSerie: options.xAxisTimeSeries } : {}),
|
|
406
|
+
...(options.yAxisSerie && options.yAxisSerie.length > 0
|
|
407
|
+
? { yAxisSeries: options.yAxisSerie.map((entry) => JSON.parse(entry)) }
|
|
408
|
+
: {}),
|
|
409
|
+
...(legend !== undefined ? { legend } : {}),
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (isEmptyObject(body)) {
|
|
414
|
+
throw new CliError("invalid_cli_usage", "Nothing to send for widget mutation.");
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return body;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function buildNavigationTargetBody(options: NavigationTargetMutationOptions): Record<string, unknown> {
|
|
421
|
+
const body: Record<string, unknown> = {};
|
|
422
|
+
if (options.sourceWidget !== undefined) body.sourceWidgetId = options.sourceWidget;
|
|
423
|
+
if (options.targetDisplay !== undefined) body.targetDisplayId = options.targetDisplay;
|
|
424
|
+
if (options.targetVariable !== undefined) body.targetVariableId = options.targetVariable;
|
|
425
|
+
if (options.label !== undefined) body.label = options.label;
|
|
426
|
+
if (isEmptyObject(body)) {
|
|
427
|
+
throw new CliError("invalid_cli_usage", "Nothing to send for navigation target mutation.");
|
|
428
|
+
}
|
|
429
|
+
return body;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function buildCrossWidgetFilterBody(options: CrossWidgetFilterSetOptions): Record<string, unknown> {
|
|
433
|
+
return {
|
|
434
|
+
source: {
|
|
435
|
+
widgetId: options.sourceWidget,
|
|
436
|
+
fieldId: options.sourceField,
|
|
437
|
+
},
|
|
438
|
+
targets: options.target.map((entry) => {
|
|
439
|
+
const [widgetId, fieldId] = entry.split(":");
|
|
440
|
+
if (!widgetId || !fieldId) {
|
|
441
|
+
throw new CliError("invalid_cli_usage", `Invalid --target entry "${entry}". Use <widgetId:fieldId>.`);
|
|
442
|
+
}
|
|
443
|
+
return { widgetId, fieldId };
|
|
444
|
+
}),
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export function registerDisplaysCommands(
|
|
449
|
+
program: Command,
|
|
450
|
+
options: RegisterDisplaysCommandsOptions = {},
|
|
451
|
+
): void {
|
|
452
|
+
const filePath = options.configPath ?? defaultConfigPath;
|
|
453
|
+
const stdout = getCommandIo(options.stdout);
|
|
454
|
+
const displays = program.command("displays").description("Manage displays");
|
|
455
|
+
|
|
456
|
+
displays
|
|
457
|
+
.command("list")
|
|
458
|
+
.description("List displays")
|
|
459
|
+
.action(async function action(this: Command) {
|
|
460
|
+
const { api, json } = createDisplaysApi(this, filePath);
|
|
461
|
+
const data = await api.listDisplays();
|
|
462
|
+
if (json) {
|
|
463
|
+
emitJson(stdout, "displays list", data);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
printTable(displayRows(data), ["id", "name", "layoutTemplate", "variableCount", "widgetCount"], stdout);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
displays
|
|
470
|
+
.command("get")
|
|
471
|
+
.description("Get a display")
|
|
472
|
+
.argument("<display>", "display uuid or slug", parseUuidOrSlugSelector)
|
|
473
|
+
.action(async function action(this: Command, display: NormalizedUuidOrSlug) {
|
|
474
|
+
const { api, json } = createDisplaysApi(this, filePath);
|
|
475
|
+
const data = await api.getDisplay(display);
|
|
476
|
+
if (json) {
|
|
477
|
+
emitJson(stdout, "displays get", data);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
printTable([{ id: data.id, name: data.name ?? "", layoutTemplate: data.layout?.template ?? "" }], ["id", "name", "layoutTemplate"], stdout);
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
displays
|
|
484
|
+
.command("create")
|
|
485
|
+
.description("Create a display")
|
|
486
|
+
.requiredOption("--name <name>", "display name")
|
|
487
|
+
.requiredOption("--template <template>", "layout template")
|
|
488
|
+
.option("--freeform-layouts <json>", "freeform layouts JSON")
|
|
489
|
+
.option("--freeform-layouts-file <path>", "freeform layouts JSON file")
|
|
490
|
+
.action(async function action(this: Command, commandOptions: DisplayCreateOptions) {
|
|
491
|
+
const { api, json, readOnly } = createDisplaysApi(this, filePath);
|
|
492
|
+
ensureWritable("displays create", readOnly, "write");
|
|
493
|
+
const layout: Record<string, unknown> = { template: commandOptions.template };
|
|
494
|
+
const freeformLayouts = readNamedJsonOption(
|
|
495
|
+
commandOptions.freeformLayouts,
|
|
496
|
+
commandOptions.freeformLayoutsFile,
|
|
497
|
+
"--freeform-layouts",
|
|
498
|
+
"--freeform-layouts-file",
|
|
499
|
+
);
|
|
500
|
+
if (freeformLayouts !== undefined) layout.freeformLayouts = freeformLayouts;
|
|
501
|
+
const data = await api.createDisplay({ name: commandOptions.name, layout });
|
|
502
|
+
if (json) {
|
|
503
|
+
emitJson(stdout, "displays create", data);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
writeLine(stdout, `Created display "${commandOptions.name}".`);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
displays
|
|
510
|
+
.command("update")
|
|
511
|
+
.description("Update a display")
|
|
512
|
+
.argument("<display>", "display uuid or slug", parseUuidOrSlugSelector)
|
|
513
|
+
.option("--name <name>", "display name")
|
|
514
|
+
.action(async function action(this: Command, display: NormalizedUuidOrSlug, commandOptions: DisplayUpdateOptions) {
|
|
515
|
+
const { api, json, readOnly } = createDisplaysApi(this, filePath);
|
|
516
|
+
ensureWritable("displays update", readOnly, "write");
|
|
517
|
+
if (!commandOptions.name) throw new CliError("invalid_cli_usage", "Nothing to update.");
|
|
518
|
+
const data = await api.updateDisplay(display, { name: commandOptions.name });
|
|
519
|
+
if (json) {
|
|
520
|
+
emitJson(stdout, "displays update", data);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
writeLine(stdout, `Updated display "${display.value}".`);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
displays
|
|
527
|
+
.command("delete")
|
|
528
|
+
.description("Delete a display")
|
|
529
|
+
.argument("<display>", "display uuid or slug", parseUuidOrSlugSelector)
|
|
530
|
+
.action(async function action(this: Command, display: NormalizedUuidOrSlug) {
|
|
531
|
+
const { api, json, readOnly } = createDisplaysApi(this, filePath);
|
|
532
|
+
ensureWritable("displays delete", readOnly, "destructive");
|
|
533
|
+
const data = await api.deleteDisplay(display);
|
|
534
|
+
if (json) {
|
|
535
|
+
emitJson(stdout, "displays delete", data);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
writeLine(stdout, `Deleted display "${display.value}".`);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
const layout = displays.command("layout").description("Manage display layout");
|
|
542
|
+
layout
|
|
543
|
+
.command("get")
|
|
544
|
+
.description("Get display layout")
|
|
545
|
+
.argument("<display>", "display uuid or slug", parseUuidOrSlugSelector)
|
|
546
|
+
.action(async function action(this: Command, display: NormalizedUuidOrSlug) {
|
|
547
|
+
const { api, json } = createDisplaysApi(this, filePath);
|
|
548
|
+
const data = await api.getDisplay(display);
|
|
549
|
+
const layoutData = data.layout ?? null;
|
|
550
|
+
if (json) {
|
|
551
|
+
emitJson(stdout, "displays layout get", layoutData);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
printTable(layoutData ? [{ template: layoutData.template ?? "", freeformLayouts: layoutData.freeformLayouts ?? "" }] : [], ["template", "freeformLayouts"], stdout);
|
|
555
|
+
});
|
|
556
|
+
layout
|
|
557
|
+
.command("update")
|
|
558
|
+
.description("Update display layout")
|
|
559
|
+
.argument("<display>", "display uuid or slug", parseUuidOrSlugSelector)
|
|
560
|
+
.requiredOption("--template <template>", "layout template")
|
|
561
|
+
.option("--freeform-layouts <json>", "freeform layouts JSON")
|
|
562
|
+
.option("--freeform-layouts-file <path>", "freeform layouts JSON file")
|
|
563
|
+
.action(async function action(this: Command, display: NormalizedUuidOrSlug, commandOptions: LayoutUpdateOptions) {
|
|
564
|
+
const { api, json, readOnly } = createDisplaysApi(this, filePath);
|
|
565
|
+
ensureWritable("displays layout update", readOnly, "write");
|
|
566
|
+
const body: Record<string, unknown> = { template: commandOptions.template };
|
|
567
|
+
const freeformLayouts = readNamedJsonOption(commandOptions.freeformLayouts, commandOptions.freeformLayoutsFile, "--freeform-layouts", "--freeform-layouts-file");
|
|
568
|
+
if (freeformLayouts !== undefined) body.freeformLayouts = freeformLayouts;
|
|
569
|
+
const data = await api.patchLayout(display, body);
|
|
570
|
+
if (json) {
|
|
571
|
+
emitJson(stdout, "displays layout update", data);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
writeLine(stdout, `Updated layout for display "${display.value}".`);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
const tabs = displays.command("tabs").description("Manage display tabs");
|
|
578
|
+
tabs.command("list").description("List tabs").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).action(async function action(this: Command, display: NormalizedUuidOrSlug) {
|
|
579
|
+
const { api, json } = createDisplaysApi(this, filePath);
|
|
580
|
+
const data = await api.getDisplay(display);
|
|
581
|
+
const tabsData = getDisplaySubresource(data.layout?.tabs);
|
|
582
|
+
if (json) { emitJson(stdout, "displays tabs list", tabsData); return; }
|
|
583
|
+
printTable(tabsData, undefined, stdout);
|
|
584
|
+
});
|
|
585
|
+
tabs.command("create").description("Create a tab").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).option("--name <name>").option("--orientation <orientation>").action(async function action(this: Command, display: NormalizedUuidOrSlug, commandOptions: TabMutationOptions) {
|
|
586
|
+
const { api, json, readOnly } = createDisplaysApi(this, filePath);
|
|
587
|
+
ensureWritable("displays tabs create", readOnly, "write");
|
|
588
|
+
const data = await api.createTab(display, { name: commandOptions.name, orientation: commandOptions.orientation });
|
|
589
|
+
if (json) { emitJson(stdout, "displays tabs create", data); return; }
|
|
590
|
+
writeLine(stdout, `Created tab on display "${display.value}".`);
|
|
591
|
+
});
|
|
592
|
+
tabs.command("update").description("Update a tab").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).argument("<tab>", "tab id").option("--name <name>").option("--orientation <orientation>").action(async function action(this: Command, display: NormalizedUuidOrSlug, tabId: string, commandOptions: TabMutationOptions) {
|
|
593
|
+
const { api, json, readOnly } = createDisplaysApi(this, filePath);
|
|
594
|
+
ensureWritable("displays tabs update", readOnly, "write");
|
|
595
|
+
const data = await api.updateTab(display, tabId, { name: commandOptions.name, orientation: commandOptions.orientation });
|
|
596
|
+
if (json) { emitJson(stdout, "displays tabs update", data); return; }
|
|
597
|
+
writeLine(stdout, `Updated tab "${tabId}".`);
|
|
598
|
+
});
|
|
599
|
+
tabs.command("delete").description("Delete a tab").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).argument("<tab>", "tab id").action(async function action(this: Command, display: NormalizedUuidOrSlug, tabId: string) {
|
|
600
|
+
const { api, json, readOnly } = createDisplaysApi(this, filePath);
|
|
601
|
+
ensureWritable("displays tabs delete", readOnly, "destructive");
|
|
602
|
+
const data = await api.deleteTab(display, tabId);
|
|
603
|
+
if (json) { emitJson(stdout, "displays tabs delete", data); return; }
|
|
604
|
+
writeLine(stdout, `Deleted tab "${tabId}".`);
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
const variables = displays.command("variables").description("Manage display variables");
|
|
608
|
+
variables.command("list").description("List variables").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).action(async function action(this: Command, display: NormalizedUuidOrSlug) {
|
|
609
|
+
const { api, json } = createDisplaysApi(this, filePath);
|
|
610
|
+
const data = await api.getDisplay(display);
|
|
611
|
+
const items = getDisplaySubresource(data.variables);
|
|
612
|
+
if (json) { emitJson(stdout, "displays variables list", items); return; }
|
|
613
|
+
printTable(items, undefined, stdout);
|
|
614
|
+
});
|
|
615
|
+
variables.command("get").description("Get variable").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).argument("<variable>", "variable id").action(async function action(this: Command, display: NormalizedUuidOrSlug, variableId: string) {
|
|
616
|
+
const { api, json } = createDisplaysApi(this, filePath);
|
|
617
|
+
const data = await api.getDisplay(display);
|
|
618
|
+
const items = getDisplaySubresource(data.variables);
|
|
619
|
+
const match = items.find((item) => item && typeof item === "object" && "id" in item && String(item.id) === variableId);
|
|
620
|
+
if (!match) throw new CliError("invalid_cli_usage", `Variable "${variableId}" not found.`);
|
|
621
|
+
if (json) { emitJson(stdout, "displays variables get", match); return; }
|
|
622
|
+
printTable([match], undefined, stdout);
|
|
623
|
+
});
|
|
624
|
+
variables.command("create").description("Create variable").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector)
|
|
625
|
+
.option("--type <type>").option("--name <name>").option("--allow-multiple").option("--static")
|
|
626
|
+
.option("--default <value>").option("--default-value <value>", "repeatable default value", (v, p: string[] = []) => [...p, v], [])
|
|
627
|
+
.option("--source-schema <id>").option("--source-field <id>").option("--filter <expr>", "repeatable filter", (v, p: string[] = []) => [...p, v], [])
|
|
628
|
+
.option("--date-mode <mode>").option("--date-dynamic-option <option>").option("--date-specific <iso>").option("--date-range-start <iso>").option("--date-range-end <iso>")
|
|
629
|
+
.option("--raw <json>").option("--raw-file <path>")
|
|
630
|
+
.action(async function action(this: Command, display: NormalizedUuidOrSlug, commandOptions: VariableMutationOptions) {
|
|
631
|
+
const { api, json, readOnly } = createDisplaysApi(this, filePath); ensureWritable("displays variables create", readOnly, "write");
|
|
632
|
+
const data = await api.createVariable(display, buildVariableBody(commandOptions));
|
|
633
|
+
if (json) { emitJson(stdout, "displays variables create", data); return; }
|
|
634
|
+
writeLine(stdout, `Created variable on display "${display.value}".`);
|
|
635
|
+
});
|
|
636
|
+
variables.command("update").description("Update variable").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).argument("<variable>", "variable id")
|
|
637
|
+
.option("--type <type>").option("--name <name>").option("--allow-multiple").option("--static")
|
|
638
|
+
.option("--default <value>").option("--default-value <value>", "repeatable default value", (v, p: string[] = []) => [...p, v], [])
|
|
639
|
+
.option("--source-schema <id>").option("--source-field <id>").option("--filter <expr>", "repeatable filter", (v, p: string[] = []) => [...p, v], [])
|
|
640
|
+
.option("--date-mode <mode>").option("--date-dynamic-option <option>").option("--date-specific <iso>").option("--date-range-start <iso>").option("--date-range-end <iso>")
|
|
641
|
+
.option("--raw <json>").option("--raw-file <path>")
|
|
642
|
+
.action(async function action(this: Command, display: NormalizedUuidOrSlug, variableId: string, commandOptions: VariableMutationOptions) {
|
|
643
|
+
const { api, json, readOnly } = createDisplaysApi(this, filePath); ensureWritable("displays variables update", readOnly, "write");
|
|
644
|
+
const data = await api.updateVariable(display, variableId, buildVariableBody(commandOptions));
|
|
645
|
+
if (json) { emitJson(stdout, "displays variables update", data); return; }
|
|
646
|
+
writeLine(stdout, `Updated variable "${variableId}".`);
|
|
647
|
+
});
|
|
648
|
+
variables.command("delete").description("Delete variable").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).argument("<variable>", "variable id").action(async function action(this: Command, display: NormalizedUuidOrSlug, variableId: string) {
|
|
649
|
+
const { api, json, readOnly } = createDisplaysApi(this, filePath); ensureWritable("displays variables delete", readOnly, "destructive");
|
|
650
|
+
const data = await api.deleteVariable(display, variableId);
|
|
651
|
+
if (json) { emitJson(stdout, "displays variables delete", data); return; }
|
|
652
|
+
writeLine(stdout, `Deleted variable "${variableId}".`);
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
const widgets = displays.command("widgets").description("Manage display widgets");
|
|
656
|
+
widgets.command("list").description("List widgets").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).action(async function action(this: Command, display: NormalizedUuidOrSlug) {
|
|
657
|
+
const { api, json } = createDisplaysApi(this, filePath); const data = await api.getDisplay(display); const items = getDisplaySubresource(data.widgets);
|
|
658
|
+
if (json) { emitJson(stdout, "displays widgets list", items); return; } printTable(items, undefined, stdout);
|
|
659
|
+
});
|
|
660
|
+
widgets.command("get").description("Get widget").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).argument("<widget>", "widget id").action(async function action(this: Command, display: NormalizedUuidOrSlug, widgetId: string) {
|
|
661
|
+
const { api, json } = createDisplaysApi(this, filePath); const data = await api.getWidget(display, widgetId);
|
|
662
|
+
if (json) { emitJson(stdout, "displays widgets get", data); return; } printTable([data], undefined, stdout);
|
|
663
|
+
});
|
|
664
|
+
widgets.command("create").description("Create widget").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector)
|
|
665
|
+
.requiredOption("--type <type>", "widget type")
|
|
666
|
+
.option("--name <name>").option("--description <text>").option("--icon <icon>").option("--color <color>")
|
|
667
|
+
.option("--origin-resource <uuid>").option("--origin-subschema")
|
|
668
|
+
.option("--tab <tabId>").option("--slot <slotId>").option("--layout <json>").option("--layout-file <path>")
|
|
669
|
+
.option("--field <field>", "repeatable field", (v, p: string[] = []) => [...p, v], [])
|
|
670
|
+
.option("--sort <sort>", "repeatable sort", (v, p: string[] = []) => [...p, v], [])
|
|
671
|
+
.option("--filter <expr>", "repeatable filter", (v, p: string[] = []) => [...p, v], [])
|
|
672
|
+
.option("--widget-read-only").option("--hide-add-button").option("--hide-delete-button").option("--hide-sort-button").option("--hide-filter-button").option("--hide-see-button")
|
|
673
|
+
.option("--calc <type>").option("--calc-field <uuid>").option("--kpi-config <json>")
|
|
674
|
+
.option("--chart-type <type>").option("--x-axis-field <uuid>").option("--x-axis-date-format <granularity>").option("--x-axis-time-series")
|
|
675
|
+
.option("--y-axis-serie <json>", "repeatable y axis serie", (v, p: string[] = []) => [...p, v], [])
|
|
676
|
+
.option("--legend <json>").option("--raw <json>").option("--raw-file <path>")
|
|
677
|
+
.action(async function action(this: Command, display: NormalizedUuidOrSlug, commandOptions: WidgetMutationOptions) {
|
|
678
|
+
const { api, json, readOnly } = createDisplaysApi(this, filePath); ensureWritable("displays widgets create", readOnly, "write");
|
|
679
|
+
const data = await api.createWidget(display, buildWidgetBody(commandOptions));
|
|
680
|
+
if (json) { emitJson(stdout, "displays widgets create", data); return; }
|
|
681
|
+
writeLine(stdout, `Created widget on display "${display.value}".`);
|
|
682
|
+
});
|
|
683
|
+
widgets.command("update").description("Update widget").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).argument("<widget>", "widget id")
|
|
684
|
+
.option("--type <type>").option("--name <name>").option("--description <text>").option("--icon <icon>").option("--color <color>")
|
|
685
|
+
.option("--origin-resource <uuid>").option("--origin-subschema")
|
|
686
|
+
.option("--tab <tabId>").option("--slot <slotId>").option("--layout <json>").option("--layout-file <path>")
|
|
687
|
+
.option("--field <field>", "repeatable field", (v, p: string[] = []) => [...p, v], [])
|
|
688
|
+
.option("--sort <sort>", "repeatable sort", (v, p: string[] = []) => [...p, v], [])
|
|
689
|
+
.option("--filter <expr>", "repeatable filter", (v, p: string[] = []) => [...p, v], [])
|
|
690
|
+
.option("--widget-read-only").option("--hide-add-button").option("--hide-delete-button").option("--hide-sort-button").option("--hide-filter-button").option("--hide-see-button")
|
|
691
|
+
.option("--calc <type>").option("--calc-field <uuid>").option("--kpi-config <json>")
|
|
692
|
+
.option("--chart-type <type>").option("--x-axis-field <uuid>").option("--x-axis-date-format <granularity>").option("--x-axis-time-series")
|
|
693
|
+
.option("--y-axis-serie <json>", "repeatable y axis serie", (v, p: string[] = []) => [...p, v], [])
|
|
694
|
+
.option("--legend <json>").option("--raw <json>").option("--raw-file <path>")
|
|
695
|
+
.action(async function action(this: Command, display: NormalizedUuidOrSlug, widgetId: string, commandOptions: WidgetMutationOptions) {
|
|
696
|
+
const { api, json, readOnly } = createDisplaysApi(this, filePath); ensureWritable("displays widgets update", readOnly, "write");
|
|
697
|
+
const data = await api.updateWidget(display, widgetId, buildWidgetBody(commandOptions));
|
|
698
|
+
if (json) { emitJson(stdout, "displays widgets update", data); return; }
|
|
699
|
+
writeLine(stdout, `Updated widget "${widgetId}".`);
|
|
700
|
+
});
|
|
701
|
+
widgets.command("delete").description("Delete widget").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).argument("<widget>", "widget id").action(async function action(this: Command, display: NormalizedUuidOrSlug, widgetId: string) {
|
|
702
|
+
const { api, json, readOnly } = createDisplaysApi(this, filePath); ensureWritable("displays widgets delete", readOnly, "destructive");
|
|
703
|
+
const data = await api.deleteWidget(display, widgetId);
|
|
704
|
+
if (json) { emitJson(stdout, "displays widgets delete", data); return; }
|
|
705
|
+
writeLine(stdout, `Deleted widget "${widgetId}".`);
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
const targets = displays.command("navigation-targets").description("Manage navigation targets");
|
|
709
|
+
targets.command("list").description("List navigation targets").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).action(async function action(this: Command, display: NormalizedUuidOrSlug) {
|
|
710
|
+
const { api, json } = createDisplaysApi(this, filePath); const data = await api.getDisplay(display); const items = getDisplaySubresource(data.navigationTargets);
|
|
711
|
+
if (json) { emitJson(stdout, "displays navigation-targets list", items); return; } printTable(items, undefined, stdout);
|
|
712
|
+
});
|
|
713
|
+
targets.command("create").description("Create navigation target").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector)
|
|
714
|
+
.requiredOption("--source-widget <id>").requiredOption("--target-display <id>").requiredOption("--target-variable <id>").option("--label <label>")
|
|
715
|
+
.action(async function action(this: Command, display: NormalizedUuidOrSlug, commandOptions: NavigationTargetMutationOptions) {
|
|
716
|
+
const { api, json, readOnly } = createDisplaysApi(this, filePath); ensureWritable("displays navigation-targets create", readOnly, "write");
|
|
717
|
+
const data = await api.createNavigationTarget(display, buildNavigationTargetBody(commandOptions));
|
|
718
|
+
if (json) { emitJson(stdout, "displays navigation-targets create", data); return; }
|
|
719
|
+
writeLine(stdout, `Created navigation target on display "${display.value}".`);
|
|
720
|
+
});
|
|
721
|
+
targets.command("update").description("Update navigation target").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).argument("<target>", "target id")
|
|
722
|
+
.option("--source-widget <id>").option("--target-display <id>").option("--target-variable <id>").option("--label <label>")
|
|
723
|
+
.action(async function action(this: Command, display: NormalizedUuidOrSlug, targetId: string, commandOptions: NavigationTargetMutationOptions) {
|
|
724
|
+
const { api, json, readOnly } = createDisplaysApi(this, filePath); ensureWritable("displays navigation-targets update", readOnly, "write");
|
|
725
|
+
const data = await api.updateNavigationTarget(display, targetId, buildNavigationTargetBody(commandOptions));
|
|
726
|
+
if (json) { emitJson(stdout, "displays navigation-targets update", data); return; }
|
|
727
|
+
writeLine(stdout, `Updated navigation target "${targetId}".`);
|
|
728
|
+
});
|
|
729
|
+
targets.command("delete").description("Delete navigation target").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).argument("<target>", "target id").action(async function action(this: Command, display: NormalizedUuidOrSlug, targetId: string) {
|
|
730
|
+
const { api, json, readOnly } = createDisplaysApi(this, filePath); ensureWritable("displays navigation-targets delete", readOnly, "destructive");
|
|
731
|
+
const data = await api.deleteNavigationTarget(display, targetId);
|
|
732
|
+
if (json) { emitJson(stdout, "displays navigation-targets delete", data); return; }
|
|
733
|
+
writeLine(stdout, `Deleted navigation target "${targetId}".`);
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
const cross = displays.command("cross-widget-filter").description("Manage cross widget filter");
|
|
737
|
+
cross.command("get").description("Get cross widget filter").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).action(async function action(this: Command, display: NormalizedUuidOrSlug) {
|
|
738
|
+
const { api, json } = createDisplaysApi(this, filePath); const data = await api.getDisplay(display); const item = data.crossWidgetFilter ?? null;
|
|
739
|
+
if (json) { emitJson(stdout, "displays cross-widget-filter get", item); return; } printTable(item ? [item] : [], undefined, stdout);
|
|
740
|
+
});
|
|
741
|
+
cross.command("set").description("Set cross widget filter").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector)
|
|
742
|
+
.requiredOption("--source-widget <id>").requiredOption("--source-field <id>").requiredOption("--target <widget:field>", "repeatable target", (v, p: string[] = []) => [...p, v], [])
|
|
743
|
+
.action(async function action(this: Command, display: NormalizedUuidOrSlug, commandOptions: CrossWidgetFilterSetOptions) {
|
|
744
|
+
const { api, json, readOnly } = createDisplaysApi(this, filePath); ensureWritable("displays cross-widget-filter set", readOnly, "write");
|
|
745
|
+
const data = await api.setCrossWidgetFilter(display, buildCrossWidgetFilterBody(commandOptions));
|
|
746
|
+
if (json) { emitJson(stdout, "displays cross-widget-filter set", data); return; }
|
|
747
|
+
writeLine(stdout, `Updated cross widget filter for display "${display.value}".`);
|
|
748
|
+
});
|
|
749
|
+
cross.command("delete").description("Delete cross widget filter").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).action(async function action(this: Command, display: NormalizedUuidOrSlug) {
|
|
750
|
+
const { api, json, readOnly } = createDisplaysApi(this, filePath); ensureWritable("displays cross-widget-filter delete", readOnly, "destructive");
|
|
751
|
+
const data = await api.deleteCrossWidgetFilter(display);
|
|
752
|
+
if (json) { emitJson(stdout, "displays cross-widget-filter delete", data); return; }
|
|
753
|
+
writeLine(stdout, `Deleted cross widget filter for display "${display.value}".`);
|
|
754
|
+
});
|
|
755
|
+
}
|