pptb-standard-sample-tool 1.2.7 → 1.3.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/dist/app.js +83 -0
- package/dist/headless.js +53 -11
- package/dist/index.html +54 -0
- package/dist/styles.css +23 -5
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/pptb.config.json +9 -0
package/dist/app.js
CHANGED
|
@@ -26,6 +26,23 @@ let securitySuites = null;
|
|
|
26
26
|
let terminalFeature = null;
|
|
27
27
|
let fileSystemFeature = null;
|
|
28
28
|
let inputEntityName = null;
|
|
29
|
+
const powerPlatformNamespaces = [
|
|
30
|
+
"Analytics",
|
|
31
|
+
"AppManagement",
|
|
32
|
+
"Authorization",
|
|
33
|
+
"Connectivity",
|
|
34
|
+
"CopilotStudio",
|
|
35
|
+
"Dynamics",
|
|
36
|
+
"EnvironmentManagement",
|
|
37
|
+
"Governance",
|
|
38
|
+
"Licensing",
|
|
39
|
+
"PowerApps",
|
|
40
|
+
"PowerAutomate",
|
|
41
|
+
"PowerPages",
|
|
42
|
+
"ResourceQuery",
|
|
43
|
+
"UserManagement",
|
|
44
|
+
"WorkflowAgents",
|
|
45
|
+
];
|
|
29
46
|
/**
|
|
30
47
|
* Initialize the application
|
|
31
48
|
*/
|
|
@@ -272,6 +289,7 @@ function setupEventHandlers() {
|
|
|
272
289
|
document.getElementById("query-role-definitions-btn")?.addEventListener("click", queryRoleDefinitions);
|
|
273
290
|
document.getElementById("assign-role-btn")?.addEventListener("click", assignRole);
|
|
274
291
|
document.getElementById("query-role-assignments-btn")?.addEventListener("click", queryRoleAssignments);
|
|
292
|
+
document.getElementById("pp-generic-execute-btn")?.addEventListener("click", executeGenericPowerPlatformEndpoint);
|
|
275
293
|
// Clear log button
|
|
276
294
|
document.getElementById("clear-log-btn")?.addEventListener("click", clearLog);
|
|
277
295
|
// Advanced utilities demos
|
|
@@ -1212,6 +1230,71 @@ async function queryRoleAssignments() {
|
|
|
1212
1230
|
await showNotification("Error", `Failed to retrieve role assignments: ${errorMessage}`, "error");
|
|
1213
1231
|
}
|
|
1214
1232
|
}
|
|
1233
|
+
async function executeGenericPowerPlatformEndpoint() {
|
|
1234
|
+
const output = document.getElementById("pp-generic-output");
|
|
1235
|
+
const method = document.getElementById("pp-generic-method")?.value;
|
|
1236
|
+
const namespace = document.getElementById("pp-generic-namespace")?.value;
|
|
1237
|
+
const path = document.getElementById("pp-generic-path")?.value?.trim();
|
|
1238
|
+
const rawBody = document.getElementById("pp-generic-body")?.value?.trim();
|
|
1239
|
+
if (!method || !namespace || !path) {
|
|
1240
|
+
await showNotification("Missing Inputs", "Method, namespace, and path are required", "warning");
|
|
1241
|
+
if (output)
|
|
1242
|
+
output.textContent = "Please provide method, namespace, and path.";
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
if (!powerPlatformNamespaces.includes(namespace)) {
|
|
1246
|
+
await showNotification("Invalid Namespace", `Unsupported namespace: ${namespace}`, "error");
|
|
1247
|
+
if (output)
|
|
1248
|
+
output.textContent = `Unsupported namespace: ${namespace}`;
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
let parsedBody = undefined;
|
|
1252
|
+
if (rawBody) {
|
|
1253
|
+
try {
|
|
1254
|
+
parsedBody = JSON.parse(rawBody);
|
|
1255
|
+
}
|
|
1256
|
+
catch {
|
|
1257
|
+
await showNotification("Invalid JSON Body", "Request body must be valid JSON", "error");
|
|
1258
|
+
if (output)
|
|
1259
|
+
output.textContent = "Request body is not valid JSON.";
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
try {
|
|
1264
|
+
if (output) {
|
|
1265
|
+
output.textContent = `Executing ${method.toUpperCase()} ${namespace}/${path}\n\n`;
|
|
1266
|
+
}
|
|
1267
|
+
const namespaceClient = powerplatform[namespace];
|
|
1268
|
+
if (!namespaceClient) {
|
|
1269
|
+
throw new Error(`Namespace '${namespace}' not found on powerplatformAPI`);
|
|
1270
|
+
}
|
|
1271
|
+
const methodClient = namespaceClient[method];
|
|
1272
|
+
if (typeof methodClient !== "function") {
|
|
1273
|
+
throw new Error(`Method '${method}' is not available on namespace '${namespace}'`);
|
|
1274
|
+
}
|
|
1275
|
+
let result;
|
|
1276
|
+
if (method === "Get") {
|
|
1277
|
+
result = await methodClient(path);
|
|
1278
|
+
}
|
|
1279
|
+
else if (method === "Delete") {
|
|
1280
|
+
result = parsedBody === undefined ? await methodClient(path) : await methodClient(path, undefined, undefined, parsedBody);
|
|
1281
|
+
}
|
|
1282
|
+
else {
|
|
1283
|
+
result = await methodClient(path, parsedBody ?? {});
|
|
1284
|
+
}
|
|
1285
|
+
if (output) {
|
|
1286
|
+
output.textContent += JSON.stringify(result, null, 2);
|
|
1287
|
+
}
|
|
1288
|
+
log(`Executed ${method} on ${namespace}: ${path}`, "success");
|
|
1289
|
+
}
|
|
1290
|
+
catch (error) {
|
|
1291
|
+
const message = error.message;
|
|
1292
|
+
if (output)
|
|
1293
|
+
output.textContent = `Error executing endpoint: ${message}`;
|
|
1294
|
+
log(`Generic Power Platform endpoint error: ${message}`, "error");
|
|
1295
|
+
await showNotification("Endpoint Execution Failed", message, "error");
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1215
1298
|
// Initialize when DOM is ready
|
|
1216
1299
|
if (document.readyState === "loading") {
|
|
1217
1300
|
document.addEventListener("DOMContentLoaded", initialize);
|
package/dist/headless.js
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/// <reference types="@pptb/types" />
|
|
3
|
+
function getDataverseApi() {
|
|
4
|
+
const runtime = globalThis;
|
|
5
|
+
const api = runtime.dataverseAPI ?? runtime.window?.dataverseAPI;
|
|
6
|
+
if (!api) {
|
|
7
|
+
throw new Error("Dataverse API is not available in this runtime context");
|
|
8
|
+
}
|
|
9
|
+
return api;
|
|
10
|
+
}
|
|
11
|
+
function sanitizeEntityName(value) {
|
|
12
|
+
const trimmed = value.trim();
|
|
13
|
+
if (!/^[A-Za-z0-9_]+$/.test(trimmed)) {
|
|
14
|
+
throw new Error("Invalid entity name. Only letters, numbers, and underscores are allowed.");
|
|
15
|
+
}
|
|
16
|
+
return trimmed.toLowerCase();
|
|
17
|
+
}
|
|
18
|
+
async function resolveEntityAttributes(api, entityName) {
|
|
19
|
+
try {
|
|
20
|
+
const metadata = await api.getEntityMetadata(entityName, true, ["PrimaryIdAttribute", "PrimaryNameAttribute"]);
|
|
21
|
+
const idAttribute = typeof metadata.PrimaryIdAttribute === "string" && metadata.PrimaryIdAttribute.trim().length > 0 ? metadata.PrimaryIdAttribute : `${entityName}id`;
|
|
22
|
+
const nameAttribute = typeof metadata.PrimaryNameAttribute === "string" && metadata.PrimaryNameAttribute.trim().length > 0 ? metadata.PrimaryNameAttribute : undefined;
|
|
23
|
+
return { idAttribute, nameAttribute };
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// Fallback keeps headless invocation resilient when metadata lookup is unavailable.
|
|
27
|
+
return {
|
|
28
|
+
idAttribute: `${entityName}id`,
|
|
29
|
+
nameAttribute: "name",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function buildFetchXml(entityName, idAttribute, nameAttribute) {
|
|
34
|
+
const attributeLines = [
|
|
35
|
+
` <attribute name="${idAttribute}" />`,
|
|
36
|
+
...(nameAttribute ? [` <attribute name="${nameAttribute}" />`] : []),
|
|
37
|
+
];
|
|
38
|
+
const orderLine = nameAttribute ? `\n <order attribute="${nameAttribute}" />` : "";
|
|
39
|
+
return `<fetch top="10">\n <entity name="${entityName}">\n${attributeLines.join("\n")}${orderLine}\n </entity>\n</fetch>`;
|
|
40
|
+
}
|
|
3
41
|
/**
|
|
4
42
|
* Headless invocation handler.
|
|
5
43
|
*
|
|
@@ -8,26 +46,30 @@
|
|
|
8
46
|
*/
|
|
9
47
|
async function invokeHeadless(input, context) {
|
|
10
48
|
const { toolId, toolName, invocationMode, authToken, updateProgress, logger } = context;
|
|
49
|
+
const dataverseApi = getDataverseApi();
|
|
11
50
|
logger.info(`Starting headless run for ${toolName} (${toolId}) in mode ${invocationMode}`);
|
|
12
51
|
updateProgress(10, "validating input");
|
|
13
|
-
const entityName = typeof input.entityName === "string" && input.entityName.trim() !== "" ? input.entityName
|
|
52
|
+
const entityName = sanitizeEntityName(typeof input.entityName === "string" && input.entityName.trim() !== "" ? input.entityName : "account");
|
|
14
53
|
if (authToken) {
|
|
15
54
|
updateProgress(40, "auth token received");
|
|
16
55
|
}
|
|
17
56
|
else {
|
|
18
57
|
updateProgress(40, "running without auth token");
|
|
19
58
|
}
|
|
20
|
-
updateProgress(
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
</entity>
|
|
27
|
-
</fetch>`;
|
|
59
|
+
updateProgress(55, "resolving entity metadata");
|
|
60
|
+
const { idAttribute, nameAttribute } = await resolveEntityAttributes(dataverseApi, entityName);
|
|
61
|
+
updateProgress(75, "building FetchXML");
|
|
62
|
+
const fetchXml = buildFetchXml(entityName, idAttribute, nameAttribute);
|
|
63
|
+
updateProgress(90, "executing FetchXML query");
|
|
64
|
+
const result = await dataverseApi.fetchXmlQuery(fetchXml);
|
|
28
65
|
updateProgress(100, "done");
|
|
29
|
-
logger.info(`Headless run complete for entity: ${entityName}
|
|
30
|
-
return {
|
|
66
|
+
logger.info(`Headless run complete for entity: ${entityName}. Returned ${result.value.length} record(s).`);
|
|
67
|
+
return {
|
|
68
|
+
entityName,
|
|
69
|
+
fetchXml,
|
|
70
|
+
recordCount: result.value.length,
|
|
71
|
+
records: result.value,
|
|
72
|
+
};
|
|
31
73
|
}
|
|
32
74
|
module.exports = {
|
|
33
75
|
invokeHeadless,
|
package/dist/index.html
CHANGED
|
@@ -177,6 +177,60 @@
|
|
|
177
177
|
<h2>⚡ Power Platform API Examples</h2>
|
|
178
178
|
<p style="margin: 4px 0 15px; font-size: 12px; opacity: 0.85">Generic HTTP methods for Power Platform Admin APIs (Power Apps, Power Automate, Environment Management, Governance).</p>
|
|
179
179
|
|
|
180
|
+
<div class="example-group">
|
|
181
|
+
<h3>Generic Endpoint Tester</h3>
|
|
182
|
+
<p style="margin: 4px 0 10px; font-size: 12px; opacity: 0.85">
|
|
183
|
+
Select method + namespace from the namespace catalog and provide a relative path (for example:
|
|
184
|
+
<code>environments?api-version=2024-10-01</code>).
|
|
185
|
+
</p>
|
|
186
|
+
<div class="input-group">
|
|
187
|
+
<label for="pp-generic-method">Method:</label>
|
|
188
|
+
<select id="pp-generic-method">
|
|
189
|
+
<option value="Get" selected>GET</option>
|
|
190
|
+
<option value="Post">POST</option>
|
|
191
|
+
<option value="Put">PUT</option>
|
|
192
|
+
<option value="Patch">PATCH</option>
|
|
193
|
+
<option value="Delete">DELETE</option>
|
|
194
|
+
</select>
|
|
195
|
+
</div>
|
|
196
|
+
<div class="input-group">
|
|
197
|
+
<label for="pp-generic-namespace">Namespace:</label>
|
|
198
|
+
<select id="pp-generic-namespace">
|
|
199
|
+
<option value="Analytics">Analytics</option>
|
|
200
|
+
<option value="AppManagement">AppManagement</option>
|
|
201
|
+
<option value="Authorization">Authorization</option>
|
|
202
|
+
<option value="Connectivity">Connectivity</option>
|
|
203
|
+
<option value="CopilotStudio">CopilotStudio</option>
|
|
204
|
+
<option value="Dynamics">Dynamics</option>
|
|
205
|
+
<option value="EnvironmentManagement" selected>EnvironmentManagement</option>
|
|
206
|
+
<option value="Governance">Governance</option>
|
|
207
|
+
<option value="Licensing">Licensing</option>
|
|
208
|
+
<option value="PowerApps">PowerApps</option>
|
|
209
|
+
<option value="PowerAutomate">PowerAutomate</option>
|
|
210
|
+
<option value="PowerPages">PowerPages</option>
|
|
211
|
+
<option value="ResourceQuery">ResourceQuery</option>
|
|
212
|
+
<option value="UserManagement">UserManagement</option>
|
|
213
|
+
<option value="WorkflowAgents">WorkflowAgents</option>
|
|
214
|
+
</select>
|
|
215
|
+
</div>
|
|
216
|
+
<div class="input-group">
|
|
217
|
+
<label for="pp-generic-path">Relative Path:</label>
|
|
218
|
+
<input type="text" id="pp-generic-path" value="environments?api-version=2024-10-01" placeholder="e.g., environments/{environmentId}/apps?api-version=2024-10-01" />
|
|
219
|
+
</div>
|
|
220
|
+
<div class="input-group">
|
|
221
|
+
<label for="pp-generic-body">Request Body (JSON, optional for POST/PUT/PATCH/DELETE):</label>
|
|
222
|
+
<textarea
|
|
223
|
+
id="pp-generic-body"
|
|
224
|
+
style="width: 100%; min-height: 110px; padding: 10px; border: 2px solid #e0e0e0; border-radius: 6px; font-family: monospace; font-size: 12px; line-height: 1.4"
|
|
225
|
+
placeholder="{\n \"sample\": \"value\"\n}"
|
|
226
|
+
></textarea>
|
|
227
|
+
</div>
|
|
228
|
+
<div class="button-group">
|
|
229
|
+
<button id="pp-generic-execute-btn" class="btn btn-primary">Execute Endpoint</button>
|
|
230
|
+
</div>
|
|
231
|
+
<div id="pp-generic-output" class="output"></div>
|
|
232
|
+
</div>
|
|
233
|
+
|
|
180
234
|
<div class="example-group">
|
|
181
235
|
<h3>Power Apps API</h3>
|
|
182
236
|
<div class="input-group">
|
package/dist/styles.css
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
body {
|
|
9
|
-
font-family: -apple-system, BlinkMacSystemFont,
|
|
9
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica", "Arial", sans-serif;
|
|
10
10
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
11
11
|
color: #333;
|
|
12
12
|
line-height: 1.6;
|
|
@@ -91,7 +91,7 @@ header h1 {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
.loading::before {
|
|
94
|
-
content:
|
|
94
|
+
content: "";
|
|
95
95
|
width: 16px;
|
|
96
96
|
height: 16px;
|
|
97
97
|
border: 2px solid #667eea;
|
|
@@ -101,7 +101,9 @@ header h1 {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
@keyframes spin {
|
|
104
|
-
to {
|
|
104
|
+
to {
|
|
105
|
+
transform: rotate(360deg);
|
|
106
|
+
}
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
/* Connection Status */
|
|
@@ -256,6 +258,22 @@ header h1 {
|
|
|
256
258
|
border-color: #667eea;
|
|
257
259
|
}
|
|
258
260
|
|
|
261
|
+
.input-group select,
|
|
262
|
+
.input-group textarea {
|
|
263
|
+
width: 100%;
|
|
264
|
+
padding: 10px;
|
|
265
|
+
border: 2px solid #e0e0e0;
|
|
266
|
+
border-radius: 6px;
|
|
267
|
+
font-size: 14px;
|
|
268
|
+
transition: border-color 0.3s ease;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.input-group select:focus,
|
|
272
|
+
.input-group textarea:focus {
|
|
273
|
+
outline: none;
|
|
274
|
+
border-color: #667eea;
|
|
275
|
+
}
|
|
276
|
+
|
|
259
277
|
/* Example Groups */
|
|
260
278
|
.example-group {
|
|
261
279
|
margin: 20px 0;
|
|
@@ -271,7 +289,7 @@ header h1 {
|
|
|
271
289
|
color: #d4d4d4;
|
|
272
290
|
padding: 15px;
|
|
273
291
|
border-radius: 6px;
|
|
274
|
-
font-family:
|
|
292
|
+
font-family: "Consolas", "Monaco", "Courier New", monospace;
|
|
275
293
|
font-size: 13px;
|
|
276
294
|
white-space: pre-wrap;
|
|
277
295
|
word-wrap: break-word;
|
|
@@ -283,7 +301,7 @@ header h1 {
|
|
|
283
301
|
|
|
284
302
|
.output:empty::before,
|
|
285
303
|
.log:empty::before {
|
|
286
|
-
content:
|
|
304
|
+
content: "No output yet...";
|
|
287
305
|
color: #666;
|
|
288
306
|
font-style: italic;
|
|
289
307
|
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pptb-standard-sample-tool",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.8",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pptb-standard-sample-tool",
|
|
9
|
-
"version": "1.2.
|
|
9
|
+
"version": "1.2.8",
|
|
10
10
|
"license": "GPL-3.0",
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"@pptb/types": "^1.2.4-beta.4",
|
package/package.json
CHANGED
package/pptb.config.json
CHANGED