feat: add recent query logging and display in UI
CI / Windows x64 (push) Successful in 9m12s

This commit is contained in:
2026-08-09 16:25:09 +07:00
parent 0223dd817b
commit 2b8e0cb701
6 changed files with 305 additions and 5 deletions
+59 -2
View File
@@ -62,7 +62,7 @@ function useLog() {
function useSites() {
const dns = ref({
listening: false, upstream: '', queries: 0, blocked: 0,
cache_hits: 0, timeouts: 0, error: null, attempts: [],
cache_hits: 0, timeouts: 0, error: null, attempts: [], recent: [],
});
const load = async () => { dns.value = await api('/dns'); };
// сам список закрытых сайтов живёт в правилах, здесь — только состояние
@@ -259,6 +259,62 @@ const SitesPanel = {
</section>`,
};
const QueryLog = {
props: {
entries: { type: Array, default: () => [] },
active: { type: Boolean, default: false },
},
setup(props) {
const filter = ref('');
const visible = computed(() => {
const q = filter.value.trim().toLowerCase();
return q ? props.entries.filter((e) => e.name.includes(q)) : props.entries;
});
const verdicts = { blocked: 'закрыт', cached: 'из кэша', upstream: 'наверх' };
const ago = (s) => (s < 60 ? `${s} с` : `${Math.floor(s / 60)} мин`);
return { filter, visible, verdicts, ago };
},
template: `
<section class="card">
<div class="row" style="justify-content:space-between">
<h2 style="margin:0">Последние запросы</h2>
<span class="muted">{{ visible.length }} из {{ entries.length }}</span>
</div>
<p v-if="!active" class="muted" style="margin:8px 0 0">
Блокировка выключена — резолвер запросов не видит.
</p>
<template v-else>
<div class="row" style="margin:10px 0">
<input v-model="filter" placeholder="фильтр по имени" style="flex:1">
</div>
<p class="muted" style="margin:0 0 8px">
Хранится в памяти, на диск не пишется и пропадает вместе с выключением.
</p>
<div class="scroll" style="max-height:260px">
<table>
<thead>
<tr><th>Когда</th><th>Имя</th><th>Тип</th><th>Что было</th></tr>
</thead>
<tbody>
<tr v-for="(e, i) in visible" :key="i"
:class="{ blocked: e.verdict === 'blocked' }">
<td class="muted">{{ ago(e.ago_secs) }}</td>
<td>{{ e.name }}</td>
<td class="muted">{{ e.kind }}</td>
<td>{{ verdicts[e.verdict] || e.verdict }}</td>
</tr>
<tr v-if="!visible.length">
<td colspan="4" class="muted">пусто</td>
</tr>
</tbody>
</table>
</div>
</template>
</section>`,
};
const RandomKill = {
emits: ['schedule'],
setup(_, { emit }) {
@@ -354,7 +410,7 @@ const EventLog = {
/* ── корневой компонент ────────────────────────────────────── */
const App = {
components: { UpdateBar, RulesPanel, SitesPanel, RandomKill, ProcessTable, EventLog },
components: { UpdateBar, RulesPanel, SitesPanel, QueryLog, RandomKill, ProcessTable, EventLog },
setup() {
const { rules, load: loadRules, block, allow, remove, save } = useRules();
const { groups, load: loadProcesses, kill } = useProcesses();
@@ -411,6 +467,7 @@ const App = {
@block="blockSite" @remove="removeSite"
@enable="save({ sites_enforce: $event })"
@lockdown="save({ dns_lockdown: $event })" />
<query-log :entries="dns.recent" :active="rules.enforce && rules.sites_enforce" />
<random-kill @schedule="scheduleRandom" />
<process-table :groups="groups" :whitelist="whitelist" @toggle="toggle" @kill="kill" />
<event-log :lines="lines" />`,