Created comprehensive VPN setup tooling for Peaceful Spirit L2TP/IPsec connection and enhanced agent documentation framework. VPN Configuration (PST-NW-VPN): - Setup-PST-L2TP-VPN.ps1: Automated L2TP/IPsec setup with split-tunnel and DNS - Connect-PST-VPN.ps1: Connection helper with PPP adapter detection, DNS (192.168.0.2), and route config (192.168.0.0/24) - Connect-PST-VPN-Standalone.ps1: Self-contained connection script for remote deployment - Fix-PST-VPN-Auth.ps1: Authentication troubleshooting for CHAP/MSChapv2 - Diagnose-VPN-Interface.ps1: Comprehensive VPN interface and routing diagnostic - Quick-Test-VPN.ps1: Fast connectivity verification (DNS/router/routes) - Add-PST-VPN-Route-Manual.ps1: Manual route configuration helper - vpn-connect.bat, vpn-disconnect.bat: Simple batch file shortcuts - OpenVPN config files (Windows-compatible, abandoned for L2TP) Key VPN Implementation Details: - L2TP creates PPP adapter with connection name as interface description - UniFi auto-configures DNS (192.168.0.2) but requires manual route to 192.168.0.0/24 - Split-tunnel enabled (only remote traffic through VPN) - All-user connection for pre-login auto-connect via scheduled task - Authentication: CHAP + MSChapv2 for UniFi compatibility Agent Documentation: - AGENT_QUICK_REFERENCE.md: Quick reference for all specialized agents - documentation-squire.md: Documentation and task management specialist agent - Updated all agent markdown files with standardized formatting Project Organization: - Moved conversation logs to dedicated directories (guru-connect-conversation-logs, guru-rmm-conversation-logs) - Cleaned up old session JSONL files from projects/msp-tools/ - Added guru-connect infrastructure (agent, dashboard, proto, scripts, .gitea workflows) - Added guru-rmm server components and deployment configs Technical Notes: - VPN IP pool: 192.168.4.x (client gets 192.168.4.6) - Remote network: 192.168.0.0/24 (router at 192.168.0.10) - PSK: rrClvnmUeXEFo90Ol+z7tfsAZHeSK6w7 - Credentials: pst-admin / 24Hearts$ Files: 15 VPN scripts, 2 agent docs, conversation log reorganization, guru-connect/guru-rmm infrastructure additions Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
158 lines
4.0 KiB
Rust
158 lines
4.0 KiB
Rust
//! Client (organization) database operations
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use sqlx::PgPool;
|
|
use uuid::Uuid;
|
|
|
|
/// Client record from database
|
|
#[derive(Debug, Clone, sqlx::FromRow)]
|
|
pub struct Client {
|
|
pub id: Uuid,
|
|
pub name: String,
|
|
pub code: Option<String>,
|
|
pub notes: Option<String>,
|
|
pub is_active: bool,
|
|
pub created_at: chrono::DateTime<chrono::Utc>,
|
|
pub updated_at: chrono::DateTime<chrono::Utc>,
|
|
}
|
|
|
|
/// Client response for API
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct ClientResponse {
|
|
pub id: Uuid,
|
|
pub name: String,
|
|
pub code: Option<String>,
|
|
pub notes: Option<String>,
|
|
pub is_active: bool,
|
|
pub created_at: chrono::DateTime<chrono::Utc>,
|
|
pub site_count: Option<i64>,
|
|
pub agent_count: Option<i64>,
|
|
}
|
|
|
|
impl From<Client> for ClientResponse {
|
|
fn from(c: Client) -> Self {
|
|
ClientResponse {
|
|
id: c.id,
|
|
name: c.name,
|
|
code: c.code,
|
|
notes: c.notes,
|
|
is_active: c.is_active,
|
|
created_at: c.created_at,
|
|
site_count: None,
|
|
agent_count: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Data for creating a new client
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct CreateClient {
|
|
pub name: String,
|
|
pub code: Option<String>,
|
|
pub notes: Option<String>,
|
|
}
|
|
|
|
/// Data for updating a client
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct UpdateClient {
|
|
pub name: Option<String>,
|
|
pub code: Option<String>,
|
|
pub notes: Option<String>,
|
|
pub is_active: Option<bool>,
|
|
}
|
|
|
|
/// Create a new client
|
|
pub async fn create_client(pool: &PgPool, client: CreateClient) -> Result<Client, sqlx::Error> {
|
|
sqlx::query_as::<_, Client>(
|
|
r#"
|
|
INSERT INTO clients (name, code, notes)
|
|
VALUES ($1, $2, $3)
|
|
RETURNING *
|
|
"#,
|
|
)
|
|
.bind(&client.name)
|
|
.bind(&client.code)
|
|
.bind(&client.notes)
|
|
.fetch_one(pool)
|
|
.await
|
|
}
|
|
|
|
/// Get a client by ID
|
|
pub async fn get_client_by_id(pool: &PgPool, id: Uuid) -> Result<Option<Client>, sqlx::Error> {
|
|
sqlx::query_as::<_, Client>("SELECT * FROM clients WHERE id = $1")
|
|
.bind(id)
|
|
.fetch_optional(pool)
|
|
.await
|
|
}
|
|
|
|
/// Get all clients
|
|
pub async fn get_all_clients(pool: &PgPool) -> Result<Vec<Client>, sqlx::Error> {
|
|
sqlx::query_as::<_, Client>("SELECT * FROM clients ORDER BY name")
|
|
.fetch_all(pool)
|
|
.await
|
|
}
|
|
|
|
/// Get all clients with counts
|
|
#[derive(Debug, Clone, sqlx::FromRow)]
|
|
pub struct ClientWithCounts {
|
|
pub id: Uuid,
|
|
pub name: String,
|
|
pub code: Option<String>,
|
|
pub notes: Option<String>,
|
|
pub is_active: bool,
|
|
pub created_at: chrono::DateTime<chrono::Utc>,
|
|
pub updated_at: chrono::DateTime<chrono::Utc>,
|
|
pub site_count: i64,
|
|
pub agent_count: i64,
|
|
}
|
|
|
|
pub async fn get_all_clients_with_counts(pool: &PgPool) -> Result<Vec<ClientWithCounts>, sqlx::Error> {
|
|
sqlx::query_as::<_, ClientWithCounts>(
|
|
r#"
|
|
SELECT
|
|
c.*,
|
|
COALESCE((SELECT COUNT(*) FROM sites WHERE client_id = c.id), 0) as site_count,
|
|
COALESCE((SELECT COUNT(*) FROM agents a JOIN sites s ON a.site_id = s.id WHERE s.client_id = c.id), 0) as agent_count
|
|
FROM clients c
|
|
ORDER BY c.name
|
|
"#,
|
|
)
|
|
.fetch_all(pool)
|
|
.await
|
|
}
|
|
|
|
/// Update a client
|
|
pub async fn update_client(
|
|
pool: &PgPool,
|
|
id: Uuid,
|
|
update: UpdateClient,
|
|
) -> Result<Option<Client>, sqlx::Error> {
|
|
sqlx::query_as::<_, Client>(
|
|
r#"
|
|
UPDATE clients
|
|
SET name = COALESCE($1, name),
|
|
code = COALESCE($2, code),
|
|
notes = COALESCE($3, notes),
|
|
is_active = COALESCE($4, is_active)
|
|
WHERE id = $5
|
|
RETURNING *
|
|
"#,
|
|
)
|
|
.bind(&update.name)
|
|
.bind(&update.code)
|
|
.bind(&update.notes)
|
|
.bind(&update.is_active)
|
|
.bind(id)
|
|
.fetch_optional(pool)
|
|
.await
|
|
}
|
|
|
|
/// Delete a client
|
|
pub async fn delete_client(pool: &PgPool, id: Uuid) -> Result<bool, sqlx::Error> {
|
|
let result = sqlx::query("DELETE FROM clients WHERE id = $1")
|
|
.bind(id)
|
|
.execute(pool)
|
|
.await?;
|
|
Ok(result.rows_affected() > 0)
|
|
}
|