diff --git a/backend/app/Contracts/DatabaseDriverInterface.php b/backend/app/Contracts/DatabaseDriverInterface.php
index 1c5dbe9..1497538 100644
--- a/backend/app/Contracts/DatabaseDriverInterface.php
+++ b/backend/app/Contracts/DatabaseDriverInterface.php
@@ -14,6 +14,16 @@ interface DatabaseDriverInterface
*/
public function query(string $sql, array $bindings = []): array;
+ /**
+ * Get the table schema.
+ */
+ public function getTableSchema(string $table): array;
+
+ /**
+ * Get table data.
+ */
+ public function getTableData(string $table, int $limit = 100, int $offset = 0): array;
+
/**
* Get the underlying connection instance.
*/
diff --git a/backend/app/Http/Controllers/Api/SchemaController.php b/backend/app/Http/Controllers/Api/SchemaController.php
index d67eef2..323bf2b 100644
--- a/backend/app/Http/Controllers/Api/SchemaController.php
+++ b/backend/app/Http/Controllers/Api/SchemaController.php
@@ -60,4 +60,16 @@ class SchemaController extends Controller
return Response::json(['error' => $e->getMessage()], 400);
}
}
+
+ public function data(Request $request, $table)
+ {
+ try {
+ $this->initializeDriver($request);
+ $limit = $request->get('limit', 100);
+ $offset = $request->get('offset', 0);
+ return Response::json($this->databaseService->getTableData($table, $limit, $offset));
+ } catch (\Exception $e) {
+ return Response::json(['error' => $e->getMessage()], 400);
+ }
+ }
}
diff --git a/backend/app/Services/Database/MySqlDriver.php b/backend/app/Services/Database/MySqlDriver.php
index 18d2a9e..a72ad6b 100644
--- a/backend/app/Services/Database/MySqlDriver.php
+++ b/backend/app/Services/Database/MySqlDriver.php
@@ -64,6 +64,11 @@ class MySqlDriver implements DatabaseDriverInterface, SchemaDiscoveryInterface
return $this->query("DESCRIBE `{$table}`");
}
+ public function getTableData(string $table, int $limit = 100, int $offset = 0): array
+ {
+ return $this->query("SELECT * FROM `{$table}` LIMIT ? OFFSET ?", [$limit, $offset]);
+ }
+
public function getForeignKeys(string $table): array
{
$sql = "
diff --git a/backend/app/Services/DatabaseService.php b/backend/app/Services/DatabaseService.php
index 3ab2f88..770cb81 100644
--- a/backend/app/Services/DatabaseService.php
+++ b/backend/app/Services/DatabaseService.php
@@ -74,4 +74,12 @@ class DatabaseService
}
throw new \Exception("Driver does not support schema discovery.");
}
+
+ /**
+ * Get table data.
+ */
+ public function getTableData(string $table, int $limit = 100, int $offset = 0): array
+ {
+ return $this->getDriver()->getTableData($table, $limit, $offset);
+ }
}
diff --git a/backend/routes/api.php b/backend/routes/api.php
index d1c00a0..ebf2b23 100644
--- a/backend/routes/api.php
+++ b/backend/routes/api.php
@@ -12,4 +12,5 @@ Route::prefix('schema')->group(function () {
Route::get('/databases', [SchemaController::class, 'databases']);
Route::get('/tables/{database}', [SchemaController::class, 'tables']);
Route::get('/{table}', [SchemaController::class, 'schema']);
+ Route::get('/{table}/data', [SchemaController::class, 'data']);
});
diff --git a/frontend/index.html b/frontend/index.html
index 0fca6f0..040fe1b 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -4,7 +4,10 @@
-
frontend
+ Mariavel - Premium Database Manager
+
+
+
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index fc357b5..2d83a72 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -5,9 +5,10 @@ import { useAppStore } from './store/useAppStore';
import Sidebar from './components/Sidebar.tsx';
import MainContent from './components/MainContent.tsx';
import Header from './components/Header.tsx';
+import Login from './components/Login.tsx';
const App: React.FC = () => {
- const darkMode = useAppStore((state) => state.darkMode);
+ const { darkMode, connected } = useAppStore();
const theme = useMemo(() => getTheme(darkMode ? 'dark' : 'light'), [darkMode]);
useEffect(() => {
@@ -18,6 +19,15 @@ const App: React.FC = () => {
}
}, [darkMode]);
+ if (!connected) {
+ return (
+
+
+
+
+ );
+ }
+
return (
diff --git a/frontend/src/components/Login.tsx b/frontend/src/components/Login.tsx
new file mode 100644
index 0000000..792d4d4
--- /dev/null
+++ b/frontend/src/components/Login.tsx
@@ -0,0 +1,140 @@
+import React, { useState } from 'react';
+import { Box, Paper, TextField, Button, Typography, Stack, InputAdornment } from '@mui/material';
+import { Storage, Person, Lock, Dns } from '@mui/icons-material';
+import { useAppStore } from '../store/useAppStore';
+import { SchemaService } from '../services/api';
+
+const Login: React.FC = () => {
+ const setConnection = useAppStore((state) => state.setConnection);
+ const [config, setConfig] = useState({
+ host: '127.0.0.1',
+ username: 'root',
+ password: '',
+ port: 3306,
+ });
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState('');
+
+ const handleConnect = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setLoading(true);
+ setError('');
+
+ try {
+ // Temporarily set connection to test connectivity
+ // In a real app, we'd have a specific /connect endpoint
+ // Here we just try to fetch databases to verify
+ const tempConnection = { ...config };
+ setConnection(tempConnection);
+
+ await SchemaService.getDatabases();
+ // If success, we are already "connected" in the store
+ } catch (err: any) {
+ setError(err.response?.data?.error || 'Failed to connect to database');
+ useAppStore.getState().clearConnection();
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
+
+
+ MARIAVEL
+
+
+ Connect to your MySQL Database
+
+
+
+
+
+
+ );
+};
+
+export default Login;
diff --git a/frontend/src/components/MainContent.tsx b/frontend/src/components/MainContent.tsx
index 47caae8..401a545 100644
--- a/frontend/src/components/MainContent.tsx
+++ b/frontend/src/components/MainContent.tsx
@@ -1,59 +1,103 @@
-import React from 'react';
-import { Box, Paper, Typography } from '@mui/material';
+import React, { useEffect, useState } from 'react';
+import { Box, Paper, Typography, CircularProgress } from '@mui/material';
import DataGrid, {
Column,
- Editing,
Scrolling,
- Paging,
FilterRow,
- HeaderFilter
+ HeaderFilter,
+ SearchPanel,
+ GroupPanel,
+ Export
} from 'devextreme-react/data-grid';
import { useAppStore } from '../store/useAppStore';
+import { SchemaService } from '../services/api';
const MainContent: React.FC = () => {
- const { activeDatabase } = useAppStore();
+ const { activeTable, activeDatabase } = useAppStore();
+ const [data, setData] = useState([]);
+ const [columns, setColumns] = useState([]);
+ const [loading, setLoading] = useState(false);
- // Mock data for initial UI
- const dummyData = [
- { id: 1, name: 'Users', type: 'BASE TABLE', engine: 'InnoDB', rows: 1500, size: '256 KB' },
- { id: 2, name: 'Products', type: 'BASE TABLE', engine: 'InnoDB', rows: 54200, size: '12 MB' },
- { id: 3, name: 'Orders', type: 'BASE TABLE', engine: 'InnoDB', rows: 120400, size: '45 MB' },
- { id: 4, name: 'Order_Items', type: 'BASE TABLE', engine: 'InnoDB', rows: 450000, size: '120 MB' },
- ];
+ useEffect(() => {
+ const fetchTableData = async () => {
+ if (!activeTable || !activeDatabase) return;
+
+ setLoading(true);
+ try {
+ // Fetch schema for columns
+ const schemaRes = await SchemaService.getTableSchema(activeTable);
+ const cols = schemaRes.data.map((col: any) => ({
+ dataField: col.Field,
+ caption: col.Field,
+ dataType: mapSqlTypeToDxType(col.Type)
+ }));
+ setColumns(cols);
+
+ // Fetch actual data
+ const dataRes = await SchemaService.getTableData(activeTable, activeDatabase);
+ setData(dataRes.data);
+ } catch (error) {
+ console.error('Failed to fetch table data', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchTableData();
+ }, [activeTable, activeDatabase]);
+
+ // Helper to map SQL types to DevExtreme types
+ const mapSqlTypeToDxType = (sqlType: string) => {
+ sqlType = sqlType.toLowerCase();
+ if (sqlType.includes('int') || sqlType.includes('decimal') || sqlType.includes('float')) return 'number';
+ if (sqlType.includes('date') || sqlType.includes('time')) return 'date';
+ if (sqlType.includes('bool')) return 'boolean';
+ return 'string';
+ };
+
+ if (!activeTable) {
+ return (
+
+ Select a table to view data
+
+ );
+ }
return (
-
-
+
+
- {activeDatabase ? `Tables in ${activeDatabase}` : 'Welcome to Mariavel'}
-
-
- Manage your database tables with high-performance tools.
+ {activeDatabase}.{activeTable}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ {loading ? (
+
+
+
+ ) : (
+
+
+
+
+
+
+
+
+ {columns.map(col => (
+
+ ))}
+
+ )}
);
diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx
index f20767f..a3ecccf 100644
--- a/frontend/src/components/Sidebar.tsx
+++ b/frontend/src/components/Sidebar.tsx
@@ -1,45 +1,105 @@
-import React from 'react';
-import { Box, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Typography, Divider } from '@mui/material';
-import { Storage, TableChart, Folder } from '@mui/icons-material';
+import React, { useEffect, useState } from 'react';
+import { Box, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Typography, Divider, CircularProgress } from '@mui/material';
+import { Storage } from '@mui/icons-material';
import { useAppStore } from '../store/useAppStore';
+import { SchemaService } from '../services/api';
const Sidebar: React.FC = () => {
- const { activeDatabase, setActiveDatabase } = useAppStore();
+ const { activeDatabase, setActiveDatabase, setActiveTable, activeTable } = useAppStore();
+ const [databases, setDatabases] = useState([]);
+ const [tables, setTables] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [tablesLoading, setTablesLoading] = useState(false);
- // Mock data for initial UI
- const databases = ['information_schema', 'mysql', 'performance_schema', 'sys', 'mariavel_db'];
+ useEffect(() => {
+ const fetchDatabases = async () => {
+ try {
+ const response = await SchemaService.getDatabases();
+ setDatabases(response.data);
+ } catch (error) {
+ console.error('Failed to fetch databases', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+ fetchDatabases();
+ }, []);
+
+ const handleDatabaseClick = async (db: string) => {
+ if (activeDatabase === db) {
+ setActiveDatabase(null);
+ setTables([]);
+ return;
+ }
+
+ setActiveDatabase(db);
+ setTablesLoading(true);
+ try {
+ const response = await SchemaService.getTables(db);
+ setTables(response.data);
+ } catch (error) {
+ console.error('Failed to fetch tables', error);
+ } finally {
+ setTablesLoading(false);
+ }
+ };
return (
-
-
-
- Databases
-
+
+
+
+ Explorer
-
- {databases.map((db) => (
-
- setActiveDatabase(db)}
- >
-
-
-
-
+
+
+ {loading ? (
+
+ ) : (
+
+ {databases.map((db) => (
+
+
+ handleDatabaseClick(db)}
+ selected={activeDatabase === db}
+ sx={{ py: 1 }}
>
- {db}
-
- }
- />
-
-
- ))}
-
+
+
+
+ {db}}
+ />
+
+
+
+ {activeDatabase === db && (
+
+ {tablesLoading ? (
+
+ ) : tables.length === 0 ? (
+ No tables found
+ ) : tables.map((table) => (
+
+ setActiveTable(table)}
+ sx={{ py: 0.5 }}
+ >
+ {table}}
+ />
+
+
+ ))}
+
+ )}
+
+ ))}
+
+ )}
+
diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts
new file mode 100644
index 0000000..9ad4a18
--- /dev/null
+++ b/frontend/src/services/api.ts
@@ -0,0 +1,30 @@
+import axios from 'axios';
+import { useAppStore } from '../store/useAppStore';
+
+const api = axios.create({
+ baseURL: 'http://localhost:8000/api',
+});
+
+api.interceptors.request.use((config) => {
+ const connection = useAppStore.getState().connection;
+ if (connection) {
+ config.params = {
+ ...config.params,
+ host: connection.host,
+ username: connection.username,
+ password: connection.password,
+ port: connection.port,
+ database: connection.database || config.params?.database,
+ };
+ }
+ return config;
+});
+
+export default api;
+
+export const SchemaService = {
+ getDatabases: () => api.get('/schema/databases'),
+ getTables: (db: string) => api.get(`/schema/tables/${db}`),
+ getTableSchema: (table: string) => api.get(`/schema/${table}`),
+ getTableData: (table: string, database?: string) => api.get(`/schema/${table}/data`, { params: { database } }),
+};
diff --git a/frontend/src/store/useAppStore.ts b/frontend/src/store/useAppStore.ts
index da0cf38..67c345b 100644
--- a/frontend/src/store/useAppStore.ts
+++ b/frontend/src/store/useAppStore.ts
@@ -1,10 +1,22 @@
import { create } from 'zustand';
+interface ConnectionConfig {
+ host: string;
+ username: string;
+ password?: string;
+ port: number;
+ database?: string;
+}
+
interface AppState {
darkMode: boolean;
activeDatabase: string | null;
activeTable: string | null;
+ connection: ConnectionConfig | null;
+ connected: boolean;
toggleDarkMode: () => void;
+ setConnection: (config: ConnectionConfig) => void;
+ clearConnection: () => void;
setActiveDatabase: (db: string | null) => void;
setActiveTable: (table: string | null) => void;
}
@@ -13,7 +25,11 @@ export const useAppStore = create((set) => ({
darkMode: true,
activeDatabase: null,
activeTable: null,
+ connection: null,
+ connected: false,
toggleDarkMode: () => set((state) => ({ darkMode: !state.darkMode })),
+ setConnection: (config) => set({ connection: config, connected: true }),
+ clearConnection: () => set({ connection: null, connected: false, activeDatabase: null, activeTable: null }),
setActiveDatabase: (db) => set({ activeDatabase: db, activeTable: null }),
setActiveTable: (table) => set({ activeTable: table }),
}));