pptb-standard-sample-tool 1.0.12 → 1.1.2

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/dist/app.js CHANGED
@@ -22,7 +22,7 @@ let createdId = null;
22
22
  * Initialize the application
23
23
  */
24
24
  async function initialize() {
25
- log('Initializing HTML Sample Tool...', 'info');
25
+ log("Initializing HTML Sample Tool...", "info");
26
26
  try {
27
27
  // Check connection
28
28
  await refreshConnection();
@@ -34,10 +34,10 @@ async function initialize() {
34
34
  await applyTheme();
35
35
  // Load any previously saved setting into UI
36
36
  await loadToolSetting();
37
- log('Tool initialized successfully', 'success');
37
+ log("Tool initialized successfully", "success");
38
38
  }
39
39
  catch (error) {
40
- log(`Initialization error: ${error.message}`, 'error');
40
+ log(`Initialization error: ${error.message}`, "error");
41
41
  }
42
42
  }
43
43
  /**
@@ -47,12 +47,12 @@ async function refreshConnection() {
47
47
  try {
48
48
  currentConnection = await toolbox.connections.getActiveConnection();
49
49
  secondaryConnection = await toolbox.connections.getSecondaryConnection();
50
- const connectionInfo = document.getElementById('connection-info');
50
+ const connectionInfo = document.getElementById("connection-info");
51
51
  if (!connectionInfo)
52
52
  return;
53
53
  if (currentConnection) {
54
54
  const envClass = currentConnection.environment.toLowerCase();
55
- connectionInfo.className = 'info-box success';
55
+ connectionInfo.className = "info-box success";
56
56
  connectionInfo.innerHTML = `
57
57
  <div class="connection-details">
58
58
  <div class="connection-item">
@@ -73,19 +73,20 @@ async function refreshConnection() {
73
73
  </div>
74
74
  </div>
75
75
  `;
76
- log(`Connected to: ${currentConnection.name}`, 'success');
76
+ log(`Connected to: ${currentConnection.name}`, "success");
77
77
  }
78
78
  else {
79
- connectionInfo.className = 'info-box warning';
80
- connectionInfo.innerHTML = '<p><strong>⚠️ No active connection</strong><br>Please connect to a Dataverse environment to use this tool.</p>';
81
- log('No active connection found', 'warning');
79
+ connectionInfo.className = "info-box warning";
80
+ connectionInfo.innerHTML =
81
+ "<p><strong>⚠️ No active connection</strong><br>Please connect to a Dataverse environment to use this tool.</p>";
82
+ log("No active connection found", "warning");
82
83
  }
83
84
  if (secondaryConnection) {
84
- const secondaryInfo = document.getElementById('secondary-connection-info');
85
+ const secondaryInfo = document.getElementById("secondary-connection-info");
85
86
  if (!secondaryInfo)
86
87
  return;
87
88
  const envClass = secondaryConnection.environment.toLowerCase();
88
- secondaryInfo.className = 'info-box success';
89
+ secondaryInfo.className = "info-box success";
89
90
  secondaryInfo.innerHTML = `
90
91
  <div class="connection-details">
91
92
  <div class="connection-item">
@@ -106,19 +107,20 @@ async function refreshConnection() {
106
107
  </div>
107
108
  </div>
108
109
  `;
109
- log(`Secondary connection: ${secondaryConnection.name}`, 'success');
110
+ log(`Secondary connection: ${secondaryConnection.name}`, "success");
110
111
  }
111
112
  else {
112
- const secondaryInfo = document.getElementById('secondary-connection-info');
113
+ const secondaryInfo = document.getElementById("secondary-connection-info");
113
114
  if (!secondaryInfo)
114
115
  return;
115
- secondaryInfo.className = 'info-box warning';
116
- secondaryInfo.innerHTML = '<p><strong>⚠️ No secondary connection</strong><br>Please connect to a secondary Dataverse environment to use this tool.</p>';
117
- log('No secondary connection found', 'warning');
116
+ secondaryInfo.className = "info-box warning";
117
+ secondaryInfo.innerHTML =
118
+ "<p><strong>⚠️ No secondary connection</strong><br>Please connect to a secondary Dataverse environment to use this tool.</p>";
119
+ log("No secondary connection found", "warning");
118
120
  }
119
121
  }
120
122
  catch (error) {
121
- log(`Error refreshing connection: ${error.message}`, 'error');
123
+ log(`Error refreshing connection: ${error.message}`, "error");
122
124
  }
123
125
  }
124
126
  /**
@@ -126,24 +128,24 @@ async function refreshConnection() {
126
128
  */
127
129
  function subscribeToEvents() {
128
130
  toolbox.events.on((event, payload) => {
129
- log(`Event: ${payload.event}`, 'info');
131
+ log(`Event: ${payload.event}`, "info");
130
132
  switch (payload.event) {
131
- case 'connection:updated':
132
- case 'connection:created':
133
+ case "connection:updated":
134
+ case "connection:created":
133
135
  refreshConnection();
134
136
  break;
135
- case 'connection:deleted':
137
+ case "connection:deleted":
136
138
  currentConnection = null;
137
139
  refreshConnection();
138
140
  break;
139
- case 'terminal:output':
141
+ case "terminal:output":
140
142
  handleTerminalOutput(payload.data);
141
143
  break;
142
- case 'terminal:command:completed':
144
+ case "terminal:command:completed":
143
145
  handleCommandCompleted(payload.data);
144
146
  break;
145
- case 'terminal:error':
146
- log(`Terminal error: ${payload.data.error}`, 'error');
147
+ case "terminal:error":
148
+ log(`Terminal error: ${payload.data.error}`, "error");
147
149
  break;
148
150
  }
149
151
  });
@@ -153,35 +155,56 @@ function subscribeToEvents() {
153
155
  */
154
156
  function setupEventHandlers() {
155
157
  // Notification buttons
156
- document.getElementById('show-success-btn')?.addEventListener('click', () => showNotification('Success!', 'Operation completed successfully', 'success'));
157
- document.getElementById('show-info-btn')?.addEventListener('click', () => showNotification('Information', 'This is an informational message', 'info'));
158
- document.getElementById('show-warning-btn')?.addEventListener('click', () => showNotification('Warning', 'Please review this warning', 'warning'));
159
- document.getElementById('show-error-btn')?.addEventListener('click', () => showNotification('Error', 'An error has occurred', 'error'));
158
+ document
159
+ .getElementById("show-success-btn")
160
+ ?.addEventListener("click", () => showNotification("Success!", "Operation completed successfully", "success"));
161
+ document
162
+ .getElementById("show-info-btn")
163
+ ?.addEventListener("click", () => showNotification("Information", "This is an informational message", "info"));
164
+ document
165
+ .getElementById("show-warning-btn")
166
+ ?.addEventListener("click", () => showNotification("Warning", "Please review this warning", "warning"));
167
+ document
168
+ .getElementById("show-error-btn")
169
+ ?.addEventListener("click", () => showNotification("Error", "An error has occurred", "error"));
170
+ document.getElementById("show-loading-btn")?.addEventListener("click", showLoading);
160
171
  // Utility buttons
161
- document.getElementById('copy-clipboard-btn')?.addEventListener('click', copyToClipboard);
162
- document.getElementById('get-theme-btn')?.addEventListener('click', showCurrentTheme);
163
- document.getElementById('save-file-btn')?.addEventListener('click', saveDataToFile);
172
+ document.getElementById("copy-clipboard-btn")?.addEventListener("click", copyToClipboard);
173
+ document.getElementById("get-theme-btn")?.addEventListener("click", showCurrentTheme);
174
+ document.getElementById("save-file-btn")?.addEventListener("click", saveDataToFile);
175
+ // File System API buttons
176
+ document.getElementById("read-text-btn")?.addEventListener("click", readText);
177
+ document.getElementById("read-directory-btn")?.addEventListener("click", readDirectory);
178
+ document.getElementById("create-directory-btn")?.addEventListener("click", createDirectory);
179
+ document.getElementById("read-system-file-btn")?.addEventListener("click", readSystemFile);
180
+ document.getElementById("read-hardcoded-file-btn")?.addEventListener("click", readHardcodedFile);
181
+ document.getElementById("read-direct-file-btn")?.addEventListener("click", readDirectFile);
164
182
  // Terminal buttons
165
- document.getElementById('create-terminal-btn')?.addEventListener('click', createTerminal);
166
- document.getElementById('execute-command-btn')?.addEventListener('click', executeTerminalCommand);
167
- document.getElementById('close-terminal-btn')?.addEventListener('click', closeTerminal);
183
+ document.getElementById("create-terminal-btn")?.addEventListener("click", createTerminal);
184
+ document.getElementById("execute-command-btn")?.addEventListener("click", executeTerminalCommand);
185
+ document.getElementById("close-terminal-btn")?.addEventListener("click", closeTerminal);
168
186
  // Dataverse query button
169
- document.getElementById('query-accounts-btn')?.addEventListener('click', queryAccounts);
170
- document.getElementById('query-accounts-secondary-btn')?.addEventListener('click', queryAccountsSecondary);
187
+ document.getElementById("query-accounts-btn")?.addEventListener("click", queryAccounts);
188
+ document.getElementById("query-accounts-secondary-btn")?.addEventListener("click", queryAccountsSecondary);
189
+ document.getElementById("query-contacts-querydata-btn")?.addEventListener("click", queryContactQueryData);
171
190
  // CRUD buttons
172
- document.getElementById('create-contact-btn')?.addEventListener('click', createContact);
173
- document.getElementById('update-contact-btn')?.addEventListener('click', updateContact);
174
- document.getElementById('delete-contact-btn')?.addEventListener('click', deleteContact);
191
+ document.getElementById("create-contact-btn")?.addEventListener("click", createContact);
192
+ document.getElementById("update-contact-btn")?.addEventListener("click", updateContact);
193
+ document.getElementById("delete-contact-btn")?.addEventListener("click", deleteContact);
175
194
  // Metadata button
176
- document.getElementById('get-metadata-btn')?.addEventListener('click', getContactMetadata);
195
+ document.getElementById("get-metadata-btn")?.addEventListener("click", getContactMetadata);
196
+ document.getElementById("get-metadata-attributes")?.addEventListener("click", getAccountAttributesMetadata);
197
+ document.getElementById("get-metadata-allentities")?.addEventListener("click", getAllEntities);
198
+ //Execute buttons
199
+ document.getElementById("whoami-btn")?.addEventListener("click", executeWhoAmI);
177
200
  // Clear log button
178
- document.getElementById('clear-log-btn')?.addEventListener('click', clearLog);
201
+ document.getElementById("clear-log-btn")?.addEventListener("click", clearLog);
179
202
  // Advanced utilities demos
180
- document.getElementById('parallel-demo-btn')?.addEventListener('click', demoExecuteParallel);
181
- document.getElementById('loading-demo-btn')?.addEventListener('click', demoLoading);
203
+ document.getElementById("parallel-demo-btn")?.addEventListener("click", demoExecuteParallel);
204
+ document.getElementById("loading-demo-btn")?.addEventListener("click", demoLoading);
182
205
  // Settings buttons
183
- document.getElementById('load-setting-btn')?.addEventListener('click', loadToolSetting);
184
- document.getElementById('save-setting-btn')?.addEventListener('click', saveToolSetting);
206
+ document.getElementById("load-setting-btn")?.addEventListener("click", loadToolSetting);
207
+ document.getElementById("save-setting-btn")?.addEventListener("click", saveToolSetting);
185
208
  }
186
209
  /**
187
210
  * Show notification
@@ -192,12 +215,23 @@ async function showNotification(title, body, type) {
192
215
  title,
193
216
  body,
194
217
  type,
195
- duration: 3000
218
+ duration: 3000,
196
219
  });
197
220
  log(`Notification shown: ${title} - ${body}`, type);
198
221
  }
199
222
  catch (error) {
200
- log(`Error showing notification: ${error.message}`, 'error');
223
+ log(`Error showing notification: ${error.message}`, "error");
224
+ }
225
+ }
226
+ async function showLoading() {
227
+ try {
228
+ await toolbox.utils.showLoading("Loading... for 3 seconds");
229
+ log("Loading shown for 3 seconds", "info");
230
+ await new Promise((resolve) => setTimeout(resolve, 3000));
231
+ await toolbox.utils.hideLoading();
232
+ }
233
+ catch (error) {
234
+ log(`Error showing loading: ${error.message}`, "error");
201
235
  }
202
236
  }
203
237
  /**
@@ -207,14 +241,14 @@ async function copyToClipboard() {
207
241
  try {
208
242
  const data = {
209
243
  timestamp: new Date().toISOString(),
210
- connection: currentConnection?.name || 'No connection',
211
- message: 'This data was copied from the HTML Sample Tool'
244
+ connection: currentConnection?.name || "No connection",
245
+ message: "This data was copied from the HTML Sample Tool",
212
246
  };
213
247
  await toolbox.utils.copyToClipboard(JSON.stringify(data, null, 2));
214
- await showNotification('Copied!', 'Data copied to clipboard', 'success');
248
+ await showNotification("Copied!", "Data copied to clipboard", "success");
215
249
  }
216
250
  catch (error) {
217
- log(`Error copying to clipboard: ${error.message}`, 'error');
251
+ log(`Error copying to clipboard: ${error.message}`, "error");
218
252
  }
219
253
  }
220
254
  /**
@@ -223,11 +257,11 @@ async function copyToClipboard() {
223
257
  async function showCurrentTheme() {
224
258
  try {
225
259
  const theme = await toolbox.utils.getCurrentTheme();
226
- await showNotification('Current Theme', `The current theme is: ${theme}`, 'info');
227
- log(`Current theme: ${theme}`, 'info');
260
+ await showNotification("Current Theme", `The current theme is: ${theme}`, "info");
261
+ log(`Current theme: ${theme}`, "info");
228
262
  }
229
263
  catch (error) {
230
- log(`Error getting theme: ${error.message}`, 'error');
264
+ log(`Error getting theme: ${error.message}`, "error");
231
265
  }
232
266
  }
233
267
  /**
@@ -237,24 +271,305 @@ async function saveDataToFile() {
237
271
  try {
238
272
  const data = {
239
273
  timestamp: new Date().toISOString(),
240
- connection: currentConnection ? {
241
- name: currentConnection.name,
242
- url: currentConnection.url,
243
- environment: currentConnection.environment
244
- } : null,
245
- message: 'Export from HTML Sample Tool'
274
+ connection: currentConnection
275
+ ? {
276
+ name: currentConnection.name,
277
+ url: currentConnection.url,
278
+ environment: currentConnection.environment,
279
+ }
280
+ : null,
281
+ message: "Export from HTML Sample Tool",
246
282
  };
247
- const filePath = await toolbox.utils.saveFile('sample-export.json', JSON.stringify(data, null, 2));
283
+ const filePath = await toolbox.fileSystem.saveFile("sample-export.json", JSON.stringify(data, null, 2));
248
284
  if (filePath) {
249
- await showNotification('File Saved', `File saved to: ${filePath}`, 'success');
250
- log(`File saved to: ${filePath}`, 'success');
285
+ await showNotification("File Saved", `File saved to: ${filePath}`, "success");
286
+ log(`File saved to: ${filePath}`, "success");
251
287
  }
252
288
  else {
253
- log('File save cancelled', 'info');
289
+ log("File save cancelled", "info");
290
+ }
291
+ }
292
+ catch (error) {
293
+ log(`Error saving file: ${error.message}`, "error");
294
+ }
295
+ }
296
+ /**
297
+ * Read text file
298
+ */
299
+ async function readText() {
300
+ try {
301
+ const output = document.getElementById("filesystem-output");
302
+ if (output)
303
+ output.textContent = "Selecting text file...\n";
304
+ const filePath = await toolbox.fileSystem.selectPath({
305
+ type: "file",
306
+ title: "Select a Text File",
307
+ filters: [
308
+ { name: "Text Files", extensions: ["txt", "json", "xml", "csv", "md"] },
309
+ { name: "All Files", extensions: ["*"] },
310
+ ],
311
+ });
312
+ if (!filePath) {
313
+ if (output)
314
+ output.textContent = "File selection cancelled.";
315
+ log("File selection cancelled", "info");
316
+ return;
317
+ }
318
+ if (output)
319
+ output.textContent = `Reading file: ${filePath}\n\n`;
320
+ const content = await toolbox.fileSystem.readText(filePath);
321
+ if (output) {
322
+ output.textContent += `File Size: ${content.length} characters\n\n`;
323
+ output.textContent += "Content:\n";
324
+ output.textContent += "─".repeat(50) + "\n";
325
+ output.textContent += content;
326
+ }
327
+ await showNotification("Success", `File read successfully (${content.length} characters)`, "success");
328
+ log(`Read text file: ${filePath} (${content.length} chars)`, "success");
329
+ }
330
+ catch (error) {
331
+ const output = document.getElementById("filesystem-output");
332
+ if (output)
333
+ output.textContent = `Error: ${error.message}`;
334
+ log(`Error reading text file: ${error.message}`, "error");
335
+ }
336
+ }
337
+ /**
338
+ * Read directory contents
339
+ */
340
+ async function readDirectory() {
341
+ try {
342
+ const output = document.getElementById("filesystem-output");
343
+ if (output)
344
+ output.textContent = "Selecting directory...\n";
345
+ const dirPath = await toolbox.fileSystem.selectPath({
346
+ type: "folder",
347
+ title: "Select a Directory",
348
+ });
349
+ if (!dirPath) {
350
+ if (output)
351
+ output.textContent = "Directory selection cancelled.";
352
+ log("Directory selection cancelled", "info");
353
+ return;
354
+ }
355
+ if (output)
356
+ output.textContent = `Reading directory: ${dirPath}\n\n`;
357
+ const entries = await toolbox.fileSystem.readDirectory(dirPath);
358
+ // Separate files and directories
359
+ const directories = entries.filter((e) => e.type === "directory");
360
+ const files = entries.filter((e) => e.type === "file");
361
+ if (output) {
362
+ output.textContent += `Found ${entries.length} entries:\n`;
363
+ output.textContent += "─".repeat(50) + "\n";
364
+ if (directories.length > 0) {
365
+ output.textContent += `\nDirectories (${directories.length}):\n`;
366
+ directories.forEach((entry) => {
367
+ output.textContent += ` 📁 ${entry.name}\n`;
368
+ });
369
+ }
370
+ if (files.length > 0) {
371
+ output.textContent += `\nFiles (${files.length}):\n`;
372
+ files.forEach((entry) => {
373
+ output.textContent += ` 📄 ${entry.name}\n`;
374
+ });
375
+ }
254
376
  }
377
+ await showNotification("Success", `Directory read successfully (${entries.length} entries)`, "success");
378
+ log(`Read directory: ${dirPath} (${entries.length} entries: ${directories.length} dirs, ${files.length} files)`, "success");
255
379
  }
256
380
  catch (error) {
257
- log(`Error saving file: ${error.message}`, 'error');
381
+ const output = document.getElementById("filesystem-output");
382
+ if (output)
383
+ output.textContent = `Error: ${error.message}`;
384
+ log(`Error reading directory: ${error.message}`, "error");
385
+ }
386
+ }
387
+ /**
388
+ * Create directory
389
+ */
390
+ async function createDirectory() {
391
+ try {
392
+ const output = document.getElementById("filesystem-output");
393
+ if (output)
394
+ output.textContent = "Selecting parent directory...\n";
395
+ const parentPath = await toolbox.fileSystem.selectPath({
396
+ type: "folder",
397
+ title: "Select Parent Directory",
398
+ });
399
+ if (!parentPath) {
400
+ if (output)
401
+ output.textContent = "Directory selection cancelled.";
402
+ log("Directory selection cancelled", "info");
403
+ return;
404
+ }
405
+ // Use built-in prompt for directory name (workaround since browser prompt may not be available)
406
+ // For better UX, you could add an input field to the UI
407
+ const dirName = prompt("Enter new directory name:", "new-folder");
408
+ if (!dirName || dirName.trim() === "") {
409
+ if (output)
410
+ output.textContent = "Directory creation cancelled.";
411
+ log("Directory creation cancelled", "info");
412
+ return;
413
+ }
414
+ const newDirPath = `${parentPath}/${dirName.trim()}`;
415
+ if (output)
416
+ output.textContent = `Creating directory: ${newDirPath}\n`;
417
+ await toolbox.fileSystem.createDirectory(newDirPath);
418
+ if (output) {
419
+ output.textContent += `✓ Directory created successfully!\n`;
420
+ output.textContent += `Path: ${newDirPath}`;
421
+ }
422
+ await showNotification("Success", `Directory created: ${newDirPath}`, "success");
423
+ log(`Created directory: ${newDirPath}`, "success");
424
+ }
425
+ catch (error) {
426
+ const output = document.getElementById("filesystem-output");
427
+ if (output)
428
+ output.textContent = `Error: ${error.message}`;
429
+ log(`Error creating directory: ${error.message}`, "error");
430
+ }
431
+ }
432
+ /**
433
+ * Read system file (hardcoded macOS path for testing)
434
+ * This tests error handling when accessing restricted files
435
+ */
436
+ async function readSystemFile() {
437
+ try {
438
+ const output = document.getElementById("filesystem-output");
439
+ const systemFilePath = "/var/log/system.log";
440
+ if (output)
441
+ output.textContent = `Attempting to read system file: ${systemFilePath}\n\n`;
442
+ log(`Attempting to read system file: ${systemFilePath}`, "info");
443
+ const content = await toolbox.fileSystem.readText(systemFilePath);
444
+ if (output) {
445
+ output.textContent += `File Size: ${content.length} characters\n\n`;
446
+ output.textContent += "Content (first 1000 chars):\n";
447
+ output.textContent += "─".repeat(50) + "\n";
448
+ output.textContent += content.substring(0, 1000);
449
+ if (content.length > 1000) {
450
+ output.textContent += "\n\n... (truncated)";
451
+ }
452
+ }
453
+ await showNotification("Success", `System file read (${content.length} characters)`, "success");
454
+ log(`Successfully read system file: ${systemFilePath}`, "success");
455
+ }
456
+ catch (error) {
457
+ const output = document.getElementById("filesystem-output");
458
+ const errorMsg = error.message;
459
+ if (output) {
460
+ output.textContent = `❌ Error Reading System File\n`;
461
+ output.textContent += `Path: /var/log/system.log\n\n`;
462
+ output.textContent += `Error: ${errorMsg}\n\n`;
463
+ output.textContent += "This is expected - system files are typically restricted due to:\n";
464
+ output.textContent += "- File permissions (insufficient access)\n";
465
+ output.textContent += "- Security restrictions\n";
466
+ output.textContent += "- Sandbox limitations";
467
+ }
468
+ log(`Error reading system file: ${errorMsg}`, "error");
469
+ await showNotification("Error Reading System File", errorMsg, "error");
470
+ }
471
+ }
472
+ /**
473
+ * Read file with selection dialog, but then hardcode a macOS path instead
474
+ * This tests ignoring user selection and attempting to read a restricted path
475
+ */
476
+ async function readHardcodedFile() {
477
+ try {
478
+ const output = document.getElementById("filesystem-output");
479
+ if (output)
480
+ output.textContent = "Opening file selection dialog...\n";
481
+ log("Opening file selection dialog", "info");
482
+ // Open file picker dialog
483
+ const selectedPath = await toolbox.fileSystem.selectPath({
484
+ type: "file",
485
+ title: "Select a File (will be ignored)",
486
+ filters: [{ name: "All Files", extensions: ["*"] }],
487
+ });
488
+ if (!selectedPath) {
489
+ if (output)
490
+ output.textContent = "File selection cancelled.";
491
+ log("File selection cancelled", "info");
492
+ return;
493
+ }
494
+ // Show what was selected
495
+ if (output) {
496
+ output.textContent = `User selected: ${selectedPath}\n\n`;
497
+ output.textContent += "But we're ignoring that and attempting to read a hardcoded system path instead...\n\n";
498
+ output.textContent += "─".repeat(50) + "\n\n";
499
+ }
500
+ log(`User selected: ${selectedPath} (will be ignored)`, "info");
501
+ // Hardcode a macOS system path and ignore the selection
502
+ const hardcodedPath = "/etc/passwd";
503
+ if (output)
504
+ output.textContent += `Attempting to read hardcoded path: ${hardcodedPath}\n\n`;
505
+ log(`Attempting to read hardcoded path: ${hardcodedPath}`, "info");
506
+ const content = await toolbox.fileSystem.readText(hardcodedPath);
507
+ if (output) {
508
+ output.textContent += `✓ Success! File Size: ${content.length} characters\n\n`;
509
+ output.textContent += "Content:\n";
510
+ output.textContent += "─".repeat(50) + "\n";
511
+ output.textContent += content.substring(0, 1500);
512
+ if (content.length > 1500) {
513
+ output.textContent += "\n\n... (truncated)";
514
+ }
515
+ }
516
+ await showNotification("File Read Successfully", `Hardcoded file read: ${hardcodedPath} (${content.length} characters)`, "success");
517
+ log(`Successfully read hardcoded file: ${hardcodedPath}`, "success");
518
+ }
519
+ catch (error) {
520
+ const output = document.getElementById("filesystem-output");
521
+ const errorMsg = error.message;
522
+ if (output) {
523
+ output.textContent += `❌ Error Reading Hardcoded File\n`;
524
+ output.textContent += `Path: /etc/passwd\n\n`;
525
+ output.textContent += `Error: ${errorMsg}\n\n`;
526
+ output.textContent += "This demonstrates that hardcoded system paths are typically restricted:\n";
527
+ output.textContent += "- Permission denied (macOS sandbox restrictions)\n";
528
+ output.textContent += "- The file picker selected a different path, but we tried to read this one instead\n";
529
+ output.textContent += "- Real-world use case: don't hardcode paths, always use user selection";
530
+ }
531
+ log(`Error reading hardcoded file: ${errorMsg}`, "error");
532
+ await showNotification("Error Reading Hardcoded File", errorMsg, "error");
533
+ }
534
+ }
535
+ /**
536
+ * Read a hardcoded macOS file path directly without using selectPath dialog
537
+ * This tests reading a restricted file with no user interaction
538
+ */
539
+ async function readDirectFile() {
540
+ try {
541
+ const output = document.getElementById("filesystem-output");
542
+ const hardcodedPath = "/etc/hosts";
543
+ if (output)
544
+ output.textContent = `Reading hardcoded path directly (no dialog)...\n`;
545
+ if (output)
546
+ output.textContent += `Path: ${hardcodedPath}\n\n`;
547
+ log(`Attempting direct read of hardcoded path: ${hardcodedPath}`, "info");
548
+ const content = await toolbox.fileSystem.readText(hardcodedPath);
549
+ if (output) {
550
+ output.textContent += `✓ Success! File Size: ${content.length} characters\n\n`;
551
+ output.textContent += "Content:\n";
552
+ output.textContent += "─".repeat(50) + "\n";
553
+ output.textContent += content;
554
+ }
555
+ await showNotification("File Read Successfully", `Direct file read: ${hardcodedPath} (${content.length} characters)`, "success");
556
+ log(`Successfully read direct file: ${hardcodedPath}`, "success");
557
+ }
558
+ catch (error) {
559
+ const output = document.getElementById("filesystem-output");
560
+ const errorMsg = error.message;
561
+ if (output) {
562
+ output.textContent = `❌ Error Reading Hardcoded Path\n`;
563
+ output.textContent += `Path: /etc/hosts\n`;
564
+ output.textContent += `Method: Direct read (no selectPath dialog)\n\n`;
565
+ output.textContent += `Error: ${errorMsg}\n\n`;
566
+ output.textContent += "This demonstrates:\n";
567
+ output.textContent += "- System files are protected even with hardcoded paths\n";
568
+ output.textContent += "- No file picker dialog was used\n";
569
+ output.textContent += "- Security restrictions apply to all file access attempts";
570
+ }
571
+ log(`Error reading direct file: ${errorMsg}`, "error");
572
+ await showNotification("Error Reading Direct File", errorMsg, "error");
258
573
  }
259
574
  }
260
575
  /**
@@ -263,20 +578,20 @@ async function saveDataToFile() {
263
578
  async function createTerminal() {
264
579
  try {
265
580
  currentTerminal = await toolbox.terminal.create({
266
- name: 'HTML Sample Terminal'
581
+ name: "HTML Sample Terminal",
267
582
  });
268
- log(`Terminal created: ${currentTerminal.name} (${currentTerminal.id})`, 'success');
583
+ log(`Terminal created: ${currentTerminal.name} (${currentTerminal.id})`, "success");
269
584
  // Enable command buttons
270
- const executeBtn = document.getElementById('execute-command-btn');
271
- const closeBtn = document.getElementById('close-terminal-btn');
585
+ const executeBtn = document.getElementById("execute-command-btn");
586
+ const closeBtn = document.getElementById("close-terminal-btn");
272
587
  if (executeBtn)
273
588
  executeBtn.disabled = false;
274
589
  if (closeBtn)
275
590
  closeBtn.disabled = false;
276
- await showNotification('Terminal Created', `Terminal ${currentTerminal.name} is ready`, 'success');
591
+ await showNotification("Terminal Created", `Terminal ${currentTerminal.name} is ready`, "success");
277
592
  }
278
593
  catch (error) {
279
- log(`Error creating terminal: ${error.message}`, 'error');
594
+ log(`Error creating terminal: ${error.message}`, "error");
280
595
  }
281
596
  }
282
597
  /**
@@ -284,21 +599,21 @@ async function createTerminal() {
284
599
  */
285
600
  async function executeTerminalCommand() {
286
601
  if (!currentTerminal) {
287
- await showNotification('No Terminal', 'Please create a terminal first', 'warning');
602
+ await showNotification("No Terminal", "Please create a terminal first", "warning");
288
603
  return;
289
604
  }
290
605
  try {
291
- const isWindows = navigator.platform.toLowerCase().includes('win');
292
- const command = isWindows ? 'dir' : 'ls -la';
293
- const output = document.getElementById('terminal-output');
606
+ const isWindows = navigator.platform.toLowerCase().includes("win");
607
+ const command = isWindows ? "dir" : "ls -la";
608
+ const output = document.getElementById("terminal-output");
294
609
  if (output) {
295
610
  output.textContent = `> ${command}\n`;
296
611
  }
297
- log(`Executing command: ${command}`, 'info');
612
+ log(`Executing command: ${command}`, "info");
298
613
  await toolbox.terminal.execute(currentTerminal.id, command);
299
614
  }
300
615
  catch (error) {
301
- log(`Error executing command: ${error.message}`, 'error');
616
+ log(`Error executing command: ${error.message}`, "error");
302
617
  }
303
618
  }
304
619
  /**
@@ -309,21 +624,21 @@ async function closeTerminal() {
309
624
  return;
310
625
  try {
311
626
  await toolbox.terminal.close(currentTerminal.id);
312
- log('Terminal closed', 'info');
627
+ log("Terminal closed", "info");
313
628
  currentTerminal = null;
314
629
  // Disable command buttons
315
- const executeBtn = document.getElementById('execute-command-btn');
316
- const closeBtn = document.getElementById('close-terminal-btn');
630
+ const executeBtn = document.getElementById("execute-command-btn");
631
+ const closeBtn = document.getElementById("close-terminal-btn");
317
632
  if (executeBtn)
318
633
  executeBtn.disabled = true;
319
634
  if (closeBtn)
320
635
  closeBtn.disabled = true;
321
- const output = document.getElementById('terminal-output');
636
+ const output = document.getElementById("terminal-output");
322
637
  if (output)
323
- output.textContent = '';
638
+ output.textContent = "";
324
639
  }
325
640
  catch (error) {
326
- log(`Error closing terminal: ${error.message}`, 'error');
641
+ log(`Error closing terminal: ${error.message}`, "error");
327
642
  }
328
643
  }
329
644
  /**
@@ -332,7 +647,7 @@ async function closeTerminal() {
332
647
  function handleTerminalOutput(data) {
333
648
  if (!currentTerminal || data.terminalId !== currentTerminal.id)
334
649
  return;
335
- const output = document.getElementById('terminal-output');
650
+ const output = document.getElementById("terminal-output");
336
651
  if (output) {
337
652
  output.textContent += data.data;
338
653
  output.scrollTop = output.scrollHeight;
@@ -344,7 +659,7 @@ function handleTerminalOutput(data) {
344
659
  function handleCommandCompleted(data) {
345
660
  if (!currentTerminal || data.terminalId !== currentTerminal.id)
346
661
  return;
347
- const output = document.getElementById('terminal-output');
662
+ const output = document.getElementById("terminal-output");
348
663
  if (output) {
349
664
  output.textContent += `\n[Command completed with exit code: ${data.exitCode}]\n`;
350
665
  output.scrollTop = output.scrollHeight;
@@ -355,16 +670,18 @@ function handleCommandCompleted(data) {
355
670
  */
356
671
  async function queryAccounts() {
357
672
  if (!currentConnection) {
358
- await showNotification('No Connection', 'Please connect to a Dataverse environment', 'warning');
673
+ await showNotification("No Connection", "Please connect to a Dataverse environment", "warning");
359
674
  return;
360
675
  }
361
676
  try {
362
- const output = document.getElementById('query-output');
677
+ const output = document.getElementById("query-output");
363
678
  if (output)
364
- output.textContent = 'Querying accounts...\n';
679
+ output.textContent = "Querying accounts...\n";
365
680
  // If user saved a FetchXML in settings, prefer that; otherwise use default
366
- const saved = await getSetting('demo.fetchxml');
367
- const fetchXml = (saved && saved.trim().length > 0) ? saved : `
681
+ const saved = await getSetting("demo.fetchxml");
682
+ const fetchXml = saved && saved.trim().length > 0
683
+ ? saved
684
+ : `
368
685
  <fetch top="10">
369
686
  <entity name="account">
370
687
  <attribute name="name" />
@@ -385,16 +702,51 @@ async function queryAccounts() {
385
702
  output.textContent += ` Email: ${account.emailaddress1}\n`;
386
703
  if (account.telephone1)
387
704
  output.textContent += ` Phone: ${account.telephone1}\n`;
388
- output.textContent += '\n';
705
+ output.textContent += "\n";
389
706
  });
390
707
  }
391
- log(`Queried ${result.value.length} accounts`, 'success');
708
+ log(`Queried ${result.value.length} accounts`, "success");
392
709
  }
393
710
  catch (error) {
394
- const output = document.getElementById('query-output');
711
+ const output = document.getElementById("query-output");
395
712
  if (output)
396
713
  output.textContent = `Error: ${error.message}`;
397
- log(`Error querying accounts: ${error.message}`, 'error');
714
+ log(`Error querying accounts: ${error.message}`, "error");
715
+ }
716
+ }
717
+ async function queryContactQueryData() {
718
+ if (!currentConnection) {
719
+ await showNotification("No Connection", "Please connect to a Dataverse environment", "warning");
720
+ return;
721
+ }
722
+ const output = document.getElementById("query-output");
723
+ try {
724
+ if (output)
725
+ output.textContent = "Querying contacts... with QueryData\n";
726
+ const queryString = "contacts?$top=10&$select=fullname,contactid,emailaddress1,telephone1&$orderby=fullname asc";
727
+ if (output)
728
+ output.textContent += "Using QueryData:\n" + queryString + "\n\n";
729
+ const result = await window.dataverseAPI.queryData(queryString);
730
+ if (output)
731
+ output.textContent += `Found ${result.value.length} contact(s):\n\n`;
732
+ result.value.forEach((contact, index) => {
733
+ if (output) {
734
+ output.textContent += `${index + 1}. ${contact.fullname}\n`;
735
+ output.textContent += ` ID: ${contact.contactid}\n`;
736
+ if (contact.emailaddress1)
737
+ output.textContent += ` Email: ${contact.emailaddress1}\n`;
738
+ if (contact.telephone1)
739
+ output.textContent += ` Phone: ${contact.telephone1}\n`;
740
+ output.textContent += "\n";
741
+ }
742
+ });
743
+ log(`Queried ${result.value.length} contacts`, "success");
744
+ }
745
+ catch (error) {
746
+ const errorMsg = `Error: ${error.message}`;
747
+ if (output)
748
+ output.textContent = errorMsg;
749
+ log(`Error querying contacts: ${error.message}`, "error");
398
750
  }
399
751
  }
400
752
  /**
@@ -402,16 +754,18 @@ async function queryAccounts() {
402
754
  */
403
755
  async function queryAccountsSecondary() {
404
756
  if (!secondaryConnection) {
405
- await showNotification('No Secondary Connection', 'Please configure a secondary Dataverse connection', 'warning');
757
+ await showNotification("No Secondary Connection", "Please configure a secondary Dataverse connection", "warning");
406
758
  return;
407
759
  }
408
760
  try {
409
- const output = document.getElementById('query-output-secondary');
761
+ const output = document.getElementById("query-output-secondary");
410
762
  if (output)
411
- output.textContent = 'Querying accounts using secondary connection...\n';
763
+ output.textContent = "Querying accounts using secondary connection...\n";
412
764
  // Use saved FetchXML if present, otherwise default
413
- const saved = await getSetting('demo.fetchxml');
414
- const fetchXml = (saved && saved.trim().length > 0) ? saved : `
765
+ const saved = await getSetting("demo.fetchxml");
766
+ const fetchXml = saved && saved.trim().length > 0
767
+ ? saved
768
+ : `
415
769
  <fetch top="10">
416
770
  <entity name="account">
417
771
  <attribute name="name" />
@@ -423,7 +777,7 @@ async function queryAccountsSecondary() {
423
777
  </fetch>
424
778
  `.trim();
425
779
  // Pass 'secondary' to target the secondary connection
426
- const result = await dataverse.fetchXmlQuery(fetchXml, 'secondary');
780
+ const result = await dataverse.fetchXmlQuery(fetchXml, "secondary");
427
781
  if (output) {
428
782
  output.textContent = `Found ${result.value.length} account(s) on secondary:\n\n`;
429
783
  result.value.forEach((account, index) => {
@@ -433,16 +787,16 @@ async function queryAccountsSecondary() {
433
787
  output.textContent += ` Email: ${account.emailaddress1}\n`;
434
788
  if (account.telephone1)
435
789
  output.textContent += ` Phone: ${account.telephone1}\n`;
436
- output.textContent += '\n';
790
+ output.textContent += "\n";
437
791
  });
438
792
  }
439
- log(`Queried ${result.value.length} accounts on secondary`, 'success');
793
+ log(`Queried ${result.value.length} accounts on secondary`, "success");
440
794
  }
441
795
  catch (error) {
442
- const output = document.getElementById('query-output-secondary');
796
+ const output = document.getElementById("query-output-secondary");
443
797
  if (output)
444
798
  output.textContent = `Error (secondary): ${error.message}`;
445
- log(`Error querying accounts (secondary): ${error.message}`, 'error');
799
+ log(`Error querying accounts (secondary): ${error.message}`, "error");
446
800
  }
447
801
  }
448
802
  /**
@@ -450,20 +804,20 @@ async function queryAccountsSecondary() {
450
804
  */
451
805
  async function createContact() {
452
806
  if (!currentConnection) {
453
- await showNotification('No Connection', 'Please connect to a Dataverse environment', 'warning');
807
+ await showNotification("No Connection", "Please connect to a Dataverse environment", "warning");
454
808
  return;
455
809
  }
456
810
  try {
457
- const firstnameInput = document.getElementById('contact-firstname');
458
- const lastnameInput = document.getElementById('contact-lastname');
459
- const output = document.getElementById('crud-output');
811
+ const firstnameInput = document.getElementById("contact-firstname");
812
+ const lastnameInput = document.getElementById("contact-lastname");
813
+ const output = document.getElementById("crud-output");
460
814
  if (output)
461
- output.textContent = 'Creating contact...\n';
462
- const result = await dataverse.create('contact', {
815
+ output.textContent = "Creating contact...\n";
816
+ const result = await dataverse.create("contact", {
463
817
  firstname: firstnameInput.value,
464
818
  lastname: lastnameInput.value,
465
- telephone1: '555-0100',
466
- description: 'Created by HTML Sample Tool'
819
+ telephone1: "555-0100",
820
+ description: "Created by HTML Sample Tool",
467
821
  });
468
822
  createdId = result.id;
469
823
  if (output) {
@@ -472,20 +826,20 @@ async function createContact() {
472
826
  output.textContent += `Name: ${firstnameInput.value} ${lastnameInput.value}\n`;
473
827
  }
474
828
  // Enable update and delete buttons
475
- const updateBtn = document.getElementById('update-contact-btn');
476
- const deleteBtn = document.getElementById('delete-contact-btn');
829
+ const updateBtn = document.getElementById("update-contact-btn");
830
+ const deleteBtn = document.getElementById("delete-contact-btn");
477
831
  if (updateBtn)
478
832
  updateBtn.disabled = false;
479
833
  if (deleteBtn)
480
834
  deleteBtn.disabled = false;
481
- await showNotification('Contact Created', `Contact "${firstnameInput.value} ${lastnameInput.value}" created successfully`, 'success');
482
- log(`Contact created: ${result.id}`, 'success');
835
+ await showNotification("Contact Created", `Contact "${firstnameInput.value} ${lastnameInput.value}" created successfully`, "success");
836
+ log(`Contact created: ${result.id}`, "success");
483
837
  }
484
838
  catch (error) {
485
- const output = document.getElementById('crud-output');
839
+ const output = document.getElementById("crud-output");
486
840
  if (output)
487
841
  output.textContent = `Error: ${error.message}`;
488
- log(`Error creating contact: ${error.message}`, 'error');
842
+ log(`Error creating contact: ${error.message}`, "error");
489
843
  }
490
844
  }
491
845
  /**
@@ -493,30 +847,30 @@ async function createContact() {
493
847
  */
494
848
  async function updateContact() {
495
849
  if (!createdId) {
496
- await showNotification('No Contact', 'Please create a contact first', 'warning');
850
+ await showNotification("No Contact", "Please create a contact first", "warning");
497
851
  return;
498
852
  }
499
853
  try {
500
- const output = document.getElementById('crud-output');
854
+ const output = document.getElementById("crud-output");
501
855
  if (output)
502
- output.textContent = 'Updating contact...\n';
503
- await dataverse.update('contact', createdId, {
504
- description: 'Updated by HTML Sample Tool at ' + new Date().toISOString(),
505
- telephone1: '555-0200'
856
+ output.textContent = "Updating contact...\n";
857
+ await dataverse.update("contact", createdId, {
858
+ description: "Updated by HTML Sample Tool at " + new Date().toISOString(),
859
+ telephone1: "555-0200",
506
860
  });
507
861
  if (output) {
508
862
  output.textContent = `Contact updated successfully!\n\n`;
509
863
  output.textContent += `ID: ${createdId}\n`;
510
864
  output.textContent += `Updated fields: description, telephone1\n`;
511
865
  }
512
- await showNotification('Contact Updated', 'Contact updated successfully', 'success');
513
- log(`Contact updated: ${createdId}`, 'success');
866
+ await showNotification("Contact Updated", "Contact updated successfully", "success");
867
+ log(`Contact updated: ${createdId}`, "success");
514
868
  }
515
869
  catch (error) {
516
- const output = document.getElementById('crud-output');
870
+ const output = document.getElementById("crud-output");
517
871
  if (output)
518
872
  output.textContent = `Error: ${error.message}`;
519
- log(`Error updating contact: ${error.message}`, 'error');
873
+ log(`Error updating contact: ${error.message}`, "error");
520
874
  }
521
875
  }
522
876
  /**
@@ -524,34 +878,34 @@ async function updateContact() {
524
878
  */
525
879
  async function deleteContact() {
526
880
  if (!createdId) {
527
- await showNotification('No Contact', 'Please create a contact first', 'warning');
881
+ await showNotification("No Contact", "Please create a contact first", "warning");
528
882
  return;
529
883
  }
530
884
  try {
531
- const output = document.getElementById('crud-output');
885
+ const output = document.getElementById("crud-output");
532
886
  if (output)
533
- output.textContent = 'Deleting contact...\n';
534
- await dataverse.delete('contact', createdId);
887
+ output.textContent = "Deleting contact...\n";
888
+ await dataverse.delete("contact", createdId);
535
889
  if (output) {
536
890
  output.textContent = `Contact deleted successfully!\n\n`;
537
891
  output.textContent += `ID: ${createdId}\n`;
538
892
  }
539
893
  // Disable update and delete buttons
540
- const updateBtn = document.getElementById('update-contact-btn');
541
- const deleteBtn = document.getElementById('delete-contact-btn');
894
+ const updateBtn = document.getElementById("update-contact-btn");
895
+ const deleteBtn = document.getElementById("delete-contact-btn");
542
896
  if (updateBtn)
543
897
  updateBtn.disabled = true;
544
898
  if (deleteBtn)
545
899
  deleteBtn.disabled = true;
546
- await showNotification('Contact Deleted', 'Contact deleted successfully', 'success');
547
- log(`Contact deleted: ${createdId}`, 'success');
900
+ await showNotification("Contact Deleted", "Contact deleted successfully", "success");
901
+ log(`Contact deleted: ${createdId}`, "success");
548
902
  createdId = null;
549
903
  }
550
904
  catch (error) {
551
- const output = document.getElementById('crud-output');
905
+ const output = document.getElementById("crud-output");
552
906
  if (output)
553
907
  output.textContent = `Error: ${error.message}`;
554
- log(`Error deleting contact: ${error.message}`, 'error');
908
+ log(`Error deleting contact: ${error.message}`, "error");
555
909
  }
556
910
  }
557
911
  /**
@@ -559,28 +913,106 @@ async function deleteContact() {
559
913
  */
560
914
  async function getContactMetadata() {
561
915
  if (!currentConnection) {
562
- await showNotification('No Connection', 'Please connect to a Dataverse environment', 'warning');
916
+ await showNotification("No Connection", "Please connect to a Dataverse environment", "warning");
563
917
  return;
564
918
  }
565
919
  try {
566
- const output = document.getElementById('metadata-output');
920
+ const output = document.getElementById("metadata-output");
567
921
  if (output)
568
- output.textContent = 'Retrieving metadata...\n';
922
+ output.textContent = "Retrieving metadata...\n";
569
923
  // Adjusted to match current API signature (logical name, includeAttributes?)
570
- const metadata = await dataverse.getEntityMetadata('contact', true);
924
+ const metadata = await dataverse.getEntityMetadata("contact", true);
571
925
  if (output) {
572
- output.textContent = 'Contact Entity Metadata:\n\n';
926
+ output.textContent = "Contact Entity Metadata:\n\n";
573
927
  output.textContent += `Logical Name: ${metadata.LogicalName}\n`;
574
928
  output.textContent += `Metadata ID: ${metadata.MetadataId}\n`;
575
- output.textContent += `Display Name: ${metadata.DisplayName?.LocalizedLabels?.[0]?.Label || 'N/A'}\n`;
929
+ output.textContent += `Display Name: ${metadata.DisplayName?.LocalizedLabels?.[0]?.Label || "N/A"}\n`;
930
+ }
931
+ log("Contact metadata retrieved", "success");
932
+ }
933
+ catch (error) {
934
+ const output = document.getElementById("metadata-output");
935
+ if (output)
936
+ output.textContent = `Error: ${error.message}`;
937
+ log(`Error getting metadata: ${error.message}`, "error");
938
+ }
939
+ }
940
+ async function getAccountAttributesMetadata() {
941
+ if (!currentConnection) {
942
+ await showNotification("No Connection", "Please connect to a Dataverse environment", "warning");
943
+ return;
944
+ }
945
+ const output = document.getElementById("metadata-output");
946
+ try {
947
+ if (output) {
948
+ output.textContent = "Retrieving account attributes metadata...\n";
949
+ const metadata = await dataverse.getEntityRelatedMetadata("account", "Attributes", [
950
+ "LogicalName",
951
+ "DisplayName",
952
+ "AttributeType",
953
+ ]);
954
+ if (metadata) {
955
+ const metadataArray = metadata.value;
956
+ output.textContent += `Found ${metadataArray.length} attributes:\n\n`;
957
+ metadataArray.forEach((attr, index) => {
958
+ output.textContent += `${index + 1}. ${attr.LogicalName} (${attr.AttributeType}) - ${attr.DisplayName?.LocalizedLabels?.[0]?.Label || "N/A"}\n`;
959
+ });
960
+ }
961
+ }
962
+ }
963
+ catch (error) {
964
+ const output = document.getElementById("metadata-output");
965
+ if (output)
966
+ output.textContent = `Error: ${error.message}`;
967
+ log(`Error getting account attributes metadata: ${error.message}`, "error");
968
+ }
969
+ }
970
+ async function getAllEntities() {
971
+ if (!currentConnection) {
972
+ await showNotification("No Connection", "Please connect to a Dataverse environment", "warning");
973
+ return;
974
+ }
975
+ const output = document.getElementById("metadata-output");
976
+ if (output) {
977
+ try {
978
+ output.textContent = "Retrieving all entities metadata...\n";
979
+ const metadata = await dataverse.getAllEntitiesMetadata();
980
+ if (metadata) {
981
+ const metadataArray = metadata.value;
982
+ output.textContent += `Found ${metadataArray.length} entities:\n\n`;
983
+ metadataArray.forEach((entity, index) => {
984
+ output.textContent += `${index + 1}. ${entity.LogicalName} - ${entity.DisplayName?.LocalizedLabels?.[0]?.Label || "N/A"}\n`;
985
+ });
986
+ }
987
+ }
988
+ catch (error) {
989
+ if (output)
990
+ output.textContent = `Error: ${error.message}`;
991
+ log(`Error getting all entities metadata: ${error.message}`, "error");
992
+ }
993
+ }
994
+ }
995
+ async function executeWhoAmI() {
996
+ if (!currentConnection) {
997
+ await showNotification("No Connection", "Please connect to a Dataverse environment", "warning");
998
+ return;
999
+ }
1000
+ try {
1001
+ const output = document.getElementById("execute-output");
1002
+ if (output)
1003
+ output.textContent = "Executing WhoAmI action...\n";
1004
+ const result = await dataverse.execute({ operationName: "WhoAmI", operationType: "function" });
1005
+ if (output) {
1006
+ output.textContent = "WhoAmI Result:\n\n";
1007
+ output.textContent += JSON.stringify(result, null, 2);
576
1008
  }
577
- log('Contact metadata retrieved', 'success');
1009
+ log("WhoAmI executed successfully", "success");
578
1010
  }
579
1011
  catch (error) {
580
- const output = document.getElementById('metadata-output');
1012
+ const output = document.getElementById("execute-output");
581
1013
  if (output)
582
1014
  output.textContent = `Error: ${error.message}`;
583
- log(`Error getting metadata: ${error.message}`, 'error');
1015
+ log(`Error executing WhoAmI: ${error.message}`, "error");
584
1016
  }
585
1017
  }
586
1018
  /**
@@ -589,21 +1021,21 @@ async function getContactMetadata() {
589
1021
  async function applyTheme() {
590
1022
  try {
591
1023
  const theme = await toolbox.utils.getCurrentTheme();
592
- document.body.setAttribute('data-theme', theme);
1024
+ document.body.setAttribute("data-theme", theme);
593
1025
  }
594
1026
  catch (error) {
595
- log(`Error applying theme: ${error.message}`, 'error');
1027
+ log(`Error applying theme: ${error.message}`, "error");
596
1028
  }
597
1029
  }
598
1030
  /**
599
1031
  * Log message to event log
600
1032
  */
601
- function log(message, type = 'info') {
602
- const logDiv = document.getElementById('event-log');
1033
+ function log(message, type = "info") {
1034
+ const logDiv = document.getElementById("event-log");
603
1035
  if (!logDiv)
604
1036
  return;
605
1037
  const timestamp = new Date().toLocaleTimeString();
606
- const logEntry = document.createElement('div');
1038
+ const logEntry = document.createElement("div");
607
1039
  logEntry.className = `log-entry ${type}`;
608
1040
  logEntry.innerHTML = `
609
1041
  <span class="log-timestamp">[${timestamp}]</span>
@@ -620,15 +1052,15 @@ function log(message, type = 'info') {
620
1052
  * Clear event log
621
1053
  */
622
1054
  function clearLog() {
623
- const logDiv = document.getElementById('event-log');
1055
+ const logDiv = document.getElementById("event-log");
624
1056
  if (logDiv) {
625
- logDiv.innerHTML = '';
1057
+ logDiv.innerHTML = "";
626
1058
  }
627
1059
  }
628
1060
  // -----------------------------
629
1061
  // Tool Settings helpers & demos
630
1062
  // -----------------------------
631
- const SETTINGS_KEY = 'demo.fetchxml';
1063
+ const SETTINGS_KEY = "demo.fetchxml";
632
1064
  async function getSetting(key) {
633
1065
  try {
634
1066
  // Tool settings are scoped to the tool; implementation provided by the host app
@@ -637,7 +1069,7 @@ async function getSetting(key) {
637
1069
  return value;
638
1070
  }
639
1071
  catch (error) {
640
- log(`Error reading setting: ${error.message}`, 'error');
1072
+ log(`Error reading setting: ${error.message}`, "error");
641
1073
  return undefined;
642
1074
  }
643
1075
  }
@@ -646,21 +1078,20 @@ async function setSetting(key, value) {
646
1078
  await toolbox.settings?.set?.(key, value);
647
1079
  }
648
1080
  catch (error) {
649
- log(`Error saving setting: ${error.message}`, 'error');
1081
+ log(`Error saving setting: ${error.message}`, "error");
650
1082
  throw error;
651
1083
  }
652
1084
  }
653
1085
  async function loadToolSetting() {
654
- const textarea = document.getElementById('settings-fetchxml');
655
- const output = document.getElementById('settings-output');
1086
+ const textarea = document.getElementById("settings-fetchxml");
1087
+ const output = document.getElementById("settings-output");
656
1088
  try {
657
1089
  const val = await getSetting(SETTINGS_KEY);
658
- debugger;
659
1090
  if (textarea)
660
- textarea.value = val || '';
1091
+ textarea.value = val || "";
661
1092
  if (output)
662
- output.textContent = val ? 'Loaded saved FetchXML from settings.' : 'No saved FetchXML found.';
663
- log('Loaded tool setting', 'info');
1093
+ output.textContent = val ? "Loaded saved FetchXML from settings." : "No saved FetchXML found.";
1094
+ log("Loaded tool setting", "info");
664
1095
  }
665
1096
  catch (error) {
666
1097
  if (output)
@@ -668,24 +1099,24 @@ async function loadToolSetting() {
668
1099
  }
669
1100
  }
670
1101
  async function saveToolSetting() {
671
- const textarea = document.getElementById('settings-fetchxml');
672
- const output = document.getElementById('settings-output');
673
- const value = (textarea?.value || '').trim();
1102
+ const textarea = document.getElementById("settings-fetchxml");
1103
+ const output = document.getElementById("settings-output");
1104
+ const value = (textarea?.value || "").trim();
674
1105
  if (!value) {
675
- await showNotification('Nothing to save', 'Enter FetchXML before saving.', 'warning');
1106
+ await showNotification("Nothing to save", "Enter FetchXML before saving.", "warning");
676
1107
  return;
677
1108
  }
678
1109
  try {
679
1110
  await setSetting(SETTINGS_KEY, value);
680
1111
  if (output)
681
- output.textContent = 'Saved FetchXML to tool settings.';
682
- await showNotification('Setting Saved', 'Your FetchXML has been saved.', 'success');
683
- log('Saved tool setting', 'success');
1112
+ output.textContent = "Saved FetchXML to tool settings.";
1113
+ await showNotification("Setting Saved", "Your FetchXML has been saved.", "success");
1114
+ log("Saved tool setting", "success");
684
1115
  }
685
1116
  catch (error) {
686
1117
  if (output)
687
1118
  output.textContent = `Error saving setting: ${error.message}`;
688
- await showNotification('Save Failed', error.message, 'error');
1119
+ await showNotification("Save Failed", error.message, "error");
689
1120
  }
690
1121
  }
691
1122
  /**
@@ -693,13 +1124,13 @@ async function saveToolSetting() {
693
1124
  * Falls back gracefully if no connection is available.
694
1125
  */
695
1126
  async function demoExecuteParallel() {
696
- const output = document.getElementById('parallel-output');
1127
+ const output = document.getElementById("parallel-output");
697
1128
  if (output)
698
- output.textContent = 'Running parallel operations...\n';
1129
+ output.textContent = "Running parallel operations...\n";
699
1130
  if (!currentConnection) {
700
1131
  if (output)
701
- output.textContent += 'No active Dataverse connection.\n';
702
- await showNotification('No Connection', 'Connect to a Dataverse environment to run the parallel demo', 'warning');
1132
+ output.textContent += "No active Dataverse connection.\n";
1133
+ await showNotification("No Connection", "Connect to a Dataverse environment to run the parallel demo", "warning");
703
1134
  return;
704
1135
  }
705
1136
  try {
@@ -707,70 +1138,70 @@ async function demoExecuteParallel() {
707
1138
  const accountFetchXml = `<fetch top="1"><entity name="account"><attribute name="name" /><attribute name="accountid" /></entity></fetch>`;
708
1139
  const contactFetchXml = `<fetch top="1"><entity name="contact"><attribute name="fullname" /><attribute name="contactid" /></entity></fetch>`;
709
1140
  const userFetchXml = `<fetch top="3"><entity name="systemuser"><attribute name="fullname" /><attribute name="systemuserid" /></entity></fetch>`;
710
- log('Starting parallel Dataverse queries', 'info');
1141
+ log("Starting parallel Dataverse queries", "info");
711
1142
  const [accounts, contacts, users] = await toolbox.utils.executeParallel(dataverse.fetchXmlQuery(accountFetchXml), dataverse.fetchXmlQuery(contactFetchXml), dataverse.fetchXmlQuery(userFetchXml));
712
1143
  if (output) {
713
- output.textContent += 'All operations completed!\n\n';
1144
+ output.textContent += "All operations completed!\n\n";
714
1145
  output.textContent += `Accounts Returned: ${accounts.value.length}\n`;
715
- accounts.value.forEach((a) => output.textContent += ` • ${a.name} (${a.accountid})\n`);
1146
+ accounts.value.forEach((a) => (output.textContent += ` • ${a.name} (${a.accountid})\n`));
716
1147
  output.textContent += `\nContacts Returned: ${contacts.value.length}\n`;
717
- contacts.value.forEach((c) => output.textContent += ` • ${c.fullname} (${c.contactid})\n`);
1148
+ contacts.value.forEach((c) => (output.textContent += ` • ${c.fullname} (${c.contactid})\n`));
718
1149
  output.textContent += `\nUsers Returned: ${users.value.length}\n`;
719
- users.value.forEach((u) => output.textContent += ` • ${u.fullname} (${u.systemuserid})\n`);
1150
+ users.value.forEach((u) => (output.textContent += ` • ${u.fullname} (${u.systemuserid})\n`));
720
1151
  }
721
- await showNotification('Parallel Complete', 'Fetched accounts, contacts & users', 'success');
722
- log('Parallel queries finished successfully', 'success');
1152
+ await showNotification("Parallel Complete", "Fetched accounts, contacts & users", "success");
1153
+ log("Parallel queries finished successfully", "success");
723
1154
  }
724
1155
  catch (error) {
725
1156
  if (output)
726
1157
  output.textContent += `Error: ${error.message}\n`;
727
- log(`Parallel query error: ${error.message}`, 'error');
728
- await showNotification('Parallel Error', error.message, 'error');
1158
+ log(`Parallel query error: ${error.message}`, "error");
1159
+ await showNotification("Parallel Error", error.message, "error");
729
1160
  }
730
1161
  }
731
1162
  /**
732
1163
  * Demonstrate showLoading/hideLoading utilities wrapping async work.
733
1164
  */
734
1165
  async function demoLoading() {
735
- const output = document.getElementById('parallel-output');
1166
+ const output = document.getElementById("parallel-output");
736
1167
  if (output)
737
- output.textContent = 'Showing loading screen...\n';
1168
+ output.textContent = "Showing loading screen...\n";
738
1169
  try {
739
- await toolbox.utils.showLoading('Processing data...');
740
- log('Loading screen displayed', 'info');
1170
+ await toolbox.utils.showLoading("Processing data...");
1171
+ log("Loading screen displayed", "info");
741
1172
  // Simulate async work or perform a lightweight query
742
1173
  if (currentConnection) {
743
1174
  const fetchXml = `<fetch top="2"><entity name="account"><attribute name="name" /></entity></fetch>`;
744
1175
  const result = await dataverse.fetchXmlQuery(fetchXml);
745
1176
  if (output) {
746
1177
  output.textContent += `Fetched ${result.value.length} account(s) during loading:\n`;
747
- result.value.forEach((a) => output.textContent += ` • ${a.name}\n`);
1178
+ result.value.forEach((a) => (output.textContent += ` • ${a.name}\n`));
748
1179
  }
749
1180
  }
750
1181
  else {
751
1182
  // Fallback simulated delay
752
- await new Promise(r => setTimeout(r, 1500));
1183
+ await new Promise((r) => setTimeout(r, 1500));
753
1184
  if (output)
754
- output.textContent += 'Simulated work (no connection).\n';
1185
+ output.textContent += "Simulated work (no connection).\n";
755
1186
  }
756
1187
  }
757
1188
  catch (error) {
758
1189
  if (output)
759
1190
  output.textContent += `Error during loading demo: ${error.message}\n`;
760
- log(`Loading demo error: ${error.message}`, 'error');
761
- await showNotification('Loading Demo Error', error.message, 'error');
1191
+ log(`Loading demo error: ${error.message}`, "error");
1192
+ await showNotification("Loading Demo Error", error.message, "error");
762
1193
  }
763
1194
  finally {
764
1195
  await toolbox.utils.hideLoading();
765
1196
  if (output)
766
- output.textContent += '\nLoading screen hidden.';
767
- log('Loading screen hidden', 'info');
768
- await showNotification('Loading Complete', 'Demo finished', 'success');
1197
+ output.textContent += "\nLoading screen hidden.";
1198
+ log("Loading screen hidden", "info");
1199
+ await showNotification("Loading Complete", "Demo finished", "success");
769
1200
  }
770
1201
  }
771
1202
  // Initialize when DOM is ready
772
- if (document.readyState === 'loading') {
773
- document.addEventListener('DOMContentLoaded', initialize);
1203
+ if (document.readyState === "loading") {
1204
+ document.addEventListener("DOMContentLoaded", initialize);
774
1205
  }
775
1206
  else {
776
1207
  initialize();