netget/scripting/
manager.rs1use super::executor::{execute_script, execute_script_async};
4use super::types::{ScriptConfig, ScriptInput, ScriptResponse};
5use anyhow::Result;
6use tracing::{debug, info, warn};
7
8pub struct ScriptManager;
10
11impl ScriptManager {
12 fn select_config<'a>(
17 config: Option<&'a ScriptConfig>,
18 input: &ScriptInput,
19 ) -> Option<&'a ScriptConfig> {
20 let config = match config {
22 Some(cfg) => cfg,
23 None => {
24 debug!("No script configured, using LLM");
25 return None;
26 }
27 };
28
29 if !config.handles_context(&input.event_type_id) {
31 debug!(
32 "Script does not handle context '{}', using LLM",
33 input.event_type_id
34 );
35 return None;
36 }
37
38 info!(
39 "Executing {} script for context '{}'",
40 config.language.as_str(),
41 input.event_type_id
42 );
43
44 Some(config)
45 }
46
47 fn finish(result: Result<ScriptResponse>) -> Result<Option<ScriptResponse>> {
49 match result {
50 Ok(response) => {
51 debug!(
52 "Script handled request with {} actions",
53 response.actions.len()
54 );
55 Ok(Some(response))
56 }
57 Err(e) => {
58 warn!("Script execution failed: {}. Falling back to LLM", e);
59 Err(e)
61 }
62 }
63 }
64
65 pub async fn try_execute_async(
79 config: Option<&ScriptConfig>,
80 input: &ScriptInput,
81 ) -> Result<Option<ScriptResponse>> {
82 let Some(config) = Self::select_config(config, input) else {
83 return Ok(None);
84 };
85
86 Self::finish(execute_script_async(config, input).await)
87 }
88
89 pub fn try_execute(
99 config: Option<&ScriptConfig>,
100 input: &ScriptInput,
101 ) -> Result<Option<ScriptResponse>> {
102 let Some(config) = Self::select_config(config, input) else {
103 return Ok(None);
104 };
105
106 Self::finish(execute_script(config, input))
107 }
108
109 pub fn build_config(
122 selected_mode: crate::state::app_state::ScriptingMode,
123 script_runtime: Option<&str>,
124 script_inline: Option<&str>,
125 handles: Option<Vec<String>>,
126 ) -> Result<Option<ScriptConfig>> {
127 let code = match script_inline {
129 Some(c) => c,
130 None => return Ok(None),
131 };
132
133 let language = match selected_mode {
135 crate::state::app_state::ScriptingMode::On => {
136 let runtime = script_runtime.ok_or_else(|| {
138 anyhow::anyhow!("script_runtime is required when script_inline is provided")
139 })?;
140
141 match runtime.to_lowercase().as_str() {
143 "python" | "py" => super::types::ScriptLanguage::Python,
144 "javascript" | "js" | "node" => super::types::ScriptLanguage::JavaScript,
145 "go" | "golang" => super::types::ScriptLanguage::Go,
146 "perl" => super::types::ScriptLanguage::Perl,
147 _ => anyhow::bail!(
148 "Invalid script_runtime: '{}'. Valid options: python, javascript, go, perl",
149 runtime
150 ),
151 }
152 }
153 crate::state::app_state::ScriptingMode::Off => return Ok(None),
154 crate::state::app_state::ScriptingMode::Python => super::types::ScriptLanguage::Python,
155 crate::state::app_state::ScriptingMode::JavaScript => {
156 super::types::ScriptLanguage::JavaScript
157 }
158 crate::state::app_state::ScriptingMode::Go => super::types::ScriptLanguage::Go,
159 crate::state::app_state::ScriptingMode::Perl => super::types::ScriptLanguage::Perl,
160 };
161
162 let source = super::types::ScriptSource::Inline(code.to_string());
164
165 let handles_contexts = handles.unwrap_or_else(|| vec!["all".to_string()]);
167
168 if handles_contexts.is_empty() {
169 anyhow::bail!("Script handles_contexts cannot be empty");
170 }
171
172 Ok(Some(ScriptConfig {
173 language,
174 source,
175 handles_contexts,
176 }))
177 }
178
179 pub fn extract_context_type(event_description: &str) -> String {
194 let desc_lower = event_description.to_lowercase();
195
196 if desc_lower.contains("ssh") {
198 if desc_lower.contains("authentication") || desc_lower.contains("auth") {
199 return "ssh_auth".to_string();
200 }
201 if desc_lower.contains("banner") || desc_lower.contains("greeting") {
202 return "ssh_banner".to_string();
203 }
204 if desc_lower.contains("shell command") {
205 return "ssh_shell".to_string();
206 }
207 return "ssh_unknown".to_string();
208 }
209
210 if desc_lower.contains("http request") {
212 return "http_request".to_string();
213 }
214
215 if desc_lower.contains("tcp") && desc_lower.contains("data") {
217 return "tcp_data".to_string();
218 }
219
220 if desc_lower.contains("dns query") {
222 return "dns_query".to_string();
223 }
224
225 if desc_lower.contains("dhcp") {
227 if desc_lower.contains("discover") {
228 return "dhcp_discover".to_string();
229 }
230 if desc_lower.contains("request") {
231 return "dhcp_request".to_string();
232 }
233 return "dhcp_unknown".to_string();
234 }
235
236 if desc_lower.contains("smtp") {
238 if desc_lower.contains("ehlo") || desc_lower.contains("helo") {
239 return "smtp_ehlo".to_string();
240 }
241 if desc_lower.contains("mail from") {
242 return "smtp_mail_from".to_string();
243 }
244 if desc_lower.contains("data") {
245 return "smtp_data".to_string();
246 }
247 return "smtp_unknown".to_string();
248 }
249
250 warn!("Could not extract context type from: {}", event_description);
252 "unknown".to_string()
253 }
254}