From 1e0a2ebc369d8c30565ba7a9c6989b8b6974251e Mon Sep 17 00:00:00 2001 From: Oliver Giles Date: Sat, 26 Sep 2015 22:54:27 +0200 Subject: [PATCH] frontend love --- CMakeLists.txt | 2 +- laminar.conf | 7 ++ src/laminar.cpp | 117 ++++++++++++--------- src/resources.cpp | 1 - src/resources/index.html | 11 +- src/resources/js/app.js | 184 +++++++++++++++++++++++----------- src/resources/tpl/browse.html | 17 ++-- src/resources/tpl/home.html | 14 +-- src/resources/tpl/job.html | 56 ++++++++--- src/resources/tpl/log.html | 8 -- src/resources/tpl/run.html | 38 +++++-- 11 files changed, 304 insertions(+), 151 deletions(-) delete mode 100644 src/resources/tpl/log.html diff --git a/CMakeLists.txt b/CMakeLists.txt index e667f26..782c84c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,7 +57,7 @@ add_custom_command(OUTPUT laminar.capnp.c++ laminar.capnp.h # Zip and compile statically served resources generate_compressed_bins(${CMAKE_SOURCE_DIR}/src/resources index.html js/app.js - tpl/home.html tpl/job.html tpl/run.html tpl/log.html tpl/browse.html + tpl/home.html tpl/job.html tpl/run.html tpl/browse.html favicon.ico favicon-152.png icon.png) # Download 3rd-party frontend JS libs... file(DOWNLOAD https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js diff --git a/laminar.conf b/laminar.conf index fdf1254..ec88bfe 100644 --- a/laminar.conf +++ b/laminar.conf @@ -8,6 +8,13 @@ ### #LAMINAR_HOME=/var/lib/laminar +### +### LAMINAR_TITLE +### +### Page title to show in web frontend +### +#LAMINAR_TITLE= + ### ### LAMINAR_KEEP_WORKDIR ### diff --git a/src/laminar.cpp b/src/laminar.cpp index 29d79d3..81eda88 100644 --- a/src/laminar.cpp +++ b/src/laminar.cpp @@ -118,48 +118,52 @@ void Laminar::sendStatus(LaminarClient* client) { client->sendMessage(log); }); } - } else if(client->scope.type == MonitorScope::RUN) { - Json j; - j.set("type", "status"); - j.startObject("data"); - db->stmt("SELECT startedAt, result, reason FROM builds WHERE name = ? AND number = ?") + return; + } + + Json j; + j.set("type", "status"); + j.startObject("data"); + j.set("title", getenv("LAMINAR_TITLE") ?: ""); + if(client->scope.type == MonitorScope::RUN) { + db->stmt("SELECT queuedAt,startedAt,completedAt, result, reason FROM builds WHERE name = ? AND number = ?") .bind(client->scope.job, client->scope.num) - .fetch([&](time_t started, int result, std::string reason) { + .fetch([&](time_t queued, time_t started, time_t completed, int result, std::string reason) { + j.set("queued", started-queued); j.set("started", started); + j.set("completed", completed); + j.set("duration", completed-started); j.set("result", to_string(RunState(result))); j.set("reason", reason); }); + j.set("latestNum", int(buildNums[client->scope.job])); j.startArray("artifacts"); fs::path dir(fs::path(homeDir)/"archive"/client->scope.job/std::to_string(client->scope.num)); - fs::recursive_directory_iterator rdt(dir); - int prefixLen = (fs::path(homeDir)/"archive").string().length(); - int scopeLen = dir.string().length(); - for(fs::directory_entry e : rdt) { - if(!fs::is_regular_file(e)) - continue; - j.StartObject(); - j.set("url", archiveUrl + e.path().string().substr(prefixLen)); - j.set("filename", e.path().string().substr(scopeLen+1)); - j.EndObject(); + if(fs::is_directory(dir)) { + fs::recursive_directory_iterator rdt(dir); + int prefixLen = (fs::path(homeDir)/"archive").string().length(); + int scopeLen = dir.string().length(); + for(fs::directory_entry e : rdt) { + if(!fs::is_regular_file(e)) + continue; + j.StartObject(); + j.set("url", archiveUrl + e.path().string().substr(prefixLen)); + j.set("filename", e.path().string().substr(scopeLen+1)); + j.EndObject(); + } } j.EndArray(); - j.EndObject(); - client->sendMessage(j.str()); } else if(client->scope.type == MonitorScope::JOB) { - Json j; - j.set("type", "status"); - j.startObject("data"); j.startArray("recent"); - db->stmt("SELECT * FROM builds WHERE name = ? ORDER BY completedAt DESC LIMIT 5") + db->stmt("SELECT number,startedAt,completedAt,result,reason FROM builds WHERE name = ? ORDER BY completedAt DESC LIMIT 25") .bind(client->scope.job) - .fetch([&](str name,int build,str node,time_t,time_t started,time_t completed,int result){ + .fetch([&](int build,time_t started,time_t completed,int result,str reason){ j.StartObject(); - j.set("name", name) - .set("number", build) - .set("node", node) + j.set("number", build) .set("duration", completed - started) .set("started", started) .set("result", to_string(RunState(result))) + .set("reason", reason) .EndObject(); }); j.EndArray(); @@ -168,45 +172,52 @@ void Laminar::sendStatus(LaminarClient* client) { for(auto it = p.first; it != p.second; ++it) { const std::shared_ptr run = *it; j.StartObject(); - j.set("name", run->name); j.set("number", run->build); j.set("node", run->node->name); j.set("started", run->startedAt); j.EndObject(); } j.EndArray(); - j.startArray("queued"); + int nQueued = 0; for(const std::shared_ptr run : queuedJobs) { if (run->name == client->scope.job) { - j.StartObject(); - j.set("name", run->name); - j.EndObject(); + nQueued++; } } - j.EndArray(); - j.EndObject(); - client->sendMessage(j.str()); + j.set("nQueued", nQueued); + db->stmt("SELECT number,startedAt FROM builds WHERE name = ? AND result = ? ORDER BY completedAt DESC LIMIT 1") + .bind(client->scope.job, int(RunState::SUCCESS)) + .fetch([&](int build, time_t started){ + j.startObject("lastSuccess"); + j.set("number", build).set("started", started); + j.EndObject(); + }); + db->stmt("SELECT number,startedAt FROM builds WHERE name = ? AND result <> ? ORDER BY completedAt DESC LIMIT 1") + .bind(client->scope.job, int(RunState::SUCCESS)) + .fetch([&](int build, time_t started){ + j.startObject("lastFailed"); + j.set("number", build).set("started", started); + j.EndObject(); + }); } else if(client->scope.type == MonitorScope::ALL) { - Json j; - j.set("type", "status"); - j.startObject("data"); j.startArray("jobs"); - db->stmt("SELECT name FROM builds GROUP BY name") - .fetch([&](str name){ + db->stmt("SELECT name,number,startedAt,result FROM builds GROUP BY name ORDER BY number DESC") + .fetch([&](str name,int number, time_t started, int result){ j.StartObject(); - j.set("name", name) - .EndObject(); + j.set("name", name); + j.set("number", number).set("result", to_string(RunState(result))).set("started", started); + j.startArray("tags"); + for(const str& t: jobTags[name]) { + j.String(t.c_str()); + } + j.EndArray(); + j.EndObject(); }); j.EndArray(); - j.EndObject(); - client->sendMessage(j.str()); } else { // Home page - Json j; - j.set("type", "status"); - j.startObject("data"); j.startArray("recent"); - db->stmt("SELECT * FROM builds ORDER BY completedAt DESC LIMIT 5") + db->stmt("SELECT * FROM builds ORDER BY completedAt DESC LIMIT 15") .fetch([&](str name,int build,str node,time_t,time_t started,time_t completed,int result){ j.StartObject(); j.set("name", name) @@ -262,10 +273,18 @@ void Laminar::sendStatus(LaminarClient* client) { j.set(job.c_str(), count); }); j.EndObject(); - + j.startObject("timePerJob"); + db->stmt("SELECT name, AVG(completedAt-startedAt) FROM builds WHERE completedAt > ? GROUP BY name") + .bind(time(0) - 7 * 86400) + .fetch([&](str job, int time){ + j.set(job.c_str(), time); + }); j.EndObject(); - client->sendMessage(j.str()); + } + j.EndObject(); + client->sendMessage(j.str()); + } Laminar::~Laminar() { diff --git a/src/resources.cpp b/src/resources.cpp index c2ef685..692b8e5 100644 --- a/src/resources.cpp +++ b/src/resources.cpp @@ -37,7 +37,6 @@ Resources::Resources() INIT_RESOURCE("/tpl/home.html", tpl_home_html); INIT_RESOURCE("/tpl/job.html", tpl_job_html); INIT_RESOURCE("/tpl/run.html", tpl_run_html); - INIT_RESOURCE("/tpl/log.html", tpl_log_html); INIT_RESOURCE("/tpl/browse.html", tpl_browse_html); INIT_RESOURCE("/js/angular.min.js", js_angular_min_js); INIT_RESOURCE("/js/angular-route.min.js", js_angular_route_min_js); diff --git a/src/resources/index.html b/src/resources/index.html index 8384288..4a43146 100644 --- a/src/resources/index.html +++ b/src/resources/index.html @@ -17,11 +17,14 @@ + + + +
{{job.name}} #{{job.number}}{{formatDate(job.started)}}
diff --git a/src/resources/tpl/home.html b/src/resources/tpl/home.html index 644db97..c98e518 100644 --- a/src/resources/tpl/home.html +++ b/src/resources/tpl/home.html @@ -1,7 +1,6 @@
-

Recent Builds

@@ -13,16 +12,15 @@ - +
{{job.name}} queued
{{job.name}} #{{job.number}}
Took {{job.duration}} at {{job.when}}
{{job.name}} #{{job.number}}
Took {{job.duration}}s at {{formatDate(job.started)}}
-

Dashboard

-
Builds per day
+
Total builds per day this week
@@ -38,16 +36,18 @@
-
Current executor utilization
+
Average build time per job this week
- +
+
-
what to put here?
+
Current executor utilization
+
diff --git a/src/resources/tpl/job.html b/src/resources/tpl/job.html index b129848..99470e6 100644 --- a/src/resources/tpl/job.html +++ b/src/resources/tpl/job.html @@ -1,22 +1,48 @@
-
-

{{name}}

- - - - - - - - - - -
queued
#{{job.number}} progressbar?
#{{job.number}}
+ +
+

{{name}}

+
+
Last Successful Run
+ #{{lastSuccess.number}} + {{lastSuccess?" - at "+formatDate(lastSuccess.started):"never"}}
+
Last Failed Run
+ #{{lastFailed.number}} + {{lastFailed?" - at "+formatDate(lastFailed.started):"never"}}
+
+ +
+
+
+
Build time
+
+ +
+
+
+
-
+
+ +
+ + + + + + + + + + + + + + +
RunStartedDuration
{{nQueued}} run(s) queued
#{{job.number}} progressbar?
#{{job.number}}{{formatDate(job.started)}}{{job.duration + " seconds"}}
+
-
diff --git a/src/resources/tpl/log.html b/src/resources/tpl/log.html deleted file mode 100644 index 0a0b67c..0000000 --- a/src/resources/tpl/log.html +++ /dev/null @@ -1,8 +0,0 @@ -
-
-
-

Log output for {{name}} #{{num}}

-

-
-
-
diff --git a/src/resources/tpl/run.html b/src/resources/tpl/run.html index 405816d..c700811 100644 --- a/src/resources/tpl/run.html +++ b/src/resources/tpl/run.html @@ -1,14 +1,40 @@
-
+
+

{{name}} #{{num}}

+ +
-

{{name}} #{{num}}

-
< Job
Log output
-
 
Reason
{{job.reason}}
-
Started
{{job.when}}
-
Artifacts
+
Queued for
{{job.queued}}s
+
Started
{{formatDate(job.started)}}
+
Completed
{{formatDate(job.completed)}}
+
Duration
{{job.duration}}s
+
+
+
Artifacts
+
+ +
+
+
+
+
+
+ +

Console output

+

+