fix per code review
This commit is contained in:
@@ -169,8 +169,10 @@ export function TerminalPage({
|
|||||||
const selectedAgentSessionCount = sessions.filter(
|
const selectedAgentSessionCount = sessions.filter(
|
||||||
(item) => item.agent_id === selectedAgentId,
|
(item) => item.agent_id === selectedAgentId,
|
||||||
).length;
|
).length;
|
||||||
const selectedAgentSessionLimit =
|
const selectedAgentSessionLimit = Math.max(
|
||||||
selectedAgent?.capability_options?.terminal?.max_sessions ?? 2;
|
1,
|
||||||
|
selectedAgent?.capability_options?.terminal?.max_sessions ?? 2,
|
||||||
|
);
|
||||||
const agentAtSessionLimit =
|
const agentAtSessionLimit =
|
||||||
selectedAgentSessionCount >= selectedAgentSessionLimit;
|
selectedAgentSessionCount >= selectedAgentSessionLimit;
|
||||||
const canRequestStart =
|
const canRequestStart =
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ use std::fmt;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
use crate::protocol::{DEFAULT_TERMINAL_MAX_SESSIONS, DEFAULT_TERMINAL_SESSION_TTL_SECONDS};
|
use crate::protocol::{
|
||||||
|
DEFAULT_TERMINAL_MAX_SESSIONS, DEFAULT_TERMINAL_SESSION_TTL_SECONDS,
|
||||||
|
checked_terminal_session_ttl,
|
||||||
|
};
|
||||||
|
|
||||||
pub const DEFAULT_CONFIG_PATH: &str = "/etc/wakey-agent/config.toml";
|
pub const DEFAULT_CONFIG_PATH: &str = "/etc/wakey-agent/config.toml";
|
||||||
pub const DEFAULT_PID_FILE: &str = "/var/run/wakey-agent.pid";
|
pub const DEFAULT_PID_FILE: &str = "/var/run/wakey-agent.pid";
|
||||||
@@ -158,14 +161,7 @@ const fn default_terminal_session_ttl_seconds() -> u64 {
|
|||||||
|
|
||||||
impl TerminalConfig {
|
impl TerminalConfig {
|
||||||
pub(crate) fn session_ttl(&self) -> Result<Option<std::time::Duration>> {
|
pub(crate) fn session_ttl(&self) -> Result<Option<std::time::Duration>> {
|
||||||
if self.session_ttl_seconds == 0 {
|
checked_terminal_session_ttl(self.session_ttl_seconds).map_err(anyhow::Error::msg)
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
let ttl = std::time::Duration::from_secs(self.session_ttl_seconds);
|
|
||||||
std::time::Instant::now()
|
|
||||||
.checked_add(ttl)
|
|
||||||
.context("terminal.session_ttl_seconds is too large for the platform timer")?;
|
|
||||||
Ok(Some(ttl))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use macaddr::MacAddr;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
use wakey_core::parse::mac;
|
use wakey_core::parse::mac;
|
||||||
use wakey_core::{
|
use wakey_core::{
|
||||||
Device, DeviceInventory, DhcpLeaseWithState, InterfaceSummary, InventoryQuery,
|
Device, DeviceInventory, DhcpLeaseWithState, InterfaceSummary, InventoryQuery,
|
||||||
@@ -68,6 +69,21 @@ pub enum AgentCapability {
|
|||||||
pub const DEFAULT_TERMINAL_MAX_SESSIONS: usize = 2;
|
pub const DEFAULT_TERMINAL_MAX_SESSIONS: usize = 2;
|
||||||
pub const DEFAULT_TERMINAL_SESSION_TTL_SECONDS: u64 = 12 * 60 * 60;
|
pub const DEFAULT_TERMINAL_SESSION_TTL_SECONDS: u64 = 12 * 60 * 60;
|
||||||
|
|
||||||
|
/// Validates a terminal TTL against the platform's monotonic timer range.
|
||||||
|
/// Zero is the explicit unlimited policy.
|
||||||
|
pub fn checked_terminal_session_ttl(
|
||||||
|
session_ttl_seconds: u64,
|
||||||
|
) -> Result<Option<Duration>, &'static str> {
|
||||||
|
if session_ttl_seconds == 0 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let ttl = Duration::from_secs(session_ttl_seconds);
|
||||||
|
Instant::now()
|
||||||
|
.checked_add(ttl)
|
||||||
|
.ok_or("terminal session TTL is too large for the platform timer")?;
|
||||||
|
Ok(Some(ttl))
|
||||||
|
}
|
||||||
|
|
||||||
const fn default_terminal_session_ttl_seconds() -> u64 {
|
const fn default_terminal_session_ttl_seconds() -> u64 {
|
||||||
DEFAULT_TERMINAL_SESSION_TTL_SECONDS
|
DEFAULT_TERMINAL_SESSION_TTL_SECONDS
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ pub async fn get_terminal(
|
|||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(terminal_id): Path<String>,
|
Path(terminal_id): Path<String>,
|
||||||
) -> Result<Json<TerminalSessionResponse>, ApiError> {
|
) -> Result<Json<TerminalSessionResponse>, ApiError> {
|
||||||
let (agent_id, created_at_unix, agent_attached, operator_attached, expires_at_unix) = state
|
let summary = state
|
||||||
.terminals
|
.terminals
|
||||||
.summary(&terminal_id)
|
.summary(&terminal_id)
|
||||||
.await
|
.await
|
||||||
@@ -161,11 +161,11 @@ pub async fn get_terminal(
|
|||||||
Ok(Json(TerminalSessionResponse {
|
Ok(Json(TerminalSessionResponse {
|
||||||
websocket_url: operator_ws_path(&terminal_id),
|
websocket_url: operator_ws_path(&terminal_id),
|
||||||
terminal_id,
|
terminal_id,
|
||||||
agent_id,
|
agent_id: summary.agent_id,
|
||||||
created_at_unix,
|
created_at_unix: summary.created_at_unix,
|
||||||
expires_at_unix,
|
expires_at_unix: summary.expires_at_unix,
|
||||||
agent_attached,
|
agent_attached: summary.agent_attached,
|
||||||
operator_attached,
|
operator_attached: summary.operator_attached,
|
||||||
attachment_token: None,
|
attachment_token: None,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@@ -192,7 +192,7 @@ pub async fn attach_terminal(
|
|||||||
.issue_attachment_token_for_operator(&terminal_id, &request.operator_id)
|
.issue_attachment_token_for_operator(&terminal_id, &request.operator_id)
|
||||||
.await
|
.await
|
||||||
.map_err(registry_error)?;
|
.map_err(registry_error)?;
|
||||||
let (agent_id, created_at_unix, agent_attached, operator_attached, expires_at_unix) = state
|
let summary = state
|
||||||
.terminals
|
.terminals
|
||||||
.summary(&terminal_id)
|
.summary(&terminal_id)
|
||||||
.await
|
.await
|
||||||
@@ -200,11 +200,11 @@ pub async fn attach_terminal(
|
|||||||
Ok(Json(TerminalSessionResponse {
|
Ok(Json(TerminalSessionResponse {
|
||||||
websocket_url: operator_ws_path(&terminal_id),
|
websocket_url: operator_ws_path(&terminal_id),
|
||||||
terminal_id,
|
terminal_id,
|
||||||
agent_id,
|
agent_id: summary.agent_id,
|
||||||
created_at_unix,
|
created_at_unix: summary.created_at_unix,
|
||||||
expires_at_unix,
|
expires_at_unix: summary.expires_at_unix,
|
||||||
agent_attached,
|
agent_attached: summary.agent_attached,
|
||||||
operator_attached,
|
operator_attached: summary.operator_attached,
|
||||||
attachment_token: Some(attachment_token),
|
attachment_token: Some(attachment_token),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@@ -365,10 +365,10 @@ async fn handle_operator_terminal_socket(
|
|||||||
warn!(terminal_id, code, "failed to request terminal snapshot");
|
warn!(terminal_id, code, "failed to request terminal snapshot");
|
||||||
}
|
}
|
||||||
let summary = state.terminals.summary(&terminal_id).await;
|
let summary = state.terminals.summary(&terminal_id).await;
|
||||||
if let Some((agent_id, _, _, _, _)) = &summary {
|
if let Some(summary) = &summary {
|
||||||
append_terminal_audit(
|
append_terminal_audit(
|
||||||
&state,
|
&state,
|
||||||
agent_id,
|
&summary.agent_id,
|
||||||
&terminal_id,
|
&terminal_id,
|
||||||
TerminalAudit {
|
TerminalAudit {
|
||||||
actor_type: "admin_api",
|
actor_type: "admin_api",
|
||||||
@@ -382,7 +382,7 @@ async fn handle_operator_terminal_socket(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let (mut write, mut read) = socket.split();
|
let (mut write, mut read) = socket.split();
|
||||||
if summary.is_some_and(|(_, _, agent_attached, _, _)| agent_attached) {
|
if summary.is_some_and(|summary| summary.agent_attached) {
|
||||||
let ready = serde_json::to_string(&TerminalControl::Ready)
|
let ready = serde_json::to_string(&TerminalControl::Ready)
|
||||||
.expect("terminal ready control serializes");
|
.expect("terminal ready control serializes");
|
||||||
if send_relay_frame(&mut write, TerminalRelayFrame::Text(ready))
|
if send_relay_frame(&mut write, TerminalRelayFrame::Text(ready))
|
||||||
|
|||||||
@@ -448,21 +448,12 @@ impl TerminalRegistry {
|
|||||||
Some(detached_at)
|
Some(detached_at)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn summary(
|
pub async fn summary(&self, terminal_id: &str) -> Option<TerminalSummary> {
|
||||||
&self,
|
|
||||||
terminal_id: &str,
|
|
||||||
) -> Option<(String, u64, bool, bool, Option<u64>)> {
|
|
||||||
let mut sessions = self.inner.lock().await;
|
let mut sessions = self.inner.lock().await;
|
||||||
prune_expired_sessions(&mut sessions);
|
prune_expired_sessions(&mut sessions);
|
||||||
sessions.get(terminal_id).map(|session| {
|
sessions
|
||||||
(
|
.get(terminal_id)
|
||||||
session.agent_id.clone(),
|
.map(|session| terminal_summary(terminal_id, session))
|
||||||
session.created_at_unix,
|
|
||||||
session.agent_tx.is_some(),
|
|
||||||
session.operator_tx.is_some(),
|
|
||||||
session.expiry.unix(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn summaries(&self) -> Vec<TerminalSummary> {
|
pub async fn summaries(&self) -> Vec<TerminalSummary> {
|
||||||
@@ -470,20 +461,24 @@ impl TerminalRegistry {
|
|||||||
prune_expired_sessions(&mut sessions);
|
prune_expired_sessions(&mut sessions);
|
||||||
let mut summaries = sessions
|
let mut summaries = sessions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(terminal_id, session)| TerminalSummary {
|
.map(|(terminal_id, session)| terminal_summary(terminal_id, session))
|
||||||
terminal_id: terminal_id.clone(),
|
|
||||||
agent_id: session.agent_id.clone(),
|
|
||||||
created_at_unix: session.created_at_unix,
|
|
||||||
expires_at_unix: session.expiry.unix(),
|
|
||||||
agent_attached: session.agent_tx.is_some(),
|
|
||||||
operator_attached: session.operator_tx.is_some(),
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
summaries.sort_by_key(|session| std::cmp::Reverse(session.created_at_unix));
|
summaries.sort_by_key(|session| std::cmp::Reverse(session.created_at_unix));
|
||||||
summaries
|
summaries
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn terminal_summary(terminal_id: &str, session: &TerminalSession) -> TerminalSummary {
|
||||||
|
TerminalSummary {
|
||||||
|
terminal_id: terminal_id.to_string(),
|
||||||
|
agent_id: session.agent_id.clone(),
|
||||||
|
created_at_unix: session.created_at_unix,
|
||||||
|
expires_at_unix: session.expiry.unix(),
|
||||||
|
agent_attached: session.agent_tx.is_some(),
|
||||||
|
operator_attached: session.operator_tx.is_some(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn active_session<'a>(
|
fn active_session<'a>(
|
||||||
sessions: &'a mut HashMap<String, TerminalSession>,
|
sessions: &'a mut HashMap<String, TerminalSession>,
|
||||||
terminal_id: &str,
|
terminal_id: &str,
|
||||||
@@ -739,7 +734,7 @@ mod tests {
|
|||||||
.summary(&unlimited.terminal_id)
|
.summary(&unlimited.terminal_id)
|
||||||
.await
|
.await
|
||||||
.expect("unlimited summary")
|
.expect("unlimited summary")
|
||||||
.4,
|
.expires_at_unix,
|
||||||
None
|
None
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -763,7 +758,7 @@ mod tests {
|
|||||||
.summary(terminal_id.as_str())
|
.summary(terminal_id.as_str())
|
||||||
.await
|
.await
|
||||||
.expect("adopted unlimited session")
|
.expect("adopted unlimited session")
|
||||||
.4,
|
.expires_at_unix,
|
||||||
None
|
None
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -805,9 +800,9 @@ mod tests {
|
|||||||
.summary(terminal_id.as_str())
|
.summary(terminal_id.as_str())
|
||||||
.await
|
.await
|
||||||
.expect("adopted summary");
|
.expect("adopted summary");
|
||||||
assert_eq!(summary.0, "router");
|
assert_eq!(summary.agent_id, "router");
|
||||||
assert_eq!(summary.1, created_at_unix);
|
assert_eq!(summary.created_at_unix, created_at_unix);
|
||||||
assert!(summary.2);
|
assert!(summary.agent_attached);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -869,7 +864,7 @@ mod tests {
|
|||||||
.summary(&created.terminal_id)
|
.summary(&created.terminal_id)
|
||||||
.await
|
.await
|
||||||
.expect("session remains after detach");
|
.expect("session remains after detach");
|
||||||
assert!(!summary.3);
|
assert!(!summary.operator_attached);
|
||||||
registry
|
registry
|
||||||
.issue_attachment_token_for_operator(&created.terminal_id, OPERATOR_A)
|
.issue_attachment_token_for_operator(&created.terminal_id, OPERATOR_A)
|
||||||
.await
|
.await
|
||||||
@@ -895,7 +890,13 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.expect("handoff token");
|
.expect("handoff token");
|
||||||
|
|
||||||
assert!(!registry.summary(&previous.terminal_id).await.unwrap().3);
|
assert!(
|
||||||
|
!registry
|
||||||
|
.summary(&previous.terminal_id)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.operator_attached
|
||||||
|
);
|
||||||
registry
|
registry
|
||||||
.attach_operator(&next.terminal_id, &next_token, OPERATOR_A)
|
.attach_operator(&next.terminal_id, &next_token, OPERATOR_A)
|
||||||
.await
|
.await
|
||||||
@@ -930,8 +931,20 @@ mod tests {
|
|||||||
.expect_err("occupied target remains protected");
|
.expect_err("occupied target remains protected");
|
||||||
|
|
||||||
assert_eq!(error, "terminal_operator_already_attached");
|
assert_eq!(error, "terminal_operator_already_attached");
|
||||||
assert!(registry.summary(&previous.terminal_id).await.unwrap().3);
|
assert!(
|
||||||
assert!(registry.summary(&occupied.terminal_id).await.unwrap().3);
|
registry
|
||||||
|
.summary(&previous.terminal_id)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.operator_attached
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
registry
|
||||||
|
.summary(&occupied.terminal_id)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.operator_attached
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -959,7 +972,13 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.is_none()
|
.is_none()
|
||||||
);
|
);
|
||||||
assert!(registry.summary(&created.terminal_id).await.unwrap().3);
|
assert!(
|
||||||
|
registry
|
||||||
|
.summary(&created.terminal_id)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.operator_attached
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ use axum::extract::ws::{Message, WebSocket, WebSocketUpgrade};
|
|||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
use futures_util::{SinkExt, StreamExt};
|
use futures_util::{SinkExt, StreamExt};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::Instant;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tracing::{debug, info, info_span, warn};
|
use tracing::{debug, info, info_span, warn};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use wakey_agent::protocol::{
|
use wakey_agent::protocol::{
|
||||||
AgentCapability, AgentCapabilityOptions, AgentTerminalSession, DEFAULT_TERMINAL_MAX_SESSIONS,
|
AgentCapability, AgentCapabilityOptions, AgentTerminalSession, DEFAULT_TERMINAL_MAX_SESSIONS,
|
||||||
DEFAULT_TERMINAL_SESSION_TTL_SECONDS, ErrorPayload, RequestId, ServerMessage,
|
DEFAULT_TERMINAL_SESSION_TTL_SECONDS, ErrorPayload, RequestId, ServerMessage,
|
||||||
TerminalCapabilityOptions, TerminalControl, TerminalId,
|
TerminalCapabilityOptions, TerminalControl, TerminalId, checked_terminal_session_ttl,
|
||||||
};
|
};
|
||||||
use wakey_core::Device;
|
use wakey_core::Device;
|
||||||
|
|
||||||
@@ -225,13 +225,7 @@ async fn process_agent_text(
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|terminal| terminal.session_ttl_seconds)
|
.map(|terminal| terminal.session_ttl_seconds)
|
||||||
.unwrap_or(DEFAULT_TERMINAL_SESSION_TTL_SECONDS);
|
.unwrap_or(DEFAULT_TERMINAL_SESSION_TTL_SECONDS);
|
||||||
if session_ttl_seconds != 0
|
checked_terminal_session_ttl(session_ttl_seconds).map_err(anyhow::Error::msg)?;
|
||||||
&& Instant::now()
|
|
||||||
.checked_add(Duration::from_secs(session_ttl_seconds))
|
|
||||||
.is_none()
|
|
||||||
{
|
|
||||||
anyhow::bail!("terminal session TTL is too large for the platform timer");
|
|
||||||
}
|
|
||||||
Some(TerminalCapabilityOptions {
|
Some(TerminalCapabilityOptions {
|
||||||
max_sessions,
|
max_sessions,
|
||||||
session_ttl_seconds,
|
session_ttl_seconds,
|
||||||
|
|||||||
Reference in New Issue
Block a user