1use std::{collections::BTreeMap, fmt, str::FromStr};
2
3use chrono::{DateTime, Utc};
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6use uuid::Uuid;
7
8use crate::ssh::SshConnectionId;
9
10#[derive(
11 Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
12)]
13#[serde(transparent)]
14pub struct SessionId(String);
15
16impl SessionId {
17 pub fn new() -> Self {
18 Self(format!("pty_{}", Uuid::new_v4().simple()))
19 }
20
21 pub fn as_str(&self) -> &str {
22 &self.0
23 }
24}
25
26impl Default for SessionId {
27 fn default() -> Self {
28 Self::new()
29 }
30}
31
32impl fmt::Display for SessionId {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 f.write_str(&self.0)
35 }
36}
37
38impl FromStr for SessionId {
39 type Err = &'static str;
40
41 fn from_str(value: &str) -> Result<Self, Self::Err> {
42 if value.trim().is_empty() {
43 return Err("session id cannot be empty");
44 }
45
46 Ok(Self(value.to_string()))
47 }
48}
49
50impl From<String> for SessionId {
51 fn from(value: String) -> Self {
52 Self(value)
53 }
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
57#[serde(rename_all = "snake_case")]
58pub enum SessionStatus {
59 Starting,
60 Running,
61 Exited,
62 FailedToSpawn,
63 Closing,
64 Killed,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
68#[schemars(inline)]
69#[serde(rename_all = "snake_case")]
70pub enum SessionTransport {
71 #[default]
72 Local,
73 Ssh,
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
77#[schemars(inline)]
78#[serde(rename_all = "snake_case")]
79pub enum ReadView {
80 Plain,
81 Ansi,
82 Raw,
83}
84
85#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
86#[schemars(inline)]
87#[serde(rename_all = "snake_case")]
88pub enum SignalKind {
89 Sigint,
90 Sigterm,
91 Sigkill,
92}
93
94#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
95pub struct Pagination {
96 pub offset: usize,
97 pub limit: usize,
98}
99
100#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
101pub struct BufferStats {
102 pub line_count: usize,
103 pub byte_count: usize,
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
107pub struct ExitInfo {
108 #[serde(skip_serializing_if = "Option::is_none")]
109 pub exit_code: Option<i32>,
110 #[serde(skip_serializing_if = "Option::is_none")]
111 pub exit_signal: Option<String>,
112 #[serde(skip_serializing_if = "Option::is_none")]
113 pub finished_at: Option<DateTime<Utc>>,
114}
115
116#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
117pub struct SessionSummary {
118 pub session_id: SessionId,
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub title: Option<String>,
121 pub description: String,
122 pub command: String,
123 #[serde(default, skip_serializing_if = "Vec::is_empty")]
124 pub args: Vec<String>,
125 #[serde(skip_serializing_if = "Option::is_none")]
126 pub cwd: Option<String>,
127 #[serde(default)]
128 pub transport: SessionTransport,
129 #[serde(skip_serializing_if = "Option::is_none")]
130 pub connection_id: Option<SshConnectionId>,
131 #[serde(skip_serializing_if = "Option::is_none")]
132 pub target_summary: Option<String>,
133 #[serde(skip_serializing_if = "Option::is_none")]
134 pub remote_cwd: Option<String>,
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub remote_command: Option<String>,
137 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
138 pub remote_env_preview: BTreeMap<String, String>,
139 pub status: SessionStatus,
140 #[serde(skip_serializing_if = "Option::is_none")]
141 pub pid: Option<u32>,
142 pub started_at: DateTime<Utc>,
143 pub buffer_stats: BufferStats,
144 #[serde(skip_serializing_if = "Option::is_none")]
145 pub exit_info: Option<ExitInfo>,
146}
147
148impl SessionSummary {
149 pub fn placeholder(description: impl Into<String>, command: impl Into<String>) -> Self {
150 Self {
151 session_id: SessionId::new(),
152 title: None,
153 description: description.into(),
154 command: command.into(),
155 args: Vec::new(),
156 cwd: None,
157 transport: SessionTransport::Local,
158 connection_id: None,
159 target_summary: None,
160 remote_cwd: None,
161 remote_command: None,
162 remote_env_preview: BTreeMap::new(),
163 status: SessionStatus::Starting,
164 pid: None,
165 started_at: Utc::now(),
166 buffer_stats: BufferStats::default(),
167 exit_info: None,
168 }
169 }
170}