added JSON support to select the services
This commit is contained in:
parent
d9d0643dfc
commit
1e9a377c2a
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
cmake-build-debug
|
||||
cmake-build-release
|
||||
build
|
||||
|
||||
# JetBrains IDEs
|
||||
.idea/
|
||||
!.idea/codeStyles/
|
||||
!.idea/runConfigurations/
|
||||
!.idea/inspectionProfiles/
|
||||
*.iml
|
||||
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
@ -81,6 +81,12 @@
|
||||
"typeinfo": "cpp",
|
||||
"valarray": "cpp",
|
||||
"variant": "cpp",
|
||||
"*.ipp": "cpp"
|
||||
"*.ipp": "cpp",
|
||||
"expected": "cpp",
|
||||
"queue": "cpp",
|
||||
"stack": "cpp",
|
||||
"strstream": "cpp",
|
||||
"complex": "cpp",
|
||||
"typeindex": "cpp"
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(CrowHTMX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Allow selection of build type if not set
|
||||
@ -32,7 +32,21 @@ endforeach()
|
||||
# Use Crow from system include (installed via yay -S crow + asio)
|
||||
include_directories(/usr/include)
|
||||
|
||||
add_executable(app main.cpp)
|
||||
add_executable(app src/main.cpp
|
||||
src/htmx/HtmxTable.cpp
|
||||
src/htmx/HtmxTable.h
|
||||
src/systemd.cpp
|
||||
src/systemd.h
|
||||
src/json.hpp
|
||||
src/htmx/HtmxTableRow.cpp
|
||||
src/htmx/HtmxTableRow.h
|
||||
src/htmx/HtmxObject.cpp
|
||||
src/htmx/HtmxObject.h
|
||||
src/htmx_helper.cpp
|
||||
src/htmx_helper.h
|
||||
src/json_settings.cpp
|
||||
src/json_settings.h
|
||||
src/json.hpp)
|
||||
|
||||
target_link_libraries(app pthread)
|
||||
|
||||
|
||||
136
main.cpp
136
main.cpp
@ -1,136 +0,0 @@
|
||||
#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();
|
||||
}
|
||||
11
src/htmx/HtmxObject.cpp
Normal file
11
src/htmx/HtmxObject.cpp
Normal file
@ -0,0 +1,11 @@
|
||||
//
|
||||
// Created by lukas on 5/11/25.
|
||||
//
|
||||
|
||||
#include "HtmxObject.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
const string& HtmxObject::htmx() const {
|
||||
return html;
|
||||
}
|
||||
28
src/htmx/HtmxObject.h
Normal file
28
src/htmx/HtmxObject.h
Normal file
@ -0,0 +1,28 @@
|
||||
//
|
||||
// Created by lukas on 5/11/25.
|
||||
//
|
||||
|
||||
#ifndef HTMXOBJECT_H
|
||||
#define HTMXOBJECT_H
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
class HtmxObject {
|
||||
public:
|
||||
|
||||
HtmxObject() = default;
|
||||
|
||||
/**
|
||||
* Get the HTMX representation of the object as a string
|
||||
* @return htmx string
|
||||
*/
|
||||
const std::string& htmx() const;
|
||||
|
||||
protected:
|
||||
std::string html;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif //HTMXOBJECT_H
|
||||
17
src/htmx/HtmxTable.cpp
Normal file
17
src/htmx/HtmxTable.cpp
Normal file
@ -0,0 +1,17 @@
|
||||
//
|
||||
// Created by lukas on 5/11/25.
|
||||
//
|
||||
|
||||
#include <format>
|
||||
#include "HtmxTable.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
void HtmxTable::add_row(const HtmxTableRow& row){
|
||||
html += row.htmx();
|
||||
}
|
||||
|
||||
void HtmxTable::complete() {
|
||||
html += "</table>";
|
||||
}
|
||||
|
||||
40
src/htmx/HtmxTable.h
Normal file
40
src/htmx/HtmxTable.h
Normal file
@ -0,0 +1,40 @@
|
||||
//
|
||||
// Created by lukas on 5/11/25.
|
||||
//
|
||||
|
||||
#ifndef HTMXTABLE_H
|
||||
#define HTMXTABLE_H
|
||||
|
||||
#include "HtmxObject.h"
|
||||
#include "HtmxTableRow.h"
|
||||
#include "format"
|
||||
|
||||
class HtmxTable : public HtmxObject {
|
||||
|
||||
public:
|
||||
template<std::ranges::input_range R>
|
||||
requires std::convertible_to<std::ranges::range_value_t<R>, std::string_view>
|
||||
HtmxTable(const R& strings) {
|
||||
// define the table header
|
||||
html = "<table><tr>";
|
||||
for (const auto& s : strings) {
|
||||
html += format("<th>{}</th>", s);
|
||||
}
|
||||
html += "</tr>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a htmx row to the table
|
||||
*
|
||||
* @param row
|
||||
* @return htmx representation of the row
|
||||
*/
|
||||
void add_row(const HtmxTableRow& row);
|
||||
|
||||
/**
|
||||
* Complete the table
|
||||
*/
|
||||
void complete();
|
||||
|
||||
};
|
||||
#endif //HTMXTABLE_H
|
||||
35
src/htmx/HtmxTableRow.cpp
Normal file
35
src/htmx/HtmxTableRow.cpp
Normal file
@ -0,0 +1,35 @@
|
||||
//
|
||||
// Created by lukas on 5/11/25.
|
||||
//
|
||||
#include <format>
|
||||
#include "HtmxTableRow.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
void HtmxTableRow::add_button(string_view endpoint, string_view name, string_view text) {
|
||||
html += format("<td>\
|
||||
<button\
|
||||
hx-post=\"{}\"\
|
||||
hx-vals='{{\"name\":\"{}\"}}'\
|
||||
hx-target=\"closest tr\"\
|
||||
hx-swap=\"outerHTML\">\
|
||||
{} \
|
||||
</button> \
|
||||
</td>", endpoint, name, text);
|
||||
}
|
||||
|
||||
static string_view get_button_class(bool is_active) {
|
||||
return is_active ? "active-button" : "inactive-button";
|
||||
}
|
||||
|
||||
void HtmxTableRow::add_status_box(std::string_view name, bool is_active) {
|
||||
html += format("<td class='{}'>{}</td>", get_button_class(is_active), name);
|
||||
}
|
||||
|
||||
void HtmxTableRow::add(string_view text) {
|
||||
html += format("<td>{}</td>", text);
|
||||
}
|
||||
|
||||
void HtmxTableRow::complete() {
|
||||
html += "</tr>";
|
||||
}
|
||||
28
src/htmx/HtmxTableRow.h
Normal file
28
src/htmx/HtmxTableRow.h
Normal file
@ -0,0 +1,28 @@
|
||||
//
|
||||
// Created by lukas on 5/11/25.
|
||||
//
|
||||
|
||||
#ifndef HTMXTABLEROW_H
|
||||
#define HTMXTABLEROW_H
|
||||
|
||||
#include <string>
|
||||
#include "HtmxObject.h"
|
||||
|
||||
class HtmxTableRow : public HtmxObject {
|
||||
|
||||
public:
|
||||
void add_status_box(std::string_view name, bool is_active);
|
||||
|
||||
void add_button(std::string_view endpoint, std::string_view name, std::string_view text);
|
||||
|
||||
void add(std::string_view text);
|
||||
|
||||
/**
|
||||
* Complete the row
|
||||
*/
|
||||
void complete();
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif //HTMXTABLEROW_H
|
||||
59
src/htmx_helper.cpp
Normal file
59
src/htmx_helper.cpp
Normal file
@ -0,0 +1,59 @@
|
||||
//
|
||||
// Created by lukas on 5/11/25.
|
||||
//
|
||||
#include <array>
|
||||
#include "systemd.h"
|
||||
#include "htmx_helper.h"
|
||||
#include "json_settings.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
HtmxTableRow create_service_table_row(string_view service_name, string_view service_id) {
|
||||
HtmxTableRow row;
|
||||
|
||||
const bool isRunning = systemd::is_service_active(service_id);
|
||||
const bool isEnabled = systemd::is_service_active(service_id);
|
||||
|
||||
const auto running = isRunning ? "Running" : "Stopped";
|
||||
const auto enabled = isEnabled ? "Enabled" : "Disabled";
|
||||
|
||||
row.add(service_name);
|
||||
row.add_status_box(running, isRunning);
|
||||
row.add_status_box(enabled, isEnabled);
|
||||
|
||||
// create buttons
|
||||
row.add_button("/toggle-service", service_name, isRunning ? "Stop" : "Start");
|
||||
row.add_button("/restart-service", service_name, "Restart");
|
||||
row.complete();
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
HtmxTableRow create_error_table_row(string_view error) {
|
||||
HtmxTableRow row;
|
||||
row.add("Error");
|
||||
row.add(error);
|
||||
return row;
|
||||
}
|
||||
|
||||
HtmxTable create_service_table() {
|
||||
constexpr array<string_view, 3> cols = {
|
||||
"Service", "Status", "State"
|
||||
};
|
||||
|
||||
HtmxTable table(cols);
|
||||
|
||||
auto settings = AppSettings::loadAppSettings();
|
||||
|
||||
if(settings.has_value()){
|
||||
AppSettings& data = settings.value();
|
||||
for (auto& service : data.services) {
|
||||
table.add_row(create_service_table_row(service.name, service.service));
|
||||
}
|
||||
}
|
||||
else {
|
||||
table.add_row(create_error_table_row(settings.error()));
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
15
src/htmx_helper.h
Normal file
15
src/htmx_helper.h
Normal file
@ -0,0 +1,15 @@
|
||||
//
|
||||
// Created by lukas on 5/11/25.
|
||||
//
|
||||
|
||||
#ifndef HTMX_HELPER_H
|
||||
#define HTMX_HELPER_H
|
||||
|
||||
#include <string>
|
||||
#include "htmx/HtmxTable.h"
|
||||
|
||||
HtmxTableRow create_service_table_row(std::string_view service_name, std::string_view service_id);
|
||||
HtmxTableRow create_error_table_row(std::string_view error);
|
||||
HtmxTable create_service_table();
|
||||
|
||||
#endif //HTMX_HELPER_H
|
||||
25580
src/json.hpp
Normal file
25580
src/json.hpp
Normal file
File diff suppressed because it is too large
Load Diff
54
src/json_settings.cpp
Normal file
54
src/json_settings.cpp
Normal file
@ -0,0 +1,54 @@
|
||||
#include <fstream>
|
||||
#include <format>
|
||||
#include "json.hpp"
|
||||
#include "json_settings.h"
|
||||
|
||||
using namespace std;
|
||||
using json = nlohmann::json;
|
||||
|
||||
expected<AppSettings, string> AppSettings::loadAppSettings() {
|
||||
constexpr char settings_file[] = "static/settings.json";
|
||||
ifstream file(settings_file);
|
||||
if (!file.is_open()) {
|
||||
return unexpected(format("Failed to open {}",settings_file));
|
||||
}
|
||||
|
||||
// Parse the JSON
|
||||
json j;
|
||||
try {
|
||||
file >> j;
|
||||
} catch (const json::parse_error& e) {
|
||||
return unexpected(format("Failed to parse JSON {}",e.what()));
|
||||
}
|
||||
|
||||
if (!j.contains("services") || !j["services"].is_array()) {
|
||||
return unexpected("JSON does not contain an array called 'services'");
|
||||
}
|
||||
|
||||
AppSettings settings;
|
||||
|
||||
for (const auto& item : j["services"]) {
|
||||
if (item.is_array() && item.size() == 2) {
|
||||
Service service = {
|
||||
item[0].get<std::string>(),
|
||||
item[1].get<std::string>()
|
||||
};
|
||||
settings.services.push_back(service);
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.services.empty()){
|
||||
return unexpected("'services' array in JSON file is empty");
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
optional<const std::string&> AppSettings::getId(string_view name){
|
||||
for (auto& service : services) {
|
||||
if(service.name == name) {
|
||||
return service.service;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
22
src/json_settings.h
Normal file
22
src/json_settings.h
Normal file
@ -0,0 +1,22 @@
|
||||
#ifndef JSON_SETTINGS_H
|
||||
#define JSON_SETTINGS_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <expected>
|
||||
#include <optional>
|
||||
|
||||
struct Service {
|
||||
std::string name;
|
||||
std::string service;
|
||||
};
|
||||
|
||||
struct AppSettings {
|
||||
static std::expected<AppSettings, std::string> loadAppSettings();
|
||||
|
||||
std::optional<const std::string&> getId(std::string_view name);
|
||||
|
||||
std::vector<Service> services;
|
||||
};
|
||||
|
||||
#endif // JSON_SETTINGS_H
|
||||
72
src/main.cpp
Normal file
72
src/main.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
#include <crow.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include "json_settings.h"
|
||||
#include "htmx_helper.h"
|
||||
#include "systemd.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
string load_file(const string& path) {
|
||||
ifstream f(path);
|
||||
stringstream buffer;
|
||||
buffer << f.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 table = create_service_table();
|
||||
return crow::response{table.htmx()};
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
auto opt_settings = AppSettings::loadAppSettings();
|
||||
|
||||
HtmxTableRow row;
|
||||
if (opt_settings.has_value()) {
|
||||
auto& settings = opt_settings.value();
|
||||
const auto& service_id = settings.getId(serviceName).value_or(serviceName);
|
||||
|
||||
systemd::toggle_service(service_id);
|
||||
row = create_service_table_row(serviceName, service_id);
|
||||
}
|
||||
else {
|
||||
row = create_error_table_row(opt_settings.error());
|
||||
}
|
||||
return crow::response{row.htmx()};
|
||||
});
|
||||
|
||||
app.port(8080).multithreaded().run();
|
||||
}
|
||||
26
src/systemd.cpp
Normal file
26
src/systemd.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
//
|
||||
// Created by lukas on 5/11/25.
|
||||
//
|
||||
|
||||
#include "format"
|
||||
#include "systemd.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace systemd {
|
||||
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());
|
||||
}
|
||||
}
|
||||
34
src/systemd.h
Normal file
34
src/systemd.h
Normal file
@ -0,0 +1,34 @@
|
||||
//
|
||||
// Created by lukas on 5/11/25.
|
||||
//
|
||||
|
||||
#ifndef SYSTEMD_H
|
||||
#define SYSTEMD_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace systemd {
|
||||
/**
|
||||
* Check if a service is active
|
||||
* @param service_name name of the systemd service
|
||||
* @return
|
||||
*/
|
||||
bool is_service_active(std::string_view service_name);
|
||||
|
||||
/**
|
||||
* Check if a service is enabled
|
||||
* @param service_name name of the systemd service
|
||||
* @return
|
||||
*/
|
||||
bool is_service_enabled(std::string_view service_name);
|
||||
|
||||
/**
|
||||
* Toggle the service on or off dependent on its current state
|
||||
* @param service_name name of the systemd service
|
||||
*/
|
||||
void toggle_service(std::string_view service_name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif //SYSTEMD_H
|
||||
6
static/settings.json
Normal file
6
static/settings.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"services": [
|
||||
["Cups", "cups.service"],
|
||||
["Waydriod", "waydroid-container.service"]
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user