Compare commits

..

No commits in common. "b01a6b50348991b0f5edfb302d7754f01f73c543" and "07c95224444555cbb5000099ef504cfb13b46853" have entirely different histories.

6 changed files with 28 additions and 290 deletions

View file

@ -204,51 +204,6 @@ def delete_file_or_folder():
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
@api.route('/files/content', methods=['GET'])
@oidc.require_login
def get_file_content():
username = oidc.user_getfield('preferred_username')
path = request.args.get('path')
base_path = os.path.abspath(f'./servers/mc-{username}')
file_path = os.path.abspath(os.path.join(base_path, path))
if not file_path.startswith(base_path) or not os.path.isfile(file_path):
return abort(403)
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
return jsonify({"success": True, "content": content})
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 500
@api.route('/files/content', methods=['POST'])
@oidc.require_login
def save_file_content():
data = request.get_json()
username = data.get('username')
if username != oidc.user_getfield('preferred_username'):
return jsonify({"error": "Unauthorized request."}), 403
path = data.get('path')
content = data.get('content')
base_path = os.path.abspath(f'./servers/mc-{username}')
file_path = os.path.abspath(os.path.join(base_path, path))
if not file_path.startswith(base_path) or not os.path.isfile(file_path):
return abort(403)
try:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
return jsonify({"success": True})
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 500
# Server config # Server config
@api.route('/config') @api.route('/config')
@oidc.require_login @oidc.require_login

View file

@ -115,9 +115,9 @@ def start_server(username):
f"{ports[1]}/tcp": ports[1], f"{ports[1]}/tcp": ports[1],
f"{ports[2]}/tcp": ports[2], f"{ports[2]}/tcp": ports[2],
}, },
volumes=[ volumes={
f"{os.path.abspath(path)}:/data" os.path.abspath(path): {'bind': '/data', 'mode': 'rw'}
], },
environment=environment, environment=environment,
restart_policy={"Name": "unless-stopped"} restart_policy={"Name": "unless-stopped"}
) )

View file

@ -1,134 +0,0 @@
let codeMirrorEditor = null;
let currentEditingFilePath = null;
function getFileExtension(filename) {
const lastDot = filename.lastIndexOf('.');
if (lastDot === -1) return '';
return filename.slice(lastDot + 1);
}
function getCodeMirrorMode(filename) {
const ext = getFileExtension(filename).toLowerCase();
switch (ext) {
case 'js':
return 'javascript';
case 'json':
return {
name: "javascript",
json: true
};
case 'html':
case 'htm':
return 'htmlmixed';
case 'css':
return 'css';
case 'xml':
return 'xml';
case 'yml':
case 'yaml':
return 'yaml';
case 'properties':
return 'properties';
case 'log':
case 'txt':
return 'text/plain';
default:
return null;
}
}
async function openFileEditor(filePath) {
const fileExtension = getFileExtension(filePath);
const textFileExtensions = ['txt', 'log', 'json', 'yml', 'yaml', 'properties', 'html', 'css', 'js', 'xml', 'py'];
if (!textFileExtensions.includes(fileExtension.toLowerCase())) {
alert('Ten typ pliku nie może być edytowany bezpośrednio. Możesz go pobrać.');
return;
}
try {
const response = await fetch(`/api/files/content?username=${username}&path=${encodeURIComponent(filePath)}`);
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Server responded with status ${response.status}: ${errorText}`);
}
const data = await response.json();
if (data.success) {
document.getElementById('file-list').style.display = 'none';
document.getElementById('drop-zone').style.display = 'none';
document.getElementById('file-editor-container').style.display = 'block';
document.getElementById('editing-filename').textContent = filePath.split('/').pop();
currentEditingFilePath = filePath;
if (!codeMirrorEditor) {
codeMirrorEditor = CodeMirror.fromTextArea(document.getElementById('file-editor'), {
lineNumbers: true,
theme: 'material-darker',
mode: getCodeMirrorMode(filePath),
indentUnit: 4,
tabSize: 4,
indentWithTabs: false,
lineWrapping: true
});
} else {
codeMirrorEditor.setValue(data.content);
codeMirrorEditor.setOption('mode', getCodeMirrorMode(filePath));
}
codeMirrorEditor.refresh();
} else {
alert('Błąd podczas ładowania pliku: ' + (data.error || 'Nieznany błąd.'));
}
} catch (error) {
console.error('Błąd:', error);
alert('Wystąpił błąd podczas ładowania pliku: ' + error.message);
}
}
async function saveFileContent() {
if (!codeMirrorEditor || !currentEditingFilePath) {
alert('Brak pliku do zapisania.');
return;
}
const content = codeMirrorEditor.getValue();
try {
const response = await fetch('/api/files/content', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: username,
path: currentEditingFilePath,
content: content
})
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Server responded with status ${response.status}: ${errorText}`);
}
const data = await response.json();
if (data.success) {
alert('Plik został pomyślnie zapisany!');
} else {
alert('Błąd podczas zapisywania pliku: ' + (data.error || 'Nieznany błąd.'));
}
} catch (error) {
console.error('Błąd:', error);
alert('Wystąpił błąd podczas zapisywania pliku: ' + error.message);
}
}
function closeFileEditor() {
document.getElementById('file-editor-container').style.display = 'none';
document.getElementById('file-list').style.display = 'block';
document.getElementById('drop-zone').style.display = 'block';
currentEditingFilePath = null;
if (codeMirrorEditor) {
codeMirrorEditor.setValue('');
}
}

View file

@ -9,42 +9,25 @@ function loadFileList(path = '') {
if (path) { if (path) {
list.innerHTML += `<li><a class="non-link" href="#" onclick="loadFileList('${path.split('/').slice(0, -1).join('/')}')">⬅️ ..</a></li>`; list.innerHTML += `<li><a class="non-link" href="#" onclick="loadFileList('${path.split('/').slice(0, -1).join('/')}')">⬅️ ..</a></li>`;
} }
data.sort((a, b) => { data.sort((a, b) => a.name.localeCompare(b.name));
if (a.is_dir === b.is_dir) {
return a.name.localeCompare(b.name);
}
return a.is_dir ? -1 : 1;
});
data.forEach(entry => { data.forEach(entry => {
const itemPath = path ? `${path}/${entry.name}` : entry.name; const itemPath = path ? `${path}/${entry.name}` : entry.name;
let html = ''; const html = entry.is_dir
if (entry.is_dir) { ? `<li>📁 <a class="non-link" href="#" onclick="loadFileList('${itemPath}')">${entry.name}</a>
html = `<li>📁 <a class="non-link" href="#" onclick="loadFileList('${itemPath}')">${entry.name}</a> <a class="non-link" onclick="deleteItem('${itemPath}')">
<a class="non-link action-icon" onclick="deleteItem('${itemPath}')" title="Usuń folder">
<i class="fa-solid fa-trash"></i> <i class="fa-solid fa-trash"></i>
</a> </a>
</li>`; </li>`
} else { : `<li>📄 ${entry.name}
const fileExtension = getFileExtension(entry.name); <a class="non-link" href="/api/files/download?username=${username}&path=${encodeURIComponent(itemPath)}" target="_blank">
const editableExtensions = ['txt', 'log', 'json', 'yml', 'yaml', 'properties', 'html', 'css', 'js', 'xml', 'py'];
const isEditable = editableExtensions.includes(fileExtension.toLowerCase());
html = `<li>📄 ${entry.name}
${isEditable ? `
<a class="non-link action-icon" onclick="openFileEditor('${itemPath}')" title="Edytuj plik">
<i class="fa-solid fa-edit"></i>
</a>` : ''}
<a class="non-link action-icon" href="/api/files/download?username=${username}&path=${encodeURIComponent(itemPath)}" target="_blank" title="Pobierz plik">
<i class="fa-solid fa-download"></i> <i class="fa-solid fa-download"></i>
</a> </a>
<a class="non-link action-icon" onclick="deleteItem('${itemPath}')" title="Usuń plik"> <a class="non-link" onclick="deleteItem('${itemPath}')">
<i class="fa-solid fa-trash"></i> <i class="fa-solid fa-trash"></i>
</a> </a>
</li>`; </li>`;
}
list.innerHTML += html; list.innerHTML += html;
}); });
closeFileEditor();
}); });
} }
@ -70,9 +53,7 @@ function deleteItem(path) {
}); });
} }
document.querySelector('[onclick="showTab(\'files\')"]').addEventListener('click', () => { document.querySelector('[onclick="showTab(\'files\')"]').addEventListener('click', () => loadFileList());
loadFileList(); // Load root directory when the tab is opened
});
const dropZone = document.getElementById('drop-zone'); const dropZone = document.getElementById('drop-zone');
const fileInput = document.getElementById('file-input'); const fileInput = document.getElementById('file-input');

View file

@ -105,9 +105,8 @@ h2 {
/* Tabs */ /* Tabs */
.tabs { .tabs {
/* Desktop default layout */
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); /* Original desktop layout */ grid-template-columns: repeat(3, 1fr);
gap: 14px; gap: 14px;
margin-bottom: 26px; margin-bottom: 26px;
} }
@ -122,11 +121,6 @@ h2 {
color: #ccc; color: #ccc;
transition: background-color 0.25s, transform 0.2s; transition: background-color 0.25s, transform 0.2s;
box-shadow: 0 0 8px transparent; box-shadow: 0 0 8px transparent;
/* Flexbox for text and icon alignment */
display: flex;
align-items: center;
justify-content: center; /* Center content horizontally */
gap: 8px; /* Space between text and icon */
} }
.tab-button:hover { .tab-button:hover {
@ -140,16 +134,6 @@ h2 {
box-shadow: 0 0 14px #4a5aef99; box-shadow: 0 0 14px #4a5aef99;
} }
.tab-button .tab-icon {
font-size: 1.2em; /* Adjust icon size as needed */
}
.tab-button .tab-text {
/* By default, text is visible on desktop */
display: block;
}
.square-button { .square-button {
aspect-ratio: 1 / 1; aspect-ratio: 1 / 1;
width: 200px; width: 200px;
@ -682,28 +666,18 @@ a.non-link:hover {
box-sizing: border-box; box-sizing: border-box;
} }
/* Tabs on Mobile: Single row, icons only */
.tabs { .tabs {
display: flex; /* Use flexbox for a single row */ display: grid;
grid-template-columns: unset; /* Override grid for mobile */ grid-template-columns: repeat(2, 1fr);
justify-content: space-around; /* Distribute items evenly */ gap: 10px;
flex-wrap: nowrap; /* Keep tabs in a single row */ margin-bottom: 12px;
overflow-x: auto; /* Enable horizontal scrolling if buttons exceed screen width */
-webkit-overflow-scrolling: touch; /* Smooth scrolling on iOS */
padding-bottom: 5px; /* Add some padding for scrollbar if present */
gap: 8px; /* Slightly smaller gap on mobile */
} }
.tab-button { .tab-button {
flex-shrink: 0; /* Prevent buttons from shrinking */ padding: 12px;
padding: 10px 15px; /* Adjust padding for icon-only buttons */ font-size: 0.95rem;
/* Center content for icon-only */ width: 100%;
justify-content: center; box-sizing: border-box;
width: auto; /* Allow buttons to size to content */
}
.tab-button .tab-text {
display: none; /* Hide the text on mobile */
} }
.square-button { .square-button {

View file

@ -3,24 +3,12 @@
<h2>Twój serwer Minecraft</h2> <h2>Twój serwer Minecraft</h2>
<div class="tabs"> <div class="tabs">
<button class="tab-button" onclick="showTab('controls')"> <button class="tab-button" onclick="showTab('controls')">Sterowanie 🎛️</button>
<span class="tab-text">Sterowanie</span> <span class="tab-icon">🎛️</span> <button class="tab-button" onclick="showTab('console')">Konsola 📟</button>
</button> <button class="tab-button" onclick="showTab('files')">Pliki 📁</button>
<button class="tab-button" onclick="showTab('console')"> <button class="tab-button" onclick="showTab('config')">Konfiguracja 🛠️</button>
<span class="tab-text">Konsola</span> <span class="tab-icon">📟</span> <button class="tab-button" onclick="showTab('mods')">Mody/Pluginy 🧩</button>
</button> <button class="tab-button" onclick="showTab('statistics')">Statystyki 📈</button>
<button class="tab-button" onclick="showTab('files')">
<span class="tab-text">Pliki</span> <span class="tab-icon">📁</span>
</button>
<button class="tab-button" onclick="showTab('config')">
<span class="tab-text">Konfiguracja</span> <span class="tab-icon">🛠️</span>
</button>
<button class="tab-button" onclick="showTab('mods')">
<span class="tab-text">Mody/Pluginy</span> <span class="tab-icon">🧩</span>
</button>
<button class="tab-button" onclick="showTab('statistics')">
<span class="tab-text">Statystyki</span> <span class="tab-icon">📈</span>
</button>
</div> </div>
<div class="tab-content"> <div class="tab-content">
@ -58,13 +46,6 @@
<br> <br>
<h4>Lista plików:</h4> <h4>Lista plików:</h4>
<ul id="file-list"></ul> <ul id="file-list"></ul>
<div id="file-editor-container" style="display: none;">
<h3>Edytuj plik: <span id="editing-filename"></span></h3>
<textarea id="file-editor" style="width: 100%; height: 400px;"></textarea>
<button class="btn btn-primary" onclick="saveFileContent()">Zapisz zmiany</button>
<button class="btn btn-secondary" onclick="closeFileEditor()">Zamknij</button>
</div>
</div> </div>
<div class="tab-panel" id="config"> <div class="tab-panel" id="config">
@ -142,8 +123,6 @@
</div> </div>
</div> </div>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/theme/material-darker.min.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="/static/js/controls.js"></script> <script src="/static/js/controls.js"></script>
<script src="/static/js/console.js"></script> <script src="/static/js/console.js"></script>
@ -162,11 +141,6 @@
function showTab(id) { function showTab(id) {
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active')); document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
$(id).classList.add('active'); $(id).classList.add('active');
// Close the mobile menu if it's open (optional, depending on your menu implementation)
const tabsContainer = document.querySelector('.tabs');
if (tabsContainer.classList.contains('mobile-active')) {
tabsContainer.classList.remove('mobile-active');
}
} }
let currentPath = ''; let currentPath = '';
@ -293,16 +267,4 @@
setInterval(checkServerStatus, 5000); setInterval(checkServerStatus, 5000);
</script> </script>
<!-- CSS do Edytora Plików --> {% endblock %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/javascript/javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/xml/xml.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/css/css.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/htmlmixed/htmlmixed.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/clike/clike.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/python/python.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/yaml/yaml.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/properties/properties.min.js"></script>
<script src="/static/js/file_editor.js"></script>
{% endblock %}