From 175bdff9cd4a2f22f83a143e17aca8cde54c040a Mon Sep 17 00:00:00 2001 From: pyr0ball Date: Fri, 15 May 2026 21:23:03 -0700 Subject: [PATCH] feat(blocklist): BlocklistView + Pi-hole settings UI --- web/src/App.vue | 1 + web/src/router/index.ts | 2 + web/src/views/BlocklistView.vue | 333 ++++++++++++++++++++++++++++++++ web/src/views/SettingsView.vue | 117 ++++++++++- 4 files changed, 452 insertions(+), 1 deletion(-) create mode 100644 web/src/views/BlocklistView.vue diff --git a/web/src/App.vue b/web/src/App.vue index 9166780..2afaef6 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -58,6 +58,7 @@ const navLinks = [ { to: '/bundles', label: 'Bundles' }, { to: '/sources', label: 'Sources' }, { to: '/context', label: 'Context' }, + { to: '/blocklist', label: 'Blocklist' }, ] const isDark = ref(document.documentElement.classList.contains('dark')) diff --git a/web/src/router/index.ts b/web/src/router/index.ts index ee1903d..b2c7f97 100644 --- a/web/src/router/index.ts +++ b/web/src/router/index.ts @@ -7,6 +7,7 @@ import IncidentsView from '@/views/IncidentsView.vue' import BundlesView from '@/views/BundlesView.vue' import SettingsView from '@/views/SettingsView.vue' import ContextView from '@/views/ContextView.vue' +import BlocklistView from '@/views/BlocklistView.vue' export default createRouter({ history: createWebHistory(import.meta.env.BASE_URL), @@ -19,6 +20,7 @@ export default createRouter({ { path: '/bundles', component: BundlesView }, { path: '/sources', component: SourcesView }, { path: '/context', component: ContextView }, + { path: '/blocklist', component: BlocklistView }, { path: '/settings', component: SettingsView }, ], }) diff --git a/web/src/views/BlocklistView.vue b/web/src/views/BlocklistView.vue new file mode 100644 index 0000000..ad23a36 --- /dev/null +++ b/web/src/views/BlocklistView.vue @@ -0,0 +1,333 @@ + + + diff --git a/web/src/views/SettingsView.vue b/web/src/views/SettingsView.vue index 99a8cd2..463152a 100644 --- a/web/src/views/SettingsView.vue +++ b/web/src/views/SettingsView.vue @@ -175,6 +175,84 @@ + +
+

Pi-hole Blocklist

+

+ Push flagged IoT destinations to your Pi-hole instance. + Find your API key (v5) or app password (v6) in Pi-hole's admin settings. +

+
+
+
+ + +
+
+ + +
+
+
+ +
+ + +
+
+
+ + +
+
+ + +
+
+ + + {{ piholeStatus.msg }} +
+
+
+

({ entry_point_style: 'topbar', llm_url: '', llm_model: '', llm_api_key: '', severity_overrides: [] }) +const prefs = ref({ entry_point_style: 'topbar', llm_url: '', llm_model: '', llm_api_key: '', severity_overrides: [], pihole_url: '', pihole_version: 'v6', pihole_api_key: '', router_source_ids: '', device_names: '' }) const saveStatus = ref<{ ok: boolean; msg: string } | null>(null) const showAddOverride = ref(false) const showApiKey = ref(false) +const showPiholeKey = ref(false) +const piholeStatus = ref<{ ok: boolean; msg: string } | null>(null) const newRule = ref({ name: '', pattern: '', override_severity: 'WARN', enabled: true }) const entryPointBtnRefs = ref([]) @@ -304,4 +389,34 @@ async function addOverride() { showAddOverride.value = false await saveOverrides() } + +async function savePihole() { + saveStatus.value = null + try { + await patch({ + pihole_url: prefs.value.pihole_url, + pihole_version: prefs.value.pihole_version, + pihole_api_key: prefs.value.pihole_api_key, + router_source_ids: prefs.value.router_source_ids, + device_names: prefs.value.device_names, + }) + saveStatus.value = { ok: true, msg: 'Pi-hole settings saved' } + setTimeout(() => { saveStatus.value = null }, 2000) + } catch { + saveStatus.value = { ok: false, msg: 'Save failed — check server connection' } + } +} + +async function testPihole() { + piholeStatus.value = null + try { + const res = await fetch(`${BASE}/api/blocklist/test`, { method: 'POST' }) + const data = await res.json() + piholeStatus.value = data.ok + ? { ok: true, msg: `Connected — Pi-hole ${data.version}, ${data.domain_count} domains` } + : { ok: false, msg: data.error ?? 'Connection failed' } + } catch { + piholeStatus.value = { ok: false, msg: 'Network error' } + } +}