Remove uploading css functionality. Themes are hard-coded. Change the code
This commit is contained in:
+32
-96
@@ -2,12 +2,12 @@ pub mod dto;
|
||||
pub mod mcp_server;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::api::dto::{
|
||||
BucketValuesParams, CreateAlertRuleRequest, CreateDashboardRequest, CreateProfileRequest,
|
||||
CssEntryDto, DashboardDto, ErrorDto, ImportCssRequest, LinkCssRequest, MetricDeclarationDto,
|
||||
DashboardDto, ErrorDto, MetricDeclarationDto,
|
||||
ParamDeclarationDto, LogsParams, ProfileSummaryDto, PromqlParams, SaveDashboardRequest,
|
||||
SettingsDto, TestAlertNotificationRequest, TimelineParams, UpdateAlertRuleRequest, UpdateSettingsRequest,
|
||||
};
|
||||
@@ -18,7 +18,7 @@ use crate::engine::{
|
||||
};
|
||||
use crate::engine::alerts::{deliver_ntfy, AlertError};
|
||||
use crate::config::profile::{Dashboard, ParamDeclarationRecord, Profile, ProfileError};
|
||||
use crate::config::{CssError, SettingsHandle};
|
||||
use crate::config::SettingsHandle;
|
||||
use axum::extract::{Path as AxumPath, Query, State};
|
||||
use axum::http::{header, StatusCode};
|
||||
use axum::response::{IntoResponse, Json, Response};
|
||||
@@ -29,6 +29,11 @@ use tokio::sync::oneshot;
|
||||
|
||||
static WEBAPP_DIST: EmbedDir = fs_embed::fs_embed!("webapp/dist");
|
||||
|
||||
// Every file here is a selectable theme: a complete stylesheet (colors, spacing, fonts, ...)
|
||||
// applied on top of the base app shell. Add a new theme by dropping another `.css` file in
|
||||
// `themes/` and rebuilding — no code changes needed (see `webapp/src/index.css`).
|
||||
static THEMES: EmbedDir = fs_embed::fs_embed!("themes");
|
||||
|
||||
const SKILL_MD: &str = include_str!("../../SKILL.md");
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -39,13 +44,6 @@ struct ApiState {
|
||||
}
|
||||
|
||||
impl ApiState {
|
||||
fn css_dir(&self) -> PathBuf {
|
||||
self.settings_path
|
||||
.parent()
|
||||
.map(Path::to_path_buf)
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
}
|
||||
|
||||
fn persist_param_declaration(&self, request: &DeclareParamRequest) {
|
||||
let mut settings = self.settings.lock().unwrap();
|
||||
let Some(profile_name) = settings.active_profile.clone() else {
|
||||
@@ -159,10 +157,8 @@ pub fn router(engine: EngineHandle, settings: SettingsHandle, settings_path: Pat
|
||||
)
|
||||
.route("/api/query", get(run_promql_query))
|
||||
.route("/api/settings", get(get_settings).patch(update_settings))
|
||||
.route("/api/settings/css", get(list_css).post(import_css))
|
||||
.route("/api/settings/css/link", post(link_css))
|
||||
.route("/api/settings/css/{id}", delete(remove_css))
|
||||
.route("/api/settings/css/{id}/content", get(get_css_content))
|
||||
.route("/api/themes", get(list_themes))
|
||||
.route("/api/themes/{name}", get(get_theme_content))
|
||||
.route("/api/settings/alerts", get(list_alert_rules).post(create_alert_rule))
|
||||
.route(
|
||||
"/api/settings/alerts/{id}",
|
||||
@@ -553,84 +549,27 @@ async fn run_promql_query(
|
||||
}
|
||||
}
|
||||
|
||||
impl CssError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
match self {
|
||||
CssError::NotFound => (StatusCode::NOT_FOUND, self.to_string()).into_response(),
|
||||
CssError::NotImported => (StatusCode::BAD_REQUEST, self.to_string()).into_response(),
|
||||
CssError::Io(_) => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response(),
|
||||
async fn list_themes() -> impl IntoResponse {
|
||||
let mut names: Vec<String> = THEMES
|
||||
.walk()
|
||||
.filter_map(|file| file.file_name().map(|name| name.trim_end_matches(".css").to_string()))
|
||||
.collect();
|
||||
names.sort();
|
||||
Json(names).into_response()
|
||||
}
|
||||
|
||||
async fn get_theme_content(AxumPath(name): AxumPath<String>) -> impl IntoResponse {
|
||||
// Themes are just filenames within a flat embedded dir, not filesystem paths, so reject
|
||||
// anything that isn't a plain name to rule out path traversal.
|
||||
if name.is_empty() || !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-') {
|
||||
return StatusCode::NOT_FOUND.into_response();
|
||||
}
|
||||
match THEMES.get_file(&format!("{name}.css")) {
|
||||
Some(file) => {
|
||||
let bytes = file.read_bytes().unwrap_or_default();
|
||||
([(header::CONTENT_TYPE, "text/css; charset=utf-8")], bytes).into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_css(State(state): State<ApiState>) -> impl IntoResponse {
|
||||
let settings = state.settings.lock().unwrap();
|
||||
let entries: Vec<CssEntryDto> = settings.css_entries.iter().map(CssEntryDto::from).collect();
|
||||
Json(entries).into_response()
|
||||
}
|
||||
|
||||
async fn import_css(
|
||||
State(state): State<ApiState>,
|
||||
Json(body): Json<ImportCssRequest>,
|
||||
) -> impl IntoResponse {
|
||||
let css_dir = state.css_dir();
|
||||
let mut settings = state.settings.lock().unwrap();
|
||||
match settings.import_css(&state.settings_path, &css_dir, body.filename, &body.content) {
|
||||
Ok(id) => {
|
||||
let entry = settings
|
||||
.css_entries
|
||||
.iter()
|
||||
.find(|entry| crate::config::css_entry_id(entry) == id)
|
||||
.expect("entry was just inserted by import_css");
|
||||
(StatusCode::CREATED, Json(CssEntryDto::from(entry))).into_response()
|
||||
}
|
||||
Err(err) => err.into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn link_css(
|
||||
State(state): State<ApiState>,
|
||||
Json(body): Json<LinkCssRequest>,
|
||||
) -> impl IntoResponse {
|
||||
let mut settings = state.settings.lock().unwrap();
|
||||
match settings.link_css(&state.settings_path, body.path_or_url.clone()) {
|
||||
Ok(()) => {
|
||||
let entry = settings
|
||||
.css_entries
|
||||
.last()
|
||||
.expect("entry was just pushed by link_css");
|
||||
(StatusCode::CREATED, Json(CssEntryDto::from(entry))).into_response()
|
||||
}
|
||||
Err(err) => err.into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn remove_css(
|
||||
State(state): State<ApiState>,
|
||||
AxumPath(id): AxumPath<String>,
|
||||
) -> impl IntoResponse {
|
||||
let css_dir = state.css_dir();
|
||||
let mut settings = state.settings.lock().unwrap();
|
||||
match settings.remove_css(&state.settings_path, &css_dir, &id) {
|
||||
Ok(()) => StatusCode::NO_CONTENT.into_response(),
|
||||
Err(err) => err.into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_css_content(
|
||||
State(state): State<ApiState>,
|
||||
AxumPath(id): AxumPath<String>,
|
||||
) -> impl IntoResponse {
|
||||
let css_dir = state.css_dir();
|
||||
let settings = state.settings.lock().unwrap();
|
||||
match settings.css_content(&css_dir, &id) {
|
||||
Ok(content) => (
|
||||
StatusCode::OK,
|
||||
[(header::CONTENT_TYPE, "text/css; charset=utf-8")],
|
||||
content,
|
||||
)
|
||||
.into_response(),
|
||||
Err(err) => err.into_response(),
|
||||
None => StatusCode::NOT_FOUND.into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,15 +638,12 @@ async fn update_settings(
|
||||
let max_logs_changed = body.max_logs.is_some();
|
||||
let new_max_logs = {
|
||||
let mut settings = state.settings.lock().unwrap();
|
||||
if let Some(color_scheme) = body.color_scheme {
|
||||
settings.color_scheme = color_scheme;
|
||||
if let Some(theme) = body.theme {
|
||||
settings.theme = theme;
|
||||
}
|
||||
if let Some(max_logs) = body.max_logs {
|
||||
settings.max_logs = max_logs;
|
||||
}
|
||||
if let Some(custom_theme_id) = body.custom_theme_id {
|
||||
settings.custom_theme_id = custom_theme_id;
|
||||
}
|
||||
if let Some(ntfy_topic) = body.ntfy_topic {
|
||||
settings.ntfy_topic = ntfy_topic;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user