Juggernaut1397 commited on
Commit
c2627ee
·
verified ·
1 Parent(s): d7eef0d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +681 -267
app.py CHANGED
@@ -9,6 +9,11 @@ import os
9
  from dotenv import load_dotenv
10
  from pathlib import Path
11
 
 
 
 
 
 
12
  script_dir = Path(__file__).resolve().parent
13
  env_path = script_dir / '.env'
14
  load_dotenv(dotenv_path=env_path)
@@ -131,121 +136,362 @@ def group_endpoints(endpoints, spec_choice):
131
 
132
  return groups
133
 
134
- with gr.Blocks(
135
- theme=gr.themes.Default(
136
- primary_hue=gr.themes.colors.red,
137
- secondary_hue=gr.themes.colors.gray,
138
- neutral_hue="slate",
139
- text_size="md",
140
- radius_size="md",
141
- font=gr.themes.GoogleFont("Inter")
142
- ),
143
- css="""
144
- .disabled-button {
145
- opacity: 0.5;
146
- pointer-events: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  }
148
- .loading-overlay {
149
- position: fixed;
150
- top: 0;
151
- left: 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  width: 100%;
153
- height: 100%;
154
- background: rgba(255, 255, 255, 0.8);
155
- display: flex;
156
- justify-content: center;
157
- align-items: center;
158
- z-index: 1000;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  }
160
  """
161
- ) as demo:
162
- gr.Markdown("# Data Connector Demo")
163
- gr.Markdown("Select an API spec, then click Refresh Endpoints to see available endpoints grouped in accordions.")
164
-
165
  # Session state
166
  session_id_state = gr.State("")
167
  confirmed_endpoints_state = gr.State([])
168
  display_values_state = gr.State([])
169
-
170
- max_groups = 100
171
-
172
- # API Spec Selection
173
- with gr.Row():
174
- spec_choice = gr.Radio(
175
- label="Choose API Spec",
176
  choices=["Okta (JSON)", "SailPoint IdentityNow (YAML)", "Sailpoint IIQ (YAML)"],
177
- value="SailPoint IdentityNow (YAML)"
 
 
 
178
  )
179
- refresh_eps = gr.Button("Refresh Endpoints", variant="primary")
180
-
181
- # Loading indicator
182
- loading_status = gr.Markdown("Select an API spec and click 'Refresh Endpoints' to load available endpoints.")
183
-
184
- # Create accordion placeholders
185
- accordion_placeholders = []
186
- with gr.Column():
187
- for i in range(max_groups):
188
- with gr.Accordion(label="", open=False, visible=False) as acc:
189
- cb = gr.CheckboxGroup(label="", choices=[], value=[])
190
- accordion_placeholders.append((acc, cb))
191
-
192
- # Parameter group
193
- with gr.Group(visible=False) as param_group:
194
- param_header = gr.Markdown("### Parameters Required") # Default header
195
- param_components = []
196
- with gr.Column() as param_container:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  for i in range(5):
198
  with gr.Group(visible=False) as group:
199
- display = gr.Markdown(visible=False)
200
- input_box = gr.Textbox(
201
- label="Parameter Value",
202
- visible=False,
203
- interactive=True
204
- )
205
- param_components.append((group, display, input_box))
206
-
207
- # Authentication sections
208
- with gr.Group() as identitynow_auth:
209
- with gr.Row():
210
- grant_type = gr.Textbox(label="Enter grant_type", value="client_credentials")
211
- client_id = gr.Textbox(label="Enter client_id")
212
- client_secret = gr.Textbox(label="Enter client_secret", type="password")
213
-
214
- with gr.Group(visible=False) as okta_auth:
215
- api_token = gr.Textbox(label="Enter Okta API Token", type="password")
216
-
217
- with gr.Group(visible=False) as iiq_auth:
218
- with gr.Row():
219
- iiq_username = gr.Textbox(label="Enter IIQ Username")
220
- iiq_password = gr.Textbox(label="Enter IIQ Password", type="password")
221
-
222
- api_base_url = gr.Textbox(label="Enter API Base URL")
223
-
224
- # Buttons
225
- confirm_endpoints_btn = gr.Button("Submit and Confirm Endpoints", variant="primary")
226
- call_api_btn = gr.Button("Call API", variant="primary")
227
-
228
- # Output components
229
- responses_out = gr.JSON(label="API Responses")
230
- download_out = gr.File(label="Download Session Data (ZIP)")
 
 
231
 
232
- def update_acc(spec_choice):
233
- """Update accordions with endpoints"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  try:
235
  endpoints = get_endpoints(spec_choice)
236
-
237
  if not endpoints:
238
- return [gr.update(visible=False)] * (max_groups * 2) + ["⚠️ No endpoints found"]
 
 
 
 
 
 
 
239
 
240
  groups = group_endpoints(endpoints, spec_choice)
241
  group_keys = list(groups.keys())
242
-
243
- if not group_keys:
244
- return [gr.update(visible=False)] * (max_groups * 2) + ["⚠️ No endpoint groups found"]
245
-
246
  updates = []
247
 
248
- # Always process exactly max_groups number of slots
249
  for i in range(max_groups):
250
  if i < len(group_keys):
251
  group = group_keys[i]
@@ -254,234 +500,402 @@ with gr.Blocks(
254
  for ep, methods in groups[group].items():
255
  if 'get' in methods:
256
  summary = methods['get'].get('summary', 'No summary')
257
- label = f"{ep} | GET - {summary}"
258
- choices.append(label)
259
 
260
- # Add updates regardless of choices
261
- acc_update = gr.update(
262
- label=f"Group: {group}" if choices else "",
263
- visible=bool(choices),
264
- open=(i == 0 and bool(choices))
265
- )
266
- cb_update = gr.update(choices=choices, value=[], visible=bool(choices))
267
- updates.append((acc_update, cb_update))
 
 
 
 
 
 
 
 
 
 
 
 
268
  else:
269
- # Fill remaining slots with hidden updates
270
- acc_update = gr.update(visible=False, label="")
271
- cb_update = gr.update(visible=False, choices=[], value=[])
272
- updates.append((acc_update, cb_update))
273
-
274
- # Flatten updates and add status message
275
- flattened = []
276
- for up in updates:
277
- flattened.extend(up) # This will give us exactly max_groups * 2 updates
278
 
279
- visible_groups = sum(1 for group in groups.values() if any('get' in methods for methods in group.values()))
280
- success_msg = f"✅ Loaded {visible_groups} groups with GET endpoints"
281
- flattened.append(success_msg)
 
 
 
 
282
 
283
- return flattened
284
-
285
  except Exception as e:
286
- error_msg = f"❌ Error loading endpoints: {str(e)}"
287
- return [gr.update(visible=False)] * (max_groups * 2) + [error_msg]
288
-
289
- def update_auth_fields(api_choice):
290
- updates = {
291
- "Okta (JSON)": [False, True, False],
292
- "SailPoint IdentityNow (YAML)": [True, False, False],
293
- "Sailpoint IIQ (YAML)": [False, False, True]
294
- }
295
- visibilities = updates.get(api_choice, [False, False, False])
296
  return [
297
- gr.update(visible=visibilities[0]),
298
- gr.update(visible=visibilities[1]),
299
- gr.update(visible=visibilities[2])
 
 
 
 
 
 
300
  ]
301
 
302
- loading_overlay = gr.HTML(visible=False, value='<div class="loading-overlay">⏳ Processing...</div>')
 
 
 
 
 
 
 
 
 
 
 
 
 
303
 
304
- def confirm_selected_endpoints(spec_choice, *checkbox_values):
305
- """Collect and confirm all selected endpoints"""
306
  all_selected = []
307
  for checkbox_group in checkbox_values:
308
  if isinstance(checkbox_group, list) and checkbox_group:
309
  all_selected.extend(checkbox_group)
310
 
311
  if not all_selected:
312
- return ([], # confirmed_endpoints_state
313
- [], # display_values_state
314
- gr.update(visible=False), # param_group
315
- gr.update(value="❌ No endpoints selected", visible=True), # param_header
316
- *[gr.update(visible=False) for _ in range(15)]) # 5 sets of 3 components each
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
 
 
 
 
 
 
 
 
318
  # Get the API spec
319
- endpoints = get_endpoints(spec_choice)
320
 
321
- # Process selected endpoints to find ones with parameters
322
- endpoints_with_params = []
323
- display_values = []
324
  required_params_count = 0
325
 
326
- for selection in all_selected:
327
- endpoint = selection.split(" | ")[0]
 
328
  endpoint_spec = endpoints.get(endpoint, {}).get('get', {})
329
 
 
330
  path_params = extract_path_params(endpoint)
331
- # Only include required query parameters
332
- query_params = [param for param in extract_query_params(endpoint_spec)
333
- if param[2]] # param[2] is the required flag
 
 
 
 
 
 
334
 
335
- if path_params or query_params:
336
- endpoints_with_params.append((endpoint, path_params, query_params))
337
- display_values.append(f"Endpoint: {endpoint}")
338
- required_params_count += len(path_params) + len(query_params)
 
 
 
 
 
 
 
 
339
 
340
- # Create updates for all components
341
  updates = []
342
- updates.append(endpoints_with_params) # confirmed_endpoints_state
343
- updates.append(display_values) # display_values_state
344
 
345
  if not required_params_count:
346
- # No parameters required
347
- updates.append(gr.update(visible=False)) # param_group
348
- updates.append(gr.update(value="✅ No parameters required. You can proceed to call the API.",
349
- visible=True)) # param_header
350
- # Enable API call button here
351
  else:
352
- # Parameters required
353
- header_text = f"⚠️ Required Parameters ({required_params_count})"
354
- updates.append(gr.update(visible=True)) # param_group
355
- updates.append(gr.update(value=header_text, visible=True)) # param_header
356
-
357
- # Then create updates for each parameter component
358
- param_index = 0
359
- for endpoint, path_params, query_params in endpoints_with_params:
360
- # Handle path parameters
361
- for param_name in path_params:
362
- if param_index < 5:
363
  updates.extend([
364
- gr.update(visible=True),
365
- gr.update(visible=True,
366
- value=f"Endpoint: {endpoint} - Path Parameter (Required)"),
367
- gr.update(visible=True,
368
- label=f"🔑 Enter path parameter: {param_name}",
369
- placeholder=f"Required path parameter for {endpoint}")
 
 
 
 
370
  ])
371
- param_index += 1
372
-
373
- # Handle required query parameters only
374
- for _, name, required, description in query_params:
375
- if required and param_index < 5:
376
  updates.extend([
377
- gr.update(visible=True),
378
- gr.update(visible=True,
379
- value=f"Endpoint: {endpoint} - Query Parameter (Required)"),
380
- gr.update(visible=True,
381
- label=f"🔍 Enter query parameter: {name}",
382
- placeholder=description)
383
  ])
384
- param_index += 1
385
-
386
- # Hide remaining parameter components
387
- while param_index < 5:
388
- updates.extend([
389
- gr.update(visible=False),
390
- gr.update(visible=False, value=""),
391
- gr.update(visible=False, label="")
392
- ])
393
- param_index += 1
394
 
395
  return updates
396
 
397
- def handle_api_call(spec_choice, api_base_url, session_id, grant_type, client_id, client_secret,
398
- api_token, iiq_username, iiq_password, display_values, *args):
399
- # Create param_values dictionary
400
- num_params = len(param_components)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
401
  path_params = {}
402
  query_params = {}
403
-
404
- for i, display_value in enumerate(display_values):
405
- if display_value and args[i]:
406
- endpoint_text = display_value.split(": ")[1].split(" - ")[0]
407
- param_type = "path" if "Path Parameter" in display_value else "query"
408
- param_name = args[i].split(":")[0].strip()
409
-
410
- if param_type == "path":
411
- path_params[param_name] = args[i]
412
- else:
413
- query_params[param_name] = args[i]
414
-
415
- checkbox_values = args[num_params:]
416
-
 
 
 
 
 
 
 
 
417
  try:
 
418
  if spec_choice == "Okta (JSON)":
419
- return handle_okta_call(api_base_url, api_token, session_id, path_params, query_params, *checkbox_values)
 
 
 
 
 
 
 
420
  elif spec_choice == "SailPoint IdentityNow (YAML)":
421
- return handle_identitynow_call(api_base_url, grant_type, client_id, client_secret,
422
- session_id, path_params, query_params, *checkbox_values)
423
- else: # IIQ
424
- return handle_iiq_call(api_base_url, iiq_username, iiq_password, session_id,
425
- path_params, query_params, *checkbox_values)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
426
  except Exception as e:
427
- print(f"Error in handle_api_call: {str(e)}")
428
- return (
 
429
  {"error": f"API call failed: {str(e)}"},
430
- None,
431
- session_id,
432
- f"❌ Error: {str(e)}"
433
  )
434
 
435
- # Wire up the events
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
436
  spec_choice.change(
437
- fn=update_auth_fields,
438
- inputs=[spec_choice],
439
- outputs=[identitynow_auth, okta_auth, iiq_auth]
 
 
 
 
 
 
 
 
 
 
440
  )
441
-
442
- refresh_eps.click(
443
- fn=update_acc,
444
- inputs=spec_choice,
445
- outputs=[*[accordion_placeholders[i][j] for i in range(max_groups) for j in range(2)], loading_status]
 
 
 
 
 
 
 
 
 
 
 
 
446
  )
447
-
448
- confirm_endpoints_btn.click(
449
- fn=confirm_selected_endpoints,
450
- inputs=[
451
- spec_choice,
452
- *[acc_cb[1] for acc_cb in accordion_placeholders]
453
- ],
454
  outputs=[
455
- confirmed_endpoints_state,
456
- display_values_state,
 
 
 
457
  param_group,
458
- param_header, # Add param_header to outputs
459
- *[item for group, display, input_box in param_components
460
- for item in (group, display, input_box)]
 
461
  ]
462
  )
463
-
464
- call_api_btn.click(
465
  fn=handle_api_call,
466
  inputs=[
467
- spec_choice,
468
- api_base_url,
469
- session_id_state,
470
- grant_type,
471
- client_id,
472
- client_secret,
473
- api_token,
474
- iiq_username,
475
  iiq_password,
476
- display_values_state,
477
- *[input_box for _, _, input_box in param_components],
478
- *[acc_cb[1] for acc_cb in accordion_placeholders]
479
  ],
480
- outputs=[responses_out, download_out, session_id_state, loading_status]
 
 
 
 
 
481
  )
482
-
483
  if __name__ == "__main__":
484
- demo.launch(
485
- favicon_path="https://www.sailpoint.com/wp-content/uploads/2020/08/favicon.png",
486
- show_error=True
487
- )
 
9
  from dotenv import load_dotenv
10
  from pathlib import Path
11
 
12
+ # Load environment variables
13
+ script_dir = Path(__file__).resolve().parent
14
+ env_path = script_dir / '.env'
15
+ load_dotenv(dotenv_path=env_path)
16
+
17
  script_dir = Path(__file__).resolve().parent
18
  env_path = script_dir / '.env'
19
  load_dotenv(dotenv_path=env_path)
 
136
 
137
  return groups
138
 
139
+ def verify_credentials(spec_choice, api_base_url, grant_type=None, client_id=None,
140
+ client_secret=None, api_token=None, iiq_username=None, iiq_password=None):
141
+ """Verify API credentials using existing handlers"""
142
+
143
+ # Base URL validation
144
+ if not api_base_url or not api_base_url.strip():
145
+ return (
146
+ gr.update(value="❌ API Base URL is required", visible=True),
147
+ False, # endpoints_vis
148
+ False # fetch_vis
149
+ )
150
+
151
+ try:
152
+ # Credential validation based on API type
153
+ if spec_choice == "Okta (JSON)":
154
+ if not api_token or not api_token.strip():
155
+ return (
156
+ gr.update(value="❌ API Token is required", visible=True),
157
+ False,
158
+ False
159
+ )
160
+
161
+ # Test Okta connection
162
+ result = handle_okta_call(api_base_url, api_token, "", {}, ["/api/v1/users/me"])
163
+
164
+ elif spec_choice == "SailPoint IdentityNow (YAML)":
165
+ # Validate all required IdentityNow credentials
166
+ if not all([
167
+ grant_type and grant_type.strip(),
168
+ client_id and client_id.strip(),
169
+ client_secret and client_secret.strip()
170
+ ]):
171
+ return (
172
+ gr.update(value="❌ All IdentityNow credentials (Grant Type, Client ID, Client Secret) are required", visible=True),
173
+ False,
174
+ False
175
+ )
176
+
177
+ # Test IdentityNow connection
178
+ result = handle_identitynow_call(
179
+ api_base_url,
180
+ grant_type,
181
+ client_id,
182
+ client_secret,
183
+ "", # session_id
184
+ {}, # param_values
185
+ [] # endpoints
186
+ )
187
+
188
+ elif spec_choice == "Sailpoint IIQ (YAML)":
189
+ # Validate IIQ credentials
190
+ if not all([
191
+ iiq_username and iiq_username.strip(),
192
+ iiq_password and iiq_password.strip()
193
+ ]):
194
+ return (
195
+ gr.update(value="❌ Both IIQ username and password are required", visible=True),
196
+ False,
197
+ False
198
+ )
199
+
200
+ # Test IIQ connection
201
+ result = handle_iiq_call(
202
+ api_base_url,
203
+ iiq_username,
204
+ iiq_password,
205
+ "", # session_id
206
+ {}, # param_values
207
+ [] # endpoints
208
+ )
209
+ else:
210
+ return (
211
+ gr.update(value="❌ Invalid API type selected", visible=True),
212
+ False,
213
+ False
214
+ )
215
+
216
+ # Validate API response
217
+ if not isinstance(result, tuple) or len(result) < 4:
218
+ return (
219
+ gr.update(value="❌ Invalid API response format", visible=True),
220
+ False,
221
+ False
222
+ )
223
+
224
+ if "error" in result[0]:
225
+ return (
226
+ gr.update(value=f"❌ Connection failed: {result[0]['error']}", visible=True),
227
+ False,
228
+ False
229
+ )
230
+
231
+ # Success case
232
+ return (
233
+ gr.update(value="✅ API Connection successful! Credentials verified.", visible=True),
234
+ True, # Show endpoints section
235
+ True # Show fetch button
236
+ )
237
+
238
+ except requests.exceptions.ConnectionError:
239
+ return (
240
+ gr.update(value="❌ Connection Error: Could not reach the API. Please check the URL and your network connection.", visible=True),
241
+ False,
242
+ False
243
+ )
244
+ except Exception as e:
245
+ return (
246
+ gr.update(value=f"❌ API Error: {str(e)}", visible=True),
247
+ False,
248
+ False
249
+ )
250
+
251
+ # CSS to match dummy.html
252
+ custom_css = """
253
+ body, .gradio-container {
254
+ font-family: Arial, sans-serif;
255
+ background: #f3faf7;
256
+ color: #333
257
+ }
258
+ .container {
259
+ max-width: 800px;
260
+ margin: auto;
261
+ padding: 20px;
262
+ border-radius: 4px;
263
+ background: black;
264
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
265
+ }
266
+ .container h1 {
267
+ color: White !important;
268
  }
269
+ .title h1 {
270
+ color: #ff6347 !important;
271
+ }
272
+ .section {
273
+ margin-bottom: 20px;
274
+ padding-bottom: 10px;
275
+ border-bottom: 1px solid #eee;
276
+ }
277
+ .section h2 {
278
+ margin-top: 0;
279
+ }
280
+ .section #api_choice {
281
+ background: #ff6347;
282
+ }
283
+ .section-text #api_base_url {
284
+ background: #ff6347;
285
+ }
286
+ .section-acc #acc {
287
+ background: #ff6347;
288
+ }
289
+ .section-acc #lock {
290
+ background: gray;
291
+ }
292
+ .section-acc #unlock {
293
+ background: gray;
294
+ }
295
+ input[type="text"], select, .gr-textbox, .gr-dropdown {
296
  width: 100%;
297
+ padding: 8px;
298
+ margin-bottom: 10px;
299
+ border: 1px solid #ccc;
300
+ border-radius: 4px;
301
+ }
302
+ .gr-button {
303
+ background-color: #007BFF;
304
+ color: #fff;
305
+ border: none;
306
+ padding: 10px 20px;
307
+ border-radius: 4px;
308
+ cursor: pointer;
309
+ }
310
+ .gr-textbox label {
311
+ color: black !important;
312
+ }
313
+ .gradio-container .label-wrap {
314
+ color: black !important;
315
+ }
316
+ .gr-button:disabled {
317
+ background-color: #ccc;
318
+ cursor: not-allowed;
319
+ }
320
+ .message {
321
+ margin-top: 10px;
322
+ padding: 8px;
323
+ border-radius: 4px;
324
+ font-size: 0.9em;
325
+ }
326
+ .success { background-color: #d4edda; color: #155724; }
327
+ .error { background-color: #f8d7da; color: #721c24; }
328
+ .endpoint { padding: 10px 0; border-bottom: 1px solid #eee; }
329
+ .endpoint:last-child { border-bottom: none; }
330
+ .endpoint-params { margin-left: 20px; margin-top: 5px; }
331
+ .results {
332
+ border: 1px solid #eee;
333
+ padding: 10px;
334
+ background: #fafafa;
335
+ margin-bottom: 10px;
336
+ white-space: pre;
337
+ font-family: monospace;
338
+ }
339
+ .download-btn {
340
+ margin-top: 10px;
341
+ padding: 5px 10px;
342
+ font-size: 0.9em;
343
+ background-color: #007BFF;
344
+ color: #fff;
345
+ }
346
+ """
347
+ text_box_css = """
348
+ #api_base_url {
349
+ background: gr.themes.colors.red;
350
  }
351
  """
352
+
353
+ with gr.Blocks(css=custom_css) as demo:
354
+ gr.Markdown("# Connect to Your Data Source", elem_classes="title")
355
+
356
  # Session state
357
  session_id_state = gr.State("")
358
  confirmed_endpoints_state = gr.State([])
359
  display_values_state = gr.State([])
360
+ locked_endpoints_state = gr.State([])
361
+
362
+ # API Selection Section
363
+ with gr.Group(elem_classes="section"):
364
+ gr.Markdown("### Select an API")
365
+ spec_choice = gr.Dropdown(
 
366
  choices=["Okta (JSON)", "SailPoint IdentityNow (YAML)", "Sailpoint IIQ (YAML)"],
367
+ value="SailPoint IdentityNow (YAML)",
368
+ label="Select an API",
369
+ filterable=True,
370
+ elem_id="api_choice"
371
  )
372
+
373
+ # Credentials Section
374
+ with gr.Group(elem_classes="section-text"):
375
+ gr.Markdown("### Enter Credentials")
376
+ api_base_url = gr.Textbox(label="API Base URL", placeholder="Enter your API Base URL", elem_id="api_base_url")
377
+
378
+ # Authentication inputs (dynamically shown)
379
+ with gr.Column(visible=True) as identitynow_auth:
380
+ grant_type = gr.Textbox(label="Grant Type", value="client_credentials", elem_id="api_base_url")
381
+ client_id = gr.Textbox(label="Client ID", placeholder="Enter your Client ID", elem_id="api_base_url")
382
+ client_secret = gr.Textbox(label="Client Secret", placeholder="Enter your Client Secret", type="password", elem_id="api_base_url")
383
+ with gr.Column(visible=False) as okta_auth:
384
+ api_token = gr.Textbox(label="API Token", placeholder="Enter your API Token", type="password", elem_id="api_base_url")
385
+ with gr.Column(visible=False) as iiq_auth:
386
+ iiq_username = gr.Textbox(label="Username", placeholder="Enter your IIQ Username", elem_id="api_base_url")
387
+ iiq_password = gr.Textbox(label="Password", placeholder="Enter your IIQ Password", type="password", elem_id="api_base_url")
388
+
389
+ verify_btn = gr.Button("Verify Credentials")
390
+ loading_indicator = gr.Markdown("", visible=False) # Add this
391
+ credentials_message = gr.Markdown("")
392
+
393
+ max_groups = 100
394
+ # Endpoint Selection Section (hidden until verified)
395
+ with gr.Group(elem_classes="section-acc", visible=False) as endpoints_section:
396
+ gr.Markdown("### Choose Endpoints")
397
+ accordion_placeholders = []
398
+ with gr.Column():
399
+ for i in range(max_groups):
400
+ with gr.Accordion(label="", open=False, visible=False, elem_id= "acc") as acc:
401
+ cb = gr.CheckboxGroup(label="", choices=[], value=[])
402
+ accordion_placeholders.append((acc, cb))
403
+
404
+ with gr.Row():
405
+ lock_endpoints_btn = gr.Button("Lock Endpoints", variant="primary", elem_id= "lock")
406
+ unlock_endpoints_btn = gr.Button("Unlock Endpoints", variant="secondary", elem_id="unlock", interactive=False)
407
+ lock_loading_indicator = gr.Markdown("", visible=False) # Add loading indicator for lock operation
408
+
409
+ # Parameter inputs (dynamically populated)
410
+ with gr.Group(visible=False) as param_group:
411
+ param_header = gr.Markdown("### Parameters Required")
412
+ param_components = []
413
  for i in range(5):
414
  with gr.Group(visible=False) as group:
415
+ param_display = gr.Markdown(visible=False)
416
+ param_input = gr.Textbox(label="Parameter Value", visible=False)
417
+ param_components.append((group, param_display, param_input))
418
+
419
+ # Fetch Data Section (hidden until verified)
420
+ with gr.Group(elem_classes="section", visible=False) as fetch_section:
421
+ fetch_btn = gr.Button("Fetch Data", interactive=False)
422
+ fetch_loading_indicator = gr.Markdown("", visible=False) # Add loading indicator for fetch operation
423
+ # Results Section (hidden until data fetched)
424
+ with gr.Group(elem_classes="section", visible=False) as results_section:
425
+ gr.Markdown("### Results")
426
+ results_out = gr.JSON(label="API Responses")
427
+ download_out = gr.File(label="Download Session Data (ZIP)")
428
+
429
+ # Event handlers
430
+ def update_auth_fields(api_choice):
431
+ """Update authentication fields and reset UI when API selection changes"""
432
+ # Update auth field visibility
433
+ auth_updates = {
434
+ "Okta (JSON)": [False, True, False],
435
+ "SailPoint IdentityNow (YAML)": [True, False, False],
436
+ "Sailpoint IIQ (YAML)": [False, False, True]
437
+ }
438
+ visibilities = auth_updates.get(api_choice, [False, False, False])
439
+
440
+ return [
441
+ *[gr.update(visible=v) for v in visibilities], # auth fields (3)
442
+ gr.update(visible=False), # endpoints_section
443
+ gr.update(visible=False), # fetch_section
444
+ gr.update(visible=False), # results_section
445
+ gr.update(value=None), # results_out
446
+ gr.update(value=None), # download_out
447
+ gr.update(value="") # credentials_message
448
+ ]
449
 
450
+ def verify_and_load_endpoints(spec_choice, api_base_url, grant_type, client_id, client_secret,
451
+ api_token, iiq_username, iiq_password):
452
+ """Verify credentials and load endpoints into accordions"""
453
+ # Show loading message first
454
+ yield (
455
+ gr.update(value="⏳ Verifying credentials and loading endpoints...", visible=True), # loading indicator
456
+ gr.update(value="", visible=False), # credentials message
457
+ gr.update(visible=False), # endpoints_section
458
+ gr.update(visible=False), # fetch_section
459
+ *[gr.update(visible=False, label="", value=[]) for _ in range(max_groups * 2)] # accordion updates
460
+ )
461
+
462
+ # Verify credentials
463
+ status, endpoints_vis, fetch_vis = verify_credentials(
464
+ spec_choice, api_base_url, grant_type, client_id, client_secret,
465
+ api_token, iiq_username, iiq_password
466
+ )
467
+
468
+ if not endpoints_vis:
469
+ yield (
470
+ gr.update(visible=False), # hide loading
471
+ status, # show error in credentials message
472
+ gr.update(visible=False), # endpoints_section
473
+ gr.update(visible=False), # fetch_section
474
+ *[gr.update(visible=False, label="", value=[]) for _ in range(max_groups * 2)]
475
+ )
476
+ return
477
+
478
  try:
479
  endpoints = get_endpoints(spec_choice)
 
480
  if not endpoints:
481
+ yield (
482
+ gr.update(visible=False),
483
+ gr.update(value="No endpoints found", visible=True),
484
+ gr.update(visible=False),
485
+ gr.update(visible=False),
486
+ *[gr.update(visible=False, label="", value=[]) for _ in range(max_groups * 2)]
487
+ )
488
+ return
489
 
490
  groups = group_endpoints(endpoints, spec_choice)
491
  group_keys = list(groups.keys())
 
 
 
 
492
  updates = []
493
 
494
+ # Process each group
495
  for i in range(max_groups):
496
  if i < len(group_keys):
497
  group = group_keys[i]
 
500
  for ep, methods in groups[group].items():
501
  if 'get' in methods:
502
  summary = methods['get'].get('summary', 'No summary')
503
+ choices.append(f"{ep} | GET - {summary}")
 
504
 
505
+ # Add updates for this accordion
506
+ if choices:
507
+ updates.extend([
508
+ gr.update(
509
+ label=f"Group: {group}",
510
+ visible=True,
511
+ open=False # Don't auto-open any accordion
512
+ ),
513
+ gr.update(
514
+ choices=choices,
515
+ value=[],
516
+ visible=True,
517
+ interactive=True # Make sure checkboxes are interactive
518
+ )
519
+ ])
520
+ else:
521
+ updates.extend([
522
+ gr.update(visible=False, label=""),
523
+ gr.update(visible=False, choices=[], value=[])
524
+ ])
525
  else:
526
+ # Hide unused accordions
527
+ updates.extend([
528
+ gr.update(visible=False, label=""),
529
+ gr.update(visible=False, choices=[], value=[])
530
+ ])
 
 
 
 
531
 
532
+ yield (
533
+ gr.update(visible=False), # hide loading
534
+ gr.update(value="✅ Endpoints loaded successfully", visible=True),
535
+ gr.update(visible=True),
536
+ gr.update(visible=True),
537
+ *updates
538
+ )
539
 
 
 
540
  except Exception as e:
541
+ yield (
542
+ gr.update(visible=False), # hide loading
543
+ gr.update(value=f"Error loading endpoints: {str(e)}", visible=True),
544
+ gr.update(visible=False),
545
+ gr.update(visible=False),
546
+ *[gr.update(visible=False, label="", value=[]) for _ in range(max_groups * 2)]
547
+ )
548
+
549
+ def unlock_selected_endpoints():
550
+ """Unlock endpoints and reset parameter section"""
551
  return [
552
+ gr.update(value="✅ Endpoints unlocked", visible=True), # message
553
+ [], # locked_endpoints_state
554
+ gr.update(interactive=True), # lock button
555
+ gr.update(interactive=False), # unlock button - disable when unlocked
556
+ *[gr.update(interactive=True) for _ in range(max_groups)], # checkboxes
557
+ gr.update(visible=False), # param_group
558
+ gr.update(visible=False), # param_header
559
+ *[gr.update(visible=False) for _ in range(len(param_components) * 3)], # parameter components
560
+ gr.update(interactive=False) # fetch button
561
  ]
562
 
563
+ def lock_selected_endpoints(*checkbox_values):
564
+ """Collect and lock all selected endpoints"""
565
+ # Show loading message first
566
+ yield (
567
+ gr.update(value="⏳ Locking endpoints and processing parameters...", visible=True), # loading indicator
568
+ [], # locked_endpoints_state - clear state initially
569
+ gr.update(interactive=False), # lock button
570
+ gr.update(interactive=False), # unlock button
571
+ *[gr.update(interactive=False) for _ in range(len(checkbox_values))], # checkboxes
572
+ gr.update(visible=False), # param_group
573
+ gr.update(visible=False), # param_header
574
+ *[gr.update(visible=False) for _ in range(len(param_components) * 3)], # parameter components
575
+ gr.update(interactive=False) # fetch button
576
+ )
577
 
 
 
578
  all_selected = []
579
  for checkbox_group in checkbox_values:
580
  if isinstance(checkbox_group, list) and checkbox_group:
581
  all_selected.extend(checkbox_group)
582
 
583
  if not all_selected:
584
+ base_updates = [
585
+ gr.update(visible=False), # loading indicator
586
+ [], # locked_endpoints_state - keep empty
587
+ gr.update(interactive=True), # lock button
588
+ gr.update(interactive=False), # unlock button
589
+ *[gr.update(interactive=True) for _ in range(len(checkbox_values))] # checkboxes
590
+ ]
591
+ param_updates = [
592
+ gr.update(visible=False), # param_group
593
+ gr.update(visible=False), # param_header
594
+ *[gr.update(visible=False) for _ in range(len(param_components) * 3)] # parameter components
595
+ ]
596
+ yield base_updates + param_updates + [gr.update(interactive=False)] # fetch button
597
+ return
598
+
599
+ base_updates = [
600
+ gr.update(visible=False), # loading indicator
601
+ all_selected, # locked_endpoints_state - update with selected endpoints
602
+ gr.update(interactive=False), # lock button
603
+ gr.update(interactive=True), # unlock button
604
+ *[gr.update(interactive=False) for _ in range(len(checkbox_values))] # checkboxes
605
+ ]
606
+
607
+ param_updates = update_params(spec_choice.value, all_selected)
608
+ fetch_update = [gr.update(interactive=True)]
609
 
610
+ yield base_updates + param_updates + fetch_update
611
+
612
+ def update_params(spec_choice_value, locked_endpoints):
613
+ """Update parameter inputs based on selected endpoints"""
614
+ if not locked_endpoints:
615
+ return [gr.update(visible=False), gr.update(visible=False)] + [gr.update(visible=False)] * (len(param_components) * 3)
616
+
617
  # Get the API spec
618
+ endpoints = get_endpoints(spec_choice_value)
619
 
620
+ # First pass: collect all parameters from all selected endpoints
621
+ all_parameters = [] # This will store ALL parameters from ALL endpoints
 
622
  required_params_count = 0
623
 
624
+ # Collect parameters from all endpoints first
625
+ for ep in locked_endpoints:
626
+ endpoint = ep.split(" | GET")[0].strip()
627
  endpoint_spec = endpoints.get(endpoint, {}).get('get', {})
628
 
629
+ # Collect path parameters
630
  path_params = extract_path_params(endpoint)
631
+ for param in path_params:
632
+ all_parameters.append({
633
+ 'endpoint': endpoint,
634
+ 'type': 'path',
635
+ 'name': param,
636
+ 'required': True,
637
+ 'description': f"Required path parameter for {endpoint}"
638
+ })
639
+ required_params_count += 1
640
 
641
+ # Collect required query parameters
642
+ query_params = extract_query_params(endpoint_spec)
643
+ for param_type, name, required, description in query_params:
644
+ if required:
645
+ all_parameters.append({
646
+ 'endpoint': endpoint,
647
+ 'type': 'query',
648
+ 'name': name,
649
+ 'required': True,
650
+ 'description': description
651
+ })
652
+ required_params_count += 1
653
 
654
+ # Create updates for components
655
  updates = []
 
 
656
 
657
  if not required_params_count:
658
+ updates.extend([
659
+ gr.update(visible=False), # param_group
660
+ gr.update(value="✅ No parameters required. You can proceed to call the API.", visible=True) # param_header
661
+ ])
662
+ updates.extend([gr.update(visible=False)] * (len(param_components) * 3))
663
  else:
664
+ # Show parameter section
665
+ updates.extend([
666
+ gr.update(visible=True), # param_group
667
+ gr.update(value=f"⚠️ Required Parameters ({required_params_count})", visible=True) # param_header
668
+ ])
669
+
670
+ # Update parameter components (limited to first 5 parameters)
671
+ for i in range(5):
672
+ if i < len(all_parameters):
673
+ param = all_parameters[i] # Get parameter from the collected list
674
+ emoji = "🔑" if param['type'] == 'path' else "🔍"
675
  updates.extend([
676
+ gr.update(visible=True), # group
677
+ gr.update(
678
+ visible=True,
679
+ value=f"Endpoint: {param['endpoint']} - {param['type'].title()} Parameter"
680
+ ), # display
681
+ gr.update(
682
+ visible=True,
683
+ label=f"{emoji} Enter {param['type']} parameter: {param['name']}",
684
+ placeholder=param['description']
685
+ ) # input
686
  ])
687
+ else:
688
+ # Hide unused parameter components
 
 
 
689
  updates.extend([
690
+ gr.update(visible=False),
691
+ gr.update(visible=False, value=""),
692
+ gr.update(visible=False, label="")
 
 
 
693
  ])
 
 
 
 
 
 
 
 
 
 
694
 
695
  return updates
696
 
697
+ def update_fetch_button(*param_values):
698
+ """Enable fetch button if all required parameters are filled"""
699
+ if all(val.strip() for val in param_values if val is not None):
700
+ return gr.update(interactive=True)
701
+ return gr.update(interactive=False)
702
+
703
+ for _, _, input_box in param_components:
704
+ input_box.change(
705
+ fn=update_fetch_button,
706
+ inputs=[input_box for _, _, input_box in param_components],
707
+ outputs=[fetch_btn]
708
+ )
709
+
710
+ def handle_api_call(spec_choice, api_base_url, grant_type, client_id, client_secret, api_token,
711
+ iiq_username, iiq_password, locked_endpoints, *param_values):
712
+ """Handle API calls for selected endpoints with parameters"""
713
+
714
+ yield (
715
+ gr.update(value="⏳ Fetching data from API...", visible=True), # loading indicator
716
+ gr.update(visible=False), # results_section
717
+ gr.update(value=None), # results_out
718
+ gr.update(value=None) # download_out
719
+ )
720
+
721
+ if not locked_endpoints:
722
+ yield (
723
+ gr.update(visible=False), # hide loading
724
+ gr.update(visible=True),
725
+ {"error": "No endpoints selected"},
726
+ None
727
+ )
728
+ return
729
+
730
+ # Initialize parameter dictionaries
731
  path_params = {}
732
  query_params = {}
733
+ param_idx = 0
734
+ endpoints = get_endpoints(spec_choice)
735
+
736
+ # Process each endpoint and map parameters
737
+ for ep in locked_endpoints:
738
+ endpoint = ep.split(" | GET")[0].strip() # Extract endpoint path
739
+ endpoint_spec = endpoints.get(endpoint, {}).get('get', {})
740
+
741
+ # Get path parameters
742
+ path_params_list = extract_path_params(endpoint)
743
+ for param in path_params_list:
744
+ if param_idx < len(param_values) and param_values[param_idx]:
745
+ path_params[param] = param_values[param_idx]
746
+ param_idx += 1
747
+
748
+ # Get query parameters
749
+ query_params_list = extract_query_params(endpoint_spec)
750
+ for _, name, required, _ in query_params_list:
751
+ if required and param_idx < len(param_values) and param_values[param_idx]:
752
+ query_params[name] = param_values[param_idx]
753
+ param_idx += 1
754
+
755
  try:
756
+ # Call appropriate API handler based on selection
757
  if spec_choice == "Okta (JSON)":
758
+ result = handle_okta_call(
759
+ api_base_url,
760
+ api_token,
761
+ "", # session_id
762
+ path_params,
763
+ query_params,
764
+ locked_endpoints
765
+ )
766
  elif spec_choice == "SailPoint IdentityNow (YAML)":
767
+ result = handle_identitynow_call(
768
+ api_base_url,
769
+ grant_type,
770
+ client_id,
771
+ client_secret,
772
+ "", # session_id
773
+ path_params,
774
+ query_params,
775
+ locked_endpoints
776
+ )
777
+ else: # Sailpoint IIQ
778
+ result = handle_iiq_call(
779
+ api_base_url,
780
+ iiq_username,
781
+ iiq_password,
782
+ "", # session_id
783
+ path_params,
784
+ query_params,
785
+ locked_endpoints
786
+ )
787
+
788
+ # Show results section and return API response
789
+ yield (
790
+ gr.update(visible=False), # hide loading
791
+ gr.update(visible=True), # Show results section
792
+ result[0], # API response data
793
+ result[1] # Download file if any
794
+ )
795
+
796
  except Exception as e:
797
+ yield (
798
+ gr.update(visible=False), # hide loading
799
+ gr.update(visible=True),
800
  {"error": f"API call failed: {str(e)}"},
801
+ None
 
 
802
  )
803
 
804
+ # Wire events
805
+ spec_choice.change(fn=update_auth_fields, inputs=[spec_choice], outputs=[identitynow_auth, okta_auth, iiq_auth])
806
+ verify_btn.click(
807
+ fn=verify_and_load_endpoints,
808
+ inputs=[
809
+ spec_choice,
810
+ api_base_url,
811
+ grant_type,
812
+ client_id,
813
+ client_secret,
814
+ api_token,
815
+ iiq_username,
816
+ iiq_password
817
+ ],
818
+ outputs=[
819
+ loading_indicator, # Add loading indicator to outputs
820
+ credentials_message,
821
+ endpoints_section,
822
+ fetch_section,
823
+ *[comp for acc_cb in accordion_placeholders for comp in acc_cb]
824
+ ]
825
+ )
826
+
827
  spec_choice.change(
828
+ fn=update_auth_fields,
829
+ inputs=[spec_choice],
830
+ outputs=[
831
+ identitynow_auth, # auth columns
832
+ okta_auth,
833
+ iiq_auth,
834
+ endpoints_section, # sections
835
+ fetch_section,
836
+ results_section,
837
+ results_out, # results
838
+ download_out,
839
+ credentials_message # message
840
+ ]
841
  )
842
+
843
+
844
+ lock_endpoints_btn.click(
845
+ fn=lock_selected_endpoints,
846
+ inputs=[cb for _, cb in accordion_placeholders],
847
+ outputs=[
848
+ lock_loading_indicator, # Add loading indicator
849
+ locked_endpoints_state, # Add state to outputs
850
+ lock_endpoints_btn,
851
+ unlock_endpoints_btn,
852
+ *[cb for _, cb in accordion_placeholders],
853
+ param_group,
854
+ param_header,
855
+ *[comp for group, display, input_box in param_components
856
+ for comp in (group, display, input_box)],
857
+ fetch_btn
858
+ ]
859
  )
860
+
861
+ unlock_endpoints_btn.click(
862
+ fn=unlock_selected_endpoints,
863
+ inputs=[],
 
 
 
864
  outputs=[
865
+ credentials_message,
866
+ locked_endpoints_state,
867
+ lock_endpoints_btn,
868
+ unlock_endpoints_btn,
869
+ *[cb for _, cb in accordion_placeholders],
870
  param_group,
871
+ param_header,
872
+ *[comp for group, display, input_box in param_components
873
+ for comp in (group, display, input_box)],
874
+ fetch_btn
875
  ]
876
  )
877
+
878
+ fetch_btn.click(
879
  fn=handle_api_call,
880
  inputs=[
881
+ spec_choice,
882
+ api_base_url,
883
+ grant_type,
884
+ client_id,
885
+ client_secret,
886
+ api_token,
887
+ iiq_username,
 
888
  iiq_password,
889
+ locked_endpoints_state,
890
+ *[input_box for _, _, input_box in param_components]
 
891
  ],
892
+ outputs=[
893
+ fetch_loading_indicator, # Add loading indicator
894
+ results_section,
895
+ results_out,
896
+ download_out
897
+ ]
898
  )
899
+
900
  if __name__ == "__main__":
901
+ demo.launch(show_error=True)