replace screenshot pipeline and update admin

This commit is contained in:
2025-12-31 16:50:35 +08:00
parent 2d98ab66a3
commit 41ead4bead
25 changed files with 443 additions and 2250 deletions

View File

@@ -46,6 +46,11 @@ export async function getIpRisk(ip) {
return data
}
export async function clearIpRisk(ip) {
const { data } = await api.post('/admin/security/ip-risk/clear', { ip })
return data
}
export async function getUserRisk(userId) {
const safeUserId = encodeURIComponent(String(userId || '').trim())
const { data } = await api.get(`/admin/security/user-risk/${safeUserId}`)
@@ -56,4 +61,3 @@ export async function cleanup() {
const { data } = await api.post('/admin/security/cleanup', {})
return data
}

View File

@@ -25,6 +25,7 @@ const refreshStats = inject('refreshStats', null)
const adminStats = inject('adminStats', null)
const loading = ref(false)
const refreshing = ref(false)
const lastUpdatedAt = ref('')
const taskStats = ref(null)
@@ -181,9 +182,13 @@ const runningCountsLabel = computed(() => {
return `运行中 ${runningCount} / 排队 ${queuingCount} / 并发上限 ${maxGlobal || maxConcurrentGlobal.value || '-'}`
})
async function refreshAll() {
if (loading.value) return
loading.value = true
async function refreshAll(options = {}) {
const showLoading = options.showLoading ?? true
if (refreshing.value) return
refreshing.value = true
if (showLoading) {
loading.value = true
}
try {
const [
taskResult,
@@ -217,15 +222,22 @@ async function refreshAll() {
await refreshStats?.()
recordUpdatedAt()
} finally {
loading.value = false
refreshing.value = false
if (showLoading) {
loading.value = false
}
}
}
let refreshTimer = null
function manualRefresh() {
return refreshAll({ showLoading: true })
}
onMounted(() => {
refreshAll()
refreshTimer = setInterval(refreshAll, 1000)
refreshAll({ showLoading: false })
refreshTimer = setInterval(() => refreshAll({ showLoading: false }), 1000)
})
onUnmounted(() => {
@@ -252,7 +264,7 @@ onUnmounted(() => {
</div>
<div class="hero-actions">
<el-button type="primary" plain :loading="loading" @click="refreshAll">刷新</el-button>
<el-button type="primary" plain :loading="loading" @click="manualRefresh">刷新</el-button>
</div>
</div>
@@ -593,9 +605,9 @@ onUnmounted(() => {
<div class="panel-head">
<div class="head-left">
<div class="head-text">
<div class="panel-title">浏览器</div>
<div class="panel-title">截图线程</div>
<div class="panel-sub app-muted">
活跃浏览器{{ browserPoolActiveWorkers }} · 忙碌 {{ browserPoolBusyWorkers }} · 队列 {{ browserPoolQueueSize }}
活跃执行环境{{ browserPoolActiveWorkers }} · 忙碌 {{ browserPoolBusyWorkers }} · 队列 {{ browserPoolQueueSize }}
</div>
</div>
</div>
@@ -609,7 +621,7 @@ onUnmounted(() => {
</div>
<div class="tile">
<div class="tile-v ok">{{ browserPoolActiveWorkers }}</div>
<div class="tile-k app-muted">活跃浏览器</div>
<div class="tile-k app-muted">活跃执行环境</div>
</div>
<div class="tile">
<div class="tile-v">{{ browserPoolIdleWorkers }}</div>
@@ -645,7 +657,7 @@ onUnmounted(() => {
</el-table-column>
<el-table-column prop="browser_use_count" label="复用" width="90" />
<el-table-column prop="last_active_at" label="最近活跃" min-width="160" />
<el-table-column prop="browser_created_at" label="浏览器创建" min-width="160" />
<el-table-column prop="browser_created_at" label="环境创建" min-width="160" />
</el-table>
</div>
</el-card>

View File

@@ -6,6 +6,7 @@ import {
banIp,
banUser,
cleanup,
clearIpRisk,
getBannedIps,
getBannedUsers,
getDashboard,
@@ -381,6 +382,35 @@ async function unbanFromRisk() {
}
}
async function clearIpRiskScore() {
if (riskResultKind.value !== 'ip') return
const ipText = String(riskResult.value?.ip || '').trim()
if (!ipText) return
try {
await ElMessageBox.confirm(
`确定清除 IP ${ipText} 的风险分吗?\n\n清除风险分不会删除威胁历史也不会解除封禁。`,
'清除风险分',
{ confirmButtonText: '清除', cancelButtonText: '取消', type: 'warning' },
)
} catch {
return
}
if (riskLoading.value) return
riskLoading.value = true
try {
await clearIpRisk(ipText)
ElMessage.success('IP风险分已清零')
} catch {
// handled by interceptor
} finally {
riskLoading.value = false
}
await queryIpRisk()
}
const cleanupLoading = ref(false)
async function onCleanup() {
@@ -613,6 +643,15 @@ onMounted(async () => {
<div class="toolbar">
<el-button v-if="!riskResult.is_banned" type="primary" plain @click="openBanFromRisk">封禁</el-button>
<el-button v-else type="danger" plain @click="unbanFromRisk">解除封禁</el-button>
<el-button
v-if="riskResultKind === 'ip'"
type="warning"
plain
:loading="riskLoading"
@click="clearIpRiskScore"
>
清除风险分
</el-button>
</div>
</div>

View File

@@ -261,7 +261,7 @@ onMounted(loadAll)
<el-form-item label="截图最大并发数">
<el-input-number v-model="maxScreenshotConcurrent" :min="1" :max="50" />
<div class="help">同时进行截图的最大数量每个浏览器约占用 200MB 内存</div>
<div class="help">同时进行截图的最大数量wkhtmltoimage 资源占用较低可按需提高</div>
</el-form-item>
</el-form>