chore: full stack stability and migration fixes, plus react UI progress
This commit is contained in:
511
control_plane/src/templates.rs
Normal file
511
control_plane/src/templates.rs
Normal file
@@ -0,0 +1,511 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use anyhow::Result;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TemplateConfig {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub version: String,
|
||||
pub min_hetzner_plan: String,
|
||||
#[serde(rename = "min_hetzner_plan_num")]
|
||||
pub min_hetzner_plan_num: u32,
|
||||
#[serde(rename = "estimated_monthly_cost")]
|
||||
pub estimated_monthly_cost: f64,
|
||||
pub services: Vec<ServiceConfig>,
|
||||
pub requirements: TemplateRequirements,
|
||||
#[serde(rename = "estimated_time_minutes")]
|
||||
pub estimated_time_minutes: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServiceConfig {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub image: String,
|
||||
pub ports: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub environment: Vec<EnvVar>,
|
||||
#[serde(default)]
|
||||
pub volumes: Vec<String>,
|
||||
#[serde(rename = "resource_profile", default)]
|
||||
pub resource_profile: String,
|
||||
#[serde(rename = "has_persistent_data", default)]
|
||||
pub has_persistent_data: bool,
|
||||
#[serde(rename = "is_critical", default)]
|
||||
pub is_critical: bool,
|
||||
#[serde(default)]
|
||||
pub optional: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct EnvVar {
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TemplateRequirements {
|
||||
#[serde(rename = "min_nodes")]
|
||||
pub min_nodes: i32,
|
||||
#[serde(rename = "max_nodes")]
|
||||
pub max_nodes: i32,
|
||||
#[serde(rename = "supports_ha", default)]
|
||||
pub supports_ha: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct TemplateValidation {
|
||||
pub valid: bool,
|
||||
pub warnings: Vec<String>,
|
||||
}
|
||||
|
||||
impl TemplateConfig {
|
||||
/// Load all available templates
|
||||
pub async fn all_templates() -> Vec<TemplateConfig> {
|
||||
vec![
|
||||
Self::db_node_template(),
|
||||
Self::worker_node_template(),
|
||||
Self::control_plane_node_template(),
|
||||
Self::monitoring_node_template(),
|
||||
Self::worker_db_combo_template(),
|
||||
Self::worker_monitor_combo_template(),
|
||||
Self::all_in_one_template(),
|
||||
]
|
||||
}
|
||||
|
||||
/// Load template by ID
|
||||
pub async fn from_template_id(id: &str) -> Result<Self> {
|
||||
let templates = Self::all_templates().await;
|
||||
templates.into_iter()
|
||||
.find(|t| t.id == id)
|
||||
.ok_or_else(|| anyhow::anyhow!("Template not found: {}", id))
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> TemplateValidation {
|
||||
let mut warnings = Vec::new();
|
||||
|
||||
if self.min_hetzner_plan_num < 11 {
|
||||
warnings.push("Plan CX11 is minimum recommended".to_string());
|
||||
}
|
||||
|
||||
if self.services.is_empty() {
|
||||
warnings.push("Template has no services".to_string());
|
||||
}
|
||||
|
||||
if self.requirements.max_nodes > 1 && !self.requirements.supports_ha {
|
||||
warnings.push("Multiple nodes but HA not supported".to_string());
|
||||
}
|
||||
|
||||
TemplateValidation {
|
||||
valid: warnings.is_empty(),
|
||||
warnings,
|
||||
}
|
||||
}
|
||||
|
||||
// Template definitions
|
||||
|
||||
fn db_node_template() -> Self {
|
||||
Self {
|
||||
id: "db-node".to_string(),
|
||||
name: "Database Node".to_string(),
|
||||
description: "PostgreSQL with Patroni for HA clustering".to_string(),
|
||||
version: "1.0".to_string(),
|
||||
min_hetzner_plan: "CX21".to_string(),
|
||||
min_hetzner_plan_num: 21,
|
||||
estimated_monthly_cost: 6.94,
|
||||
estimated_time_minutes: 15,
|
||||
services: vec![
|
||||
ServiceConfig {
|
||||
id: "postgresql".to_string(),
|
||||
name: "PostgreSQL".to_string(),
|
||||
image: "registry.gitlab.com/postgres-ai/postgresql-autobase/patroni:3.0.2".to_string(),
|
||||
ports: vec!["5432:5432".to_string(), "8008:8008".to_string()],
|
||||
environment: vec![],
|
||||
volumes: vec!["postgres_data:/var/lib/postgresql/data".to_string()],
|
||||
resource_profile: "balanced".to_string(),
|
||||
has_persistent_data: true,
|
||||
is_critical: true,
|
||||
optional: false,
|
||||
},
|
||||
ServiceConfig {
|
||||
id: "etcd".to_string(),
|
||||
name: "etcd".to_string(),
|
||||
image: "quay.io/coreos/etcd:v3.5.9".to_string(),
|
||||
ports: vec!["2379:2379".to_string(), "2380:2380".to_string()],
|
||||
environment: vec![],
|
||||
volumes: vec!["etcd_data:/etcd-data".to_string()],
|
||||
resource_profile: "minimal".to_string(),
|
||||
has_persistent_data: true,
|
||||
is_critical: true,
|
||||
optional: false,
|
||||
},
|
||||
ServiceConfig {
|
||||
id: "haproxy".to_string(),
|
||||
name: "HAProxy".to_string(),
|
||||
image: "haproxy:2.8-alpine".to_string(),
|
||||
ports: vec!["5433:5433".to_string()],
|
||||
environment: vec![],
|
||||
volumes: vec![],
|
||||
resource_profile: "minimal".to_string(),
|
||||
has_persistent_data: false,
|
||||
is_critical: false,
|
||||
optional: false,
|
||||
},
|
||||
],
|
||||
requirements: TemplateRequirements {
|
||||
min_nodes: 3,
|
||||
max_nodes: 7,
|
||||
supports_ha: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn worker_node_template() -> Self {
|
||||
Self {
|
||||
id: "worker-node".to_string(),
|
||||
name: "Worker Node".to_string(),
|
||||
description: "API worker nodes for horizontal scaling".to_string(),
|
||||
version: "1.0".to_string(),
|
||||
min_hetzner_plan: "CX11".to_string(),
|
||||
min_hetzner_plan_num: 11,
|
||||
estimated_monthly_cost: 3.69,
|
||||
estimated_time_minutes: 10,
|
||||
services: vec![
|
||||
ServiceConfig {
|
||||
id: "worker".to_string(),
|
||||
name: "MadBase Worker".to_string(),
|
||||
image: "madbase/worker:latest".to_string(),
|
||||
ports: vec!["8002:8002".to_string()],
|
||||
environment: vec![],
|
||||
volumes: vec![],
|
||||
resource_profile: "cpu_intensive".to_string(),
|
||||
has_persistent_data: false,
|
||||
is_critical: true,
|
||||
optional: false,
|
||||
},
|
||||
ServiceConfig {
|
||||
id: "vmagent".to_string(),
|
||||
name: "VictoriaMetrics Agent".to_string(),
|
||||
image: "victoriametrics/vmagent:latest".to_string(),
|
||||
ports: vec!["8429:8429".to_string()],
|
||||
environment: vec![],
|
||||
volumes: vec!["./config/vmagent.yml:/etc/vmagent/prometheus.yml:ro".to_string()],
|
||||
resource_profile: "minimal".to_string(),
|
||||
has_persistent_data: false,
|
||||
is_critical: false,
|
||||
optional: true,
|
||||
},
|
||||
],
|
||||
requirements: TemplateRequirements {
|
||||
min_nodes: 1,
|
||||
max_nodes: 20,
|
||||
supports_ha: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn control_plane_node_template() -> Self {
|
||||
Self {
|
||||
id: "control-plane-node".to_string(),
|
||||
name: "Control Plane Node".to_string(),
|
||||
description: "Management APIs and Studio UI".to_string(),
|
||||
version: "1.0".to_string(),
|
||||
min_hetzner_plan: "CX11".to_string(),
|
||||
min_hetzner_plan_num: 11,
|
||||
estimated_monthly_cost: 3.69,
|
||||
estimated_time_minutes: 12,
|
||||
services: vec![
|
||||
ServiceConfig {
|
||||
id: "proxy".to_string(),
|
||||
name: "Gateway Proxy".to_string(),
|
||||
image: "madbase/proxy:latest".to_string(),
|
||||
ports: vec!["8080:8080".to_string()],
|
||||
environment: vec![],
|
||||
volumes: vec![],
|
||||
resource_profile: "balanced".to_string(),
|
||||
has_persistent_data: false,
|
||||
is_critical: true,
|
||||
optional: false,
|
||||
},
|
||||
ServiceConfig {
|
||||
id: "control".to_string(),
|
||||
name: "Control Plane API".to_string(),
|
||||
image: "madbase/control:latest".to_string(),
|
||||
ports: vec!["8001:8001".to_string()],
|
||||
environment: vec![],
|
||||
volumes: vec![],
|
||||
resource_profile: "balanced".to_string(),
|
||||
has_persistent_data: false,
|
||||
is_critical: true,
|
||||
optional: false,
|
||||
},
|
||||
ServiceConfig {
|
||||
id: "grafana".to_string(),
|
||||
name: "Grafana".to_string(),
|
||||
image: "grafana/grafana:latest".to_string(),
|
||||
ports: vec!["3030:3030".to_string()],
|
||||
environment: vec![],
|
||||
volumes: vec!["grafana_data:/var/lib/grafana".to_string()],
|
||||
resource_profile: "balanced".to_string(),
|
||||
has_persistent_data: true,
|
||||
is_critical: false,
|
||||
optional: true,
|
||||
},
|
||||
],
|
||||
requirements: TemplateRequirements {
|
||||
min_nodes: 1,
|
||||
max_nodes: 2,
|
||||
supports_ha: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn monitoring_node_template() -> Self {
|
||||
Self {
|
||||
id: "monitoring-node".to_string(),
|
||||
name: "Monitoring Node".to_string(),
|
||||
description: "Centralized metrics and logging".to_string(),
|
||||
version: "1.0".to_string(),
|
||||
min_hetzner_plan: "CX11".to_string(),
|
||||
min_hetzner_plan_num: 11,
|
||||
estimated_monthly_cost: 3.69,
|
||||
estimated_time_minutes: 10,
|
||||
services: vec![
|
||||
ServiceConfig {
|
||||
id: "victoriametrics".to_string(),
|
||||
name: "VictoriaMetrics".to_string(),
|
||||
image: "victoriametrics/victoria-metrics:latest".to_string(),
|
||||
ports: vec!["8428:8428".to_string()],
|
||||
environment: vec![],
|
||||
volumes: vec!["vm_data:/victoria-metrics-data".to_string()],
|
||||
resource_profile: "balanced".to_string(),
|
||||
has_persistent_data: true,
|
||||
is_critical: false,
|
||||
optional: false,
|
||||
},
|
||||
ServiceConfig {
|
||||
id: "loki".to_string(),
|
||||
name: "Loki".to_string(),
|
||||
image: "grafana/loki:latest".to_string(),
|
||||
ports: vec!["3100:3100".to_string()],
|
||||
environment: vec![],
|
||||
volumes: vec!["loki_data:/loki".to_string()],
|
||||
resource_profile: "balanced".to_string(),
|
||||
has_persistent_data: true,
|
||||
is_critical: false,
|
||||
optional: false,
|
||||
},
|
||||
],
|
||||
requirements: TemplateRequirements {
|
||||
min_nodes: 1,
|
||||
max_nodes: 2,
|
||||
supports_ha: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn worker_db_combo_template() -> Self {
|
||||
Self {
|
||||
id: "worker-db-combo".to_string(),
|
||||
name: "Worker + Database Combo".to_string(),
|
||||
description: "Combined worker and database node for smaller deployments".to_string(),
|
||||
version: "1.0".to_string(),
|
||||
min_hetzner_plan: "CX31".to_string(),
|
||||
min_hetzner_plan_num: 31,
|
||||
estimated_monthly_cost: 14.21,
|
||||
estimated_time_minutes: 20,
|
||||
services: vec![
|
||||
ServiceConfig {
|
||||
id: "postgresql".to_string(),
|
||||
name: "PostgreSQL".to_string(),
|
||||
image: "registry.gitlab.com/postgres-ai/postgresql-autobase/patroni:3.0.2".to_string(),
|
||||
ports: vec!["5432:5432".to_string(), "8008:8008".to_string()],
|
||||
environment: vec![],
|
||||
volumes: vec!["postgres_data:/var/lib/postgresql/data".to_string()],
|
||||
resource_profile: "balanced".to_string(),
|
||||
has_persistent_data: true,
|
||||
is_critical: true,
|
||||
optional: false,
|
||||
},
|
||||
ServiceConfig {
|
||||
id: "etcd".to_string(),
|
||||
name: "etcd".to_string(),
|
||||
image: "quay.io/coreos/etcd:v3.5.9".to_string(),
|
||||
ports: vec!["2379:2379".to_string(), "2380:2380".to_string()],
|
||||
environment: vec![],
|
||||
volumes: vec!["etcd_data:/etcd-data".to_string()],
|
||||
resource_profile: "minimal".to_string(),
|
||||
has_persistent_data: true,
|
||||
is_critical: true,
|
||||
optional: false,
|
||||
},
|
||||
ServiceConfig {
|
||||
id: "haproxy".to_string(),
|
||||
name: "HAProxy".to_string(),
|
||||
image: "haproxy:2.8-alpine".to_string(),
|
||||
ports: vec!["5433:5433".to_string()],
|
||||
environment: vec![],
|
||||
volumes: vec![],
|
||||
resource_profile: "minimal".to_string(),
|
||||
has_persistent_data: false,
|
||||
is_critical: false,
|
||||
optional: false,
|
||||
},
|
||||
ServiceConfig {
|
||||
id: "worker".to_string(),
|
||||
name: "MadBase Worker".to_string(),
|
||||
image: "madbase/worker:latest".to_string(),
|
||||
ports: vec!["8002:8002".to_string()],
|
||||
environment: vec![],
|
||||
volumes: vec![],
|
||||
resource_profile: "cpu_intensive".to_string(),
|
||||
has_persistent_data: false,
|
||||
is_critical: true,
|
||||
optional: false,
|
||||
},
|
||||
ServiceConfig {
|
||||
id: "vmagent".to_string(),
|
||||
name: "VictoriaMetrics Agent".to_string(),
|
||||
image: "victoriametrics/vmagent:latest".to_string(),
|
||||
ports: vec!["8429:8429".to_string()],
|
||||
environment: vec![],
|
||||
volumes: vec!["./config/vmagent.yml:/etc/vmagent/prometheus.yml:ro".to_string()],
|
||||
resource_profile: "minimal".to_string(),
|
||||
has_persistent_data: false,
|
||||
is_critical: false,
|
||||
optional: false,
|
||||
},
|
||||
],
|
||||
requirements: TemplateRequirements {
|
||||
min_nodes: 1,
|
||||
max_nodes: 2,
|
||||
supports_ha: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn worker_monitor_combo_template() -> Self {
|
||||
Self {
|
||||
id: "worker-monitor-combo".to_string(),
|
||||
name: "Worker + Monitoring Combo".to_string(),
|
||||
description: "Worker node with local VictoriaMetrics and Loki".to_string(),
|
||||
version: "1.0".to_string(),
|
||||
min_hetzner_plan: "CX21".to_string(),
|
||||
min_hetzner_plan_num: 21,
|
||||
estimated_monthly_cost: 6.94,
|
||||
estimated_time_minutes: 15,
|
||||
services: vec![
|
||||
ServiceConfig {
|
||||
id: "worker".to_string(),
|
||||
name: "MadBase Worker".to_string(),
|
||||
image: "madbase/worker:latest".to_string(),
|
||||
ports: vec!["8002:8002".to_string()],
|
||||
environment: vec![],
|
||||
volumes: vec![],
|
||||
resource_profile: "cpu_intensive".to_string(),
|
||||
has_persistent_data: false,
|
||||
is_critical: true,
|
||||
optional: false,
|
||||
},
|
||||
ServiceConfig {
|
||||
id: "victoriametrics".to_string(),
|
||||
name: "VictoriaMetrics".to_string(),
|
||||
image: "victoriametrics/victoria-metrics:latest".to_string(),
|
||||
ports: vec!["8428:8428".to_string()],
|
||||
environment: vec![],
|
||||
volumes: vec!["vm_data:/victoria-metrics-data".to_string()],
|
||||
resource_profile: "balanced".to_string(),
|
||||
has_persistent_data: true,
|
||||
is_critical: false,
|
||||
optional: false,
|
||||
},
|
||||
ServiceConfig {
|
||||
id: "loki".to_string(),
|
||||
name: "Loki".to_string(),
|
||||
image: "grafana/loki:latest".to_string(),
|
||||
ports: vec!["3100:3100".to_string()],
|
||||
environment: vec![],
|
||||
volumes: vec!["loki_data:/loki".to_string()],
|
||||
resource_profile: "balanced".to_string(),
|
||||
has_persistent_data: true,
|
||||
is_critical: false,
|
||||
optional: false,
|
||||
},
|
||||
],
|
||||
requirements: TemplateRequirements {
|
||||
min_nodes: 1,
|
||||
max_nodes: 3,
|
||||
supports_ha: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn all_in_one_template() -> Self {
|
||||
Self {
|
||||
id: "all-in-one".to_string(),
|
||||
name: "All-in-One Development Node".to_string(),
|
||||
description: "Complete MadBase stack on a single server".to_string(),
|
||||
version: "1.0".to_string(),
|
||||
min_hetzner_plan: "CX41".to_string(),
|
||||
min_hetzner_plan_num: 41,
|
||||
estimated_monthly_cost: 25.60,
|
||||
estimated_time_minutes: 25,
|
||||
services: vec![
|
||||
ServiceConfig {
|
||||
id: "postgresql".to_string(),
|
||||
name: "PostgreSQL".to_string(),
|
||||
image: "registry.gitlab.com/postgres-ai/postgresql-autobase/patroni:3.0.2".to_string(),
|
||||
ports: vec!["5432:5432".to_string(), "8008:8008".to_string()],
|
||||
environment: vec![],
|
||||
volumes: vec!["postgres_data:/var/lib/postgresql/data".to_string()],
|
||||
resource_profile: "balanced".to_string(),
|
||||
has_persistent_data: true,
|
||||
is_critical: true,
|
||||
optional: false,
|
||||
},
|
||||
ServiceConfig {
|
||||
id: "worker".to_string(),
|
||||
name: "MadBase Worker".to_string(),
|
||||
image: "madbase/worker:latest".to_string(),
|
||||
ports: vec!["8002:8002".to_string()],
|
||||
environment: vec![],
|
||||
volumes: vec![],
|
||||
resource_profile: "cpu_intensive".to_string(),
|
||||
has_persistent_data: false,
|
||||
is_critical: true,
|
||||
optional: false,
|
||||
},
|
||||
ServiceConfig {
|
||||
id: "proxy".to_string(),
|
||||
name: "Gateway Proxy".to_string(),
|
||||
image: "madbase/proxy:latest".to_string(),
|
||||
ports: vec!["8080:8080".to_string()],
|
||||
environment: vec![],
|
||||
volumes: vec![],
|
||||
resource_profile: "balanced".to_string(),
|
||||
has_persistent_data: false,
|
||||
is_critical: true,
|
||||
optional: false,
|
||||
},
|
||||
ServiceConfig {
|
||||
id: "control".to_string(),
|
||||
name: "Control Plane API".to_string(),
|
||||
image: "madbase/control:latest".to_string(),
|
||||
ports: vec!["8001:8001".to_string()],
|
||||
environment: vec![],
|
||||
volumes: vec![],
|
||||
resource_profile: "balanced".to_string(),
|
||||
has_persistent_data: false,
|
||||
is_critical: true,
|
||||
optional: false,
|
||||
},
|
||||
],
|
||||
requirements: TemplateRequirements {
|
||||
min_nodes: 1,
|
||||
max_nodes: 1,
|
||||
supports_ha: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user