feat: user-scoped history/delete/rollback, dashboard & login UI, ignore *.pem

- Filter history/rollback/delete by user_id in secrets-core
- MCP tools/web pass user context; dashboard refresh; favicon static
- .gitignore *.pem; vscode tasks tweaks
- clippy: collapse else-if in rollback latest-history branch

Made-with: Cursor
This commit is contained in:
voson
2026-03-20 20:11:19 +08:00
parent 49fb7430a8
commit 5df4141935
14 changed files with 536 additions and 165 deletions

View File

@@ -1,8 +1,9 @@
use askama::Template;
use axum::{
Json, Router,
body::Body,
extract::{Path, Query, State},
http::StatusCode,
http::{StatusCode, header},
response::{Html, IntoResponse, Redirect, Response},
routing::{get, post},
};
@@ -33,6 +34,7 @@ const SESSION_LOGIN_PROVIDER: &str = "login_provider";
#[template(path = "login.html")]
struct LoginTemplate {
has_google: bool,
version: &'static str,
}
#[derive(Template)]
@@ -42,6 +44,7 @@ struct DashboardTemplate {
user_email: String,
has_passphrase: bool,
base_url: String,
version: &'static str,
}
// ── App state helpers ─────────────────────────────────────────────────────────
@@ -63,6 +66,11 @@ async fn current_user_id(session: &Session) -> Option<Uuid> {
pub fn web_router() -> Router<AppState> {
Router::new()
.route("/favicon.svg", get(favicon_svg))
.route(
"/favicon.ico",
get(|| async { Redirect::permanent("/favicon.svg") }),
)
.route("/", get(login_page))
.route("/auth/google", get(auth_google))
.route("/auth/google/callback", get(auth_google_callback))
@@ -80,6 +88,15 @@ pub fn web_router() -> Router<AppState> {
.route("/api/apikey/regenerate", post(api_apikey_regenerate))
}
async fn favicon_svg() -> Response {
Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "image/svg+xml")
.header(header::CACHE_CONTROL, "public, max-age=86400")
.body(Body::from(include_str!("../static/favicon.svg")))
.expect("favicon response")
}
// ── Login page ────────────────────────────────────────────────────────────────
async fn login_page(
@@ -92,6 +109,7 @@ async fn login_page(
let tmpl = LoginTemplate {
has_google: state.google_config.is_some(),
version: env!("CARGO_PKG_VERSION"),
};
render_template(tmpl)
}
@@ -272,20 +290,24 @@ async fn dashboard(
State(state): State<AppState>,
session: Session,
) -> Result<Response, StatusCode> {
let user_id = current_user_id(&session)
.await
.ok_or(StatusCode::UNAUTHORIZED)?;
let Some(user_id) = current_user_id(&session).await else {
return Ok(Redirect::to("/").into_response());
};
let user = get_user_by_id(&state.pool, user_id)
let user = match get_user_by_id(&state.pool, user_id)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.ok_or(StatusCode::UNAUTHORIZED)?;
{
Some(u) => u,
None => return Ok(Redirect::to("/").into_response()),
};
let tmpl = DashboardTemplate {
user_name: user.name.clone(),
user_email: user.email.clone().unwrap_or_default(),
has_passphrase: user.key_salt.is_some(),
base_url: state.base_url.clone(),
version: env!("CARGO_PKG_VERSION"),
};
render_template(tmpl)