Update: 2020-06-08

Der Esp8266 Filesystemmanger alphabetisch sortiert als Arduino Tab.

Inhalte von Ordnern werden aktuell nur im Stammverzeichnis gelesen.

LittleFS.ino

// ****************************************************************
// Sketch Esp8266 Dateiverwaltung alphabetisch Sortiert Modular(Tab)
// created: Jens Fleischer, 2020-06-08
// last mod: Jens Fleischer, 2020-06-08
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp8266
// Software: Esp8266 Arduino Core 2.6.0 - 2.7.1
// Geprüft: von 1MB bis 2MB Flash
// Getestet auf: Nodemcu
/******************************************************************
  Copyright (c) 2020 Jens Fleischer. All rights reserved.

  This file is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  This file is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
*******************************************************************/
// Diese Version von LittelFS sollte als Tab eingebunden werden.
// #include <FS.h> #include <ESP8266WebServer.h> müssen im Haupttab aufgerufen werden
// Die Funktionalität des ESP8266 Webservers ist erforderlich.
// "server.onNotFound()" darf nicht im Setup des ESP8266 Webserver stehen.
// Die Funktion "setupFS();" muss im Setup aufgerufen werden.
/**************************************************************************************/

#include <LittleFS.h>
#include <list>

const char WARNING[] PROGMEM = R"(<h2>Der Sketch wurde mit "FS:none" kompilliert!)";
const char HELPER[] PROGMEM = R"(<form method="POST" action="/upload" enctype="multipart/form-data">
<input type="file" name="upload"><button>Upload</button></form>Lade die fs.html hoch.)";

void setupFS() {                             // Funktionsaufruf "setupFS();" muss im Setup eingebunden werden
  LittleFS.begin();
  server.on("/json", handleList);
  server.on("/format", formatFS);
  server.on("/upload", HTTP_POST, sendResponce, handleUpload);
  server.onNotFound([]() {
    if (!handleFile(server.urlDecode(server.uri())))
      server.send(404, "text/plain", "FileNotFound");
  });
  createFolder();                            // Nur zur Präsentation 
}

void createFolder() {                        // Erzeugt zum testen einen Ordner mit 2 Dateien
  String folder = "Secrets";
  LittleFS.mkdir(folder);
  File file = LittleFS.open(folder + "/help.txt", "w");
  if (file) {
    file.print("Help! wurde am 19. Juli 1965 in den USA und am 23. Juli 1965 in Großbritannien als Single veröffentlicht.");
    file.close();
  }
  file = LittleFS.open(folder + "/example.html", "w");
  if (file) {
    file.print(R"(<!DOCTYPE html>
<html lang="de">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Titel</title>
  </head>
  <body>
    <p>
      Sehen Sie sich den Quellcode dieser Seite an.
      <kbd>(Kontextmenu: Seitenquelltext anzeigen)</kbd>
    </p>
  </body>
</html>)");
    file.close();
  }
  sendResponce();
}

void handleList() {                                                             // Senden aller Daten an den Client
  FSInfo fs_info;  LittleFS.info(fs_info);                                      // Füllt FSInfo Struktur mit Informationen über das Dateisystem
  Dir dir = LittleFS.openDir("/");                                              // Auflistung aller im Root vorhandenen Dateien
  std::list<String> folderList;                                                 // Ordnerliste anlegen                                           
  while (dir.next()) if (dir.isDirectory()) folderList.push_back(dir.fileName() + "/");
  folderList.push_back("/");
  std::list<String> fileList;                                                   // Dateiliste anlegen
  for (auto& f : folderList) {
    dir = LittleFS.openDir(f);
    while (dir.next()) if (dir.isFile()) fileList.push_back((f != "/" ? f : "") + dir.fileName() + ":" + dir.fileSize()); // Liste füllen
  }
  fileList.sort( [](const String & first, const String & second) {              // Dateiliste sortieren
    uint8_t i {0};
    while (true) {
      if (tolower(first[i]) < tolower(second[i])) return true;
      else if (tolower(first[i]) > tolower(second[i])) return false;
      i++;
    }
  });
  String temp = "[";
  for (auto& d : fileList) {
    if (temp != "[") temp += ',';
    temp += "{\"name\":\"" + d.substring(0, d.indexOf (":")) + "\",\"size\":\"" + formatBytes(d.substring((d.indexOf (":")) + 1).toInt()) + "\"}";
  }
  temp += ",{\"usedBytes\":\"" + formatBytes(fs_info.usedBytes) +                      // Berechnet den verwendeten Speicherplatz
          "\",\"totalBytes\":\"" + formatBytes(fs_info.totalBytes) +                   // Zeigt die Größe des Speichers
          "\",\"freeBytes\":\"" + (fs_info.totalBytes - fs_info.usedBytes) + "\"}]";   // Berechnet den freien Speicherplatz
  server.send(200, "application/json", temp);
}

bool handleFile(String && path) {
  if (server.hasArg("delete")) {
    LittleFS.remove(server.arg("delete"));                 // Datei löschen
    sendResponce();
    return true;
  }
  if (!LittleFS.exists("fs.html")) server.send(200, "text/html", LittleFS.begin() ? HELPER : WARNING);     // ermöglicht das hochladen der fs.html
  if (path.endsWith("/")) path += "index.html";
  return LittleFS.exists(path) ? ({File f = LittleFS.open(path, "r"); server.streamFile(f, getContentType(path)); f.close(); true;}) : false;
}

void handleUpload() {                                      // Dateien ins Filesystem schreiben
  static File fsUploadFile;                                // enthält den aktuellen Upload
  HTTPUpload& upload = server.upload();
  if (upload.status == UPLOAD_FILE_START) {
    if (upload.filename.length() > 31) {
      upload.filename = upload.filename.substring(upload.filename.length() - 31, upload.filename.length());  // Dateinamen kürzen
    }
    printf(PSTR("handleFileUpload Name: /%s\n"), upload.filename.c_str());
    fsUploadFile = LittleFS.open("/" + server.urlDecode(upload.filename), "w");
  } else if (upload.status == UPLOAD_FILE_WRITE) {
    printf(PSTR("handleFileUpload Data: %u\n"), upload.currentSize);
    fsUploadFile.write(upload.buf, upload.currentSize);
  } else if (upload.status == UPLOAD_FILE_END) {
    printf(PSTR("handleFileUpload Size: %u\n"), upload.totalSize);
    fsUploadFile.close();
  }
}

void formatFS() {                                          // Formatiert das Filesystem
  LittleFS.format();
  sendResponce();
}

void sendResponce() {
  server.sendHeader("Location", "fs.html");
  server.send(303, "message/http");
}

const String formatBytes(size_t const & bytes) {           // lesbare Anzeige der Speichergrößen
  return bytes < 1024 ? static_cast<String>(bytes) + " Byte" : bytes < 1048576 ? static_cast<String>(bytes / 1024.0) + " KB" : static_cast<String>(bytes / 1048576.0) + " MB";
}

const String getContentType(const String & path) {         // ermittelt den MIME-Type
  using namespace mime;
  char buff[sizeof(mimeTable[0].mimeType)];
  for (size_t i = 0; i < maxType - 1; i++) {
    strcpy_P(buff, mimeTable[i].endsWith);
    if (path.endsWith(buff)) {
      strcpy_P(buff, mimeTable[i].mimeType);
      return static_cast<String>(buff);
    }
  }
  strcpy_P(buff, mimeTable[maxType - 1].mimeType);
  return static_cast<String>(buff);
}

Das Webinterface zum LittleFS Tab.

fs.html

<!DOCTYPE HTML> <!-- For more information visit: https://fipsok.de -->
<html lang="de">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="style.css">
    <title>Esp8266 Datei Manager</title>
    <script>
      document.addEventListener('DOMContentLoaded', () => {
        let myList = document.querySelector('main');
        fetch('json').then( (response) => {
          return response.json();
        }).then((json) => {
          for (var i = 0; i < json.length - 1; i++) {
            let dir = `<li><a href ="${json[i].name}">${json[i].name}</a><small> ${json[i].size}</small><a href ="${json[i].name}"download="${json[i].name.substr(json[i].name.lastIndexOf("/")+1)}"> Download </a>`;
            if (json[i].name != 'fs.html') dir += `or <a href ="?delete=${json[i].name}">Delete </a>`;
            myList.insertAdjacentHTML('beforeend', dir);
          }
          myList.insertAdjacentHTML('beforeend', `<li><b>LittleFS</b> belegt ${json[i].usedBytes} von ${json[i].totalBytes.replace(".00","")}`);
          free = json[i].freeBytes;
        });
        fs.addEventListener('change', () => {
		  for (var bytes = 0, j = 0; j < event.target.files.length; j++) bytes += event.target.files[j].size;
          for (var output = `${bytes} Byte`, i = 0, circa = bytes / 1024; circa > 1; circa /= 1024) {
            output = circa.toFixed(2) + [' KB', ' MB', ' GB'][i++];
          }
          if (bytes > free) {
            si.innerHTML = `<li><b> ${output}</b> <strong style="color: red;"> Ungenügend Speicher frei </strong></li>`;
            up.setAttribute('disabled', 'disabled');
          } 
          else {
            si.innerHTML = `<li><b>Dateigröße:</b> ${output} </li>`;
            up.removeAttribute('disabled');
          }
        });
		btn.addEventListener('click', () => {
	      if (!confirm(`Wirklich formatieren? Alle Daten gehen verloren.\nDu musst anschließend fs.html wieder laden.`)) event.preventDefault();
	    });
	  });
    </script>
  </head>
  <body>
    <h2>ESP8266 Filesystem Manager</h2>
    <form action="/upload" method="POST" enctype="multipart/form-data">
	  <input id="fs" type="file" name="upload[]" multiple>
      <input id="up" type="submit" value="Upload" disabled>
    </form>
    <div>
      <span id="si"></span>
      <main></main>
    </div>
    <form action="/format" method="POST"><input id="btn" type="submit" value="Format LittleFS"></form>
  </body>
</html>

Die CSS Datei für den Esp8266 Webserver.

style.css

/* For more information visit:https://fipsok.de */
body {
	font-family: sans-serif;
	background-color: #87cefa;
	display: flex;
	flex-flow: column;
	align-items: center;
}
h1,h2 {
	color: #e1e1e1;
	text-shadow: 2px 2px 2px black;
}
li {
  background-color: #feb1e2;
  list-style-type: none;
  margin-bottom: 10px;
  padding: 2px 5px 1px 0;
  box-shadow: 5px 5px 5px rgba(0,0,0,0.7);
}
li a:first-child, li b:first-child {
  background-color: #8f05a5;
  font-weight: bold;
  color: white;
  text-decoration:none;
  padding: 2px 5px;
  text-shadow: 2px 2px 1px black;
}
input {
	height:35px;
	font-size:14px;
}
h1 + main {
  display: flex;
}  
aside {
  display: flex;
  flex-direction: column;
  padding: 0.2em;
}
#left {
  align-items:flex-end;
  text-shadow: 0.5px 0.5px 1px #757474;
}
.note {
	background-color: #fecdee;
	padding: 0.5em;
	margin-top: 1em;
	text-align: center;
	max-width: 320px;
	border-radius: 0.5em;
}
[type=submit] {
	height:40px; 
	font-size: 16px;
}
[value*=Format] {
	margin-top: 1em;
	box-shadow: 5px 5px 5px rgba(0,0,0,0.7);
}
button {
	height:40px;
	width:130px;
	background-color: #7bff97;
	font-size:16px;
	margin-top: 1em;
	box-shadow: 5px 5px 5px rgba(0,0,0,0.7);
}
form [title] {
	background-color: skyblue;
	font-size: 16px;
	width: 125px;
}