diff --git a/app/api.py b/app/api.py index aa3545d..a2cfde5 100644 --- a/app/api.py +++ b/app/api.py @@ -204,6 +204,51 @@ def delete_file_or_folder(): 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 @api.route('/config') @oidc.require_login diff --git a/app/static/js/file_editor.js b/app/static/js/file_editor.js new file mode 100644 index 0000000..3276c66 --- /dev/null +++ b/app/static/js/file_editor.js @@ -0,0 +1,134 @@ +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(''); + } +} \ No newline at end of file diff --git a/app/static/js/files.js b/app/static/js/files.js index 8036b12..970510d 100644 --- a/app/static/js/files.js +++ b/app/static/js/files.js @@ -9,25 +9,42 @@ function loadFileList(path = '') { if (path) { list.innerHTML += `