Need help to create a file manager on the web

Hi there,

The reason is because for some reason editing of code is only through available SSH,vim and nano. I would like to create a browser based file editor that can access all the files on the current directory.

The concept:
project_directory/
├── backend/
│ └── server.py
└── frontend/
└── index.html

On the backend server:
python server.py

On the frontend server:
python -m http.server

The backend code:

from flask import Flask, request, jsonify, send_file
import os

app = Flask(__name__)

# API endpoint to read a file
@app.route('/api/read-file/<path:filename>')
def read_file(filename):
    try:
        file_path = os.path.join(os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)), filename)
        with open(file_path, 'r') as f:
            content = f.read()
        return content
    except FileNotFoundError:
        return jsonify(error="File not found"), 404
    except Exception as e:
        return jsonify(error=str(e)), 500

# API endpoint to write to a file
@app.route('/api/write-file/<path:filename>', methods=['POST'])
def write_file(filename):
    try:
        content = request.json['content']
        file_path = os.path.join(os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)), filename)
        with open(file_path, 'w') as f:
            f.write(content)
        return 'File saved successfully'
    except Exception as e:
        return jsonify(error=str(e)), 500

# API endpoint to list directory contents
@app.route('/api/list-directory')
def list_directory():
    try:
        directory = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
        files = []
        for root, _, filenames in os.walk(directory):
            for filename in filenames:
                files.append(os.path.relpath(os.path.join(root, filename), directory))
        return jsonify(files)
    except Exception as e:
        return jsonify(error=str(e)), 500

# Route to serve frontend files
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def serve_frontend(path):
    if path == '':
        return send_file('frontend/index.html')
    else:
        return send_file(os.path.join('frontend', path))

if __name__ == '__main__':
    app.run(debug=True)

The frontend code:

<!-- frontend/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Browser-based File Editor</title>
  <!-- Include CodeMirror CSS -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.0/codemirror.min.css">
  <!-- Include CodeMirror JavaScript -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.0/codemirror.min.js"></script>
  <!-- Include CodeMirror mode for the programming language you want to support (e.g., JavaScript) -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.0/mode/javascript/javascript.min.js"></script>
  <!-- Include CodeMirror keymap (optional) -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.0/keymap/sublime.min.js"></script>
</head>
<body>
  <select id="fileList"></select><br>
  <button onclick="loadFile()">Load File</button><br>
  <textarea id="code" name="code"></textarea>
  <button onclick="saveFile()">Save File</button>

  <script>
    var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
      lineNumbers: true,
      mode: "javascript",
      theme: "default",
      keyMap: "sublime",
      autofocus: true
    });

    // Function to fetch directory contents
    fetch('/api/list-directory')
      .then(response => response.json())
      .then(data => {
        var fileList = document.getElementById('fileList');
        data.forEach(filename => {
          var option = document.createElement('option');
          option.text = filename;
          fileList.add(option);
        });
      });

    // Function to load file content into the editor
    function loadFile() {
      var filename = document.getElementById('fileList').value;

      fetch(`/api/read-file/${filename}`)
        .then(response => response.text())
        .then(data => {
          editor.setValue(data);
        })
        .catch(error => {
          console.error('Error loading file:', error);
          alert('Error loading file.');
        });
    }

    // Function to save file content
    function saveFile() {
      var content = editor.getValue();
      var filename = document.getElementById('fileList').value;

      fetch(`/api/write-file/${filename}`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ content })
      })
      .then(response => {
        if (!response.ok) {
          throw new Error('Failed to save file.');
        }
        alert('File saved successfully.');
      })
      .catch(error => {
        console.error('Error saving file:', error);
        alert('Error saving file.');
      });
    }
  </script>
</body>
</html>

Currently the problem is nothing on the parent directory gets loaded for me to edit the file when I try to run it.

There is a following error on the list directory:
127.0.0.1 - - [05/Apr/2024 04:45:14] "GET /fileeditor.html HTTP/1.1" 304 -
127.0.0.1 - - [05/Apr/2024 04:45:16] code 404, message File not found
127.0.0.1 - - [05/Apr/2024 04:45:16] "GET /api/list-directory HTTP/1.1" 404 -

Error response

Error code: 404

Message: File not found.

Error code explanation: 404 - Nothing matches the given URI.

Note, I am already running as root for both server

I have used curl on the following, it says 404 error

There are running on different port could this be the reason

could VS code (probably others (git ....) ) provide the functionality you're after?

How about Minio (single instance for your use case) and using mc tool on clients to manipulate files.
Or git as mentioned.

Try not to reinvent the wheel, there are many ready and free solutions out there.

Only exception would be to self-educate of course :slight_smile:

Regards
Peasant.