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