yggdrasil/src/pages/admin/runner.rs
xfy b207babdb6 refactor(admin): 统一按钮令牌,消除样式散落
将 7 个 admin 页面中复制粘贴/各自为政的按钮 class 收敛到 ui.rs 令牌:

主操作色系统一为主题绿(bg-paper-accent):
- dashboard/posts「发布文章」、write「发布/更新」+「确认」、runner 语言切换选中态
  改用 BTN_PRIMARY / BTN_PRIMARY_SM(原为深色 bg-paper-primary,现统一为绿)
- system 4 处刷新/执行/创建备份、trash 保存设置、write 发布按钮改用 LoadingButton
  统一 loading 态为 spinner 绝对居中叠加(按钮宽度不变,原 system 用文字切换、
  write 用整体变灰,现全部一致)

描边/图标/行内按钮统一:
- posts 重建、system 刷新列表 → BTN_OUTLINE
- trash 清空回收站 → BTN_DANGER_OUTLINE
- trash 步进 ± 按钮(2 处重复字符串)→ BTN_ICON
- write 关闭 × 按钮(2 处重复)→ BTN_CLOSE_ICON
- system BackupRow 恢复/删除绕过令牌 → BTN_TEXT_AMBER / BTN_TEXT_RED
  (删除色从 red-600 校正为令牌的 red-500,与其他行内删除一致)

消除约 15 处内联 class 字符串重复,system.rs 4 种主操作变体收敛为 1 种。
2026-07-06 00:17:25 +08:00

125 lines
6.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 管理后台「代码试运行」页面。
//!
//! 作者在写作时可在此沙箱快速试运行代码(验证围栏 ` ```lang runnable ` 的预期输出),
//! 而无需进入文章渲染后才能运行。沙箱使用与读者相同的 StartExec / GetExecResult
//! 接口受同一套资源钳制约束admin 跳过速率限制,见 `start_exec`)。
//!
//! 仅 WASM 前端交互;语言在受支持集合内切换。
use dioxus::prelude::*;
use crate::components::code_runner::CodeRunner;
use crate::components::ui::{ADMIN_CARD_CLASS, BTN_PRIMARY_SM};
use crate::infra::runner_config::ResourceLimits;
/// 受支持的语言集合(与 LANGUAGES 注册表 / CODE_RUNNER_LANGUAGES 对齐)。
const SUPPORTED_LANGS: &[&str] = &["python", "node"];
/// 默认示例源码(按语言)。
fn default_source(lang: &str) -> String {
match lang {
"python" => "print('Hello from author sandbox')\nfor i in range(3):\n print(f'line {i}')\n".to_string(),
"node" => "console.log('Hello from author sandbox');\n[0,1,2].forEach(i => console.log(`line ${i}`));\n".to_string(),
_ => String::new(),
}
}
/// 管理后台代码试运行页面。
#[component]
#[cfg_attr(not(target_arch = "wasm32"), allow(unused_mut))]
pub fn Runner() -> Element {
let mut lang = use_signal(|| "python".to_string());
// 语言切换时刷新示例源码(首次进入也有默认值)。
let mut source = use_signal(|| default_source("python"));
let mut overrides_json = use_signal(String::new);
// overrides 解析用 use_memo 承载render 体只读不写Dioxus render purity
// 避免 render 期间 .set() override_error。畸形 JSON 标记在 memo 返回值里。
let parsed = use_memo(move || {
let raw = overrides_json();
match serde_json::from_str::<ResourceLimits>(raw.trim()) {
Ok(o) => (Some(o), String::new()),
Err(_) => {
if raw.trim().is_empty() {
(None, String::new())
} else {
(None, "overrides JSON 格式错误,已忽略".to_string())
}
}
}
});
let (overrides, override_error) = (parsed.read().0.clone(), parsed.read().1.clone());
rsx! {
div { class: "w-full max-w-5xl mx-auto space-y-8",
// 页头:与 dashboard / posts / system 对齐h1 text-4xl + 底部分割线)
div { class: "flex flex-col md:flex-row md:items-end justify-between gap-6 pb-8 border-b border-[var(--color-paper-border)]/50",
div {
h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]", "代码试运行沙箱" }
p { class: "text-base text-[var(--color-paper-secondary)] mt-2",
"在此快速试运行代码,验证文章中可运行代码块的预期输出。资源钳制与读者侧一致,速率限制对 admin 放行。"
}
}
}
// 配置卡片:语言切换 + 资源覆盖
div { class: "{ADMIN_CARD_CLASS} p-8 flex flex-col gap-6",
// 语言切换
div { class: "flex flex-col gap-2",
label { class: "text-sm font-medium text-[var(--color-paper-secondary)]", "语言" }
div { class: "flex gap-2",
for l in SUPPORTED_LANGS {
button {
key: "{l}",
class: (if lang() == *l {
BTN_PRIMARY_SM
} else {
"px-4 py-1.5 text-sm font-medium rounded-full text-[var(--color-paper-secondary)] bg-[var(--color-paper-theme)] hover:bg-[var(--color-paper-border)] hover:text-[var(--color-paper-primary)] transition cursor-pointer"
}).to_string(),
onclick: {
let ll = (*l).to_string();
move |_| {
if ll != lang() {
lang.set(ll.clone());
source.set(default_source(&ll));
}
}
},
"{l}"
}
}
}
}
// 资源覆盖JSON
div { class: "flex flex-col gap-2",
label { class: "text-sm font-medium text-[var(--color-paper-secondary)]",
"资源覆盖 (JSON, 可选)"
}
input {
class: "w-full px-3 py-2 text-sm border border-paper-border rounded-lg bg-[var(--color-paper-theme)] text-[var(--color-paper-primary)] font-mono focus:outline-none focus:border-[var(--color-paper-accent)] transition-colors",
r#type: "text",
placeholder: "如 {{\"timeout_secs\":10,\"memory_mb\":512}}",
value: "{overrides_json()}",
oninput: move |e| overrides_json.set(e.value()),
}
if !override_error.is_empty() {
p { class: "text-xs text-red-500 dark:text-red-400", "{override_error}" }
} else {
p { class: "text-xs text-[var(--color-paper-tertiary)]",
"覆盖 cpu_cores / memory_mb / timeout_secs / output_bytes / allow_network最终仍受 CODE_RUNNER_MAX_* 钳制"
}
}
}
}
// 运行器
CodeRunner {
source: source(),
language: lang(),
overrides: overrides,
}
}
}
}