File UI changes (#19014)

* Remove recursion into folders on Manage Files.  May be enabled with UFILESYS_RECURSEFOLDERS_GUI.  On Edit of a file, Save and Magane btuttons return to the folder containgint the file being edited.  On delete file, UI returns to the folder that the deleted file was in.

* Make newfile put in in the current folder, and return to current folder on save of manage button.

* Add folderOnly and FileOnly functions to reduce code duplication.
Enable folder delete.
Enable folder listing to be aborted (x in browser)
Disbale ESP32 Download Task.  Needs attention.
Allow folder create from newfile name.
This commit is contained in:
btsimonh 2023-07-10 09:27:20 +01:00 committed by GitHub
parent fcfc3ecb37
commit b1cc87d24f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 272 additions and 51 deletions

View File

@ -18,6 +18,17 @@
*/ */
#ifdef USE_UFILESYS #ifdef USE_UFILESYS
// saves 80 bytes of flash, makes the UI cleaner for folders containing lots of files.
// disables recursive folder listing in file UI
//#define UFILESYS_NO_RECURSE_GUI
// Enables serving of static files on /fs/
// costs 1844 bytes of flash and 40 bytes of RAM
// probably not useful on esp8266, but useful on esp32
// You could serve a whole webapp from Tas itself.
//#define UFILESYS_STATIC_SERVING
/*********************************************************************************************\ /*********************************************************************************************\
This driver adds universal file system support for This driver adds universal file system support for
- ESP8266 (sd card or littlefs on > 1 M devices with special linker file e.g. eagle.flash.4m2m.ld) - ESP8266 (sd card or littlefs on > 1 M devices with special linker file e.g. eagle.flash.4m2m.ld)
@ -135,6 +146,36 @@ void UfsInit(void) {
} }
} }
// simple put a zero at last '/'
// modifies input string
char *folderOnly(char *fname){
for (int i = strlen(fname)-1; i >= 0; i--){
if (fname[i] == '/'){
fname[i] = 0;
break;
}
fname[i] = 0;
}
if (!fname[0]){
fname[0] = '/';
fname[1] = 0;
}
return fname;
}
// returns everything after last '/' of whiole input if no '/'
char *fileOnly(char *fname){
char *cp = fname;
for (uint32_t cnt = strlen(fname); cnt >= 0; cnt--) {
if (fname[cnt] == '/') {
cp = &fname[cnt + 1];
break;
}
}
return cp;
}
#ifdef USE_SDCARD #ifdef USE_SDCARD
void UfsCheckSDCardInit(void) { void UfsCheckSDCardInit(void) {
// Try SPI mode first // Try SPI mode first
@ -503,10 +544,18 @@ char* UfsFilename(char* fname, char* fname_in) {
} }
const char kUFSCommands[] PROGMEM = "Ufs|" // Prefix const char kUFSCommands[] PROGMEM = "Ufs|" // Prefix
"|Type|Size|Free|Delete|Rename|Run"; "|Type|Size|Free|Delete|Rename|Run"
#ifdef UFILESYS_STATIC_SERVING
"|Serve"
#endif
;
void (* const kUFSCommand[])(void) PROGMEM = { void (* const kUFSCommand[])(void) PROGMEM = {
&UFSInfo, &UFSType, &UFSSize, &UFSFree, &UFSDelete, &UFSRename, &UFSRun}; &UFSInfo, &UFSType, &UFSSize, &UFSFree, &UFSDelete, &UFSRename, &UFSRun
#ifdef UFILESYS_STATIC_SERVING
,&UFSServe
#endif
};
void UFSInfo(void) { void UFSInfo(void) {
Response_P(PSTR("{\"Ufs\":{\"Type\":%d,\"Size\":%d,\"Free\":%d}"), ufs_type, UfsInfo(0, 0), UfsInfo(1, 0)); Response_P(PSTR("{\"Ufs\":{\"Type\":%d,\"Size\":%d,\"Free\":%d}"), ufs_type, UfsInfo(0, 0), UfsInfo(1, 0));
@ -586,6 +635,115 @@ void UFSRename(void) {
} }
} }
#ifdef UFILESYS_STATIC_SERVING
/*
* Serves a filesystem folder at a web url.
* NOTE - this is expensive on flash -> +2.5kbytes.
* like "UFSServe <fs path>,<url>[,<noauth>]"
* e.g. "UFSServe /sd/,/mysdcard/,1" - will serve the /sd/ fs folder as https://<ip>/mysdcard/ with no auth required
* e.g. "UFSServe /www/,/" - will serve the /www/ fs folder as https://<ip>/ with auth required if TAS has a password setup
* <noauth> defaults to 0 - i.e. the default is to require auth if configured
* it WILL serve on / - so conflicting urls could occur. I beleive native TAS urls will have priority.
* you can serve multiple folders, and they can each be auth or noauth
*
* by default, it also enables cors on the webserver - this allows you to have
* a website external to TAS which can access the files, else the browser refuses.
*/
#include "detail/RequestHandlersImpl.h"
// class to allow us to request auth when required.
// StaticRequestHandler is in the above header
class StaticRequestHandlerAuth : public StaticRequestHandler {
public:
StaticRequestHandlerAuth(FS& fs, const char* path, const char* uri, const char* cache_header):
StaticRequestHandler(fs, path, uri, cache_header)
{
}
// we replace the handle method,
// and look for authentication only if we would serve the file.
// if we check earlier, then we will reject as unauth even though a later
// handler may be public, and so fail to serve public files.
bool handle(WebServer& server, HTTPMethod requestMethod, String requestUri) override {
if (!canHandle(requestMethod, requestUri))
return false;
log_v("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str());
String path(_path);
if (!_isFile) {
// Base URI doesn't point to a file.
// If a directory is requested, look for index file.
if (requestUri.endsWith("/"))
requestUri += "index.htm";
// Append whatever follows this URI in request to get the file path.
path += requestUri.substring(_baseUriLength);
}
log_v("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);
String contentType = getContentType(path);
// look for gz file, only if the original specified path is not a gz. So part only works to send gzip via content encoding when a non compressed is asked for
// if you point the the path to gzip you will serve the gzip as content type "application/x-gzip", not text or javascript etc...
if (!path.endsWith(FPSTR(mimeTable[gz].endsWith)) && !_fs.exists(path)) {
String pathWithGz = path + FPSTR(mimeTable[gz].endsWith);
if(_fs.exists(pathWithGz))
path += FPSTR(mimeTable[gz].endsWith);
}
File f = _fs.open(path, "r");
if (!f || !f.available())
return false;
if (!WebAuthenticate()) {
AddLog(LOG_LEVEL_ERROR, PSTR("UFS: serv of %s denied"), requestUri.c_str());
server.requestAuthentication();
return true;
}
if (_cache_header.length() != 0)
server.sendHeader("Cache-Control", _cache_header);
server.streamFile(f, contentType);
return true;
}
};
void UFSServe(void) {
if (XdrvMailbox.data_len > 0) {
bool result = false;
char *fpath = strtok(XdrvMailbox.data, ",");
char *url = strtok(nullptr, ",");
char *noauth = strtok(nullptr, ",");
if (fpath && url) {
char t[] = "";
StaticRequestHandlerAuth *staticHandler;
if (noauth && *noauth == '1'){
staticHandler = (StaticRequestHandlerAuth *) new StaticRequestHandler(*ffsp, fpath, url, (char *)nullptr);
} else {
staticHandler = new StaticRequestHandlerAuth(*ffsp, fpath, url, (char *)nullptr);
}
if (staticHandler) {
//Webserver->serveStatic(url, *ffsp, fpath);
Webserver->addHandler(staticHandler);
Webserver->enableCORS(true);
result = true;
} else {
// could this happen? only lack of memory.
result = false;
}
}
if (!result) {
ResponseCmndFailed();
} else {
ResponseCmndDone();
}
}
}
#endif // UFILESYS_STATIC_SERVING
void UFSRun(void) { void UFSRun(void) {
if (XdrvMailbox.data_len > 0) { if (XdrvMailbox.data_len > 0) {
char fname[UFS_FILENAME_SIZE]; char fname[UFS_FILENAME_SIZE];
@ -601,6 +759,8 @@ void UFSRun(void) {
} }
} }
/*********************************************************************************************\ /*********************************************************************************************\
* Web support * Web support
\*********************************************************************************************/ \*********************************************************************************************/
@ -608,7 +768,14 @@ void UFSRun(void) {
#ifdef USE_WEBSERVER #ifdef USE_WEBSERVER
const char UFS_WEB_DIR[] PROGMEM = const char UFS_WEB_DIR[] PROGMEM =
"<p><form action='" "ufsd" "' method='get'><button>" "%s" "</button></form></p>"; "<p><form action='ufsd' method='get'><input type='hidden' name='download' value='%s' /> <button>%s</button></form></p>";
const char UFS_CURRDIR[] PROGMEM =
"<p>%s: %s</p>";
#ifndef D_CURR_DIR
#define D_CURR_DIR "Folder"
#endif
const char UFS_FORM_FILE_UPLOAD[] PROGMEM = const char UFS_FORM_FILE_UPLOAD[] PROGMEM =
"<div id='f1' name='f1' style='display:block;'>" "<div id='f1' name='f1' style='display:block;'>"
@ -632,7 +799,7 @@ const char UFS_FORM_SDC_DIRa[] PROGMEM =
const char UFS_FORM_SDC_DIRc[] PROGMEM = const char UFS_FORM_SDC_DIRc[] PROGMEM =
"</div>"; "</div>";
const char UFS_FORM_FILE_UPGb[] PROGMEM = const char UFS_FORM_FILE_UPGb[] PROGMEM =
"<form method='get' action='ufse'><input type='hidden' file='" D_NEW_FILE "'>" "<form method='get' action='ufse'><input type='hidden' name='file' value='%s/" D_NEW_FILE "'>"
"<button type='submit'>" D_CREATE_NEW_FILE "</button></form>"; "<button type='submit'>" D_CREATE_NEW_FILE "</button></form>";
const char UFS_FORM_FILE_UPGb1[] PROGMEM = const char UFS_FORM_FILE_UPGb1[] PROGMEM =
"<input type='checkbox' id='shf' onclick='sf(eb(\"shf\").checked);' name='shf'>" D_SHOW_HIDDEN_FILES "</input>"; "<input type='checkbox' id='shf' onclick='sf(eb(\"shf\").checked);' name='shf'>" D_SHOW_HIDDEN_FILES "</input>";
@ -648,14 +815,14 @@ const char UFS_FORM_SDC_DIR_HIDDABLE[] PROGMEM =
const char UFS_FORM_SDC_DIRd[] PROGMEM = const char UFS_FORM_SDC_DIRd[] PROGMEM =
"<pre><a href='%s' file='%s'>%s</a></pre>"; "<pre><a href='%s' file='%s'>%s</a></pre>";
const char UFS_FORM_SDC_DIRb[] PROGMEM = const char UFS_FORM_SDC_DIRb[] PROGMEM =
"<pre%s><a href='%s' file='%s'>%s</a> %s %8d %s %s</pre>"; "<pre%s><a href='%s' file='%s'>%s</a> %19s %8d %s %s</pre>";
const char UFS_FORM_SDC_HREF[] PROGMEM = const char UFS_FORM_SDC_HREF[] PROGMEM =
"ufsd?download=%s/%s"; "ufsd?download=%s/%s";
#ifdef GUI_TRASH_FILE #ifdef GUI_TRASH_FILE
const char UFS_FORM_SDC_HREFdel[] PROGMEM = const char UFS_FORM_SDC_HREFdel[] PROGMEM =
//"<a href=ufsd?delete=%s/%s>&#128465;</a>"; // 🗑️ //"<a href=ufsd?delete=%s/%s>&#128465;</a>"; // 🗑️
"<a href='ufsd?delete=%s/%s' onclick=\"return confirm('" D_CONFIRM_FILE_DEL "')\">&#128293;</a>"; // 🔥 "<a href='ufsd?delete=%s/%s&download=%s' onclick=\"return confirm('" D_CONFIRM_FILE_DEL "')\">&#128293;</a>"; // 🔥
#endif // GUI_TRASH_FILE #endif // GUI_TRASH_FILE
#ifdef GUI_EDIT_FILE #ifdef GUI_EDIT_FILE
@ -684,23 +851,15 @@ void UfsDirectory(void) {
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_MANAGE_FILE_SYSTEM)); AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_MANAGE_FILE_SYSTEM));
uint8_t depth = 0; uint8_t depth = 0;
uint8_t isdir = 0;
strcpy(ufs_path, "/"); strcpy(ufs_path, "/");
if (Webserver->hasArg(F("download"))) {
String stmp = Webserver->arg(F("download"));
char *cp = (char*)stmp.c_str();
if (UfsDownloadFile(cp)) {
// is directory
strcpy(ufs_path, cp);
} else {
return;
}
}
if (Webserver->hasArg(F("dir"))) { if (Webserver->hasArg(F("dir"))) {
String stmp = Webserver->arg(F("dir")); String stmp = Webserver->arg(F("dir"));
ufs_dir = atoi(stmp.c_str()); ufs_dir = atoi(stmp.c_str());
}
if (ufs_dir == 1) { if (ufs_dir == 1) {
dfsp = ufsp; dfsp = ufsp;
} else { } else {
@ -708,13 +867,33 @@ void UfsDirectory(void) {
dfsp = ffsp; dfsp = ffsp;
} }
} }
}
if (Webserver->hasArg(F("delete"))) { if (Webserver->hasArg(F("delete"))) {
String stmp = Webserver->arg(F("delete")); String stmp = Webserver->arg(F("delete"));
char *cp = (char*)stmp.c_str(); char *cp = (char*)stmp.c_str();
File download_file = dfsp->open(cp, UFS_FILE_READ);
if (download_file) {
if (download_file.isDirectory()) {
download_file.close();
dfsp->rmdir(cp);
} else {
download_file.close();
dfsp->remove(cp); dfsp->remove(cp);
} }
}
}
if (Webserver->hasArg(F("download"))) {
String stmp = Webserver->arg(F("download"));
char *cp = (char*)stmp.c_str();
if (UfsDownloadFile(cp)) {
// is directory
strcpy(ufs_path, cp);
isdir = 1;
} else {
return;
}
}
WSContentStart_P(PSTR(D_MANAGE_FILE_SYSTEM)); WSContentStart_P(PSTR(D_MANAGE_FILE_SYSTEM));
WSContentSendStyle(); WSContentSendStyle();
@ -733,13 +912,20 @@ void UfsDirectory(void) {
WSContentSend_P(UFS_FORM_FILE_UPG, PSTR(D_SCRIPT_UPLOAD)); WSContentSend_P(UFS_FORM_FILE_UPG, PSTR(D_SCRIPT_UPLOAD));
if (isdir){
// if a folder, show 'folder: xxx' if not '/'
if (ufs_path[0] != '/' || strlen(ufs_path) != 1){
WSContentSend_P(UFS_CURRDIR, PSTR(D_CURR_DIR), ufs_path);
}
}
WSContentSend_P(UFS_FORM_SDC_DIRa); WSContentSend_P(UFS_FORM_SDC_DIRa);
if (ufs_type) { if (ufs_type) {
UfsListDir(ufs_path, depth); UfsListDir(ufs_path, depth);
} }
WSContentSend_P(UFS_FORM_SDC_DIRc); WSContentSend_P(UFS_FORM_SDC_DIRc);
#ifdef GUI_EDIT_FILE #ifdef GUI_EDIT_FILE
WSContentSend_P(UFS_FORM_FILE_UPGb); WSContentSend_P(UFS_FORM_FILE_UPGb, ufs_path);
#endif #endif
if (!isSDC()) { if (!isSDC()) {
WSContentSend_P(UFS_FORM_FILE_UPGb1); WSContentSend_P(UFS_FORM_FILE_UPGb1);
@ -788,6 +974,13 @@ void UfsListDir(char *path, uint8_t depth) {
} }
char *ep; char *ep;
while (true) { while (true) {
WiFiClient client = Webserver->client();
// abort if the client disconnected
// if there is a huge folder, then this gives a way out by refresh of browser
if (!client.connected()){
break;
}
File entry = dir.openNextFile(); File entry = dir.openNextFile();
if (!entry) { if (!entry) {
break; break;
@ -820,9 +1013,19 @@ void UfsListDir(char *path, uint8_t depth) {
const char* ppe = pp_escaped_string.c_str(); // this can't be merged on a single line otherwise the String object can be freed const char* ppe = pp_escaped_string.c_str(); // this can't be merged on a single line otherwise the String object can be freed
const char* epe = ep_escaped_string.c_str(); const char* epe = ep_escaped_string.c_str();
sprintf(cp, format, ep); sprintf(cp, format, ep);
#ifdef GUI_TRASH_FILE
char delpath[128+UFS_FILENAME_SIZE];
ext_snprintf_P(delpath, sizeof(delpath), UFS_FORM_SDC_HREFdel, ppe, epe, ppe[0]?ppe:"/");
#else
char delpath[2] = " ";
#endif // GUI_TRASH_FILE
if (entry.isDirectory()) { if (entry.isDirectory()) {
ext_snprintf_P(npath, sizeof(npath), UFS_FORM_SDC_HREF, ppe, epe); ext_snprintf_P(npath, sizeof(npath), UFS_FORM_SDC_HREF, ppe, epe);
WSContentSend_P(UFS_FORM_SDC_DIRd, npath, ep, name);
WSContentSend_P(UFS_FORM_SDC_DIRb, hiddable ? UFS_FORM_SDC_DIR_HIDDABLE : UFS_FORM_SDC_DIR_NORMAL, npath, epe,
HtmlEscape(name).c_str(), "", 0, delpath, " ");
//WSContentSend_P(UFS_FORM_SDC_DIRd, npath, ep, name);
#ifdef UFILESYS_RECURSEFOLDERS_GUI
uint8_t plen = strlen(path); uint8_t plen = strlen(path);
if (plen > 1) { if (plen > 1) {
strcat(path, "/"); strcat(path, "/");
@ -830,14 +1033,8 @@ void UfsListDir(char *path, uint8_t depth) {
strcat(path, ep); strcat(path, ep);
UfsListDir(path, depth + 4); UfsListDir(path, depth + 4);
path[plen] = 0; path[plen] = 0;
#endif
} else { } else {
#ifdef GUI_TRASH_FILE
char delpath[128];
ext_snprintf_P(delpath, sizeof(delpath), UFS_FORM_SDC_HREFdel, ppe, epe);
#else
char delpath[2];
delpath[0]=0;
#endif // GUI_TRASH_FILE
#ifdef GUI_EDIT_FILE #ifdef GUI_EDIT_FILE
char editpath[128]; char editpath[128];
ext_snprintf_P(editpath, sizeof(editpath), UFS_FORM_SDC_HREFedit, ppe, epe); ext_snprintf_P(editpath, sizeof(editpath), UFS_FORM_SDC_HREFedit, ppe, epe);
@ -857,12 +1054,16 @@ void UfsListDir(char *path, uint8_t depth) {
} }
#ifdef ESP32 #ifdef ESP32
#define ESP32_DOWNLOAD_TASK // this actually does not work reliably, as the
// webserver can close the connection before the download task completes
//#define ESP32_DOWNLOAD_TASK
#endif // ESP32 #endif // ESP32
uint8_t UfsDownloadFile(char *file) { uint8_t UfsDownloadFile(char *file) {
File download_file; File download_file;
AddLog(LOG_LEVEL_INFO, PSTR("UFS: File '%s' download"), file);
if (!dfsp->exists(file)) { if (!dfsp->exists(file)) {
AddLog(LOG_LEVEL_INFO, PSTR("UFS: File '%s' not found"), file); AddLog(LOG_LEVEL_INFO, PSTR("UFS: File '%s' not found"), file);
return 0; return 0;
@ -875,6 +1076,7 @@ uint8_t UfsDownloadFile(char *file) {
} }
if (download_file.isDirectory()) { if (download_file.isDirectory()) {
AddLog(LOG_LEVEL_DEBUG, PSTR("UFS: File '%s' to download is directory"), file);
download_file.close(); download_file.close();
return 1; return 1;
} }
@ -887,13 +1089,7 @@ uint8_t UfsDownloadFile(char *file) {
Webserver->setContentLength(flen); Webserver->setContentLength(flen);
char attachment[100]; char attachment[100];
char *cp; char *cp = fileOnly(file);
for (uint32_t cnt = strlen(file); cnt >= 0; cnt--) {
if (file[cnt] == '/') {
cp = &file[cnt + 1];
break;
}
}
snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=%s"), cp); snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=%s"), cp);
Webserver->sendHeader(F("Content-Disposition"), attachment); Webserver->sendHeader(F("Content-Disposition"), attachment);
WSSend(200, CT_APP_STREAM, ""); WSSend(200, CT_APP_STREAM, "");
@ -922,6 +1118,10 @@ uint8_t UfsDownloadFile(char *file) {
#endif // ESP32_DOWNLOAD_TASK #endif // ESP32_DOWNLOAD_TASK
// to make this work I thing you wouold need to duplicate the client
// BEFORE starting the task, so that the webserver does not close it's
// copy of the client.
#ifdef ESP32_DOWNLOAD_TASK #ifdef ESP32_DOWNLOAD_TASK
download_file.close(); download_file.close();
@ -952,20 +1152,18 @@ void download_task(void *path) {
WiFiClient download_Client; WiFiClient download_Client;
char *file = (char*) path; char *file = (char*) path;
AddLog(LOG_LEVEL_DEBUG, PSTR("UFS: ESP32 File '%s' to download"), file);
download_file = dfsp->open(file, UFS_FILE_READ); download_file = dfsp->open(file, UFS_FILE_READ);
uint32_t flen = download_file.size(); uint32_t flen = download_file.size();
AddLog(LOG_LEVEL_DEBUG, PSTR("UFS: len %d to download"), flen);
download_Client = Webserver->client(); download_Client = Webserver->client();
Webserver->setContentLength(flen); Webserver->setContentLength(flen);
char attachment[100]; char attachment[100];
char *cp; char *cp = fileOnly(file);
for (uint32_t cnt = strlen(file); cnt >= 0; cnt--) {
if (file[cnt] == '/') {
cp = &file[cnt + 1];
break;
}
}
//snprintf_P(attachment, sizeof(attachment), PSTR("download file '%s' as '%s'"), file, cp); //snprintf_P(attachment, sizeof(attachment), PSTR("download file '%s' as '%s'"), file, cp);
//Webserver->sendHeader(F("X-Tasmota-Debug"), attachment); //Webserver->sendHeader(F("X-Tasmota-Debug"), attachment);
snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=%s"), cp); snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=%s"), cp);
@ -987,6 +1185,7 @@ void download_task(void *path) {
UfsData.download_busy = false; UfsData.download_busy = false;
vTaskDelete( NULL ); vTaskDelete( NULL );
free(path); free(path);
AddLog(LOG_LEVEL_DEBUG, PSTR("UFS: esp32 sent file"));
} }
#endif // ESP32_DOWNLOAD_TASK #endif // ESP32_DOWNLOAD_TASK
@ -1065,7 +1264,8 @@ void UfsEditor(void) {
} }
WSContentSend_P(HTTP_EDITOR_FORM_END); WSContentSend_P(HTTP_EDITOR_FORM_END);
WSContentSend_P(UFS_WEB_DIR, PSTR(D_MANAGE_FILE_SYSTEM)); folderOnly(fname);
WSContentSend_P(UFS_WEB_DIR, fname, PSTR(D_MANAGE_FILE_SYSTEM));
WSContentStop(); WSContentStop();
} }
@ -1100,6 +1300,23 @@ void UfsEditorUpload(void) {
return; return;
} }
// recursively create folder(s)
char tmp[UFS_FILENAME_SIZE];
char folder[UFS_FILENAME_SIZE] = "";
strcpy(tmp, fname);
// zap file name off the end
folderOnly(tmp);
char *tf = strtok(tmp, "/");
while(tf){
if (*tf){
strcat(folder, "/");
strcat(folder, tf);
}
// we don;t care if it fails - it may already exist.
dfsp->mkdir(folder);
tf = strtok(nullptr, "/");
}
File fp = dfsp->open(fname, "w"); File fp = dfsp->open(fname, "w");
if (!fp) { if (!fp) {
Web.upload_error = 1; Web.upload_error = 1;
@ -1119,7 +1336,11 @@ void UfsEditorUpload(void) {
fp.close(); fp.close();
Webserver->sendHeader(F("Location"),F("/ufsu")); // zap file name off the end
folderOnly(fname);
char t[20+UFS_FILENAME_SIZE] = "/ufsu?download=";
strcat(t, fname);
Webserver->sendHeader(F("Location"), t);
Webserver->send(303); Webserver->send(303);
} }
@ -1160,7 +1381,7 @@ bool Xdrv50(uint32_t function) {
if (XdrvMailbox.index) { if (XdrvMailbox.index) {
XdrvMailbox.index++; XdrvMailbox.index++;
} else { } else {
WSContentSend_PD(UFS_WEB_DIR, PSTR(D_MANAGE_FILE_SYSTEM)); WSContentSend_PD(UFS_WEB_DIR, "/", PSTR(D_MANAGE_FILE_SYSTEM));
} }
} }
break; break;