fix(ui): keep device details open across identity changes
This commit is contained in:
12 files changed
+324
-32
No files matched your search
@@ -14,5 +14,5 @@ pub use enroll::{
|
||||
};
|
||||
pub(crate) use fleet::FleetCache;
|
||||
pub(crate) use fleet::sample_fleet_counts;
|
||||
pub use fleet::{list_fleet_devices, refresh_fleet_devices, wake_fleet_device};
|
||||
pub use fleet::{get_fleet_device, list_fleet_devices, refresh_fleet_devices, wake_fleet_device};
|
||||
pub use stats::{StateStatsResponse, state_stats};
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
|
||||
use axum::Json;
|
||||
use axum::extract::{Query, State};
|
||||
use axum::extract::{Path, Query, State};
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::IntoResponse;
|
||||
use tracing::warn;
|
||||
@@ -105,6 +105,30 @@ pub async fn list_fleet_devices(
|
||||
}
|
||||
}
|
||||
|
||||
/// Details are an exact identity lookup, not a filtered/paginated list lookup.
|
||||
/// In particular, remembering an unsaved row must remain readable under its new key.
|
||||
#[tracing::instrument(name = "fleet_get", skip_all)]
|
||||
pub async fn get_fleet_device(
|
||||
State(state): State<AppState>,
|
||||
Path(device_key): Path<String>,
|
||||
) -> Result<impl IntoResponse, ApiError> {
|
||||
let devices = fleet_view(&state)
|
||||
.await
|
||||
.map_err(|err| fleet_error(err, "get_fleet_device_failed"))?;
|
||||
let device = devices
|
||||
.iter()
|
||||
.find(|device| device.device_key == device_key)
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
ApiError::new(
|
||||
StatusCode::NOT_FOUND,
|
||||
"fleet_device_not_found",
|
||||
"fleet device not found",
|
||||
)
|
||||
})?;
|
||||
Ok(Json(device))
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "fleet_load", skip_all, fields(grouped = tracing::field::Empty, filtered = tracing::field::Empty, returned = tracing::field::Empty))]
|
||||
async fn load_fleet_devices(
|
||||
state: &AppState,
|
||||
|
||||
@@ -159,6 +159,35 @@ async fn fleet_cache_tracks_store_edits_and_connections_without_caching_filters(
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
);
|
||||
// The filtered list excludes this row, but exact details still follow the
|
||||
// authoritative saved key after remembering and renaming the observation.
|
||||
{
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
response::IntoResponse,
|
||||
};
|
||||
let response = super::get_fleet_device(
|
||||
State(state.clone()),
|
||||
Path(format!("known:{}", saved.device_id)),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_response();
|
||||
assert_eq!(response.status(), axum::http::StatusCode::OK);
|
||||
let body = axum::body::to_bytes(response.into_body(), usize::MAX)
|
||||
.await
|
||||
.unwrap();
|
||||
let details: serde_json::Value = serde_json::from_slice(&body).unwrap();
|
||||
assert_eq!(details["display_name"], "renamed pc");
|
||||
assert_eq!(details["known_device"]["device_id"], saved.device_id);
|
||||
let missing =
|
||||
super::get_fleet_device(State(state.clone()), Path(initial[0].device_key.clone()))
|
||||
.await
|
||||
.err()
|
||||
.expect("old unsaved grouping key no longer exists");
|
||||
assert_eq!(missing.status, axum::http::StatusCode::NOT_FOUND);
|
||||
assert_eq!(missing.code, "fleet_device_not_found");
|
||||
}
|
||||
let counts = super::sample_fleet_counts(&state).await.unwrap();
|
||||
assert_eq!((counts.saved, counts.unsaved), (1, 0));
|
||||
let all = ListFleetDevicesQuery {
|
||||
|
||||
@@ -216,6 +216,20 @@ mod tests {
|
||||
.unwrap();
|
||||
assert_eq!(listed.len(), 1000);
|
||||
assert!(listed.iter().all(|row| row.device_key != "pc-1000"));
|
||||
{
|
||||
use axum::extract::Path;
|
||||
let details =
|
||||
super::super::get_fleet_device(State(state.clone()), Path("pc-1000".into()))
|
||||
.await
|
||||
.unwrap()
|
||||
.into_response();
|
||||
assert_eq!(details.status(), StatusCode::OK);
|
||||
let body = axum::body::to_bytes(details.into_body(), usize::MAX)
|
||||
.await
|
||||
.unwrap();
|
||||
let row: serde_json::Value = serde_json::from_slice(&body).unwrap();
|
||||
assert_eq!(row["device_key"], "pc-1000");
|
||||
}
|
||||
for route_id in [None, Some("valid")] {
|
||||
let selected = load_selected_route(&state, "pc-1000", route_id)
|
||||
.await
|
||||
|
||||
@@ -15,10 +15,10 @@ pub use commands::{list_agents, run_command};
|
||||
pub use control::{
|
||||
EnrollTokenStatus, IssueEnrollTokenResponse, RevokeAgentResponse, RevokeEnrollTokenResponse,
|
||||
StateStatsResponse, attach_device_identifier, create_known_device, detach_device_identifier,
|
||||
enroll, forget_known_device, get_known_device, healthz, issue_enroll_token, list_enroll_tokens,
|
||||
list_fleet_devices, list_known_devices, merge_known_device, refresh_fleet_devices,
|
||||
rename_known_device, revoke_agent, revoke_enroll_token, set_agent_nickname, state_stats,
|
||||
wake_fleet_device,
|
||||
enroll, forget_known_device, get_fleet_device, get_known_device, healthz, issue_enroll_token,
|
||||
list_enroll_tokens, list_fleet_devices, list_known_devices, merge_known_device,
|
||||
refresh_fleet_devices, rename_known_device, revoke_agent, revoke_enroll_token,
|
||||
set_agent_nickname, state_stats, wake_fleet_device,
|
||||
};
|
||||
pub use terminals::{
|
||||
agent_terminal_ws, attach_terminal, close_terminal, create_terminal, get_terminal,
|
||||
|
||||
@@ -109,6 +109,10 @@ fn control_api_routes() -> Router<AppState> {
|
||||
"/api/v1/control/fleet/devices/refresh",
|
||||
post(api::refresh_fleet_devices),
|
||||
)
|
||||
.route(
|
||||
"/api/v1/control/fleet/devices/{device_key}",
|
||||
get(api::get_fleet_device),
|
||||
)
|
||||
.route("/api/v1/control/fleet/wake", post(api::wake_fleet_device))
|
||||
.route(
|
||||
"/api/v1/control/devices",
|
||||
|
||||
Reference in new issue
Block a user