changeset 41:00b9af607524

scid: implement /jobresults/id page
author David Demelier <markand@malikania.fr>
date Tue, 09 Aug 2022 14:52:34 +0200
parents 752bb1cd2dd8
children 4076b07c7a6f
files Makefile examples/irccd.sh scid/db.h scid/http.c scid/page-jobresults.c scid/page-jobresults.d scid/page-jobresults.h scid/page-jobresults.o scid/theme.c scid/theme.h themes/bulma/header.mustache themes/bulma/index.mustache themes/bulma/jobresults.mustache themes/bulma/static/scid.css themes/bulma/theme.js
diffstat 15 files changed, 292 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Sun Aug 07 08:43:32 2022 +0200
+++ b/Makefile	Tue Aug 09 14:52:34 2022 +0200
@@ -84,6 +84,7 @@
                         scid/page-api-todo.c \
                         scid/page-api-workers.c \
                         scid/page-index.c \
+                        scid/page-jobresults.c \
                         scid/page-static.c \
                         scid/pageutil.c \
                         scid/scid.c \
--- a/examples/irccd.sh	Sun Aug 07 08:43:32 2022 +0200
+++ b/examples/irccd.sh	Tue Aug 09 14:52:34 2022 +0200
@@ -23,7 +23,7 @@
 	;;
 esac
 
-echo "=> Cloning repository $repo (revision $1) into $wkrdir"
+echo "=> Cloning repository $repo (revision $1) into $wrkdir"
 hg clone -r "$1" "$repo" "$wrkdir"
 cd "$wrkdir"
 
--- a/scid/db.h	Sun Aug 07 08:43:32 2022 +0200
+++ b/scid/db.h	Tue Aug 09 14:52:34 2022 +0200
@@ -113,7 +113,7 @@
  * \return the JSON model or NULL on failure
  */
 json_t *
-db_jobresult_list_by_job_group(intmax_t);
+db_jobresult_list_by_job_group(intmax_t job_id);
 
 /**
  * Get a list of job results realized by this worker.
--- a/scid/http.c	Sun Aug 07 08:43:32 2022 +0200
+++ b/scid/http.c	Tue Aug 09 14:52:34 2022 +0200
@@ -34,14 +34,16 @@
 #include "page-api-todo.h"
 #include "page-api-workers.h"
 #include "page-index.h"
+#include "page-jobresults.h"
 #include "page-static.h"
 #include "pageutil.h"
 
 enum page {
-	PAGE_INDEX,     /* Job results at index. */
+	PAGE_INDEX,             /* Job results at index. */
+	PAGE_JOBRESULTS,        /* List of jobresult for one job. */
 	PAGE_API,
 	PAGE_STATIC,
-	PAGE_LAST       /* Not used. */
+	PAGE_LAST               /* Not used. */
 };
 
 static void
@@ -68,12 +70,14 @@
 
 static const char *pages[] = {
 	[PAGE_INDEX]            = "",
+	[PAGE_JOBRESULTS]       = "jobresults",
 	[PAGE_API]              = "api",
 	[PAGE_STATIC]           = "static"
 };
 
 static void (*handlers[])(struct kreq *req) = {
 	[PAGE_INDEX]            = page_index,
+	[PAGE_JOBRESULTS]       = page_jobresults,
 	[PAGE_API]              = dispatch_api,
 	[PAGE_STATIC]           = page_static
 };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scid/page-jobresults.c	Tue Aug 09 14:52:34 2022 +0200
@@ -0,0 +1,72 @@
+/*
+ * page-jobresults.c -- page /jobresults/<id> route
+ *
+ * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+
+#include "db.h"
+#include "page-jobresults.h"
+#include "pageutil.h"
+#include "theme.h"
+#include "util.h"
+
+static void
+list(struct kreq *r, intmax_t id)
+{
+	json_t *results, *doc;
+	char *data;
+
+	if (!(results = db_jobresult_list_by_job(id)))
+		pageutil_status(r, KHTTP_404);
+	else {
+		doc = util_json_pack("{sI so}",
+		    "job_id", (json_int_t)id,
+		    "jobresults", results
+		);
+		data = theme_page_jobresults(doc);
+		pageutil_render(r, KHTTP_200, KMIME_TEXT_HTML, data);
+		json_decref(doc);
+		free(data);
+	}
+}
+
+static void
+get(struct kreq *r)
+{
+	intmax_t id;
+
+	if (sscanf(r->fullpath, "/jobresults/%jd", &id) != 1)
+		pageutil_status(r, KHTTP_400);
+	else
+		list(r, id);
+}
+
+void
+page_jobresults(struct kreq *r)
+{
+	(void)r;
+
+	switch (r->method) {
+	case KMETHOD_GET:
+		get(r);
+		break;
+	default:
+		pageutil_status(r, KHTTP_400);
+		break;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scid/page-jobresults.d	Tue Aug 09 14:52:34 2022 +0200
@@ -0,0 +1,6 @@
+scid/page-jobresults.o: scid/page-jobresults.c scid/db.h \
+  /usr/local/Cellar/jansson/2.14/include/jansson.h \
+  /usr/local/Cellar/jansson/2.14/include/jansson_config.h \
+  scid/page-jobresults.h scid/pageutil.h \
+  /usr/local/Cellar/kcgi/0.13.0/include/kcgi.h scid/theme.h \
+  libsci/util.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scid/page-jobresults.h	Tue Aug 09 14:52:34 2022 +0200
@@ -0,0 +1,40 @@
+/*
+ * page-jobresults.h -- page /jobresults/<id> route
+ *
+ * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef SCI_PAGE_JOBRESULTS_H
+#define SCI_PAGE_JOBRESULTS_H
+
+/**
+ * \file page-jobresults.h
+ * \brief Page /jobresults/id route.
+ *
+ * This module does not log messages.
+ */
+
+struct kreq;
+
+/**
+ * Run the page.
+ *
+ * \pre r != NULL
+ * \param r the request
+ */
+void
+page_jobresults(struct kreq *r);
+
+#endif /* !SCI_PAGE_JOBRESULTS_H */
Binary file scid/page-jobresults.o has changed
--- a/scid/theme.c	Sun Aug 07 08:43:32 2022 +0200
+++ b/scid/theme.c	Tue Aug 09 14:52:34 2022 +0200
@@ -360,6 +360,14 @@
 }
 
 char *
+theme_page_jobresults(const json_t *json)
+{
+	assert(json);
+
+	return call(json, "onPageJobresults");
+}
+
+char *
 theme_page_status(int status, const char *message)
 {
 	json_t *doc;
--- a/scid/theme.h	Sun Aug 07 08:43:32 2022 +0200
+++ b/scid/theme.h	Tue Aug 09 14:52:34 2022 +0200
@@ -99,6 +99,35 @@
 theme_page_index(const json_t *doc);
 
 /**
+ * Render the jobresults page.
+ *
+ * The document requires the following properties:
+ *
+ * ```
+ * {
+ *   "jobresults": [
+ *     {
+ *       "id": jobresult id,
+ *       "job_id": parent job id,
+ *       "worker_name": "worker which realized the task",
+ *       "console": "stdout and stderr merge",
+ *       "exitcode": process exit code,
+ *       "sigcode": process termination code (0 means success),
+ *       "date": job result insertion date
+ *     }
+ *   ]
+ * }
+ * ```
+ *
+ * \pre doc != NULL
+ * \param doc the page document
+ * \return a newly allocated rendered string
+ * \note You must free the return value
+ */
+char *
+theme_page_jobresults(const json_t *doc);
+
+/**
  * Render the status page (for error code).
  *
  * The document requires the following properties:
--- a/themes/bulma/header.mustache	Sun Aug 07 08:43:32 2022 +0200
+++ b/themes/bulma/header.mustache	Tue Aug 09 14:52:34 2022 +0200
@@ -5,7 +5,22 @@
 		<meta name="viewport" content="width=device-width, initial-scale=1" />
 		<title>{{title}}</title>
 		<link rel="stylesheet" href="/static/bulma.min.css" />
+		<link rel="stylesheet" href="/static/scid.css" />
 	</head>
 	<body>
+		<nav class="navbar" role="navigation" aria-label="main navigation">
+				<div class="navbar-brand">
+					<a class="navbar-item" href="/"><strong>sci</strong></a>
+				</div>
+
+				<div class="navbar-menu">
+					<div class="navbar-start">
+						<a class="navbar-item">projects</a>
+						<a class="navbar-item">workers</a>
+					</div>
+				</div>
+			</div>
+		</nav>
+
 		<section class="section">
 			<div class="container">
--- a/themes/bulma/index.mustache	Sun Aug 07 08:43:32 2022 +0200
+++ b/themes/bulma/index.mustache	Tue Aug 09 14:52:34 2022 +0200
@@ -12,7 +12,7 @@
 
 								<div class="content">
 									{{#jobs}}
-									<table class="table">
+									<table class="table jobresult-table">
 										<thead>
 											<tr>
 												<th>job</th>
@@ -22,7 +22,7 @@
 										</thead>
 									{{#jobs}}
 										<tr>
-											<td><a href="jobs/{{job}}">{{id}}</a></td>
+											<td><a href="jobresults/{{id}}">{{id}}</a></td>
 											<td>{{tag}}</td>
 											<td><span class="tag {{classname}} is-light">{{status}}</span></td>
 										</tr>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/bulma/jobresults.mustache	Tue Aug 09 14:52:34 2022 +0200
@@ -0,0 +1,85 @@
+				<h1 class="title">Results for job {{job_id}}</h1>
+				<table class="table">
+					<thead>
+						<tr>
+							<th>Worker</th>
+							<th>Exit code</th>
+							<th>Termination code</th>
+							<th>Status</th>
+							<th>View log</th>
+						</tr>
+					</thead>
+					<tbody>
+						{{#jobresults}}
+						<tr>
+							<td><a href="/workers/{{worker_name}}">{{worker_name}}</a></td>
+							<td>{{exitcode}}</td>
+							<td>{{sigcode}}</td>
+							<td class="{{classname}}">{{status}}</td>
+							<td><button class="button is-light is-info is-small js-modal-trigger" data-target="console-{{job_id}}">view log</button></td>
+						</tr>
+						{{/jobresults}}
+					</tbody>
+				</table>
+
+				<!-- Modal dialogs for every console output. -->
+{{#jobresults}}
+				<div id="console-{{job_id}}" class="modal">
+					<div class="modal-background"></div>
+
+					<div class="modal-content console">
+						<div class="box">
+							<pre>{{console}}</pre>
+						</div>
+					</div>
+
+					<button class="modal-close is-large" aria-label="close"></button>
+				</div>
+{{/jobresults}}
+
+				<script type="text/javascript">
+					document.addEventListener('DOMContentLoaded', () => {
+					  // Functions to open and close a modal
+					  function openModal($el) {
+					    $el.classList.add('is-active');
+					  }
+
+					  function closeModal($el) {
+					    $el.classList.remove('is-active');
+					  }
+
+					  function closeAllModals() {
+					    (document.querySelectorAll('.modal') || []).forEach(($modal) => {
+					      closeModal($modal);
+					    });
+					  }
+
+					  // Add a click event on buttons to open a specific modal
+					  (document.querySelectorAll('.js-modal-trigger') || []).forEach(($trigger) => {
+					    const modal = $trigger.dataset.target;
+					    const $target = document.getElementById(modal);
+
+					    $trigger.addEventListener('click', () => {
+					      openModal($target);
+					    });
+					  });
+
+					  // Add a click event on various child elements to close the parent modal
+					  (document.querySelectorAll('.modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button') || []).forEach(($close) => {
+					    const $target = $close.closest('.modal');
+
+					    $close.addEventListener('click', () => {
+					      closeModal($target);
+					    });
+					  });
+
+					  // Add a keyboard event to close all modals
+					  document.addEventListener('keydown', (event) => {
+					    const e = event || window.event;
+
+					    if (e.keyCode === 27) { // Escape key
+					      closeAllModals();
+					    }
+					  });
+					});
+				</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/bulma/static/scid.css	Tue Aug 09 14:52:34 2022 +0200
@@ -0,0 +1,8 @@
+.jobresult-table {
+	text-align: left;
+}
+
+/* Console log jobresults page. */
+.console {
+	width: 80%;
+}
--- a/themes/bulma/theme.js	Sun Aug 07 08:43:32 2022 +0200
+++ b/themes/bulma/theme.js	Tue Aug 09 14:52:34 2022 +0200
@@ -44,8 +44,25 @@
 	render(rdr, "index.mustache", "sci -- index page", data);
 }
 
+function onPageJobresults(rdr, data)
+{
+	/*
+	 * Add a status on failed/successful tasks.
+	 */
+	for (var i = 0; i < data.jobresults.length; ++i) {
+		if (data.jobresults[i].exitcode !== 0 || data.jobresults[i].sigcode !== 0) {
+			data.jobresults[i].status = "failed";
+			data.jobresults[i].classname = "has-text-danger";
+		} else {
+			data.jobresults[i].status = "passed";
+			data.jobresults[i].classname = "has-text-success";
+		}
+	}
+
+	render(rdr, "jobresults.mustache", "sci -- job results", data);
+}
+
 function onPageStatus(rdr, data)
 {
-	Scid.print(JSON.stringify(data));
 	render(rdr, "status.mustache", "sci -- " + data.status, data);
 }