@@ -276,6 +282,22 @@
import { ref, computed, watch, nextTick } from 'vue';
import { useProcessBuilderStore } from '@/stores/processBuilder';
import VariableBrowser from './VariableBrowser.vue';
+import KeyValueTable from './KeyValueTable.vue';
+
+const AUTH_TYPES = [
+ { value: 'none', label: 'None' },
+ { value: 'bearer', label: 'Bearer Token' },
+ { value: 'basic', label: 'Basic Auth' },
+ { value: 'apiKey', label: 'API Key' }
+];
+
+const BODY_TYPES = [
+ { value: 'none', label: 'None' },
+ { value: 'form-data', label: 'form-data' },
+ { value: 'x-www-form-urlencoded', label: 'x-www-form-urlencoded' },
+ { value: 'raw', label: 'raw' },
+ { value: 'binary', label: 'binary' }
+];
const props = defineProps({
nodeData: {
@@ -294,17 +316,7 @@ const emit = defineEmits(['update']);
const processStore = useProcessBuilderStore();
// Local state for node data - create a deep copy to avoid mutation issues
-const localNodeData = ref({
- label: 'API Call',
- description: '',
- apiMethod: 'GET',
- apiUrl: '',
- requestBody: '',
- headers: '{ "Content-Type": "application/json" }',
- outputVariable: 'apiResponse',
- continueOnError: false,
- errorVariable: 'apiError'
-});
+const localNodeData = ref(migrateNodeData(props.nodeData));
// Computed for showing request body based on method
const showRequestBody = computed(() => {
@@ -324,22 +336,16 @@ const availableVariables = computed(() => {
return globalVars;
});
+// Get process variables for substitution
+const processVariables = computed(() => {
+ // Use processStore.currentProcess.variables (object) and convert to array
+ const varsObj = processStore.currentProcess?.variables || {};
+ return Object.values(varsObj);
+});
+
// Watch for changes from parent props
watch(() => props.nodeData, (newNodeData) => {
- if (newNodeData) {
- // Create a deep copy to break reactivity chains with parent
- localNodeData.value = {
- label: newNodeData.label || 'API Call',
- description: newNodeData.description || '',
- apiMethod: newNodeData.apiMethod || 'GET',
- apiUrl: newNodeData.apiUrl || '',
- requestBody: newNodeData.requestBody || '',
- headers: newNodeData.headers || '{ "Content-Type": "application/json" }',
- outputVariable: newNodeData.outputVariable || 'apiResponse',
- continueOnError: newNodeData.continueOnError || false,
- errorVariable: newNodeData.errorVariable || 'apiError'
- };
- }
+ if (newNodeData) localNodeData.value = migrateNodeData(newNodeData);
}, { immediate: true, deep: true });
// Function to create a new global variable
@@ -559,6 +565,178 @@ function getPreviewWithValues(field) {
return localNodeData.value[field] || '';
}
}
+
+// --- Authorization Fields ---
+function getAuthFields() {
+ switch (localNodeData.value.authorization.type) {
+ case 'bearer':
+ return [
+ { key: 'token', label: 'Token', type: 'text', placeholder: 'Bearer token', value: localNodeData.value.authorization.token || '' }
+ ];
+ case 'basic':
+ return [
+ { key: 'username', label: 'Username', type: 'text', placeholder: 'Username', value: localNodeData.value.authorization.username || '' },
+ { key: 'password', label: 'Password', type: 'password', placeholder: 'Password', value: localNodeData.value.authorization.password || '' }
+ ];
+ case 'apiKey':
+ return [
+ { key: 'key', label: 'Key Name', type: 'text', placeholder: 'e.g. X-API-Key', value: localNodeData.value.authorization.key || '' },
+ { key: 'value', label: 'Key Value', type: 'text', placeholder: 'API Key Value', value: localNodeData.value.authorization.value || '' },
+ { key: 'in', label: 'Add To', type: 'select', options: [
+ { value: 'header', label: 'Header' },
+ { value: 'query', label: 'Query Param' }
+ ], value: localNodeData.value.authorization.in || 'header' }
+ ];
+ default:
+ return [];
+ }
+}
+function updateAuthField(field, value) {
+ localNodeData.value.authorization[field] = value;
+ saveChanges();
+}
+
+// --- Body Data Handling ---
+function getBodyData() {
+ if (['form-data', 'x-www-form-urlencoded'].includes(localNodeData.value.body.type)) {
+ return Array.isArray(localNodeData.value.body.data) ? localNodeData.value.body.data : [];
+ }
+ if (localNodeData.value.body.type === 'raw') {
+ return typeof localNodeData.value.body.data === 'string' ? localNodeData.value.body.data : '';
+ }
+ if (localNodeData.value.body.type === 'binary') {
+ return localNodeData.value.body.data || null;
+ }
+ return [];
+}
+function setBodyData(val) {
+ if (['form-data', 'x-www-form-urlencoded'].includes(localNodeData.value.body.type)) {
+ localNodeData.value.body.data = val;
+ } else if (localNodeData.value.body.type === 'raw') {
+ localNodeData.value.body.data = val;
+ } else if (localNodeData.value.body.type === 'binary') {
+ localNodeData.value.body.data = val;
+ }
+ saveChanges();
+}
+
+// --- File Upload for Binary ---
+function handleFileUpload(e) {
+ const file = e.target.files[0];
+ if (file) {
+ localNodeData.value.body.data = file.name;
+ saveChanges();
+ }
+}
+
+// --- JSON Validation for Raw Body ---
+const rawBodyIsJson = computed(() => {
+ if (bodyType.value !== 'raw') return false;
+ const val = localNodeData.value.body?.data || '';
+ // Only check if it looks like JSON
+ if (!val.trim().startsWith('{') && !val.trim().startsWith('[')) return false;
+ try {
+ // Replace {variable} with a string to allow parsing
+ const replaced = val.replace(/\{[a-zA-Z0-9_]+\}/g, '"VAR"');
+ JSON.parse(replaced);
+ return true;
+ } catch {
+ return false;
+ }
+});
+const rawBodyHasVariable = computed(() => {
+ if (bodyType.value !== 'raw') return false;
+ const val = localNodeData.value.body?.data || '';
+ return /\{[a-zA-Z0-9_]+\}/.test(val);
+});
+const rawBodyJsonWarning = computed(() => {
+ if (bodyType.value !== 'raw') return '';
+ const val = localNodeData.value.body?.data || '';
+ if (!val.trim().startsWith('{') && !val.trim().startsWith('[')) return '';
+ try {
+ // Replace both quoted and unquoted {variable} with "VAR"
+ let replaced = val
+ .replace(/"\{[a-zA-Z0-9_]+\}"/g, '"VAR"') // quoted
+ .replace(/\{[a-zA-Z0-9_]+\}/g, '"VAR"'); // unquoted
+ JSON.parse(replaced);
+ return '';
+ } catch {
+ return 'Warning: Your request body is not valid JSON. Make sure variables are inside quotes.';
+ }
+});
+
+// --- Variable Insertion Helper ---
+function insertVariableToRaw(varName) {
+ if (!varName) return;
+ const textarea = document.querySelector('textarea[placeholder="Raw body (JSON, XML, etc.)"]');
+ let val = localNodeData.value.body.data || '';
+ let insertText = `{${varName}}`;
+ // Detect if inside quotes
+ let cursor = textarea ? textarea.selectionStart : val.length;
+ let before = val.substring(0, cursor);
+ let after = val.substring(cursor);
+ // Check if body looks like JSON
+ const isJson = val.trim().startsWith('{') || val.trim().startsWith('[');
+ // Check if cursor is inside quotes
+ let insideQuotes = false;
+ if (isJson && textarea) {
+ // Count quotes before cursor
+ const quotesBefore = before.split('"').length - 1;
+ insideQuotes = quotesBefore % 2 === 1;
+ }
+ if (isJson && !insideQuotes) {
+ insertText = `"{${varName}}"`;
+ }
+ // Insert at cursor
+ localNodeData.value.body.data = before + insertText + after;
+ saveChanges();
+ // Move cursor after inserted variable
+ nextTick(() => {
+ if (textarea) {
+ const newPos = before.length + insertText.length;
+ textarea.focus();
+ textarea.selectionStart = textarea.selectionEnd = newPos;
+ }
+ });
+}
+
+// --- UI State ---
+const showParams = computed(() => ['GET', 'DELETE', 'HEAD', 'OPTIONS'].includes(localNodeData.value.apiMethod));
+
+// Defensive computed for bodyType and authType
+const bodyType = computed(() => localNodeData.value.body && localNodeData.value.body.type ? localNodeData.value.body.type : 'none');
+const authType = computed(() => localNodeData.value.authorization && localNodeData.value.authorization.type ? localNodeData.value.authorization.type : 'none');
+
+function migrateNodeData(data) {
+ const migrated = { ...data };
+ // Headers
+ if (typeof migrated.headers === 'string') {
+ try {
+ const obj = JSON.parse(migrated.headers);
+ migrated.headers = Object.entries(obj).map(([key, value]) => ({ key, value }));
+ } catch {
+ migrated.headers = [];
+ }
+ } else if (!Array.isArray(migrated.headers)) {
+ migrated.headers = [];
+ }
+ // Params
+ if (!Array.isArray(migrated.params)) migrated.params = [];
+ // Body
+ if (!migrated.body || typeof migrated.body !== 'object') {
+ migrated.body = { type: 'none', data: [] };
+ } else {
+ if (!migrated.body.type) migrated.body.type = 'none';
+ if (migrated.body.data === undefined) migrated.body.data = migrated.body.type === 'raw' ? '' : [];
+ }
+ if (typeof data.requestBody === 'string' && (!migrated.body || migrated.body.type === 'none')) {
+ migrated.body = { type: 'raw', data: data.requestBody };
+ }
+ // Auth
+ if (!migrated.authorization || typeof migrated.authorization !== 'object') migrated.authorization = { type: 'none' };
+ if (!migrated.authorization.type) migrated.authorization.type = 'none';
+ return migrated;
+}