- Add all view templates (servers, auth, dashboard, admin, layouts, errors, terminal) - Fix SSH double decryption bug in SSHService - Fix router parameter type casting for strict_types - Add missing error views (401, 403, 404) - Add nginx manager with file editing - Add SSL certificates management - Add database manager with phpMyAdmin-like interface - Add sidebar server accordion
414 lines
25 KiB
PHP
414 lines
25 KiB
PHP
<?php
|
|
$server = $server ?? [];
|
|
$db = $db ?? [];
|
|
$installed = $db['installed'] ?? false;
|
|
$dbType = $db['db_type'] ?? '';
|
|
$databases = $db['databases'] ?? [];
|
|
$totalSize = $db['total_size_mb'] ?? 0;
|
|
$status = $db['status'] ?? 'inactive';
|
|
?>
|
|
|
|
<div class="page-header">
|
|
<div class="page-header-left">
|
|
<div class="back-link">
|
|
<a href="/servers/<?= $server['id'] ?>"><i class="fas fa-arrow-left"></i> Back to Server</a>
|
|
</div>
|
|
<h1 class="page-title"><i class="fas fa-database"></i> <?= htmlspecialchars($dbType) ?> Manager</h1>
|
|
<p class="page-subtitle"><?= htmlspecialchars($server['name']) ?> · <?= count($databases) ?> databases · <?= $totalSize ?> MB</p>
|
|
</div>
|
|
<div class="page-header-right">
|
|
<span class="status-badge status-<?= $status === 'active' ? 'online' : 'offline' ?>" style="font-size:0.8rem"><?= ucfirst($status) ?></span>
|
|
<button class="btn btn-secondary" onclick="location.reload()"><i class="fas fa-sync-alt"></i></button>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if (!$installed): ?>
|
|
<div class="card"><div class="card-body"><div class="empty-state">
|
|
<i class="fas fa-database empty-icon" style="font-size:3rem;opacity:0.4"></i>
|
|
<h3>No database server found</h3>
|
|
<p>Neither MySQL nor MariaDB is installed on this server.</p>
|
|
<p class="text-muted">Install with: <code>sudo apt install mysql-server</code> or <code>sudo apt install mariadb-server</code></p>
|
|
</div></div></div>
|
|
<?php else: ?>
|
|
|
|
<div class="pma-layout">
|
|
<aside class="pma-sidebar">
|
|
<div class="pma-sidebar-header" style="justify-content:space-between">
|
|
<span><i class="fas fa-database"></i> Databases</span>
|
|
<span class="badge badge-info"><?= count($databases) ?></span>
|
|
</div>
|
|
<div class="pma-sidebar-filter"><input type="text" class="form-input" placeholder="Filter..." oninput="filterSidebar(this.value)" style="font-size:0.8rem"></div>
|
|
<div class="pma-sidebar-list" id="sidebarList">
|
|
<?php foreach ($databases as $d): ?>
|
|
<div class="pma-accordion-item" data-db="<?= htmlspecialchars($d['name']) ?>">
|
|
<div class="pma-accordion-header" onclick="toggleDb('<?= htmlspecialchars($d['name']) ?>')">
|
|
<i class="fas fa-database"></i>
|
|
<span class="pma-db-name"><?= htmlspecialchars($d['name']) ?></span>
|
|
<span class="pma-db-size"><?= $d['size_mb'] ?> MB</span>
|
|
<i class="fas fa-chevron-right pma-chevron"></i>
|
|
</div>
|
|
<div class="pma-accordion-body" id="tables_<?= htmlspecialchars(md5($d['name'])) ?>">
|
|
<div class="pma-loading-tables" style="display:none"><i class="fas fa-spinner fa-spin"></i> Loading...</div>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<div style="padding:0.5rem;border-top:1px solid var(--border-color);display:flex;gap:0.5rem">
|
|
<button class="btn btn-xs btn-secondary" style="flex:1" onclick="switchTab('sql')"><i class="fas fa-terminal"></i> SQL</button>
|
|
<button class="btn btn-xs btn-secondary" style="flex:1" onclick="switchTab('users');loadUsers()"><i class="fas fa-users"></i> Users</button>
|
|
</div>
|
|
</aside>
|
|
|
|
<main class="pma-content">
|
|
<div class="pma-tabs">
|
|
<button class="pma-tab" id="tabBrowse" onclick="switchTab('browse')" style="display:none"><i class="fas fa-table"></i> Browse</button>
|
|
<button class="pma-tab" id="tabStructure" onclick="switchTab('structure')" style="display:none"><i class="fas fa-list"></i> Structure</button>
|
|
<button class="pma-tab active" id="tabSql" onclick="switchTab('sql')"><i class="fas fa-terminal"></i> SQL</button>
|
|
<button class="pma-tab" id="tabUsers" onclick="switchTab('users')" style="display:none"><i class="fas fa-users"></i> Users</button>
|
|
</div>
|
|
|
|
<div id="pmaWelcome" class="pma-panel active">
|
|
<div class="empty-state"><i class="fas fa-database empty-icon" style="opacity:0.2;font-size:5rem"></i><h3>Select a database</h3><p>Click a database in the left panel.</p></div>
|
|
</div>
|
|
|
|
<div id="pmaTables" class="pma-panel"></div>
|
|
<div id="pmaStructure" class="pma-panel"></div>
|
|
<div id="pmaBrowse" class="pma-panel"></div>
|
|
<div id="pmaUsers" class="pma-panel"></div>
|
|
|
|
<div id="pmaSql" class="pma-panel active">
|
|
<div class="pma-sql-editor">
|
|
<textarea id="sqlInput" class="form-input" rows="6" style="font-family:var(--font-mono);font-size:0.85rem;border-radius:var(--radius) var(--radius) 0 0" placeholder="SELECT * FROM ...">SHOW DATABASES</textarea>
|
|
<div class="pma-sql-toolbar">
|
|
<button class="btn btn-primary btn-sm" onclick="runSql()"><i class="fas fa-play"></i> Go</button>
|
|
<button class="btn btn-secondary btn-sm" onclick="document.getElementById('sqlInput').value=''"><i class="fas fa-eraser"></i></button>
|
|
<span style="flex:1"></span>
|
|
<span class="text-muted" style="font-size:0.78rem">Ctrl+Enter</span>
|
|
</div>
|
|
<div id="sqlResult" class="nginx-config-viewer" style="display:none;border-radius:0 0 var(--radius) var(--radius);border-top:0"><pre><code id="sqlResultText"></code></pre></div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<script>
|
|
let currentDb = '';
|
|
let currentTable = '';
|
|
let openedDb = '';
|
|
|
|
function getCsrfToken() { return document.querySelector('meta[name="csrf-token"]')?.content || ''; }
|
|
|
|
function filterTables(inp) {
|
|
const q = inp.value.toLowerCase();
|
|
const body = inp.closest('.pma-accordion-body');
|
|
if (body) {
|
|
body.querySelectorAll('.pma-table-item').forEach(el => {
|
|
el.style.display = !q || (el.dataset.tbl || '').includes(q) ? '' : 'none';
|
|
});
|
|
}
|
|
}
|
|
|
|
function filterSidebar(q) {
|
|
document.querySelectorAll('.pma-accordion-item').forEach(el => {
|
|
el.style.display = el.textContent.toLowerCase().includes(q.toLowerCase()) ? '' : 'none';
|
|
});
|
|
}
|
|
|
|
function toggleDb(db) {
|
|
const item = document.querySelector(`.pma-accordion-item[data-db="${db.replace(/"/g,'\\"')}"]`);
|
|
if (!item) return;
|
|
const body = item.querySelector('.pma-accordion-body');
|
|
const header = item.querySelector('.pma-accordion-header');
|
|
const chevron = item.querySelector('.pma-chevron');
|
|
|
|
if (item.classList.contains('expanded')) {
|
|
item.classList.remove('expanded');
|
|
body.style.maxHeight = '0';
|
|
return;
|
|
}
|
|
|
|
document.querySelectorAll('.pma-accordion-item.expanded').forEach(el => {
|
|
el.classList.remove('expanded');
|
|
el.querySelector('.pma-accordion-body').style.maxHeight = '0';
|
|
});
|
|
|
|
item.classList.add('expanded');
|
|
currentDb = db;
|
|
|
|
if (body.querySelector('.pma-table-item')) {
|
|
body.style.maxHeight = body.scrollHeight + 'px';
|
|
return;
|
|
}
|
|
|
|
body.querySelector('.pma-loading-tables').style.display = '';
|
|
body.style.maxHeight = '60px';
|
|
|
|
fetch('/servers/<?= $server['id'] ?>/database', {
|
|
method: 'POST',
|
|
headers: {'Content-Type':'application/x-www-form-urlencoded','X-CSRF-Token':getCsrfToken()},
|
|
body: 'action=tables&db=' + encodeURIComponent(db) + '&_csrf_token=' + encodeURIComponent(getCsrfToken()),
|
|
})
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
body.querySelector('.pma-loading-tables').style.display = 'none';
|
|
let html = '<div style="padding:0.3rem 0.5rem"><input type="text" class="form-input tbl-filter" placeholder="Filter tables..." style="font-size:0.75rem;padding:0.25rem 0.4rem;width:100%;box-sizing:border-box" oninput="filterTables(this)"></div>';
|
|
(data.tables || []).forEach(t => {
|
|
html += '<div class="pma-table-item" data-tbl="' + escHtml(t.name).toLowerCase() + '" onclick="selectTable(\'' + db.replace(/'/g,"\\'") + "','" + t.name.replace(/'/g,"\\'") + '\')">'
|
|
+ '<i class="fas fa-table"></i> ' + escHtml(t.name)
|
|
+ ' <span class="pma-db-size">' + t.rows + ' rows</span></div>';
|
|
});
|
|
if (!html) html = '<div class="pma-table-item" style="color:var(--text-muted);cursor:default">No tables</div>';
|
|
body.innerHTML = html;
|
|
body.style.maxHeight = body.scrollHeight + 'px';
|
|
});
|
|
}
|
|
|
|
function selectTable(db, table) {
|
|
currentDb = db; currentTable = table;
|
|
document.getElementById('tabBrowse').style.display = '';
|
|
document.getElementById('tabStructure').style.display = '';
|
|
document.querySelectorAll('.pma-tab').forEach(t=>t.classList.remove('active'));
|
|
document.getElementById('tabStructure').classList.add('active');
|
|
showPanel('structure');
|
|
loadStructure(db, table);
|
|
}
|
|
|
|
function loadStructure(db, table) {
|
|
const panel = document.getElementById('pmaStructure');
|
|
panel.innerHTML = '<div style="text-align:center;padding:2rem;color:var(--text-muted)"><i class="fas fa-spinner fa-spin"></i> Loading structure...</div>';
|
|
fetch('/servers/<?= $server['id'] ?>/database', {
|
|
method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded','X-CSRF-Token':getCsrfToken()},
|
|
body:'action=structure&db='+encodeURIComponent(db)+'&table='+encodeURIComponent(table)+'&_csrf_token='+encodeURIComponent(getCsrfToken()),
|
|
}).then(r=>r.json()).then(d=>{
|
|
if (!d.success||!d.columns) return;
|
|
let html='<div style="display:flex;gap:0.75rem;align-items:center;margin-bottom:1rem">'
|
|
+'<h2 style="margin:0;font-size:1rem"><i class="fas fa-list"></i> <code>'+escHtml(table)+'</code></h2>'
|
|
+'<span class="badge badge-info">'+d.columns.length+' columns</span>'
|
|
+'<span style="flex:1"></span>'
|
|
+'<button class="btn btn-xs btn-info" onclick="browseTable(\''+db.replace(/'/g,"\\'")+"','"+table.replace(/'/g,"\\'")+'\')"><i class="fas fa-search"></i> Browse</button>'
|
|
+'</div>';
|
|
html+='<div class="table-responsive"><table class="table table-sm"><thead><tr><th>#</th><th>Field</th><th>Type</th><th>Collation</th><th>Null</th><th>Key</th><th>Default</th><th>Extra</th></tr></thead><tbody>';
|
|
d.columns.forEach((c,i)=>{
|
|
html+='<tr><td>'+(i+1)+'</td><td><strong>'+escHtml(c.field)+'</strong></td><td><code>'+escHtml(c.type)+'</code></td>'
|
|
+'<td>'+escHtml(c.collation)+'</td><td>'+escHtml(c.null)+'</td><td><strong>'+escHtml(c.key)+'</strong></td>'
|
|
+'<td>'+(c.default??'NULL')+'</td><td>'+escHtml(c.extra)+'</td></tr>';
|
|
});
|
|
html+='</tbody></table></div>';
|
|
panel.innerHTML=html;
|
|
});
|
|
}
|
|
|
|
function browseTable(db, table) {
|
|
currentDb=db; currentTable=table;
|
|
document.getElementById('tabBrowse').style.display='';
|
|
document.getElementById('tabStructure').style.display='';
|
|
document.querySelectorAll('.pma-tab').forEach(t=>t.classList.remove('active'));
|
|
document.getElementById('tabBrowse').classList.add('active');
|
|
showPanel('browse');
|
|
loadBrowse(db,table,1);
|
|
}
|
|
|
|
function loadBrowse(db,table,page) {
|
|
const panel=document.getElementById('pmaBrowse');
|
|
|
|
const inputs=document.querySelectorAll('.pma-fi');
|
|
let filters='';
|
|
if(inputs.length){
|
|
const p={};
|
|
inputs.forEach(inp=>{if(inp.value.trim())p[inp.dataset.col]=inp.value.trim()});
|
|
const s=new URLSearchParams(p).toString();
|
|
if(s)filters='&'+s;
|
|
}
|
|
|
|
panel.innerHTML='<div style="text-align:center;padding:2rem;color:var(--text-muted)"><i class="fas fa-spinner fa-spin"></i> Loading...</div>';
|
|
|
|
let body='action=browse&db='+encodeURIComponent(db)+'&table='+encodeURIComponent(table)+'&page='+page+filters+'&_csrf_token='+encodeURIComponent(getCsrfToken());
|
|
|
|
fetch('/servers/<?= $server['id'] ?>/database',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded','X-CSRF-Token':getCsrfToken()},body})
|
|
.then(r=>r.json()).then(d=>{
|
|
if(!d.success)return;
|
|
const tp=Math.ceil(d.total/d.per_page);
|
|
let html='<div style="display:flex;gap:0.75rem;align-items:center;margin-bottom:0.5rem">'
|
|
+'<h2 style="margin:0;font-size:1rem"><i class="fas fa-search"></i> <code>'+escHtml(table)+'</code></h2>'
|
|
+'<span class="badge badge-info">'+d.total+' rows</span></div>';
|
|
html+='<form onsubmit="event.preventDefault();loadBrowse(\''+db.replace(/'/g,"\\'")+"','"+table.replace(/'/g,"\\'")+'\',1)">';
|
|
html+='<div class="table-responsive" style="max-height:450px;overflow-y:auto"><table class="table table-sm"><thead><tr>';
|
|
d.columns.forEach(c=>{html+='<th>'+escHtml(c)+'</th>'});
|
|
html+='</tr><tr class="pma-filter-row">';
|
|
d.columns.forEach(c=>{
|
|
const cv=new URLSearchParams(filters.replace(/^&/,'')).get(c)||'';
|
|
html+='<td><input type="text" class="form-input pma-fi" data-col="'+escHtml(c)+'" value="'+escHtml(cv)+'" placeholder="'+escHtml(c)+'" style="font-size:0.75rem;padding:0.2rem 0.35rem;width:100%;min-width:50px"></td>';
|
|
});
|
|
html+='</tr></thead><tbody>';
|
|
d.rows.forEach(row=>{
|
|
html+='<tr>';
|
|
row.forEach(val=>{html+='<td style="max-width:180px;overflow:hidden;text-overflow:ellipsis"><code>'+escHtml(String(val).substring(0,80))+'</code></td>'});
|
|
html+='</tr>';
|
|
});
|
|
html+='</tbody></table></div>';
|
|
html+='<div style="display:flex;gap:0.5rem;margin-top:0.5rem;align-items:center">';
|
|
html+='<button type="submit" class="btn btn-primary btn-sm"><i class="fas fa-search"></i> Filter</button>';
|
|
html+='<button type="button" class="btn btn-secondary btn-sm" onclick="clearBrowseFilters(\''+db.replace(/'/g,"\\'")+"','"+table.replace(/'/g,"\\'")+'\')"><i class="fas fa-undo"></i> Clear</button>';
|
|
if(tp>1){
|
|
html+='<span style="flex:1"></span>';
|
|
for(let p=1;p<=tp&&p<=10;p++)html+='<button type="button" class="btn btn-xs '+(p===page?'btn-primary':'btn-secondary')+'" onclick="loadBrowse(\''+db.replace(/'/g,"\\'")+"','"+table.replace(/'/g,"\\'")+'\','+p+')">'+p+'</button>';
|
|
if(tp>10)html+='<span class="text-muted">...</span>';
|
|
}
|
|
html+='</div></form>';
|
|
panel.innerHTML=html;
|
|
});
|
|
}
|
|
|
|
function clearBrowseFilters(db,table){
|
|
document.querySelectorAll('.pma-fi').forEach(inp=>inp.value='');
|
|
loadBrowse(db,table,1);
|
|
}
|
|
|
|
function loadUsers() {
|
|
showPanel('users');
|
|
document.getElementById('tabUsers').style.display='';
|
|
const panel=document.getElementById('pmaUsers');
|
|
panel.innerHTML='<div style="text-align:center;padding:2rem;color:var(--text-muted)"><i class="fas fa-spinner fa-spin"></i> Loading users...</div>';
|
|
|
|
fetch('/servers/<?= $server['id'] ?>/database',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded','X-CSRF-Token':getCsrfToken()},body:'action=users&_csrf_token='+encodeURIComponent(getCsrfToken())})
|
|
.then(r=>r.json()).then(d=>{
|
|
let html='<div style="display:flex;gap:0.75rem;align-items:center;margin-bottom:1rem">'
|
|
+'<h2 style="margin:0;font-size:1rem"><i class="fas fa-users"></i> Database Users</h2>'
|
|
+'<span class="badge badge-info">'+(d.users?.length||0)+' users</span>'
|
|
+'<span style="flex:1"></span>'
|
|
+'<button class="btn btn-sm btn-success" onclick="showCreateUser()"><i class="fas fa-plus"></i> New User</button></div>';
|
|
html+='<div class="table-responsive"><table class="table table-sm"><thead><tr><th>User</th><th>Host</th><th>Databases</th><th>Expired</th><th>Actions</th></tr></thead><tbody>';
|
|
(d.users||[]).forEach(u=>{
|
|
const dbList=(d.grants||[]).filter(g=>g.user===u.user&&g.host===u.host).map(g=>g.db).join(', ');
|
|
html+='<tr><td><strong>'+escHtml(u.user)+'</strong></td><td><code>'+escHtml(u.host)+'</code></td><td>'+(dbList||'<span class="text-muted">-</span>')+'</td><td>'+escHtml(u.expired)+'</td>'
|
|
+'<td class="actions-cell">'
|
|
+'<button class="btn btn-xs btn-secondary" onclick="showEditUser(\''+escHtml(u.user)+"','"+escHtml(u.host)+'\')" title="Change password"><i class="fas fa-key"></i></button> '
|
|
+'<button class="btn btn-xs btn-danger" onclick="deleteUser(\''+escHtml(u.user)+"','"+escHtml(u.host)+'\')" title="Delete"><i class="fas fa-trash"></i></button>'
|
|
+'</td></tr>';
|
|
});
|
|
html+='</tbody></table></div>';
|
|
panel.innerHTML=html;
|
|
});
|
|
}
|
|
|
|
function showCreateUser() {
|
|
const panel=document.getElementById('pmaUsers');
|
|
let html='<div style="display:flex;gap:0.75rem;align-items:center;margin-bottom:1rem">'
|
|
+'<h2 style="margin:0;font-size:1rem"><i class="fas fa-user-plus"></i> Create User</h2>'
|
|
+'<span style="flex:1"></span>'
|
|
+'<button class="btn btn-sm btn-secondary" onclick="loadUsers()"><i class="fas fa-arrow-left"></i> Back</button></div>'
|
|
+'<div class="dashboard-grid"><div class="dashboard-card"><div class="card-header"><h2><i class="fas fa-user"></i> Account</h2></div><div class="card-body">'
|
|
+'<div class="form-row"><div class="form-group"><label>Username</label><input type="text" id="nuUser" class="form-input" placeholder="username"></div>'
|
|
+'<div class="form-group"><label>Password</label><input type="password" id="nuPass" class="form-input" placeholder="password"></div>'
|
|
+'<div class="form-group"><label>Host</label><input type="text" id="nuHost" class="form-input" value="localhost" placeholder="localhost"></div></div></div></div>'
|
|
+'<div class="dashboard-card"><div class="card-header"><h2><i class="fas fa-database"></i> Database Access</h2></div><div class="card-body">'
|
|
+'<div style="margin-bottom:0.75rem"><label class="checkbox-label"><input type="checkbox" id="nuAllDb" onchange="document.querySelectorAll(\'.nuDbCheck\').forEach(c=>c.checked=this.checked)"> <strong>Select all</strong></label></div>';
|
|
|
|
<?php foreach ($databases as $d): ?>
|
|
var dbname = '<?= htmlspecialchars($d['name']) ?>';
|
|
if (dbname !== 'information_schema' && dbname !== 'performance_schema' && dbname !== 'mysql' && dbname !== 'sys') {
|
|
html+='<label class="checkbox-label" style="margin-top:0.25rem"><input type="checkbox" class="nuDbCheck" value="'+dbname+'"> <span>'+dbname+'</span></label>';
|
|
}
|
|
<?php endforeach; ?>
|
|
|
|
html+='</div></div></div>'
|
|
+'<div class="dashboard-card"><div class="card-header"><h2><i class="fas fa-lock"></i> Privileges</h2></div><div class="card-body">'
|
|
+'<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:0.25rem">';
|
|
['ALL PRIVILEGES','SELECT','INSERT','UPDATE','DELETE','CREATE','DROP','ALTER','INDEX','CREATE VIEW','SHOW VIEW','CREATE ROUTINE','ALTER ROUTINE','EXECUTE','LOCK TABLES','REFERENCES','EVENT','TRIGGER'].forEach(function(p){
|
|
var checked = p === 'ALL PRIVILEGES' ? ' checked' : '';
|
|
html+='<label class="checkbox-label" style="font-size:0.82rem"><input type="checkbox" class="nuPriv" value="'+p+'"'+checked+'> <span>'+p+'</span></label>';
|
|
});
|
|
html+='</div></div></div>'
|
|
+'<div style="margin-top:1rem;display:flex;gap:0.5rem">'
|
|
+'<button class="btn btn-primary" onclick="doCreateUser()"><i class="fas fa-save"></i> Create User</button>'
|
|
+'<button class="btn btn-secondary" onclick="loadUsers()">Cancel</button></div>';
|
|
panel.innerHTML=html;
|
|
}
|
|
|
|
function doCreateUser() {
|
|
const user=document.getElementById('nuUser').value;
|
|
const pass=document.getElementById('nuPass').value;
|
|
const host=document.getElementById('nuHost').value||'localhost';
|
|
if(!user||!pass){showToast('error','Username and password required');return;}
|
|
|
|
const privs=[];
|
|
document.querySelectorAll('.nuPriv:checked').forEach(cb=>privs.push(cb.value));
|
|
const grantPrivs = privs.includes('ALL PRIVILEGES') ? 'ALL PRIVILEGES' : privs.join(',');
|
|
|
|
fetch('/servers/<?= $server['id'] ?>/database',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded','X-CSRF-Token':getCsrfToken()},body: 'action=create_user&new_user='+encodeURIComponent(user)+'&new_pass='+encodeURIComponent(pass)+'&new_host='+encodeURIComponent(host)+'&_csrf_token='+encodeURIComponent(getCsrfToken())})
|
|
.then(r=>r.json()).then(d=>{
|
|
showToast(d.success?'success':'error',d.message);
|
|
if(d.success){
|
|
const dbs=[];
|
|
document.querySelectorAll('.nuDbCheck:checked').forEach(cb=>dbs.push(cb.value));
|
|
if(dbs.length>0){
|
|
let cnt=0;
|
|
dbs.forEach(db=>{
|
|
fetch('/servers/<?= $server['id'] ?>/database',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded','X-CSRF-Token':getCsrfToken()},body:'action=grant&grant_user='+encodeURIComponent(user)+'&grant_db='+encodeURIComponent(db)+'&grant_host='+encodeURIComponent(host)+'&grant_privs='+encodeURIComponent(grantPrivs)+'&_csrf_token='+encodeURIComponent(getCsrfToken())})
|
|
.then(r2=>r2.json()).then(g=>{
|
|
cnt++;
|
|
if(cnt===dbs.length)loadUsers();
|
|
});
|
|
});
|
|
}else{loadUsers();}
|
|
}
|
|
});
|
|
}
|
|
|
|
function showEditUser(user, host) {
|
|
const panel = document.getElementById('pmaUsers');
|
|
panel.innerHTML='<div style="display:flex;gap:0.75rem;align-items:center;margin-bottom:1rem">'
|
|
+'<h2 style="margin:0;font-size:1rem"><i class="fas fa-key"></i> Change Password</h2>'
|
|
+'<code>'+escHtml(user)+'</code> @ <code>'+escHtml(host)+'</code>'
|
|
+'<span style="flex:1"></span>'
|
|
+'<button class="btn btn-sm btn-secondary" onclick="loadUsers()"><i class="fas fa-arrow-left"></i> Back</button></div>'
|
|
+'<div class="card"><div class="card-body">'
|
|
+'<div class="form-group"><label>New Password</label><input type="password" id="epPass" class="form-input" placeholder="new password"></div>'
|
|
+'<div style="margin-top:1rem;display:flex;gap:0.5rem">'
|
|
+'<button class="btn btn-primary" onclick="doChangePass(\''+escHtml(user)+"','"+escHtml(host)+'\')"><i class="fas fa-save"></i> Save</button>'
|
|
+'<button class="btn btn-secondary" onclick="loadUsers()">Cancel</button></div></div></div>';
|
|
}
|
|
|
|
function doChangePass(user, host) {
|
|
const pass = document.getElementById('epPass').value;
|
|
if (!pass) { showToast('error','Password required'); return; }
|
|
fetch('/servers/<?= $server['id'] ?>/database',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded','X-CSRF-Token':getCsrfToken()},
|
|
body:'action=change_pass&target_user='+encodeURIComponent(user)+'&target_host='+encodeURIComponent(host)+'&new_pass='+encodeURIComponent(pass)+'&_csrf_token='+encodeURIComponent(getCsrfToken())})
|
|
.then(r=>r.json()).then(d=>{showToast(d.success?'success':'error',d.message);if(d.success)loadUsers()});
|
|
}
|
|
|
|
function deleteUser(user, host) {
|
|
if (!confirm('Delete user \''+user+'\'@\''+host+'\'?\nThis cannot be undone.')) return;
|
|
fetch('/servers/<?= $server['id'] ?>/database',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded','X-CSRF-Token':getCsrfToken()},
|
|
body:'action=drop_user&target_user='+encodeURIComponent(user)+'&target_host='+encodeURIComponent(host)+'&_csrf_token='+encodeURIComponent(getCsrfToken())})
|
|
.then(r=>r.json()).then(d=>{showToast(d.success?'success':'error',d.message);if(d.success)loadUsers()});
|
|
}
|
|
|
|
function runSql() {
|
|
const sql=document.getElementById('sqlInput').value; if(!sql)return;
|
|
const rd=document.getElementById('sqlResult'); const rt=document.getElementById('sqlResultText');
|
|
rd.style.display='none';
|
|
fetch('/servers/<?= $server['id'] ?>/database',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded','X-CSRF-Token':getCsrfToken()},body:'action=query&query='+encodeURIComponent(sql)+'&_csrf_token='+encodeURIComponent(getCsrfToken())})
|
|
.then(r=>r.json()).then(d=>{if(d.output){rt.textContent=d.output;rd.style.display='block'}else showToast('error',d.message||'No results')});
|
|
}
|
|
|
|
function showPanel(id) {
|
|
document.querySelectorAll('.pma-panel').forEach(p=>p.classList.remove('active'));
|
|
document.getElementById('pma'+id.charAt(0).toUpperCase()+id.slice(1)).classList.add('active');
|
|
}
|
|
|
|
function switchTab(tab) {
|
|
document.querySelectorAll('.pma-tab').forEach(t=>t.classList.remove('active'));
|
|
const map={browse:'pmaBrowse',structure:'pmaStructure',sql:'pmaSql',users:'pmaUsers'};
|
|
const el=document.getElementById('tab'+tab.charAt(0).toUpperCase()+tab.slice(1));
|
|
if(el)el.classList.add('active');
|
|
showPanel(tab);
|
|
if(tab==='sql'){document.getElementById('pmaWelcome').classList.remove('active');document.getElementById('pmaSql').classList.add('active');}
|
|
}
|
|
|
|
document.getElementById('sqlInput').addEventListener('keydown',function(e){if((e.ctrlKey||e.metaKey)&&e.key==='Enter')runSql()});
|
|
|
|
function escHtml(s){const d=document.createElement('div');d.textContent=s;return d.innerHTML;}
|
|
</script>
|
|
<?php endif; ?>
|