Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ members = [
"crates/temps-wireguard",
"crates/temps-ai-gateway",
"crates/temps-agent",
"crates/temps-compose",
"crates/temps-external-plugins",
"examples/example-plugin",
"examples/lighthouse-plugin",
Expand Down
26 changes: 26 additions & 0 deletions crates/temps-auth/src/permissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,12 @@ pub enum Permission {
AiGatewayRead,
AiGatewayWrite,
AiGatewayExecute,

// Compose Stacks permissions
StacksRead,
StacksWrite,
StacksDelete,
StacksCreate,
}

impl fmt::Display for Permission {
Expand Down Expand Up @@ -350,6 +356,10 @@ impl fmt::Display for Permission {
Permission::AiGatewayRead => "ai_gateway:read",
Permission::AiGatewayWrite => "ai_gateway:write",
Permission::AiGatewayExecute => "ai_gateway:execute",
Permission::StacksRead => "stacks:read",
Permission::StacksWrite => "stacks:write",
Permission::StacksDelete => "stacks:delete",
Permission::StacksCreate => "stacks:create",
};
write!(f, "{}", name)
}
Expand Down Expand Up @@ -490,6 +500,10 @@ impl Permission {
"ai_gateway:read" => Some(Permission::AiGatewayRead),
"ai_gateway:write" => Some(Permission::AiGatewayWrite),
"ai_gateway:execute" => Some(Permission::AiGatewayExecute),
"stacks:read" => Some(Permission::StacksRead),
"stacks:write" => Some(Permission::StacksWrite),
"stacks:delete" => Some(Permission::StacksDelete),
"stacks:create" => Some(Permission::StacksCreate),
_ => None,
}
}
Expand Down Expand Up @@ -627,6 +641,10 @@ impl Permission {
Permission::AiGatewayRead,
Permission::AiGatewayWrite,
Permission::AiGatewayExecute,
Permission::StacksRead,
Permission::StacksWrite,
Permission::StacksDelete,
Permission::StacksCreate,
]
}
}
Expand Down Expand Up @@ -819,6 +837,10 @@ impl Role {
Permission::AiGatewayRead,
Permission::AiGatewayWrite,
Permission::AiGatewayExecute,
Permission::StacksRead,
Permission::StacksWrite,
Permission::StacksDelete,
Permission::StacksCreate,
],
Role::User => &[
Permission::ProjectsRead,
Expand Down Expand Up @@ -917,6 +939,9 @@ impl Role {
Permission::OtelWrite,
Permission::AiGatewayRead,
Permission::AiGatewayExecute,
Permission::StacksRead,
Permission::StacksWrite,
Permission::StacksCreate,
],
Role::Reader => &[
Permission::ProjectsRead,
Expand Down Expand Up @@ -956,6 +981,7 @@ impl Role {
Permission::StatusPageRead,
Permission::OtelRead,
Permission::AiGatewayRead,
Permission::StacksRead,
],
Role::Mcp => &[
Permission::ProjectsRead,
Expand Down
1 change: 1 addition & 0 deletions crates/temps-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ temps-analytics-session-replay = { path = "../temps-analytics-session-replay" }
temps-audit = { path = "../temps-audit" }
temps-auth = { path = "../temps-auth" }
temps-backup = { path = "../temps-backup" }
temps-compose = { path = "../temps-compose" }
temps-config = { path = "../temps-config" }
temps-core = { path = "../temps-core" }
temps-database = { path = "../temps-database" }
Expand Down
6 changes: 6 additions & 0 deletions crates/temps-cli/src/commands/serve/console.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use temps_audit::AuditPlugin;
use temps_auth::{ApiKeyPlugin, AuthPlugin};
use temps_backup::BackupPlugin;
use temps_blob::BlobPlugin;
use temps_compose::ComposePlugin;
use temps_config::ConfigPlugin;
use temps_config::ServerConfig;
use temps_core::plugin::PluginManager;
Expand Down Expand Up @@ -848,6 +849,11 @@ pub async fn start_console_api(params: ConsoleApiParams) -> anyhow::Result<()> {
let backup_plugin = Box::new(BackupPlugin::new());
plugin_manager.register_plugin(backup_plugin);

// ComposePlugin - provides Docker Compose stack management (depends on database and audit services)
debug!("Registering ComposePlugin");
let compose_plugin = Box::new(ComposePlugin::new());
plugin_manager.register_plugin(compose_plugin);

// AI Gateway Plugin - provides AI provider key management and OpenAI-compatible API
debug!("Registering AiGatewayPlugin");
let ai_gateway_plugin = Box::new(temps_ai_gateway::AiGatewayPlugin::new());
Expand Down
28 changes: 28 additions & 0 deletions crates/temps-compose/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[package]
name = "temps-compose"
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
repository.workspace = true
homepage.workspace = true

[dependencies]
temps-auth = { path = "../temps-auth" }
temps-core = { path = "../temps-core" }
temps-entities = { path = "../temps-entities" }
sea-orm = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
anyhow = { workspace = true }
chrono = { workspace = true }
tokio = { workspace = true }
utoipa = { workspace = true }
axum = { workspace = true }
async-trait = { workspace = true }

[dev-dependencies]
sea-orm = { workspace = true, features = ["mock"] }
tokio = { workspace = true, features = ["test-util", "macros"] }
125 changes: 125 additions & 0 deletions crates/temps-compose/src/handlers/audit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use anyhow::Result;
use serde::Serialize;
pub use temps_core::AuditContext;
use temps_core::AuditOperation;

#[derive(Debug, Clone, Serialize)]
pub struct StackCreatedAudit {
pub context: AuditContext,
pub stack_id: i32,
pub name: String,
}

impl AuditOperation for StackCreatedAudit {
fn operation_type(&self) -> String {
"COMPOSE_STACK_CREATED".to_string()
}

fn user_id(&self) -> i32 {
self.context.user_id
}

fn ip_address(&self) -> Option<String> {
self.context.ip_address.clone()
}

fn user_agent(&self) -> &str {
&self.context.user_agent
}

fn serialize(&self) -> Result<String> {
serde_json::to_string(self)
.map_err(|e| anyhow::anyhow!("Failed to serialize audit operation {}", e))
}
}

#[derive(Debug, Clone, Serialize)]
pub struct StackUpdatedAudit {
pub context: AuditContext,
pub stack_id: i32,
pub name: String,
}

impl AuditOperation for StackUpdatedAudit {
fn operation_type(&self) -> String {
"COMPOSE_STACK_UPDATED".to_string()
}

fn user_id(&self) -> i32 {
self.context.user_id
}

fn ip_address(&self) -> Option<String> {
self.context.ip_address.clone()
}

fn user_agent(&self) -> &str {
&self.context.user_agent
}

fn serialize(&self) -> Result<String> {
serde_json::to_string(self)
.map_err(|e| anyhow::anyhow!("Failed to serialize audit operation {}", e))
}
}

#[derive(Debug, Clone, Serialize)]
pub struct StackDeletedAudit {
pub context: AuditContext,
pub stack_id: i32,
pub name: String,
}

impl AuditOperation for StackDeletedAudit {
fn operation_type(&self) -> String {
"COMPOSE_STACK_DELETED".to_string()
}

fn user_id(&self) -> i32 {
self.context.user_id
}

fn ip_address(&self) -> Option<String> {
self.context.ip_address.clone()
}

fn user_agent(&self) -> &str {
&self.context.user_agent
}

fn serialize(&self) -> Result<String> {
serde_json::to_string(self)
.map_err(|e| anyhow::anyhow!("Failed to serialize audit operation {}", e))
}
}

#[derive(Debug, Clone, Serialize)]
pub struct StackStateChangedAudit {
pub context: AuditContext,
pub stack_id: i32,
pub name: String,
pub new_state: String,
}

impl AuditOperation for StackStateChangedAudit {
fn operation_type(&self) -> String {
"COMPOSE_STACK_STATE_CHANGED".to_string()
}

fn user_id(&self) -> i32 {
self.context.user_id
}

fn ip_address(&self) -> Option<String> {
self.context.ip_address.clone()
}

fn user_agent(&self) -> &str {
&self.context.user_agent
}

fn serialize(&self) -> Result<String> {
serde_json::to_string(self)
.map_err(|e| anyhow::anyhow!("Failed to serialize audit operation {}", e))
}
}
Loading
Loading