pub mod agent_config;
pub mod agent_groups;
pub mod agent_logs;
pub mod agent_releases;
pub mod agents;
pub mod app_packages;
pub mod audit;
pub mod exec;
pub mod executions;
pub mod fleet_perf;
pub mod health;
pub mod host_perf;
pub mod inventory;
pub mod jetstream_status;
pub mod jobs;
pub mod obs_events;
pub mod process_perf;
pub mod results;
pub mod run;
pub mod schedules;
pub mod schemas;
pub mod script_objects;
pub mod scripts;
pub mod yaml_body;
use axum::Router;
use axum::extract::{DefaultBodyLimit, FromRef};
use axum::routing::{delete, get, post};
use sqlx::SqlitePool;
const PUBLISH_BODY_LIMIT: usize = 64 * 1024 * 1024;
const APP_PACKAGE_BODY_LIMIT: usize = 256 * 1024 * 1024;
const SCRIPT_OBJECT_BODY_LIMIT: usize = 4 * 1024 * 1024;
#[derive(Clone)]
pub struct AppState {
pub pool: SqlitePool,
pub nats: async_nats::Client,
pub jetstream: async_nats::jetstream::Context,
pub explode_spec_cache: crate::projector::spec_cache::ExplodeSpecCache,
}
impl FromRef<AppState> for SqlitePool {
fn from_ref(state: &AppState) -> Self {
state.pool.clone()
}
}
pub fn router(state: AppState) -> Router {
Router::new()
.route("/health", get(health))
.route("/api/agents", get(agents::list))
.route("/api/agents/{pc_id}", get(agents::detail))
.route("/api/agents/{pc_id}/perf", get(host_perf::perf))
.route("/api/perf/fleet", get(fleet_perf::fleet))
.route("/api/perf/top", get(fleet_perf::top))
.route("/api/obs_events", get(obs_events::list))
.route("/api/obs_events/kinds", get(obs_events::kinds))
.route("/api/obs_events/recent", get(obs_events::recent))
.route(
"/api/perf/active-investigations",
get(fleet_perf::active_investigations),
)
.route(
"/api/agents/{pc_id}/processes",
get(process_perf::processes),
)
.route(
"/api/agents/{pc_id}/processes/timeline",
get(process_perf::timeline),
)
.route(
"/api/agents/{pc_id}/groups",
get(agent_groups::list_groups)
.put(agent_groups::set_groups)
.post(agent_groups::add_group),
)
.route(
"/api/agents/{pc_id}/groups/{group}",
delete(agent_groups::remove_group),
)
.route(
"/api/agents/{pc_id}/effective_config",
get(agent_config::effective),
)
.route(
"/api/config",
get(agent_config::get_global).put(agent_config::put_global),
)
.route(
"/api/groups/{name}/config",
get(agent_config::get_group)
.put(agent_config::put_group)
.delete(agent_config::delete_group),
)
.route(
"/api/pcs/{pc_id}/config",
get(agent_config::get_pc)
.put(agent_config::put_pc)
.delete(agent_config::delete_pc),
)
.route("/api/results", get(results::list))
.route("/api/results/{result_id}", get(results::detail))
.route("/api/executions", get(executions::list))
.route("/api/executions/{exec_id}", get(executions::detail))
.route("/api/audit", get(audit::list))
.route("/api/exec/{job_id}", post(exec::create))
.route(
"/api/schedules",
get(schedules::list).post(schedules::create),
)
.route("/api/schedules/{id}", delete(schedules::delete))
.route("/api/schedules/{id}/disable", post(schedules::disable))
.route("/api/run", post(run::run))
.route("/api/agents/{pc_id}/ping", post(run::ping))
.route("/api/scripts/status", get(scripts::list_status))
.route("/api/scripts/{cmd_id}/revoke", post(scripts::revoke))
.route("/api/scripts/{cmd_id}/unrevoke", post(scripts::unrevoke))
.route("/api/jobs", get(jobs::list).post(jobs::create))
.route("/api/jobs/{id}", delete(jobs::delete))
.route("/api/jobs/{id}/yaml", get(jobs::get_yaml))
.route("/api/jobs/{job_id}/kill", post(jobs::kill))
.route("/api/schedules/{id}/yaml", get(schedules::get_yaml))
.route("/api/schemas/manifest.json", get(schemas::manifest_schema))
.route("/api/schemas/schedule.json", get(schemas::schedule_schema))
.route("/api/jetstream/status", get(jetstream_status::status))
.route("/api/health/fleet", get(health::fleet))
.route("/api/health/scan_durations", get(health::scan_durations))
.route("/api/inventory/jobs", get(inventory::list_jobs))
.route(
"/api/inventory/by-job/{manifest_id}",
get(inventory::list_for_job),
)
.route(
"/api/inventory/{manifest_id}/search/{field}",
get(inventory::search),
)
.route(
"/api/inventory/{manifest_id}/history/pc/{pc_id}",
get(inventory::history_for_pc),
)
.route(
"/api/inventory/{manifest_id}/history/search",
get(inventory::fleet_history_search),
)
.route(
"/api/inventory/{manifest_id}/history/first_seen",
get(inventory::first_seen),
)
.route("/api/inventory/{pc_id}", get(inventory::list_for_pc))
.route("/api/agents/{pc_id}/logs", get(agent_logs::tail))
.route("/api/agents/releases", get(agent_releases::list_releases))
.route(
"/api/agents/releases/{version}",
delete(agent_releases::delete_release),
)
.route("/api/agents/rollout", post(agent_releases::rollout))
.route(
"/api/agents/publish",
post(agent_releases::publish).layer(DefaultBodyLimit::max(PUBLISH_BODY_LIMIT)),
)
.route("/api/app-packages", get(app_packages::list_packages))
.route(
"/api/app-packages/{name}/{version}",
get(app_packages::download)
.post(app_packages::publish)
.delete(app_packages::delete_package)
.layer(DefaultBodyLimit::max(APP_PACKAGE_BODY_LIMIT)),
)
.route("/api/script-objects", get(script_objects::list_objects))
.route(
"/api/script-objects/{name}/{version}",
get(script_objects::download)
.post(script_objects::publish)
.delete(script_objects::delete_object)
.layer(DefaultBodyLimit::max(SCRIPT_OBJECT_BODY_LIMIT)),
)
.with_state(state)
.fallback(crate::web::serve)
}
async fn health() -> &'static str {
"ok"
}