replace screenshot pipeline and update admin
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user