Digital-Bodyguard / src /components /UserLogView.vue
BrianChuan
後端連線測試
8f14012
Raw
History Blame Contribute Delete
8.11 kB
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { Shield, Clock, Globe, Laptop, Activity, RefreshCcw, AlertCircle } from 'lucide-vue-next'
const props = defineProps<{
sessionId: string
}>()
interface UserLog {
time: string
action: string
ip: string
location: string
device: string
ai_status: string
status_detail?: string
}
const logs = ref<UserLog[]>([])
const loading = ref(false)
const error = ref('')
const fetchLogs = async () => {
if (!props.sessionId) {
error.value = '未偵測到 Session ID,無法讀取日誌。'
return
}
loading.value = true
error.value = ''
try {
// 使用目前 host 的 7860 端口(開發環境慣例)或指向後端 Space 網域
// 🌐 【HF 部署模式】指向 Hugging Face Space 後端
// 💻 【本地開發模式】指向本地後端
const baseUrl = window.location.port === '5173' ? 'http://localhost:7860' : 'https://my-project-team-infosecai-finalproj.hf.space'
const response = await fetch(`${baseUrl}/api/get_user_logs?session_id=${props.sessionId}`)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json()
if (data.status === 'success') {
logs.value = data.data
} else {
error.value = data.message || '後端回傳資料格式錯誤'
}
} catch (err) {
console.error('Fetch logs error:', err)
error.value = '無法連接至 AI 後端安全引擎,請確認伺服器是否正常運作。'
} finally {
loading.value = false
}
}
onMounted(() => {
fetchLogs()
})
</script>
<template>
<div class="user-log-view-root space-y-6">
<!-- Header Card -->
<div class="bg-slate-900 rounded-3xl border border-slate-800 p-8 shadow-2xl relative overflow-hidden group">
<div class="absolute top-0 right-0 p-12 opacity-5 pointer-events-none group-hover:scale-110 transition-transform duration-700">
<Shield class="w-48 h-48 text-blue-500" />
</div>
<div class="flex flex-col md:flex-row md:items-center justify-between gap-6 relative z-10">
<div class="flex items-center gap-5">
<div class="bg-gradient-to-br from-blue-600 to-indigo-700 p-4 rounded-2xl shadow-xl shadow-blue-900/40">
<Shield class="w-8 h-8 text-white" />
</div>
<div>
<h2 class="text-2xl font-black text-white tracking-tight">數位保鑣|個人安全日誌</h2>
<div class="flex items-center gap-2 mt-1">
<span class="text-[10px] bg-blue-500/20 text-blue-400 px-2 py-0.5 rounded font-bold tracking-widest uppercase border border-blue-500/30">Live Monitor</span>
<p class="text-xs text-slate-500 font-medium italic">目前監控 Session: {{ props.sessionId }}</p>
</div>
</div>
</div>
<button
@click="fetchLogs"
:disabled="loading"
class="flex items-center justify-center gap-3 px-6 py-3 bg-slate-800 hover:bg-slate-700 text-slate-200 rounded-2xl text-sm font-bold transition-all border border-slate-700 active:scale-95 disabled:opacity-50 hover:shadow-lg hover:shadow-blue-900/10"
>
<RefreshCcw class="w-4 h-4" :class="{ 'animate-spin': loading }" />
<span>同步最新紀錄</span>
</button>
</div>
</div>
<!-- Error State -->
<div v-if="error" class="bg-red-500/10 border border-red-500/30 text-red-400 p-6 rounded-3xl flex items-start gap-4 animate-in fade-in slide-in-from-top-4 duration-300">
<AlertCircle class="w-6 h-6 shrink-0 mt-1" />
<div>
<h4 class="font-bold text-lg">系統連線異常</h4>
<p class="text-sm opacity-80 leading-relaxed">{{ error }}</p>
</div>
</div>
<!-- Data Table Card -->
<div class="bg-white rounded-3xl border border-slate-200 shadow-sm overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="bg-slate-50/80 border-b border-slate-100">
<th class="p-5 text-xs font-bold text-slate-500 uppercase tracking-widest">
<div class="flex items-center gap-2"><Clock class="w-4 h-4 text-slate-400" /> 紀錄時間</div>
</th>
<th class="p-5 text-xs font-bold text-slate-500 uppercase tracking-widest">
<div class="flex items-center gap-2"><Activity class="w-4 h-4 text-slate-400" /> 行為名稱</div>
</th>
<th class="p-5 text-xs font-bold text-slate-500 uppercase tracking-widest">
<div class="flex items-center gap-2"><Globe class="w-4 h-4 text-slate-400" /> 來源 IP</div>
</th>
<th class="p-5 text-xs font-bold text-slate-500 uppercase tracking-widest">
地理位置
</th>
<th class="p-5 text-xs font-bold text-slate-500 uppercase tracking-widest">
<div class="flex items-center gap-2"><Laptop class="w-4 h-4 text-slate-400" /> 存取設備</div>
</th>
<th class="p-5 text-xs font-bold text-slate-500 uppercase tracking-widest text-center">
AI 防護判定
</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-50">
<tr v-if="loading" class="animate-pulse">
<td colspan="6" class="p-20 text-center text-slate-400 font-medium">
正在從雲端 Dataset 同步資料...
</td>
</tr>
<tr v-else-if="logs.length === 0" class="fade-in">
<td colspan="6" class="p-20 text-center">
<div class="flex flex-col items-center gap-3">
<div class="w-16 h-16 bg-slate-100 rounded-full flex items-center justify-center">
<Activity class="w-8 h-8 text-slate-300" />
</div>
<p class="text-slate-400 font-medium">目前尚無相關行為日誌紀錄</p>
</div>
</td>
</tr>
<tr v-for="(log, idx) in logs" :key="idx" class="hover:bg-blue-50/30 transition-all group duration-300">
<td class="p-5 text-sm font-mono text-slate-500">{{ log.time }}</td>
<td class="p-5">
<span class="text-sm font-bold text-slate-800 group-hover:text-blue-600 transition-colors">{{ log.action }}</span>
</td>
<td class="p-5">
<span class="text-sm font-mono text-blue-600 bg-blue-50 px-2 py-1 rounded-lg border border-blue-100">{{ log.ip }}</span>
</td>
<td class="p-5">
<div class="flex items-center gap-2">
<span class="text-sm text-slate-600">{{ log.location }}</span>
</div>
</td>
<td class="p-5 text-sm text-slate-500 italic">{{ log.device }}</td>
<td class="p-5">
<div class="flex justify-center">
<span
:class="log.ai_status === '正常'
? 'bg-emerald-50 text-emerald-600 border-emerald-200'
: 'bg-red-50 text-red-600 border-red-200 animate-pulse font-black'"
class="px-4 py-1.5 rounded-full text-[11px] font-bold border shadow-sm flex items-center gap-1.5"
>
<div class="w-1.5 h-1.5 rounded-full" :class="log.ai_status === '正常' ? 'bg-emerald-500' : 'bg-red-500'"></div>
{{ log.ai_status }}
</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<style scoped>
.fade-in {
animation: fadeIn 0.5s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* 讓表格在窄螢幕也能看 */
.overflow-x-auto {
-webkit-overflow-scrolling: touch;
}
</style>