feat(gotify): Phase 3 C2 — UI 接线 + Connection status + 真实数据
- src/config.rs: AppConfig::placeholder() 硬编码 NAS 地址 + 假 token (§20 token 永不日志;Phase 5 接持久化前占位) - src/models.rs: gotify_messages_to_items 把 GotifyMessage 列表转 Slint MessageItem[];try_fetch_real_messages 一次完成 health → applications → messages(50);失败 None 让调用方 fallback mock - src/gotify/mod.rs: re-export ServerConfig - src/lib.rs: run() 启动时调 try_fetch_real_messages,成功用真实数 据 + Connected + 服务端 URL,失败 fallback mock + Disconnected; on_test_connection_requested 重新跑同一路径 - Cargo.toml: 加 chrono 0.4 (RFC3339 解析) - ui/app.slint: is-connected (绿/灰 dot) + status-text (顶部副标) - ui/settings-page.slint: Connection section 加 Test Connection 按钮 (in-progress 时禁用显示 "Testing...") 避坑: - Gotify RFC3339 date 解析失败 silent fallback 到 unix=0(显示 "just now" 而不是崩) - Gotify priority 0..10 → Slint Priority 4 桶分桶:<=0 Low / <=3 Normal / <=7 High / else Max - ServerConfig 在 crate::gotify::model 子模块,mod.rs 必须 re-export 验证:cargo build / build --release 都 exit 0,0 warning - cargo test --test gotify_api: 6 passed - debug 33.36 MB, subsystem = WINDOWS_CUI - release 9.43 MB, subsystem = WINDOWS_GUI(chrono +1.3 MB)
This commit is contained in:
+64
-13
@@ -1,12 +1,15 @@
|
||||
//! Windows Gotify Client (Rust) — core library.
|
||||
//!
|
||||
//! Phase 2 surface:
|
||||
//! Phase 3 surface:
|
||||
//! - [`config`]: placeholder AppConfig (Phase 5 swaps for persistence)
|
||||
//! - [`error`]: AppError / AppResult
|
||||
//! - [`gotify`]: blocking REST client (health / applications / messages)
|
||||
//! - [`logging`]: tracing-subscriber init
|
||||
//! - [`models`]: Mock message feed (Phase 3 replaces with live Gotify data)
|
||||
//! - [`models`]: mock feed + Gotify→Slint adapter
|
||||
//! - [`storage`]: `%APPDATA%/WindowsGotify/state.json` (Phase 2 C3)
|
||||
//! - [`run`]: builds the MainWindow and runs its event loop
|
||||
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod gotify;
|
||||
pub mod logging;
|
||||
@@ -32,7 +35,7 @@ pub fn run() -> error::AppResult<()> {
|
||||
|
||||
let ui = MainWindow::new().map_err(|e| error::AppError::Ui(e.to_string()))?;
|
||||
|
||||
// Phase 2 C3: load persisted state and apply it before `run()`.
|
||||
// ── Load persisted window geometry (Phase 2 C3) ───────────────
|
||||
let initial_state = storage::load_state();
|
||||
tracing::info!(
|
||||
width = initial_state.last_window_width,
|
||||
@@ -45,7 +48,30 @@ pub fn run() -> error::AppResult<()> {
|
||||
initial_state.last_window_height.max(0) as f32,
|
||||
)));
|
||||
|
||||
// Phase 2 C2 callbacks.
|
||||
// ── Phase 3 C2: try the real Gotify server, fall back to mock ──
|
||||
let cfg = config::AppConfig::placeholder();
|
||||
let (messages_model, status_text, is_connected) = match cfg.server.as_ref() {
|
||||
Some(server) => match models::try_fetch_real_messages(&server.base_url, &server.token) {
|
||||
Some((model, text)) => {
|
||||
tracing::info!(server = %server.base_url, "connected to real Gotify");
|
||||
(model, text, true)
|
||||
}
|
||||
None => {
|
||||
tracing::warn!(server = %server.base_url, "real Gotify unreachable; using mock data");
|
||||
let text = format!("Disconnected · mock feed · {}", server.base_url);
|
||||
(models::mock_messages_model(), text, false)
|
||||
}
|
||||
},
|
||||
None => {
|
||||
tracing::info!("no server configured; using mock data");
|
||||
(models::mock_messages_model(), "No server configured · mock feed".to_string(), false)
|
||||
}
|
||||
};
|
||||
ui.set_messages(messages_model);
|
||||
ui.set_status_text(status_text.into());
|
||||
ui.set_is_connected(is_connected);
|
||||
|
||||
// ── Phase 2 C2 callbacks ───────────────────────────────────────
|
||||
ui.on_test_notification_clicked(|| {
|
||||
tracing::info!("empty-state: Test Notification clicked (stub)");
|
||||
});
|
||||
@@ -53,7 +79,39 @@ pub fn run() -> error::AppResult<()> {
|
||||
tracing::debug!(idx, "message card clicked");
|
||||
});
|
||||
|
||||
// Phase 2 C3 callbacks.
|
||||
// ── Phase 3 C2: Settings → Connection → Test button ───────────
|
||||
ui.on_test_connection_requested({
|
||||
let ui_handle = ui.as_weak();
|
||||
move || {
|
||||
// Re-run the same fetch path; UI already shows "Disconnected"
|
||||
// so this is the only way the user can poke the server.
|
||||
tracing::info!("settings: Test Connection clicked");
|
||||
let Some(server) = config::AppConfig::placeholder().server else {
|
||||
return;
|
||||
};
|
||||
match models::try_fetch_real_messages(&server.base_url, &server.token) {
|
||||
Some((model, text)) => {
|
||||
if let Some(ui) = ui_handle.upgrade() {
|
||||
ui.set_messages(model);
|
||||
ui.set_status_text(text.into());
|
||||
ui.set_is_connected(true);
|
||||
}
|
||||
tracing::info!("Test Connection: ok");
|
||||
}
|
||||
None => {
|
||||
if let Some(ui) = ui_handle.upgrade() {
|
||||
ui.set_status_text(
|
||||
format!("Disconnected · mock feed · {}", server.base_url).into(),
|
||||
);
|
||||
ui.set_is_connected(false);
|
||||
}
|
||||
tracing::warn!("Test Connection: failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ── Phase 2 C3 callbacks ───────────────────────────────────────
|
||||
let state_for_save: Rc<RefCell<AppState>> = Rc::new(RefCell::new(initial_state));
|
||||
ui.on_about_clicked({
|
||||
let ui_handle = ui.as_weak();
|
||||
@@ -65,17 +123,11 @@ pub fn run() -> error::AppResult<()> {
|
||||
}
|
||||
});
|
||||
|
||||
// Phase 2 C2 message feed (mock).
|
||||
ui.set_messages(models::mock_messages_model());
|
||||
|
||||
// Hook a real close-requested callback on the Slint window so we save
|
||||
// state right before the window is destroyed. Slint 1.18's `Window` has
|
||||
// a `close-requested` callback that fires on OS-close.
|
||||
// ── Phase 2 C3: save state on OS-close ────────────────────────
|
||||
ui.window().on_close_requested({
|
||||
let state_for_save = state_for_save.clone();
|
||||
let ui_handle = ui.as_weak();
|
||||
move || {
|
||||
// Capture final size from the live window before saving.
|
||||
if let Some(ui) = ui_handle.upgrade() {
|
||||
let size = ui.window().size();
|
||||
let mut state = state_for_save.borrow_mut();
|
||||
@@ -91,7 +143,6 @@ pub fn run() -> error::AppResult<()> {
|
||||
"saved state.json on close"
|
||||
);
|
||||
}
|
||||
// Allow the window to hide (default CloseRequestResponse behaviour).
|
||||
slint::CloseRequestResponse::HideWindow
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user