-
Notifications
You must be signed in to change notification settings - Fork 2
Debugging Guide
This guide provides systematic procedures for debugging TMI issues including debug logging, log analysis, browser developer tools, and database queries.
TMI supports multiple log levels to control verbosity:
Log Levels:
-
debug- Detailed diagnostic information -
info- General informational messages (default) -
warn- Warning messages for potentially harmful situations -
error- Error messages for serious problems
Enable debug logging:
# Via environment variable
export TMI_LOG_LEVEL=debug
./bin/tmiserver
# Via configuration file (under the logging: section in config-development.yml)
# logging:
# level: debug
# Via Docker
docker run -e TMI_LOG_LEVEL=debug tmi-serverTMI uses Go's slog package for structured logging. The output format is controlled by the TMI_LOG_IS_DEV setting:
Text format (human-readable, development mode):
export TMI_LOG_IS_DEV=trueExample output:
time=2025-01-15T10:30:00Z level=INFO msg="Server starting on :8080"
time=2025-01-15T10:30:01Z level=DEBUG msg="Connected to PostgreSQL" host=localhost port=5432
time=2025-01-15T10:30:01Z level=DEBUG msg="Connected to Redis" host=localhost port=6379
JSON format (structured, production; this is the default when TMI_LOG_IS_DEV is false or unset):
Example output:
{"time":"2025-01-15T10:30:00Z","level":"INFO","msg":"Server starting on :8080"}
{"time":"2025-01-15T10:30:01Z","level":"DEBUG","msg":"Connected to PostgreSQL","host":"localhost","port":5432}Enable detailed logging for specific subsystems using the TMI_LOG_* environment variables:
# Set log level to debug for verbose output
export TMI_LOG_LEVEL=debug
# Log API request details
export TMI_LOG_API_REQUESTS=true
# Log API response details
export TMI_LOG_API_RESPONSES=true
# WebSocket message logging
export TMI_LOG_WEBSOCKET_MESSAGES=true
# Redact auth tokens in log output (recommended for shared environments)
export TMI_LOG_REDACT_AUTH_TOKENS=true
# Suppress logs for unauthenticated requests (e.g., health probes)
export TMI_LOG_SUPPRESS_UNAUTH_LOGS=trueTMI writes log files to a configurable directory. The log file is always named tmi.log within that directory. The default directory is logs/ (relative to the working directory).
Specify log directory:
# Set the log directory via environment variable
export TMI_LOG_DIR=/var/log/tmi
./bin/tmiserver
# Logs will be written to /var/log/tmi/tmi.log
# Also send logs to console (stdout) in addition to the file
export TMI_LOG_ALSO_LOG_TO_CONSOLE=trueConfiguration file (in config-development.yml or similar):
logging:
level: debug
is_dev: true
log_dir: /var/log/tmi
also_log_to_console: trueBuilt-in log rotation (via lumberjack; no external logrotate needed):
# Maximum size of a single log file before rotation (in MB, default varies)
export TMI_LOG_MAX_SIZE_MB=100
# Maximum number of old log files to retain
export TMI_LOG_MAX_BACKUPS=10
# Maximum number of days to retain old log files
export TMI_LOG_MAX_AGE_DAYS=30Or in configuration:
logging:
log_dir: /var/log/tmi
max_size_mb: 100
max_backups: 10
max_age_days: 30The TMI web application (Angular) uses a LoggerService for standardized client-side logging with component-specific filtering and ISO8601 timestamps.
- ISO8601 timestamp format for all log messages
- Four log levels: DEBUG, INFO, WARN, ERROR
- Environment-configurable log level threshold
- Component-specific debug logging for granular control
- Automatic redaction of sensitive URL parameters
import { LoggerService, LogLevel } from '../../core/services/logger.service';
@Component({
// ...
})
export class MyComponent {
constructor(private logger: LoggerService) {
// Component-specific debug logging (preferred for DEBUG level)
logger.debugComponent('MyComponent', 'Detailed debugging information');
logger.debugComponent('MyComponent', 'State changed', { oldState, newState });
// General log levels
logger.info('Informational message');
logger.warn('Warning message');
logger.error('Error message');
// Include additional data with any log level
logger.error('Failed to load user data', error);
}
}The debugComponent() method provides fine-grained control over debug logging by component name.
Benefits:
- Filter debug logs by specific components during development
- Reduce console noise by enabling only relevant debug output
- Better organize debug logs by application area
Configuration in environment files:
// src/environments/environment.ts
export const environment = {
// ...
logLevel: 'DEBUG',
debugComponents: ['AppDiagramService', 'websocket-api', 'DfdCollaborationService'],
};When debugComponents is defined, only debug logs from those components will be shown. When undefined or empty, all debug logs are shown (if log level permits).
Configure log levels in environment files:
| Environment | File | Log Level | Description |
|---|---|---|---|
| Development | environment.ts |
DEBUG | Show all logs for debugging |
| Test | environment.test.ts |
WARN | Only warnings and errors |
| Staging | environment.staging.ts |
WARN | Only warnings and errors |
| Production | environment.prod.ts |
ERROR | Only errors |
From most to least verbose:
- DEBUG - Detailed information for debugging purposes
- INFO - General informational messages about system operation
- WARN - Potential issues that are not yet errors
- ERROR - Error conditions that should be addressed
Each level includes all higher-priority levels. Setting the level to WARN will show both WARN and ERROR messages, but not INFO or DEBUG.
You can change the log level at runtime:
import { LogLevel, LoggerService } from '../../core/services/logger.service';
// Temporarily enable verbose logging
logger.setLogLevel(LogLevel.DEBUG);Below is the list of component names used throughout the application for debugComponent() logging. Use these names when configuring debugComponents in your environment:
Application Layer (DFD):
-
AppDfdFacade,AppDfdOrchestrator,AppDiagramLoadingService -
AppDiagramOperationBroadcaster,AppDiagramResyncService,AppDiagramService -
AppEdgeService,AppExportService,AppGraphOperationManager -
AppHistoryService,AppNotificationService,AppOperationRejectionHandler -
AppPersistenceCoordinator,AppRemoteOperationHandler,AppStateService
Executors (DFD):
-
BaseOperationExecutor,BatchOperationExecutor,EdgeOperationExecutor -
LoadDiagramExecutor,NodeOperationExecutor
Validators (DFD):
BaseOperationValidator
Infrastructure Layer (DFD):
-
InfraDfdWebsocketAdapter,InfraEdgeService,InfraLocalStorageAdapter -
InfraNodeService,InfraRestPersistenceStrategy,InfraX6GraphAdapter -
InfraX6SelectionAdapter,WebSocketPersistenceStrategy
X6 Adapters (DFD):
-
X6CoreOperations,X6Embedding,X6EventHandlers,X6EventLogger -
X6Graph,X6Keyboard,X6Tooltip,ZOrderService
Presentation Layer (DFD):
-
DfdComponent,DfdDiagram,DfdEdge,DfdEdgeQuery -
DfdEventHandlers,DfdExport,DfdPortStateManager,DfdState -
DfdStateStore,DfdTooltip,DfdVisualEffects,Embedding -
UiPresenterCoordinator,UiPresenterCursorDisplayService
Core Services:
-
Api,api,DfdCollaborationService,SecurityConfigService -
ServerConnection,SessionManager,websocket-api
Collaboration:
-
CollaborationComponent,CollaborationDialog,CollaborationSession -
GraphHistoryCoordinator,WebSocketCollaboration
Threat Modeling:
-
TM,TmEdit,TmEditComponent,ThreatEditorDialog -
ThreatModelAuthorizationService,ThreatModelReportService -
ThreatModelResolver,ThreatModelService,ThreatModelValidator -
SvgCacheService,SvgOptimizationService
Authentication and Authorization:
-
Auth,AuthGuard,HomeGuard,RoleGuard
Utilities:
-
CellDataExtractionService,CellRelationshipValidation,Notification
Other:
-
App,Dashboard,DFD
To debug collaboration-related issues, configure your environment:
export const environment = {
// ...
logLevel: 'DEBUG',
debugComponents: [
'DfdCollaborationService',
'CollaborationSession',
'WebSocketCollaboration',
'websocket-api',
'AppDiagramOperationBroadcaster',
'AppRemoteOperationHandler',
],
};This configuration shows only debug logs related to collaboration, filtering out noise from other components.
TMI uses Go's slog package for structured JSON logging with consistent fields:
Key fields:
-
time- Timestamp (RFC3339 format) -
level- Log level (DEBUG, INFO, WARN, ERROR) -
msg- Human-readable message -
request_id- Request trace ID (added by context logger middleware) -
client_ip- Client IP address (added by context logger middleware) -
user_id- User identifier (added by context logger middleware when authenticated) -
method- HTTP method (on request completion logs) -
path- Request path (on request completion logs) -
status_code- HTTP response status code (on request completion logs) -
duration- Request/operation duration (Go duration format, e.g.,"1.234ms") -
source- Source file and line number (development mode only)
Example log entries:
{"time":"2025-01-15T10:30:00Z","level":"INFO","msg":"Request completed successfully","method":"GET","path":"/api/v1/threat-models","status_code":200,"duration":"45.2ms","request_id":"abc-123","user_id":"user@example.com"}
{"time":"2025-01-15T10:30:01Z","level":"ERROR","msg":"Query failed","error":"connection refused","request_id":"def-456"}
{"time":"2025-01-15T10:30:02Z","level":"DEBUG","msg":"Client connected","user_id":"xyz-789"}Search for errors:
# All errors
grep '"level":"ERROR"' logs/tmi.log
# Errors with text format
grep 'ERROR' logs/tmi.log
# Errors in last hour
grep '"level":"ERROR"' logs/tmi.log | grep "$(date -u +%Y-%m-%dT%H)"Search by message content:
# Database-related logs
grep -i '"msg":".*database\|.*postgres\|.*redis' logs/tmi.log
# Authentication-related logs
grep -i '"msg":".*auth\|.*token\|.*login\|.*OAuth' logs/tmi.log
# WebSocket-related logs
grep -i '"msg":".*[Ww]eb[Ss]ocket\|.*WS ' logs/tmi.logSearch by user:
# All activity for specific user
grep '"user_id":"abc-123"' logs/tmi.log
# Failed operations for user
grep '"user_id":"abc-123"' logs/tmi.log | grep '"level":"ERROR"'Search by request:
# Follow request through system
grep '"request_id":"req-xyz-789"' logs/tmi.log
# All GET requests
grep '"method":"GET"' logs/tmi.log
# Slow requests (duration is a Go duration string like "45.2ms" or "1.5s")
grep '"duration":"[0-9]*\.[0-9]*s"' logs/tmi.log # Requests taking secondsjq for JSON logs:
# Pretty print JSON logs
cat logs/tmi.log | jq .
# Extract error messages
cat logs/tmi.log | jq 'select(.level=="ERROR") | .msg'
# Count errors by message
cat logs/tmi.log | jq 'select(.level=="ERROR") | .msg' | sort | uniq -c
# Find requests with durations in seconds (slow requests)
cat logs/tmi.log | jq 'select(.duration != null and (.duration | test("[0-9]+\\.?[0-9]*s$"))) | {path, duration}'grep patterns:
# Authentication failures
grep -i "authentication failed\|unauthorized\|invalid token" logs/tmi.log
# Database errors
grep -i "database error\|connection refused\|query failed" logs/tmi.log
# WebSocket issues
grep -i "websocket.*error\|connection.*closed\|upgrade failed" logs/tmi.log
# Performance issues (requests taking multiple seconds)
grep '"duration":"[5-9][0-9]*\.[0-9]*s\|[0-9][0-9]\+\.[0-9]*s' logs/tmi.logCommon patterns:
# Top 10 API endpoints by count
grep '"method":"' logs/tmi.log | jq -r '.path' | sort | uniq -c | sort -rn | head -10
# Error rate per hour
grep '"level":"ERROR"' logs/tmi.log | jq -r '.time' | cut -d: -f1-2 | uniq -c
# List request durations by endpoint (duration is a Go duration string, not numeric)
grep '"duration":' logs/tmi.log | jq '{path, duration}' | jq -s 'group_by(.path) | map({path: .[0].path, count: length, durations: map(.duration)})'Open Developer Tools:
- Chrome/Edge: F12 or Ctrl+Shift+I (Windows/Linux), Cmd+Option+I (Mac)
- Firefox: F12 or Ctrl+Shift+I (Windows/Linux), Cmd+Option+I (Mac)
- Safari: Cmd+Option+I (Mac) - Enable "Show Develop menu" in Preferences first
Key areas to check:
-
Console Tab:
- JavaScript errors
- Failed API calls
- WebSocket connection issues
- Application warnings
-
Network Tab:
- API request/response details
- HTTP status codes
- Request/response headers
- WebSocket handshake
-
Application Tab:
- Cookies (HttpOnly session cookies; tokens are not in localStorage)
- Local storage (OAuth state parameters)
- Session storage (PKCE verifier)
- Cache status
Network tab inspection:
- Open Network tab in Developer Tools
- Refresh page or trigger API call
- Find request in list
- Click to view details:
- Headers: Request/response headers, status code
- Preview: Formatted response data
- Response: Raw response data
- Timing: Request timing breakdown
Common issues:
401 Unauthorized:
TMI uses HttpOnly cookies for session management, so tokens are not accessible to JavaScript through localStorage or sessionStorage. To debug 401 errors:
// Check the Network tab for the failing request:
// 1. Look at Request Headers for the Cookie header
// 2. Check Response Headers for Set-Cookie directives
// 3. In the Application tab > Cookies, verify cookie presence and expiration
// Check if a session is active by calling the userinfo endpoint
fetch('/oauth2/userinfo', { credentials: 'include' })
.then(r => { console.log('Status:', r.status); return r.json(); })
.then(data => console.log('User info:', data))
.catch(e => console.error('Not authenticated:', e));CORS errors:
Access to fetch at 'https://api.example.com' from origin 'https://app.example.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present.
Check server CORS configuration:
# Verify CORS headers
curl -H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: GET" \
-H "Access-Control-Request-Headers: Authorization" \
-X OPTIONS \
-v \
https://api.example.com/api/v1/threat-modelsNetwork errors:
// Check for network connectivity (the root endpoint / returns health and version info)
fetch('https://api.example.com/')
.then(r => console.log('Server reachable:', r.status))
.catch(e => console.error('Network error:', e));Network tab WebSocket inspection:
- Open Network tab
- Filter by "WS" or "WebSocket"
- Click WebSocket connection
- View Messages tab:
- Outgoing messages (sent)
- Incoming messages (received)
- Message timestamps
Console WebSocket debugging:
// Monitor WebSocket events
const socket = new WebSocket('wss://api.example.com/ws');
socket.onopen = (e) => console.log('WebSocket connected:', e);
socket.onerror = (e) => console.error('WebSocket error:', e);
socket.onclose = (e) => console.log('WebSocket closed:', e.code, e.reason);
socket.onmessage = (e) => console.log('WebSocket message:', JSON.parse(e.data));Common WebSocket issues:
Connection refused:
- Check WebSocket URL (ws:// vs wss://)
- Verify server is running
- Check firewall/proxy settings
Connection closes immediately:
- Check authentication (JWT token)
- Verify CORS headers
- Check server logs for errors
Messages not received:
- Verify subscription is active
- Check Redis connectivity on server
- Monitor Network tab Messages
Set breakpoints:
- Open Sources tab
- Find JavaScript file
- Click line number to set breakpoint
- Trigger action to hit breakpoint
- Inspect variables, step through code
Console debugging:
// Log API responses
fetch('/api/v1/threat-models')
.then(r => r.json())
.then(data => {
console.log('API response:', data);
debugger; // Pause execution here
});
// Log WebSocket messages
socket.onmessage = (e) => {
const data = JSON.parse(e.data);
console.table(data); // Display as table
console.dir(data); // Display as object
};
// Trace function calls
console.trace('Function call stack');psql command line:
# Connect to database (default user is 'tmi' per docker-compose.prod.yml)
psql -h localhost -U tmi -d tmi
# Or use the TMI_DATABASE_URL directly
psql "$TMI_DATABASE_URL"
# Or use environment variables
export PGHOST=localhost
export PGUSER=tmi
export PGDATABASE=tmi
psqlFrom Docker:
# Connect to PostgreSQL container (production container name from docker-compose.prod.yml)
docker exec -it tmi-postgres-prod psql -U tmi -d tmiCheck active connections:
-- List all connections
SELECT pid, usename, application_name, client_addr, state, query
FROM pg_stat_activity
WHERE datname = 'tmi'
ORDER BY state, query_start DESC;
-- Count connections by state
SELECT state, COUNT(*)
FROM pg_stat_activity
WHERE datname = 'tmi'
GROUP BY state;Check database size:
-- Database size
SELECT pg_size_pretty(pg_database_size('tmi')) AS database_size;
-- Table sizes
SELECT schemaname, tablename,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC
LIMIT 20;
-- Largest indexes
SELECT schemaname, tablename, indexname,
pg_size_pretty(pg_relation_size(indexrelid)) AS size
FROM pg_indexes
JOIN pg_class ON pg_class.relname = indexname
WHERE schemaname = 'public'
ORDER BY pg_relation_size(indexrelid) DESC
LIMIT 10;Check slow queries:
-- Currently running slow queries
SELECT pid, now() - query_start AS duration, state, query
FROM pg_stat_activity
WHERE state = 'active'
AND now() - query_start > interval '5 seconds'
ORDER BY duration DESC;
-- Kill a long-running query (use carefully!)
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE pid = <process_id>;Check table statistics:
-- Row counts
SELECT schemaname, tablename, n_live_tup AS row_count
FROM pg_stat_user_tables
WHERE schemaname = 'public'
ORDER BY n_live_tup DESC;
-- Table activity (inserts, updates, deletes)
SELECT schemaname, relname,
seq_scan, seq_tup_read,
idx_scan, idx_tup_fetch,
n_tup_ins, n_tup_upd, n_tup_del
FROM pg_stat_user_tables
WHERE schemaname = 'public'
ORDER BY n_tup_ins + n_tup_upd + n_tup_del DESC;Check indexes:
-- List all indexes
SELECT schemaname, tablename, indexname, indexdef
FROM pg_indexes
WHERE schemaname = 'public'
ORDER BY tablename, indexname;
-- Find unused indexes
SELECT schemaname, tablename, indexname, idx_scan
FROM pg_stat_user_indexes
WHERE schemaname = 'public'
AND idx_scan = 0
AND indexrelname NOT LIKE 'pg_toast%'
ORDER BY pg_relation_size(indexrelid) DESC;
-- Find missing indexes (high seq_scan, no idx_scan)
SELECT schemaname, tablename, seq_scan, idx_scan,
seq_scan - idx_scan AS too_much_seq,
CASE WHEN seq_scan - idx_scan > 0
THEN 'Missing Index?'
ELSE 'OK'
END AS advice
FROM pg_stat_user_tables
WHERE schemaname = 'public'
AND seq_scan - idx_scan > 0
ORDER BY too_much_seq DESC;Application-specific queries:
-- Count threat models by user
SELECT owner, COUNT(*) as threat_model_count
FROM threat_models
GROUP BY owner
ORDER BY threat_model_count DESC
LIMIT 10;
-- Recent threat model activity
SELECT id, name, owner, created_at, modified_at
FROM threat_models
ORDER BY modified_at DESC
LIMIT 20;
-- Users with most authorization grants
SELECT subject, COUNT(*) as grant_count
FROM authorization
GROUP BY subject
ORDER BY grant_count DESC
LIMIT 10;
-- Webhook subscription status
SELECT status, COUNT(*) as count
FROM webhook_subscriptions
GROUP BY status;
-- Failed webhook deliveries (delivery records are stored in Redis, not Postgres)
-- Use the admin API endpoint GET /admin/webhooks/deliveries to query delivery statusEXPLAIN ANALYZE:
-- Analyze query performance
EXPLAIN ANALYZE
SELECT tm.*, u.email
FROM threat_models tm
JOIN users u ON u.id = tm.owner
WHERE tm.created_at > NOW() - INTERVAL '7 days'
ORDER BY tm.created_at DESC;
-- Look for:
-- - Seq Scan (consider adding index)
-- - High execution time
-- - High row countsEnable query logging:
-- Log slow queries (PostgreSQL config)
ALTER DATABASE tmi SET log_min_duration_statement = 1000; -- Log queries >1s
-- Log all queries (development only)
ALTER DATABASE tmi SET log_statement = 'all';# Connect via redis-cli
redis-cli
# Connect to specific host/port
redis-cli -h localhost -p 6379
# Connect with password
redis-cli -a yourpassword
# From Docker (production container name from docker-compose.prod.yml)
docker exec -it tmi-redis-prod redis-cliCheck Redis status:
# Server info
redis-cli INFO
# Memory usage
redis-cli INFO memory
# Connected clients
redis-cli CLIENT LIST
# Server statistics
redis-cli INFO statsInspect keys:
# Count keys
redis-cli DBSIZE
# List keys (use carefully in production!)
redis-cli KEYS "*"
# Scan keys safely (paginated)
redis-cli SCAN 0 MATCH "session:*" COUNT 100
# Get key value
redis-cli GET "session:abc123"
# Get key type and TTL
redis-cli TYPE "session:abc123"
redis-cli TTL "session:abc123"WebSocket debugging:
# List WebSocket sessions
redis-cli KEYS "websocket:*"
# Check WebSocket connection
redis-cli GET "websocket:connection:xyz789"
# Monitor real-time commands
redis-cli MONITORWebhook debugging:
# Check event stream
redis-cli XINFO STREAM tmi:events
# View recent events
redis-cli XRANGE tmi:events - + COUNT 10
# Check webhook consumer group
redis-cli XINFO GROUPS tmi:events
# Check pending webhook deliveries
redis-cli XPENDING tmi:events webhook-consumersCache debugging:
# Check cache hit rate
redis-cli INFO stats | grep hit_rate
# Find large keys
redis-cli --bigkeys
# Sample keys
redis-cli --sample-
Check logs:
grep -i '"msg":".*auth\|.*token\|.*OAuth' logs/tmi.log | tail -20
-
Verify OAuth configuration:
# List available OAuth providers curl http://localhost:8080/oauth2/providers # Initiate OAuth authorization for a specific provider (e.g., github) curl -v http://localhost:8080/oauth2/authorize?idp=github
-
Check session status:
// In browser console: tokens are in HttpOnly cookies, not accessible to JS // Instead, verify session by calling the userinfo endpoint fetch('/oauth2/userinfo', { credentials: 'include' }) .then(r => r.json()) .then(data => console.log('Session:', data)) .catch(e => console.error('No active session:', e));
-
Test API with token:
curl -H "Authorization: Bearer $TOKEN" \ http://localhost:8080/api/v1/threat-models
-
Check server logs:
grep -i '"msg":".*[Ww]eb[Ss]ocket' logs/tmi.log -
Verify Redis connectivity:
redis-cli PING redis-cli KEYS "websocket:*" -
Monitor WebSocket in browser:
- Open Network tab, filter by WS
- Check connection status and messages
-
Test WebSocket connection:
websocat wss://api.example.com/ws
-
Use WebSocket Test Harness: See WebSocket-Test-Harness for a comprehensive Go-based testing tool that supports OAuth authentication, host/participant modes, and detailed message logging.
-
Check server logs for slow requests:
grep '"duration":"[0-9]*\.[0-9]*s"' logs/tmi.log -
Profile application:
# CPU profile curl http://localhost:8080/debug/pprof/profile?seconds=30 > cpu.prof go tool pprof cpu.prof # Memory profile curl http://localhost:8080/debug/pprof/heap > mem.prof go tool pprof mem.prof
-
Check database performance:
-- Find slow queries SELECT query, calls, total_time, mean_time FROM pg_stat_statements ORDER BY mean_time DESC LIMIT 10;
-
Check Redis performance:
# Monitor commands redis-cli MONITOR # Check slow log redis-cli SLOWLOG GET 10
- Common-Issues - Solutions to frequent problems
- Getting-Help - Support resources
- Performance-Troubleshooting - Performance optimization
- Database-Operations - Database procedures
- Monitoring-and-Health - System monitoring
- Using TMI for Threat Modeling
- Accessing TMI
- Authentication
- Creating Your First Threat Model
- Understanding the User Interface
- Working with Data Flow Diagrams
- Managing Threats
- Collaborative Threat Modeling
- Using Notes and Documentation
- Timmy AI Assistant
- Metadata and Extensions
- Planning Your Deployment
- Terraform Deployment (AWS, OCI, GCP, Azure)
- Deploying TMI Server
- OCI Container Deployment
- Certificate Automation
- Deploying TMI Web Application
- Setting Up Authentication
- Database Setup
- Component Integration
- Post-Deployment
- Branding and Customization
- Monitoring and Health
- Cloud Logging
- Database Operations
- Security Operations
- Performance and Scaling
- Maintenance Tasks
- Getting Started with Development
- Architecture and Design
- API Integration
- Testing
- Contributing
- Extending TMI
- Dependency Upgrade Plans
- DFD Graphing Library Reference
- Migration Instructions