diff --git a/src/interface.h b/src/interface.h index d3a48b8..a8a1559 100644 --- a/src/interface.h +++ b/src/interface.h @@ -60,6 +60,7 @@ struct MonitorScope { Type type; std::string job; uint num = 0; + uint page = 0; }; // Represents a (websocket) client that wants to be notified about events diff --git a/src/laminar.cpp b/src/laminar.cpp index 713566d..4105f50 100644 --- a/src/laminar.cpp +++ b/src/laminar.cpp @@ -195,9 +195,10 @@ void Laminar::sendStatus(LaminarClient* client) { populateArtifacts(j, client->scope.job, client->scope.num); j.EndArray(); } else if(client->scope.type == MonitorScope::JOB) { + const uint runsPerPage = 10; j.startArray("recent"); - db->stmt("SELECT number,startedAt,completedAt,result,reason FROM builds WHERE name = ? ORDER BY completedAt DESC LIMIT 25") - .bind(client->scope.job) + db->stmt("SELECT number,startedAt,completedAt,result,reason FROM builds WHERE name = ? ORDER BY completedAt DESC LIMIT ?,?") + .bind(client->scope.job, client->scope.page * runsPerPage, runsPerPage) .fetch([&](uint build,time_t started,time_t completed,int result,str reason){ j.StartObject(); j.set("number", build) @@ -208,6 +209,11 @@ void Laminar::sendStatus(LaminarClient* client) { .EndObject(); }); j.EndArray(); + db->stmt("SELECT COUNT(*) FROM builds WHERE name = ?") + .bind(client->scope.job) + .fetch([&](uint nRuns){ + j.set("pages", (nRuns-1) / runsPerPage + 1); + }); j.startArray("running"); auto p = activeJobs.byJobName().equal_range(client->scope.job); for(auto it = p.first; it != p.second; ++it) { diff --git a/src/resources/index.html b/src/resources/index.html index 95905be..571d02f 100644 --- a/src/resources/index.html +++ b/src/resources/index.html @@ -189,6 +189,11 @@ {{job.reason}} + diff --git a/src/resources/js/app.js b/src/resources/js/app.js index 13eb2b1..2c4e6bf 100644 --- a/src/resources/js/app.js +++ b/src/resources/js/app.js @@ -21,8 +21,10 @@ const WebsocketHandler = function() { // "status" is the first message the websocket always delivers. // Use this to confirm the navigation. The component is not // created until next() is called, so creating a reference - // for other message types must be deferred - if (msg.type === 'status') { + // for other message types must be deferred. Also check that + // the reference is not already created, this allows a subsequent + // status message to be handled as an update. + if (msg.type === 'status' && !this.comp) { next(comp => { // Set up bidirectional reference // 1. needed to reference the component for other msg types @@ -394,6 +396,8 @@ var Job = function() { lastSuccess: null, lastFailed: null, nQueued: 0, + pages: 0, + page: 0 }; return Vue.extend({ template: '#job', @@ -408,6 +412,7 @@ var Job = function() { state.lastSuccess = msg.lastSuccess; state.lastFailed = msg.lastFailed; state.nQueued = msg.nQueued; + state.pages = msg.pages; var chtBt = new Chart(document.getElementById("chartBt").getContext("2d")).Bar({ labels: msg.recent.map(function(e) { @@ -454,6 +459,12 @@ var Job = function() { break; } } + }, + page_next: function() { + this.ws.send(JSON.stringify({page:++state.page})); + }, + page_prev: function() { + this.ws.send(JSON.stringify({page:--state.page})); } } }); diff --git a/src/server.cpp b/src/server.cpp index 419418c..f7c5018 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -36,6 +36,8 @@ #include #include +#include + // Size of buffer used to read from file descriptors. Should be // a multiple of sizeof(struct signalfd_siginfo) == 128 #define PROC_IO_BUFSIZE 4096 @@ -213,11 +215,18 @@ public: // wss.set_access_channels(websocketpp::log::alevel::all); // wss.set_error_channels(websocketpp::log::elevel::all); - // TODO: This could be used in the future to trigger actions on the - // server in response to a web client request. Currently not supported. - // wss.set_message_handler([](std::weak_ptr s, websocket::message_ptr msg){ - // msg->get_payload(); - // }); + // Handle incoming websocket message + wss.set_message_handler([this](websocketpp::connection_hdl hdl, websocket::message_ptr msg){ + websocket::connection_ptr c = wss.get_con_from_hdl(hdl); + std::string payload = msg->get_payload(); + rapidjson::Document d; + d.ParseInsitu(const_cast(payload.data())); + if(d.HasMember("page") && d["page"].IsInt()) { + int page = d["page"].GetInt(); + c->lc->scope.page = page; + laminar.sendStatus(c->lc); + } + }); // Handle plain HTTP requests by delivering the binary resource wss.set_http_handler([this](websocketpp::connection_hdl hdl){