first commit
This commit is contained in:
commit
d9d0643dfc
24
.vscode/launch.json
vendored
Normal file
24
.vscode/launch.json
vendored
Normal 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
86
.vscode/settings.json
vendored
Normal 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
14
.vscode/tasks.json
vendored
Normal 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
40
CMakeLists.txt
Normal 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
10
README.md
Normal 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
136
main.cpp
Normal 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
18
service-viewer/PKGBUILD
Normal 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
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
50
templates/index.html
Normal 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>
|
||||
Loading…
x
Reference in New Issue
Block a user