first commit

This commit is contained in:
Lukas Forsberg 2025-05-11 14:16:05 +02:00
commit d9d0643dfc
9 changed files with 379 additions and 0 deletions

24
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,24 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Crow App",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/app",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty printing",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}

86
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,86 @@
{
"files.associations": {
"any": "cpp",
"array": "cpp",
"atomic": "cpp",
"hash_map": "cpp",
"bit": "cpp",
"*.tcc": "cpp",
"bitset": "cpp",
"cctype": "cpp",
"charconv": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"codecvt": "cpp",
"compare": "cpp",
"concepts": "cpp",
"condition_variable": "cpp",
"coroutine": "cpp",
"csignal": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"forward_list": "cpp",
"list": "cpp",
"map": "cpp",
"set": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"vector": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"ratio": "cpp",
"source_location": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"format": "cpp",
"fstream": "cpp",
"future": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"numbers": "cpp",
"ostream": "cpp",
"ranges": "cpp",
"semaphore": "cpp",
"shared_mutex": "cpp",
"span": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"stdfloat": "cpp",
"stop_token": "cpp",
"streambuf": "cpp",
"text_encoding": "cpp",
"thread": "cpp",
"cfenv": "cpp",
"cinttypes": "cpp",
"typeinfo": "cpp",
"valarray": "cpp",
"variant": "cpp",
"*.ipp": "cpp"
}
}

14
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,14 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "shell",
"command": "cmake -S . -B build && cmake --build build",
"group": "build",
"problemMatcher": [
"$gcc"
]
}
]
}

40
CMakeLists.txt Normal file
View File

@ -0,0 +1,40 @@
cmake_minimum_required(VERSION 3.10)
project(CrowHTMX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Allow selection of build type if not set
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE)
endif()
# Supported build types
set(SUPPORTED_BUILD_TYPES Debug Release RelWithDebInfo MinSizeRel)
if(NOT CMAKE_BUILD_TYPE IN_LIST SUPPORTED_BUILD_TYPES)
message(FATAL_ERROR "Unsupported build type: ${CMAKE_BUILD_TYPE}")
endif()
# Copy 'static' and 'templates' directories to build directory
file(GLOB_RECURSE STATIC_FILES "${CMAKE_SOURCE_DIR}/static/*")
file(GLOB_RECURSE TEMPLATE_FILES "${CMAKE_SOURCE_DIR}/templates/*")
foreach(file IN LISTS STATIC_FILES)
file(RELATIVE_PATH rel_path "${CMAKE_SOURCE_DIR}" "${file}")
configure_file("${file}" "${CMAKE_BINARY_DIR}/${rel_path}" COPYONLY)
endforeach()
foreach(file IN LISTS TEMPLATE_FILES)
file(RELATIVE_PATH rel_path "${CMAKE_SOURCE_DIR}" "${file}")
configure_file("${file}" "${CMAKE_BINARY_DIR}/${rel_path}" COPYONLY)
endforeach()
# Use Crow from system include (installed via yay -S crow + asio)
include_directories(/usr/include)
add_executable(app main.cpp)
target_link_libraries(app pthread)
# Optional: Print build type at configuration time
message(STATUS "Configuring build type: ${CMAKE_BUILD_TYPE}")

10
README.md Normal file
View File

@ -0,0 +1,10 @@
## Setup
pacman -S crow asio gdb gcc cmake make
## Build
### vscode
build : Ctrl+Shift+P → "CMake: Configure"
run : F5 or Run → Start Debugging

136
main.cpp Normal file
View File

@ -0,0 +1,136 @@
#include <crow.h>
#include <fstream>
#include <sstream>
#include <cstdlib>
#include <string>
#include <array>
#include <format>
#include <optional>
using namespace std;
string load_file(const string& path) {
ifstream f(path);
stringstream buffer;
buffer << f.rdbuf();
return buffer.str();
}
bool is_service_active(string_view service_name) {
const string cmd = format("systemctl is-active --quiet {}", service_name);
return system(cmd.c_str()) == 0;
}
bool is_service_enabled(string_view service_name) {
const string cmd = format("systemctl is-enabled --quiet {}", service_name);
return system(cmd.c_str()) == 0;
}
void toggle_service(string_view serviceName){
string_view toggle = is_service_active(serviceName) ? "stop" : "start";
const string cmd = format("systemctl {} {}", toggle, serviceName);
system(cmd.c_str());
}
string_view get_button_class(bool isActive) {
return isActive ? "active-button" : "inactive-button";
}
optional<string> get_body_name(const string& body) {
const auto pos = body.find('=');
if (pos == std::string::npos) return {};
const string key = body.substr(0, pos);
string value = body.substr(pos + 1);
if (key != "name") return {};
return value;
}
string create_htmx_button(string_view endpoint, string_view serviceName, string_view text) {
return format(
"<td>\
<button\
hx-post=\"{}\"\
hx-vals='{{\"name\":\"{}\"}}'\
hx-target=\"closest tr\"\
hx-swap=\"outerHTML\">\
{} \
</button> \
</td>", endpoint, serviceName, text);
}
string create_htmx_table_row(string_view serviceName){
const bool isRunning = is_service_active(serviceName);
const bool isEnabled = is_service_active(serviceName);
const auto running = isRunning ? "Running" : "Stopped";
const auto enabled = isEnabled ? "Enabled" : "Disabled";
// create status indicators
string html = format(
"<tr>\
<td>{}</td>\
<td class='{}'>{}</td>\
<td class='{}'>{}</td>",
serviceName, get_button_class(isRunning), running, get_button_class(isEnabled), enabled);
// create buttons
html += create_htmx_button("/toggle-service", serviceName, isRunning ? "Stop" : "Start");
html += create_htmx_button("/enable-service", serviceName, isEnabled ? "Disable" : "Enable");
html += create_htmx_button("/restart-service", serviceName, "Restart");
return html;
}
const array<string, 1>& get_service_names() {
static const array<string, 1> arr = {
"cups.service"
};
return arr;
}
int main() {
crow::SimpleApp app;
CROW_ROUTE(app, "/")([] {
return crow::response(load_file("templates/index.html"));
});
CROW_ROUTE(app, "/static/<string>")([](const std::string& file) {
return crow::response(load_file("static/" + file));
});
CROW_ROUTE(app, "/status")([] {
auto names = get_service_names();
// define the table header
string html = "<table><tr>\
<th>Service</th>\
<th>Status</th>\
<th>State</th>\
</tr>";
// display each service as an entry
for (auto& serviceName : names) {
html += create_htmx_table_row(serviceName);
}
html += " </tr></table>";
return crow::response{html};
});
CROW_ROUTE(app, "/toggle-service").methods(crow::HTTPMethod::Post)([](const crow::request& req) {
auto body = get_body_name(req.body);
if (!body.has_value())
return crow::response(400);
const string& serviceName = body.value();
toggle_service(serviceName);
const string html = create_htmx_table_row(serviceName);
return crow::response{html};
});
app.port(8080).multithreaded().run();
}

18
service-viewer/PKGBUILD Normal file
View File

@ -0,0 +1,18 @@
pkgname=service-viewer
pkgver=1.0.0
pkgrel=1
arch=('x86_64')
depends=('crow') # add any libraries you need
pkgdesc="A C++ Crow/HTMX web service"
license=('MIT')
source=('src/')
md5sums=('SKIP') # SKIP if local files
build() {
# No build needed if already compiled
return 0
}
package() {
install -Dm755 "$srcdir/src/yourapp" "$pkgdir/usr/bin/yourapp"
install -Dm644 "$srcdir/src/assets/index.html" "$pkgdir/usr/share/yourapp/index.html"
install -Dm644 "$srcdir/src/assets/style.css" "$pkgdir/usr/share/yourapp/style.css"
}

1
static/htmx.min.js vendored Normal file

File diff suppressed because one or more lines are too long

50
templates/index.html Normal file
View File

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html>
<head>
<script src="/static/htmx.min.js"></script>
<title>Service Status</title>
<style>
.active-button {
background-color: #4CAF50; /* Green */
color: white;
border: none;
padding: 10px 20px;
text-align: center;
cursor: pointer;
border-radius: 5px;
}
.inactive-button {
background-color: #f44336; /* Red */
color: white;
border: none;
padding: 10px 20px;
text-align: center;
cursor: pointer;
border-radius: 5px;
}
body {
margin: 0;
height: 100vh; /* Full screen height */
display: flex;
justify-content: center; /* Horizontal centering */
align-items: center; /* Vertical centering */
font-family: sans-serif;
}
.column {
display: flex;
flex-direction: column;
align-items: center; /* Optional: center items within the column */
gap: 1rem; /* Space between elements */
}
</style>
</head>
<body>
<div class="column">
<h1>Service Status</h1>
<div id="services" hx-get="/status" hx-trigger="load, every 5s" hx-swap="innerHTML">
Loading services...
</div>
</div>
</body>
</html>