Initial commit

This commit is contained in:
2026-01-14 23:04:53 +01:00
parent 2f7d11b7d2
commit d9b4c73baa
25 changed files with 3742 additions and 30 deletions

View File

@@ -0,0 +1,105 @@
</main>
</div><!-- end dbm-main -->
<!-- SQL Console (Full Width at Bottom) -->
<div class="dbm-console expanded">
<div class="dbm-console-header">
<div class="dbm-console-title">
<i data-lucide="terminal" class="icon"></i>
SQL Console
</div>
<i data-lucide="chevron-up" class="dbm-console-toggle"></i>
</div>
<div class="dbm-console-body">
<form id="sql-form" method="post" action="<?php echo Config::get('URL'); ?>sql/execute">
<?php $current_database = isset($this->database_name) ? $this->database_name : Config::get('DB_NAME'); ?>
<input type="hidden" name="database_name" value="<?php echo htmlspecialchars($current_database); ?>">
<div class="dbm-sql-editor">
<div id="sql-highlight" class="dbm-sql-highlight"></div>
<textarea name="sql_query" id="sql_query" placeholder="SELECT * FROM table_name LIMIT 10;
-- Write your SQL query here
-- Press Execute or Ctrl+Enter to run"></textarea>
</div>
<div class="dbm-sql-actions">
<button type="submit" class="dbm-btn dbm-btn-success">
<i data-lucide="play"></i>
Execute
</button>
<button type="button" class="dbm-btn dbm-btn-secondary" onclick="document.getElementById('sql_query').value = ''; document.getElementById('sql-highlight').innerHTML = '';">
Clear
</button>
<select class="db-select" onchange="document.querySelector('input[name=database_name]').value = this.value;">
<?php foreach (DatabaseModel::getAllDatabases() as $db): ?>
<option value="<?php echo htmlspecialchars($db); ?>" <?php echo $db === $current_database ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($db); ?>
</option>
<?php endforeach; ?>
</select>
</div>
</form>
<div id="sql-result" class="dbm-sql-result">
<?php
// Check for session result
$result = Session::get('sql_result');
if ($result) {
Session::set('sql_result', null);
if ($result['success']) {
echo '<div class="dbm-sql-result success">';
echo '<div class="dbm-sql-result-header">';
echo '<i data-lucide="check-circle"></i>';
echo htmlspecialchars($result['message']);
echo '<span style="margin-left: auto; color: var(--text-muted); font-size: 12px;">' . $result['execution_time'] . 'ms</span>';
echo '</div>';
if (!empty($result['result'])) {
echo '<div class="dbm-sql-result-body"><div class="dbm-table-wrapper"><table class="dbm-table"><thead><tr>';
foreach (array_keys($result['result'][0]) as $col) {
echo '<th>' . htmlspecialchars($col) . '</th>';
}
echo '</tr></thead><tbody>';
foreach ($result['result'] as $row) {
echo '<tr>';
foreach ($row as $value) {
echo '<td>' . ($value === null ? '<span class="null-value">NULL</span>' : htmlspecialchars(substr($value, 0, 100))) . '</td>';
}
echo '</tr>';
}
echo '</tbody></table></div></div>';
}
echo '</div>';
} else {
echo '<div class="dbm-sql-result error">';
echo '<div class="dbm-sql-result-header">';
echo '<i data-lucide="x-circle"></i>';
echo htmlspecialchars($result['message']);
echo '</div>';
if (!empty($result['error'])) {
echo '<div class="dbm-sql-result-body" style="padding: 16px; font-family: monospace; font-size: 13px; color: var(--accent-red);">' . htmlspecialchars($result['error']) . '</div>';
}
echo '</div>';
}
}
?>
</div>
</div>
</div>
</div><!-- end dbm-wrapper -->
</div><!-- end wrapper -->
<script src="<?php echo Config::get('URL'); ?>js/dbmanager.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
lucide.createIcons();
DBManager.init('<?php echo Config::get('URL'); ?>');
});
</script>
</body>
</html>

View File

@@ -0,0 +1,107 @@
<!doctype html>
<html>
<head>
<title>Database Manager - HUGE</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:;base64,=">
<link rel="stylesheet" href="<?php echo Config::get('URL'); ?>css/style.css" />
<link rel="stylesheet" href="<?php echo Config::get('URL'); ?>css/dbmanager.css" />
<script src="https://unpkg.com/lucide@latest"></script>
</head>
<body>
<div class="wrapper dbm-page-wrapper">
<?php
$uri = trim($_SERVER['REQUEST_URI'], '/');
$uri_parts = explode('/', $uri);
$current_controller = isset($uri_parts[0]) ? strtolower($uri_parts[0]) : 'database';
$is_db_page = in_array($current_controller, ['database', 'table', 'sql']);
$is_user_page = ($current_controller === 'dbuser');
?>
<ul class="navigation">
<li><a href="<?php echo Config::get('URL'); ?>index/index">Home</a></li>
<li class="<?php echo $is_db_page ? 'active' : ''; ?>"><a href="<?php echo Config::get('URL'); ?>database/index">Database</a></li>
<li class="<?php echo $is_user_page ? 'active' : ''; ?>"><a href="<?php echo Config::get('URL'); ?>dbuser/index">Users</a></li>
</ul>
<ul class="navigation right">
<li><a href="<?php echo Config::get('URL'); ?>admin/">Admin</a></li>
<li><a href="<?php echo Config::get('URL'); ?>login/logout">Logout</a></li>
</ul>
<div class="dbm-wrapper">
<div class="dbm-main">
<aside class="dbm-sidebar">
<div class="dbm-sidebar-header">
<i data-lucide="database" class="icon"></i>
<h3>Databases</h3>
</div>
<nav class="dbm-tree">
<?php
$all_databases = DatabaseModel::getAllDatabases();
$current_database = isset($this->database_name) ? $this->database_name : Config::get('DB_NAME');
$current_table = isset($this->table_name) ? $this->table_name : null;
foreach ($all_databases as $db):
$is_current_db = ($db === $current_database);
$tables = $is_current_db ? DatabaseModel::getTablesInDatabase($db) : [];
?>
<div class="tree-item <?php echo $is_current_db ? 'expanded' : ''; ?>" data-db="<?php echo htmlspecialchars($db); ?>">
<div class="tree-header <?php echo $is_current_db ? 'active' : ''; ?>" data-href="<?php echo Config::get('URL'); ?>database/show/<?php echo urlencode($db); ?>">
<span class="tree-toggle">
<i data-lucide="chevron-right"></i>
</span>
<span class="tree-icon database">
<i data-lucide="database"></i>
</span>
<span class="tree-label"><?php echo htmlspecialchars($db); ?></span>
<?php if (!empty($tables)): ?>
<span class="tree-badge"><?php echo count($tables); ?></span>
<?php endif; ?>
</div>
<div class="tree-children" <?php echo $is_current_db ? 'data-loaded="true"' : ''; ?>>
<?php if ($is_current_db && !empty($tables)): ?>
<?php foreach ($tables as $table):
$is_current_table = ($table === $current_table);
$columns = $is_current_table ? TableModel::getTableColumns($db, $table) : [];
?>
<div class="tree-item <?php echo $is_current_table ? 'expanded' : ''; ?>" data-table="<?php echo htmlspecialchars($table); ?>">
<div class="tree-header <?php echo $is_current_table ? 'active' : ''; ?>" data-href="<?php echo Config::get('URL'); ?>table/show/<?php echo urlencode($db); ?>/<?php echo urlencode($table); ?>">
<span class="tree-toggle">
<i data-lucide="chevron-right"></i>
</span>
<span class="tree-icon table">
<i data-lucide="table"></i>
</span>
<span class="tree-label"><?php echo htmlspecialchars($table); ?></span>
</div>
<div class="tree-children">
<?php if ($is_current_table && !empty($columns)): ?>
<?php foreach ($columns as $col): ?>
<div class="tree-item">
<div class="tree-header">
<span class="tree-icon <?php echo $col['Key'] === 'PRI' ? 'key' : 'column'; ?>">
<?php if ($col['Key'] === 'PRI'): ?>
<i data-lucide="key-round"></i>
<?php else: ?>
<i data-lucide="columns-2"></i>
<?php endif; ?>
</span>
<span class="tree-label"><?php echo htmlspecialchars($col['Field']); ?></span>
<span class="tree-badge"><?php echo htmlspecialchars($col['Type']); ?></span>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</nav>
</aside>
<main class="dbm-content">

View File

@@ -80,6 +80,11 @@
</ul>
</li>
<?php if (Session::get("user_account_type") == 7) : ?>
<li <?php if (View::checkForActiveControllers($filename, ['database', 'table', 'sql', 'dbuser'])) {
echo ' class="active" ';
} ?> >
<a href="<?php echo Config::get('URL'); ?>database/index">Database</a>
</li>
<li <?php if (View::checkForActiveController($filename, "admin")) {
echo ' class="active" ';
} ?> >

View File

@@ -0,0 +1,86 @@
<div class="dbm-content-header">
<div class="dbm-breadcrumb">
<a href="<?php echo Config::get('URL'); ?>database/index">Databases</a>
</div>
<div class="dbm-title">
<h1>All Databases</h1>
<span class="badge"><?php echo count($this->databases); ?> total</span>
</div>
<div class="dbm-actions">
<button type="button" class="dbm-btn dbm-btn-success" onclick="document.getElementById('create-db-modal').style.display='flex'">
<i data-lucide="plus"></i>
Create Database
</button>
</div>
</div>
<div class="dbm-content-body">
<div class="dbm-stats">
<div class="dbm-stat">
<div class="dbm-stat-value"><?php echo count($this->databases); ?></div>
<div class="dbm-stat-label">Databases</div>
</div>
<div class="dbm-stat">
<div class="dbm-stat-value"><?php echo htmlspecialchars($this->current_db); ?></div>
<div class="dbm-stat-label">Current Database</div>
</div>
</div>
<div class="dbm-table-wrapper">
<table class="dbm-table">
<thead>
<tr>
<th>Database Name</th>
<th>Tables</th>
<th style="width: 200px;">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($this->databases as $db):
$tables = DatabaseModel::getTablesInDatabase($db);
?>
<tr>
<td>
<a href="<?php echo Config::get('URL'); ?>database/show/<?php echo urlencode($db); ?>" style="color: var(--accent-blue); text-decoration: none;">
<?php echo htmlspecialchars($db); ?>
</a>
<?php if ($db === $this->current_db): ?>
<span style="margin-left: 8px; font-size: 10px; padding: 2px 6px; background: var(--accent-green); color: #fff; border-radius: 3px;">ACTIVE</span>
<?php endif; ?>
</td>
<td><?php echo count($tables); ?></td>
<td>
<a href="<?php echo Config::get('URL'); ?>database/show/<?php echo urlencode($db); ?>" class="dbm-btn dbm-btn-sm dbm-btn-secondary">Browse</a>
<?php if ($db !== $this->current_db): ?>
<a href="<?php echo Config::get('URL'); ?>database/delete/<?php echo urlencode($db); ?>"
class="dbm-btn dbm-btn-sm dbm-btn-danger"
data-confirm="Delete database '<?php echo htmlspecialchars($db); ?>'? This cannot be undone!">Drop</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<!-- Create Database Modal -->
<div id="create-db-modal" style="display:none; position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.8); z-index:1000; align-items:center; justify-content:center;">
<div class="dbm-card" style="width: 400px; max-width: 90%;">
<div class="dbm-card-header">
<h3>Create New Database</h3>
</div>
<div class="dbm-card-body">
<form method="post" action="<?php echo Config::get('URL'); ?>database/create" data-ajax-form>
<div class="dbm-form-group">
<label class="dbm-form-label">Database Name</label>
<input type="text" name="database_name" class="dbm-form-input" required pattern="[a-zA-Z0-9_]+" placeholder="my_database" style="width:100%;max-width:100%;">
</div>
<div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px;">
<button type="button" class="dbm-btn dbm-btn-secondary" onclick="document.getElementById('create-db-modal').style.display='none'">Cancel</button>
<button type="submit" class="dbm-btn dbm-btn-success">Create</button>
</div>
</form>
</div>
</div>
</div>

View File

@@ -0,0 +1,76 @@
<div class="dbm-content-header">
<div class="dbm-breadcrumb">
<a href="<?php echo Config::get('URL'); ?>database/index">Databases</a>
<span class="separator">/</span>
<span><?php echo htmlspecialchars($this->database_name); ?></span>
</div>
<div class="dbm-title">
<h1><?php echo htmlspecialchars($this->database_name); ?></h1>
<span class="badge"><?php echo count($this->tables); ?> tables</span>
</div>
<div class="dbm-actions">
<a href="<?php echo Config::get('URL'); ?>table/create/<?php echo urlencode($this->database_name); ?>" class="dbm-btn dbm-btn-success">
<i data-lucide="plus"></i>
New Table
</a>
<a href="<?php echo Config::get('URL'); ?>database/export/<?php echo urlencode($this->database_name); ?>" class="dbm-btn dbm-btn-secondary" target="_blank">
<i data-lucide="download"></i>
Export SQL
</a>
<a href="<?php echo Config::get('URL'); ?>sql/index/<?php echo urlencode($this->database_name); ?>" class="dbm-btn dbm-btn-secondary">
<i data-lucide="terminal"></i>
SQL Console
</a>
</div>
</div>
<div class="dbm-content-body">
<?php if (empty($this->tables)): ?>
<div class="dbm-empty">
<i data-lucide="table" class="dbm-empty-icon"></i>
<h3>No tables yet</h3>
<p>This database is empty. Create your first table to get started.</p>
<a href="<?php echo Config::get('URL'); ?>table/create/<?php echo urlencode($this->database_name); ?>" class="dbm-btn dbm-btn-success" style="margin-top: 16px;">Create Table</a>
</div>
<?php else: ?>
<div class="dbm-table-wrapper">
<table class="dbm-table">
<thead>
<tr>
<th>Table Name</th>
<th>Engine</th>
<th>Rows</th>
<th>Size</th>
<th>Collation</th>
<th style="width: 220px;">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($this->tables as $table):
$info = isset($this->table_info[$table]) ? $this->table_info[$table] : [];
?>
<tr>
<td>
<a href="<?php echo Config::get('URL'); ?>table/show/<?php echo urlencode($this->database_name); ?>/<?php echo urlencode($table); ?>" style="color: var(--accent-blue); text-decoration: none; font-weight: 500;">
<?php echo htmlspecialchars($table); ?>
</a>
</td>
<td><span class="type-column"><?php echo isset($info['engine']) ? htmlspecialchars($info['engine']) : '-'; ?></span></td>
<td><?php echo isset($info['rows']) ? number_format($info['rows']) : '-'; ?></td>
<td><?php echo isset($info['total_size']) ? $info['total_size'] : '-'; ?></td>
<td><span style="font-size: 11px;"><?php echo isset($info['collation']) ? htmlspecialchars($info['collation']) : '-'; ?></span></td>
<td>
<a href="<?php echo Config::get('URL'); ?>table/show/<?php echo urlencode($this->database_name); ?>/<?php echo urlencode($table); ?>" class="dbm-btn dbm-btn-sm dbm-btn-primary">Browse</a>
<a href="<?php echo Config::get('URL'); ?>table/structure/<?php echo urlencode($this->database_name); ?>/<?php echo urlencode($table); ?>" class="dbm-btn dbm-btn-sm dbm-btn-secondary">Structure</a>
<a href="<?php echo Config::get('URL'); ?>table/export/<?php echo urlencode($this->database_name); ?>/<?php echo urlencode($table); ?>" class="dbm-btn dbm-btn-sm dbm-btn-secondary" target="_blank">Export</a>
<a href="<?php echo Config::get('URL'); ?>table/delete/<?php echo urlencode($this->database_name); ?>/<?php echo urlencode($table); ?>"
class="dbm-btn dbm-btn-sm dbm-btn-danger"
data-confirm="Drop table '<?php echo htmlspecialchars($table); ?>'? This cannot be undone!">Drop</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>

View File

@@ -0,0 +1,68 @@
<div class="dbm-content-header">
<div class="dbm-breadcrumb">
<a href="<?php echo Config::get('URL'); ?>dbuser/index">Users</a>
<span class="separator">/</span>
<span>Create User</span>
</div>
<div class="dbm-title">
<h1>Create New User</h1>
</div>
</div>
<div class="dbm-content-body">
<div class="dbm-card">
<div class="dbm-card-body">
<form method="post" action="<?php echo Config::get('URL'); ?>dbuser/create">
<div class="dbm-form-group">
<label class="dbm-form-label">Username</label>
<input type="text" name="username" class="dbm-form-input" required pattern="[a-zA-Z0-9_]+" placeholder="username">
</div>
<div class="dbm-form-group">
<label class="dbm-form-label">Password</label>
<input type="password" name="password" class="dbm-form-input" required placeholder="password">
</div>
<div class="dbm-form-group">
<label class="dbm-form-label">Host</label>
<select name="host" class="dbm-form-select">
<option value="localhost">localhost</option>
<option value="%">% (any host)</option>
<option value="127.0.0.1">127.0.0.1</option>
</select>
</div>
<div class="dbm-form-group">
<label class="dbm-form-label">Privileges</label>
<div style="margin-top: 8px; padding: 12px; background: var(--dbm-bg-secondary); border-radius: var(--dbm-radius);">
<div style="margin-bottom: 12px; padding-bottom: 12px; border-bottom: 1px solid var(--dbm-border);">
<label style="display: flex; align-items: center; gap: 6px; cursor: pointer; font-size: 13px; font-weight: 500; color: var(--dbm-text);">
<input type="checkbox" name="privileges[]" value="ALL PRIVILEGES" id="all-privs-check"
onchange="document.querySelectorAll('.priv-checkbox').forEach(cb => { cb.checked = this.checked; cb.disabled = this.checked; })">
ALL PRIVILEGES (*)
</label>
<small style="color: var(--dbm-text-muted); font-size: 11px; margin-left: 22px;">Grant all privileges on all databases</small>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 10px;">
<?php
$all_privileges = ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'CREATE', 'DROP', 'ALTER', 'INDEX', 'REFERENCES', 'CREATE TEMPORARY TABLES', 'LOCK TABLES', 'EXECUTE', 'CREATE VIEW', 'SHOW VIEW', 'CREATE ROUTINE', 'ALTER ROUTINE', 'EVENT', 'TRIGGER'];
foreach ($all_privileges as $priv):
?>
<label style="display: flex; align-items: center; gap: 6px; cursor: pointer; font-size: 12px; color: var(--dbm-text-secondary);">
<input type="checkbox" name="privileges[]" value="<?php echo $priv; ?>" class="priv-checkbox">
<?php echo $priv; ?>
</label>
<?php endforeach; ?>
</div>
</div>
</div>
<div style="margin-top: 20px; display: flex; gap: 8px;">
<button type="submit" name="submit_create_user" class="dbm-btn dbm-btn-success">
<i data-lucide="plus"></i>
Create User
</button>
<a href="<?php echo Config::get('URL'); ?>dbuser/index" class="dbm-btn dbm-btn-secondary">Cancel</a>
</div>
</form>
</div>
</div>
</div>

View File

@@ -0,0 +1,92 @@
<div class="dbm-content-header">
<div class="dbm-breadcrumb">
<a href="<?php echo Config::get('URL'); ?>dbuser/index">Users</a>
<span class="separator">/</span>
<span><?php echo htmlspecialchars($this->user->User); ?>@<?php echo htmlspecialchars($this->user->Host); ?></span>
</div>
<div class="dbm-title">
<h1>Edit User</h1>
</div>
</div>
<div class="dbm-content-body">
<form method="post" action="<?php echo Config::get('URL'); ?>dbuser/edit/<?php echo urlencode($this->user->User); ?>/<?php echo urlencode($this->user->Host); ?>">
<div class="dbm-card" style="margin-bottom: 20px;">
<div class="dbm-card-header">
<h3>User Details</h3>
</div>
<div class="dbm-card-body">
<div class="dbm-form-group">
<label class="dbm-form-label">Username</label>
<input type="text" class="dbm-form-input" value="<?php echo htmlspecialchars($this->user->User); ?>" disabled>
</div>
<div class="dbm-form-group">
<label class="dbm-form-label">Host</label>
<input type="text" class="dbm-form-input" value="<?php echo htmlspecialchars($this->user->Host); ?>" disabled>
</div>
<div class="dbm-form-group">
<label class="dbm-form-label">New Password</label>
<input type="password" name="password" class="dbm-form-input" placeholder="Leave empty to keep current password">
<small style="color: var(--dbm-text-muted); font-size: 11px; display: block; margin-top: 4px;">Only fill this if you want to change the password</small>
</div>
</div>
</div>
<div class="dbm-card" style="margin-bottom: 20px;">
<div class="dbm-card-header">
<h3>Global Privileges</h3>
</div>
<div class="dbm-card-body">
<?php
$all_privileges = ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'CREATE', 'DROP', 'ALTER', 'INDEX', 'REFERENCES', 'CREATE TEMPORARY TABLES', 'LOCK TABLES', 'EXECUTE', 'CREATE VIEW', 'SHOW VIEW', 'CREATE ROUTINE', 'ALTER ROUTINE', 'EVENT', 'TRIGGER'];
$current_grants = implode(' ', $this->privileges);
$has_all = stripos($current_grants, 'ALL PRIVILEGES') !== false;
?>
<div style="margin-bottom: 12px; padding-bottom: 12px; border-bottom: 1px solid var(--dbm-border);">
<label style="display: flex; align-items: center; gap: 6px; cursor: pointer; font-size: 13px; font-weight: 500; color: var(--dbm-text);">
<input type="checkbox" name="privileges[]" value="ALL PRIVILEGES" id="all-privs-check"
<?php echo $has_all ? 'checked' : ''; ?>
onchange="document.querySelectorAll('.priv-checkbox').forEach(cb => { cb.checked = this.checked; cb.disabled = this.checked; })">
ALL PRIVILEGES (*)
</label>
<small style="color: var(--dbm-text-muted); font-size: 11px; margin-left: 22px;">Grant all privileges on all databases</small>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 10px;">
<?php foreach ($all_privileges as $priv): ?>
<label style="display: flex; align-items: center; gap: 6px; cursor: pointer; font-size: 12px; color: var(--dbm-text-secondary);">
<input type="checkbox" name="privileges[]" value="<?php echo $priv; ?>" class="priv-checkbox"
<?php echo ($has_all || stripos($current_grants, $priv) !== false) ? 'checked' : ''; ?>
<?php echo $has_all ? 'disabled' : ''; ?>>
<?php echo $priv; ?>
</label>
<?php endforeach; ?>
</div>
</div>
</div>
<div class="dbm-card">
<div class="dbm-card-header">
<h3>Current Grants</h3>
</div>
<div class="dbm-card-body">
<?php if (!empty($this->privileges)): ?>
<?php foreach ($this->privileges as $grant): ?>
<div style="font-family: monospace; font-size: 11px; padding: 8px; background: var(--dbm-bg-secondary); border-radius: var(--dbm-radius); margin-bottom: 6px; word-break: break-all;">
<?php echo htmlspecialchars($grant); ?>
</div>
<?php endforeach; ?>
<?php else: ?>
<p style="color: var(--dbm-text-muted); font-size: 12px; margin: 0;">No grants found</p>
<?php endif; ?>
</div>
</div>
<div style="margin-top: 20px; display: flex; gap: 8px;">
<button type="submit" name="submit_edit_user" class="dbm-btn dbm-btn-success">
<i data-lucide="check"></i>
Save Changes
</button>
<a href="<?php echo Config::get('URL'); ?>dbuser/index" class="dbm-btn dbm-btn-secondary">Cancel</a>
</div>
</form>
</div>

View File

@@ -0,0 +1,101 @@
<div class="dbm-content-header">
<div class="dbm-breadcrumb">
<span>Users</span>
</div>
<div class="dbm-title">
<h1>MySQL Users</h1>
<span class="badge"><?php echo count($this->users); ?> users</span>
</div>
<div class="dbm-actions">
<button type="button" class="dbm-btn dbm-btn-success" onclick="document.getElementById('create-user-modal').style.display='flex'">
<i data-lucide="plus"></i>
Create User
</button>
</div>
</div>
<div class="dbm-content-body">
<div class="dbm-card" style="margin-bottom: 16px;">
<div class="dbm-card-body" style="padding: 12px 16px;">
<span style="color: var(--dbm-text-muted); font-size: 12px;">Connected as:</span>
<strong style="margin-left: 6px; color: var(--dbm-text);"><?php echo htmlspecialchars($this->current_user); ?></strong>
</div>
</div>
<div class="dbm-table-wrapper">
<table class="dbm-table">
<thead>
<tr>
<th>Username</th>
<th>Host</th>
<th style="width: 200px;">Actions</th>
</tr>
</thead>
<tbody>
<?php if (!empty($this->users)): ?>
<?php foreach ($this->users as $user): ?>
<tr>
<td style="font-weight: 500; color: var(--dbm-text);">
<i data-lucide="user" style="width: 12px; height: 12px; margin-right: 6px; color: var(--dbm-text-muted);"></i>
<?php echo htmlspecialchars($user->User); ?>
<?php if ($user->User === $this->current_user): ?>
<span class="badge" style="margin-left: 6px; font-size: 9px;">current</span>
<?php endif; ?>
</td>
<td><span class="type-column"><?php echo htmlspecialchars($user->Host); ?></span></td>
<td>
<a href="<?php echo Config::get('URL'); ?>dbuser/edit/<?php echo urlencode($user->User); ?>/<?php echo urlencode($user->Host); ?>" class="dbm-btn dbm-btn-sm dbm-btn-secondary">
<i data-lucide="pencil" style="width: 11px; height: 11px;"></i>
Edit
</a>
<?php if ($user->User !== $this->current_user): ?>
<a href="<?php echo Config::get('URL'); ?>dbuser/delete/<?php echo urlencode($user->User); ?>/<?php echo urlencode($user->Host); ?>"
class="dbm-btn dbm-btn-sm dbm-btn-danger"
data-confirm="Delete user '<?php echo htmlspecialchars($user->User); ?>'@'<?php echo htmlspecialchars($user->Host); ?>'? This cannot be undone!">
<i data-lucide="trash-2" style="width: 11px; height: 11px;"></i>
Delete
</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="3" style="text-align: center; padding: 40px; color: var(--dbm-text-muted);">
No users found
</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<div id="create-user-modal" class="dbm-modal" style="display: none;">
<div class="dbm-modal-content">
<div class="dbm-modal-header">
<h3>Create New User</h3>
<button type="button" class="dbm-modal-close" onclick="this.closest('.dbm-modal').style.display='none'">&times;</button>
</div>
<form method="post" action="<?php echo Config::get('URL'); ?>dbuser/create">
<div class="dbm-modal-body">
<div class="dbm-form-group">
<label class="dbm-form-label">Username</label>
<input type="text" name="username" class="dbm-form-input" required pattern="[a-zA-Z0-9_]+" placeholder="username">
</div>
<div class="dbm-form-group">
<label class="dbm-form-label">Password</label>
<input type="password" name="password" class="dbm-form-input" required placeholder="password">
</div>
<div class="dbm-form-group">
<label class="dbm-form-label">Host</label>
<input type="text" name="host" class="dbm-form-input" required value="localhost" placeholder="localhost or %">
</div>
</div>
<div class="dbm-modal-footer">
<button type="button" class="dbm-btn dbm-btn-secondary" onclick="this.closest('.dbm-modal').style.display='none'">Cancel</button>
<button type="submit" name="submit_create_user" class="dbm-btn dbm-btn-success">Create User</button>
</div>
</form>
</div>
</div>

View File

@@ -0,0 +1,37 @@
<div class="dbm-content-header">
<div class="dbm-breadcrumb">
<a href="<?php echo Config::get('URL'); ?>dbuser/index">Users</a>
<span class="separator">/</span>
<span><?php echo htmlspecialchars($this->user->User); ?>@<?php echo htmlspecialchars($this->user->Host); ?></span>
<span class="separator">/</span>
<span>Privileges</span>
</div>
<div class="dbm-title">
<h1>User Privileges</h1>
</div>
<div class="dbm-actions">
<a href="<?php echo Config::get('URL'); ?>dbuser/edit/<?php echo urlencode($this->user->User); ?>/<?php echo urlencode($this->user->Host); ?>" class="dbm-btn dbm-btn-secondary">
<i data-lucide="pencil"></i>
Edit User
</a>
</div>
</div>
<div class="dbm-content-body">
<div class="dbm-card">
<div class="dbm-card-header">
<h3>Grant Statements</h3>
</div>
<div class="dbm-card-body">
<?php if (!empty($this->privileges)): ?>
<?php foreach ($this->privileges as $grant): ?>
<div style="font-family: monospace; font-size: 11px; padding: 10px; background: var(--dbm-bg-secondary); border-radius: var(--dbm-radius); margin-bottom: 8px; word-break: break-all; border: 1px solid var(--dbm-border);">
<?php echo htmlspecialchars($grant); ?>
</div>
<?php endforeach; ?>
<?php else: ?>
<p style="color: var(--dbm-text-muted); font-size: 13px; margin: 0;">No privileges found for this user.</p>
<?php endif; ?>
</div>
</div>
</div>

View File

@@ -0,0 +1,282 @@
<div class="dbmanager-container">
<div class="dbmanager-sidebar">
<h3>Databases</h3>
<ul class="db-tree">
<?php foreach ($this->databases as $db): ?>
<li class="db-item <?php echo ($db === $this->database_name) ? 'active' : ''; ?>">
<a href="<?php echo Config::get('URL'); ?>sql/index/<?php echo urlencode($db); ?>" class="db-link">
<?php echo htmlspecialchars($db); ?>
</a>
</li>
<?php endforeach; ?>
</ul>
<div class="db-actions">
<a href="<?php echo Config::get('URL'); ?>database/index" class="btn" style="display:block;text-align:center;">Back to Databases</a>
</div>
<?php if (!empty($this->history)): ?>
<div style="margin-top:20px;">
<h4>Query History</h4>
<ul class="query-history">
<?php foreach (array_slice($this->history, 0, 10) as $item): ?>
<li class="history-item" onclick="document.getElementById('sql_query').value = this.dataset.query;" data-query="<?php echo htmlspecialchars($item['query_text']); ?>">
<?php echo htmlspecialchars(substr($item['query_text'], 0, 40)); ?>...
</li>
<?php endforeach; ?>
</ul>
<a href="<?php echo Config::get('URL'); ?>sql/clearHistory" class="btn btn-small btn-danger" style="margin-top:10px;">Clear History</a>
</div>
<?php endif; ?>
</div>
<div class="dbmanager-content">
<h2>SQL Console</h2>
<p>Database: <strong><?php echo htmlspecialchars($this->database_name); ?></strong></p>
<form method="post" action="<?php echo Config::get('URL'); ?>sql/execute" id="sql-form">
<input type="hidden" name="database_name" value="<?php echo htmlspecialchars($this->database_name); ?>">
<div class="sql-editor">
<textarea name="sql_query" id="sql_query" rows="6" placeholder="Enter your SQL query here...
Example: SELECT * FROM users LIMIT 10"></textarea>
</div>
<div class="sql-actions">
<button type="submit" class="btn">Execute Query</button>
<button type="button" class="btn" style="background:#6c757d;" onclick="document.getElementById('sql_query').value = '';">Clear</button>
</div>
</form>
<div id="sql-result" class="sql-result">
<?php
// Check for session result
$result = Session::get('sql_result');
if ($result) {
Session::set('sql_result', null);
if ($result['success']) {
echo '<div class="result-success">';
echo '<p>' . htmlspecialchars($result['message']) . '</p>';
echo '<p class="execution-time">Execution time: ' . $result['execution_time'] . ' ms</p>';
if (!empty($result['result'])) {
echo '<div class="table-wrapper"><table class="data-table"><thead><tr>';
foreach (array_keys($result['result'][0]) as $col) {
echo '<th>' . htmlspecialchars($col) . '</th>';
}
echo '</tr></thead><tbody>';
foreach ($result['result'] as $row) {
echo '<tr>';
foreach ($row as $value) {
echo '<td>' . ($value === null ? '<span style="color:#999;">NULL</span>' : htmlspecialchars(substr($value, 0, 100))) . '</td>';
}
echo '</tr>';
}
echo '</tbody></table></div>';
}
echo '</div>';
} else {
echo '<div class="result-error">';
echo '<p><strong>Error:</strong> ' . htmlspecialchars($result['message']) . '</p>';
if (!empty($result['error'])) {
echo '<p class="error-details">' . htmlspecialchars($result['error']) . '</p>';
}
echo '</div>';
}
}
?>
</div>
</div>
</div>
<style>
.dbmanager-container {
display: flex;
gap: 20px;
margin-top: 20px;
}
.dbmanager-sidebar {
width: 250px;
background: #f5f5f5;
padding: 15px;
border-radius: 5px;
flex-shrink: 0;
}
.dbmanager-sidebar h3, .dbmanager-sidebar h4 {
margin: 0 0 10px 0;
font-size: 14px;
color: #333;
}
.db-tree {
list-style: none;
padding: 0;
margin: 0 0 20px 0;
max-height: 200px;
overflow-y: auto;
}
.db-item {
padding: 5px 10px;
border-radius: 3px;
margin-bottom: 2px;
}
.db-item:hover {
background: #e0e0e0;
}
.db-item.active {
background: #007bff;
}
.db-item.active .db-link {
color: #fff;
}
.db-link {
text-decoration: none;
color: #333;
display: block;
}
.query-history {
list-style: none;
padding: 0;
margin: 0;
max-height: 200px;
overflow-y: auto;
}
.history-item {
padding: 5px;
font-size: 11px;
color: #666;
cursor: pointer;
border-bottom: 1px solid #ddd;
word-break: break-all;
}
.history-item:hover {
background: #e0e0e0;
}
.db-actions {
border-top: 1px solid #ddd;
padding-top: 15px;
}
.dbmanager-content {
flex: 1;
}
.dbmanager-content h2 {
margin-top: 0;
}
.sql-editor textarea {
width: 100%;
padding: 12px;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 5px;
resize: vertical;
box-sizing: border-box;
}
.sql-actions {
margin-top: 10px;
display: flex;
gap: 10px;
}
.sql-result {
margin-top: 20px;
}
.result-success {
background: #d4edda;
border: 1px solid #c3e6cb;
border-radius: 5px;
padding: 15px;
}
.result-error {
background: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 5px;
padding: 15px;
}
.execution-time {
font-size: 12px;
color: #666;
}
.error-details {
font-family: monospace;
font-size: 12px;
color: #721c24;
}
.table-wrapper {
overflow-x: auto;
margin-top: 15px;
}
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 12px;
background: #fff;
}
.data-table th,
.data-table td {
padding: 8px;
text-align: left;
border: 1px solid #ddd;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.data-table th {
background: #f8f9fa;
font-weight: bold;
}
.btn {
display: inline-block;
padding: 8px 16px;
text-decoration: none;
border-radius: 3px;
font-size: 14px;
cursor: pointer;
border: none;
background: #007bff;
color: #fff;
}
.btn:hover {
background: #0056b3;
}
.btn-small {
padding: 4px 8px;
font-size: 11px;
}
.btn-danger {
background: #dc3545;
}
.btn-danger:hover {
background: #c82333;
}
</style>

View File

@@ -0,0 +1,68 @@
<div class="dbm-content-header">
<div class="dbm-breadcrumb">
<a href="<?php echo Config::get('URL'); ?>database/index">Databases</a>
<span class="separator">/</span>
<a href="<?php echo Config::get('URL'); ?>database/show/<?php echo urlencode($this->database_name); ?>"><?php echo htmlspecialchars($this->database_name); ?></a>
<span class="separator">/</span>
<a href="<?php echo Config::get('URL'); ?>table/structure/<?php echo urlencode($this->database_name); ?>/<?php echo urlencode($this->table_name); ?>"><?php echo htmlspecialchars($this->table_name); ?></a>
<span class="separator">/</span>
<span>Add Column</span>
</div>
<div class="dbm-title">
<h1>Add Column</h1>
</div>
</div>
<div class="dbm-content-body">
<div class="dbm-card" style="max-width: 500px;">
<div class="dbm-card-body">
<form method="post" action="<?php echo Config::get('URL'); ?>table/addColumn/<?php echo urlencode($this->database_name); ?>/<?php echo urlencode($this->table_name); ?>">
<div class="dbm-form-group">
<label class="dbm-form-label">Column Name</label>
<input type="text" name="column_name" class="dbm-form-input" required pattern="[a-zA-Z0-9_]+" placeholder="column_name" style="width: 100%; max-width: 100%;">
</div>
<div class="dbm-form-group">
<label class="dbm-form-label">Type</label>
<select name="column_type" class="dbm-form-select" style="width: 100%; max-width: 100%;">
<option value="INT">INT</option>
<option value="VARCHAR(255)">VARCHAR(255)</option>
<option value="TEXT">TEXT</option>
<option value="DATETIME">DATETIME</option>
<option value="TIMESTAMP">TIMESTAMP</option>
<option value="DECIMAL(10,2)">DECIMAL(10,2)</option>
<option value="BOOLEAN">BOOLEAN</option>
</select>
</div>
<div class="dbm-form-group">
<label class="dbm-form-label">Null</label>
<select name="column_null" class="dbm-form-select" style="width: 100%; max-width: 100%;">
<option value="YES">NULL</option>
<option value="NO">NOT NULL</option>
</select>
</div>
<div class="dbm-form-group">
<label class="dbm-form-label">Default Value</label>
<input type="text" name="column_default" class="dbm-form-input" placeholder="Leave empty for none" style="width: 100%; max-width: 100%;">
</div>
<div class="dbm-form-group">
<label class="dbm-form-label">Extra</label>
<select name="column_extra" class="dbm-form-select" style="width: 100%; max-width: 100%;">
<option value="">None</option>
<option value="auto_increment">AUTO_INCREMENT</option>
</select>
</div>
<input type="hidden" name="column_key" value="">
<div style="margin-top: 24px; display: flex; gap: 10px;">
<button type="submit" name="submit_add_column" class="dbm-btn dbm-btn-success">Add Column</button>
<a href="<?php echo Config::get('URL'); ?>table/structure/<?php echo urlencode($this->database_name); ?>/<?php echo urlencode($this->table_name); ?>" class="dbm-btn dbm-btn-secondary">Cancel</a>
</div>
</form>
</div>
</div>
</div>

View File

@@ -0,0 +1,122 @@
<div class="dbm-content-header">
<div class="dbm-breadcrumb">
<a href="<?php echo Config::get('URL'); ?>database/index">Databases</a>
<span class="separator">/</span>
<a href="<?php echo Config::get('URL'); ?>database/show/<?php echo urlencode($this->database_name); ?>"><?php echo htmlspecialchars($this->database_name); ?></a>
<span class="separator">/</span>
<span>New Table</span>
</div>
<div class="dbm-title">
<h1>Create New Table</h1>
</div>
</div>
<div class="dbm-content-body">
<div class="dbm-card">
<div class="dbm-card-body">
<form method="post" action="<?php echo Config::get('URL'); ?>table/create/<?php echo urlencode($this->database_name); ?>" id="create-table-form">
<div class="dbm-form-group">
<label class="dbm-form-label">Table Name</label>
<input type="text" name="table_name" class="dbm-form-input" required pattern="[a-zA-Z0-9_]+" placeholder="my_table">
</div>
<h3 style="color: var(--text-primary); margin: 24px 0 16px;">Columns</h3>
<div id="columns-container">
<div class="column-row" style="display: flex; gap: 10px; margin-bottom: 12px; padding: 16px; background: var(--bg-input); border-radius: var(--radius); flex-wrap: wrap; align-items: center;">
<input type="text" name="columns[0][name]" class="dbm-form-input" placeholder="Column name" required style="width: 150px; max-width: 150px;">
<select name="columns[0][type]" class="dbm-form-select" style="width: 140px; max-width: 140px;">
<option value="INT">INT</option>
<option value="VARCHAR(255)">VARCHAR(255)</option>
<option value="TEXT">TEXT</option>
<option value="DATETIME">DATETIME</option>
<option value="TIMESTAMP">TIMESTAMP</option>
<option value="DECIMAL(10,2)">DECIMAL(10,2)</option>
<option value="BOOLEAN">BOOLEAN</option>
</select>
<select name="columns[0][null]" class="dbm-form-select" style="width: 100px; max-width: 100px;">
<option value="YES">NULL</option>
<option value="NO">NOT NULL</option>
</select>
<select name="columns[0][key]" class="dbm-form-select" style="width: 130px; max-width: 130px;">
<option value="">No Key</option>
<option value="PRI">PRIMARY KEY</option>
</select>
<input type="text" name="columns[0][default]" class="dbm-form-input" placeholder="Default" style="width: 100px; max-width: 100px;">
<select name="columns[0][extra]" class="dbm-form-select" style="width: 140px; max-width: 140px;">
<option value="">None</option>
<option value="auto_increment">AUTO_INCREMENT</option>
</select>
<button type="button" class="dbm-btn dbm-btn-sm dbm-btn-danger remove-column" style="display: none;">Remove</button>
</div>
</div>
<button type="button" id="add-column" class="dbm-btn dbm-btn-secondary" style="margin-top: 8px;">
<i data-lucide="plus"></i>
Add Column
</button>
<div style="margin-top: 32px; padding-top: 24px; border-top: 1px solid var(--border-color);">
<button type="submit" name="submit_create_table" class="dbm-btn dbm-btn-success">
<i data-lucide="check-circle"></i>
Create Table
</button>
<a href="<?php echo Config::get('URL'); ?>database/show/<?php echo urlencode($this->database_name); ?>" class="dbm-btn dbm-btn-secondary">Cancel</a>
</div>
</form>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
let columnIndex = 1;
document.getElementById('add-column').addEventListener('click', function() {
const container = document.getElementById('columns-container');
const newRow = document.createElement('div');
newRow.className = 'column-row';
newRow.style.cssText = 'display: flex; gap: 10px; margin-bottom: 12px; padding: 16px; background: var(--bg-input); border-radius: var(--radius); flex-wrap: wrap; align-items: center;';
newRow.innerHTML = `
<input type="text" name="columns[${columnIndex}][name]" class="dbm-form-input" placeholder="Column name" required style="width: 150px; max-width: 150px;">
<select name="columns[${columnIndex}][type]" class="dbm-form-select" style="width: 140px; max-width: 140px;">
<option value="INT">INT</option>
<option value="VARCHAR(255)">VARCHAR(255)</option>
<option value="TEXT">TEXT</option>
<option value="DATETIME">DATETIME</option>
<option value="TIMESTAMP">TIMESTAMP</option>
<option value="DECIMAL(10,2)">DECIMAL(10,2)</option>
<option value="BOOLEAN">BOOLEAN</option>
</select>
<select name="columns[${columnIndex}][null]" class="dbm-form-select" style="width: 100px; max-width: 100px;">
<option value="YES">NULL</option>
<option value="NO">NOT NULL</option>
</select>
<select name="columns[${columnIndex}][key]" class="dbm-form-select" style="width: 130px; max-width: 130px;">
<option value="">No Key</option>
<option value="PRI">PRIMARY KEY</option>
</select>
<input type="text" name="columns[${columnIndex}][default]" class="dbm-form-input" placeholder="Default" style="width: 100px; max-width: 100px;">
<select name="columns[${columnIndex}][extra]" class="dbm-form-select" style="width: 140px; max-width: 140px;">
<option value="">None</option>
<option value="auto_increment">AUTO_INCREMENT</option>
</select>
<button type="button" class="dbm-btn dbm-btn-sm dbm-btn-danger remove-column">Remove</button>
`;
container.appendChild(newRow);
columnIndex++;
document.querySelectorAll('.remove-column').forEach(btn => btn.style.display = 'inline-flex');
});
document.getElementById('columns-container').addEventListener('click', function(e) {
if (e.target.classList.contains('remove-column')) {
e.target.closest('.column-row').remove();
const rows = document.querySelectorAll('.column-row');
if (rows.length === 1) {
rows[0].querySelector('.remove-column').style.display = 'none';
}
}
});
});
</script>

View File

@@ -0,0 +1,435 @@
<?php
$total_pages = ceil($this->total_rows / $this->per_page);
$pk_column = null;
foreach ($this->columns as $col) {
if ($col['Key'] === 'PRI') {
$pk_column = $col['Field'];
break;
}
}
?>
<div class="dbm-content-header">
<div class="dbm-breadcrumb">
<a href="<?php echo Config::get('URL'); ?>database/index">Databases</a>
<span class="separator">/</span>
<a href="<?php echo Config::get('URL'); ?>database/show/<?php echo urlencode($this->database_name); ?>"><?php echo htmlspecialchars($this->database_name); ?></a>
<span class="separator">/</span>
<span><?php echo htmlspecialchars($this->table_name); ?></span>
</div>
<div class="dbm-title">
<h1><?php echo htmlspecialchars($this->table_name); ?></h1>
<span class="badge"><?php echo number_format($this->total_rows); ?> rows</span>
</div>
<div class="dbm-actions">
<button type="button" id="btn-add-row" class="dbm-btn dbm-btn-success">
<i data-lucide="plus"></i>
Add Row
</button>
<a href="<?php echo Config::get('URL'); ?>table/structure/<?php echo urlencode($this->database_name); ?>/<?php echo urlencode($this->table_name); ?>" class="dbm-btn dbm-btn-secondary">
<i data-lucide="settings"></i>
Structure
</a>
<a href="<?php echo Config::get('URL'); ?>table/export/<?php echo urlencode($this->database_name); ?>/<?php echo urlencode($this->table_name); ?>" class="dbm-btn dbm-btn-secondary" target="_blank">
<i data-lucide="download"></i>
Export
</a>
<a href="<?php echo Config::get('URL'); ?>table/delete/<?php echo urlencode($this->database_name); ?>/<?php echo urlencode($this->table_name); ?>"
class="dbm-btn dbm-btn-danger"
data-confirm="Drop table '<?php echo htmlspecialchars($this->table_name); ?>'? This cannot be undone!">
<i data-lucide="trash-2"></i>
Drop Table
</a>
</div>
</div>
<div class="dbm-content-body">
<?php if (!empty($this->table_info)): ?>
<div class="dbm-stats">
<div class="dbm-stat">
<div class="dbm-stat-value"><?php echo number_format($this->total_rows); ?></div>
<div class="dbm-stat-label">Total Rows</div>
</div>
<div class="dbm-stat">
<div class="dbm-stat-value"><?php echo count($this->columns); ?></div>
<div class="dbm-stat-label">Columns</div>
</div>
<div class="dbm-stat">
<div class="dbm-stat-value"><?php echo $this->table_info['total_size'] ?? '-'; ?></div>
<div class="dbm-stat-label">Size</div>
</div>
<div class="dbm-stat">
<div class="dbm-stat-value"><?php echo $this->table_info['engine'] ?? '-'; ?></div>
<div class="dbm-stat-label">Engine</div>
</div>
</div>
<?php endif; ?>
<div class="dbm-table-wrapper" id="data-table-wrapper"
data-database="<?php echo htmlspecialchars($this->database_name); ?>"
data-table="<?php echo htmlspecialchars($this->table_name); ?>"
data-pk-column="<?php echo htmlspecialchars($pk_column ?? ''); ?>"
style="max-height: 500px; overflow: auto;">
<table class="dbm-table" id="data-table">
<thead>
<tr>
<?php foreach ($this->columns as $col): ?>
<th data-column="<?php echo htmlspecialchars($col['Field']); ?>" data-type="<?php echo htmlspecialchars($col['Type']); ?>">
<?php if ($col['Key'] === 'PRI'): ?>
<span style="color: var(--accent-green);" title="Primary Key">
<i data-lucide="key-round" style="width: 12px; height: 12px; vertical-align: middle;"></i>
</span>
<?php endif; ?>
<?php echo htmlspecialchars($col['Field']); ?>
</th>
<?php endforeach; ?>
<th style="width: 120px; text-align: center;">Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($this->rows)): ?>
<tr class="empty-row">
<td colspan="<?php echo count($this->columns) + 1; ?>" style="text-align: center; padding: 40px; color: var(--text-muted);">
No data in this table. Click "Add Row" to insert data.
</td>
</tr>
<?php else: ?>
<?php foreach ($this->rows as $row): ?>
<tr class="data-row" data-pk="<?php echo htmlspecialchars($pk_column ? ($row[$pk_column] ?? '') : ''); ?>">
<?php foreach ($this->columns as $col): ?>
<td class="editable-cell" data-column="<?php echo htmlspecialchars($col['Field']); ?>" data-original="<?php echo htmlspecialchars($row[$col['Field']] ?? ''); ?>">
<span class="cell-display"><?php
$value = $row[$col['Field']] ?? null;
if ($value === null) {
echo '<span class="null-value">NULL</span>';
} else {
$display = htmlspecialchars(substr($value, 0, 100));
if (strlen($value) > 100) $display .= '...';
echo $display;
}
?></span>
<input type="text" class="cell-input dbm-form-input" style="display: none; width: 100%; padding: 4px 8px; font-size: 13px;" value="<?php echo htmlspecialchars($row[$col['Field']] ?? ''); ?>">
</td>
<?php endforeach; ?>
<td class="actions-cell" style="text-align: center; white-space: nowrap;">
<div class="view-actions">
<button type="button" class="dbm-btn dbm-btn-sm dbm-btn-secondary btn-edit-row" title="Edit">
<i data-lucide="pencil" style="width: 12px; height: 12px;"></i>
</button>
<button type="button" class="dbm-btn dbm-btn-sm dbm-btn-danger btn-delete-row" title="Delete">
<i data-lucide="trash-2" style="width: 12px; height: 12px;"></i>
</button>
</div>
<div class="edit-actions" style="display: none;">
<button type="button" class="dbm-btn dbm-btn-sm dbm-btn-success btn-save-row" title="Save">
<i data-lucide="check" style="width: 12px; height: 12px;"></i>
</button>
<button type="button" class="dbm-btn dbm-btn-sm dbm-btn-secondary btn-cancel-edit" title="Cancel">
<i data-lucide="x" style="width: 12px; height: 12px;"></i>
</button>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<?php if ($total_pages > 1): ?>
<div class="dbm-pagination">
<div class="dbm-pagination-info">
Showing <?php echo (($this->current_page - 1) * $this->per_page) + 1; ?> - <?php echo min($this->current_page * $this->per_page, $this->total_rows); ?> of <?php echo number_format($this->total_rows); ?>
</div>
<div class="dbm-pagination-controls">
<?php if ($this->current_page > 1): ?>
<a href="<?php echo Config::get('URL'); ?>table/show/<?php echo urlencode($this->database_name); ?>/<?php echo urlencode($this->table_name); ?>/1" class="dbm-pagination-btn">First</a>
<a href="<?php echo Config::get('URL'); ?>table/show/<?php echo urlencode($this->database_name); ?>/<?php echo urlencode($this->table_name); ?>/<?php echo $this->current_page - 1; ?>" class="dbm-pagination-btn">Prev</a>
<?php endif; ?>
<?php
$start = max(1, $this->current_page - 2);
$end = min($total_pages, $this->current_page + 2);
for ($i = $start; $i <= $end; $i++):
?>
<a href="<?php echo Config::get('URL'); ?>table/show/<?php echo urlencode($this->database_name); ?>/<?php echo urlencode($this->table_name); ?>/<?php echo $i; ?>"
class="dbm-pagination-btn <?php echo $i === $this->current_page ? 'active' : ''; ?>"><?php echo $i; ?></a>
<?php endfor; ?>
<?php if ($this->current_page < $total_pages): ?>
<a href="<?php echo Config::get('URL'); ?>table/show/<?php echo urlencode($this->database_name); ?>/<?php echo urlencode($this->table_name); ?>/<?php echo $this->current_page + 1; ?>" class="dbm-pagination-btn">Next</a>
<a href="<?php echo Config::get('URL'); ?>table/show/<?php echo urlencode($this->database_name); ?>/<?php echo urlencode($this->table_name); ?>/<?php echo $total_pages; ?>" class="dbm-pagination-btn">Last</a>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const wrapper = document.getElementById('data-table-wrapper');
const table = document.getElementById('data-table');
const database = wrapper.dataset.database;
const tableName = wrapper.dataset.table;
const pkColumn = wrapper.dataset.pkColumn;
const baseUrl = '<?php echo Config::get('URL'); ?>';
// Get column info from table headers
function getColumns() {
const headers = table.querySelectorAll('thead th[data-column]');
return Array.from(headers).map(th => ({
name: th.dataset.column,
type: th.dataset.type
}));
}
// Edit row
table.addEventListener('click', function(e) {
const editBtn = e.target.closest('.btn-edit-row');
if (editBtn) {
const row = editBtn.closest('tr');
enterEditMode(row);
}
});
// Save row
table.addEventListener('click', function(e) {
const saveBtn = e.target.closest('.btn-save-row');
if (saveBtn) {
const row = saveBtn.closest('tr');
saveRow(row);
}
});
// Cancel edit
table.addEventListener('click', function(e) {
const cancelBtn = e.target.closest('.btn-cancel-edit');
if (cancelBtn) {
const row = cancelBtn.closest('tr');
cancelEdit(row);
}
});
// Delete row
table.addEventListener('click', function(e) {
const deleteBtn = e.target.closest('.btn-delete-row');
if (deleteBtn) {
const row = deleteBtn.closest('tr');
if (confirm('Delete this row? This cannot be undone.')) {
deleteRow(row);
}
}
});
// Add new row
document.getElementById('btn-add-row').addEventListener('click', function() {
addNewRow();
});
function enterEditMode(row) {
row.classList.add('editing');
row.querySelectorAll('.cell-display').forEach(el => el.style.display = 'none');
row.querySelectorAll('.cell-input').forEach(el => el.style.display = 'block');
row.querySelector('.view-actions').style.display = 'none';
row.querySelector('.edit-actions').style.display = 'inline-flex';
}
function exitEditMode(row) {
row.classList.remove('editing');
row.querySelectorAll('.cell-display').forEach(el => el.style.display = 'inline');
row.querySelectorAll('.cell-input').forEach(el => el.style.display = 'none');
row.querySelector('.view-actions').style.display = 'inline-flex';
row.querySelector('.edit-actions').style.display = 'none';
}
function cancelEdit(row) {
if (row.classList.contains('new-row')) {
row.remove();
// Show empty message if no rows left
const remainingRows = table.querySelectorAll('tbody tr.data-row');
if (remainingRows.length === 0) {
const cols = getColumns();
const emptyRow = document.createElement('tr');
emptyRow.className = 'empty-row';
emptyRow.innerHTML = '<td colspan="' + (cols.length + 1) + '" style="text-align: center; padding: 40px; color: var(--text-muted);">No data in this table. Click "Add Row" to insert data.</td>';
table.querySelector('tbody').appendChild(emptyRow);
}
return;
}
// Restore original values
row.querySelectorAll('.editable-cell').forEach(cell => {
const original = cell.dataset.original;
cell.querySelector('.cell-input').value = original;
});
exitEditMode(row);
}
function saveRow(row) {
const isNew = row.classList.contains('new-row');
const pkValue = row.dataset.pk;
const data = {};
row.querySelectorAll('.editable-cell').forEach(cell => {
const column = cell.dataset.column;
const input = cell.querySelector('.cell-input');
data[column] = input.value;
});
const url = isNew
? baseUrl + 'table/insertRow/' + encodeURIComponent(database) + '/' + encodeURIComponent(tableName)
: baseUrl + 'table/updateRow/' + encodeURIComponent(database) + '/' + encodeURIComponent(tableName);
const formData = new FormData();
if (!isNew) {
formData.append('pk_value', pkValue);
}
for (const key in data) {
formData.append('data[' + key + ']', data[key]);
}
fetch(url, {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(result => {
if (result.success) {
// Update display values
row.querySelectorAll('.editable-cell').forEach(cell => {
const input = cell.querySelector('.cell-input');
const display = cell.querySelector('.cell-display');
const value = input.value;
cell.dataset.original = value;
if (value === '' || value === null) {
display.innerHTML = '<span class="null-value">NULL</span>';
} else {
display.textContent = value.length > 100 ? value.substring(0, 100) + '...' : value;
}
});
if (isNew && result.insert_id && pkColumn) {
row.dataset.pk = result.insert_id;
// Update the PK cell display
const pkCell = row.querySelector('.editable-cell[data-column="' + pkColumn + '"]');
if (pkCell) {
pkCell.querySelector('.cell-input').value = result.insert_id;
pkCell.querySelector('.cell-display').textContent = result.insert_id;
pkCell.dataset.original = result.insert_id;
}
}
row.classList.remove('new-row');
exitEditMode(row);
} else {
alert('Error: ' + result.message);
}
})
.catch(error => {
alert('Error saving row: ' + error.message);
});
}
function deleteRow(row) {
const pkValue = row.dataset.pk;
if (!pkValue) {
alert('Cannot delete: no primary key');
return;
}
const formData = new FormData();
formData.append('pk_value', pkValue);
fetch(baseUrl + 'table/deleteRow/' + encodeURIComponent(database) + '/' + encodeURIComponent(tableName), {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(result => {
if (result.success) {
row.remove();
// Show empty message if no rows left
const remainingRows = table.querySelectorAll('tbody tr.data-row');
if (remainingRows.length === 0) {
const cols = getColumns();
const emptyRow = document.createElement('tr');
emptyRow.className = 'empty-row';
emptyRow.innerHTML = '<td colspan="' + (cols.length + 1) + '" style="text-align: center; padding: 40px; color: var(--text-muted);">No data in this table. Click "Add Row" to insert data.</td>';
table.querySelector('tbody').appendChild(emptyRow);
}
} else {
alert('Error: ' + result.message);
}
})
.catch(error => {
alert('Error deleting row: ' + error.message);
});
}
function addNewRow() {
// Remove empty message if present
const emptyRow = table.querySelector('tbody tr.empty-row');
if (emptyRow) {
emptyRow.remove();
}
const columns = getColumns();
const tr = document.createElement('tr');
tr.className = 'data-row new-row editing';
tr.dataset.pk = '';
columns.forEach(col => {
const td = document.createElement('td');
td.className = 'editable-cell';
td.dataset.column = col.name;
td.dataset.original = '';
td.innerHTML = `
<span class="cell-display" style="display: none;"><span class="null-value">NULL</span></span>
<input type="text" class="cell-input dbm-form-input" style="display: block; width: 100%; padding: 4px 8px; font-size: 13px;" value="">
`;
tr.appendChild(td);
});
// Actions cell
const actionsTd = document.createElement('td');
actionsTd.className = 'actions-cell';
actionsTd.style.textAlign = 'center';
actionsTd.style.whiteSpace = 'nowrap';
actionsTd.innerHTML = `
<div class="view-actions" style="display: none;">
<button type="button" class="dbm-btn dbm-btn-sm dbm-btn-secondary btn-edit-row" title="Edit">
<i data-lucide="pencil" style="width: 12px; height: 12px;"></i>
</button>
<button type="button" class="dbm-btn dbm-btn-sm dbm-btn-danger btn-delete-row" title="Delete">
<i data-lucide="trash-2" style="width: 12px; height: 12px;"></i>
</button>
</div>
<div class="edit-actions" style="display: inline-flex;">
<button type="button" class="dbm-btn dbm-btn-sm dbm-btn-success btn-save-row" title="Save">
<i data-lucide="check" style="width: 12px; height: 12px;"></i>
</button>
<button type="button" class="dbm-btn dbm-btn-sm dbm-btn-secondary btn-cancel-edit" title="Cancel">
<i data-lucide="x" style="width: 12px; height: 12px;"></i>
</button>
</div>
`;
lucide.createIcons();
tr.appendChild(actionsTd);
// Insert at the top of tbody
const tbody = table.querySelector('tbody');
tbody.insertBefore(tr, tbody.firstChild);
// Focus first input
const firstInput = tr.querySelector('.cell-input');
if (firstInput) {
firstInput.focus();
}
}
});
</script>

View File

@@ -0,0 +1,112 @@
<div class="dbm-content-header">
<div class="dbm-breadcrumb">
<a href="<?php echo Config::get('URL'); ?>database/index">Databases</a>
<span class="separator">/</span>
<a href="<?php echo Config::get('URL'); ?>database/show/<?php echo urlencode($this->database_name); ?>"><?php echo htmlspecialchars($this->database_name); ?></a>
<span class="separator">/</span>
<a href="<?php echo Config::get('URL'); ?>table/show/<?php echo urlencode($this->database_name); ?>/<?php echo urlencode($this->table_name); ?>"><?php echo htmlspecialchars($this->table_name); ?></a>
<span class="separator">/</span>
<span>Structure</span>
</div>
<div class="dbm-title">
<h1>Structure: <?php echo htmlspecialchars($this->table_name); ?></h1>
<span class="badge"><?php echo count($this->columns); ?> columns</span>
</div>
<div class="dbm-actions">
<a href="<?php echo Config::get('URL'); ?>table/show/<?php echo urlencode($this->database_name); ?>/<?php echo urlencode($this->table_name); ?>" class="dbm-btn dbm-btn-primary">
<i data-lucide="list"></i>
Browse Data
</a>
<a href="<?php echo Config::get('URL'); ?>table/addColumn/<?php echo urlencode($this->database_name); ?>/<?php echo urlencode($this->table_name); ?>" class="dbm-btn dbm-btn-success">
<i data-lucide="plus"></i>
Add Column
</a>
</div>
</div>
<div class="dbm-content-body">
<div class="dbm-card" style="margin-bottom: 24px;">
<div class="dbm-card-header">
<h3>Columns</h3>
</div>
<div class="dbm-table-wrapper" style="border: none; border-radius: 0;">
<table class="dbm-table">
<thead>
<tr>
<th>Field</th>
<th>Type</th>
<th>Null</th>
<th>Key</th>
<th>Default</th>
<th>Extra</th>
<th style="width: 80px;">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($this->columns as $col): ?>
<tr>
<td style="font-weight: 500;">
<?php if ($col['Key'] === 'PRI'): ?>
<span class="key-column">
<i data-lucide="key-round" style="width: 12px; height: 12px; vertical-align: middle; margin-right: 4px;"></i>
</span>
<?php endif; ?>
<?php echo htmlspecialchars($col['Field']); ?>
</td>
<td><span class="type-column"><?php echo htmlspecialchars($col['Type']); ?></span></td>
<td><?php echo $col['Null'] === 'YES' ? '<span style="color: var(--accent-orange);">YES</span>' : 'NO'; ?></td>
<td>
<?php if ($col['Key'] === 'PRI'): ?>
<span style="color: var(--accent-green);">PRIMARY</span>
<?php elseif ($col['Key'] === 'UNI'): ?>
<span style="color: var(--accent-blue);">UNIQUE</span>
<?php elseif ($col['Key'] === 'MUL'): ?>
<span style="color: var(--accent-purple);">INDEX</span>
<?php else: ?>
-
<?php endif; ?>
</td>
<td><?php echo $col['Default'] !== null ? htmlspecialchars($col['Default']) : '<span class="null-value">NULL</span>'; ?></td>
<td><span class="type-column"><?php echo htmlspecialchars($col['Extra']); ?></span></td>
<td>
<a href="<?php echo Config::get('URL'); ?>table/dropColumn/<?php echo urlencode($this->database_name); ?>/<?php echo urlencode($this->table_name); ?>/<?php echo urlencode($col['Field']); ?>"
class="dbm-btn dbm-btn-sm dbm-btn-danger"
data-confirm="Drop column '<?php echo htmlspecialchars($col['Field']); ?>'? This cannot be undone!">Drop</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php if (!empty($this->indexes)): ?>
<div class="dbm-card">
<div class="dbm-card-header">
<h3>Indexes</h3>
</div>
<div class="dbm-table-wrapper" style="border: none; border-radius: 0;">
<table class="dbm-table">
<thead>
<tr>
<th>Key Name</th>
<th>Column</th>
<th>Unique</th>
<th>Type</th>
</tr>
</thead>
<tbody>
<?php foreach ($this->indexes as $idx): ?>
<tr>
<td style="font-weight: 500;"><?php echo htmlspecialchars($idx['Key_name']); ?></td>
<td><?php echo htmlspecialchars($idx['Column_name']); ?></td>
<td><?php echo $idx['Non_unique'] ? '<span style="color: var(--text-muted);">No</span>' : '<span style="color: var(--accent-green);">Yes</span>'; ?></td>
<td><span class="type-column"><?php echo htmlspecialchars($idx['Index_type']); ?></span></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
</div>