feat: priority crawl UI to dashboard
This commit is contained in:
+1
-3
@@ -2,7 +2,6 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import Dashboard from './views/Dashboard.vue'
|
import Dashboard from './views/Dashboard.vue'
|
||||||
import RecentCrawls from './views/RecentCrawls.vue'
|
import RecentCrawls from './views/RecentCrawls.vue'
|
||||||
import PriorityCrawl from './views/PriorityCrawl.vue'
|
|
||||||
import SearchView from './views/SearchView.vue'
|
import SearchView from './views/SearchView.vue'
|
||||||
|
|
||||||
const tab = ref('dashboard')
|
const tab = ref('dashboard')
|
||||||
@@ -11,7 +10,6 @@ const nav = [
|
|||||||
{ id: 'dashboard', label: '概览', icon: '📊' },
|
{ id: 'dashboard', label: '概览', icon: '📊' },
|
||||||
{ id: 'recent', label: '最近', icon: '🕷️' },
|
{ id: 'recent', label: '最近', icon: '🕷️' },
|
||||||
{ id: 'search', label: '搜索', icon: '🔍' },
|
{ id: 'search', label: '搜索', icon: '🔍' },
|
||||||
{ id: 'priority', label: '插入', icon: '🚀' },
|
|
||||||
]
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -49,7 +47,7 @@ const nav = [
|
|||||||
<Dashboard v-if="tab === 'dashboard'" />
|
<Dashboard v-if="tab === 'dashboard'" />
|
||||||
<RecentCrawls v-else-if="tab === 'recent'" />
|
<RecentCrawls v-else-if="tab === 'recent'" />
|
||||||
<SearchView v-else-if="tab === 'search'" />
|
<SearchView v-else-if="tab === 'search'" />
|
||||||
<PriorityCrawl v-else-if="tab === 'priority'" />
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Mobile Bottom Navigation -->
|
<!-- Mobile Bottom Navigation -->
|
||||||
|
|||||||
+58
-2
@@ -1,6 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
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 stats = ref(null)
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
@@ -15,7 +15,9 @@ const workersInput = ref(0)
|
|||||||
const workersSaving = ref(false)
|
const workersSaving = ref(false)
|
||||||
|
|
||||||
// Priority 相关状态
|
// 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 }
|
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' }
|
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
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -255,6 +295,22 @@ function langColor(lang) {
|
|||||||
<div class="text-2xl font-bold text-blue-400">{{ priorityStatus.max_workers }}</div>
|
<div class="text-2xl font-bold text-blue-400">{{ priorityStatus.max_workers }}</div>
|
||||||
</div>
|
</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>
|
</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