feat: add frontend app, improve static file serving with lazy loading and root redirect

This commit is contained in:
2026-07-20 21:03:48 +03:00
parent 799e939037
commit d1e96d35aa
27 changed files with 2053 additions and 14 deletions
+1 -1
View File
@@ -2,5 +2,5 @@
"port": 8000,
"pipelines_dir": "./storage/pipelines/",
"logs_dir": "./storage/logs/",
"web_root": ""
"web_root": "../../frontend/build/web/"
}
+27 -13
View File
@@ -81,6 +81,11 @@ bool Server::start() {
void Server::serveStaticFiles(const PIString & webRoot) {
PIDir rootDir(webRoot);
if (!rootDir.isExists()) {
piCout << "WARNING: web_root does not exist: " << webRoot;
return;
}
piCout << "Serving static files from: " << rootDir.absolutePath();
const auto filelist = PIDir::allEntries(webRoot);
@@ -88,23 +93,22 @@ void Server::serveStaticFiles(const PIString & webRoot) {
if (!finfo.isFile()) continue;
if (finfo.name().startsWith('.')) continue;
PIString relPath = finfo.dir().removeAll(webRoot) + finfo.name();
if (relPath.isEmpty() || relPath[0] != '/') {
relPath = "/" + relPath;
// Compute relative path, same logic as meshbackend
PIString wpath = finfo.dir().removeAll(rootDir.absolutePath()) + finfo.name();
if (wpath.isEmpty() || wpath[0] != '/') {
wpath = "/" + wpath;
}
PIString ext = finfo.extension().toLowerCase();
PIString contentType = StaticContentTypes.value(ext, "application/octet-stream");
PIByteArray fileData = PIFile::readAll(finfo.path);
PIString contentType = StaticContentTypes.value(finfo.extension().toLowerCase(), "application/octet-stream");
PIByteArray fileData = PIFile::readAll(rootDir.absolutePath() + wpath);
PIString etag = PIDigest::calculate(fileData, PIDigest::Type::BLAKE2s_128).toHex().quote();
piCout << " Registered: " << relPath << " (" << contentType << ", ETag: " << etag << ")";
piCout << " Registered: " << wpath << " (" << contentType << ", " << fileData.size() << " bytes)";
auto handler = [fileData, contentType, etag](const PIHTTP::MessageConst & request) {
auto handler = [wpath, rootLocation = rootDir.absolutePath(), contentType, etag](const PIHTTP::MessageConst & request) {
PIHTTP::MessageMutable msg;
msg.addHeader(PIHTTP::Header::ContentType, contentType);
msg.addHeader(PIHTTP::Header::CacheControl, "max-age=3600");
msg.addHeader(PIHTTP::Header::CacheControl, "max-age=100");
msg.addHeader(PIHTTP::Header::ETag, etag);
PIString clientEtag = request.headers().value(PIHTTP::Header::IfNoneMatch, PIString());
@@ -113,12 +117,20 @@ void Server::serveStaticFiles(const PIString & webRoot) {
return msg;
}
msg.setBody(fileData);
msg.setBody(PIFile::readAll(rootLocation + wpath));
return msg;
};
httpserver_->registerPath(relPath, PIHTTP::Method::Get, handler);
httpserver_->registerPath(wpath, PIHTTP::Method::Get, handler);
}
// Redirect "/" to "/index.html" (same as meshbackend)
httpserver_->registerPath("/", PIHTTP::Method::Get, [](const PIHTTP::MessageConst &) {
PIHTTP::MessageMutable msg;
msg.setCode(PIHTTP::Code::MovedPermanently);
msg.addHeader("Location", "/index.html");
return msg;
});
}
PIHTTP::MessageMutable Server::listPipelines(const PIHTTP::MessageConst & request) {
@@ -327,5 +339,7 @@ PIHTTP::MessageMutable Server::getRunLog(const PIHTTP::MessageConst & request) {
PIHTTP::MessageMutable Server::unhandledRequest(const PIHTTP::MessageConst & request) {
piCout << "Unhandled: " << PIHTTP::methodName(request.method()) << " " << request.path();
return MessageUtils::errorReply(PIHTTP::Code::NotFound, "Not found");
PIHTTP::MessageMutable msg;
msg.setCode(PIHTTP::Code::NotFound);
return msg;
}