<template><div class="page-content"><div class="mb-5 flex items-start justify-between"><div><h2 class="text-xl font-semibold">工具中心</h2><p class="text-g-500 mt-1 text-sm">只注册受控 HTTP 工具，不允许任意命令或 SQL；运行时执行 DNS/SSRF 复核和响应大小限制</p></div><ElButton type="primary" @click="openCreate">注册工具</ElButton></div><ElTable v-loading="loading" :data="items" row-key="id"><ElTableColumn prop="code" label="编码" min-width="150"/><ElTableColumn prop="name" label="名称" min-width="130"/><ElTableColumn label="端点" min-width="240"><template #default="{row}"><ElTag>{{row.http_method}}</ElTag><span class="ml-2">{{row.endpoint_url}}</span></template></ElTableColumn><ElTableColumn label="凭据头" width="100"><template #default="{row}">{{row.has_secret_headers?'已加密':'无'}}</template></ElTableColumn><ElTableColumn label="范围" width="110"><template #default="{row}">{{row.department_ids.length?`${row.department_ids.length} 部门`:'未公开'}}</template></ElTableColumn><ElTableColumn label="状态" width="90"><template #default="{row}"><ElTag :type="row.enabled?'success':'info'">{{row.enabled?'启用':'停用'}}</ElTag></template></ElTableColumn><ElTableColumn label="操作" width="190" fixed="right"><template #default="{row}"><ElButton link type="primary" @click="openTest(row)">测试</ElButton><ElButton link type="primary" @click="openEdit(row)">编辑</ElButton><ElButton link type="danger" @click="remove(row)">删除</ElButton></template></ElTableColumn></ElTable>
<ElDialog v-model="visible" :title="editing?'编辑工具':'注册工具'" width="760px"><ElForm label-width="110px"><div class="grid grid-cols-2 gap-x-4"><ElFormItem label="编码"><ElInput v-model="form.code" placeholder="weather_lookup"/></ElFormItem><ElFormItem label="名称"><ElInput v-model="form.name"/></ElFormItem></div><ElFormItem label="描述"><ElInput v-model="form.description" type="textarea"/></ElFormItem><div class="grid grid-cols-[120px_1fr] gap-x-4"><ElFormItem label="方法"><ElSelect v-model="form.http_method"><ElOption v-for="m in methods" :key="m" :label="m" :value="m"/></ElSelect></ElFormItem><ElFormItem label="端点"><ElInput v-model="form.endpoint_url"/></ElFormItem></div><ElFormItem label="请求头 JSON"><ElInput v-model="headersJSON" type="textarea" :rows="3" :placeholder="editing&&currentHasHeaders?'留空以保留现有加密请求头':'{&quot;Authorization&quot;:&quot;Bearer ...&quot;}'"/></ElFormItem><ElFormItem label="输入 Schema"><ElInput v-model="schemaJSON" type="textarea" :rows="6"/></ElFormItem><ElFormItem label="部门范围"><ElSelect v-model="form.department_ids" multiple allow-create filterable class="w-full" placeholder="为安全起见，留空时不会向普通 API Key 公开"/></ElFormItem><div class="grid grid-cols-2"><ElFormItem label="超时秒数"><ElInputNumber v-model="form.timeout_seconds" :min="1" :max="120"/></ElFormItem><ElFormItem label="启用"><ElSwitch v-model="form.enabled"/></ElFormItem></div></ElForm><template #footer><ElButton @click="visible=false">取消</ElButton><ElButton type="primary" @click="save">保存</ElButton></template></ElDialog>
<ElDialog v-model="testVisible" title="测试工具" width="650px"><ElInput v-model="testInput" type="textarea" :rows="8"/><div class="my-3"><ElButton type="primary" @click="runTest">执行</ElButton></div><pre class="max-h-80 overflow-auto rounded bg-black p-3 text-xs text-white">{{testResult}}</pre></ElDialog></div></template>
<script setup lang="ts">import{ToolDefinition,ToolInput,createTool,deleteTool,fetchTools,testTool,updateTool}from'@/api/workbench';import{ElMessage,ElMessageBox}from'element-plus';const methods=['GET','POST','PUT','PATCH','DELETE'];const loading=ref(false),visible=ref(false),testVisible=ref(false),editing=ref(''),currentHasHeaders=ref(false);const items=ref<ToolDefinition[]>([]);const blank=():ToolInput=>({code:'',name:'',description:'',endpoint_url:'',http_method:'POST',headers:{},input_schema:{type:'object',properties:{}},timeout_seconds:15,department_ids:[],enabled:true});const form=reactive<ToolInput>(blank()),headersJSON=ref('{}'),schemaJSON=ref(JSON.stringify(blank().input_schema,null,2)),testInput=ref('{}'),testResult=ref(''),testing=ref<ToolDefinition>();async function load(){loading.value=true;try{items.value=await fetchTools()}finally{loading.value=false}}function openCreate(){editing.value='';currentHasHeaders.value=false;Object.assign(form,blank());headersJSON.value='{}';schemaJSON.value=JSON.stringify(form.input_schema,null,2);visible.value=true}function openEdit(row:ToolDefinition){editing.value=row.id;currentHasHeaders.value=row.has_secret_headers;Object.assign(form,{code:row.code,name:row.name,description:row.description,endpoint_url:row.endpoint_url,http_method:row.http_method,timeout_seconds:row.timeout_seconds,department_ids:[...row.department_ids],enabled:row.enabled});headersJSON.value='';schemaJSON.value=JSON.stringify(row.input_schema,null,2);visible.value=true}async function save(){try{form.input_schema=JSON.parse(schemaJSON.value);if(headersJSON.value.trim())form.headers=JSON.parse(headersJSON.value);else delete form.headers}catch{ElMessage.error('JSON 格式无效');return}editing.value?await updateTool(editing.value,form):await createTool(form);visible.value=false;await load()}async function remove(row:ToolDefinition){await ElMessageBox.confirm(`删除工具“${row.name}”？`,'确认',{type:'warning'});await deleteTool(row.id);await load()}function openTest(row:ToolDefinition){testing.value=row;testInput.value='{}';testResult.value='';testVisible.value=true}async function runTest(){if(!testing.value)return;try{const result=await testTool(testing.value.id,JSON.parse(testInput.value));testResult.value=JSON.stringify(result,null,2)}catch(error){testResult.value=String(error)}}onMounted(load)</script>
