1use aurora_core::{AuroraResult, Pipeline, Value};
2use std::fs;
3use std::process::Command;
4
5pub fn sys_info() -> AuroraResult<Pipeline> {
6 let mut rows: Vec<Vec<Value>> = Vec::new();
7
8 if let Ok(cpuinfo) = fs::read_to_string("/proc/cpuinfo") {
9 for line in cpuinfo.lines() {
10 if let Some(key) = line.split(':').next() {
11 let key = key.trim();
12 if key == "model name" {
13 if let Some(val) = line.split(':').nth(1) {
14 rows.push(vec![
15 Value::String("CPU".into()),
16 Value::String(val.trim().into()),
17 ]);
18 }
19 break;
20 }
21 }
22 }
23 }
24
25 if let Ok(meminfo) = fs::read_to_string("/proc/meminfo") {
26 let mut total = String::new();
27 for line in meminfo.lines() {
28 if line.starts_with("MemTotal:") {
29 if let Some(val) = line.split(':').nth(1) {
30 total = val.trim().to_string();
31 }
32 break;
33 }
34 }
35 rows.push(vec![
36 Value::String("Memory".into()),
37 Value::String(total),
38 ]);
39 }
40
41 if let Ok(uptime) = fs::read_to_string("/proc/uptime") {
42 if let Some(secs) = uptime.split(' ').next() {
43 rows.push(vec![
44 Value::String("Uptime".into()),
45 Value::String(format!("{}s", secs.trim())),
46 ]);
47 }
48 }
49
50 if let Ok(output) = Command::new("uname").arg("-r").output() {
51 let kernel = String::from_utf8_lossy(&output.stdout).trim().to_string();
52 rows.push(vec![
53 Value::String("Kernel".into()),
54 Value::String(kernel),
55 ]);
56 }
57
58 if let Ok(host) = fs::read_to_string("/proc/sys/kernel/hostname") {
59 rows.push(vec![
60 Value::String("Hostname".into()),
61 Value::String(host.trim().into()),
62 ]);
63 }
64
65 Ok(Pipeline::table(
66 vec!["key".into(), "value".into()],
67 rows,
68 ))
69}
70
71pub fn sys_process() -> AuroraResult<Pipeline> {
72 let mut rows: Vec<Vec<Value>> = Vec::new();
73
74 let dir = fs::read_dir("/proc").map_err(|e| aurora_core::AuroraError::Io(e))?;
75 for entry in dir.flatten() {
76 let name = entry.file_name();
77 let pid_str = name.to_string_lossy();
78 let pid: i64 = match pid_str.parse() {
79 Ok(n) => n,
80 Err(_) => continue,
81 };
82
83 let status_path = entry.path().join("status");
84 let status_content = match fs::read_to_string(&status_path) {
85 Ok(c) => c,
86 Err(_) => continue,
87 };
88
89 let mut proc_name = String::new();
90 let mut state = String::new();
91 for line in status_content.lines() {
92 if line.starts_with("Name:") {
93 if let Some(val) = line.split(':').nth(1) {
94 proc_name = val.trim().to_string();
95 }
96 }
97 if line.starts_with("State:") {
98 if let Some(val) = line.split(':').nth(1) {
99 state = val.trim().to_string();
100 }
101 }
102 }
103
104 rows.push(vec![
105 Value::Int(pid),
106 Value::String(proc_name),
107 Value::String(state),
108 Value::String("--".into()),
109 Value::String("--".into()),
110 ]);
111 }
112
113 Ok(Pipeline::table(
114 vec!["pid".into(), "name".into(), "state".into(), "cpu%".into(), "mem%".into()],
115 rows,
116 ))
117}
118
119pub fn sys_disk() -> AuroraResult<Pipeline> {
120 let output = Command::new("df")
121 .arg("-h")
122 .output()
123 .map_err(|e| aurora_core::AuroraError::ModuleError(
124 format!("df failed: {}", e)
125 ))?;
126
127 let stdout = String::from_utf8_lossy(&output.stdout);
128 let mut rows: Vec<Vec<Value>> = Vec::new();
129
130 for (i, line) in stdout.lines().enumerate() {
131 if i == 0 || line.trim().is_empty() { continue; }
132 let parts: Vec<&str> = line.split_whitespace().collect();
133 if parts.len() >= 6 {
134 rows.push(vec![
135 Value::String(parts[5].into()),
136 Value::String(parts[1].into()),
137 Value::String(parts[2].into()),
138 Value::String(parts[3].into()),
139 Value::String(parts[4].into()),
140 ]);
141 }
142 }
143
144 Ok(Pipeline::table(
145 vec!["mount".into(), "total".into(), "used".into(), "avail".into(), "use%".into()],
146 rows,
147 ))
148}
149
150pub fn sys_memory() -> AuroraResult<Pipeline> {
151 let content = fs::read_to_string("/proc/meminfo")
152 .map_err(|e| aurora_core::AuroraError::Io(e))?;
153
154 let mut total = String::new();
155 let mut free = String::new();
156 let mut buffers = String::new();
157 let mut cached = String::new();
158
159 for line in content.lines() {
160 if line.starts_with("MemTotal:") {
161 if let Some(v) = line.split(':').nth(1) {
162 total = v.trim().to_string();
163 }
164 } else if line.starts_with("MemFree:") {
165 if let Some(v) = line.split(':').nth(1) {
166 free = v.trim().to_string();
167 }
168 } else if line.starts_with("Buffers:") {
169 if let Some(v) = line.split(':').nth(1) {
170 buffers = v.trim().to_string();
171 }
172 } else if line.starts_with("Cached:") {
173 if let Some(v) = line.split(':').nth(1) {
174 cached = v.trim().to_string();
175 }
176 }
177 }
178
179 let used_kb = parse_kb(&total).saturating_sub(parse_kb(&free));
180 let used = format!("{} kB", used_kb);
181
182 Ok(Pipeline::table(
183 vec!["total".into(), "used".into(), "free".into(), "buffers".into(), "cached".into()],
184 vec![vec![
185 Value::String(total),
186 Value::String(used),
187 Value::String(free),
188 Value::String(buffers),
189 Value::String(cached),
190 ]],
191 ))
192}
193
194fn parse_kb(s: &str) -> u64 {
195 let num: String = s.chars().filter(|c| c.is_ascii_digit()).collect();
196 num.parse().unwrap_or(0)
197}
198
199pub fn sys_service(name: &str) -> AuroraResult<Pipeline> {
200 let output = Command::new("systemctl")
201 .args(["status", name, "--no-pager", "-l"])
202 .output()
203 .map_err(|e| aurora_core::AuroraError::ModuleError(
204 format!("systemctl failed: {}", e)
205 ))?;
206
207 let stdout = String::from_utf8_lossy(&output.stdout);
208 let stderr = String::from_utf8_lossy(&output.stderr);
209 let combined = format!("{}{}", stdout, stderr);
210
211 Ok(Pipeline::table(
212 vec!["service".into(), "info".into()],
213 vec![vec![
214 Value::String(name.into()),
215 Value::String(combined),
216 ]],
217 ))
218}