feat(admin): add dedicated code-runner sandbox page at /admin/runner
不改动 852 行的 write.rs 编辑器布局,改为独立 admin 页面(与 posts/system 同款):
- runner.rs: Runner 页面(语言切换 join 按钮 + overrides JSON 输入 + CodeRunner 沙箱)
切换语言自动填充示例源码;overrides JSON 实时校验,畸形时提示并忽略
- mod.rs: 注册 runner 模块并导出 Runner
- router.rs: 新增 #[route("/runner")] Runner {} 嵌套在 /admin 下
- admin_layout.rs: 导航新增「试运行」入口
This commit is contained in:
parent
0e8b5fd7b7
commit
bbd4553467
@ -46,6 +46,7 @@ pub fn AdminLayout() -> Element {
|
||||
(Route::Write {}, "写文章"),
|
||||
(Route::Posts {}, "管理文章"),
|
||||
(Route::Trash {}, "回收站"),
|
||||
(Route::Runner {}, "试运行"),
|
||||
(Route::System {}, "系统"),
|
||||
];
|
||||
|
||||
|
||||
@ -8,6 +8,8 @@ pub mod comments;
|
||||
pub mod dashboard;
|
||||
/// 文章管理列表页面模块。
|
||||
pub mod posts;
|
||||
/// 代码试运行沙箱页面模块。
|
||||
pub mod runner;
|
||||
/// 系统管理页面模块(数据库 + 服务器状态 + SQL 控制台 + 导出 + 备份)。
|
||||
pub mod system;
|
||||
/// 回收站管理页面模块。
|
||||
@ -21,6 +23,8 @@ pub use comments::{AdminComments, AdminCommentsPage};
|
||||
pub use dashboard::Admin;
|
||||
/// 文章管理列表组件(带默认分页)。
|
||||
pub use posts::{Posts, PostsPage};
|
||||
/// 代码试运行沙箱组件。
|
||||
pub use runner::Runner;
|
||||
/// 系统管理入口组件。
|
||||
pub use system::System;
|
||||
/// 回收站管理组件(带默认分页)。
|
||||
|
||||
112
src/pages/admin/runner.rs
Normal file
112
src/pages/admin/runner.rs
Normal file
@ -0,0 +1,112 @@
|
||||
//! 管理后台「代码试运行」页面。
|
||||
//!
|
||||
//! 作者在写作时可在此沙箱快速试运行代码(验证围栏 ` ```lang runnable ` 的预期输出),
|
||||
//! 而无需进入文章渲染后才能运行。沙箱使用与读者相同的 StartExec / GetExecResult
|
||||
//! 接口,受同一套资源钳制与速率限制约束。
|
||||
//!
|
||||
//! 仅 WASM 前端交互;语言在受支持集合内切换。
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::components::code_runner::CodeRunner;
|
||||
use crate::components::ui::ADMIN_CARD_CLASS;
|
||||
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());
|
||||
let mut override_error = use_signal(|| String::new());
|
||||
|
||||
let current_source = source();
|
||||
let current_lang = lang();
|
||||
let overrides = match serde_json::from_str::<ResourceLimits>(overrides_json().trim()) {
|
||||
Ok(o) => {
|
||||
// 解析成功时清掉错误。
|
||||
override_error.set(String::new());
|
||||
Some(o)
|
||||
}
|
||||
Err(_) => {
|
||||
// 空字符串视为 None(不报错);非空且畸形才提示。
|
||||
if overrides_json().trim().is_empty() {
|
||||
override_error.set(String::new());
|
||||
None
|
||||
} else {
|
||||
override_error.set("overrides JSON 格式错误,已忽略".to_string());
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
rsx! {
|
||||
div { class: "flex flex-col gap-6 max-w-4xl mx-auto w-full",
|
||||
div { class: "flex flex-col gap-2",
|
||||
h1 { class: "text-3xl font-extrabold tracking-tight", "代码试运行沙箱" }
|
||||
p { class: "text-base text-base-content/60",
|
||||
"在此快速试运行代码,验证文章中可运行代码块的预期输出。受同一套资源钳制与速率限制约束。"
|
||||
}
|
||||
}
|
||||
|
||||
div { class: "{ADMIN_CARD_CLASS} flex flex-col gap-3",
|
||||
div { class: "flex flex-wrap items-center gap-3",
|
||||
label { class: "text-sm font-semibold text-base-content/70", "语言" }
|
||||
div { class: "join",
|
||||
for l in SUPPORTED_LANGS {
|
||||
button {
|
||||
key: "{l}",
|
||||
class: (if lang() == *l { "btn btn-sm join-item btn-primary" } else { "btn btn-sm join-item btn-ghost" }).to_string(),
|
||||
onclick: {
|
||||
let ll = (*l).to_string();
|
||||
move |_| {
|
||||
if ll != lang() {
|
||||
lang.set(ll.clone());
|
||||
source.set(default_source(&ll));
|
||||
}
|
||||
}
|
||||
},
|
||||
"{l}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div { class: "flex flex-col gap-1",
|
||||
label { class: "text-sm font-semibold text-base-content/70",
|
||||
"资源覆盖 (JSON, 可选)"
|
||||
}
|
||||
input {
|
||||
class: "input input-bordered input-sm font-mono w-full",
|
||||
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() {
|
||||
span { class: "text-xs text-error", "{override_error()}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CodeRunner {
|
||||
source: current_source,
|
||||
language: current_lang,
|
||||
overrides: overrides,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -12,8 +12,8 @@ use crate::components::frontend_layout::FrontendLayout;
|
||||
use crate::context::UserContext;
|
||||
use crate::pages::about::About;
|
||||
use crate::pages::admin::{
|
||||
Admin, AdminComments, AdminCommentsPage, Posts, PostsPage, System, Trash, TrashPage, Write,
|
||||
WriteEdit,
|
||||
Admin, AdminComments, AdminCommentsPage, Posts, PostsPage, Runner, System, Trash, TrashPage,
|
||||
Write, WriteEdit,
|
||||
};
|
||||
use crate::pages::archives::Archives;
|
||||
use crate::pages::home::{Home, HomePage};
|
||||
@ -94,6 +94,9 @@ pub enum Route {
|
||||
/// 系统管理(数据库 + 服务器状态 + SQL 控制台 + 导出 + 备份)
|
||||
#[route("/system")]
|
||||
System {},
|
||||
/// 代码试运行沙箱(作者预览可运行代码块输出)
|
||||
#[route("/runner")]
|
||||
Runner {},
|
||||
#[end_layout]
|
||||
#[end_nest]
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user