mirror of
https://github.com/infinition/Bjorn.git
synced 2026-03-17 09:31:04 +00:00
Add notifier configuration management for Sentinel and LLM
This commit is contained in:
@@ -450,6 +450,12 @@ class SharedData:
|
|||||||
"sentinel_discord_webhook": "",
|
"sentinel_discord_webhook": "",
|
||||||
"sentinel_webhook_url": "",
|
"sentinel_webhook_url": "",
|
||||||
"sentinel_email_enabled": False,
|
"sentinel_email_enabled": False,
|
||||||
|
"sentinel_email_smtp_host": "",
|
||||||
|
"sentinel_email_smtp_port": "",
|
||||||
|
"sentinel_email_username": "",
|
||||||
|
"sentinel_email_password": "",
|
||||||
|
"sentinel_email_from": "",
|
||||||
|
"sentinel_email_to": "",
|
||||||
|
|
||||||
# Bifrost (Pwnagotchi Mode)
|
# Bifrost (Pwnagotchi Mode)
|
||||||
"__title_bifrost__": "Bifrost (Pwnagotchi Mode)",
|
"__title_bifrost__": "Bifrost (Pwnagotchi Mode)",
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ body.console-docked .app-container {
|
|||||||
box-shadow: 0 -30px 80px var(--glow-strong, #00ff9a33), inset 0 0 0 1px var(--glow-mid, #00ff9a22);
|
box-shadow: 0 -30px 80px var(--glow-strong, #00ff9a33), inset 0 0 0 1px var(--glow-mid, #00ff9a22);
|
||||||
z-index: 60;
|
z-index: 60;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: 8px auto auto 1fr;
|
grid-template-rows: 8px auto auto 1fr auto;
|
||||||
transform: translateY(100%);
|
transform: translateY(100%);
|
||||||
transition: transform .25s ease;
|
transition: transform .25s ease;
|
||||||
}
|
}
|
||||||
@@ -2684,6 +2684,8 @@ input[type="color"].theme-input {
|
|||||||
font-family: var(--font-mono, 'Courier New', monospace);
|
font-family: var(--font-mono, 'Courier New', monospace);
|
||||||
font-size: var(--console-font, 11px);
|
font-size: var(--console-font, 11px);
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
|
max-height: 60px;
|
||||||
|
height: 26px;
|
||||||
}
|
}
|
||||||
.console-input:focus { border-color: var(--acid, #22c55e); outline: none; }
|
.console-input:focus { border-color: var(--acid, #22c55e); outline: none; }
|
||||||
.console-send-btn {
|
.console-send-btn {
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ function buildShell() {
|
|||||||
|
|
||||||
toggleRow('llm_enabled', t('llm_cfg.enable_bridge')),
|
toggleRow('llm_enabled', t('llm_cfg.enable_bridge')),
|
||||||
toggleRow('llm_comments_enabled', t('llm_cfg.epd_comments')),
|
toggleRow('llm_comments_enabled', t('llm_cfg.epd_comments')),
|
||||||
|
toggleRow('llm_comments_log', 'Log comments to console'),
|
||||||
|
toggleRow('llm_chat_enabled', 'Enable LLM chat'),
|
||||||
|
toggleRow('llm_chat_tools_enabled', 'Enable tools in chat (function calling)'),
|
||||||
|
toggleRow('epd_buttons_enabled', 'EPD physical buttons'),
|
||||||
|
|
||||||
fieldEl(t('llm_cfg.backend'), el('select', { id: 'llm_backend', class: 'llmcfg-select' }, [
|
fieldEl(t('llm_cfg.backend'), el('select', { id: 'llm_backend', class: 'llmcfg-select' }, [
|
||||||
el('option', { value: 'auto' }, ['Auto (LaRuche → Ollama → API)']),
|
el('option', { value: 'auto' }, ['Auto (LaRuche → Ollama → API)']),
|
||||||
@@ -125,6 +129,12 @@ function buildShell() {
|
|||||||
min: '20', max: '200', value: '80' })),
|
min: '20', max: '200', value: '80' })),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
el('div', { class: 'llmcfg-row' }, [
|
||||||
|
fieldEl('Chat history size',
|
||||||
|
el('input', { type: 'number', id: 'llm_chat_history_size', class: 'llmcfg-input',
|
||||||
|
min: '2', max: '100', value: '20' })),
|
||||||
|
]),
|
||||||
|
|
||||||
el('div', { class: 'llmcfg-status-row', id: 'llm-status-row' }),
|
el('div', { class: 'llmcfg-status-row', id: 'llm-status-row' }),
|
||||||
|
|
||||||
el('div', { class: 'llmcfg-actions' }, [
|
el('div', { class: 'llmcfg-actions' }, [
|
||||||
@@ -159,6 +169,7 @@ function buildShell() {
|
|||||||
|
|
||||||
toggleRow('llm_orchestrator_log_reasoning', 'Log reasoning to chat history'),
|
toggleRow('llm_orchestrator_log_reasoning', 'Log reasoning to chat history'),
|
||||||
toggleRow('llm_orchestrator_skip_if_no_change', 'Skip cycle when nothing changed'),
|
toggleRow('llm_orchestrator_skip_if_no_change', 'Skip cycle when nothing changed'),
|
||||||
|
toggleRow('llm_orchestrator_skip_scheduler', 'Skip scheduler (LLM-only mode)'),
|
||||||
|
|
||||||
el('div', { class: 'llmcfg-status-row', id: 'orch-status-row' }),
|
el('div', { class: 'llmcfg-status-row', id: 'orch-status-row' }),
|
||||||
|
|
||||||
@@ -315,10 +326,15 @@ async function loadAll() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function applyLLMConfig(cfg) {
|
function applyLLMConfig(cfg) {
|
||||||
const boolKeys = ['llm_enabled', 'llm_comments_enabled', 'llm_laruche_discovery'];
|
const boolKeys = [
|
||||||
|
'llm_enabled', 'llm_comments_enabled', 'llm_comments_log',
|
||||||
|
'llm_chat_enabled', 'llm_chat_tools_enabled',
|
||||||
|
'llm_laruche_discovery', 'epd_buttons_enabled',
|
||||||
|
];
|
||||||
const textKeys = ['llm_backend', 'llm_laruche_url', 'llm_ollama_url',
|
const textKeys = ['llm_backend', 'llm_laruche_url', 'llm_ollama_url',
|
||||||
'llm_api_provider', 'llm_api_model', 'llm_api_base_url',
|
'llm_api_provider', 'llm_api_model', 'llm_api_base_url',
|
||||||
'llm_timeout_s', 'llm_max_tokens', 'llm_comment_max_tokens',
|
'llm_timeout_s', 'llm_max_tokens', 'llm_comment_max_tokens',
|
||||||
|
'llm_chat_history_size',
|
||||||
'llm_user_name', 'llm_user_bio',
|
'llm_user_name', 'llm_user_bio',
|
||||||
'llm_system_prompt_chat', 'llm_system_prompt_comment'];
|
'llm_system_prompt_chat', 'llm_system_prompt_comment'];
|
||||||
|
|
||||||
@@ -423,7 +439,8 @@ function applyLLMConfig(cfg) {
|
|||||||
const orchMax = $('#llm_orchestrator_max_actions', root);
|
const orchMax = $('#llm_orchestrator_max_actions', root);
|
||||||
if (orchMax && cfg.llm_orchestrator_max_actions !== undefined) orchMax.value = cfg.llm_orchestrator_max_actions;
|
if (orchMax && cfg.llm_orchestrator_max_actions !== undefined) orchMax.value = cfg.llm_orchestrator_max_actions;
|
||||||
|
|
||||||
for (const k of ['llm_orchestrator_log_reasoning', 'llm_orchestrator_skip_if_no_change']) {
|
for (const k of ['llm_orchestrator_log_reasoning', 'llm_orchestrator_skip_if_no_change',
|
||||||
|
'llm_orchestrator_skip_scheduler']) {
|
||||||
const cb = $(('#' + k), root);
|
const cb = $(('#' + k), root);
|
||||||
if (cb) cb.checked = !!cfg[k];
|
if (cb) cb.checked = !!cfg[k];
|
||||||
}
|
}
|
||||||
@@ -556,7 +573,11 @@ function populateModelSelect(selectEl, models, currentValue) {
|
|||||||
async function saveLLM() {
|
async function saveLLM() {
|
||||||
const payload = {};
|
const payload = {};
|
||||||
|
|
||||||
for (const k of ['llm_enabled', 'llm_comments_enabled', 'llm_laruche_discovery']) {
|
for (const k of [
|
||||||
|
'llm_enabled', 'llm_comments_enabled', 'llm_comments_log',
|
||||||
|
'llm_chat_enabled', 'llm_chat_tools_enabled',
|
||||||
|
'llm_laruche_discovery', 'epd_buttons_enabled',
|
||||||
|
]) {
|
||||||
const el = $(('#' + k), root);
|
const el = $(('#' + k), root);
|
||||||
payload[k] = el ? el.checked : false;
|
payload[k] = el ? el.checked : false;
|
||||||
}
|
}
|
||||||
@@ -566,7 +587,8 @@ async function saveLLM() {
|
|||||||
const el = $(('#' + k), root);
|
const el = $(('#' + k), root);
|
||||||
if (el) payload[k] = el.value;
|
if (el) payload[k] = el.value;
|
||||||
}
|
}
|
||||||
for (const k of ['llm_timeout_s', 'llm_max_tokens', 'llm_comment_max_tokens']) {
|
for (const k of ['llm_timeout_s', 'llm_max_tokens', 'llm_comment_max_tokens',
|
||||||
|
'llm_chat_history_size']) {
|
||||||
const el = $(('#' + k), root);
|
const el = $(('#' + k), root);
|
||||||
if (el) payload[k] = parseInt(el.value) || undefined;
|
if (el) payload[k] = parseInt(el.value) || undefined;
|
||||||
}
|
}
|
||||||
@@ -642,7 +664,8 @@ async function saveOrch() {
|
|||||||
const inp = $(('#' + k), root);
|
const inp = $(('#' + k), root);
|
||||||
if (inp) payload[k] = parseInt(inp.value) || undefined;
|
if (inp) payload[k] = parseInt(inp.value) || undefined;
|
||||||
}
|
}
|
||||||
for (const k of ['llm_orchestrator_log_reasoning', 'llm_orchestrator_skip_if_no_change']) {
|
for (const k of ['llm_orchestrator_log_reasoning', 'llm_orchestrator_skip_if_no_change',
|
||||||
|
'llm_orchestrator_skip_scheduler']) {
|
||||||
const cb = $(('#' + k), root);
|
const cb = $(('#' + k), root);
|
||||||
if (cb) payload[k] = cb.checked;
|
if (cb) payload[k] = cb.checked;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ let events = [];
|
|||||||
let rules = [];
|
let rules = [];
|
||||||
let devices = [];
|
let devices = [];
|
||||||
let unreadCount = 0;
|
let unreadCount = 0;
|
||||||
|
let notifierCfg = {}; // { discord_webhook: '...', webhook_url: '...', ... }
|
||||||
let sideTab = 'rules'; // 'rules' | 'devices' | 'notifiers'
|
let sideTab = 'rules'; // 'rules' | 'devices' | 'notifiers'
|
||||||
|
|
||||||
/* ── Lifecycle ─────────────────────────────────────────── */
|
/* ── Lifecycle ─────────────────────────────────────────── */
|
||||||
@@ -41,6 +42,7 @@ export function unmount() {
|
|||||||
events = [];
|
events = [];
|
||||||
rules = [];
|
rules = [];
|
||||||
devices = [];
|
devices = [];
|
||||||
|
notifierCfg = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Shell ─────────────────────────────────────────────── */
|
/* ── Shell ─────────────────────────────────────────────── */
|
||||||
@@ -248,17 +250,19 @@ function bindEvents() {
|
|||||||
|
|
||||||
async function refresh() {
|
async function refresh() {
|
||||||
try {
|
try {
|
||||||
const [statusData, eventsData, rulesData, devicesData] = await Promise.all([
|
const [statusData, eventsData, rulesData, devicesData, notifData] = await Promise.all([
|
||||||
api.get('/api/sentinel/status'),
|
api.get('/api/sentinel/status'),
|
||||||
api.get('/api/sentinel/events?limit=100'),
|
api.get('/api/sentinel/events?limit=100'),
|
||||||
api.get('/api/sentinel/rules'),
|
api.get('/api/sentinel/rules'),
|
||||||
api.get('/api/sentinel/devices'),
|
api.get('/api/sentinel/devices'),
|
||||||
|
api.get('/api/sentinel/notifiers').catch(() => null),
|
||||||
]);
|
]);
|
||||||
sentinelEnabled = statusData.enabled;
|
sentinelEnabled = statusData.enabled;
|
||||||
events = eventsData.events || [];
|
events = eventsData.events || [];
|
||||||
unreadCount = eventsData.unread_count || 0;
|
unreadCount = eventsData.unread_count || 0;
|
||||||
rules = rulesData.rules || [];
|
rules = rulesData.rules || [];
|
||||||
devices = devicesData.devices || [];
|
devices = devicesData.devices || [];
|
||||||
|
if (notifData?.notifiers) notifierCfg = notifData.notifiers;
|
||||||
paint();
|
paint();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('[sentinel] refresh error:', err.message);
|
console.warn('[sentinel] refresh error:', err.message);
|
||||||
@@ -743,6 +747,7 @@ function paintNotifiers(container) {
|
|||||||
type: f.type || 'text',
|
type: f.type || 'text',
|
||||||
'data-notifier': f.key,
|
'data-notifier': f.key,
|
||||||
placeholder: f.placeholder,
|
placeholder: f.placeholder,
|
||||||
|
value: notifierCfg[f.key] || '',
|
||||||
class: 'sentinel-notifier-input',
|
class: 'sentinel-notifier-input',
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -154,9 +154,12 @@ class LLMUtils:
|
|||||||
"llm_orchestrator_mode", "llm_orchestrator_interval_s",
|
"llm_orchestrator_mode", "llm_orchestrator_interval_s",
|
||||||
"llm_orchestrator_max_actions", "llm_orchestrator_allowed_actions",
|
"llm_orchestrator_max_actions", "llm_orchestrator_allowed_actions",
|
||||||
"llm_orchestrator_skip_if_no_change", "llm_orchestrator_log_reasoning",
|
"llm_orchestrator_skip_if_no_change", "llm_orchestrator_log_reasoning",
|
||||||
|
"llm_orchestrator_skip_scheduler",
|
||||||
# Personality & prompt keys
|
# Personality & prompt keys
|
||||||
"llm_system_prompt_chat", "llm_system_prompt_comment",
|
"llm_system_prompt_chat", "llm_system_prompt_comment",
|
||||||
"llm_user_name", "llm_user_bio",
|
"llm_user_name", "llm_user_bio",
|
||||||
|
# EPD
|
||||||
|
"epd_buttons_enabled",
|
||||||
}
|
}
|
||||||
_int_keys = {
|
_int_keys = {
|
||||||
"llm_timeout_s", "llm_max_tokens", "llm_comment_max_tokens",
|
"llm_timeout_s", "llm_max_tokens", "llm_comment_max_tokens",
|
||||||
@@ -167,6 +170,7 @@ class LLMUtils:
|
|||||||
"llm_enabled", "llm_comments_enabled", "llm_comments_log", "llm_chat_enabled",
|
"llm_enabled", "llm_comments_enabled", "llm_comments_log", "llm_chat_enabled",
|
||||||
"llm_laruche_discovery", "llm_chat_tools_enabled",
|
"llm_laruche_discovery", "llm_chat_tools_enabled",
|
||||||
"llm_orchestrator_skip_if_no_change", "llm_orchestrator_log_reasoning",
|
"llm_orchestrator_skip_if_no_change", "llm_orchestrator_log_reasoning",
|
||||||
|
"llm_orchestrator_skip_scheduler", "epd_buttons_enabled",
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
cfg = self.shared_data.config
|
cfg = self.shared_data.config
|
||||||
@@ -299,7 +303,9 @@ class LLMUtils:
|
|||||||
# Orchestrator
|
# Orchestrator
|
||||||
"llm_orchestrator_mode", "llm_orchestrator_interval_s",
|
"llm_orchestrator_mode", "llm_orchestrator_interval_s",
|
||||||
"llm_orchestrator_max_actions", "llm_orchestrator_skip_if_no_change",
|
"llm_orchestrator_max_actions", "llm_orchestrator_skip_if_no_change",
|
||||||
"llm_orchestrator_log_reasoning",
|
"llm_orchestrator_log_reasoning", "llm_orchestrator_skip_scheduler",
|
||||||
|
# EPD
|
||||||
|
"epd_buttons_enabled",
|
||||||
# Personality & prompts
|
# Personality & prompts
|
||||||
"llm_system_prompt_chat", "llm_system_prompt_comment",
|
"llm_system_prompt_chat", "llm_system_prompt_comment",
|
||||||
"llm_user_name", "llm_user_bio",
|
"llm_user_name", "llm_user_bio",
|
||||||
|
|||||||
@@ -219,12 +219,40 @@ class SentinelUtils:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"status": "error", "message": str(e)}
|
return {"status": "error", "message": str(e)}
|
||||||
|
|
||||||
|
# Mapping from frontend notifier keys to config keys
|
||||||
|
_NOTIFIER_KEY_MAP = {
|
||||||
|
"discord_webhook": "sentinel_discord_webhook",
|
||||||
|
"webhook_url": "sentinel_webhook_url",
|
||||||
|
"email_smtp_host": "sentinel_email_smtp_host",
|
||||||
|
"email_smtp_port": "sentinel_email_smtp_port",
|
||||||
|
"email_username": "sentinel_email_username",
|
||||||
|
"email_password": "sentinel_email_password",
|
||||||
|
"email_from": "sentinel_email_from",
|
||||||
|
"email_to": "sentinel_email_to",
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_notifier_config(self, handler) -> None:
|
||||||
|
"""GET /api/sentinel/notifiers — return current notifier config."""
|
||||||
|
cfg = self.shared_data.config
|
||||||
|
notifiers = {}
|
||||||
|
for frontend_key, cfg_key in self._NOTIFIER_KEY_MAP.items():
|
||||||
|
val = cfg.get(cfg_key, "")
|
||||||
|
if val:
|
||||||
|
notifiers[frontend_key] = val
|
||||||
|
self._send_json(handler, {"status": "ok", "notifiers": notifiers})
|
||||||
|
|
||||||
def save_notifier_config(self, data: Dict) -> Dict:
|
def save_notifier_config(self, data: Dict) -> Dict:
|
||||||
"""POST /api/sentinel/notifiers — save notification channel config."""
|
"""POST /api/sentinel/notifiers — save notification channel config."""
|
||||||
try:
|
try:
|
||||||
# Store notifier configs in shared_data for persistence
|
|
||||||
notifiers = data.get("notifiers", {})
|
notifiers = data.get("notifiers", {})
|
||||||
self.shared_data.sentinel_notifiers = notifiers
|
cfg = self.shared_data.config
|
||||||
|
|
||||||
|
# Map frontend keys to config keys and persist
|
||||||
|
for frontend_key, cfg_key in self._NOTIFIER_KEY_MAP.items():
|
||||||
|
cfg[cfg_key] = notifiers.get(frontend_key, "")
|
||||||
|
|
||||||
|
self.shared_data.config = cfg
|
||||||
|
self.shared_data.save_config()
|
||||||
|
|
||||||
# Re-register notifiers on the engine
|
# Re-register notifiers on the engine
|
||||||
engine = self._engine
|
engine = self._engine
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ class CustomHandler(http.server.SimpleHTTPRequestHandler):
|
|||||||
'/api/sentinel/rules': wu.sentinel.get_rules,
|
'/api/sentinel/rules': wu.sentinel.get_rules,
|
||||||
'/api/sentinel/devices': wu.sentinel.get_devices,
|
'/api/sentinel/devices': wu.sentinel.get_devices,
|
||||||
'/api/sentinel/arp': wu.sentinel.get_arp_table,
|
'/api/sentinel/arp': wu.sentinel.get_arp_table,
|
||||||
|
'/api/sentinel/notifiers': wu.sentinel.get_notifier_config,
|
||||||
|
|
||||||
# BIFROST
|
# BIFROST
|
||||||
'/api/bifrost/status': wu.bifrost.get_status,
|
'/api/bifrost/status': wu.bifrost.get_status,
|
||||||
|
|||||||
Reference in New Issue
Block a user