247 lines
8.1 KiB
Vue
247 lines
8.1 KiB
Vue
<template>
|
|
<div class="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 shadow-sm">
|
|
<div class="p-6">
|
|
<!-- Enhanced URL Input Section -->
|
|
<div class="flex items-center gap-3 mb-6">
|
|
<FormKit
|
|
type="select"
|
|
v-model="httpMethod"
|
|
:options="httpMethods"
|
|
outer-class="!mb-0 w-32 flex-shrink-0"
|
|
inner-class="!mb-0 font-medium"
|
|
/>
|
|
<div class="flex-1 relative">
|
|
<FormKit
|
|
type="text"
|
|
v-model="requestUrl"
|
|
placeholder="Enter request URL (e.g., https://httpbin.org/get)"
|
|
outer-class="!mb-0"
|
|
inner-class="!mb-0 pr-10 !bg-white dark:!bg-gray-800"
|
|
/>
|
|
<!-- Variable indicator -->
|
|
<div v-if="urlVariables.length > 0" class="absolute right-3 top-1/2 transform -translate-y-1/2">
|
|
<rs-button
|
|
variant="text"
|
|
size="sm"
|
|
@click="showVariablePreview = !showVariablePreview"
|
|
class="p-1 text-blue-500 hover:text-blue-600 transition-colors"
|
|
:title="`${urlVariables.length} variable(s) found`"
|
|
>
|
|
<Icon name="ic:outline-code" size="16" />
|
|
</rs-button>
|
|
</div>
|
|
</div>
|
|
<rs-button
|
|
variant="primary"
|
|
@click="handleSendRequest"
|
|
:loading="isLoading"
|
|
class="px-8 py-2.5 flex-shrink-0 font-medium"
|
|
>
|
|
<Icon name="ic:outline-send" size="16" class="mr-2" />
|
|
Send
|
|
</rs-button>
|
|
</div>
|
|
|
|
<!-- Enhanced Variable Preview -->
|
|
<div v-if="showVariablePreview && urlVariables.length > 0" class="mb-6 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
|
|
<div class="flex items-center gap-2 mb-3">
|
|
<Icon name="ic:outline-code" size="16" class="text-blue-600 dark:text-blue-400" />
|
|
<h4 class="text-sm font-medium text-blue-900 dark:text-blue-100">Variables in URL</h4>
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-2">
|
|
<div
|
|
v-for="variable in urlVariables"
|
|
:key="variable.name"
|
|
class="flex items-center gap-2 text-sm p-2 bg-white dark:bg-blue-900/40 rounded border"
|
|
>
|
|
<span class="font-mono text-blue-700 dark:text-blue-300 font-medium" v-text="formatVariableName(variable.name)"></span>
|
|
<Icon
|
|
:name="variable.found ? 'ic:outline-check' : 'ic:outline-warning'"
|
|
size="14"
|
|
:class="variable.found ? 'text-green-500' : 'text-yellow-500'"
|
|
/>
|
|
<span :class="variable.found ? 'text-green-700 dark:text-green-300' : 'text-yellow-700 dark:text-yellow-300'" class="truncate">
|
|
{{ variable.found ? variable.value : 'Not defined' }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Enhanced Environment Indicator -->
|
|
<div v-if="currentEnvironment" class="mb-6 flex items-center gap-2 p-3 bg-green-50 dark:bg-green-900/20 rounded-lg border border-green-200 dark:border-green-800">
|
|
<Icon name="ic:outline-layers" size="16" class="text-green-600 dark:text-green-400" />
|
|
<span class="text-sm text-green-800 dark:text-green-200">
|
|
Using environment: <span class="font-medium">{{ currentEnvironment.name }}</span>
|
|
</span>
|
|
</div>
|
|
|
|
<!-- Enhanced Tabs -->
|
|
<div class="border-b border-gray-200 dark:border-gray-700">
|
|
<nav class="-mb-px flex space-x-8 overflow-x-auto">
|
|
<button
|
|
v-for="tab in tabs"
|
|
:key="tab.id"
|
|
@click="activeTab = tab.id"
|
|
:class="[
|
|
activeTab === tab.id
|
|
? 'border-primary-500 text-primary-600 dark:text-primary-400'
|
|
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300',
|
|
'whitespace-nowrap py-3 px-1 border-b-2 font-medium text-sm flex items-center gap-2 flex-shrink-0'
|
|
]"
|
|
>
|
|
<Icon :name="tab.icon" size="16" />
|
|
{{ tab.label }}
|
|
<rs-badge v-if="tab.count" variant="secondary" size="sm">{{ tab.count }}</rs-badge>
|
|
</button>
|
|
</nav>
|
|
</div>
|
|
|
|
<!-- Enhanced Tab Content -->
|
|
<div class="py-6 overflow-x-auto">
|
|
<!-- Params Tab -->
|
|
<ParamsTab v-if="activeTab === 'params'" :params="requestParams" />
|
|
|
|
<!-- Headers Tab -->
|
|
<HeadersTab v-if="activeTab === 'headers'" :headers="requestHeaders" />
|
|
|
|
<!-- Auth Tab -->
|
|
<AuthTab v-if="activeTab === 'auth'" :auth="requestAuth" />
|
|
|
|
<!-- Body Tab -->
|
|
<BodyTab v-if="activeTab === 'body'" :body="requestBody" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import ParamsTab from './tabs/ParamsTab.vue'
|
|
import HeadersTab from './tabs/HeadersTab.vue'
|
|
import AuthTab from './tabs/AuthTab.vue'
|
|
import BodyTab from './tabs/BodyTab.vue'
|
|
|
|
const { sendRequest: sendRequestUtil } = useApiRequest()
|
|
const { processRequest, findVariables, currentEnvironment } = useVariableSubstitution()
|
|
|
|
const {
|
|
activeTab,
|
|
httpMethod,
|
|
requestUrl,
|
|
isLoading,
|
|
requestParams,
|
|
requestHeaders,
|
|
requestAuth,
|
|
requestBody,
|
|
response,
|
|
requestHistory,
|
|
requestName,
|
|
httpMethods,
|
|
showNotification
|
|
} = useApiPlatform()
|
|
|
|
const showVariablePreview = ref(false)
|
|
|
|
// Enhanced tab configuration
|
|
const tabs = computed(() => [
|
|
{
|
|
id: 'params',
|
|
label: 'Params',
|
|
icon: 'ic:outline-tune',
|
|
count: requestParams.value.filter(p => p.active && p.key).length
|
|
},
|
|
{
|
|
id: 'headers',
|
|
label: 'Headers',
|
|
icon: 'ic:outline-list',
|
|
count: requestHeaders.value.filter(h => h.active && h.key).length
|
|
},
|
|
{
|
|
id: 'auth',
|
|
label: 'Authorization',
|
|
icon: 'ic:outline-security',
|
|
count: requestAuth.value.type !== 'none' ? 1 : 0
|
|
},
|
|
{
|
|
id: 'body',
|
|
label: 'Body',
|
|
icon: 'ic:outline-description',
|
|
count: getBodyContentCount()
|
|
}
|
|
])
|
|
|
|
// Get body content count for badge
|
|
const getBodyContentCount = () => {
|
|
if (requestBody.value.type === 'none') return 0
|
|
if (requestBody.value.type === 'raw' && requestBody.value.raw) return 1
|
|
if (requestBody.value.type === 'form-data') {
|
|
return requestBody.value.formData.filter(item => item.active && item.key).length
|
|
}
|
|
if (requestBody.value.type === 'x-www-form-urlencoded') {
|
|
return requestBody.value.urlEncoded.filter(item => item.active && item.key).length
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Computed properties for variable detection
|
|
const urlVariables = computed(() => {
|
|
return findVariables(requestUrl.value)
|
|
})
|
|
|
|
// Method to format variable name with curly braces
|
|
const formatVariableName = (name) => {
|
|
return `{{${name}}}`
|
|
}
|
|
|
|
const handleSendRequest = async () => {
|
|
if (!requestUrl.value) {
|
|
showNotification('Please enter a URL', 'error')
|
|
return
|
|
}
|
|
|
|
const requestData = {
|
|
url: requestUrl.value,
|
|
method: httpMethod.value,
|
|
headers: requestHeaders.value,
|
|
params: requestParams.value,
|
|
auth: requestAuth.value,
|
|
body: requestBody.value
|
|
}
|
|
|
|
// Process variables before sending
|
|
const processedRequest = processRequest(requestData)
|
|
|
|
await sendRequestUtil(processedRequest, {
|
|
onStart: () => {
|
|
isLoading.value = true
|
|
},
|
|
onSuccess: (responseData) => {
|
|
response.value = responseData
|
|
|
|
// Add timestamp to response
|
|
response.value.timestamp = new Date().toISOString()
|
|
|
|
// Add to history
|
|
requestHistory.value.unshift({
|
|
id: Date.now(),
|
|
timestamp: new Date().toISOString(),
|
|
name: requestName.value || 'Untitled Request',
|
|
method: httpMethod.value,
|
|
url: requestUrl.value,
|
|
status: responseData.status
|
|
})
|
|
|
|
showNotification('Request completed successfully', 'success')
|
|
},
|
|
onError: (message, responseData) => {
|
|
if (responseData) {
|
|
response.value = responseData
|
|
response.value.timestamp = new Date().toISOString()
|
|
}
|
|
showNotification(`Request failed: ${message}`, 'error')
|
|
},
|
|
onComplete: () => {
|
|
isLoading.value = false
|
|
}
|
|
})
|
|
}
|
|
</script> |