From d3adf7151779fea4fba69555860831309f3cef5f Mon Sep 17 00:00:00 2001 From: Samuel Henriquez Date: Sat, 16 Aug 2025 14:17:55 -0700 Subject: [PATCH 1/2] Provider refactor Goobercoded a refactor to the provider so that it maybe --- metadata.json | 2 +- operator/metadata.json | 2 +- pkg/api/operator:sortugdev.os-v0.wit | 4 - provider/metadata.json | 2 +- provider/provider/Cargo.toml | 1 + provider/provider/src/curl_executor.rs | 188 +++++++ provider/provider/src/lib.rs | 32 +- provider/provider/src/util.rs | 232 +-------- provider/ui/src/App.tsx | 33 +- .../ui/src/components/CurlImportModal.tsx | 327 +++--------- .../ui/src/components/CurlTemplateEditor.tsx | 301 +++++++++++ .../ui/src/components/ProviderConfigModal.tsx | 242 +++------ .../ui/src/components/ValidationPanel.tsx | 154 ++---- provider/ui/src/types/hypergrid_provider.ts | 25 +- provider/ui/src/utils/providerFormUtils.ts | 476 ++++++------------ 15 files changed, 870 insertions(+), 1151 deletions(-) delete mode 100644 pkg/api/operator:sortugdev.os-v0.wit create mode 100644 provider/provider/src/curl_executor.rs create mode 100644 provider/ui/src/components/CurlTemplateEditor.tsx diff --git a/metadata.json b/metadata.json index 5adae6a..eb77b10 100644 --- a/metadata.json +++ b/metadata.json @@ -5,7 +5,7 @@ "properties": { "package_name": "hypergrid", "current_version": "1.0.0", - "publisher": "ware.hypr", + "publisher": "test.hypr", "mirrors": ["sam.hypr", "backup-distro-node.os"], "code_hashes": { "1.0.0": "001a49117374abc3bdb38179d8ce05d76205b008bb55683e116be36f3e1635ce" diff --git a/operator/metadata.json b/operator/metadata.json index ffddb19..9cb37cd 100644 --- a/operator/metadata.json +++ b/operator/metadata.json @@ -5,7 +5,7 @@ "properties": { "package_name": "hypergrid", "current_version": "0.1.2", - "publisher": "ware.hypr", + "publisher": "test.hypr", "mirrors": [], "code_hashes": { "0.1.0": "5ee79597d09c2ca644e41059489f1ed99eb8c5919b2830f9e3e4a1863cb0da88", diff --git a/pkg/api/operator:sortugdev.os-v0.wit b/pkg/api/operator:sortugdev.os-v0.wit deleted file mode 100644 index 4c62030..0000000 --- a/pkg/api/operator:sortugdev.os-v0.wit +++ /dev/null @@ -1,4 +0,0 @@ -world operator-sortugdev-dot-os-v0 { - import operator; - include process-v1; -} \ No newline at end of file diff --git a/provider/metadata.json b/provider/metadata.json index 1afc573..05928ea 100644 --- a/provider/metadata.json +++ b/provider/metadata.json @@ -5,7 +5,7 @@ "properties": { "package_name": "hypergrid", "current_version": "0.1.0", - "publisher": "ware.hypr", + "publisher": "test.hypr", "mirrors": [], "code_hashes": { "0.1.0": "" diff --git a/provider/provider/Cargo.toml b/provider/provider/Cargo.toml index 4e6a091..485e943 100644 --- a/provider/provider/Cargo.toml +++ b/provider/provider/Cargo.toml @@ -2,6 +2,7 @@ anyhow = "1.0.97" base64ct = "=1.6.0" process_macros = "0.1" +regex = "1" rmp-serde = "1.3.0" serde_json = "1.0" url = "2.5.4" diff --git a/provider/provider/src/curl_executor.rs b/provider/provider/src/curl_executor.rs new file mode 100644 index 0000000..474a4e4 --- /dev/null +++ b/provider/provider/src/curl_executor.rs @@ -0,0 +1,188 @@ +use hyperware_app_common::hyperware_process_lib::{ + http::{ + client::{HttpClientError}, + Method as HyperwareHttpMethod, + Response as HyperwareHttpResponse, + }, + logging::{debug, error, info}, +}; +use regex::Regex; +use std::collections::HashMap; +use url::Url; + +use crate::util::send_async_http_request; + +#[derive(Debug, Clone)] +pub struct ParsedCurl { + pub method: HyperwareHttpMethod, + pub url: Url, + pub headers: HashMap, + pub body: Vec, +} + +/// Parse a curl command into its components +pub fn parse_curl_command(curl_command: &str) -> Result { + debug!("Parsing curl command: {}", curl_command); + + // Extract method (-X or --request) + let method_regex = Regex::new(r"(?i)-X\s+([A-Z]+)|--request\s+([A-Z]+)") + .map_err(|e| format!("Failed to compile method regex: {}", e))?; + + let method = if let Some(captures) = method_regex.captures(curl_command) { + let method_str = captures.get(1) + .or(captures.get(2)) + .map(|m| m.as_str()) + .unwrap_or("GET"); + + match method_str.to_uppercase().as_str() { + "GET" => HyperwareHttpMethod::GET, + "POST" => HyperwareHttpMethod::POST, + "PUT" => HyperwareHttpMethod::PUT, + "DELETE" => HyperwareHttpMethod::DELETE, + "PATCH" => HyperwareHttpMethod::PATCH, + "HEAD" => HyperwareHttpMethod::HEAD, + _ => return Err(format!("Unsupported HTTP method: {}", method_str)), + } + } else if curl_command.contains("-d ") || curl_command.contains("--data") { + // If no explicit method but has data, assume POST + HyperwareHttpMethod::POST + } else { + HyperwareHttpMethod::GET + }; + + // Extract headers (-H or --header) + let header_regex = Regex::new(r#"(?i)(?:-H|--header)\s+["']([^"']+)["']"#) + .map_err(|e| format!("Failed to compile header regex: {}", e))?; + + let mut headers = HashMap::new(); + for captures in header_regex.captures_iter(curl_command) { + if let Some(header_str) = captures.get(1) { + let header_content = header_str.as_str(); + if let Some(colon_pos) = header_content.find(':') { + let key = header_content[..colon_pos].trim().to_string(); + let value = header_content[colon_pos + 1..].trim().to_string(); + headers.insert(key, value); + } + } + } + + // Extract body/data (-d or --data) + let data_regex = Regex::new(r#"(?i)(?:-d|--data)\s+(?:["']([^"']+)["']|([^\s]+))"#) + .map_err(|e| format!("Failed to compile data regex: {}", e))?; + + let body = if let Some(captures) = data_regex.captures(curl_command) { + let data_str = captures.get(1) + .or(captures.get(2)) + .map(|m| m.as_str()) + .unwrap_or(""); + data_str.as_bytes().to_vec() + } else { + Vec::new() + }; + + // Extract URL (typically the last non-flag argument) + // First try to find URL in quotes + let url_quoted_regex = Regex::new(r#"["']([^"']*(?:https?://|/)[^"']*)["']"#) + .map_err(|e| format!("Failed to compile URL quoted regex: {}", e))?; + + // Then try without quotes + let url_unquoted_regex = Regex::new(r"(https?://[^\s]+)") + .map_err(|e| format!("Failed to compile URL unquoted regex: {}", e))?; + + let url_str = if let Some(captures) = url_quoted_regex.captures(curl_command) { + captures.get(1).map(|m| m.as_str()).unwrap_or("") + } else if let Some(captures) = url_unquoted_regex.captures(curl_command) { + captures.get(1).map(|m| m.as_str()).unwrap_or("") + } else { + return Err("No URL found in curl command".to_string()); + }; + + let url = Url::parse(url_str) + .map_err(|e| format!("Failed to parse URL '{}': {}", url_str, e))?; + + Ok(ParsedCurl { + method, + url, + headers, + body, + }) +} + +/// Execute a curl template with variable substitution +pub async fn execute_curl_template( + template: &str, + arguments: &Vec<(String, String)>, +) -> Result { + info!("Executing curl template with {} arguments", arguments.len()); + + // Step 1: Variable substitution + let mut curl_command = template.to_string(); + for (key, value) in arguments { + let placeholder = format!("{{{{{}}}}}", key); + debug!("Replacing {} with value", placeholder); + curl_command = curl_command.replace(&placeholder, value); + } + + // Check for any remaining placeholders + let remaining_placeholder_regex = Regex::new(r"\{\{[^}]+\}\}") + .map_err(|e| format!("Failed to compile placeholder regex: {}", e))?; + + if remaining_placeholder_regex.is_match(&curl_command) { + let missing_vars: Vec = remaining_placeholder_regex + .find_iter(&curl_command) + .map(|m| m.as_str().to_string()) + .collect(); + return Err(format!( + "Missing values for variables: {}", + missing_vars.join(", ") + )); + } + + // Step 2: Parse curl command + let parsed = parse_curl_command(&curl_command)?; + + debug!( + "Parsed curl - Method: {:?}, URL: {}, Headers: {:?}, Body size: {}", + parsed.method, + parsed.url, + parsed.headers, + parsed.body.len() + ); + + // Step 3: Execute using existing HTTP client + let response = send_async_http_request( + parsed.method, + parsed.url, + Some(parsed.headers), + 60, + parsed.body, + ) + .await + .map_err(|e| format!("HTTP request failed: {:?}", e))?; + + // Step 4: Format response + format_response(response) +} + +/// Format HTTP response into a string +pub fn format_response(response: HyperwareHttpResponse>) -> Result { + let status = response.status(); + let body_bytes = response.into_body(); + + // Try to convert body to string + let body_str = String::from_utf8(body_bytes.clone()) + .unwrap_or_else(|_| { + // If not valid UTF-8, return base64 encoded + format!("[Binary data, {} bytes]", body_bytes.len()) + }); + + if status.is_success() { + Ok(body_str) + } else { + Err(format!( + "HTTP request failed with status {}: {}", + status.as_u16(), + body_str + )) + } +} \ No newline at end of file diff --git a/provider/provider/src/lib.rs b/provider/provider/src/lib.rs index c0cb5ef..e6155f8 100644 --- a/provider/provider/src/lib.rs +++ b/provider/provider/src/lib.rs @@ -26,6 +26,9 @@ use util::*; // Use its public items mod db; // Declare the db module use db::*; // Use its public items +mod curl_executor; // Declare the curl_executor module +use curl_executor::*; // Use its public items + pub mod constants; // Declare the constants module #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ProviderRequest { @@ -51,14 +54,14 @@ pub struct ValidateAndRegisterRequest { pub validation_arguments: Vec<(String, String)>, } -// Type system for API endpoints +// Type system for API endpoints - kept for backward compatibility #[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] pub enum HttpMethod { GET, POST, } -// --- Added Enum for Request Structure --- +// --- Added Enum for Request Structure - kept for backward compatibility --- #[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] pub enum RequestStructureType { GetWithPath, @@ -76,22 +79,19 @@ pub enum TerminalCommand { ViewDatabase, } -// --- Modified EndpointDefinition --- +// --- New Variable struct for curl templates --- +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +pub struct Variable { + pub name: String, + pub example_value: Option, +} + +// --- New EndpointDefinition with curl templates --- #[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] pub struct EndpointDefinition { - pub name: String, // Operation name, e.g., "getUserById" - pub method: HttpMethod, // GET, POST - pub request_structure: RequestStructureType, // Explicitly define the structure - pub base_url_template: String, // e.g., "https://api.example.com/users/{id}" or "https://api.example.com/v{apiVersion}/users" - pub path_param_keys: Option>, // Keys for placeholders in base_url_template, relevant for GetWithPath, PostWithJson - pub query_param_keys: Option>, // Keys for dynamic query params, relevant for GetWithQuery, PostWithJson - pub header_keys: Option>, // Keys for dynamic headers (always potentially relevant) - pub body_param_keys: Option>, // Keys for dynamic body params, relevant for PostWithJson - - pub api_key: Option, // The actual secret key - - pub api_key_query_param_name: Option, // e.g., "api_key" - pub api_key_header_name: Option, // e.g., "X-API-Key" + pub name: String, + pub curl_template: String, + pub variables: Vec, } // --- New Provider Struct --- diff --git a/provider/provider/src/util.rs b/provider/provider/src/util.rs index c7991fd..72f53ce 100644 --- a/provider/provider/src/util.rs +++ b/provider/provider/src/util.rs @@ -1,4 +1,4 @@ -use crate::{EndpointDefinition, HttpMethod, ProviderRequest}; +use crate::{EndpointDefinition, ProviderRequest}; use crate::constants::{USDC_BASE_ADDRESS, WALLET_PREFIX}; use hyperware_app_common::hyperware_process_lib::kiprintln; use hyperware_app_common::hyperware_process_lib::{ @@ -170,231 +170,31 @@ pub async fn get_logs_for_tx( // Moved call_provider function pub async fn call_provider( provider_id_for_log: String, - endpoint_def: EndpointDefinition, // This type needs to be available here + endpoint_def: EndpointDefinition, dynamic_args: &Vec<(String, String)>, source: String, ) -> Result { info!( - "Calling provider via util: {}, endpoint: {}, structure: {:?}", + "Calling provider via util: {}, endpoint: {}", provider_id_for_log, - endpoint_def.name, - endpoint_def.request_structure + endpoint_def.name ); - let args_map: HashMap = dynamic_args.iter().cloned().collect(); + // Add the source node ID as a special variable for the curl template + let mut augmented_args = dynamic_args.clone(); + augmented_args.push(("X-Insecure-HPN-Client-Node-Id".to_string(), source.clone())); - // --- 1. Prepare Headers (API Key + General) --- - let mut http_headers = HashMap::new(); - let mut api_key_in_header = false; - if let (Some(header_name), Some(api_key_value)) = - (&endpoint_def.api_key_header_name, &endpoint_def.api_key) - { - if !api_key_value.is_empty() && !header_name.is_empty() { - http_headers.insert(header_name.clone(), api_key_value.clone()); - debug!("API Key added to header: {}", header_name); - api_key_in_header = true; - } - } - if let Some(header_keys) = &endpoint_def.header_keys { - for key in header_keys { - if let Some(value) = args_map.get(key) { - if !(api_key_in_header && endpoint_def.api_key_header_name.as_deref() == Some(key)) - { - http_headers.insert(key.clone(), value.clone()); - } - } else { - warn!( - "Warning: Missing dynamic argument for header key: '{}'", - key - ); - } - } - } - - http_headers.insert("X-Insecure-HPN-Client-Node-Id".to_string(), source); - - debug!( - "Prepared headers (before body processing): {:?}", - http_headers - ); - - // --- 2. Process URL (Path Params, Query Params based on structure) --- - let mut processed_url_template = endpoint_def.base_url_template.clone(); - let mut query_params_to_add: Vec<(String, String)> = Vec::new(); - let mut body_data = HashMap::new(); - - match endpoint_def.request_structure { - // Assuming RequestStructureType is also available via super:: or globally - super::RequestStructureType::GetWithPath => { - debug!("Structure: GetWithPath - Processing path parameters."); - if let Some(path_keys) = &endpoint_def.path_param_keys { - for path_key in path_keys { - if let Some(value) = args_map.get(path_key) { - processed_url_template = - processed_url_template.replace(&format!("{{{}}}", path_key), value); - } else { - warn!( - "Warning: Missing path parameter '{}' for URL template", - path_key - ); - } - } - } - } - super::RequestStructureType::GetWithQuery => { - debug!("Structure: GetWithQuery - Processing query parameters."); - if let Some(query_keys) = &endpoint_def.query_param_keys { - for key in query_keys { - if let Some(value) = args_map.get(key) { - query_params_to_add.push((key.clone(), value.clone())); - } else { - warn!("Warning: Missing dynamic argument for query key: '{}'", key); - } - } - } - } - super::RequestStructureType::PostWithJson => { - debug!("Structure: PostWithJson - Processing path, query, and body parameters."); - if let Some(path_keys) = &endpoint_def.path_param_keys { - for path_key in path_keys { - if let Some(value) = args_map.get(path_key) { - processed_url_template = - processed_url_template.replace(&format!("{{{}}}", path_key), value); - } else { - warn!( - "Warning: Missing optional path parameter '{}' for POST URL template", - path_key - ); - } - } - } - if let Some(query_keys) = &endpoint_def.query_param_keys { - for key in query_keys { - if let Some(value) = args_map.get(key) { - query_params_to_add.push((key.clone(), value.clone())); - } else { - warn!( - "Warning: Missing optional dynamic argument for query key: '{}'", - key - ); - } - } - } - if let Some(body_keys) = &endpoint_def.body_param_keys { - if !body_keys.is_empty() { - for key in body_keys { - if let Some(value) = args_map.get(key) { - body_data.insert(key.clone(), value.clone()); - } else { - warn!("Warning: Missing dynamic argument for body key: '{}'", key); - } - } - debug!("Collected body data: {:?}", body_data.keys()); - } else { - debug!("POST request configured with explicitly empty body_param_keys. No body generated from dynamic args."); - } - } else { - debug!("POST request configured without body_param_keys specified (Option is None). Body will be empty."); - } - } - } - - // --- 3. Finalize URL with Query Params (including API Key if needed) --- - let mut final_url = Url::parse(&processed_url_template).map_err(|e| { - let error_msg = format!( - "Invalid base URL template after path substitution: {} -> {}: {}", - endpoint_def.base_url_template, processed_url_template, e - ); - error!( - "URL parsing failed for provider '{}': original_template={}, processed_template={}, error={}", - provider_id_for_log, endpoint_def.base_url_template, processed_url_template, e - ); - error_msg - })?; - - { - let mut query_pairs = final_url.query_pairs_mut(); - for (key, value) in query_params_to_add { - query_pairs.append_pair(&key, &value); - } - if !api_key_in_header { - if let (Some(param_name), Some(api_key_value)) = ( - &endpoint_def.api_key_query_param_name, - &endpoint_def.api_key, - ) { - if !api_key_value.is_empty() && !param_name.is_empty() { - query_pairs.append_pair(param_name, api_key_value); - debug!("API Key added to query parameter: {}", param_name); - } - } - } - } - - let final_url_str = final_url.to_string(); - debug!("Final URL for call: {}", final_url_str); - - // --- 4. Finalize Body and Headers for POST --- - let mut body_bytes: Vec = Vec::new(); - if endpoint_def.method == HttpMethod::POST { - // HttpMethod also needs to be available - if !body_data.is_empty() { - http_headers.insert("Content-Type".to_string(), "application/json".to_string()); - debug!("Added Content-Type: application/json header because POST body is present."); - - body_bytes = serde_json::to_vec(&body_data).map_err(|e| { - let error_msg = format!( - "Failed to serialize POST body: {}. Data: {:?}", - e, body_data - ); - error!( - "JSON serialization failed for provider '{}': endpoint={}, body_data={:?}, error={}", - provider_id_for_log, endpoint_def.name, body_data, e - ); - error_msg - })?; - debug!("POST Body Bytes Length: {}", body_bytes.len()); - } else { - warn!("POST request proceeding with empty body."); - } - } - debug!("Final Headers being sent: {:?}", http_headers); - - // --- 5. Determine HTTP Method --- - let http_client_method = match endpoint_def.method { - // HttpMethod - HttpMethod::GET => HyperwareHttpMethod::GET, - HttpMethod::POST => HyperwareHttpMethod::POST, - }; - debug!("HTTP Method for call: {:?}", http_client_method); - - // --- 6. Execute HTTP Request --- Reuses send_async_http_request from this file - let timeout_seconds = 60; - match send_async_http_request( - http_client_method, - final_url, - Some(http_headers), - timeout_seconds, - body_bytes, - ) - .await - { - Ok(response) => { - let status = response.status().as_u16(); - let response_body_bytes = response.body().to_vec(); - let body_result = String::from_utf8(response_body_bytes) - .map_err(|e| { - error!("Failed to parse response body as UTF-8: {}", e); - format!("Failed to parse response body as UTF-8: {}", e) - })?; - - // Try to parse the body as JSON to avoid double-encoding - let body_json = match serde_json::from_str::(&body_result) { + // Use the new curl template execution + match crate::curl_executor::execute_curl_template(&endpoint_def.curl_template, &augmented_args).await { + Ok(response_body) => { + // Try to parse the body as JSON to format consistent with old implementation + let body_json = match serde_json::from_str::(&response_body) { Ok(json_value) => json_value, - Err(_) => serde_json::Value::String(body_result), // If not JSON, wrap as string + Err(_) => serde_json::Value::String(response_body), // If not JSON, wrap as string }; let response_wrapper = serde_json::json!({ - "status": status, + "status": 200, // Assuming success if we got here "body": body_json }); @@ -402,7 +202,7 @@ pub async fn call_provider( } Err(e) => { error!( - "API call failed for {}: {} - Error: {:?}", + "API call failed for {}: {} - Error: {}", provider_id_for_log, endpoint_def.name, e ); Err(format!("API call failed: {}", e)) @@ -724,7 +524,7 @@ pub fn validate_response_status(response: &str) -> Result<(), String> { } } -//use crate::{EndpointDefinition, HttpMethod, ProviderRequest}; +//use crate::{EndpointDefinition, ProviderRequest}; //use hyperware_app_common::hyperware_process_lib::kiprintln; //use hyperware_app_common::hyperware_process_lib::{ // eth::{Address as EthAddress, EthError, TransactionReceipt, TxHash, U256}, diff --git a/provider/ui/src/App.tsx b/provider/ui/src/App.tsx index 9351e3f..ef550c7 100644 --- a/provider/ui/src/App.tsx +++ b/provider/ui/src/App.tsx @@ -26,7 +26,6 @@ import ProviderConfigModal from "./components/ProviderConfigModal"; import RegisteredProviderView from './components/RegisteredProviderView'; import { processRegistrationResponse, - ProviderFormData, processUpdateResponse, createSmartUpdatePlan } from "./utils/providerFormUtils"; @@ -236,7 +235,7 @@ function AppContent() { "~price": provider.price.toString(), "~wallet": provider.registered_provider_wallet, "~provider-id": provider.provider_id, - "~site": provider.endpoint.base_url_template, + "~site": provider.endpoint.curl_template, }; const structuredDataToCopy = { [hnsName]: metadata, @@ -266,22 +265,16 @@ function AppContent() { } }, [isWalletConnected, providerRegistration]); - const handleProviderUpdate = useCallback(async (provider: RegisteredProvider, formData: ProviderFormData) => { + const handleProviderUpdate = useCallback(async (updatedProvider: RegisteredProvider) => { // This will handle the smart update system try { - const updatePlan = createSmartUpdatePlan(provider, formData); - - // Warn about instructions if config changed but instructions weren't updated - if (updatePlan.shouldWarnAboutInstructions) { - const confirmUpdate = confirm( - 'You\'ve made changes to the API configuration but haven\'t updated the instructions. ' + - 'This might create a mismatch between your actual API and the instructions users see. ' + - 'Do you want to continue with the update anyway?' - ); - if (!confirmUpdate) { - return; - } + // Use editingProvider as the original provider for comparison + if (!editingProvider) { + alert('No provider selected for editing'); + return; } + + const updatePlan = createSmartUpdatePlan(editingProvider, updatedProvider); // Check if wallet is needed for on-chain updates if (updatePlan.needsOnChainUpdate && !isWalletConnected) { @@ -294,7 +287,7 @@ function AppContent() { // Step 1: Update off-chain data (backend) if needed if (updatePlan.needsOffChainUpdate) { console.log('Updating off-chain data...'); - const response = await updateProviderApi(provider.provider_name, updatePlan.updatedProvider); + const response = await updateProviderApi(updatedProvider.provider_name, updatePlan.updatedProvider); const feedback = processUpdateResponse(response); if (!response.Ok) { @@ -312,14 +305,14 @@ function AppContent() { try { // Look up the actual TBA address for this provider from backend - const tbaAddress = await lookupProviderTbaAddressFromBackend(provider.provider_name, publicClient); + const tbaAddress = await lookupProviderTbaAddressFromBackend(updatedProvider.provider_name, publicClient); if (!tbaAddress) { - alert(`No blockchain entry found for provider "${provider.provider_name}". Please register on the hypergrid first.`); + alert(`No blockchain entry found for provider "${updatedProvider.provider_name}". Please register on the hypergrid first.`); return; } - console.log(`Found TBA address: ${tbaAddress} for provider: ${provider.provider_name}`); + console.log(`Found TBA address: ${tbaAddress} for provider: ${updatedProvider.provider_name}`); // Execute the blockchain update await providerUpdate.updateProviderNotes(tbaAddress, updatePlan.onChainNotes); @@ -339,7 +332,7 @@ function AppContent() { console.error('Failed to update provider: ', err); alert('Failed to update provider.'); } - }, [isWalletConnected, handleProviderUpdated, publicClient, providerUpdate, resetEditState, handleCloseAddNewModal]); + }, [isWalletConnected, editingProvider, handleProviderUpdated, publicClient, providerUpdate, resetEditState, handleCloseAddNewModal]); diff --git a/provider/ui/src/components/CurlImportModal.tsx b/provider/ui/src/components/CurlImportModal.tsx index e7de8f7..c2fcf5b 100644 --- a/provider/ui/src/components/CurlImportModal.tsx +++ b/provider/ui/src/components/CurlImportModal.tsx @@ -1,14 +1,13 @@ import React, { useState } from 'react'; import Modal from './Modal'; -import { parseCurlCommand, curlToFormData } from '../utils/curlParser.ts'; -import type { CurlToFormMapping } from '../utils/curlParser.ts'; -import { FiCheckCircle, FiAlertTriangle, FiXCircle } from 'react-icons/fi'; +import { Variable } from '../types/hypergrid_provider'; +import { FiCheckCircle } from 'react-icons/fi'; import { FaCopy } from 'react-icons/fa6'; interface CurlImportModalProps { isOpen: boolean; onClose: () => void; - onImport: (formData: Partial) => void; + onImport: (template: string, variables: Variable[]) => void; } const CurlImportModal: React.FC = ({ @@ -17,297 +16,97 @@ const CurlImportModal: React.FC = ({ onImport }) => { const [curlInput, setCurlInput] = useState(''); - const [parseResult, setParseResult] = useState(null); - const [showPreview, setShowPreview] = useState(false); - - const handleParseCurl = () => { - if (!curlInput.trim()) return; - - const parsed = parseCurlCommand(curlInput.trim()); - const formMapping = curlToFormData(parsed); - setParseResult(formMapping); - setShowPreview(true); - }; const handleImport = () => { - if (!parseResult) return; - - // Convert to the format expected by the form - const formData: Partial = { - endpointBaseUrl: parseResult.endpointBaseUrl, - topLevelRequestType: parseResult.topLevelRequestType, - pathParamKeys: parseResult.pathParamKeys, - queryParamKeys: parseResult.queryParamKeys, - headerKeys: parseResult.headerKeys, - bodyKeys: parseResult.bodyKeys, - endpointApiParamKey: parseResult.endpointApiParamKey, - authChoice: parseResult.authChoice, - apiKeyQueryParamName: parseResult.apiKeyQueryParamName, - apiKeyHeaderName: parseResult.apiKeyHeaderName, - }; + const trimmedCurl = curlInput.trim(); + if (!trimmedCurl) { + alert('Please enter a curl command'); + return; + } - onImport(formData); + // Simply pass the curl command as-is + // Variables will be selected later in the CurlTemplateEditor + onImport(trimmedCurl, []); handleClose(); }; const handleClose = () => { setCurlInput(''); - setParseResult(null); - setShowPreview(false); onClose(); }; const exampleCurl = `curl -X GET \\ -H "X-API-Key: your-api-key" \\ -H "Content-Type: application/json" \\ - "https://api.example.com/v1/users/123/profile?include=email&format=json"`; + https://api.example.com/v1/data`; if (!isOpen) return null; return ( - -
- {!showPreview ? ( - // Step 1: Curl Input -
-
-
- -
-

How to use Curl Import

-

- Paste a curl command below and we'll automatically extract the API configuration. - This works best with standard REST APIs using GET or POST methods. -

-
+ +
+
+
+
+ +
+

Import Curl Command

+

+ Paste a curl command below to use it as a template. + After importing, you can select parts of the command to turn into variables. +

+
-
- -