Skip to content

Commit 7eb612e

Browse files
authored
Merge pull request ZR233#13 from ZR233/dev-web
Dev web
2 parents 88aecf8 + 3ba8976 commit 7eb612e

File tree

13 files changed

+1201
-36
lines changed

13 files changed

+1201
-36
lines changed

Cargo.lock

Lines changed: 478 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,24 @@
11
[workspace]
2-
members = ["ostool", "uboot-shell", "jkconfig"]
32
exclude = ["assets/*"]
3+
members = ["ostool", "uboot-shell", "jkconfig"]
44
resolver = "3"
5+
6+
[workspace.dependencies]
7+
# Cursive TUI framework
8+
cursive = {version = "0.21", features = ["crossterm-backend"]}
9+
10+
# Data processing
11+
anyhow = "1"
12+
clap = {version = "4", features = ["derive"]}
13+
serde = {version = "1", features = ["derive"]}
14+
serde_json = "1"
15+
16+
toml = "0.9"
17+
18+
# Error handling
19+
thiserror = "2"
20+
21+
# Logging
22+
log = "0.4"
23+
24+
schemars = {version = "1.0.4", features = ["derive"]}

jkconfig/Cargo.toml

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,43 @@ keywords = ["tui", "config", "json-schema", "terminal"]
77
license = "MIT OR Apache-2.0"
88
name = "jkconfig"
99
repository = "https://github.com/ZR233/ostool"
10-
version = "0.1.1"
10+
version = "0.1.2"
11+
12+
[[bin]]
13+
name = "jkconfig"
14+
path = "src/main.rs"
15+
required-features = ["web"]
1116

1217
[dependencies]
1318
# Cursive TUI framework
14-
cursive = {version = "0.21", features = ["crossterm-backend"]}
19+
cursive = {workspace = true, features = ["crossterm-backend"]}
1520

1621
# Data processing
17-
anyhow = "1.0"
18-
clap = {version = "4.0", features = ["derive"]}
19-
serde = {version = "1.0", features = ["derive"]}
20-
serde_json = "1.0"
22+
anyhow = {workspace = true}
23+
clap = {workspace = true, features = ["derive"]}
24+
serde = {workspace = true, features = ["derive"]}
25+
serde_json = {workspace = true}
2126

22-
toml = "0.9"
27+
toml = {workspace = true}
2328

2429
# Error handling
25-
thiserror = "2.0"
30+
thiserror = {workspace = true}
2631

2732
# Logging
28-
log = "0.4"
33+
log = {workspace = true}
34+
35+
# Web server dependencies (optional)
36+
axum = {version = "0.8", optional = true}
37+
chrono = {version = "0.4", optional = true}
38+
tokio = {version = "1.0", features = ["full"], optional = true}
39+
tower = {version = "0.5", optional = true}
40+
tower-http = {version = "0.5", features = ["fs", "cors"], optional = true}
41+
42+
[features]
43+
default = ["web"]
44+
web = ["axum", "tokio", "tower", "tower-http", "chrono"]
2945

3046
[dev-dependencies]
3147
env_logger = "0.11"
32-
schemars = {version = "1.0.4", features = ["derive"]}
33-
serde = {version = "1.0", features = ["derive"]}
48+
schemars = {workspace=true, features = ["derive"]}
3449
tokio-test = "0.4"
35-
toml = "0.9"

jkconfig/src/data/app_data.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ use anyhow::bail;
88

99
use crate::data::{menu::MenuRoot, types::ElementType};
1010

11+
#[derive(Clone)]
1112
pub struct AppData {
1213
pub root: MenuRoot,
1314
pub current_key: Vec<String>,
1415
pub needs_save: bool,
1516
pub config: PathBuf,
1617
}
1718

18-
const DEFAULT_CONFIG_PATH: &str = ".project.toml";
19+
const DEFAULT_CONFIG_PATH: &str = ".config.toml";
1920

2021
fn default_schema_by_init(config: &Path) -> PathBuf {
2122
let binding = config.file_name().unwrap().to_string_lossy();

jkconfig/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,8 @@ pub mod data;
55
// UI模块暂时注释掉,使用主程序中的 MenuView
66
pub mod ui;
77

8+
// Web服务器模块(需要web feature)
9+
#[cfg(feature = "web")]
10+
pub mod web;
11+
812
pub use serde_json::Value;

jkconfig/src/main.rs

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
extern crate log;
22

3-
use clap::{Arg, Command};
3+
use clap::{Parser, Subcommand};
44
use cursive::{Cursive, CursiveExt, event::Key, views::Dialog};
5+
use std::path::PathBuf;
56

67
use jkconfig::{
78
data::AppData,
@@ -11,35 +12,69 @@ use jkconfig::{
1112
// mod menu_view;
1213
// use menu_view::MenuView;
1314

15+
/// 命令行参数结构体
16+
#[derive(Parser)]
17+
#[command(name = "jkconfig")]
18+
#[command(author = "周睿 <[email protected]>")]
19+
#[command(about = "配置编辑器", long_about = None)]
20+
struct Cli {
21+
/// config file path
22+
#[arg(short = 'c', long = "config", default_value = ".config.toml")]
23+
config: PathBuf,
24+
25+
/// schema file path, default is config file name with '-schema.json' suffix
26+
#[arg(short = 's', long = "schema")]
27+
schema: Option<PathBuf>,
28+
29+
/// 子命令
30+
#[command(subcommand)]
31+
command: Option<Commands>,
32+
}
33+
34+
/// 子命令枚举
35+
#[derive(Subcommand)]
36+
enum Commands {
37+
/// TUI (default)
38+
Tui,
39+
/// Web UI mode
40+
Web {
41+
/// server port
42+
#[arg(short = 'p', long = "port", default_value = "3000")]
43+
port: u16,
44+
},
45+
}
46+
1447
/// 主函数
1548
fn main() -> anyhow::Result<()> {
1649
// 解析命令行参数
17-
let matches = Command::new("jkconfig")
18-
.author("周睿 <[email protected]>")
19-
.arg(
20-
Arg::new("config")
21-
.long("config")
22-
.short('c')
23-
.value_name("FILE")
24-
.help("指定初始配置文件路径")
25-
.default_value(".project.toml"),
26-
)
27-
.arg(
28-
Arg::new("schema")
29-
.long("schema")
30-
.short('s')
31-
.value_name("FILE")
32-
.help("指定schema文件路径(默认基于配置文件名推导)"),
33-
)
34-
.get_matches();
50+
let cli = Cli::parse();
3551

3652
// 提取命令行参数
37-
let config_file = matches.get_one::<String>("config").map(|s| s.as_str());
38-
let schema_file = matches.get_one::<String>("schema").map(|s| s.as_str());
53+
let config_path = cli.config.to_string_lossy().to_string();
54+
let schema_path = cli.schema.as_ref().map(|p| p.to_string_lossy().to_string());
55+
56+
let config_file = Some(config_path.as_str());
57+
let schema_file = schema_path.as_deref();
3958

4059
// 初始化AppData
4160
let app_data = AppData::new(config_file, schema_file)?;
4261

62+
// 根据子命令决定运行模式
63+
match cli.command {
64+
Some(Commands::Web { port }) => {
65+
tokio::runtime::Runtime::new()?.block_on(jkconfig::web::run_server(app_data, port))?;
66+
}
67+
Some(Commands::Tui) | None => {
68+
// 运行TUI界面(默认行为)
69+
run_tui(app_data)?;
70+
}
71+
}
72+
73+
Ok(())
74+
}
75+
76+
/// 运行TUI界面
77+
fn run_tui(app_data: AppData) -> anyhow::Result<()> {
4378
let title = app_data.root.title.clone();
4479
let fields = app_data.root.menu().fields();
4580

jkconfig/src/web/handlers.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//! HTTP请求处理器
2+
//!
3+
//! 处理各种HTTP请求的函数
4+
5+
use axum::{
6+
extract::State,
7+
response::{Html, Json},
8+
};
9+
use serde_json::json;
10+
11+
use super::server::AppState;
12+
13+
/// 根路径处理器 - 返回Hello World页面
14+
pub async fn root_handler() -> Html<&'static str> {
15+
Html(include_str!("../../web/static/index.html"))
16+
}
17+
18+
/// API处理器 - 返回配置信息
19+
pub async fn api_config_handler(State(state): State<AppState>) -> Json<serde_json::Value> {
20+
Json(json!({
21+
"title": state.app_data.root.title,
22+
"message": "jkconfig Web API",
23+
"version": "0.1.1"
24+
}))
25+
}
26+
27+
/// 健康检查处理器
28+
pub async fn health_check() -> Json<serde_json::Value> {
29+
Json(json!({
30+
"status": "ok",
31+
"timestamp": chrono::Utc::now().to_rfc3339()
32+
}))
33+
}
34+
35+
/// 静态文件处理器
36+
pub async fn static_handler() -> Html<&'static str> {
37+
Html(include_str!("../../web/static/index.html"))
38+
}

jkconfig/src/web/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//! Web服务器模块
2+
//!
3+
//! 提供基于axum的Web服务器功能,用于替代TUI界面提供配置编辑功能
4+
5+
#[cfg(feature = "web")]
6+
pub mod handlers;
7+
#[cfg(feature = "web")]
8+
pub mod routes;
9+
#[cfg(feature = "web")]
10+
pub mod server;
11+
12+
// 重新导出server模块的run_server函数
13+
#[cfg(feature = "web")]
14+
pub use server::run_server;

jkconfig/src/web/routes.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//! 路由配置
2+
//!
3+
//! 定义和配置所有HTTP路由
4+
5+
use axum::{Router, routing::get};
6+
use tower_http::services::ServeDir;
7+
8+
use super::{
9+
handlers::{api_config_handler, health_check, root_handler, static_handler},
10+
server::AppState,
11+
};
12+
13+
/// 创建应用路由
14+
pub fn create_routes(state: AppState) -> Router {
15+
Router::new()
16+
// 主页面路由
17+
.route("/", get(root_handler))
18+
// API路由
19+
.route("/api/config", get(api_config_handler))
20+
.route("/api/health", get(health_check))
21+
// 静态文件服务
22+
.nest_service("/static", ServeDir::new("web/static"))
23+
// 备用路由 - 处理SPA路由
24+
.fallback(get(static_handler))
25+
// 注入应用状态
26+
.with_state(state)
27+
}

jkconfig/src/web/server.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//! Web服务器核心实现
2+
//!
3+
//! 负责启动和配置axum Web服务器
4+
5+
use std::net::SocketAddr;
6+
7+
use super::routes::create_routes;
8+
use crate::data::AppData;
9+
10+
/// 运行Web服务器
11+
pub async fn run_server(app_data: AppData, port: u16) -> anyhow::Result<()> {
12+
// 创建应用状态
13+
let state = AppState { app_data };
14+
15+
// 创建路由
16+
let app = create_routes(state);
17+
18+
// 绑定地址
19+
let addr = SocketAddr::from(([0, 0, 0, 0], port));
20+
21+
println!("🚀 Web服务器启动成功!");
22+
println!("📍 访问地址: http://localhost:{}", port);
23+
println!("⏹️ 按 Ctrl+C 停止服务器");
24+
25+
// 启动服务器
26+
let listener = tokio::net::TcpListener::bind(addr).await?;
27+
axum::serve(listener, app).await?;
28+
29+
Ok(())
30+
}
31+
32+
/// 应用状态
33+
#[derive(Clone)]
34+
pub struct AppState {
35+
pub app_data: AppData,
36+
}

0 commit comments

Comments
 (0)