94 lines
3.1 KiB
HTML
94 lines
3.1 KiB
HTML
|
|
{% extends "base.html" %}
|
||
|
|
{% block title %}Files{% endblock %}
|
||
|
|
{% block content %}
|
||
|
|
<h1>[/] File Manager</h1>
|
||
|
|
|
||
|
|
<div class="toolbar">
|
||
|
|
<input type="text" id="file-path" value="/var/www" style="width:400px">
|
||
|
|
<button class="btn" onclick="listFiles()">List</button>
|
||
|
|
<button class="btn" onclick="readFile()">Read File</button>
|
||
|
|
<button class="btn" onclick="mkdirRemote()">mkdir</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="grid grid-2">
|
||
|
|
<div class="card">
|
||
|
|
<div class="card-title">Directory Listing</div>
|
||
|
|
<div class="output" id="dir-output" style="max-height:600px;cursor:pointer" onclick="handleDirClick(event)">
|
||
|
|
<span class="info">Enter a path and click List</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<div class="card">
|
||
|
|
<div class="card-title">File Editor</div>
|
||
|
|
<label>Path</label>
|
||
|
|
<input type="text" id="edit-path" style="width:100%">
|
||
|
|
<textarea id="edit-content" rows="20" style="width:100%;resize:vertical"></textarea>
|
||
|
|
<br>
|
||
|
|
<button class="btn" onclick="saveFile()">Save File</button>
|
||
|
|
<button class="btn btn-danger" onclick="deleteFile()">Delete</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="card">
|
||
|
|
<div class="card-title">Output</div>
|
||
|
|
<div class="output" id="output"><span class="info">Ready.</span></div>
|
||
|
|
</div>
|
||
|
|
{% endblock %}
|
||
|
|
|
||
|
|
{% block scripts %}
|
||
|
|
<script>
|
||
|
|
async function listFiles() {
|
||
|
|
const path = document.getElementById('file-path').value;
|
||
|
|
const res = await apiGet('/api/files/list?path='+encodeURIComponent(path));
|
||
|
|
showResult(res, 'dir-output');
|
||
|
|
}
|
||
|
|
|
||
|
|
async function readFile() {
|
||
|
|
const path = document.getElementById('file-path').value;
|
||
|
|
const res = await apiGet('/api/files/read?path='+encodeURIComponent(path));
|
||
|
|
if (res.ok) {
|
||
|
|
document.getElementById('edit-path').value = path;
|
||
|
|
document.getElementById('edit-content').value = res.data.stdout || '';
|
||
|
|
}
|
||
|
|
showResult(res);
|
||
|
|
}
|
||
|
|
|
||
|
|
async function saveFile() {
|
||
|
|
const path = document.getElementById('edit-path').value;
|
||
|
|
const content = document.getElementById('edit-content').value;
|
||
|
|
if (!path) { alert('Enter file path'); return; }
|
||
|
|
if (!confirm('Save to '+path+'?')) return;
|
||
|
|
const res = await apiPost('/api/files/write', {path, content});
|
||
|
|
showResult(res);
|
||
|
|
}
|
||
|
|
|
||
|
|
async function deleteFile() {
|
||
|
|
const path = document.getElementById('edit-path').value || document.getElementById('file-path').value;
|
||
|
|
if (!path) return;
|
||
|
|
if (!confirm('DELETE '+path+'? This cannot be undone!')) return;
|
||
|
|
const res = await apiDelete('/api/files/delete', {path});
|
||
|
|
showResult(res);
|
||
|
|
listFiles();
|
||
|
|
}
|
||
|
|
|
||
|
|
async function mkdirRemote() {
|
||
|
|
const path = document.getElementById('file-path').value;
|
||
|
|
if (!path) return;
|
||
|
|
const res = await apiPost('/api/files/mkdir', {path});
|
||
|
|
showResult(res);
|
||
|
|
}
|
||
|
|
|
||
|
|
function handleDirClick(event) {
|
||
|
|
// Allow clicking on filenames in the listing
|
||
|
|
const text = window.getSelection().toString().trim();
|
||
|
|
if (text && !text.includes('\n')) {
|
||
|
|
const base = document.getElementById('file-path').value.replace(/\/$/,'');
|
||
|
|
document.getElementById('file-path').value = base + '/' + text;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
listFiles();
|
||
|
|
</script>
|
||
|
|
{% endblock %}
|