Skip to main content

yggdrasil/pages/admin/settings/
runner_section.rs

1//! 代码运行器分区:联网开关、并发与每任务资源上限、语言白名单。
2//!
3//! 写入数据库,下次启动生效(RUNNER_CONFIG 在首次访问时从启动配置构建)。
4
5use leptos::prelude::*;
6
7use super::kit::{use_section, SettingRow, SettingsCard, SwitchRow};
8use super::SettingsSection;
9use crate::api::error::ApiError;
10use crate::api::settings::{get_runner_settings, update_runner_settings};
11use crate::components::forms::{FormInput, NumberInput};
12use crate::models::settings::RunnerSettings;
13
14async fn save(d: RunnerSettings) -> Result<RunnerSettings, ApiError> {
15    update_runner_settings(
16        d.allow_network,
17        d.max_concurrent,
18        d.max_cpu_cores,
19        d.max_memory_mb,
20        d.max_timeout_secs,
21        d.max_output_bytes,
22        d.max_source_bytes,
23        d.queue_timeout_secs,
24        d.task_ttl_secs,
25        d.languages,
26    )
27    .await
28}
29
30/// 代码运行器分区组件。
31#[component]
32pub fn RunnerSection() -> impl IntoView {
33    let st = use_section(SettingsSection::Runner, get_runner_settings, save);
34    view! {
35        <SettingsCard
36            section=SettingsSection::Runner
37            description="文章里可运行代码块的沙箱资源限制。"
38            ready=st.ready()
39            load_failed=st.load_failed()
40            failed=st.failed()
41        >
42            <SwitchRow
43                label="允许容器联网"
44                hint="开启后读者运行的代码可以访问网络,有安全风险。"
45                checked=st.field(|d| d.allow_network)
46                ontoggle=move |()| st.edit(|d| d.allow_network = !d.allow_network)
47            />
48            <SettingRow label="最大并发任务" html_for="runner-concurrent">
49                <NumberInput
50                    id="runner-concurrent"
51                    value=st.field(|d| i64::from(d.max_concurrent))
52                    min=1
53                    max=64
54                    unit="个"
55                    onchange=move |v: i64| st.edit(|d| d.max_concurrent = v as u32)
56                />
57            </SettingRow>
58            <SettingRow label="CPU" html_for="runner-cpu" hint="每个任务可用的 CPU 核数,可以是小数。">
59                <div class="settings-inline">
60                    <FormInput
61                        id="runner-cpu"
62                        class="field settings-number"
63                        r#type="number"
64                        min="0.1"
65                        step="0.1"
66                        value=st.field(|d| format!("{:.1}", d.max_cpu_cores))
67                        oninput=move |v: String| {
68                            if let Ok(v) = v.parse::<f64>() {
69                                st.edit(|d| d.max_cpu_cores = v);
70                            }
71                        }
72                    />
73                    <span class="settings-unit">"核"</span>
74                </div>
75            </SettingRow>
76            <SettingRow label="内存" html_for="runner-mem">
77                <NumberInput
78                    id="runner-mem"
79                    value=st.field(|d| i64::from(d.max_memory_mb))
80                    min=16
81                    max=65_536
82                    step=64
83                    unit="MB"
84                    onchange=move |v: i64| st.edit(|d| d.max_memory_mb = v as u32)
85                />
86            </SettingRow>
87            <SettingRow label="执行超时" html_for="runner-timeout">
88                <NumberInput
89                    id="runner-timeout"
90                    value=st.field(|d| i64::from(d.max_timeout_secs))
91                    min=1
92                    max=600
93                    unit="秒"
94                    onchange=move |v: i64| st.edit(|d| d.max_timeout_secs = v as u32)
95                />
96            </SettingRow>
97            <SettingRow label="排队超时" html_for="runner-queue">
98                <NumberInput
99                    id="runner-queue"
100                    value=st.field(|d| i64::from(d.queue_timeout_secs))
101                    min=1
102                    max=600
103                    unit="秒"
104                    onchange=move |v: i64| st.edit(|d| d.queue_timeout_secs = v as u32)
105                />
106            </SettingRow>
107            <SettingRow label="输出上限" html_for="runner-output">
108                <NumberInput
109                    id="runner-output"
110                    value=st.field(|d| (d.max_output_bytes / 1024) as i64)
111                    min=1
112                    max=65_536
113                    unit="KB"
114                    onchange=move |v: i64| st.edit(|d| d.max_output_bytes = v as u64 * 1024)
115                />
116            </SettingRow>
117            <SettingRow label="源码上限" html_for="runner-source">
118                <NumberInput
119                    id="runner-source"
120                    value=st.field(|d| (d.max_source_bytes / 1024) as i64)
121                    min=1
122                    max=65_536
123                    unit="KB"
124                    onchange=move |v: i64| st.edit(|d| d.max_source_bytes = v as u64 * 1024)
125                />
126            </SettingRow>
127            <SettingRow label="任务记录保留" html_for="runner-ttl">
128                <NumberInput
129                    id="runner-ttl"
130                    value=st.field(|d| i64::from(d.task_ttl_secs))
131                    min=10
132                    max=86_400
133                    step=60
134                    unit="秒"
135                    hint="运行结束后保留输出供页面读取的时长。"
136                    onchange=move |v: i64| st.edit(|d| d.task_ttl_secs = v as u32)
137                />
138            </SettingRow>
139            <SettingRow label="语言白名单" html_for="runner-langs" hint="逗号分隔,留空表示全部注册语言可用。">
140                <FormInput
141                    id="runner-langs"
142                    class="field settings-mono"
143                    placeholder="python, node, rust"
144                    value=st.field(|d| d.languages.clone().unwrap_or_default())
145                    oninput=move |v: String| st.edit(|d| d.languages = (!v.trim().is_empty()).then_some(v))
146                />
147            </SettingRow>
148        </SettingsCard>
149    }
150}