avl_console/
database.rs

1//! AvilaDB Explorer - Database management and query interface
2
3use crate::{error::Result, state::AppState};
4use axum::{
5    extract::{Path, State},
6    response::{Html, IntoResponse},
7    routing::{get, post},
8    Json, Router,
9};
10use serde::{Deserialize, Serialize};
11use std::sync::Arc;
12
13pub fn routes(state: Arc<AppState>) -> Router {
14    Router::new()
15        .route("/", get(database_list_page))
16        .route("/list", get(list_databases))
17        .route("/:db_id", get(database_detail))
18        .route("/:db_id/query", post(execute_query))
19        .route("/:db_id/collections", get(list_collections))
20        .with_state(state)
21}
22
23async fn database_list_page() -> impl IntoResponse {
24    Html(DATABASE_LIST_HTML)
25}
26
27#[derive(Serialize)]
28struct DatabaseInfo {
29    id: String,
30    name: String,
31    region: String,
32    collections: usize,
33    size_bytes: u64,
34    created_at: String,
35}
36
37async fn list_databases(State(_state): State<Arc<AppState>>) -> Result<Json<Vec<DatabaseInfo>>> {
38    // TODO: Query actual AvilaDB service
39    let databases = vec![
40        DatabaseInfo {
41            id: "db_prod_001".to_string(),
42            name: "production".to_string(),
43            region: "sa-east-1".to_string(),
44            collections: 15,
45            size_bytes: 2_500_000_000,
46            created_at: "2024-11-01T10:00:00Z".to_string(),
47        },
48        DatabaseInfo {
49            id: "db_dev_001".to_string(),
50            name: "development".to_string(),
51            region: "sa-east-1".to_string(),
52            collections: 8,
53            size_bytes: 500_000_000,
54            created_at: "2024-11-15T14:30:00Z".to_string(),
55        },
56        DatabaseInfo {
57            id: "db_test_001".to_string(),
58            name: "testing".to_string(),
59            region: "sa-east-1".to_string(),
60            collections: 5,
61            size_bytes: 100_000_000,
62            created_at: "2024-11-20T09:00:00Z".to_string(),
63        },
64    ];
65
66    Ok(Json(databases))
67}
68
69async fn database_detail(
70    Path(db_id): Path<String>,
71    State(_state): State<Arc<AppState>>,
72) -> Result<Json<DatabaseInfo>> {
73    // TODO: Query specific database
74    Ok(Json(DatabaseInfo {
75        id: db_id,
76        name: "production".to_string(),
77        region: "sa-east-1".to_string(),
78        collections: 15,
79        size_bytes: 2_500_000_000,
80        created_at: "2024-11-01T10:00:00Z".to_string(),
81    }))
82}
83
84#[derive(Deserialize)]
85struct QueryRequest {
86    query: String,
87}
88
89#[derive(Serialize)]
90struct QueryResult {
91    rows: Vec<serde_json::Value>,
92    count: usize,
93    execution_time_ms: u64,
94}
95
96async fn execute_query(
97    Path(_db_id): Path<String>,
98    State(_state): State<Arc<AppState>>,
99    Json(req): Json<QueryRequest>,
100) -> Result<Json<QueryResult>> {
101    // TODO: Execute actual query on AvilaDB
102    tracing::info!("Executing query: {}", req.query);
103
104    Ok(Json(QueryResult {
105        rows: vec![
106            serde_json::json!({"id": 1, "name": "Item 1"}),
107            serde_json::json!({"id": 2, "name": "Item 2"}),
108        ],
109        count: 2,
110        execution_time_ms: 15,
111    }))
112}
113
114#[derive(Serialize)]
115struct CollectionInfo {
116    name: String,
117    partition_key: String,
118    document_count: usize,
119    size_bytes: u64,
120}
121
122async fn list_collections(
123    Path(_db_id): Path<String>,
124    State(_state): State<Arc<AppState>>,
125) -> Result<Json<Vec<CollectionInfo>>> {
126    // TODO: Query actual collections
127    let collections = vec![
128        CollectionInfo {
129            name: "users".to_string(),
130            partition_key: "userId".to_string(),
131            document_count: 10_000,
132            size_bytes: 50_000_000,
133        },
134        CollectionInfo {
135            name: "orders".to_string(),
136            partition_key: "customerId".to_string(),
137            document_count: 50_000,
138            size_bytes: 200_000_000,
139        },
140    ];
141
142    Ok(Json(collections))
143}
144
145const DATABASE_LIST_HTML: &str = r#"<!DOCTYPE html>
146<html lang="pt-BR">
147<head>
148    <meta charset="UTF-8">
149    <meta name="viewport" content="width=device-width, initial-scale=1.0">
150    <title>AvilaDB Explorer - AVL Console</title>
151    <style>
152        * { margin: 0; padding: 0; box-sizing: border-box; }
153        body {
154            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
155            background: #0a0e1a;
156            color: #e0e6ed;
157        }
158        .header {
159            background: #0f1419;
160            border-bottom: 1px solid #1a1f2e;
161            padding: 1rem 2rem;
162        }
163        .container { max-width: 1400px; margin: 2rem auto; padding: 0 2rem; }
164        .db-card {
165            background: #0f1419;
166            border: 1px solid #1a1f2e;
167            border-radius: 8px;
168            padding: 1.5rem;
169            margin-bottom: 1rem;
170            cursor: pointer;
171            transition: all 0.2s;
172        }
173        .db-card:hover {
174            border-color: #00d4ff;
175            transform: translateX(4px);
176        }
177        .db-name {
178            font-size: 1.25rem;
179            font-weight: bold;
180            color: #00d4ff;
181            margin-bottom: 0.5rem;
182        }
183        .db-info {
184            display: flex;
185            gap: 2rem;
186            font-size: 0.875rem;
187            color: #8b92a0;
188        }
189        .query-editor {
190            background: #0f1419;
191            border: 1px solid #1a1f2e;
192            border-radius: 8px;
193            padding: 1.5rem;
194            margin-top: 2rem;
195        }
196        textarea {
197            width: 100%;
198            min-height: 150px;
199            background: #0a0e1a;
200            border: 1px solid #1a1f2e;
201            border-radius: 4px;
202            padding: 1rem;
203            color: #e0e6ed;
204            font-family: 'Courier New', monospace;
205            font-size: 0.875rem;
206            resize: vertical;
207        }
208        button {
209            background: #00d4ff;
210            color: #0a0e1a;
211            border: none;
212            padding: 0.75rem 1.5rem;
213            border-radius: 4px;
214            font-weight: bold;
215            cursor: pointer;
216            margin-top: 1rem;
217        }
218        button:hover { background: #00b8e6; }
219        .results {
220            background: #0f1419;
221            border: 1px solid #1a1f2e;
222            border-radius: 8px;
223            padding: 1.5rem;
224            margin-top: 1rem;
225        }
226        pre {
227            background: #0a0e1a;
228            padding: 1rem;
229            border-radius: 4px;
230            overflow-x: auto;
231        }
232    </style>
233</head>
234<body>
235    <div class="header">
236        <h1>🗄️ AvilaDB Explorer</h1>
237    </div>
238
239    <div class="container">
240        <h2 style="margin-bottom: 1rem;">Seus Bancos de Dados</h2>
241        <div id="databases"></div>
242
243        <div class="query-editor">
244            <h3 style="margin-bottom: 1rem;">Query Editor</h3>
245            <textarea id="query" placeholder="SELECT * FROM users WHERE active = true">SELECT * FROM users LIMIT 10</textarea>
246            <button onclick="executeQuery()">Executar Query</button>
247        </div>
248
249        <div class="results" id="results" style="display: none;">
250            <h3>Resultados</h3>
251            <pre id="resultData"></pre>
252        </div>
253    </div>
254
255    <script>
256        async function loadDatabases() {
257            const res = await fetch('/databases/list');
258            const databases = await res.json();
259            const container = document.getElementById('databases');
260            container.innerHTML = databases.map(db => `
261                <div class="db-card">
262                    <div class="db-name">${db.name}</div>
263                    <div class="db-info">
264                        <span>ID: ${db.id}</span>
265                        <span>Região: ${db.region}</span>
266                        <span>Coleções: ${db.collections}</span>
267                        <span>Tamanho: ${(db.size_bytes / 1_000_000).toFixed(1)} MB</span>
268                    </div>
269                </div>
270            `).join('');
271        }
272
273        async function executeQuery() {
274            const query = document.getElementById('query').value;
275            const res = await fetch('/databases/db_prod_001/query', {
276                method: 'POST',
277                headers: { 'Content-Type': 'application/json' },
278                body: JSON.stringify({ query })
279            });
280            const result = await res.json();
281            document.getElementById('results').style.display = 'block';
282            document.getElementById('resultData').textContent = JSON.stringify(result, null, 2);
283        }
284
285        loadDatabases();
286    </script>
287</body>
288</html>"#;