TBM Configuration Management System
- Config versioning with approval workflow (Draft/Approved/Archived) - Fleet management with real-time machine status - Deploy configs to machines with status tracking - Full audit logging for compliance - React dashboard + FastAPI backend + machine simulator
This commit is contained in:
21
backend/.env.example
Normal file
21
backend/.env.example
Normal file
@@ -0,0 +1,21 @@
|
||||
# TBM Config Manager - Environment Variables
|
||||
# Copy this file to .env and update values for your environment
|
||||
|
||||
# Demo credentials - CHANGE THESE FOR PRODUCTION
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=changeme
|
||||
ADMIN_NAME=Admin User
|
||||
|
||||
ENGINEER_USERNAME=engineer
|
||||
ENGINEER_PASSWORD=changeme
|
||||
ENGINEER_NAME=Engineer User
|
||||
|
||||
OPERATOR_USERNAME=operator
|
||||
OPERATOR_PASSWORD=changeme
|
||||
OPERATOR_NAME=Operator User
|
||||
|
||||
# Database path (relative to backend directory)
|
||||
DATABASE_PATH=tbm_config.db
|
||||
|
||||
# Online threshold in seconds
|
||||
ONLINE_THRESHOLD_SECONDS=30
|
||||
1157
backend/main.py
Normal file
1157
backend/main.py
Normal file
File diff suppressed because it is too large
Load Diff
4
backend/requirements.txt
Normal file
4
backend/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
fastapi==0.109.0
|
||||
uvicorn[standard]==0.27.0
|
||||
pydantic==2.5.3
|
||||
python-dotenv==1.0.0
|
||||
343
backend/seed_data.py
Normal file
343
backend/seed_data.py
Normal file
@@ -0,0 +1,343 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Seed the database with mock data for MVP testing.
|
||||
|
||||
Usage:
|
||||
python seed_data.py # Reset database and seed with mock data
|
||||
python seed_data.py --keep # Keep existing data, only add if empty
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
from uuid import uuid4
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
DB_PATH = "tbm_config.db"
|
||||
|
||||
def create_tables(cursor):
|
||||
"""Create all tables with current schema"""
|
||||
cursor.executescript("""
|
||||
DROP TABLE IF EXISTS pending_configs;
|
||||
DROP TABLE IF EXISTS audit_logs;
|
||||
DROP TABLE IF EXISTS deployments;
|
||||
DROP TABLE IF EXISTS configurations;
|
||||
DROP TABLE IF EXISTS machines;
|
||||
|
||||
CREATE TABLE machines (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
machine_type TEXT NOT NULL,
|
||||
operational_mode TEXT DEFAULT 'active',
|
||||
current_version TEXT,
|
||||
last_seen TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE configurations (
|
||||
id TEXT PRIMARY KEY,
|
||||
version TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
machine_type TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'draft',
|
||||
config TEXT NOT NULL,
|
||||
created_by TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE deployments (
|
||||
id TEXT PRIMARY KEY,
|
||||
config_id TEXT NOT NULL,
|
||||
config_version TEXT NOT NULL,
|
||||
machine_id TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'pending',
|
||||
initiated_by TEXT NOT NULL,
|
||||
initiated_at TEXT NOT NULL,
|
||||
completed_at TEXT,
|
||||
error TEXT,
|
||||
FOREIGN KEY (config_id) REFERENCES configurations(id),
|
||||
FOREIGN KEY (machine_id) REFERENCES machines(id)
|
||||
);
|
||||
|
||||
CREATE TABLE audit_logs (
|
||||
id TEXT PRIMARY KEY,
|
||||
timestamp TEXT NOT NULL,
|
||||
user TEXT NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
resource TEXT NOT NULL,
|
||||
details TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE pending_configs (
|
||||
machine_id TEXT PRIMARY KEY,
|
||||
config_id TEXT NOT NULL,
|
||||
deployment_id TEXT NOT NULL,
|
||||
FOREIGN KEY (machine_id) REFERENCES machines(id),
|
||||
FOREIGN KEY (config_id) REFERENCES configurations(id)
|
||||
);
|
||||
""")
|
||||
print("✓ Database schema created")
|
||||
|
||||
|
||||
def seed():
|
||||
# Delete existing database file to ensure clean schema
|
||||
if os.path.exists(DB_PATH):
|
||||
os.remove(DB_PATH)
|
||||
print(f"✓ Removed old database: {DB_PATH}")
|
||||
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Create tables with current schema
|
||||
create_tables(cursor)
|
||||
conn.commit()
|
||||
|
||||
now = datetime.utcnow()
|
||||
|
||||
# ========================================================================
|
||||
# MACHINES
|
||||
# ========================================================================
|
||||
# operational_mode: active (should be online), standby (intentionally idle),
|
||||
# maintenance (under repair), decommissioned (not in use)
|
||||
machines = [
|
||||
# (id, name, machine_type, operational_mode, current_version, last_seen)
|
||||
("PRF4-001", "Prufrock 4 - Alpha", "PRUFROCK_4", "active", "2.4.1", (now - timedelta(minutes=2)).isoformat()),
|
||||
("PRF4-002", "Prufrock 4 - Beta", "PRUFROCK_4", "active", "2.4.0", (now - timedelta(minutes=5)).isoformat()),
|
||||
("PRF4-003", "Prufrock 4 - Gamma", "PRUFROCK_4", "maintenance", "2.4.1", (now - timedelta(hours=12)).isoformat()),
|
||||
("PRF5-001", "Prufrock 5 - Prototype", "PRUFROCK_5", "standby", "3.0.0", (now - timedelta(hours=48)).isoformat()),
|
||||
("PRF5-002", "Prufrock 5 - Test Unit", "PRUFROCK_5", "decommissioned", "3.0.0", (now - timedelta(days=7)).isoformat()),
|
||||
]
|
||||
|
||||
for m in machines:
|
||||
cursor.execute(
|
||||
"INSERT INTO machines (id, name, machine_type, operational_mode, current_version, last_seen) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
m
|
||||
)
|
||||
|
||||
print(f"✓ Created {len(machines)} machines")
|
||||
|
||||
# ========================================================================
|
||||
# CONFIGURATIONS
|
||||
# ========================================================================
|
||||
|
||||
# Default config template
|
||||
default_config = {
|
||||
"excavation": {
|
||||
"thrust_kn": 18500,
|
||||
"cutterhead_rpm": 2.4,
|
||||
"penetration_mm_rev": 8,
|
||||
"advance_rate_mm_min": 45
|
||||
},
|
||||
"pressure": {
|
||||
"face_pressure_bar": 2.8,
|
||||
"min_pressure_bar": 2.4,
|
||||
"max_pressure_bar": 3.4
|
||||
},
|
||||
"safety": {
|
||||
"max_thrust_kn": 26000,
|
||||
"torque_limit_pct": 85,
|
||||
"emergency_stop_enabled": True
|
||||
},
|
||||
"navigation": {
|
||||
"horizontal_tolerance_mm": 50,
|
||||
"vertical_tolerance_mm": 40,
|
||||
"steering_mode": "semi_auto"
|
||||
}
|
||||
}
|
||||
|
||||
# High performance config
|
||||
high_perf_config = {
|
||||
"excavation": {
|
||||
"thrust_kn": 22000,
|
||||
"cutterhead_rpm": 2.8,
|
||||
"penetration_mm_rev": 12,
|
||||
"advance_rate_mm_min": 65
|
||||
},
|
||||
"pressure": {
|
||||
"face_pressure_bar": 3.2,
|
||||
"min_pressure_bar": 2.8,
|
||||
"max_pressure_bar": 3.8
|
||||
},
|
||||
"safety": {
|
||||
"max_thrust_kn": 26000,
|
||||
"torque_limit_pct": 90,
|
||||
"emergency_stop_enabled": True
|
||||
},
|
||||
"navigation": {
|
||||
"horizontal_tolerance_mm": 40,
|
||||
"vertical_tolerance_mm": 30,
|
||||
"steering_mode": "full_auto"
|
||||
}
|
||||
}
|
||||
|
||||
# Soft soil config
|
||||
soft_soil_config = {
|
||||
"excavation": {
|
||||
"thrust_kn": 15000,
|
||||
"cutterhead_rpm": 2.0,
|
||||
"penetration_mm_rev": 6,
|
||||
"advance_rate_mm_min": 35
|
||||
},
|
||||
"pressure": {
|
||||
"face_pressure_bar": 2.4,
|
||||
"min_pressure_bar": 2.0,
|
||||
"max_pressure_bar": 3.0
|
||||
},
|
||||
"safety": {
|
||||
"max_thrust_kn": 26000,
|
||||
"torque_limit_pct": 75,
|
||||
"emergency_stop_enabled": True
|
||||
},
|
||||
"navigation": {
|
||||
"horizontal_tolerance_mm": 60,
|
||||
"vertical_tolerance_mm": 50,
|
||||
"steering_mode": "semi_auto"
|
||||
}
|
||||
}
|
||||
|
||||
configs = [
|
||||
(str(uuid4()), "2.4.0", "Vegas Loop - Standard", "PRUFROCK_4", "approved", json.dumps(default_config), "engineer", (now - timedelta(days=30)).isoformat()),
|
||||
(str(uuid4()), "2.4.1", "Vegas Loop - Optimized", "PRUFROCK_4", "approved", json.dumps(high_perf_config), "engineer", (now - timedelta(days=14)).isoformat()),
|
||||
(str(uuid4()), "2.4.2", "Vegas Loop - Soft Soil Zone", "PRUFROCK_4", "approved", json.dumps(soft_soil_config), "admin", (now - timedelta(days=7)).isoformat()),
|
||||
(str(uuid4()), "2.5.0", "Vegas Loop - Phase 2 Draft", "PRUFROCK_4", "draft", json.dumps(high_perf_config), "engineer", (now - timedelta(days=1)).isoformat()),
|
||||
(str(uuid4()), "3.0.0", "Prufrock 5 - Initial", "PRUFROCK_5", "approved", json.dumps(default_config), "admin", (now - timedelta(days=60)).isoformat()),
|
||||
]
|
||||
|
||||
for c in configs:
|
||||
cursor.execute(
|
||||
"INSERT INTO configurations (id, version, name, machine_type, status, config, created_by, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
c
|
||||
)
|
||||
|
||||
print(f"✓ Created {len(configs)} configurations")
|
||||
|
||||
# ========================================================================
|
||||
# DEPLOYMENTS (historical)
|
||||
# ========================================================================
|
||||
|
||||
# Get config IDs
|
||||
cursor.execute("SELECT id, version, machine_type FROM configurations")
|
||||
config_map = {(row[1], row[2]): row[0] for row in cursor.fetchall()}
|
||||
|
||||
deployments = [
|
||||
# PRF4-001 deployments
|
||||
(str(uuid4()), config_map[("2.4.0", "PRUFROCK_4")], "2.4.0", "PRF4-001", "success", "engineer",
|
||||
(now - timedelta(days=25)).isoformat(), (now - timedelta(days=25, minutes=-1)).isoformat(), None),
|
||||
(str(uuid4()), config_map[("2.4.1", "PRUFROCK_4")], "2.4.1", "PRF4-001", "success", "admin",
|
||||
(now - timedelta(days=10)).isoformat(), (now - timedelta(days=10, minutes=-1)).isoformat(), None),
|
||||
|
||||
# PRF4-002 deployments
|
||||
(str(uuid4()), config_map[("2.4.0", "PRUFROCK_4")], "2.4.0", "PRF4-002", "success", "engineer",
|
||||
(now - timedelta(days=28)).isoformat(), (now - timedelta(days=28, minutes=-1)).isoformat(), None),
|
||||
|
||||
# Failed deployment example
|
||||
(str(uuid4()), config_map[("2.4.2", "PRUFROCK_4")], "2.4.2", "PRF4-002", "failed", "operator",
|
||||
(now - timedelta(days=3)).isoformat(), (now - timedelta(days=3, minutes=-1)).isoformat(), "PLC communication timeout"),
|
||||
|
||||
# PRF5-001 deployment
|
||||
(str(uuid4()), config_map[("3.0.0", "PRUFROCK_5")], "3.0.0", "PRF5-001", "success", "admin",
|
||||
(now - timedelta(days=55)).isoformat(), (now - timedelta(days=55, minutes=-1)).isoformat(), None),
|
||||
]
|
||||
|
||||
for d in deployments:
|
||||
cursor.execute(
|
||||
"""INSERT INTO deployments (id, config_id, config_version, machine_id, status, initiated_by, initiated_at, completed_at, error)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
d
|
||||
)
|
||||
|
||||
print(f"✓ Created {len(deployments)} deployments")
|
||||
|
||||
# ========================================================================
|
||||
# AUDIT LOGS
|
||||
# ========================================================================
|
||||
|
||||
audit_logs = [
|
||||
(str(uuid4()), (now - timedelta(days=30)).isoformat(), "engineer", "config.created", "config:2.4.0", json.dumps({"name": "Vegas Loop - Standard"})),
|
||||
(str(uuid4()), (now - timedelta(days=30)).isoformat(), "engineer", "config.approved", "config:2.4.0", json.dumps({})),
|
||||
(str(uuid4()), (now - timedelta(days=25)).isoformat(), "engineer", "deployment.initiated", "deployment:PRF4-001", json.dumps({"version": "2.4.0"})),
|
||||
(str(uuid4()), (now - timedelta(days=25)).isoformat(), "PRF4-001", "deployment.success", "deployment:PRF4-001", json.dumps({"version": "2.4.0"})),
|
||||
(str(uuid4()), (now - timedelta(days=14)).isoformat(), "engineer", "config.created", "config:2.4.1", json.dumps({"name": "Vegas Loop - Optimized"})),
|
||||
(str(uuid4()), (now - timedelta(days=14)).isoformat(), "admin", "config.approved", "config:2.4.1", json.dumps({})),
|
||||
(str(uuid4()), (now - timedelta(days=10)).isoformat(), "admin", "deployment.initiated", "deployment:PRF4-001", json.dumps({"version": "2.4.1"})),
|
||||
(str(uuid4()), (now - timedelta(days=10)).isoformat(), "PRF4-001", "deployment.success", "deployment:PRF4-001", json.dumps({"version": "2.4.1"})),
|
||||
(str(uuid4()), (now - timedelta(days=7)).isoformat(), "admin", "config.created", "config:2.4.2", json.dumps({"name": "Vegas Loop - Soft Soil Zone"})),
|
||||
(str(uuid4()), (now - timedelta(days=7)).isoformat(), "admin", "config.approved", "config:2.4.2", json.dumps({})),
|
||||
(str(uuid4()), (now - timedelta(days=3)).isoformat(), "operator", "deployment.initiated", "deployment:PRF4-002", json.dumps({"version": "2.4.2"})),
|
||||
(str(uuid4()), (now - timedelta(days=3)).isoformat(), "PRF4-002", "deployment.failed", "deployment:PRF4-002", json.dumps({"error": "PLC communication timeout"})),
|
||||
(str(uuid4()), (now - timedelta(days=1)).isoformat(), "engineer", "config.created", "config:2.5.0", json.dumps({"name": "Vegas Loop - Phase 2 Draft"})),
|
||||
(str(uuid4()), (now - timedelta(hours=2)).isoformat(), "admin", "auth.login", "user:admin", json.dumps({})),
|
||||
]
|
||||
|
||||
for a in audit_logs:
|
||||
cursor.execute(
|
||||
"INSERT INTO audit_logs (id, timestamp, user, action, resource, details) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
a
|
||||
)
|
||||
|
||||
print(f"✓ Created {len(audit_logs)} audit log entries")
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
print("\n✅ Database seeded successfully!")
|
||||
print("\nTest credentials:")
|
||||
print(" admin / admin123 (full access)")
|
||||
print(" engineer / eng123 (engineer)")
|
||||
print(" operator / op123 (operator)")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Initialize tables first
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
conn.executescript("""
|
||||
CREATE TABLE IF NOT EXISTS machines (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
machine_type TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'offline',
|
||||
current_version TEXT,
|
||||
last_seen TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS configurations (
|
||||
id TEXT PRIMARY KEY,
|
||||
version TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
machine_type TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'draft',
|
||||
config TEXT NOT NULL,
|
||||
created_by TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS deployments (
|
||||
id TEXT PRIMARY KEY,
|
||||
config_id TEXT NOT NULL,
|
||||
config_version TEXT NOT NULL,
|
||||
machine_id TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'pending',
|
||||
initiated_by TEXT NOT NULL,
|
||||
initiated_at TEXT NOT NULL,
|
||||
completed_at TEXT,
|
||||
error TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS audit_logs (
|
||||
id TEXT PRIMARY KEY,
|
||||
timestamp TEXT NOT NULL,
|
||||
user TEXT NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
resource TEXT NOT NULL,
|
||||
details TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pending_configs (
|
||||
machine_id TEXT PRIMARY KEY,
|
||||
config_id TEXT NOT NULL,
|
||||
deployment_id TEXT NOT NULL
|
||||
);
|
||||
""")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
seed()
|
||||
Reference in New Issue
Block a user