aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libbrep/build.cxx6
-rw-r--r--libbrep/build.hxx68
-rw-r--r--libbrep/build.xml3
-rw-r--r--libbrep/common.hxx8
-rw-r--r--mod/build-config.cxx8
-rw-r--r--mod/mod-build-log.cxx9
-rw-r--r--mod/mod-build-task.cxx6
-rw-r--r--mod/mod-builds.cxx263
-rw-r--r--mod/mod-repository-root.cxx63
-rw-r--r--mod/options.cli49
-rw-r--r--mod/page.cxx52
-rw-r--r--mod/page.hxx49
-rw-r--r--web/xhtml.hxx70
-rw-r--r--www/brep-common.css9
-rw-r--r--www/builds-body.css21
15 files changed, 576 insertions, 108 deletions
diff --git a/libbrep/build.cxx b/libbrep/build.cxx
index c0b780d..33aad45 100644
--- a/libbrep/build.cxx
+++ b/libbrep/build.cxx
@@ -36,7 +36,8 @@ namespace brep
build (string pnm, version pvr,
string cfg,
string tnm, version tvr,
- string mnm, string msm)
+ string mnm, string msm,
+ optional<butl::target_triplet> trg)
: id (package_id (move (pnm), pvr), move (cfg), tvr),
package_name (id.package.name),
package_version (move (pvr)),
@@ -47,7 +48,8 @@ namespace brep
timestamp (timestamp_type::clock::now ()),
forced (false),
machine (move (mnm)),
- machine_summary (move (msm))
+ machine_summary (move (msm)),
+ target (move (trg))
{
}
}
diff --git a/libbrep/build.hxx b/libbrep/build.hxx
index 90e6523..112100a 100644
--- a/libbrep/build.hxx
+++ b/libbrep/build.hxx
@@ -10,6 +10,8 @@
#include <odb/core.hxx>
#include <odb/section.hxx>
+#include <libbutl/target-triplet.hxx>
+
#include <libbbot/manifest.hxx>
#include <libbrep/types.hxx>
@@ -19,9 +21,9 @@
// Used by the data migration entries.
//
-#define LIBBREP_BUILD_SCHEMA_VERSION_BASE 1
+#define LIBBREP_BUILD_SCHEMA_VERSION_BASE 2
-#pragma db model version(LIBBREP_BUILD_SCHEMA_VERSION_BASE, 1, closed)
+#pragma db model version(LIBBREP_BUILD_SCHEMA_VERSION_BASE, 2, open)
// We have to keep these mappings at the global scope instead of inside
// the brep namespace because they need to be also effective in the
@@ -92,6 +94,16 @@ namespace brep
? bbot::to_result_status (*(?)) \
: brep::optional_result_status ())
+ // target_triplet
+ //
+ using optional_target_triplet = optional<butl::target_triplet>;
+
+ #pragma db map type(optional_target_triplet) as(optional_string) \
+ to((?) ? (?)->string () : brep::optional_string ()) \
+ from((?) \
+ ? butl::target_triplet (*(?)) \
+ : brep::optional_target_triplet ())
+
// operation_results
//
using bbot::operation_result;
@@ -111,7 +123,8 @@ namespace brep
build (string package_name, version package_version,
string configuration,
string toolchain_name, version toolchain_version,
- string machine, string machine_summary);
+ string machine, string machine_summary,
+ optional<butl::target_triplet> target);
build_id id;
@@ -140,13 +153,14 @@ namespace brep
optional<string> machine;
optional<string> machine_summary;
+ // Default for the machine if absent.
+ //
+ optional<butl::target_triplet> target;
+
// Note that the logs are stored as std::string/TEXT which is Ok since
// they are UTF-8 and our database is UTF-8.
//
- #pragma db section(results_section)
operation_results results;
-
- #pragma db load(lazy) update(always)
odb::section results_section;
// Database mapping.
@@ -160,7 +174,10 @@ namespace brep
#pragma db member(toolchain_version) \
set(this.toolchain_version.init (this.id.toolchain_version, (?)))
- #pragma db member(results) id_column("") value_column("")
+ #pragma db member(results) id_column("") value_column("") \
+ section(results_section)
+
+ #pragma db member(results_section) load(lazy) update(always)
build (const build&) = delete;
build& operator= (const build&) = delete;
@@ -182,6 +199,43 @@ namespace brep
//
#pragma db member(result) column("count(" + build::package_name + ")")
};
+
+ #pragma db view object(build) query(distinct)
+ struct toolchain
+ {
+ string name;
+ upstream_version version;
+
+ // Database mapping. Note that the version member must be loaded after
+ // the virtual members since the version_ member must filled by that time.
+ //
+ #pragma db member(name) column(build::toolchain_name)
+
+ #pragma db member(version) column(build::toolchain_version) \
+ set(this.version.init (this.version_, (?)))
+
+ #pragma db member(epoch) virtual(uint16_t) \
+ before(version) access(version_.epoch) \
+ column(build::id.toolchain_version.epoch)
+
+ #pragma db member(canonical_upstream) virtual(std::string) \
+ before(version) access(version_.canonical_upstream) \
+ column(build::id.toolchain_version.canonical_upstream)
+
+ #pragma db member(canonical_release) virtual(std::string) \
+ before(version) access(version_.canonical_release) \
+ column(build::id.toolchain_version.canonical_release)
+
+ #pragma db member(revision) virtual(uint16_t) \
+ before(version) access(version_.revision) \
+ column(build::id.toolchain_version.revision)
+
+ private:
+ friend class odb::access;
+
+ #pragma db transient
+ canonical_version version_;
+ };
}
#endif // LIBBREP_BUILD_HXX
diff --git a/libbrep/build.xml b/libbrep/build.xml
index 5793de3..72cbd5f 100644
--- a/libbrep/build.xml
+++ b/libbrep/build.xml
@@ -1,5 +1,5 @@
<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="pgsql" schema-name="build" version="1">
- <model version="1">
+ <model version="2">
<table name="build" kind="object">
<column name="package_name" type="TEXT" null="false"/>
<column name="package_version_epoch" type="INTEGER" null="false"/>
@@ -22,6 +22,7 @@
<column name="status" type="TEXT" null="true"/>
<column name="machine" type="TEXT" null="true"/>
<column name="machine_summary" type="TEXT" null="true"/>
+ <column name="target" type="TEXT" null="true"/>
<primary-key>
<column name="package_name"/>
<column name="package_version_epoch"/>
diff --git a/libbrep/common.hxx b/libbrep/common.hxx
index 942790c..6bc5aca 100644
--- a/libbrep/common.hxx
+++ b/libbrep/common.hxx
@@ -318,10 +318,12 @@ namespace brep
template <typename T>
inline auto
- order_by_version_desc (const T& x) -> //decltype ("ORDER BY" + x.epoch)
- decltype (x.epoch == 0)
+ order_by_version_desc (
+ const T& x,
+ bool first = true) -> //decltype ("ORDER BY" + x.epoch)
+ decltype (x.epoch == 0)
{
- return "ORDER BY"
+ return (first ? "ORDER BY" : ", ")
+ x.epoch + "DESC,"
+ x.canonical_upstream + "DESC,"
+ x.canonical_release + "DESC,"
diff --git a/mod/build-config.cxx b/mod/build-config.cxx
index 5fd540f..9eb40ce 100644
--- a/mod/build-config.cxx
+++ b/mod/build-config.cxx
@@ -65,9 +65,9 @@ namespace brep
// encoded by design.
//
return host + root.string () +
- "?build-force&p=" + mime_url_encode (b.package_name) +
- "&v=" + b.package_version.string () +
- "&c=" + mime_url_encode (b.configuration) +
- "&t=" + b.toolchain_version.string () + "&reason=";
+ "?build-force&pn=" + mime_url_encode (b.package_name) +
+ "&pv=" + b.package_version.string () +
+ "&cf=" + mime_url_encode (b.configuration) +
+ "&tc=" + b.toolchain_version.string () + "&reason=";
}
}
diff --git a/mod/mod-build-log.cxx b/mod/mod-build-log.cxx
index b998f8d..a1e73ca 100644
--- a/mod/mod-build-log.cxx
+++ b/mod/mod-build-log.cxx
@@ -170,8 +170,8 @@ handle (request& rq, response& rs)
// Make sure the build configuration still exists.
//
- auto i (build_conf_map_->find (id.configuration.c_str ()));
- if (i == build_conf_map_->end ())
+ if (build_conf_map_->find (id.configuration.c_str ()) ==
+ build_conf_map_->end ())
config_expired ("no configuration");
// Make sure the package still exists.
@@ -215,9 +215,8 @@ handle (request& rq, response& rs)
<< "config: " << b->configuration << endl
<< "machine: " << *b->machine << " (" << *b->machine_summary << ")"
<< endl
- << "target: " << (i->second->target
- ? i->second->target->string ()
- : "<default>") << endl
+ << "target: " << (b->target ? b->target->string () : "<default>")
+ << endl
<< "timestamp: ";
butl::to_stream (os, b->timestamp, "%Y-%m-%d %H:%M:%S%[.N] %Z", true, true);
diff --git a/mod/mod-build-task.cxx b/mod/mod-build-task.cxx
index 3e02463..ec236c0 100644
--- a/mod/mod-build-task.cxx
+++ b/mod/mod-build-task.cxx
@@ -396,7 +396,8 @@ handle (request& rq, response& rs)
move (tqm.toolchain_name),
move (toolchain_version),
mh.name,
- move (mh.summary));
+ move (mh.summary),
+ cm.config->target);
build_db_->persist (b);
}
@@ -431,6 +432,7 @@ handle (request& rq, response& rs)
b->toolchain_name = move (tqm.toolchain_name);
b->machine = mh.name;
b->machine_summary = move (mh.summary);
+ b->target = cm.config->target;
b->timestamp = timestamp::clock::now ();
build_db_->update (b);
@@ -552,6 +554,8 @@ handle (request& rq, response& rs)
b->toolchain_name = tqm.toolchain_name;
b->machine_summary = mh.summary;
+ b->target = cm.config->target;
+
// Mark the section as loaded, so results are updated.
//
b->results_section.load ();
diff --git a/mod/mod-builds.cxx b/mod/mod-builds.cxx
index 6b6ab08..70ef605 100644
--- a/mod/mod-builds.cxx
+++ b/mod/mod-builds.cxx
@@ -11,8 +11,11 @@
#include <libbutl/timestamp.hxx> // to_string()
+#include <libbbot/manifest.hxx> // to_result_status(), to_string(result_status)
+
#include <web/xhtml.hxx>
#include <web/module.hxx>
+#include <web/mime-url-encoding.hxx>
#include <libbrep/build.hxx>
#include <libbrep/build-odb.hxx>
@@ -25,6 +28,7 @@
using namespace std;
using namespace bbot;
+using namespace web;
using namespace odb::core;
using namespace brep::cli;
@@ -60,12 +64,137 @@ init (scanner& s)
template <typename T, typename C>
static inline query<T>
-build_query (const C& configs)
+build_query (const C& configs, const brep::params::builds& params)
{
+ using namespace brep;
+
using query = query<T>;
- return query::id.configuration.in_range (configs.begin (), configs.end ()) &&
- (query::state == "testing" || query::state == "tested");
+ // Transform the wildcard to the LIKE-pattern.
+ //
+ auto transform = [] (const string& s) -> string
+ {
+ if (s.empty ())
+ return "%";
+
+ string r;
+ for (char c: s)
+ {
+ switch (c)
+ {
+ case '*': c = '%'; break;
+ case '?': c = '_'; break;
+ case '\\':
+ case '%':
+ case '_': r += '\\'; break;
+ }
+
+ r += c;
+ }
+
+ return r;
+ };
+
+ query q (
+ query::id.configuration.in_range (configs.begin (), configs.end ()) &&
+ (query::state == "testing" || query::state == "tested"));
+
+ // Note that there is no error reported if the filter parameters parsing
+ // fails. Instead, it is considered that no package builds match such a
+ // query.
+ //
+ try
+ {
+ // Package name.
+ //
+ if (!params.name ().empty ())
+ q = q && query::id.package.name.like (transform (params.name ()));
+
+ // Package version.
+ //
+ if (!params.version ().empty () && params.version () != "*")
+ {
+ version v (params.version ()); // May throw invalid_argument.
+ q = q && compare_version_eq (query::id.package.version, v, true);
+ }
+
+ // Build toolchain name/version.
+ //
+ const string& tc (params.toolchain ());
+
+ if (tc != "*")
+ {
+ size_t p (tc.find ('-'));
+ if (p == string::npos) // Invalid format.
+ throw invalid_argument ("");
+
+ string tn (tc, 0, p);
+ version tv (string (tc, p + 1)); // May throw invalid_argument.
+
+ q = q && query::toolchain_name == tn &&
+ compare_version_eq (query::id.toolchain_version, tv, true);
+ }
+
+ // Build configuration name.
+ //
+ if (!params.configuration ().empty ())
+ q = q && query::id.configuration.like (
+ transform (params.configuration ()));
+
+ // Build machine name.
+ //
+ if (!params.machine ().empty ())
+ query::machine.like (transform (params.machine ()));
+
+ // Build target.
+ //
+ const string& tg (params.target ());
+
+ if (tg != "*")
+ q = q && (tg.empty ()
+ ? query::target.is_null ()
+ : query::target.like (transform (tg)));
+
+ // Build result.
+ //
+ const string& rs (params.result ());
+
+ if (!rs.empty () && rs != "*")
+ {
+ if (rs == "pending")
+ q = q && query::forced;
+ else if (rs == "building")
+ q = q && query::state == "testing";
+ else
+ {
+ query sq (query::status == rs);
+ result_status st (to_result_status(rs)); // May throw invalid_argument.
+
+ if (st != result_status::success)
+ {
+ auto next = [&st] () -> bool
+ {
+ if (st == result_status::abnormal)
+ return false;
+
+ st = static_cast<result_status> (static_cast<uint8_t> (st) + 1);
+ return true;
+ };
+
+ while (next ())
+ sq = sq || query::status == to_string (st);
+ }
+
+ q = q && sq;
+ }
+ }
+ }
+ catch (const invalid_argument&)
+ {
+ return query (false);
+ }
+
+ return q;
}
bool brep::builds::
@@ -104,6 +233,15 @@ handle (request& rq, response& rs)
<< HEAD
<< TITLE << title << ~TITLE
<< CSS_LINKS (path ("builds.css"), root)
+ //
+ // This hack is required to avoid the "flash of unstyled content", which
+ // happens due to the presence of the autofocus attribute in the input
+ // element of the search form. The problem appears in Firefox and has a
+ // (4-year old, at the time of this writing) bug report:
+ //
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=712130.
+ //
+ << SCRIPT << " " << ~SCRIPT
<< ~HEAD
<< BODY
<< DIV_HEADER (root, options_->logo (), options_->menu ())
@@ -119,28 +257,103 @@ handle (request& rq, response& rs)
//
auto count (
build_db_->query_value<build_count> (
- build_query<build_count> (*build_conf_names_)));
+ build_query<build_count> (*build_conf_names_, params)));
- s << DIV_COUNTER (count, "Build", "Builds");
+ // Print the package builds filter form on the first page only.
+ //
+ if (page == 0)
+ {
+ // Populate the toolchains list with the distinct list of toolchain
+ // name/version pairs from all the existing package builds. Make sure the
+ // selected toolchain still present in the database. Otherwise fallback to
+ // the * wildcard selection.
+ //
+ string tc ("*");
+ vector<pair<string,string>> toolchains ({{"*", "*"}});
+ {
+ using query = query<toolchain>;
+
+ for (const auto& t: build_db_->query<toolchain> (
+ "ORDER BY" + query::toolchain_name +
+ order_by_version_desc (query::id.toolchain_version, false)))
+ {
+ string s (t.name + '-' + t.version.string ());
+ toolchains.emplace_back (s, s);
+
+ if (s == params.toolchain ())
+ tc = move (s);
+ }
+ }
+
+ // The 'action' attribute is optional in HTML5. While the standard doesn't
+ // specify browser behavior explicitly for the case the attribute is
+ // omitted, the only reasonable behavior is to default it to the current
+ // document URL. Note that we specify the function name using the "hidden"
+ // <input/> element since the action url must not contain the query part.
+ //
+ s << FORM
+ << *INPUT(TYPE="hidden", NAME="builds")
+ << TABLE(ID="filter", CLASS="proplist")
+ << TBODY
+ << TR_INPUT ("name", "pn", params.name (), "*", true)
+ << TR_INPUT ("version", "pv", params.version (), "*")
+ << TR_SELECT ("toolchain", "tc", tc, toolchains)
+
+ << TR(CLASS="config")
+ << TH << "config" << ~TH
+ << TD
+ << *INPUT(TYPE="text",
+ NAME="cf",
+ VALUE=params.configuration (),
+ PLACEHOLDER="*",
+ LIST="configs")
+ << DATALIST(ID="configs")
+ << *OPTION(VALUE="*");
+
+ for (const auto& c: *build_conf_names_)
+ s << *OPTION(VALUE=c);
+
+ s << ~DATALIST
+ << ~TD
+ << ~TR
+
+ << TR_INPUT ("machine", "mn", params.machine (), "*")
+ << TR_INPUT ("target", "tg", params.target (), "<default>")
+ << TR_INPUT ("result", "rs", params.result (), "*")
+ << ~TBODY
+ << ~TABLE
+ << TABLE(CLASS="form-table")
+ << TBODY
+ << TR
+ << TD(ID="build-count")
+ << DIV_COUNTER (count, "Build", "Builds")
+ << ~TD
+ << TD(ID="filter-btn")
+ << *INPUT(TYPE="submit", VALUE="Filter")
+ << ~TD
+ << ~TR
+ << ~TBODY
+ << ~TABLE
+ << ~FORM;
+ }
+ else
+ s << DIV_COUNTER (count, "Build", "Builds");
// Enclose the subsequent tables to be able to use nth-child CSS selector.
//
s << DIV;
for (auto& b: build_db_->query<build> (
- build_query<build> (*build_conf_names_) +
+ build_query<build> (*build_conf_names_, params) +
"ORDER BY" + query<build>::timestamp + "DESC" +
"OFFSET" + to_string (page * page_configs) +
"LIMIT" + to_string (page_configs)))
{
assert (b.machine);
- auto i (build_conf_map_->find (b.configuration.c_str ()));
- assert (i != build_conf_map_->end ());
- const build_config& c (*i->second);
-
string ts (butl::to_string (b.timestamp,
"%Y-%m-%d %H:%M:%S%[.N] %Z",
- true, true));
+ true,
+ true));
s << TABLE(CLASS="proplist build")
<< TBODY
@@ -151,7 +364,7 @@ handle (request& rq, response& rs)
b.toolchain_version.string ())
<< TR_VALUE ("config", b.configuration)
<< TR_VALUE ("machine", *b.machine)
- << TR_VALUE ("target", c.target ? c.target->string () : "<default>")
+ << TR_VALUE ("target", b.target ? b.target->string () : "<default>")
<< TR_VALUE ("timestamp", ts)
<< TR(CLASS="result")
<< TH << "result" << ~TH
@@ -220,8 +433,30 @@ handle (request& rq, response& rs)
t.commit ();
- s << DIV_PAGER (page, count, page_configs, options_->build_pages (),
- root.string () + "?builds")
+ string u (root.string () + "?builds");
+
+ auto add_filter = [&u] (const char* pn,
+ const string& pv,
+ const char* def = "")
+ {
+ if (pv != def)
+ {
+ u += '&';
+ u += pn;
+ u += '=';
+ u += mime_url_encode (pv);
+ }
+ };
+
+ add_filter ("pn", params.name ());
+ add_filter ("pv", params.version ());
+ add_filter ("tc", params.toolchain (), "*");
+ add_filter ("cf", params.configuration ());
+ add_filter ("mn", params.machine ());
+ add_filter ("tg", params.target (), "*");
+ add_filter ("rs", params.result ());
+
+ s << DIV_PAGER (page, count, page_configs, options_->build_pages (), u)
<< ~DIV
<< ~BODY
<< ~HTML;
diff --git a/mod/mod-repository-root.cxx b/mod/mod-repository-root.cxx
index c661b91..c8f2980 100644
--- a/mod/mod-repository-root.cxx
+++ b/mod/mod-repository-root.cxx
@@ -218,11 +218,22 @@ namespace brep
// Delegate the request handling to the selected sub-module. Intercept
// exception handling to add sub-module attribution.
//
- auto handle = [&rs, this] (request& rq, const char* name) -> bool
+ auto handle = [&rq, &rs, this] (const char* nm, bool fn = false) -> bool
{
try
{
- return handler_->handle (rq, rs, *log_);
+ // Delegate the handling straight away if the sub-module is not a
+ // function. Otherwise, cleanup the request not to confuse the
+ // sub-module with the unknown parameter.
+ //
+ if (!fn)
+ return handler_->handle (rq, rs, *log_);
+
+ name_values p (rq.parameters ());
+ p.erase (p.begin ());
+
+ request_proxy rp (rq, p);
+ return handler_->handle (rp, rs, *log_);
}
catch (const invalid_request&)
{
@@ -242,7 +253,7 @@ namespace brep
// module::handle() function call.
//
ostringstream os;
- os << name << ": " << e;
+ os << nm << ": " << e;
throw runtime_error (os.str ());
}
};
@@ -256,21 +267,15 @@ namespace brep
//
if (lpath.empty ())
{
- // Dispatch request handling to the repository_details, the
- // package_search or the one of build_* modules depending on the function
- // name passed as a first HTTP request parameter. The parameter should
- // have no value specified. Example: cppget.org/?about
+ // Dispatch request handling to the repository_details or the one of
+ // build_* modules depending on the function name passed as a first HTTP
+ // request parameter (example: cppget.org/?about). Dispatch to the
+ // package_search module if the function name is unavailable (no
+ // parameters) or is not recognized.
//
const name_values& params (rq.parameters ());
- if (!params.empty () && !params.front ().value)
+ if (!params.empty ())
{
- // Cleanup not to confuse the selected module with the unknown
- // parameter.
- //
- name_values p (params);
- p.erase (p.begin ());
-
- request_proxy rp (rq, p);
const string& fn (params.front ().name);
if (fn == "about")
@@ -278,46 +283,42 @@ namespace brep
if (handler_ == nullptr)
handler_.reset (new repository_details (*repository_details_));
- return handle (rp, "repository_details");
+ return handle ("repository_details", true);
}
else if (fn == "build-task")
{
if (handler_ == nullptr)
handler_.reset (new build_task (*build_task_));
- return handle (rp, "build_task");
+ return handle ("build_task", true);
}
else if (fn == "build-result")
{
if (handler_ == nullptr)
handler_.reset (new build_result (*build_result_));
- return handle (rp, "build_result");
+ return handle ("build_result", true);
}
else if (fn == "build-force")
{
if (handler_ == nullptr)
handler_.reset (new build_force (*build_force_));
- return handle (rp, "build_force");
+ return handle ("build_force", true);
}
else if (fn == "builds")
{
if (handler_ == nullptr)
handler_.reset (new builds (*builds_));
- return handle (rp, "builds");
+ return handle ("builds", true);
}
- else
- throw invalid_request (400, "unknown function");
}
- else
- {
- if (handler_ == nullptr)
- handler_.reset (new package_search (*package_search_));
- return handle (rq, "package_search");
- }
+ if (handler_ == nullptr)
+ handler_.reset (new package_search (*package_search_));
+
+ return handle ("package_search");
}
else
{
@@ -355,7 +356,7 @@ namespace brep
if (handler_ == nullptr)
handler_.reset (new package_details (*package_details_));
- return handle (rq, "package_details");
+ return handle ("package_details");
}
else if (++i == lpath.end ())
{
@@ -363,14 +364,14 @@ namespace brep
handler_.reset (
new package_version_details (*package_version_details_));
- return handle (rq, "package_version_details");
+ return handle ("package_version_details");
}
else if (*i == "log")
{
if (handler_ == nullptr)
handler_.reset (new build_log (*build_log_));
- return handle (rq, "build_log");
+ return handle ("build_log");
}
}
}
diff --git a/mod/options.cli b/mod/options.cli
index 9442e7f..ced3de2 100644
--- a/mod/options.cli
+++ b/mod/options.cli
@@ -392,7 +392,7 @@ namespace brep
{
// Package name.
//
- string package | p;
+ string package | pn;
// Package version. May not be url-encoded, in which case the plus
// character is considered literally (rather than as the encoded space
@@ -402,15 +402,15 @@ namespace brep
// @@ Make it of the version type? Maybe after it get moved to
// libbpkg/types.hxx or at least the second use case appear.
//
- string version | v;
+ string version | pv;
// Package build configuration.
//
- string configuration | c;
+ string configuration | cf;
// Toolchain version. May not be url-encoded (see above).
//
- string toolchain_version | t;
+ string toolchain_version | tc;
// Package rebuild reason. Must not be empty.
//
@@ -422,6 +422,47 @@ namespace brep
// Display packages build configurations list starting from this page.
//
uint16_t page | p;
+
+ // Package builds query filter options.
+ //
+
+ // Package name wildcard. An empty value is treated the same way as *.
+ //
+ string name | pn;
+
+ // Package version. If empty or *, then no version constraint is applied.
+ // Otherwise the build package version must match the value exactly.
+ //
+ string version | pv;
+
+ // Package build toolchain in the <name>-<version> form. If *, then no
+ // toolchain constraint is applied. Otherwise the build toolchain name
+ // and version must match the value exactly.
+ //
+ string toolchain | tc = "*";
+
+ // Package build configuration name wildcard. An empty value is treated
+ // the same way as *.
+ //
+ string configuration | cf;
+
+ // Package build machine name wildcard. An empty value is treated the
+ // same way as *.
+ //
+ string machine | mn;
+
+ // Package build target wildcard. If empty, then the default machine
+ // target is matched.
+ //
+ string target | tg = "*";
+
+ // Package build result. If empty or *, then no build result constraint
+ // is applied. Otherwise the value is supposed to be the one of the
+ // following (ordered) statuses: pending, building, success, warning,
+ // error, abort, abnormal. The first 3 statuses are checked for equality,
+ // the rest - for being greater or equal.
+ //
+ string result | rs;
};
}
}
diff --git a/mod/page.cxx b/mod/page.cxx
index de7b5f5..73c56be 100644
--- a/mod/page.cxx
+++ b/mod/page.cxx
@@ -80,7 +80,7 @@ namespace brep
{
// The 'action' attribute is optional in HTML5. While the standard doesn't
// specify browser behavior explicitly for the case the attribute is
- // ommited, the only reasonable behavior is to default it to the current
+ // omitted, the only reasonable behavior is to default it to the current
// document URL.
//
s << FORM(ID="search")
@@ -122,6 +122,56 @@ namespace brep
<< ~TR;
}
+ // TR_INPUT
+ //
+ void TR_INPUT::
+ operator() (serializer& s) const
+ {
+ s << TR(CLASS=label_)
+ << TH << label_ << ~TH
+ << TD
+ << INPUT(TYPE="text", NAME=name_);
+
+ if (!value_.empty ())
+ s << VALUE(value_);
+
+ if (!placeholder_.empty ())
+ s << PLACEHOLDER(placeholder_);
+
+ if (autofocus_)
+ s << AUTOFOCUS("autofocus");
+
+ s << ~INPUT
+ << ~TD
+ << ~TR;
+ }
+
+ // TR_SELECT
+ //
+ void TR_SELECT::
+ operator() (serializer& s) const
+ {
+ s << TR(CLASS=label_)
+ << TH << label_ << ~TH
+ << TD
+ << SELECT(NAME=name_);
+
+ for (const auto& o: options_)
+ {
+ s << OPTION(VALUE=o.first);
+
+ if (o.first == value_)
+ s << SELECTED("selected");
+
+ s << o.second
+ << ~OPTION;
+ }
+
+ s << ~SELECT
+ << ~TD
+ << ~TR;
+ }
+
// TR_NAME
//
void TR_NAME::
diff --git a/mod/page.hxx b/mod/page.hxx
index 6d18f36..d7c44d6 100644
--- a/mod/page.hxx
+++ b/mod/page.hxx
@@ -108,6 +108,55 @@ namespace brep
const string& value_;
};
+ // Generates table row element, that has the 'label: <input type="text"/>'
+ // layout.
+ //
+ class TR_INPUT
+ {
+ public:
+ TR_INPUT (const string& l,
+ const string& n,
+ const string& v,
+ const string& p = string (),
+ bool a = false)
+ : label_ (l), name_ (n), value_ (v), placeholder_ (p), autofocus_ (a)
+ {
+ }
+
+ void
+ operator() (xml::serializer&) const;
+
+ private:
+ const string& label_;
+ const string& name_;
+ const string& value_;
+ const string& placeholder_;
+ bool autofocus_;
+ };
+
+ // Generates table row element, that has the 'label: <select></select>'
+ // layout. Option elements are represented as a list of value/inner-text
+ // pairs.
+ //
+ class TR_SELECT
+ {
+ public:
+ TR_SELECT (const string& l,
+ const string& n,
+ const string& v,
+ const vector<pair<string, string>>& o)
+ : label_ (l), name_ (n), value_ (v), options_ (o) {}
+
+ void
+ operator() (xml::serializer&) const;
+
+ private:
+ const string& label_;
+ const string& name_;
+ const string& value_;
+ const vector<pair<string, string>>& options_;
+ };
+
// Generates package name element.
//
class TR_NAME
diff --git a/web/xhtml.hxx b/web/xhtml.hxx
index b9ccff3..4cbd1c3 100644
--- a/web/xhtml.hxx
+++ b/web/xhtml.hxx
@@ -301,28 +301,31 @@ namespace web
};
static const css_style_element CSS_STYLE;
- static const element BODY ("body");
- static const element DIV ("div");
- static const element FORM ("form");
- static const element H1 ("h1");
- static const element H2 ("h2");
- static const element H3 ("h3");
- static const element H4 ("h4");
- static const element H5 ("h5");
- static const element H6 ("h6");
- static const element LI ("li");
- static const element LINK ("link");
- static const element META ("meta");
- static const element P ("p");
- static const element PRE ("pre");
- static const element SCRIPT ("script");
- static const element TABLE ("table");
- static const element TBODY ("tbody");
- static const element TD ("td");
- static const element TH ("th");
- static const element TITLE ("title");
- static const element TR ("tr");
- static const element UL ("ul");
+ static const element BODY ("body");
+ static const element DATALIST ("datalist");
+ static const element DIV ("div");
+ static const element FORM ("form");
+ static const element H1 ("h1");
+ static const element H2 ("h2");
+ static const element H3 ("h3");
+ static const element H4 ("h4");
+ static const element H5 ("h5");
+ static const element H6 ("h6");
+ static const element LI ("li");
+ static const element LINK ("link");
+ static const element META ("meta");
+ static const element OPTION ("option");
+ static const element P ("p");
+ static const element PRE ("pre");
+ static const element SCRIPT ("script");
+ static const element SELECT ("select");
+ static const element TABLE ("table");
+ static const element TBODY ("tbody");
+ static const element TD ("td");
+ static const element TH ("th");
+ static const element TITLE ("title");
+ static const element TR ("tr");
+ static const element UL ("ul");
static const inline_element A ("a");
static const inline_element B ("b");
@@ -337,16 +340,19 @@ namespace web
// Attributes.
//
- static const attribute AUTOFOCUS ("autofocus");
- static const attribute CLASS ("class");
- static const attribute CONTENT ("content");
- static const attribute HREF ("href");
- static const attribute ID ("id");
- static const attribute NAME ("name");
- static const attribute REL ("rel");
- static const attribute STYLE ("style");
- static const attribute TYPE ("type");
- static const attribute VALUE ("value");
+ static const attribute AUTOFOCUS ("autofocus");
+ static const attribute CLASS ("class");
+ static const attribute CONTENT ("content");
+ static const attribute HREF ("href");
+ static const attribute ID ("id");
+ static const attribute LIST ("list");
+ static const attribute NAME ("name");
+ static const attribute REL ("rel");
+ static const attribute PLACEHOLDER ("placeholder");
+ static const attribute SELECTED ("selected");
+ static const attribute STYLE ("style");
+ static const attribute TYPE ("type");
+ static const attribute VALUE ("value");
}
}
diff --git a/www/brep-common.css b/www/brep-common.css
index 81fc909..1a90dda 100644
--- a/www/brep-common.css
+++ b/www/brep-common.css
@@ -170,3 +170,12 @@
#pager #curr:after {content: "]"; font-weight: normal;}
#pager #next:after {content: "\A0>";}
+
+/* Use page rather than system font settings. */
+select
+{
+ font-family: inherit;
+ font-weight: inherit;
+ font-size: inherit;
+ line-height: inherit;
+}
diff --git a/www/builds-body.css b/www/builds-body.css
index a69bb5a..a4b72fc 100644
--- a/www/builds-body.css
+++ b/www/builds-body.css
@@ -1,4 +1,16 @@
/*
+ * Filter form (based on proplist and form-table)
+ */
+#filter input, #filter select,
+#build-count, #build-count #count
+{
+ width: 100%;
+ margin:0;
+}
+
+#filter-btn {padding-left: .4em;}
+
+/*
* Build count.
*/
#count
@@ -11,9 +23,9 @@
}
/*
- * Version table.
+ * Build table.
*/
-.build
+.build, #filter
{
margin-top: .8em;
margin-bottom: .8em;
@@ -23,7 +35,10 @@
}
.build:nth-child(even) {background-color: rgba(0, 0, 0, 0.07);}
-.build th {width: 7.0em;}
+.build th, #filter th
+{
+ width: 7.0em;
+}
.build tr.name td .value,
.build tr.version td .value,