headless_lms_server/controllers/main_frontend/
status.rs1use crate::{
4 domain::authorization::{Action, Resource, authorize},
5 domain::system_health::{
6 self, HealthStatus, SystemHealthStatus, check_system_health_detailed, get_cronjobs,
7 get_deployments, get_events, get_ingresses, get_jobs, get_namespace,
8 get_pod_disruption_budgets, get_pod_logs, get_pods, get_services,
9 },
10 prelude::*,
11};
12use std::collections::HashMap;
13
14pub use system_health::{
15 CronJobInfo, DeploymentInfo, EventInfo, IngressInfo, JobInfo, PodDisruptionBudgetInfo, PodInfo,
16 ServiceInfo, ServicePortInfo,
17};
18
19pub async fn pods(
25 pool: web::Data<PgPool>,
26 user: AuthUser,
27) -> ControllerResult<web::Json<Vec<PodInfo>>> {
28 let mut conn = pool.acquire().await?;
29 let token = authorize(
30 &mut conn,
31 Action::Administrate,
32 Some(user.id),
33 Resource::GlobalPermissions,
34 )
35 .await?;
36 let ns = get_namespace();
37 let pods = get_pods(&ns).await.map_err(|e| {
38 ControllerError::new(
39 ControllerErrorType::InternalServerError,
40 e.to_string(),
41 None,
42 )
43 })?;
44 token.authorized_ok(web::Json(pods))
45}
46
47pub async fn deployments(
53 pool: web::Data<PgPool>,
54 user: AuthUser,
55) -> ControllerResult<web::Json<Vec<DeploymentInfo>>> {
56 let mut conn = pool.acquire().await?;
57 let token = authorize(
58 &mut conn,
59 Action::Administrate,
60 Some(user.id),
61 Resource::GlobalPermissions,
62 )
63 .await?;
64 let ns = get_namespace();
65 let deployments = get_deployments(&ns).await.map_err(|e| {
66 ControllerError::new(
67 ControllerErrorType::InternalServerError,
68 e.to_string(),
69 None,
70 )
71 })?;
72 token.authorized_ok(web::Json(deployments))
73}
74
75pub async fn cronjobs(
81 pool: web::Data<PgPool>,
82 user: AuthUser,
83) -> ControllerResult<web::Json<Vec<CronJobInfo>>> {
84 let mut conn = pool.acquire().await?;
85 let token = authorize(
86 &mut conn,
87 Action::Administrate,
88 Some(user.id),
89 Resource::GlobalPermissions,
90 )
91 .await?;
92 let ns = get_namespace();
93 let cronjobs = get_cronjobs(&ns).await.map_err(|e| {
94 ControllerError::new(
95 ControllerErrorType::InternalServerError,
96 e.to_string(),
97 None,
98 )
99 })?;
100 token.authorized_ok(web::Json(cronjobs))
101}
102
103pub async fn jobs(
109 pool: web::Data<PgPool>,
110 user: AuthUser,
111) -> ControllerResult<web::Json<Vec<JobInfo>>> {
112 let mut conn = pool.acquire().await?;
113 let token = authorize(
114 &mut conn,
115 Action::Administrate,
116 Some(user.id),
117 Resource::GlobalPermissions,
118 )
119 .await?;
120 let ns = get_namespace();
121 let jobs = get_jobs(&ns).await.map_err(|e| {
122 ControllerError::new(
123 ControllerErrorType::InternalServerError,
124 e.to_string(),
125 None,
126 )
127 })?;
128 token.authorized_ok(web::Json(jobs))
129}
130
131pub async fn services(
137 pool: web::Data<PgPool>,
138 user: AuthUser,
139) -> ControllerResult<web::Json<Vec<ServiceInfo>>> {
140 let mut conn = pool.acquire().await?;
141 let token = authorize(
142 &mut conn,
143 Action::Administrate,
144 Some(user.id),
145 Resource::GlobalPermissions,
146 )
147 .await?;
148 let ns = get_namespace();
149 let services = get_services(&ns).await.map_err(|e| {
150 ControllerError::new(
151 ControllerErrorType::InternalServerError,
152 e.to_string(),
153 None,
154 )
155 })?;
156 token.authorized_ok(web::Json(services))
157}
158
159pub async fn events(
165 pool: web::Data<PgPool>,
166 user: AuthUser,
167) -> ControllerResult<web::Json<Vec<EventInfo>>> {
168 let mut conn = pool.acquire().await?;
169 let token = authorize(
170 &mut conn,
171 Action::Administrate,
172 Some(user.id),
173 Resource::GlobalPermissions,
174 )
175 .await?;
176 let ns = get_namespace();
177 let events = get_events(&ns).await.map_err(|e| {
178 ControllerError::new(
179 ControllerErrorType::InternalServerError,
180 e.to_string(),
181 None,
182 )
183 })?;
184 token.authorized_ok(web::Json(events))
185}
186
187pub async fn ingresses(
193 pool: web::Data<PgPool>,
194 user: AuthUser,
195) -> ControllerResult<web::Json<Vec<IngressInfo>>> {
196 let mut conn = pool.acquire().await?;
197 let token = authorize(
198 &mut conn,
199 Action::Administrate,
200 Some(user.id),
201 Resource::GlobalPermissions,
202 )
203 .await?;
204 let ns = get_namespace();
205 let ingresses = get_ingresses(&ns).await.map_err(|e| {
206 ControllerError::new(
207 ControllerErrorType::InternalServerError,
208 e.to_string(),
209 None,
210 )
211 })?;
212 token.authorized_ok(web::Json(ingresses))
213}
214
215pub async fn pod_disruption_budgets(
221 pool: web::Data<PgPool>,
222 user: AuthUser,
223) -> ControllerResult<web::Json<Vec<PodDisruptionBudgetInfo>>> {
224 let mut conn = pool.acquire().await?;
225 let token = authorize(
226 &mut conn,
227 Action::Administrate,
228 Some(user.id),
229 Resource::GlobalPermissions,
230 )
231 .await?;
232 let ns = get_namespace();
233 let pdbs = get_pod_disruption_budgets(&ns).await.map_err(|e| {
234 ControllerError::new(
235 ControllerErrorType::InternalServerError,
236 e.to_string(),
237 None,
238 )
239 })?;
240 token.authorized_ok(web::Json(pdbs))
241}
242
243fn parse_and_validate_tail(tail_str: Option<&String>) -> i64 {
244 const DEFAULT_TAIL_LINES: i64 = 1000;
245 const MAX_TAIL_LINES: i64 = 10_000;
246
247 match tail_str {
248 Some(s) => match s.parse::<u64>() {
249 Ok(val) => {
250 let clamped = val.min(MAX_TAIL_LINES as u64);
251 clamped as i64
252 }
253 Err(_) => DEFAULT_TAIL_LINES,
254 },
255 None => DEFAULT_TAIL_LINES,
256 }
257}
258
259pub async fn pod_logs(
269 path: web::Path<String>,
270 query: web::Query<HashMap<String, String>>,
271 pool: web::Data<PgPool>,
272 user: AuthUser,
273) -> ControllerResult<HttpResponse> {
274 let mut conn = pool.acquire().await?;
275 let token = authorize(
276 &mut conn,
277 Action::Administrate,
278 Some(user.id),
279 Resource::GlobalPermissions,
280 )
281 .await?;
282 let pod_name = path.into_inner();
283 let ns = get_namespace();
284
285 let container = query.get("container").map(|s| s.as_str());
286 let tail = parse_and_validate_tail(query.get("tail"));
287
288 let logs = get_pod_logs(&ns, &pod_name, container, tail)
289 .await
290 .map_err(|e| {
291 ControllerError::new(
292 ControllerErrorType::InternalServerError,
293 e.to_string(),
294 None,
295 )
296 })?;
297
298 token.authorized_ok(
299 HttpResponse::Ok()
300 .content_type("text/plain; charset=utf-8")
301 .body(logs),
302 )
303}
304
305pub async fn health(
311 pool: web::Data<PgPool>,
312 user: AuthUser,
313) -> ControllerResult<web::Json<SystemHealthStatus>> {
314 let mut conn = pool.acquire().await?;
315 let token = authorize(
316 &mut conn,
317 Action::Administrate,
318 Some(user.id),
319 Resource::GlobalPermissions,
320 )
321 .await?;
322 let ns = get_namespace();
323 let health_status = check_system_health_detailed(&ns, Some(&pool))
324 .await
325 .map_err(|e| {
326 ControllerError::new(
327 ControllerErrorType::InternalServerError,
328 e.to_string(),
329 None,
330 )
331 })?;
332 token.authorized_ok(web::Json(health_status))
333}
334
335pub async fn system_health(
342 pool: web::Data<PgPool>,
343 user: Option<AuthUser>,
344) -> ControllerResult<web::Json<bool>> {
345 let ns = get_namespace();
346 let kubernetes_health = check_system_health_detailed(&ns, Some(&pool)).await;
347
348 let is_healthy = matches!(
349 kubernetes_health,
350 Ok(SystemHealthStatus {
351 status: HealthStatus::Healthy,
352 ..
353 })
354 );
355
356 if is_healthy {
357 let token = skip_authorize();
358 return token.authorized_ok(web::Json(true));
359 }
360
361 if let Some(user) = user {
362 let mut conn = pool.acquire().await?;
363 authorize(
364 &mut conn,
365 Action::Administrate,
366 Some(user.id),
367 Resource::GlobalPermissions,
368 )
369 .await?;
370
371 let error_msg = match kubernetes_health {
372 Err(e) => format!("System health check failed: {}", e),
373 Ok(health_status) => {
374 if health_status.issues.is_empty() {
375 "System is unhealthy".to_string()
376 } else {
377 format!("System is unhealthy: {}", health_status.issues.join(", "))
378 }
379 }
380 };
381
382 Err(ControllerError::new(
383 ControllerErrorType::InternalServerError,
384 error_msg,
385 None,
386 ))
387 } else {
388 let token = skip_authorize();
389 token.authorized_ok(web::Json(false))
390 }
391}
392
393pub fn _add_routes(cfg: &mut ServiceConfig) {
394 cfg.route("/pods", web::get().to(pods))
395 .route("/deployments", web::get().to(deployments))
396 .route("/cronjobs", web::get().to(cronjobs))
397 .route("/jobs", web::get().to(jobs))
398 .route("/services", web::get().to(services))
399 .route("/events", web::get().to(events))
400 .route("/ingresses", web::get().to(ingresses))
401 .route(
402 "/pod-disruption-budgets",
403 web::get().to(pod_disruption_budgets),
404 )
405 .route("/pods/{pod_name}/logs", web::get().to(pod_logs))
406 .route("/health", web::get().to(health))
407 .route("/system-health", web::get().to(system_health));
408}