mirror of
https://github.com/ohwgiles/laminar.git
synced 2024-10-27 20:34:20 +00:00
resolves #50: badge url
Implements serving of an SVG badge at the url /badge/JOB.svg which prettily shows the job's current status
This commit is contained in:
parent
ab7be5a6c9
commit
132d40e6a3
@ -542,6 +542,18 @@ This directory is also a good place to add any extra assets needed for this cust
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Badges
|
||||||
|
|
||||||
|
Laminar will serve a job's current status as a pretty badge at the url `/badge/JOBNAME.svg`. This can be used as a link to your server instance from your Github README.md file or cat blog:
|
||||||
|
|
||||||
|
```
|
||||||
|
<a href="https://my-example-laminar-server.com/jobs/my-project">
|
||||||
|
<img src="https://my-example-laminar-server.com/badge/my-project.svg">
|
||||||
|
</a>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
# Reference
|
# Reference
|
||||||
|
|
||||||
## Service configuration file
|
## Service configuration file
|
||||||
|
@ -135,6 +135,11 @@ struct LaminarInterface {
|
|||||||
// proper web server which handles this url.
|
// proper web server which handles this url.
|
||||||
virtual kj::Own<MappedFile> getArtefact(std::string path) = 0;
|
virtual kj::Own<MappedFile> getArtefact(std::string path) = 0;
|
||||||
|
|
||||||
|
// Given the name of a job, populate the provided string reference with
|
||||||
|
// SVG content describing the last known state of the job. Returns false
|
||||||
|
// if the job is unknown.
|
||||||
|
virtual bool handleBadgeRequest(std::string job, std::string& badge) = 0;
|
||||||
|
|
||||||
// Fetches the content of $LAMINAR_HOME/custom/style.css or an empty
|
// Fetches the content of $LAMINAR_HOME/custom/style.css or an empty
|
||||||
// string. Ideally, this would instead be served by a proper web server
|
// string. Ideally, this would instead be served by a proper web server
|
||||||
// which handles this url.
|
// which handles this url.
|
||||||
|
@ -924,6 +924,48 @@ kj::Own<MappedFile> Laminar::getArtefact(std::string path) {
|
|||||||
return kj::heap<MappedFileImpl>(fs::path(fs::path(homeDir)/"archive"/path).c_str());
|
return kj::heap<MappedFileImpl>(fs::path(fs::path(homeDir)/"archive"/path).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Laminar::handleBadgeRequest(std::string job, std::string &badge) {
|
||||||
|
RunState rs = RunState::UNKNOWN;
|
||||||
|
db->stmt("SELECT result FROM builds WHERE name = ? ORDER BY number DESC LIMIT 1")
|
||||||
|
.bind(job)
|
||||||
|
.fetch<int>([&](int result){
|
||||||
|
rs = (RunState) result;
|
||||||
|
});
|
||||||
|
if(rs == RunState::UNKNOWN)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::string status = to_string(rs);
|
||||||
|
// Empirical approximation of pixel width. Not particularly stable.
|
||||||
|
const int jobNameWidth = job.size() * 7 + 10;
|
||||||
|
const int statusWidth = status.size() * 7 + 10;
|
||||||
|
const char* gradient1 = (rs == RunState::SUCCESS) ? "#2aff4d" : "#ff2a2a";
|
||||||
|
const char* gradient2 = (rs == RunState::SUCCESS) ? "#24b43c" : "#b42424";
|
||||||
|
char* svg = NULL;
|
||||||
|
asprintf(&svg,
|
||||||
|
R"x(
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<clipPath id="clip">
|
||||||
|
<rect width="%d" height="20" rx="4"/>
|
||||||
|
</clipPath>
|
||||||
|
<linearGradient id="job" x1="0" x2="0" y1="0" y2="1">
|
||||||
|
<stop offset="0" stop-color="#666" />
|
||||||
|
<stop offset="1" stop-color="#333" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="status" x1="0" x2="0" y1="0" y2="1">
|
||||||
|
<stop offset="0" stop-color="%s" />
|
||||||
|
<stop offset="1" stop-color="%s" />
|
||||||
|
</linearGradient>
|
||||||
|
<g clip-path="url(#clip)" font-family="DejaVu Sans,Verdana,sans-serif" font-size="12" text-anchor="middle">
|
||||||
|
<rect width="%d" height="20" fill="url(#job)"/>
|
||||||
|
<text x="%d" y="14" fill="#fff">%s</text>
|
||||||
|
<rect x="%d" width="%d" height="20" fill="url(#status)"/>
|
||||||
|
<text x="%d" y="14" fill="#000">%s</text>
|
||||||
|
</g>
|
||||||
|
</svg>)x", jobNameWidth+statusWidth, gradient1, gradient2, jobNameWidth, jobNameWidth/2+1, job.data(), jobNameWidth, statusWidth, jobNameWidth+statusWidth/2, status.data());
|
||||||
|
badge = svg;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
std::string Laminar::getCustomCss() {
|
std::string Laminar::getCustomCss() {
|
||||||
MappedFileImpl cssFile(fs::path(fs::path(homeDir)/"custom"/"style.css").c_str());
|
MappedFileImpl cssFile(fs::path(fs::path(homeDir)/"custom"/"style.css").c_str());
|
||||||
if(cssFile.address()) {
|
if(cssFile.address()) {
|
||||||
|
@ -56,6 +56,7 @@ public:
|
|||||||
void sendStatus(LaminarClient* client) override;
|
void sendStatus(LaminarClient* client) override;
|
||||||
bool setParam(std::string job, uint buildNum, std::string param, std::string value) override;
|
bool setParam(std::string job, uint buildNum, std::string param, std::string value) override;
|
||||||
kj::Own<MappedFile> getArtefact(std::string path) override;
|
kj::Own<MappedFile> getArtefact(std::string path) override;
|
||||||
|
bool handleBadgeRequest(std::string job, std::string& badge) override;
|
||||||
std::string getCustomCss() override;
|
std::string getCustomCss() override;
|
||||||
void abortAll() override;
|
void abortAll() override;
|
||||||
void notifyConfigChanged() override;
|
void notifyConfigChanged() override;
|
||||||
|
@ -311,6 +311,7 @@ private:
|
|||||||
} else {
|
} else {
|
||||||
// handle regular HTTP request
|
// handle regular HTTP request
|
||||||
const char* start, *end, *content_type;
|
const char* start, *end, *content_type;
|
||||||
|
std::string badge;
|
||||||
responseHeaders.clear();
|
responseHeaders.clear();
|
||||||
if(resource.compare(0, strlen("/archive/"), "/archive/") == 0) {
|
if(resource.compare(0, strlen("/archive/"), "/archive/") == 0) {
|
||||||
kj::Own<MappedFile> file = laminar.getArtefact(resource.substr(strlen("/archive/")));
|
kj::Own<MappedFile> file = laminar.getArtefact(resource.substr(strlen("/archive/")));
|
||||||
@ -331,6 +332,11 @@ private:
|
|||||||
responseHeaders.add("Content-Transfer-Encoding", "binary");
|
responseHeaders.add("Content-Transfer-Encoding", "binary");
|
||||||
auto stream = response.send(200, "OK", responseHeaders, end-start);
|
auto stream = response.send(200, "OK", responseHeaders, end-start);
|
||||||
return stream->write(start, end-start).attach(kj::mv(stream));
|
return stream->write(start, end-start).attach(kj::mv(stream));
|
||||||
|
} else if(url.startsWith("/badge/") && url.endsWith(".svg") && laminar.handleBadgeRequest(resource.substr(7,resource.length()-11), badge)) {
|
||||||
|
responseHeaders.set(kj::HttpHeaderId::CONTENT_TYPE, "image/svg+xml");
|
||||||
|
responseHeaders.add("Cache-Control", "no-cache");
|
||||||
|
auto stream = response.send(200, "OK", responseHeaders, badge.size());
|
||||||
|
return stream->write(badge.data(), badge.size()).attach(kj::mv(badge)).attach(kj::mv(stream));
|
||||||
}
|
}
|
||||||
return response.sendError(404, "Not Found", responseHeaders);
|
return response.sendError(404, "Not Found", responseHeaders);
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,7 @@ public:
|
|||||||
MOCK_METHOD1(sendStatus, void(LaminarClient* client));
|
MOCK_METHOD1(sendStatus, void(LaminarClient* client));
|
||||||
MOCK_METHOD4(setParam, bool(std::string job, uint buildNum, std::string param, std::string value));
|
MOCK_METHOD4(setParam, bool(std::string job, uint buildNum, std::string param, std::string value));
|
||||||
MOCK_METHOD0(getCustomCss, std::string());
|
MOCK_METHOD0(getCustomCss, std::string());
|
||||||
|
MOCK_METHOD2(handleBadgeRequest, bool(std::string, std::string&));
|
||||||
MOCK_METHOD0(abortAll, void());
|
MOCK_METHOD0(abortAll, void());
|
||||||
MOCK_METHOD0(notifyConfigChanged, void());
|
MOCK_METHOD0(notifyConfigChanged, void());
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user