Web interface: first try of API server + javascript based starting/stopping of instances

This commit is contained in:
Jan Kaluza 2016-01-19 14:33:45 +01:00
parent a71462650e
commit b8926b4f0c
6 changed files with 100 additions and 5 deletions

View file

@ -12,6 +12,12 @@
#include <string>
#include <cerrno>
#define ALLOW_ONLY_ADMIN() if (!session->admin) { \
std::string _json = "{\"error\":1, \"message\": \"Only administrators can do this API call.\"}"; \
send_json(conn, _json); \
return; \
}
static std::string get_http_var(const struct http_message *hm, const char *name) {
char data[4096];
data[0] = '\0';
@ -29,6 +35,11 @@ static std::string get_http_var(const struct http_message *hm, const char *name)
return "";
}
static int has_prefix(const struct mg_str *uri, const char *prefix) {
size_t prefix_len = strlen(prefix);
return uri->len >= prefix_len && memcmp(uri->p, prefix, prefix_len) == 0;
}
APIServer::APIServer(ManagerConfig *config, StorageBackend *storage) {
m_config = config;
m_storage = storage;
@ -37,7 +48,15 @@ APIServer::APIServer(ManagerConfig *config, StorageBackend *storage) {
APIServer::~APIServer() {
}
std::string &APIServer::safe_arg(std::string &arg) {
boost::replace_all(arg, "\n", "");
boost::replace_all(arg, "\"", "'");
return arg;
}
void APIServer::send_json(struct mg_connection *conn, const std::string &json) {
std::cout << "Sending JSON:\n";
std::cout << json << "\n";
mg_printf(conn,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/json\r\n"
@ -70,7 +89,13 @@ void APIServer::serve_instances(Server *server, Server::session *session, struct
else if (*(status.end() - 1) == '\n') {
status.erase(status.end() - 1);
}
json += "\"status\":\"" + status + "\"";
json += "\"status\":\"" + safe_arg(status) + "\",";
bool running = true;
if (status.find("Running") == std::string::npos) {
running = false;
}
json += "\"running\":" + (running ? std::string("1") : std::string("0"));
json += "},";
}
json.erase(json.end() - 1);
@ -79,8 +104,42 @@ void APIServer::serve_instances(Server *server, Server::session *session, struct
send_json(conn, json);
}
void APIServer::serve_instances_start(Server *server, Server::session *session, struct mg_connection *conn, struct http_message *hm) {
ALLOW_ONLY_ADMIN();
std::string uri(hm->uri.p, hm->uri.len);
std::string instance = uri.substr(uri.rfind("/") + 1);
start_instances(m_config, instance);
std::string response = get_response();
std::string error = response.find("OK") == std::string::npos ? "1" : "0";
std::string json = "{\"error\":" + error + ", \"message\": \"" + safe_arg(response) + "\"}";
// TODO: So far it needs some time to reload Spectrum 2, so just sleep here.
sleep(1);
send_json(conn, json);
}
void APIServer::serve_instances_stop(Server *server, Server::session *session, struct mg_connection *conn, struct http_message *hm) {
ALLOW_ONLY_ADMIN();
std::string uri(hm->uri.p, hm->uri.len);
std::string instance = uri.substr(uri.rfind("/") + 1);
stop_instances(m_config, instance);
std::string response = get_response();
std::string error = response.find("OK") == std::string::npos ? "1" : "0";
std::string json = "{\"error\":" + error + ", \"message\": \"" + safe_arg(response) + "\"}"; \
send_json(conn, json);
}
void APIServer::handleRequest(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm) {
if (mg_vcmp(&hm->uri, "/api/v1/instances") == 0) {
if (has_prefix(&hm->uri, "/api/v1/instances/start/")) {
serve_instances_start(server, sess, conn, hm);
}
else if (has_prefix(&hm->uri, "/api/v1/instances/stop/")) {
serve_instances_stop(server, sess, conn, hm);
}
else if (mg_vcmp(&hm->uri, "/api/v1/instances") == 0) {
serve_instances(server, sess, conn, hm);
}
}

View file

@ -50,6 +50,9 @@ class APIServer {
private:
void serve_instances(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
void serve_instances_start(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
void serve_instances_stop(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
std::string &safe_arg(std::string &);
void send_json(struct mg_connection *conn, const std::string &json);
private:

View file

@ -7,7 +7,10 @@
<link href="/style.css" rel="stylesheet" type="text/css" media="all">
<link href="/form.css" rel="stylesheet" type="text/css" media="all">
<link href="https://code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css" rel="stylesheet" type="text/css" media="all">
<script src="https://code.jquery.com/jquery-2.2.0.min.js"></script>
<script src="https://code.jquery.com/ui/1.11.4/jquery-ui.min.js"></script>
<script src="/js/app.js"></script>
<title>Spectrum 2</title>
</head>
@ -20,7 +23,7 @@
<img id="logo" src="/logo.png" style="width:250px; margin-left: auto;margin-right: auto; display:block;">
<section id="menu" style="text-align: center;">
<a class="menuitem" href="/">Instances</a>
<a class="menuitem" href="/instances">Instances</a>
<a class="menuitem" href="/users">Users</a>
<a class="menuitem" href="/logout">Logout</a>
</section>
@ -30,3 +33,4 @@
<!-- MAIN CONTENT -->
<div id="main_content_wrap" class="outer">
<section id="main_content" class="inner">

View file

@ -0,0 +1,9 @@
<!--#include virtual="/header.html" -->
<script type="text/javascript">
$(function() {
show_instances();
});
</script>
<!--#include virtual="/footer.html" -->

View file

@ -0,0 +1,20 @@
function show_instances() {
$.get("/api/v1/instances", function(data) {
$("#main_content").html("<h2>List of Spectrum 2 instances</h2><table id='main_result'><tr><th>Hostname<th>Status</th><th>Command</th></tr>");
$.each(data.instances, function(i, instance) {
var command = instance.running ? "stop" : "start";
var row = '<tr><td>'+ instance.name + '</td><td>' +
instance.status + '</td><td><a class="button_command" href="/api/v1/instances/' +
command + '/' + instance.id + '">' + command + '</a>' + '</td></tr>';
$("#main_result > tbody:last-child").append(row);
$(".button_command").click(function(e) {
e.preventDefault();
$(this).parent().empty().progressbar( {value: false} ).css('height', '1em');
var url = $(this).attr('href');
$.get(url, function(data) {
show_instances();
});
})
});
});
}

View file

@ -180,7 +180,7 @@ void Server::authorize(struct mg_connection *conn, struct http_message *hm) {
"Set-Cookie: user=%s\r\n" // Set user, needed by Javascript code
"Set-Cookie: admin=%s\r\n" // Set user, needed by Javascript code
"Set-Cookie: original_url=/; max-age=0\r\n" // Delete original_url
"Location: /\r\n\r\n",
"Location: /instances\r\n\r\n",
session->session_id, session->user, session->admin ? "1" : "0");
} else {
// Authentication failure, redirect to login.