Add root terminal
This commit is contained in:
22
app.py
22
app.py
@@ -526,3 +526,25 @@ def api_sql_query():
|
||||
|
||||
result = run_sql_query(container, query)
|
||||
return jsonify(result)
|
||||
|
||||
# API endpoint for shell commands
|
||||
@app.post("/api/terminal/exec")
|
||||
@login_required
|
||||
def api_terminal_exec():
|
||||
data = request.json
|
||||
command = data.get("command")
|
||||
|
||||
if not command:
|
||||
return jsonify({"error": "No command provided"}), 400
|
||||
|
||||
try:
|
||||
# Use shell execution via sh()
|
||||
# sh() uses subprocess.check_output with shell=False by default (list of strings)
|
||||
# However, to support pipe/redirection for the user, we should allow shell=True-like behavior
|
||||
# Let's wrap it in ['sh', '-c', command]
|
||||
output = sh(["sh", "-c", command])
|
||||
return jsonify({"output": output, "status": "success"})
|
||||
except subprocess.CalledProcessError as e:
|
||||
return jsonify({"output": e.output or str(e), "error": True, "status": "error"})
|
||||
except Exception as e:
|
||||
return jsonify({"output": str(e), "error": True, "status": "error"})
|
||||
|
||||
@@ -591,6 +591,33 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Root Terminal -->
|
||||
<section id="root-terminal" class="station" data-label="[ STATION: ROOT_TERMINAL ]">
|
||||
<div class="station-header">
|
||||
<h2>Server Shell Access</h2>
|
||||
<a href="#top" class="back-to-top">[^ BACK_TO_TOP]</a>
|
||||
</div>
|
||||
|
||||
<div class="panel terminal-box" style="margin-top: 0; border-color: #ff555566;">
|
||||
<div class="terminal-header" style="border-bottom-color: #ff555566;">
|
||||
<div class="terminal-label" style="color: #ff5555;">ROOT@DOKKU:~$</div>
|
||||
<button class="copy-btn" onclick="copyLogs('terminal-output')"
|
||||
style="border-color: #ff555566; color: #ff5555;">COPY BUFFER</button>
|
||||
</div>
|
||||
<div id="terminal-output" class="terminal-body"
|
||||
style="height: 400px; font-size: 13px; background: #000;">
|
||||
<div class="log-line log-info">Dokku Terminal initialized. Ready for commands.</div>
|
||||
</div>
|
||||
<div style="display: flex; background: #000; padding: 10px; border-top: 1px solid #ff555566;">
|
||||
<span style="color: #00ff88; font-weight: 700; margin-right: 10px;">$</span>
|
||||
<input type="text" id="terminal-input" placeholder="Enter command..." spellcheck="false"
|
||||
autocomplete="off"
|
||||
style="background: transparent; color: #c9d1d9; border: none; outline: none; flex: 1; font-family: inherit; font-size: 13px;"
|
||||
onkeydown="if(event.key === 'Enter') runCommand()">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
@@ -598,7 +625,7 @@
|
||||
const el = document.getElementById(id);
|
||||
const text = el.innerText;
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
const btn = event.target;
|
||||
const btn = event.currentTarget || event.target;
|
||||
const orig = btn.innerText;
|
||||
btn.innerText = 'COPIED';
|
||||
setTimeout(() => { btn.innerText = orig; }, 2000);
|
||||
@@ -675,6 +702,47 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function runCommand() {
|
||||
const input = document.getElementById('terminal-input');
|
||||
const output = document.getElementById('terminal-output');
|
||||
const command = input.value.trim();
|
||||
|
||||
if (!command) return;
|
||||
|
||||
// Add command to terminal
|
||||
const cmdLine = document.createElement('div');
|
||||
cmdLine.className = 'log-line';
|
||||
cmdLine.innerHTML = `<span style="color: #00ff88;">$ ${command}</span>`;
|
||||
output.appendChild(cmdLine);
|
||||
|
||||
input.value = '';
|
||||
output.scrollTop = output.scrollHeight;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/terminal/exec', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ command })
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
const resultLine = document.createElement('div');
|
||||
resultLine.className = result.error ? 'log-line log-error' : 'log-line';
|
||||
resultLine.style.whiteSpace = 'pre-wrap';
|
||||
resultLine.innerText = result.output;
|
||||
output.appendChild(resultLine);
|
||||
|
||||
output.scrollTop = output.scrollHeight;
|
||||
} catch (err) {
|
||||
const errLine = document.createElement('div');
|
||||
errLine.className = 'log-line log-error';
|
||||
errLine.innerText = `FATAL ERROR: ${err.message}`;
|
||||
output.appendChild(errLine);
|
||||
output.scrollTop = output.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-scroll logic
|
||||
document.querySelectorAll('.terminal-body').forEach(el => {
|
||||
el.scrollTop = el.scrollHeight;
|
||||
|
||||
Reference in New Issue
Block a user