diff --git a/UserManual.md b/UserManual.md
index 588b5da..c3b928f 100644
--- a/UserManual.md
+++ b/UserManual.md
@@ -604,6 +604,14 @@ All=.*
Changes to this file are detected immediately and will be visible on next page refresh.
+## Adding a description to a job
+
+Edit `/var/lib/laminar/cfg/jobs/$JOBNAME.conf`:
+
+```
+DESCRIPTION=Anything here will appear on the job page in the frontend unescaped.
+```
+
## Setting the page title
Change `LAMINAR_TITLE` in `/etc/laminar.conf` to your preferred page title. Laminar must be restarted for this change to take effect.
diff --git a/src/laminar.cpp b/src/laminar.cpp
index fa3261e..acf12fa 100644
--- a/src/laminar.cpp
+++ b/src/laminar.cpp
@@ -317,7 +317,8 @@ std::string Laminar::getStatus(MonitorScope scope) {
j.set("number", build).set("started", started);
j.EndObject();
});
-
+ auto desc = jobDescriptions.find(scope.job);
+ j.set("description", desc == jobDescriptions.end() ? "" : desc->second);
} else if(scope.type == MonitorScope::ALL) {
j.startArray("jobs");
db->stmt("SELECT name,number,startedAt,completedAt,result FROM builds b JOIN (SELECT name n,MAX(number) l FROM builds GROUP BY n) q ON b.name = q.n AND b.number = q.l")
@@ -543,6 +544,10 @@ bool Laminar::loadConfiguration() {
ctxPtnList.insert(ctx);
jobContexts[jobName].swap(ctxPtnList);
}
+ std::string desc = conf.get("DESCRIPTION");
+ if(!desc.empty()) {
+ jobDescriptions[jobName] = desc;
+ }
}
}
diff --git a/src/laminar.h b/src/laminar.h
index 20a37ea..e316ba2 100644
--- a/src/laminar.h
+++ b/src/laminar.h
@@ -120,6 +120,8 @@ private:
std::unordered_map> jobContexts;
+ std::unordered_map jobDescriptions;
+
std::unordered_map jobGroups;
Settings settings;
diff --git a/src/resources/index.html b/src/resources/index.html
index 6b5a7bc..7da3bd4 100644
--- a/src/resources/index.html
+++ b/src/resources/index.html
@@ -228,6 +228,7 @@
{{$route.params.name}}
+
- Last Successful Run
- #{{lastSuccess.number}} {{lastSuccess?' - at '+formatDate(lastSuccess.started):'never'}}
diff --git a/src/resources/js/app.js b/src/resources/js/app.js
index 93a08a3..4d8fcb9 100644
--- a/src/resources/js/app.js
+++ b/src/resources/js/app.js
@@ -507,6 +507,7 @@ const Jobs = function() {
var Job = function() {
var state = {
+ description: '',
jobsRunning: [],
jobsRecent: [],
lastSuccess: null,
@@ -524,6 +525,7 @@ var Job = function() {
},
methods: {
status: function(msg) {
+ state.description = msg.description;
state.jobsRunning = msg.running;
state.jobsRecent = msg.recent;
state.lastSuccess = msg.lastSuccess;
diff --git a/test/laminar-fixture.h b/test/laminar-fixture.h
index e58d4a5..bfb635a 100644
--- a/test/laminar-fixture.h
+++ b/test/laminar-fixture.h
@@ -51,11 +51,16 @@ public:
return kj::heap(*ioContext, bind_http.c_str(), path);
}
- void defineJob(const char* name, const char* scriptContent) {
+ void defineJob(const char* name, const char* scriptContent, const char* configContent = nullptr) {
KJ_IF_MAYBE(f, tmp.fs->tryOpenFile(kj::Path{"cfg", "jobs", std::string(name) + ".run"},
kj::WriteMode::CREATE | kj::WriteMode::CREATE_PARENT | kj::WriteMode::EXECUTABLE)) {
(*f)->writeAll(std::string("#!/bin/sh\n") + scriptContent + "\n");
}
+ if(configContent) {
+ KJ_IF_MAYBE(f, tmp.fs->tryOpenFile(kj::Path{"cfg", "jobs", std::string(name) + ".conf"}, kj::WriteMode::CREATE)) {
+ (*f)->writeAll(configContent);
+ }
+ }
}
struct RunExec {
diff --git a/test/laminar-functional.cpp b/test/laminar-functional.cpp
index 23d81ee..23e4dc1 100644
--- a/test/laminar-functional.cpp
+++ b/test/laminar-functional.cpp
@@ -151,3 +151,15 @@ TEST_F(LaminarFixture, Abort) {
ASSERT_TRUE(laminar->abort("job1", 1));
EXPECT_EQ(LaminarCi::JobResult::ABORTED, res.wait(ioContext->waitScope).getResult());
}
+
+TEST_F(LaminarFixture, JobDescription) {
+ defineJob("foo", "true", "DESCRIPTION=bar");
+ auto es = eventSource("/jobs/foo");
+ ioContext->waitScope.poll();
+ ASSERT_EQ(1, es->messages().size());
+ auto json = es->messages().front().GetObject();
+ ASSERT_TRUE(json.HasMember("data"));
+ auto data = json["data"].GetObject();
+ ASSERT_TRUE(data.HasMember("description"));
+ EXPECT_STREQ("bar", data["description"].GetString());
+}