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