Skip to main content

yggdrasil/pages/admin/settings/
system_section.rs

1//! 启动配置分区(只读):进程启动前就要用到、不能运行时修改的配置。
2//!
3//! 数据库连接、日志级别、Docker 等值保留在环境变量里,这里只展示;密钥类只显示
4//! 是否已设置。
5
6use leptos::prelude::*;
7use leptos::task::spawn_local;
8
9use super::kit::SettingsCard;
10use super::SettingsSection;
11use crate::api::settings::get_system_info;
12use crate::models::settings::SystemInfo;
13
14/// 一行只读配置:名称、环境变量、当前值与说明。
15fn rows(i: &SystemInfo) -> Vec<(&'static str, &'static str, String, &'static str)> {
16    let on_off = |b: bool| if b { "开启" } else { "关闭" }.to_string();
17    vec![
18        (
19            "数据库连接",
20            "DATABASE_URL",
21            i.database_url_masked.clone(),
22            "启动时必需",
23        ),
24        ("日志级别", "RUST_LOG", i.rust_log.clone(), "tracing 过滤器"),
25        (
26            "连接池大小",
27            "DB_POOL_SIZE",
28            i.db_pool_size.to_string(),
29            "deadpool 连接池",
30        ),
31        (
32            "查询超时",
33            "STATEMENT_TIMEOUT_SECS",
34            format!("{} 秒", i.statement_timeout_secs),
35            "写进连接池选项",
36        ),
37        (
38            "SSR 缓存时长",
39            "SSR_CACHE_SECS",
40            format!("{} 秒", i.ssr_cache_secs),
41            "增量渲染缓存",
42        ),
43        (
44            "响应压缩",
45            "COMPRESSION_ALGORITHMS",
46            if i.compression_algorithms.is_empty() {
47                "关闭".into()
48            } else {
49                i.compression_algorithms.clone()
50            },
51            "压缩层启动时构建",
52        ),
53        (
54            "版本响应头",
55            "EXPOSE_VERSION_HEADERS",
56            on_off(i.expose_version_headers),
57            "中间件启动时挂载",
58        ),
59        (
60            "Docker Socket",
61            "DOCKER_SOCKET_PATH",
62            i.docker_socket_path.clone(),
63            "代码运行器连接 Docker",
64        ),
65        (
66            "MCP 加密主密钥",
67            "MCP_TOKEN_ENC_KEY",
68            if i.mcp_token_enc_key_set {
69                "已设置".into()
70            } else {
71                "未设置".into()
72            },
73            "轮换会使旧令牌无法解密",
74        ),
75        (
76            "启动迁移超时",
77            "MIGRATE_STARTUP_TIMEOUT_SECS",
78            format!("{} 秒", i.migrate_startup_timeout_secs),
79            "只在启动期间生效",
80        ),
81        (
82            "系统采样间隔",
83            "SYSINFO_SAMPLE_SECS",
84            format!("{} 秒", i.sysinfo_sample_secs),
85            "采样循环启动时读取",
86        ),
87        (
88            "日志查看器级别",
89            "LOG_VIEWER_LEVEL",
90            i.log_viewer_level.clone(),
91            "/admin/logs 入库级别,独立于 RUST_LOG",
92        ),
93        (
94            "备份导入上限",
95            "BACKUP_IMPORT_MAX_MB",
96            format!("{} MB", i.backup_import_max_mb),
97            "反代 body 上限需同步放大",
98        ),
99        (
100            "旧发布资源目录",
101            "YGG_PREVIOUS_PUBLIC_DIR",
102            if i.previous_public_dir.is_empty() {
103                "未设置".into()
104            } else {
105                i.previous_public_dir.clone()
106            },
107            "滚动发布时保留旧 hash 资源",
108        ),
109    ]
110}
111
112/// 启动配置分区组件。
113#[component]
114pub fn SystemSection() -> impl IntoView {
115    let info = RwSignal::new(None::<SystemInfo>);
116    let failed = RwSignal::new(false);
117    // Effect 只在浏览器里运行:服务端渲染不请求。
118    Effect::new(move |loaded: Option<()>| {
119        if loaded.is_some() {
120            return;
121        }
122        spawn_local(async move {
123            match get_system_info().await {
124                Ok(i) => info.set(Some(i)),
125                Err(_) => failed.set(true),
126            }
127        });
128    });
129    let ready = Signal::derive(move || info.with(|i| i.is_some()));
130    view! {
131        <SettingsCard
132            section=SettingsSection::System
133            description="进程启动时读取的环境变量,这里只展示;修改后需重启服务。"
134            ready=ready
135            load_failed=failed
136        >
137            {move || {
138                info.get()
139                    .map(|i| {
140                        view! {
141                            <dl class="settings-readonly">
142                                {rows(&i)
143                                    .into_iter()
144                                    .map(|(label, env, value, hint)| {
145                                        view! {
146                                            <div class="settings-readonly-row">
147                                                <dt>
148                                                    <span>{label}</span>
149                                                    <code>{env}</code>
150                                                </dt>
151                                                <dd>
152                                                    <span class="settings-mono">{value}</span>
153                                                    <small>{hint}</small>
154                                                </dd>
155                                            </div>
156                                        }
157                                    })
158                                    .collect_view()}
159                            </dl>
160                        }
161                    })
162            }}
163        </SettingsCard>
164    }
165}