Some checks are pending
build-and-push / image (push) Waiting to run
- Added response presets for standup, bug report, and ship update in the Slack sidecar. - Improved UI elements including a copy button for replies and conversation memory. - Enhanced diagnostics section with better health indicators and tool listings. - Introduced performance metrics for response times in the Slack form. feat: revamp system pane for better process and host introspection - Updated system pane to include live sparklines for memory and CPU usage. - Enhanced KPI display with uptime, memory usage, and CPU load metrics. - Added functionality to capture and display configured services and their statuses. feat: overhaul tools pane for improved usability and interaction - Redesigned tools pane to allow filtering, favoriting, and invoking MCP tools. - Introduced a history feature to track recent tool calls. - Enhanced form handling for tool arguments with schema-driven inputs. feat: new files pane for accessing platform data - Implemented a new files pane to surface data from files.cxllm.io. - Added tabs for providers, edge functions, health, demand runs, watcher events, and file manager categories. - Included functionality to capture health snapshots and display recent data.
144 lines
4.9 KiB
C++
144 lines
4.9 KiB
C++
#include "crow.h"
|
|
#include "routes.h"
|
|
#include <cstdlib>
|
|
#include <fstream>
|
|
#include <initializer_list>
|
|
#include <sstream>
|
|
#include <string>
|
|
|
|
namespace routes {
|
|
|
|
namespace {
|
|
|
|
std::string static_dir() {
|
|
if (const char* env = std::getenv("CXWEBAPP_STATIC_DIR")) {
|
|
return std::string(env);
|
|
}
|
|
#ifdef CXWEBAPP_STATIC_DIR_DEFAULT
|
|
return std::string(CXWEBAPP_STATIC_DIR_DEFAULT);
|
|
#else
|
|
return "static";
|
|
#endif
|
|
}
|
|
|
|
std::string read_file(const std::string& path) {
|
|
std::ifstream ifs(path, std::ios::binary);
|
|
if (!ifs.is_open()) return "";
|
|
std::ostringstream ss;
|
|
ss << ifs.rdbuf();
|
|
return ss.str();
|
|
}
|
|
|
|
std::string mime_for(const std::string& path) {
|
|
auto ends = [&](const char* s) {
|
|
size_t n = std::char_traits<char>::length(s);
|
|
return path.size() >= n && path.compare(path.size() - n, n, s) == 0;
|
|
};
|
|
if (ends(".html")) return "text/html; charset=utf-8";
|
|
if (ends(".css")) return "text/css; charset=utf-8";
|
|
if (ends(".js")) return "application/javascript; charset=utf-8";
|
|
if (ends(".mjs")) return "application/javascript; charset=utf-8";
|
|
if (ends(".worker.js")) return "application/javascript; charset=utf-8";
|
|
if (ends(".wasm")) return "application/wasm";
|
|
if (ends(".json")) return "application/json; charset=utf-8";
|
|
if (ends(".data")) return "application/octet-stream";
|
|
if (ends(".svg")) return "image/svg+xml";
|
|
if (ends(".png")) return "image/png";
|
|
if (ends(".jpg") || ends(".jpeg")) return "image/jpeg";
|
|
if (ends(".gif")) return "image/gif";
|
|
if (ends(".ico")) return "image/x-icon";
|
|
if (ends(".woff2")) return "font/woff2";
|
|
if (ends(".woff")) return "font/woff";
|
|
if (ends(".map")) return "application/json; charset=utf-8";
|
|
if (ends(".txt")) return "text/plain; charset=utf-8";
|
|
return "application/octet-stream";
|
|
}
|
|
|
|
bool is_safe(const std::string& path) {
|
|
if (path.empty()) return false;
|
|
if (path.front() == '/') return false;
|
|
if (path.find("..") != std::string::npos) return false;
|
|
if (path.find('\0') != std::string::npos) return false;
|
|
return true;
|
|
}
|
|
|
|
bool has_segment(const std::string& path, const std::string& segment) {
|
|
return path == segment || path.rfind(segment + "/", 0) == 0 ||
|
|
path.find("/" + segment + "/") != std::string::npos;
|
|
}
|
|
|
|
std::string cache_for(const std::string& rel) {
|
|
if (rel == "index.html" || rel == "service-catalog.json" || rel == "wasm/modules.json") {
|
|
return "no-cache, max-age=0, must-revalidate";
|
|
}
|
|
if (has_segment(rel, "assets") || has_segment(rel, "wasm")) {
|
|
return "public, max-age=31536000, immutable";
|
|
}
|
|
return "public, max-age=300";
|
|
}
|
|
|
|
std::string join_path(std::initializer_list<std::string> parts) {
|
|
std::string joined;
|
|
for (const auto& part : parts) {
|
|
if (joined.empty()) {
|
|
joined = part;
|
|
} else {
|
|
joined += "/" + part;
|
|
}
|
|
}
|
|
return joined;
|
|
}
|
|
|
|
crow::response serve_static(const std::string& rel) {
|
|
if (!is_safe(rel)) return crow::response(400, "bad path");
|
|
const std::string full = static_dir() + "/" + rel;
|
|
auto body = read_file(full);
|
|
if (body.empty()) return crow::response(404, "not found");
|
|
crow::response resp(body);
|
|
resp.add_header("Content-Type", mime_for(rel));
|
|
resp.add_header("Cache-Control", cache_for(rel));
|
|
return resp;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
void register_pages(crow::SimpleApp& app) {
|
|
|
|
CROW_ROUTE(app, "/")
|
|
([]() { return serve_static("index.html"); });
|
|
|
|
CROW_ROUTE(app, "/favicon.ico")
|
|
([]() { return serve_static("favicon.svg"); });
|
|
|
|
// Crow's `<string>` matches a single segment. Register enough depth for Vite and Wasm side files.
|
|
CROW_ROUTE(app, "/static/<string>")
|
|
([](const std::string& a) { return serve_static(a); });
|
|
|
|
CROW_ROUTE(app, "/static/<string>/<string>")
|
|
([](const std::string& a, const std::string& b) {
|
|
return serve_static(a + "/" + b);
|
|
});
|
|
|
|
CROW_ROUTE(app, "/static/<string>/<string>/<string>")
|
|
([](const std::string& a, const std::string& b, const std::string& c) {
|
|
return serve_static(join_path({a, b, c}));
|
|
});
|
|
|
|
CROW_ROUTE(app, "/static/<string>/<string>/<string>/<string>")
|
|
([](const std::string& a, const std::string& b, const std::string& c, const std::string& d) {
|
|
return serve_static(join_path({a, b, c, d}));
|
|
});
|
|
|
|
CROW_ROUTE(app, "/static/<string>/<string>/<string>/<string>/<string>")
|
|
([](const std::string& a, const std::string& b, const std::string& c, const std::string& d, const std::string& e) {
|
|
return serve_static(join_path({a, b, c, d, e}));
|
|
});
|
|
|
|
CROW_ROUTE(app, "/static/<string>/<string>/<string>/<string>/<string>/<string>")
|
|
([](const std::string& a, const std::string& b, const std::string& c, const std::string& d, const std::string& e, const std::string& f) {
|
|
return serve_static(join_path({a, b, c, d, e, f}));
|
|
});
|
|
}
|
|
|
|
} // namespace routes
|