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")]
21pub(crate) async fn seed_runner_settings_from_env(
22 client: &tokio_postgres::Client,
23) -> Result<(), AppError> {
24 use crate::models::settings as m;
25
26 let mut seeds: Vec<(&'static str, String)> = Vec::new();
27
28 if let Ok(v) = std::env::var("CODE_RUNNER_ALLOW_NETWORK") {
29 let l = v.to_lowercase();
30 let b = l == "true" || l == "1" || l == "yes";
31 seeds.push(("runner_allow_network", b.to_string()));
32 }
33 if let Ok(v) = std::env::var("CODE_RUNNER_MAX_CONCURRENT") {
34 match v.trim().parse::<u32>() {
35 Ok(n) => seeds.push((
36 "runner_max_concurrent",
37 m::RunnerSettings::clamp_max_concurrent(n).to_string(),
38 )),
39 Err(_) => tracing::warn!("CODE_RUNNER_MAX_CONCURRENT={v:?} 非法,跳过"),
40 }
41 }
42 if let Ok(v) = std::env::var("CODE_RUNNER_MAX_CPU_CORES") {
43 match v.trim().parse::<f64>() {
44 Ok(n) => seeds.push((
45 "runner_max_cpu_cores",
46 m::RunnerSettings::clamp_max_cpu_cores(n).to_string(),
47 )),
48 Err(_) => tracing::warn!("CODE_RUNNER_MAX_CPU_CORES={v:?} 非法,跳过"),
49 }
50 }
51 if let Ok(v) = std::env::var("CODE_RUNNER_MAX_MEMORY_MB") {
52 match v.trim().parse::<u32>() {
53 Ok(n) => seeds.push((
54 "runner_max_memory_mb",
55 m::RunnerSettings::clamp_max_memory_mb(n).to_string(),
56 )),
57 Err(_) => tracing::warn!("CODE_RUNNER_MAX_MEMORY_MB={v:?} 非法,跳过"),
58 }
59 }
60 if let Ok(v) = std::env::var("CODE_RUNNER_MAX_TIMEOUT_SECS") {
61 match v.trim().parse::<u32>() {
62 Ok(n) => seeds.push((
63 "runner_max_timeout_secs",
64 m::RunnerSettings::clamp_max_timeout_secs(n).to_string(),
65 )),
66 Err(_) => tracing::warn!("CODE_RUNNER_MAX_TIMEOUT_SECS={v:?} 非法,跳过"),
67 }
68 }
69 if let Ok(v) = std::env::var("CODE_RUNNER_MAX_OUTPUT_BYTES") {
70 match v.trim().parse::<u64>() {
71 Ok(n) => seeds.push((
72 "runner_max_output_bytes",
73 m::RunnerSettings::clamp_max_output_bytes(n).to_string(),
74 )),
75 Err(_) => tracing::warn!("CODE_RUNNER_MAX_OUTPUT_BYTES={v:?} 非法,跳过"),
76 }
77 }
78 if let Ok(v) = std::env::var("CODE_RUNNER_MAX_SOURCE_BYTES") {
79 match v.trim().parse::<u64>() {
80 Ok(n) => seeds.push((
81 "runner_max_source_bytes",
82 m::RunnerSettings::clamp_max_source_bytes(n).to_string(),
83 )),
84 Err(_) => tracing::warn!("CODE_RUNNER_MAX_SOURCE_BYTES={v:?} 非法,跳过"),
85 }
86 }
87 if let Ok(v) = std::env::var("CODE_RUNNER_QUEUE_TIMEOUT_SECS") {
88 match v.trim().parse::<u32>() {
89 Ok(n) => seeds.push((
90 "runner_queue_timeout_secs",
91 m::RunnerSettings::clamp_queue_timeout_secs(n).to_string(),
92 )),
93 Err(_) => tracing::warn!("CODE_RUNNER_QUEUE_TIMEOUT_SECS={v:?} 非法,跳过"),
94 }
95 }
96 if let Ok(v) = std::env::var("CODE_RUNNER_TASK_TTL_SECS") {
97 match v.trim().parse::<u32>() {
98 Ok(n) => seeds.push((
99 "runner_task_ttl_secs",
100 m::RunnerSettings::clamp_task_ttl_secs(n).to_string(),
101 )),
102 Err(_) => tracing::warn!("CODE_RUNNER_TASK_TTL_SECS={v:?} 非法,跳过"),
103 }
104 }
105 if let Ok(v) = std::env::var("CODE_RUNNER_LANGUAGES") {
106 if let Some(norm) = m::RunnerSettings::normalize_languages(&v) {
107 seeds.push(("runner_languages", norm));
108 }
109 }
110
111 for (key, value) in seeds {
112 client
113 .execute(
114 "INSERT INTO settings (key, value) VALUES ($1, $2) ON CONFLICT (key) DO NOTHING",
115 &[&key, &value],
116 )
117 .await
118 .map_err(AppError::query)?;
119 tracing::info!("运行器配置已从环境变量播种: {key}={value}(仅键缺失时生效)");
120 }
121 Ok(())
122}
123
124#[cfg(feature = "server")]
126pub(crate) async fn load_runner_settings(
127 client: &tokio_postgres::Client,
128) -> Result<RunnerSettings, AppError> {
129 use crate::models::settings as m;
130
131 async fn read_key(
132 client: &tokio_postgres::Client,
133 key: &str,
134 ) -> Result<Option<String>, AppError> {
135 let row = client
136 .query_opt("SELECT value FROM settings WHERE key = $1", &[&key])
137 .await
138 .map_err(AppError::query)?;
139 Ok(row.map(|r| r.get::<_, String>("value")))
140 }
141
142 let allow_network = read_key(client, "runner_allow_network")
143 .await?
144 .and_then(|v| v.parse().ok())
145 .unwrap_or(m::DEFAULT_RUNNER_ALLOW_NETWORK);
146 let max_concurrent = read_key(client, "runner_max_concurrent")
147 .await?
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 = read_key(client, "runner_max_cpu_cores")
152 .await?
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 = read_key(client, "runner_max_memory_mb")
157 .await?
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 = read_key(client, "runner_max_timeout_secs")
162 .await?
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 = read_key(client, "runner_max_output_bytes")
167 .await?
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 = read_key(client, "runner_max_source_bytes")
172 .await?
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 = read_key(client, "runner_queue_timeout_secs")
177 .await?
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 = read_key(client, "runner_task_ttl_secs")
182 .await?
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 = read_key(client, "runner_languages")
187 .await?
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, "/api")]
206pub async fn get_runner_settings() -> Result<RunnerSettings, ServerFnError> {
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(ServerFnError::from)?;
215 Ok(s)
216 }
217
218 #[cfg(not(feature = "server"))]
219 {
220 Ok(RunnerSettings::default())
221 }
222}
223
224#[server(UpdateRunnerSettings, "/api")]
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, ServerFnError> {
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 for (key, value) in [
258 ("runner_allow_network", allow_network.to_string()),
259 ("runner_max_concurrent", max_concurrent.to_string()),
260 ("runner_max_cpu_cores", max_cpu_cores.to_string()),
261 ("runner_max_memory_mb", max_memory_mb.to_string()),
262 ("runner_max_timeout_secs", max_timeout_secs.to_string()),
263 ("runner_max_output_bytes", max_output_bytes.to_string()),
264 ("runner_max_source_bytes", max_source_bytes.to_string()),
265 ("runner_queue_timeout_secs", queue_timeout_secs.to_string()),
266 ("runner_task_ttl_secs", task_ttl_secs.to_string()),
267 ("runner_languages", lang_str),
268 ] {
269 client
270 .execute(
271 "INSERT INTO settings (key, value, updated_at) VALUES ($1, $2, NOW())
272 ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()",
273 &[&key, &value],
274 )
275 .await
276 .map_err(AppError::query)?;
277 }
278
279 tracing::info!(
280 "Runner settings updated (需重启生效): allow_network={}, max_concurrent={}, \
281 cpu={}, mem={}MB, timeout={}s, languages={:?}",
282 allow_network,
283 max_concurrent,
284 max_cpu_cores,
285 max_memory_mb,
286 max_timeout_secs,
287 languages
288 );
289 }
290
291 Ok(RunnerSettings {
292 allow_network,
293 max_concurrent,
294 max_cpu_cores,
295 max_memory_mb,
296 max_timeout_secs,
297 max_output_bytes,
298 max_source_bytes,
299 queue_timeout_secs,
300 task_ttl_secs,
301 languages,
302 })
303}