diff --git a/app/src/routes/menu.svelte b/app/src/routes/menu.svelte
index e16a10e..d7dc740 100644
--- a/app/src/routes/menu.svelte
+++ b/app/src/routes/menu.svelte
@@ -5,6 +5,7 @@
import Settings from '~icons/mdi/settings';
import MdiController from '~icons/mdi/controller';
import Health from '~icons/mdi/stethoscope';
+ import Folder from '~icons/mdi/folder-outline';
import Update from '~icons/mdi/reload';
import WiFi from '~icons/mdi/wifi';
import Router from '~icons/mdi/router';
@@ -111,6 +112,13 @@
feature: true,
},
+ {
+ title: 'File System',
+ icon: Folder,
+ href: '/system/filesystem',
+ feature: true,
+
+ },
{
title: 'System Metrics',
icon: Metrics,
diff --git a/app/src/routes/system/filesystem/+page.svelte b/app/src/routes/system/filesystem/+page.svelte
new file mode 100644
index 0000000..96f6ac0
--- /dev/null
+++ b/app/src/routes/system/filesystem/+page.svelte
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/app/src/routes/system/filesystem/+page.ts b/app/src/routes/system/filesystem/+page.ts
new file mode 100644
index 0000000..d6444c1
--- /dev/null
+++ b/app/src/routes/system/filesystem/+page.ts
@@ -0,0 +1,5 @@
+import type { PageLoad } from './$types';
+
+export const load = (async () => {
+ return { title: 'File System' };
+}) satisfies PageLoad;
diff --git a/app/src/routes/system/filesystem/File.svelte b/app/src/routes/system/filesystem/File.svelte
new file mode 100644
index 0000000..e72d78a
--- /dev/null
+++ b/app/src/routes/system/filesystem/File.svelte
@@ -0,0 +1,7 @@
+
+
+{name}
diff --git a/app/src/routes/system/filesystem/FileSystem.svelte b/app/src/routes/system/filesystem/FileSystem.svelte
new file mode 100644
index 0000000..34f70eb
--- /dev/null
+++ b/app/src/routes/system/filesystem/FileSystem.svelte
@@ -0,0 +1,24 @@
+
+
+
+ File System
+
+ {#await getFiles()}
+
+ {:then files}
+
+ {/await}
+
+
\ No newline at end of file
diff --git a/app/src/routes/system/filesystem/Folder.svelte b/app/src/routes/system/filesystem/Folder.svelte
new file mode 100644
index 0000000..dc6f1ad
--- /dev/null
+++ b/app/src/routes/system/filesystem/Folder.svelte
@@ -0,0 +1,36 @@
+
+
+
+
+{#if expanded}
+
+ {#each Object.entries(files) as [name, content]}
+ -
+ {#if typeof content == 'object'}
+
+ {:else}
+
+ {/if}
+
+ {/each}
+
+{/if}
diff --git a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp
index e4f6969..18f77d2 100644
--- a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp
+++ b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp
@@ -52,7 +52,8 @@ ESP32SvelteKit::ESP32SvelteKit(PsychicHttpServer *server, unsigned int numberEnd
#endif
_restartService(server, &_securitySettingsService),
_factoryResetService(server, &ESPFS, &_securitySettingsService),
- _systemStatus(server, &_securitySettingsService) {
+ _systemStatus(server, &_securitySettingsService),
+ _fileExplorer(server, &_securitySettingsService){
}
void ESP32SvelteKit::begin() {
@@ -178,8 +179,8 @@ void ESP32SvelteKit::startServices() {
#if FT_ENABLED(FT_BATTERY)
_batteryService.begin();
#endif
-
_taskManager.begin();
+ _fileExplorer.begin();
}
void ESP32SvelteKit::_loop() {
diff --git a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h
index 2e4e746..e65fd09 100644
--- a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h
+++ b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h
@@ -23,6 +23,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -153,6 +154,11 @@ public:
return &_taskManager;
}
+ FileExplorer *getFileExplorer()
+ {
+ return &_fileExplorer;
+ }
+
void factoryReset()
{
_factoryResetService.factoryReset();
@@ -209,6 +215,7 @@ private:
FactoryResetService _factoryResetService;
SystemStatus _systemStatus;
TaskManager _taskManager;
+ FileExplorer _fileExplorer;
String _appName = APP_NAME;
diff --git a/esp32/lib/ESP32-sveltekit/FileExplorerService.h b/esp32/lib/ESP32-sveltekit/FileExplorerService.h
new file mode 100644
index 0000000..3194a9f
--- /dev/null
+++ b/esp32/lib/ESP32-sveltekit/FileExplorerService.h
@@ -0,0 +1,72 @@
+#ifndef FileExplorer_h
+#define FileExplorer_h
+
+#include
+#include
+#include
+
+#define FILE_EXPLORER_SERVICE_PATH "/api/files/list"
+
+class FileExplorer
+{
+ public:
+ FileExplorer(PsychicHttpServer *server, SecurityManager *securityManager)
+ : _server(server), _securityManager(securityManager)
+ {
+ }
+
+ void begin()
+ {
+ _server->on(FILE_EXPLORER_SERVICE_PATH, HTTP_GET,
+ _securityManager->wrapRequest(std::bind(&FileExplorer::explore, this, std::placeholders::_1),
+ AuthenticationPredicates::IS_AUTHENTICATED));
+
+ ESP_LOGV("APStatus", "Registered GET endpoint: %s", FILE_EXPLORER_SERVICE_PATH);
+ }
+
+ private:
+ PsychicHttpServer *_server;
+ SecurityManager *_securityManager;
+ esp_err_t explore(PsychicRequest *request)
+ {
+ return request->reply(200, "application/json", listFiles("/").c_str());
+ }
+
+ String listFiles(const String &directory, bool isRoot = true)
+ {
+ File root = ESPFS.open(directory.startsWith("/") ? directory : "/" + directory);
+ if (!root.isDirectory())
+ {
+ return "";
+ }
+
+ File file = root.openNextFile();
+ String output = isRoot ? "{ \"root\": {" : "{";
+
+ while (file)
+ {
+ if (file.isDirectory())
+ {
+ output += "\"" + String(file.name()) + "\": " + listFiles(file.name(), false) + ", ";
+ }
+ else
+ {
+ output += "\"" + String(file.name()) + "\": " + String(file.size()) + ", ";
+ }
+ file = root.openNextFile();
+ }
+
+ if (output.endsWith(", "))
+ {
+ output.remove(output.length() - 2);
+ }
+ output += "}";
+ if (isRoot)
+ {
+ output += "}";
+ }
+ return output;
+ }
+};
+
+#endif // end FileExplorer_h