feat: priority crawl UI to dashboard
This commit is contained in:
+1
-3
@@ -2,7 +2,6 @@
|
||||
import { ref } from 'vue'
|
||||
import Dashboard from './views/Dashboard.vue'
|
||||
import RecentCrawls from './views/RecentCrawls.vue'
|
||||
import PriorityCrawl from './views/PriorityCrawl.vue'
|
||||
import SearchView from './views/SearchView.vue'
|
||||
|
||||
const tab = ref('dashboard')
|
||||
@@ -11,7 +10,6 @@ const nav = [
|
||||
{ id: 'dashboard', label: '概览', icon: '📊' },
|
||||
{ id: 'recent', label: '最近', icon: '🕷️' },
|
||||
{ id: 'search', label: '搜索', icon: '🔍' },
|
||||
{ id: 'priority', label: '插入', icon: '🚀' },
|
||||
]
|
||||
</script>
|
||||
|
||||
@@ -49,7 +47,7 @@ const nav = [
|
||||
<Dashboard v-if="tab === 'dashboard'" />
|
||||
<RecentCrawls v-else-if="tab === 'recent'" />
|
||||
<SearchView v-else-if="tab === 'search'" />
|
||||
<PriorityCrawl v-else-if="tab === 'priority'" />
|
||||
|
||||
</main>
|
||||
|
||||
<!-- Mobile Bottom Navigation -->
|
||||
|
||||
+58
-2
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { fetchStats, flushIndex, fetchFlushStatus, fetchWorkers, setWorkers, fetchBacklink, triggerBacklink, fetchPriorityStatus, fetchCrawlStatus } from '../api.js'
|
||||
import { fetchStats, flushIndex, fetchFlushStatus, fetchWorkers, setWorkers, fetchBacklink, triggerBacklink, fetchPriorityStatus, fetchCrawlStatus, addPriority } from '../api.js'
|
||||
|
||||
const stats = ref(null)
|
||||
const loading = ref(true)
|
||||
@@ -15,7 +15,9 @@ const workersInput = ref(0)
|
||||
const workersSaving = ref(false)
|
||||
|
||||
// Priority 相关状态
|
||||
const priorityStatus = ref(null) // { pending, active, max_workers, children_queue }
|
||||
const priorityStatus = ref(null) // { level1, level2_queue, level2_inflight, active, max_workers }
|
||||
const batchUrls = ref('')
|
||||
const batchAdding = ref(false)
|
||||
|
||||
// 爬取状态
|
||||
const crawlStatus = ref(null) // { current_epoch, max_epoch, queue_length, completed_count, visited_total, is_running }
|
||||
@@ -172,6 +174,44 @@ function langColor(lang) {
|
||||
const map = { zh: '#e53e3e', en: '#3182ce', ja: '#e53e3e', ko: '#3182ce', fr: '#38a169', de: '#d69e2e', es: '#38a169', ru: '#805ad5', other: '#718096' }
|
||||
return map[lang] || map.other
|
||||
}
|
||||
|
||||
function isValidUrl(s) {
|
||||
try {
|
||||
const u = new URL(s)
|
||||
return u.protocol === 'http:' || u.protocol === 'https:'
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function doBatchAdd() {
|
||||
const lines = batchUrls.value.split('\n').map(l => l.trim()).filter(l => l)
|
||||
const valid = lines.filter(isValidUrl)
|
||||
if (valid.length === 0) {
|
||||
error.value = '未检测到有效 URL'
|
||||
return
|
||||
}
|
||||
batchAdding.value = true
|
||||
let ok = 0, fail = 0
|
||||
try {
|
||||
for (const url of valid) {
|
||||
try {
|
||||
await addPriority(url)
|
||||
ok++
|
||||
} catch {
|
||||
fail++
|
||||
}
|
||||
}
|
||||
batchUrls.value = ''
|
||||
await loadPriorityStatus()
|
||||
error.value = null
|
||||
if (fail > 0) {
|
||||
error.value = `添加完成:${ok} 成功,${fail} 失败`
|
||||
}
|
||||
} finally {
|
||||
batchAdding.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -255,6 +295,22 @@ function langColor(lang) {
|
||||
<div class="text-2xl font-bold text-blue-400">{{ priorityStatus.max_workers }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 批量添加 -->
|
||||
<div class="mt-4 flex gap-2">
|
||||
<textarea
|
||||
v-model="batchUrls"
|
||||
rows="3"
|
||||
placeholder="批量添加 URL(每行一个,仅发送有效链接)"
|
||||
class="flex-1 bg-gray-800 border border-gray-700 text-gray-200 text-sm rounded-lg px-3 py-2 resize-none focus:outline-none focus:border-blue-500 placeholder-gray-600"
|
||||
></textarea>
|
||||
<button
|
||||
class="bg-blue-700 hover:bg-blue-600 disabled:bg-gray-700 text-white text-sm font-medium px-4 py-2 rounded-lg transition-colors cursor-pointer whitespace-nowrap self-end"
|
||||
:disabled="batchAdding || !batchUrls.trim()"
|
||||
@click="doBatchAdd"
|
||||
>
|
||||
{{ batchAdding ? '添加中...' : '批量添加' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 爬取进度 -->
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { fetchPriority, addPriority, removePriority } from '../api.js'
|
||||
|
||||
const items = ref([])
|
||||
const loading = ref(true)
|
||||
const error = ref(null)
|
||||
const submitting = ref(false)
|
||||
const submitError = ref(null)
|
||||
const submitSuccess = ref(false)
|
||||
const inputUrl = ref('')
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const data = await fetchPriority()
|
||||
items.value = data.items || []
|
||||
} catch (e) {
|
||||
error.value = '加载失败,请检查人服务器是否启动'
|
||||
console.error(e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
const raw = inputUrl.value.trim()
|
||||
if (!raw) return
|
||||
submitting.value = true
|
||||
submitError.value = null
|
||||
submitSuccess.value = false
|
||||
try {
|
||||
await addPriority(raw)
|
||||
inputUrl.value = ''
|
||||
submitSuccess.value = true
|
||||
setTimeout(() => { submitSuccess.value = false }, 3000)
|
||||
await load()
|
||||
} catch (e) {
|
||||
submitError.value = e?.response?.data?.error || '添加失败'
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function del(url) {
|
||||
try {
|
||||
await removePriority(url)
|
||||
await load()
|
||||
} catch (e) {
|
||||
error.value = '删除失败'
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
function fmtTime(ts) {
|
||||
if (!ts) return '-'
|
||||
return new Date(ts * 1000).toLocaleString('zh-CN', {
|
||||
month: '2-digit', day: '2-digit',
|
||||
hour: '2-digit', minute: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-4 md:p-8">
|
||||
<h1 class="text-xl md:text-2xl font-semibold text-white mb-2">插入爬取</h1>
|
||||
<p class="text-sm text-gray-500 mb-6 md:mb-8">
|
||||
添加 URL 或域名,下一轮爬取时会优先抓取。纯域名会自动补全为 https://www.域名/。
|
||||
</p>
|
||||
|
||||
<!-- Input -->
|
||||
<div class="bg-gray-900 rounded-xl p-4 md:p-6 mb-4 md:mb-6 border border-gray-800">
|
||||
<div class="flex flex-col sm:flex-row gap-3">
|
||||
<input
|
||||
v-model="inputUrl"
|
||||
@keyup.enter="submit"
|
||||
type="text"
|
||||
placeholder="输入 URL 或域名,例如 https://example.com 或 example.com"
|
||||
class="flex-1 bg-gray-800 border border-gray-700 rounded-lg px-4 py-2.5 text-gray-100 placeholder-gray-500 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 text-sm"
|
||||
:disabled="submitting"
|
||||
/>
|
||||
<button
|
||||
@click="submit"
|
||||
:disabled="submitting || !inputUrl.trim()"
|
||||
class="px-6 py-2.5 bg-blue-600 hover:bg-blue-500 disabled:bg-gray-700 disabled:text-gray-500 text-white text-sm font-medium rounded-lg transition-colors cursor-pointer whitespace-nowrap"
|
||||
>
|
||||
{{ submitting ? '添加中...' : '插入队列' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Error -->
|
||||
<div v-if="submitError" class="mt-3 text-sm text-red-400">
|
||||
{{ submitError }}
|
||||
</div>
|
||||
|
||||
<!-- Success -->
|
||||
<div v-if="submitSuccess" class="mt-3 text-sm text-green-400">
|
||||
已添加到优先队列,将在下一轮爬取时优先抓取
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- List -->
|
||||
<div class="bg-gray-900 rounded-xl border border-gray-800">
|
||||
<div class="px-4 md:px-6 py-3 md:py-4 border-b border-gray-800 flex items-center justify-between">
|
||||
<span class="text-sm font-medium text-gray-300">待爬取队列</span>
|
||||
<span class="text-xs text-gray-500">{{ items.length }} 条</span>
|
||||
</div>
|
||||
|
||||
<!-- Loading -->
|
||||
<div v-if="loading" class="p-8 text-center text-gray-500 text-sm">
|
||||
加载中...
|
||||
</div>
|
||||
|
||||
<!-- Error -->
|
||||
<div v-else-if="error" class="p-8 text-center text-red-400 text-sm">
|
||||
{{ error }}
|
||||
</div>
|
||||
|
||||
<!-- Empty -->
|
||||
<div v-else-if="items.length === 0" class="p-8 text-center text-gray-500 text-sm">
|
||||
暂无待爬取的优先 URL
|
||||
</div>
|
||||
|
||||
<!-- Desktop Table -->
|
||||
<table v-else class="hidden md:table w-full text-sm">
|
||||
<thead>
|
||||
<tr class="text-left text-gray-500 text-xs border-b border-gray-800">
|
||||
<th class="px-6 py-3 font-medium">URL</th>
|
||||
<th class="px-6 py-3 font-medium w-28">类型</th>
|
||||
<th class="px-6 py-3 font-medium w-40">添加时间</th>
|
||||
<th class="px-6 py-3 font-medium w-16">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-800">
|
||||
<tr v-for="item in items" :key="item.url" class="hover:bg-gray-800/50">
|
||||
<td class="px-6 py-3">
|
||||
<span class="text-gray-300 break-all">{{ item.url }}</span>
|
||||
</td>
|
||||
<td class="px-6 py-3">
|
||||
<span v-if="item.domain" class="inline-block px-2 py-0.5 text-xs rounded bg-purple-900 text-purple-300">域名</span>
|
||||
<span v-else class="inline-block px-2 py-0.5 text-xs rounded bg-blue-900 text-blue-300">URL</span>
|
||||
</td>
|
||||
<td class="px-6 py-3 text-gray-500">{{ fmtTime(item.added_at) }}</td>
|
||||
<td class="px-6 py-3">
|
||||
<button
|
||||
@click="del(item.url)"
|
||||
class="text-red-400 hover:text-red-300 text-xs cursor-pointer"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Mobile Cards -->
|
||||
<div class="md:hidden divide-y divide-gray-800">
|
||||
<div
|
||||
v-for="item in items"
|
||||
:key="item.url"
|
||||
class="p-4 hover:bg-gray-800/50 flex items-center justify-between gap-3"
|
||||
>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-gray-300 text-sm break-all mb-1">{{ item.url }}</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span v-if="item.domain" class="inline-block px-2 py-0.5 text-xs rounded bg-purple-900 text-purple-300">域名</span>
|
||||
<span v-else class="inline-block px-2 py-0.5 text-xs rounded bg-blue-900 text-blue-300">URL</span>
|
||||
<span class="text-xs text-gray-500">{{ fmtTime(item.added_at) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@click="del(item.url)"
|
||||
class="text-red-400 hover:text-red-300 text-xs cursor-pointer shrink-0 px-2 py-1"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user