1#![allow(clippy::unused_unit, deprecated, clippy::too_many_arguments)]
4
5use dioxus::prelude::*;
6
7#[cfg(feature = "server")]
8use crate::api::auth::get_current_admin_user;
9#[cfg(feature = "server")]
10use crate::api::error::AppError;
11#[cfg(feature = "server")]
12use crate::db::pool::get_conn;
13use crate::models::settings::RunnerSettings;
14
15#[cfg(feature = "server")]
20fn parse_allow_network(value: &str) -> bool {
21 let value = value.to_lowercase();
22 value == "true" || value == "1" || value == "yes"
23}
24
25#[cfg(feature = "server")]
27pub(crate) async fn seed_runner_settings_from_env(
28 client: &tokio_postgres::Client,
29) -> Result<(), AppError> {
30 use crate::models::settings as m;
31
32 let mut seeds: Vec<(&'static str, String)> = Vec::new();
33
34 if let Ok(v) = std::env::var("CODE_RUNNER_ALLOW_NETWORK") {
35 seeds.push(("runner_allow_network", parse_allow_network(&v).to_string()));
36 }
37 if let Ok(v) = std::env::var("CODE_RUNNER_MAX_CONCURRENT") {
38 match v.trim().parse::<u32>() {
39 Ok(n) => seeds.push((
40 "runner_max_concurrent",
41 m::RunnerSettings::clamp_max_concurrent(n).to_string(),
42 )),
43 Err(_) => tracing::warn!("CODE_RUNNER_MAX_CONCURRENT={v:?} 非法,跳过"),
44 }
45 }
46 if let Ok(v) = std::env::var("CODE_RUNNER_MAX_CPU_CORES") {
47 match v.trim().parse::<f64>() {
48 Ok(n) => seeds.push((
49 "runner_max_cpu_cores",
50 m::RunnerSettings::clamp_max_cpu_cores(n).to_string(),
51 )),
52 Err(_) => tracing::warn!("CODE_RUNNER_MAX_CPU_CORES={v:?} 非法,跳过"),
53 }
54 }
55 if let Ok(v) = std::env::var("CODE_RUNNER_MAX_MEMORY_MB") {
56 match v.trim().parse::<u32>() {
57 Ok(n) => seeds.push((
58 "runner_max_memory_mb",
59 m::RunnerSettings::clamp_max_memory_mb(n).to_string(),
60 )),
61 Err(_) => tracing::warn!("CODE_RUNNER_MAX_MEMORY_MB={v:?} 非法,跳过"),
62 }
63 }
64 if let Ok(v) = std::env::var("CODE_RUNNER_MAX_TIMEOUT_SECS") {
65 match v.trim().parse::<u32>() {
66 Ok(n) => seeds.push((
67 "runner_max_timeout_secs",
68 m::RunnerSettings::clamp_max_timeout_secs(n).to_string(),
69 )),
70 Err(_) => tracing::warn!("CODE_RUNNER_MAX_TIMEOUT_SECS={v:?} 非法,跳过"),
71 }
72 }
73 if let Ok(v) = std::env::var("CODE_RUNNER_MAX_OUTPUT_BYTES") {
74 match v.trim().parse::<u64>() {
75 Ok(n) => seeds.push((
76 "runner_max_output_bytes",
77 m::RunnerSettings::clamp_max_output_bytes(n).to_string(),
78 )),
79 Err(_) => tracing::warn!("CODE_RUNNER_MAX_OUTPUT_BYTES={v:?} 非法,跳过"),
80 }
81 }
82 if let Ok(v) = std::env::var("CODE_RUNNER_MAX_SOURCE_BYTES") {
83 match v.trim().parse::<u64>() {
84 Ok(n) => seeds.push((
85 "runner_max_source_bytes",
86 m::RunnerSettings::clamp_max_source_bytes(n).to_string(),
87 )),
88 Err(_) => tracing::warn!("CODE_RUNNER_MAX_SOURCE_BYTES={v:?} 非法,跳过"),
89 }
90 }
91 if let Ok(v) = std::env::var("CODE_RUNNER_QUEUE_TIMEOUT_SECS") {
92 match v.trim().parse::<u32>() {
93 Ok(n) => seeds.push((
94 "runner_queue_timeout_secs",
95 m::RunnerSettings::clamp_queue_timeout_secs(n).to_string(),
96 )),
97 Err(_) => tracing::warn!("CODE_RUNNER_QUEUE_TIMEOUT_SECS={v:?} 非法,跳过"),
98 }
99 }
100 if let Ok(v) = std::env::var("CODE_RUNNER_TASK_TTL_SECS") {
101 match v.trim().parse::<u32>() {
102 Ok(n) => seeds.push((
103 "runner_task_ttl_secs",
104 m::RunnerSettings::clamp_task_ttl_secs(n).to_string(),
105 )),
106 Err(_) => tracing::warn!("CODE_RUNNER_TASK_TTL_SECS={v:?} 非法,跳过"),
107 }
108 }
109 if let Ok(v) = std::env::var("CODE_RUNNER_LANGUAGES") {
110 if let Some(norm) = m::RunnerSettings::normalize_languages(&v) {
111 seeds.push(("runner_languages", norm));
112 }
113 }
114
115 super::insert_env_seeds(client, seeds).await
116}
117
118#[cfg(feature = "server")]
120pub(crate) async fn load_runner_settings(
121 client: &tokio_postgres::Client,
122) -> Result<RunnerSettings, AppError> {
123 use crate::models::settings as m;
124
125 let values = super::load_setting_values(
126 client,
127 &[
128 "runner_allow_network",
129 "runner_max_concurrent",
130 "runner_max_cpu_cores",
131 "runner_max_memory_mb",
132 "runner_max_timeout_secs",
133 "runner_max_output_bytes",
134 "runner_max_source_bytes",
135 "runner_queue_timeout_secs",
136 "runner_task_ttl_secs",
137 "runner_languages",
138 ],
139 )
140 .await?;
141 let allow_network = values
142 .get("runner_allow_network")
143 .and_then(|v| v.parse().ok())
144 .unwrap_or(m::DEFAULT_RUNNER_ALLOW_NETWORK);
145 let max_concurrent = values
146 .get("runner_max_concurrent")
147 .and_then(|v| v.parse().ok())
148 .map(m::RunnerSettings::clamp_max_concurrent)
149 .unwrap_or(m::DEFAULT_RUNNER_MAX_CONCURRENT);
150 let max_cpu_cores = values
151 .get("runner_max_cpu_cores")
152 .and_then(|v| v.parse().ok())
153 .map(m::RunnerSettings::clamp_max_cpu_cores)
154 .unwrap_or(m::DEFAULT_RUNNER_MAX_CPU_CORES);
155 let max_memory_mb = values
156 .get("runner_max_memory_mb")
157 .and_then(|v| v.parse().ok())
158 .map(m::RunnerSettings::clamp_max_memory_mb)
159 .unwrap_or(m::DEFAULT_RUNNER_MAX_MEMORY_MB);
160 let max_timeout_secs = values
161 .get("runner_max_timeout_secs")
162 .and_then(|v| v.parse().ok())
163 .map(m::RunnerSettings::clamp_max_timeout_secs)
164 .unwrap_or(m::DEFAULT_RUNNER_MAX_TIMEOUT_SECS);
165 let max_output_bytes = values
166 .get("runner_max_output_bytes")
167 .and_then(|v| v.parse().ok())
168 .map(m::RunnerSettings::clamp_max_output_bytes)
169 .unwrap_or(m::DEFAULT_RUNNER_MAX_OUTPUT_BYTES);
170 let max_source_bytes = values
171 .get("runner_max_source_bytes")
172 .and_then(|v| v.parse().ok())
173 .map(m::RunnerSettings::clamp_max_source_bytes)
174 .unwrap_or(m::DEFAULT_RUNNER_MAX_SOURCE_BYTES);
175 let queue_timeout_secs = values
176 .get("runner_queue_timeout_secs")
177 .and_then(|v| v.parse().ok())
178 .map(m::RunnerSettings::clamp_queue_timeout_secs)
179 .unwrap_or(m::DEFAULT_RUNNER_QUEUE_TIMEOUT_SECS);
180 let task_ttl_secs = values
181 .get("runner_task_ttl_secs")
182 .and_then(|v| v.parse().ok())
183 .map(m::RunnerSettings::clamp_task_ttl_secs)
184 .unwrap_or(m::DEFAULT_RUNNER_TASK_TTL_SECS);
185 let languages = values
186 .get("runner_languages")
187 .and_then(|v| m::RunnerSettings::normalize_languages(v));
188
189 Ok(RunnerSettings {
190 allow_network,
191 max_concurrent,
192 max_cpu_cores,
193 max_memory_mb,
194 max_timeout_secs,
195 max_output_bytes,
196 max_source_bytes,
197 queue_timeout_secs,
198 task_ttl_secs,
199 languages,
200 })
201}
202
203#[server(GetRunnerSettings, "/api")]
205pub async fn get_runner_settings() -> Result<RunnerSettings, ServerFnError> {
206 let _user = get_current_admin_user().await?;
207
208 #[cfg(feature = "server")]
209 {
210 let client = get_conn().await.map_err(AppError::db_conn)?;
211 let s = load_runner_settings(&client)
212 .await
213 .map_err(ServerFnError::from)?;
214 Ok(s)
215 }
216
217 #[cfg(not(feature = "server"))]
218 {
219 Ok(RunnerSettings::default())
220 }
221}
222
223#[server(UpdateRunnerSettings, "/api")]
227pub async fn update_runner_settings(
228 allow_network: bool,
229 max_concurrent: u32,
230 max_cpu_cores: f64,
231 max_memory_mb: u32,
232 max_timeout_secs: u32,
233 max_output_bytes: u64,
234 max_source_bytes: u64,
235 queue_timeout_secs: u32,
236 task_ttl_secs: u32,
237 languages: Option<String>,
238) -> Result<RunnerSettings, ServerFnError> {
239 let _user = get_current_admin_user().await?;
240
241 let max_concurrent = RunnerSettings::clamp_max_concurrent(max_concurrent);
242 let max_cpu_cores = RunnerSettings::clamp_max_cpu_cores(max_cpu_cores);
243 let max_memory_mb = RunnerSettings::clamp_max_memory_mb(max_memory_mb);
244 let max_timeout_secs = RunnerSettings::clamp_max_timeout_secs(max_timeout_secs);
245 let max_output_bytes = RunnerSettings::clamp_max_output_bytes(max_output_bytes);
246 let max_source_bytes = RunnerSettings::clamp_max_source_bytes(max_source_bytes);
247 let queue_timeout_secs = RunnerSettings::clamp_queue_timeout_secs(queue_timeout_secs);
248 let task_ttl_secs = RunnerSettings::clamp_task_ttl_secs(task_ttl_secs);
249 let languages = languages.and_then(|s| RunnerSettings::normalize_languages(&s));
250
251 #[cfg(feature = "server")]
252 {
253 let client = get_conn().await.map_err(AppError::db_conn)?;
254
255 let lang_str = languages.clone().unwrap_or_default();
256 for (key, value) in [
257 ("runner_allow_network", allow_network.to_string()),
258 ("runner_max_concurrent", max_concurrent.to_string()),
259 ("runner_max_cpu_cores", max_cpu_cores.to_string()),
260 ("runner_max_memory_mb", max_memory_mb.to_string()),
261 ("runner_max_timeout_secs", max_timeout_secs.to_string()),
262 ("runner_max_output_bytes", max_output_bytes.to_string()),
263 ("runner_max_source_bytes", max_source_bytes.to_string()),
264 ("runner_queue_timeout_secs", queue_timeout_secs.to_string()),
265 ("runner_task_ttl_secs", task_ttl_secs.to_string()),
266 ("runner_languages", lang_str),
267 ] {
268 client
269 .execute(
270 "INSERT INTO settings (key, value, updated_at) VALUES ($1, $2, NOW())
271 ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()",
272 &[&key, &value],
273 )
274 .await
275 .map_err(AppError::query)?;
276 }
277
278 tracing::info!(
279 "Runner settings updated (需重启生效): allow_network={}, max_concurrent={}, \
280 cpu={}, mem={}MB, timeout={}s, languages={:?}",
281 allow_network,
282 max_concurrent,
283 max_cpu_cores,
284 max_memory_mb,
285 max_timeout_secs,
286 languages
287 );
288 }
289
290 Ok(RunnerSettings {
291 allow_network,
292 max_concurrent,
293 max_cpu_cores,
294 max_memory_mb,
295 max_timeout_secs,
296 max_output_bytes,
297 max_source_bytes,
298 queue_timeout_secs,
299 task_ttl_secs,
300 languages,
301 })
302}
303
304#[cfg(all(test, feature = "server"))]
305mod tests {
306 use super::parse_allow_network;
307
308 #[test]
309 fn allow_network_env_requires_explicit_opt_in() {
310 for value in ["true", "TRUE", "True", "1", "yes", "YES", "Yes"] {
311 assert!(parse_allow_network(value), "{value:?}");
312 }
313 for value in ["false", "0", "no", "", "maybe", "on", " true "] {
314 assert!(!parse_allow_network(value), "{value:?}");
315 }
316 }
317}