v0.26.1
This commit is contained in:
1 parent
ddc5c6593f
commit
b9d137eee1
63 files changed
+1232
-445
No files matched your search
@@ -0,0 +1,197 @@
|
||||
const LIVE_LOG_COLUMNS: &[&str] = &[
|
||||
"time",
|
||||
"request",
|
||||
"host",
|
||||
"source_ip",
|
||||
"vhost",
|
||||
"backend",
|
||||
"status",
|
||||
"duration",
|
||||
"cache",
|
||||
"outcome",
|
||||
"transport",
|
||||
"websocket",
|
||||
"upstream_error",
|
||||
];
|
||||
const FILE_LOG_COLUMNS: &[&str] = &[
|
||||
"time", "event", "status", "client_ip", "vhost", "transport", "matched_by", "sni",
|
||||
"authority", "record", "source",
|
||||
];
|
||||
const BACKEND_TABLE_COLUMNS: &[&str] = &[
|
||||
"vhost",
|
||||
"backend",
|
||||
"status",
|
||||
"active",
|
||||
"requests",
|
||||
"errors",
|
||||
"latency",
|
||||
"traffic",
|
||||
"generation",
|
||||
"generationRequests",
|
||||
"generationAge",
|
||||
"rotations",
|
||||
"lastFailure",
|
||||
"lastRecovery",
|
||||
];
|
||||
|
||||
fn default_admin_theme() -> String {
|
||||
"dark".to_owned()
|
||||
}
|
||||
fn default_file_log_columns() -> Vec<String> {
|
||||
["time", "event", "status", "client_ip", "vhost", "record", "source"]
|
||||
.into_iter()
|
||||
.map(str::to_owned)
|
||||
.collect()
|
||||
}
|
||||
fn default_backend_table_columns() -> Vec<String> {
|
||||
BACKEND_TABLE_COLUMNS
|
||||
.iter()
|
||||
.map(|value| (*value).to_owned())
|
||||
.collect()
|
||||
}
|
||||
fn default_backend_table_sort() -> String {
|
||||
"vhost".to_owned()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct AdminPreferences {
|
||||
live_log_columns: Vec<String>,
|
||||
#[serde(default = "default_admin_theme")]
|
||||
theme: String,
|
||||
#[serde(default = "default_file_log_columns")]
|
||||
file_log_columns: Vec<String>,
|
||||
#[serde(default = "default_backend_table_columns")]
|
||||
backend_table_columns: Vec<String>,
|
||||
#[serde(default = "default_backend_table_sort")]
|
||||
backend_table_sort: String,
|
||||
#[serde(default)]
|
||||
backend_table_descending: bool,
|
||||
}
|
||||
|
||||
impl Default for AdminPreferences {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
live_log_columns: LIVE_LOG_COLUMNS
|
||||
.iter()
|
||||
.map(|value| (*value).to_owned())
|
||||
.collect(),
|
||||
theme: default_admin_theme(),
|
||||
file_log_columns: default_file_log_columns(),
|
||||
backend_table_columns: default_backend_table_columns(),
|
||||
backend_table_sort: default_backend_table_sort(),
|
||||
backend_table_descending: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_admin_preferences(preferences: &AdminPreferences) -> Result<(), ApiError> {
|
||||
if preferences.live_log_columns.is_empty()
|
||||
|| preferences.live_log_columns.len() > LIVE_LOG_COLUMNS.len()
|
||||
{
|
||||
return Err(ApiError(
|
||||
StatusCode::UNPROCESSABLE_ENTITY,
|
||||
"at least one live-log column must be visible".into(),
|
||||
));
|
||||
}
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for column in &preferences.live_log_columns {
|
||||
if !LIVE_LOG_COLUMNS.contains(&column.as_str()) || !seen.insert(column.as_str()) {
|
||||
return Err(ApiError(
|
||||
StatusCode::UNPROCESSABLE_ENTITY,
|
||||
"invalid live-log column preference".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
if preferences.file_log_columns.is_empty()
|
||||
|| preferences.file_log_columns.len() > FILE_LOG_COLUMNS.len()
|
||||
{
|
||||
return Err(ApiError(
|
||||
StatusCode::UNPROCESSABLE_ENTITY,
|
||||
"at least one stored-log column must be visible".into(),
|
||||
));
|
||||
}
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for column in &preferences.file_log_columns {
|
||||
if !FILE_LOG_COLUMNS.contains(&column.as_str()) || !seen.insert(column.as_str()) {
|
||||
return Err(ApiError(
|
||||
StatusCode::UNPROCESSABLE_ENTITY,
|
||||
"invalid stored-log column preference".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
if !matches!(preferences.theme.as_str(), "dark" | "light") {
|
||||
return Err(ApiError(
|
||||
StatusCode::UNPROCESSABLE_ENTITY,
|
||||
"invalid administration theme preference".into(),
|
||||
));
|
||||
}
|
||||
if preferences.backend_table_columns.is_empty()
|
||||
|| preferences.backend_table_columns.len() > BACKEND_TABLE_COLUMNS.len()
|
||||
{
|
||||
return Err(ApiError(
|
||||
StatusCode::UNPROCESSABLE_ENTITY,
|
||||
"at least one backend table column must be visible".into(),
|
||||
));
|
||||
}
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for column in &preferences.backend_table_columns {
|
||||
if !BACKEND_TABLE_COLUMNS.contains(&column.as_str()) || !seen.insert(column.as_str()) {
|
||||
return Err(ApiError(
|
||||
StatusCode::UNPROCESSABLE_ENTITY,
|
||||
"invalid backend table column preference".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
if !BACKEND_TABLE_COLUMNS.contains(&preferences.backend_table_sort.as_str()) {
|
||||
return Err(ApiError(
|
||||
StatusCode::UNPROCESSABLE_ENTITY,
|
||||
"invalid backend table sort preference".into(),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn preferences(
|
||||
State(state): State<Arc<App>>,
|
||||
Extension(session): Extension<Session>,
|
||||
) -> Result<Json<AdminPreferences>, ApiError> {
|
||||
let stored = state
|
||||
.store
|
||||
.admin_preferences(&session.username)
|
||||
.await
|
||||
.map_err(auth::internal)?;
|
||||
let preferences = match stored {
|
||||
Some(document) => match serde_json::from_str::<AdminPreferences>(&document) {
|
||||
Ok(preferences) if validate_admin_preferences(&preferences).is_ok() => preferences,
|
||||
Ok(_) | Err(_) => {
|
||||
tracing::warn!(user = %session.username, "invalid admin view preferences ignored");
|
||||
AdminPreferences::default()
|
||||
}
|
||||
},
|
||||
None => AdminPreferences::default(),
|
||||
};
|
||||
Ok(Json(preferences))
|
||||
}
|
||||
|
||||
async fn update_preferences(
|
||||
State(state): State<Arc<App>>,
|
||||
Extension(session): Extension<Session>,
|
||||
input: Result<Json<AdminPreferences>, axum::extract::rejection::JsonRejection>,
|
||||
) -> Result<Json<AdminPreferences>, ApiError> {
|
||||
let Json(preferences) = input.map_err(public::invalid_json)?;
|
||||
validate_admin_preferences(&preferences)?;
|
||||
let document = serde_json::to_string(&preferences).map_err(auth::internal)?;
|
||||
if !state
|
||||
.store
|
||||
.save_admin_preferences(&session.username, &document)
|
||||
.await
|
||||
.map_err(auth::internal)?
|
||||
{
|
||||
return Err(ApiError(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"auth.invalid_credentials".into(),
|
||||
));
|
||||
}
|
||||
Ok(Json(preferences))
|
||||
}
|
||||
Reference in new issue
Block a user