diff options
-rw-r--r-- | libbrep/build.cxx | 6 | ||||
-rw-r--r-- | libbrep/build.hxx | 68 | ||||
-rw-r--r-- | libbrep/build.xml | 3 | ||||
-rw-r--r-- | libbrep/common.hxx | 8 | ||||
-rw-r--r-- | mod/build-config.cxx | 8 | ||||
-rw-r--r-- | mod/mod-build-log.cxx | 9 | ||||
-rw-r--r-- | mod/mod-build-task.cxx | 6 | ||||
-rw-r--r-- | mod/mod-builds.cxx | 263 | ||||
-rw-r--r-- | mod/mod-repository-root.cxx | 63 | ||||
-rw-r--r-- | mod/options.cli | 49 | ||||
-rw-r--r-- | mod/page.cxx | 52 | ||||
-rw-r--r-- | mod/page.hxx | 49 | ||||
-rw-r--r-- | web/xhtml.hxx | 70 | ||||
-rw-r--r-- | www/brep-common.css | 9 | ||||
-rw-r--r-- | www/builds-body.css | 21 |
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, |