Files
toolbox/resources/js/components/message-sync/MessageSync.vue
2025-12-02 10:16:32 +08:00

359 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="p-6">
<!-- 页面标题 -->
<div class="mb-6">
<h1 class="text-2xl font-bold text-gray-900">消息同步</h1>
<p class="text-gray-600 mt-2">批量输入消息ID从crmslave数据库查询并同步到agent服务</p>
</div>
<!-- 输入区域 -->
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 mb-6">
<h2 class="text-xl font-semibold text-gray-700 mb-4">消息ID输入</h2>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
消息ID列表 (每行一个ID)
</label>
<textarea
v-model="messageIdsText"
rows="8"
class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="请输入消息ID每行一个&#10;例如:&#10;af7e5ca7-2779-0e9e-93d1-68c79ceffd9033&#10;bf8f6db8-3880-1f0f-a4e2-79d8adf00144"
></textarea>
<div class="text-sm text-gray-500 mt-1">
{{ messageIdsList.length }} 个消息ID
</div>
</div>
<div class="flex space-x-4">
<button
@click="queryMessages"
:disabled="loading.query || messageIdsList.length === 0"
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center"
>
<svg v-if="loading.query" class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
查询消息
</button>
<button
@click="syncMessages"
:disabled="loading.sync || !queryResults || messageIdsList.length === 0"
class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center"
>
<svg v-if="loading.sync" class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
执行同步
</button>
<button
@click="testConnection"
:disabled="loading.test"
class="px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center"
>
<svg v-if="loading.test" class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
测试连接
</button>
</div>
</div>
</div>
<!-- 错误信息 -->
<div v-if="error" class="bg-red-50 border border-red-200 rounded-lg p-4 mb-6">
<div class="flex">
<svg class="w-5 h-5 text-red-400 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<div>
<h3 class="text-sm font-medium text-red-800">错误</h3>
<p class="text-sm text-red-700 mt-1">{{ error }}</p>
</div>
</div>
</div>
<!-- 查询结果 -->
<div v-if="queryResults" class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 mb-6">
<h2 class="text-xl font-semibold text-gray-700 mb-4">查询结果</h2>
<!-- 统计信息 -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
<div class="bg-blue-50 rounded-lg p-4">
<div class="text-2xl font-bold text-blue-600">{{ queryResults.stats.total_requested }}</div>
<div class="text-sm text-blue-600">请求总数</div>
</div>
<div class="bg-green-50 rounded-lg p-4">
<div class="text-2xl font-bold text-green-600">{{ queryResults.stats.total_found }}</div>
<div class="text-sm text-green-600">找到记录</div>
</div>
<div class="bg-red-50 rounded-lg p-4">
<div class="text-2xl font-bold text-red-600">{{ queryResults.stats.total_missing }}</div>
<div class="text-sm text-red-600">缺失记录</div>
</div>
<div class="bg-purple-50 rounded-lg p-4">
<div class="text-2xl font-bold text-purple-600">{{ Object.keys(queryResults.stats.event_types).length }}</div>
<div class="text-sm text-purple-600">事件类型</div>
</div>
</div>
<!-- 消息列表 -->
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">消息ID</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">事件类型</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">跟踪ID</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">时间戳</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tr v-for="message in queryResults.messages" :key="message.msg_id">
<td class="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-900">{{ message.msg_id }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ message.event_type }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-500">{{ message.trace_id }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ formatTimestamp(message.timestamp) }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<button
@click="showMessageDetail(message)"
class="text-blue-600 hover:text-blue-900"
>
查看详情
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- 同步结果 -->
<div v-if="syncResults" class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<h2 class="text-xl font-semibold text-gray-700 mb-4">同步结果</h2>
<!-- 同步统计 -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<div class="bg-blue-50 rounded-lg p-4">
<div class="text-2xl font-bold text-blue-600">{{ syncResults.summary.total }}</div>
<div class="text-sm text-blue-600">总计</div>
</div>
<div class="bg-green-50 rounded-lg p-4">
<div class="text-2xl font-bold text-green-600">{{ syncResults.summary.success }}</div>
<div class="text-sm text-green-600">成功</div>
</div>
<div class="bg-red-50 rounded-lg p-4">
<div class="text-2xl font-bold text-red-600">{{ syncResults.summary.failure }}</div>
<div class="text-sm text-red-600">失败</div>
</div>
</div>
<!-- 同步结果列表 -->
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">消息ID</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">响应</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tr v-for="result in syncResults.results" :key="result.msg_id">
<td class="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-900">{{ result.msg_id }}</td>
<td class="px-6 py-4 whitespace-nowrap">
<span v-if="result.success" class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
成功
</span>
<span v-else class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
失败
</span>
</td>
<td class="px-6 py-4 text-sm text-gray-500 max-w-xs truncate">
{{ result.success ? '同步成功' : result.error }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<button
@click="showSyncDetail(result)"
class="text-blue-600 hover:text-blue-900"
>
查看详情
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- 详情模态框 -->
<div v-if="showDetailModal" class="fixed inset-0 overflow-y-auto h-full w-full z-50">
<div class="relative top-20 mx-auto p-5 border w-11/12 md:w-3/4 lg:w-1/2 shadow-lg rounded-md bg-white">
<div class="mt-3">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium text-gray-900">详细信息</h3>
<button @click="closeDetailModal" class="text-gray-400 hover:text-gray-600">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<div class="max-h-96 overflow-y-auto">
<pre class="bg-gray-100 p-4 rounded-lg text-sm overflow-x-auto">{{ JSON.stringify(selectedDetail, null, 2) }}</pre>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'MessageSync',
data() {
return {
messageIdsText: '',
queryResults: null,
syncResults: null,
loading: {
query: false,
sync: false,
test: false
},
error: '',
showDetailModal: false,
selectedDetail: null
}
},
computed: {
messageIdsList() {
return this.messageIdsText
.split('\n')
.map(id => id.trim())
.filter(id => id.length > 0);
}
},
methods: {
async queryMessages() {
if (this.messageIdsList.length === 0) {
this.error = '请输入至少一个消息ID';
return;
}
this.loading.query = true;
this.error = '';
this.queryResults = null;
try {
const response = await fetch('/api/message-sync/query', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
},
body: JSON.stringify({
message_ids: this.messageIdsList
})
});
const data = await response.json();
if (data.success) {
this.queryResults = data.data;
} else {
this.error = data.message;
}
} catch (error) {
this.error = '网络请求失败: ' + error.message;
} finally {
this.loading.query = false;
}
},
async syncMessages() {
if (this.messageIdsList.length === 0) {
this.error = '请输入至少一个消息ID';
return;
}
this.loading.sync = true;
this.error = '';
this.syncResults = null;
try {
const response = await fetch('/api/message-sync/sync', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
},
body: JSON.stringify({
message_ids: this.messageIdsList
})
});
const data = await response.json();
if (data.success) {
this.syncResults = data.data;
} else {
this.error = data.message;
}
} catch (error) {
this.error = '网络请求失败: ' + error.message;
} finally {
this.loading.sync = false;
}
},
async testConnection() {
this.loading.test = true;
this.error = '';
try {
const response = await fetch('/api/message-sync/test-connection');
const data = await response.json();
if (data.success) {
alert('数据库连接测试成功!');
} else {
this.error = data.message;
}
} catch (error) {
this.error = '网络请求失败: ' + error.message;
} finally {
this.loading.test = false;
}
},
showMessageDetail(message) {
this.selectedDetail = message;
this.showDetailModal = true;
},
showSyncDetail(result) {
this.selectedDetail = result;
this.showDetailModal = true;
},
closeDetailModal() {
this.showDetailModal = false;
this.selectedDetail = null;
},
formatTimestamp(timestamp) {
if (!timestamp) return '-';
return new Date(timestamp * 1000).toLocaleString();
}
}
}
</script>