diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..1ccb157c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,24 @@ +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{cc,hh,hpp,pl,pm,sh,t}] +indent_style = space +intend_size = 4 + +[Makefile] +indent_style = tab + +[*.nix] +indent_style = space +indent_size = 2 + +# Match diffs, avoid to trim trailing whitespace +[*.{diff,patch}] +trim_trailing_whitespace = false diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..3550a30f --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..d69140ed --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,37 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Hydra Server:** + +Please fill out this data as well as you can, but don't worry if you can't -- just do your best. + +- OS and version: [e.g. NixOS 22.05.20211203.ee3794c] +- Version of Hydra +- Version of Nix Hydra is built against +- Version of the Nix daemon + + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..bbcbbe7d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..5ace4600 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 16554711..790e7565 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,22 +1,27 @@ name: "Test" on: pull_request: + merge_group: push: + branches: + - master jobs: tests: - runs-on: ubuntu-18.04 + strategy: + matrix: + include: + - system: x86_64-linux + runner: ubuntu-latest + - system: aarch64-linux + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.runner }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: cachix/install-nix-action@v8 - #- run: nix flake check - - run: nix-build -A checks.x86_64-linux.build - validate-openapi: - runs-on: ubuntu-18.04 - steps: - - uses: actions/checkout@v2 + - uses: cachix/install-nix-action@v31 with: - fetch-depth: 0 - - uses: cachix/install-nix-action@v8 - - run: nix-shell -p openapi-generator-cli --run "openapi-generator-cli validate -i ./hydra-api.yaml" + extra_nix_config: | + extra-systems = ${{ matrix.system }} + - uses: DeterminateSystems/magic-nix-cache-action@main + - run: nix-build -A checks.${{ matrix.system }}.build -A checks.${{ matrix.system }}.validate-openapi diff --git a/.github/workflows/update-flakes.yml b/.github/workflows/update-flakes.yml new file mode 100644 index 00000000..b5c0c2dd --- /dev/null +++ b/.github/workflows/update-flakes.yml @@ -0,0 +1,28 @@ +name: "Update Flakes" +on: + schedule: + # Run weekly on Monday at 00:00 UTC + - cron: '0 0 * * 1' + workflow_dispatch: +jobs: + update-flakes: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v3 + - uses: cachix/install-nix-action@v31 + - name: Update flake inputs + run: nix flake update + - name: Create Pull Request + uses: peter-evans/create-pull-request@v5 + with: + commit-message: "flake.lock: Update" + title: "Update flake inputs" + body: | + Automated flake input updates. + + This PR was automatically created by the update-flakes workflow. + branch: update-flakes + delete-branch: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index b5693cf3..ea9c2985 100644 --- a/.gitignore +++ b/.gitignore @@ -1,36 +1,13 @@ -*.o *~ -Makefile -Makefile.in -.deps -.hydra-data -/config.guess -/config.log -/config.status -/config.sub -/configure -/depcomp -/libtool -/ltmain.sh -/autom4te.cache -/aclocal.m4 -/missing -/install-sh +/.direnv/ +.test_info.* +/src/root/static/bootstrap +/src/root/static/fontawesome +/src/root/static/js/flot /src/sql/hydra-postgresql.sql /src/sql/hydra-sqlite.sql /src/sql/tmp.sqlite -/src/hydra-eval-jobs/hydra-eval-jobs -/src/root/static/bootstrap -/src/root/static/js/flot -/doc/manual/images -/doc/manual/manual.html -/doc/manual/manual.pdf -/tests/.bzr* -/tests/.git* -/tests/.hg* -/tests/nix -/inst -hydra-config.h -hydra-config.h.in +.hydra-data result -tests/jobs/config.nix +result-* +outputs diff --git a/.perlcriticrc b/.perlcriticrc new file mode 100644 index 00000000..77d9e724 --- /dev/null +++ b/.perlcriticrc @@ -0,0 +1,10 @@ +theme = community + +# 5 is the least complainy, 1 is the most complainy +severity = 1 + +# Disallow backticks - use IPC::Run3 instead for better security +include = InputOutput::ProhibitBacktickOperators + +# Prohibit shell-invoking system() and exec() - use list form or IPC::Run3 instead +include = Hydra::ProhibitShellInvokingSystemCalls diff --git a/Makefile.am b/Makefile.am deleted file mode 100644 index ad911b4d..00000000 --- a/Makefile.am +++ /dev/null @@ -1,8 +0,0 @@ -SUBDIRS = src tests doc -BOOTCLEAN_SUBDIRS = $(SUBDIRS) -DIST_SUBDIRS = $(SUBDIRS) -EXTRA_DIST = hydra-module.nix - -install-data-local: hydra-module.nix - $(INSTALL) -d $(DESTDIR)$(datadir)/nix - $(INSTALL_DATA) hydra-module.nix $(DESTDIR)$(datadir)/nix/ diff --git a/Procfile b/Procfile index 45a1e8fe..42e57a21 100644 --- a/Procfile +++ b/Procfile @@ -1,5 +1,6 @@ -hydra-server: ./foreman/start-hydra.sh -hydra-queue-runner: ./foreman/start-queue-runner.sh hydra-evaluator: ./foreman/start-evaluator.sh +hydra-queue-runner: ./foreman/start-queue-runner.sh hydra-notify: ./foreman/start-notify.sh +hydra-server: ./foreman/start-hydra.sh +manual: ./foreman/start-manual.sh postgres: ./foreman/start-postgres.sh diff --git a/README.md b/README.md index 100e24f2..5ff24eb2 100644 --- a/README.md +++ b/README.md @@ -23,41 +23,41 @@ Running Hydra is currently only supported on NixOS. The [hydra module](https://g } ``` ### Creating An Admin User -Once the Hydra service has been configured as above and activate you should already be able to access the UI interface at the specified URL. However some actions require an admin user which has to be created first: +Once the Hydra service has been configured as above and activated, you should already be able to access the UI interface at the specified URL. However some actions require an admin user which has to be created first: ``` $ su - hydra $ hydra-create-user --full-name '' \ - --email-address '' --password --role admin + --email-address '' --password-prompt --role admin ``` -Afterwards you should be able to log by clicking on "_Sign In_" on the top right of the web interface using the credentials specified by `hydra-crate-user`. Once you are logged in you can click "_Admin -> Create Project_" to configure your first project. +Afterwards you should be able to log by clicking on "_Sign In_" on the top right of the web interface using the credentials specified by `hydra-create-user`. Once you are logged in you can click "_Admin -> Create Project_" to configure your first project. ### Creating A Simple Project And Jobset -In order to evaluate and build anything you need to crate _projects_ that contain _jobsets_. Hydra supports imperative and declarative projects and many different configurations. The steps below will guide you through the required steps to creating a minimal imperative project configuration. +In order to evaluate and build anything you need to create _projects_ that contain _jobsets_. Hydra supports imperative and declarative projects and many different configurations. The steps below will guide you through the required steps to creating a minimal imperative project configuration. #### Creating A Project -Log in as adminstrator, click "_Admin_" and select "_Create project_". Fill the form as follows: +Log in as administrator, click "_Admin_" and select "_Create project_". Fill the form as follows: -- **Identifier**: `hello` +- **Identifier**: `hello-project` - **Display name**: `hello` - **Description**: `hello project` Click "_Create project_". #### Creating A Jobset -After creating a project you are forwarded to the project page. Click "_Actions_" and choose "_Create jobset_". Fill the form with the following values: +After creating a project you are forwarded to the project page. Click "_Actions_" and choose "_Create jobset_". Change **Type** to Legacy for the example below. Fill the form with the following values: -- **Identifier**: `hello` +- **Identifier**: `hello-project` - **Nix expression**: `examples/hello.nix` in `hydra` - **Check interval**: 60 - **Scheduling shares**: 1 -We have to add two inputs for this jobset. One for _nixpkgs_ and one for _hydra_ (which we are referrencing in the Nix expression above): +We have to add two inputs for this jobset. One for _nixpkgs_ and one for _hydra_ (which we are referencing in the Nix expression above): - **Input name**: `nixpkgs` - **Type**: `Git checkout` -- **Value**: `https://github.com/nixos/nixpkgs-channels nixos-20.03` +- **Value**: `https://github.com/NixOS/nixpkgs nixos-24.05` - **Input name**: `hydra` - **Type**: `Git checkout` @@ -72,40 +72,73 @@ Make sure **State** at the top of the page is set to "_Enabled_" and click on "_ You can build Hydra via `nix-build` using the provided [default.nix](./default.nix): ``` -$ nix-build +$ nix build ``` ### Development Environment You can use the provided shell.nix to get a working development environment: ``` -$ nix-shell -$ ./bootstrap -$ configurePhase # NOTE: not ./configure -$ make +$ nix develop +$ ln -svf ../../../build/src/bootstrap src/root/static/bootstrap +$ ln -svf ../../../build/src/fontawesome src/root/static/fontawesome +$ ln -svf ../../../../build/src/flot src/root/static/js/flot +$ meson setup build +$ ninja -C build ``` +The development environment can also automatically be established using [nix-direnv](https://github.com/nix-community/nix-direnv). + ### Executing Hydra During Development When working on new features or bug fixes you need to be able to run Hydra from your working copy. This can be done using [foreman](https://github.com/ddollar/foreman): ``` -$ nix-shell +$ nix develop $ # hack hack -$ make +$ ninja -C build $ foreman start ``` Have a look at the [Procfile](./Procfile) if you want to see how the processes are being started. In order to avoid conflicts with services that might be running on your host, hydra and postgress are started on custom ports: -- hydra-server: 63333 -- postgresql: 64444 +- hydra-server: 63333 with the username "alice" and the password "foobar" +- postgresql: 64444, can be connected to using `psql -p 64444 -h localhost hydra` Note that this is only ever meant as an ad-hoc way of executing Hydra during development. Please make use of the NixOS module for actually running Hydra in production. +### Checking your patches + +After making your changes, verify the test suite passes and perlcritic is still happy. + +Start by following the steps in [Development Environment](#development-environment). + +Then, you can run the tests and the perlcritic linter together with: + +```console +$ nix develop +$ ninja -C build test +``` + +You can run a single test with: + +``` +$ nix develop +$ cd build +$ meson test --test-args=../t/Hydra/Event.t testsuite +``` + +And you can run just perlcritic with: + +``` +$ nix develop +$ cd build +$ meson test perlcritic +``` + ### JSON API You can also interface with Hydra through a JSON API. The API is defined in [hydra-api.yaml](./hydra-api.yaml) and you can test and explore via the [swagger editor](https://editor.swagger.io/?url=https://raw.githubusercontent.com/NixOS/hydra/master/hydra-api.yaml) @@ -113,7 +146,7 @@ You can also interface with Hydra through a JSON API. The API is defined in [hyd ## Additional Resources - [Hydra User's Guide](https://nixos.org/hydra/manual/) -- [Hydra on the NixOS Wiki](https://nixos.wiki/wiki/Hydra) +- [Hydra on the NixOS Wiki](https://wiki.nixos.org/wiki/Hydra) - [hydra-cli](https://github.com/nlewo/hydra-cli) - [Peter Simons - Hydra: Setting up your own build farm (NixOS)](https://www.youtube.com/watch?v=RXV0Y5Bn-QQ) diff --git a/bootstrap b/bootstrap deleted file mode 100755 index 091b0ee4..00000000 --- a/bootstrap +++ /dev/null @@ -1,2 +0,0 @@ -#! /bin/sh -e -exec autoreconf -vfi diff --git a/configure.ac b/configure.ac deleted file mode 100644 index baff26c2..00000000 --- a/configure.ac +++ /dev/null @@ -1,81 +0,0 @@ -AC_INIT([Hydra], [m4_esyscmd([echo -n $(cat ./version)$VERSION_SUFFIX])]) -AC_CONFIG_AUX_DIR(config) -AM_INIT_AUTOMAKE([foreign serial-tests]) - -AC_LANG([C++]) - -AC_PROG_CC -AC_PROG_INSTALL -AC_PROG_LN_S -AC_PROG_LIBTOOL -AC_PROG_CXX - -CXXFLAGS+=" -std=c++17" - -AC_PATH_PROG([XSLTPROC], [xsltproc]) - -AC_ARG_WITH([docbook-xsl], - [AS_HELP_STRING([--with-docbook-xsl=PATH], - [path of the DocBook XSL stylesheets])], - [docbookxsl="$withval"], - [docbookxsl="/docbook-xsl-missing"]) -AC_SUBST([docbookxsl]) - - -AC_DEFUN([NEED_PROG], -[ -AC_PATH_PROG($1, $2) -if test -z "$$1"; then - AC_MSG_ERROR([$2 is required]) -fi -]) - -NEED_PROG(perl, perl) - -NEED_PROG([NIX_STORE_PROGRAM], [nix-store]) - -AC_MSG_CHECKING([whether $NIX_STORE_PROGRAM is recent enough]) -if test -n "$NIX_STORE" -a -n "$TMPDIR" -then - # This may be executed from within a build chroot, so pacify - # `nix-store' instead of letting it choke while trying to mkdir - # /nix/var. - NIX_STATE_DIR="$TMPDIR" - export NIX_STATE_DIR -fi -if NIX_REMOTE=daemon PAGER=cat "$NIX_STORE_PROGRAM" --timeout 123 -q; then - AC_MSG_RESULT([yes]) -else - AC_MSG_RESULT([no]) - AC_MSG_ERROR([`$NIX_STORE_PROGRAM' doesn't support `--timeout'; please use a newer version.]) -fi - -PKG_CHECK_MODULES([NIX], [nix-main nix-expr nix-store]) - -testPath="$(dirname $(type -p expr))" -AC_SUBST(testPath) - -CXXFLAGS+=" -include nix/config.h" - -AC_CONFIG_FILES([ - Makefile - doc/Makefile - doc/manual/Makefile - src/Makefile - src/hydra-evaluator/Makefile - src/hydra-eval-jobs/Makefile - src/hydra-queue-runner/Makefile - src/sql/Makefile - src/ttf/Makefile - src/lib/Makefile - src/root/Makefile - src/script/Makefile - tests/Makefile - tests/jobs/config.nix -]) - -AC_CONFIG_COMMANDS([executable-scripts], []) - -AC_CONFIG_HEADER([hydra-config.h]) - -AC_OUTPUT diff --git a/default.nix b/default.nix index 32d510a3..b81119c3 100644 --- a/default.nix +++ b/default.nix @@ -1,6 +1,6 @@ # The `default.nix` in flake-compat reads `flake.nix` and `flake.lock` from `src` and # returns an attribute set of the shape `{ defaultNix, shellNix }` -(import (fetchTarball https://github.com/edolstra/flake-compat/archive/master.tar.gz) { - src = builtins.fetchGit ./.; +(import (fetchTarball "https://github.com/edolstra/flake-compat/archive/master.tar.gz") { + src = ./.; }).defaultNix diff --git a/doc/Makefile.am b/doc/Makefile.am deleted file mode 100644 index 9ac91d24..00000000 --- a/doc/Makefile.am +++ /dev/null @@ -1,4 +0,0 @@ -SUBDIRS = manual -BOOTCLEAN_SUBDIRS = $(SUBDIRS) -DIST_SUBDIRS = $(SUBDIRS) - diff --git a/doc/architecture.md b/doc/architecture.md new file mode 100644 index 00000000..ec67bd37 --- /dev/null +++ b/doc/architecture.md @@ -0,0 +1,129 @@ +This is a rough overview from informal discussions and explanations of inner workings of Hydra. +You can use it as a guide to navigate the codebase or ask questions. + +## Architecture + +### Components + +- Postgres database + - configuration + - build queue + - what is already built + - what is going to build +- `hydra-server` + - Perl, Catalyst + - web frontend +- `hydra-evaluator` + - Perl, C++ + - fetches repositories + - evaluates job sets + - pointers to a repository + - adds builds to the queue +- `hydra-queue-runner` + - C++ + - monitors the queue + - executes build steps + - uploads build results + - copy to a Nix store +- Nix store + - contains `.drv`s + - populated by `hydra-evaluator` + - read by `hydra-queue-runner` +- destination Nix store + - can be a binary cache + - e.g. `[cache.nixos.org](http://cache.nixos.org)` or the same store again (for small Hydra instances) +- plugin architecture + - extend evaluator for new kinds of repositories + - e.g. fetch from `git` + +### Database Schema + +[https://github.com/NixOS/hydra/blob/master/src/sql/hydra.sql](https://github.com/NixOS/hydra/blob/master/src/sql/hydra.sql) + +- `Jobsets` + - populated by calling Nix evaluator + - every Nix derivation in `release.nix` is a Job + - `flake` + - URL to flake, if job is from a flake + - single-point of configuration for flake builds + - flake itself contains pointers to dependencies + - for other builds we need more configuration data +- `JobsetInputs` + - more configuration for a Job +- `JobsetInputAlts` + - historical, where you could have more than one alternative for each input + - it would have done the cross product of all possibilities + - not used any more, as now every input is unique + - originally that was to have alternative values for the system parameter + - `x86-linux`, `x86_64-darwin` + - turned out not to be a good idea, as job set names did not uniquely identify output +- `Builds` + - queue: scheduled and finished builds + - instance of a Job + - corresponds to a top-level derivation + - can have many dependencies that don’t have a corresponding build + - dependencies represented as `BuildSteps` + - a Job is all the builds with a particular name, e.g. + - `git.x86_64-linux` is a job + - there maybe be multiple builds for that job + - build ID: just an auto-increment number + - building one thing can actually cause many (hundreds of) derivations to be built + - for queued builds, the `drv` has to be present in the store + - otherwise build will fail, e.g. after garbage collection +- `BuildSteps` + - corresponds to a derivation or substitution + - are reused through the Nix store + - may be duplicated for unique derivations due to how they relate to `Jobs` +- `BuildStepOutputs` + - corresponds directly to derivation outputs + - `out`, `dev`, ... +- `BuildProducts` + - not a Nix concept + - populated from a special file `$out/nix-support/hydra-build-producs` + - used to scrape parts of build results out to the web frontend + - e.g. manuals, ISO images, etc. +- `BuildMetrics` + - scrapes data from magic location, similar to `BuildProducts` to show fancy graphs + - e.g. test coverage, build times, CPU utilization for build + - `$out/nix-support/hydra-metrics` +- `BuildInputs` + - probably obsolute +- `JobsetEvalMembers` + - joins evaluations with jobs + - huge table, 10k’s of entries for one `nixpkgs` evaluation + - can be imagined as a subset of the eval cache + - could in principle use the eval cache + +### `release.nix` + +- hydra-specific convention to describe the build +- should evaluate to an attribute set that contains derivations +- hydra considers every attribute in that set a job +- every job needs a unique name + - if you want to build for multiple platforms, you need to reflect that in the name +- hydra does a deep traversal of the attribute set + - just evaluating the names may take half an hour + +## FAQ + +Can we imagine Hydra to be a persistence layer for the build graph? + +- partially, it lacks a lot of information + - does not keep edges of the build graph + +How does Hydra relate to `nix build`? + +- reimplements the top level Nix build loop, scheduling, etc. +- Hydra has to persist build results +- Hydra has more sophisticated remote build execution and scheduling than Nix + +Is it conceptually possible to unify Hydra’s capabilities with regular Nix? + +- Nix does not have any scheduling, it just traverses the build graph +- Hydra has scheduling in terms of job set priorities, tracks how much of a job set it has worked on + - makes sure jobs don’t starve each other +- Nix cannot dynamically add build jobs at runtime + - [RFC 92](https://github.com/NixOS/rfcs/blob/master/rfcs/0092-plan-dynamism.md) should enable that + - internally it is already possible, but there is no interface to do that +- Hydra queue runner is a long running process + - Nix takes a static set of jobs, working it off at once diff --git a/doc/dev-notes.txt b/doc/dev-notes.txt index caadb57d..4035c809 100644 --- a/doc/dev-notes.txt +++ b/doc/dev-notes.txt @@ -13,7 +13,7 @@ * Creating a user: $ hydra-create-user root --email-address 'e.dolstra@tudelft.nl' \ - --password-hash "$(echo -n foobar | sha1sum | cut -c1-40)" + --password-prompt (Replace "foobar" with the desired password.) diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am deleted file mode 100644 index 10c8f6ee..00000000 --- a/doc/manual/Makefile.am +++ /dev/null @@ -1,33 +0,0 @@ -DOCBOOK_FILES = installation.xml introduction.xml manual.xml projects.xml hacking.xml - -EXTRA_DIST = $(DOCBOOK_FILES) - -xsltproc_opts = \ - --param callout.graphics.extension \'.gif\' \ - --param section.autolabel 1 \ - --param section.label.includes.component.label 1 - - -# Include the manual in the tarball. -dist_html_DATA = manual.html - -# Embed Docbook's callout images in the distribution. -EXTRA_DIST += images - -manual.html: $(DOCBOOK_FILES) - $(XSLTPROC) $(xsltproc_opts) --nonet --xinclude \ - --output manual.html \ - $(docbookxsl)/xhtml/docbook.xsl manual.xml - -images: - $(MKDIR_P) images/callouts - cp $(docbookxsl)/images/callouts/*.gif images/callouts - chmod +wx images images/callouts - -install-data-hook: images - $(INSTALL) -d $(DESTDIR)$(htmldir)/images/callouts - $(INSTALL_DATA) images/callouts/* $(DESTDIR)$(htmldir)/images/callouts - ln -sfn manual.html $(DESTDIR)$(htmldir)/index.html - -distclean-hook: - -rm -rf images diff --git a/doc/manual/api.xml b/doc/manual/api.xml deleted file mode 100644 index 6656d98b..00000000 --- a/doc/manual/api.xml +++ /dev/null @@ -1,335 +0,0 @@ - - - Using the external API - - - To be able to create integrations with other services, Hydra exposes - an external API that you can manage projects with. - - - - The API is accessed over HTTP(s) where all data is sent and received - as JSON. - - - - Creating resources requires the caller to be authenticated, while - retrieving resources does not. - - - - The API does not have a separate URL structure for it's endpoints. - Instead you request the pages of the web interface as - application/json to use the API. - - -
- List projects - - - To list all the projects of the Hydra install: - - - -GET / -Accept: application/json - - - - This will give you a list of projects, where each - project contains general information and a list - of its job sets. - - - - Example - - - -curl -i -H 'Accept: application/json' \ - https://hydra.nixos.org - - - - Note: this response is truncated - - - -GET https://hydra.nixos.org/ -HTTP/1.1 200 OK -Content-Type: application/json - -[ - { - "displayname": "Acoda", - "name": "acoda", - "description": "Acoda is a tool set for automatic data migration along an evolving data model", - "enabled": 0, - "owner": "sander", - "hidden": 1, - "jobsets": [ - "trunk" - ] - }, - { - "displayname": "cabal2nix", - "name": "cabal2nix", - "description": "Convert Cabal files into Nix build instructions", - "enabled": 0, - "owner": "simons@cryp.to", - "hidden": 1, - "jobsets": [ - "master" - ] - } -] - -
- -
- Get a single project - - - To get a single project by identifier: - - - -GET /project/:project-identifier -Accept: application/json - - - - Example - - - -curl -i -H 'Accept: application/json' \ - https://hydra.nixos.org/project/hydra - - - -GET https://hydra.nixos.org/project/hydra -HTTP/1.1 200 OK -Content-Type: application/json - -{ - "description": "Hydra, the Nix-based continuous build system", - "hidden": 0, - "displayname": "Hydra", - "jobsets": [ - "hydra-master", - "hydra-ant-logger-trunk", - "master", - "build-ng" - ], - "name": "hydra", - "enabled": 1, - "owner": "eelco" -} - - -
- -
- Get a single job set - - - To get a single job set by identifier: - - - -GET /jobset/:project-identifier/:jobset-identifier -Content-Type: application/json - - - - Example - - - -curl -i -H 'Accept: application/json' \ - https://hydra.nixos.org/jobset/hydra/build-ng - - - -GET https://hydra.nixos.org/jobset/hydra/build-ng -HTTP/1.1 200 OK -Content-Type: application/json - -{ - "errormsg": "evaluation failed due to signal 9 (Killed)", - "fetcherrormsg": null, - "nixexprpath": "release.nix", - "nixexprinput": "hydraSrc", - "emailoverride": "rob.vermaas@gmail.com, eelco.dolstra@logicblox.com", - "jobsetinputs": { - "officialRelease": { - "jobsetinputalts": [ - "false" - ] - }, - "hydraSrc": { - "jobsetinputalts": [ - "https://github.com/NixOS/hydra.git build-ng" - ] - }, - "nixpkgs": { - "jobsetinputalts": [ - "https://github.com/NixOS/nixpkgs.git release-14.12" - ] - } - }, - "enabled": 0 -} - -
- -
- List evaluations - - - To list the evaluations of a - job set by identifier: - - - -GET /jobset/:project-identifier/:jobset-identifier/evals -Content-Type: application/json - - - - Example - - - -curl -i -H 'Accept: application/json' \ - https://hydra.nixos.org/jobset/hydra/build-ng/evals - - - - Note: this response is truncated - - - -GET https://hydra.nixos.org/jobset/hydra/build-ng/evals -HTTP/1.1 200 OK -Content-Type: application/json - -{ - "evals": [ - { - "jobsetevalinputs": { - "nixpkgs": { - "dependency": null, - "type": "git", - "value": null, - "uri": "https://github.com/NixOS/nixpkgs.git", - "revision": "f60e48ce81b6f428d072d3c148f6f2e59f1dfd7a" - }, - "hydraSrc": { - "dependency": null, - "type": "git", - "value": null, - "uri": "https://github.com/NixOS/hydra.git", - "revision": "48d6f0de2ab94f728d287b9c9670c4d237e7c0f6" - }, - "officialRelease": { - "dependency": null, - "value": "false", - "type": "boolean", - "uri": null, - "revision": null - } - }, - "hasnewbuilds": 1, - "builds": [ - 24670686, - 24670684, - 24670685, - 24670687 - ], - "id": 1213758 - } - ], - "first": "?page=1", - "last": "?page=1" -} - -
- -
- Get a single build - - - To get a single build by its id: - - - -GET /build/:build-id -Content-Type: application/json - - - - Example - - - -curl -i -H 'Accept: application/json' \ - https://hydra.nixos.org/build/24670686 - - - -GET /build/24670686 -HTTP/1.1 200 OK -Content-Type: application/json - -{ - "job": "tests.api.x86_64-linux", - "jobsetevals": [ - 1213758 - ], - "buildstatus": 0, - "buildmetrics": null, - "project": "hydra", - "system": "x86_64-linux", - "priority": 100, - "releasename": null, - "starttime": 1439402853, - "nixname": "vm-test-run-unnamed", - "timestamp": 1439388618, - "id": 24670686, - "stoptime": 1439403403, - "jobset": "build-ng", - "buildoutputs": { - "out": { - "path": "/nix/store/lzrxkjc35mhp8w7r8h82g0ljyizfchma-vm-test-run-unnamed" - } - }, - "buildproducts": { - "1": { - "path": "/nix/store/lzrxkjc35mhp8w7r8h82g0ljyizfchma-vm-test-run-unnamed", - "sha1hash": null, - "defaultpath": "log.html", - "type": "report", - "sha256hash": null, - "filesize": null, - "name": "", - "subtype": "testlog" - } - }, - "finished": 1 -} - -
- -
- - diff --git a/doc/manual/declarative-projects.xml b/doc/manual/declarative-projects.xml deleted file mode 100644 index 1c9e76c1..00000000 --- a/doc/manual/declarative-projects.xml +++ /dev/null @@ -1,96 +0,0 @@ -
- - Declarative projects - - Hydra also supports declarative projects, where jobsets are generated - and configured automatically from specification files instead of being - managed through the UI. A jobset specification is a JSON object - containing the configuration of the jobset, for example: - - - { - "enabled": 1, - "hidden": false, - "description": "js", - "nixexprinput": "src", - "nixexprpath": "release.nix", - "checkinterval": 300, - "schedulingshares": 100, - "enableemail": false, - "emailoverride": "", - "keepnr": 3, - "inputs": { - "src": { "type": "git", "value": "git://github.com/shlevy/declarative-hydra-example.git", "emailresponsible": false }, - "nixpkgs": { "type": "git", "value": "git://github.com/NixOS/nixpkgs.git release-16.03", "emailresponsible": false } - } - } - - - To configure a declarative project, take the following steps: - - - - - Create a jobset repository in the normal way (e.g. a git repo with - a release.nix file, any other needed helper - files, and taking any kind of hydra input), but without adding it - to the UI. The nix expression of this repository should contain a - single job, named jobsets. The output of the - jobsets job should be a JSON file containing an - object of jobset specifications. Each member of the object will - become a jobset of the project, configured by the corresponding - jobset specification. - - - - - In some hydra-fetchable source (potentially, but not necessarily, - the same repo you created in step 1), create a JSON file - containing a jobset specification that points to the jobset - repository you created in the first step, specifying any needed - inputs (e.g. nixpkgs) as necessary. - - - - - In the project creation/edit page, set declarative input type, - declarative input value, and declarative spec file to point to the - source and JSON file you created in step 2. - - - - - Hydra will create a special jobset named .jobsets, - which whenever evaluated will go through the steps above in reverse - order: - - - - - Hydra will fetch the input specified by the declarative input type - and value. - - - - - Hydra will use the configuration given in the declarative spec - file as the jobset configuration for this evaluation. In addition - to any inputs specified in the spec file, hydra will also pass the - declInput argument corresponding to the input - fetched in step 1. - - - - - As normal, hydra will build the jobs specified in the jobset - repository, which in this case is the single - jobsets job. When that job completes, hydra - will read the created jobset specifications and create - corresponding jobsets in the project, disabling any jobsets that - used to exist but are not present in the current spec. - - - -
diff --git a/doc/manual/hacking.xml b/doc/manual/hacking.xml deleted file mode 100644 index 20cac842..00000000 --- a/doc/manual/hacking.xml +++ /dev/null @@ -1,39 +0,0 @@ - - -Hacking - -This section provides some notes on how to hack on Hydra. To -get the latest version of Hydra from GitHub: - -$ git clone git://github.com/NixOS/hydra.git -$ cd hydra - - - -To build it and its dependencies: - -$ nix-build release.nix -A build.x86_64-linux - - - -To build all dependencies and start a shell in which all -environment variables (such as PERL5LIB) are set up so -that those dependencies can be found: - -$ nix-shell - -To build Hydra, you should then do: - -[nix-shell]$ ./bootstrap -[nix-shell]$ configurePhase -[nix-shell]$ make - -You can run the Hydra web server in your source tree as follows: - -$ ./src/script/hydra-server - - - - diff --git a/doc/manual/installation.xml b/doc/manual/installation.xml deleted file mode 100644 index cb94dbbe..00000000 --- a/doc/manual/installation.xml +++ /dev/null @@ -1,282 +0,0 @@ - - - Installation - - - This chapter explains how to install Hydra on your own build farm server. - - -
- Prerequisites - - To install and use Hydra you need to have installed the following dependencies: - - - Nix - PostgreSQL - many Perl packages, notably Catalyst, EmailSender, - and NixPerl (see the Hydra - expression in Nixpkgs for the complete - list) - - - At the moment, Hydra runs only on GNU/Linux - (i686-linux and - x86_64_linux). - - - - For small projects, Hydra can be run on any reasonably modern - machine. For individual projects you can even run Hydra on a - laptop. However, the charm of a buildfarm server is usually that - it operates without disturbing the developer's working - environment and can serve releases over the internet. In - conjunction you should typically have your source code - administered in a version management system, such as - subversion. Therefore, you will probably want to install a - server that is connected to the internet. To scale up to large - and/or many projects, you will need at least a considerable - amount of diskspace to store builds. Since Hydra can schedule - multiple simultaneous build jobs, it can be useful to have a - multi-core machine, and/or attach multiple build machines in a - network to the central Hydra server. - - - - Of course we think it is a good idea to use the NixOS GNU/Linux - distribution for your buildfarm server. But this is not a - requirement. The Nix software deployment system can be - installed on any GNU/Linux distribution in parallel to the - regular package management system. Thus, you can use Hydra on a - Debian, Fedora, SuSE, or Ubuntu system. - - -
- -
- Getting Nix - - - If your server runs NixOS you are all set to continue with - installation of Hydra. Otherwise you first need to install Nix. - The latest stable version can be found one the Nix web - site, along with a manual, which includes installation - instructions. - -
- -
- Installation - - - - - The latest development snapshot of Hydra can be installed - by visiting the URL http://hydra.nixos.org/view/hydra/unstable - and using the one-click install available at one of the build - pages. You can also install Hydra through the channel by - performing the following commands: - - -nix-channel --add http://hydra.nixos.org/jobset/hydra/master/channel/latest -nix-channel --update -nix-env -i hydra - - - - Command completion should reveal a number of command-line tools - from Hydra, such as hydra-queue-runner. - -
- -
- Creating the database - - Hydra stores its results in a PostgreSQL database. - - - - To setup a PostgreSQL database with hydra - as database name and user name, issue the following commands on - the PostgreSQL server: - - -createuser -S -D -R -P hydra -createdb -O hydra hydra - - Note that $prefix is the location of Hydra - in the nix store. - - - - Hydra uses an environment variable to know which database should - be used, and a variable which point to a location that holds - some state. To set these variables for a PostgreSQL database, - add the following to the file ~/.profile of - the user running the Hydra services. - - -export HYDRA_DBI="dbi:Pg:dbname=hydra;host=dbserver.example.org;user=hydra;" -export HYDRA_DATA=/var/lib/hydra - - You can provide the username and password in the file - ~/.pgpass, e.g. - - -dbserver.example.org:*:hydra:hydra:password - - Make sure that the HYDRA_DATA directory - exists and is writable for the user which will run the Hydra - services. - - - - Having set these environment variables, you can now initialise - the database by doing: - -hydra-init - - - - To create projects, you need to create a user with - admin privileges. This can be done using - the command hydra-create-user: - - -$ hydra-create-user alice --full-name 'Alice Q. User' \ - --email-address 'alice@example.org' --password foobar --role admin - - - Additional users can be created through the web interface. - - -
- -
- Upgrading - - If you're upgrading Hydra from a previous version, you - should do the following to perform any necessary database schema migrations: - -hydra-init - - -
- -
- Getting Started - - - To start the Hydra web server, execute: - -hydra-server - - When the server is started, you can browse to - http://localhost:3000/ to start configuring - your Hydra instance. - - - - The hydra-server command launches the web - server. There are two other processes that come into play: - - - - The evaluator is responsible for - periodically evaluating job sets, checking out their - dependencies off their version control systems (VCS), and - queueing new builds if the result of the evaluation changed. - It is launched by the hydra-evaluator - command. - - - The queue runner launches builds (using - Nix) as they are queued by the evaluator, scheduling them - onto the configured Nix hosts. It is launched using the - hydra-queue-runner command. - - - - All three processes must be running for Hydra to be fully - functional, though it's possible to temporarily stop any one of - them for maintenance purposes, for instance. - - -
- -
- Serving behind reverse proxy - - - To serve hydra web server behind reverse proxy like - nginx or httpd some - additional configuration must be made. - - - - Edit your hydra.conf file in a similar way to - this example: - - -using_frontend_proxy 1 -base_uri example.com - - base_uri should be your hydra servers proxied URL. - - If you are using Hydra nixos module then setting hydraURL - option should be enough. - - - - - - If you want to serve Hydra with a prefix path, for example - http://example.com/hydra then you need to configure your - reverse proxy to pass X-Request-Base to hydra, with - prefix path as value. - - For example if you are using nginx, then use configuration similar to following: - -server { - listen 433 ssl; - server_name example.com; - .. other configuration .. - location /hydra/ { - - proxy_pass http://127.0.0.1:3000; - proxy_redirect http://127.0.0.1:3000 https://example.com/hydra; - - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Request-Base /hydra; - } -} - - -
-
- - diff --git a/doc/manual/introduction.xml b/doc/manual/introduction.xml deleted file mode 100644 index 956732f1..00000000 --- a/doc/manual/introduction.xml +++ /dev/null @@ -1,267 +0,0 @@ - - - Introduction - -
- About Hydra - - - Hydra is a tool for continuous integration testing and software - release that uses a purely functional language to describe build jobs - and their dependencies. Continuous integration is a simple technique - to improve the quality of the software development process. An - automated system continuously or periodically checks out the source - code of a project, builds it, runs tests, and produces reports for the - developers. Thus, various errors that might accidentally be committed - into the code base are automatically caught. Such a system allows - more in-depth testing than what developers could feasibly do manually: - - - Portability testing: The - software may need to be built and tested on many different - platforms. It is infeasible for each developer to do this - before every commit. - - - Likewise, many projects have very large test sets - (e.g., regression tests in a compiler, or stress tests in a - DBMS) that can take hours or days to run to completion. - - - Many kinds of static and dynamic analyses can be - performed as part of the tests, such as code coverage runs and - static analyses. - - - It may also be necessary to build many different - variants of the software. For instance, - it may be necessary to verify that the component builds with - various versions of a compiler. - - - Developers typically use incremental building to - test their changes (since a full build may take too long), but - this is unreliable with many build management tools (such as - Make), i.e., the result of the incremental build might differ - from a full build. - - - It ensures that the software can be built from the - sources under revision control. Users of version management - systems such as CVS and Subversion often forget to place - source files under revision control. - - - The machines on which the continuous integration - system runs ideally provides a clean, well-defined build - environment. If this environment is administered through - proper SCM techniques, then builds produced by the system can - be reproduced. In contrast, developer work environments are - typically not under any kind of SCM control. - - - In large projects, developers often work on a - particular component of the project, and do not build and test - the composition of those components (again since this is - likely to take too long). To prevent the phenomenon of ``big - bang integration'', where components are only tested together - near the end of the development process, it is important to - test components together as soon as possible (hence - continuous integration). - - - It allows software to be - released by automatically creating - packages that users can download and install. To do this - manually represents an often prohibitive amount of work, as - one may want to produce releases for many different platforms: - e.g., installers for Windows and Mac OS X, RPM or Debian - packages for certain Linux distributions, and so on. - - - - - - - In its simplest form, a continuous integration tool sits in a - loop building and releasing software components from a version - management system. For each component, it performs the - following tasks: - - - - It obtains the latest version of the component's - source code from the version management system. - - - It runs the component's build process (which - presumably includes the execution of the component's test - set). - - - It presents the results of the build (such as error - logs and releases) to the developers, e.g., by producing a web - page. - - - - - Examples of continuous integration tools include Jenkins, - CruiseControl Tinderbox, Sisyphus, Anthill and BuildBot. These - tools have various limitations. - - - - They do not manage the build - environment. The build environment consists of the - dependencies necessary to perform a build action, e.g., - compilers, libraries, etc. Setting up the environment is - typically done manually, and without proper SCM control (so it - may be hard to reproduce a build at a later time). Manual - management of the environment scales poorly in the number of - configurations that must be supported. For instance, suppose - that we want to build a component that requires a certain - compiler X. We then have to go to each machine and install X. - If we later need a newer version of X, the process must be - repeated all over again. An ever worse problem occurs if - there are conflicting, mutually exclusive versions of the - dependencies. Thus, simply installing the latest version is - not an option. Of course, we can install these components in - different directories and manually pass the appropriate paths - to the build processes of the various components. But this is - a rather tiresome and error-prone process. - - - They do not easily support variability in software - systems. A system may have a great deal of build-time - variability: optional functionality, whether to build a debug or - production version, different versions of dependencies, and so on. - (For instance, the Linux kernel now has over 2,600 build-time - configuration switches.) It is therefore important that a continuous - integration tool can easily select and test different instances from - the configuration space of the system to reveal problems, such as - erroneous interactions between features. In a continuous integration - setting, it is also useful to test different combinations of versions - of subsystems, e.g., the head revision of a component against stable - releases of its dependencies, and vice versa, as this can reveal - various integration problems. - - - - - - - Hydra, is a continuous integration tool - that solves these problems. It is built on top of the Nix package manager, - which has a purely functional language for describing package - build actions and their dependencies. This allows the build - environment for projects to be produced automatically and - deterministically, and variability in components to be expressed - naturally using functions; and as such is an ideal fit for a - continuous build system. - - -
- -
- About Us - - - Hydra is the successor of the Nix Buildfarm, which was developed - in tandem with the Nix software deployment system. Nix was - originally developed at the Department of Information and - Computing Sciences, Utrecht University by the TraCE project - (2003-2008). The project was funded by the Software Engineering - Research Program Jacquard to improve the support for variability - in software systems. Funding for the development of Nix and - Hydra is now provided by the NIRICT LaQuSo Build Farm project. - -
- -
- About this Manual - - - This manual tells you how to install the Hydra buildfarm - software on your own server and how to operate that server using - its web interface. - -
- - -
- License - - - Hydra is free software: you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation, either version 3 of - the License, or (at your option) any later version. - - - - Hydra is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General - Public License for more details. - -
- -
- Hydra at <literal>nixos.org</literal> - - - The nixos.org installation of Hydra runs at - http://hydra.nixos.org/. - - That installation is used to build software components from the - Nix, - NixOS, - GNU, - Stratego/XT, - and related projects. - - - - If you are one of the developers on those projects, it is likely - that you will be using the NixOS Hydra server in some way. If - you need to administer automatic builds for your project, you - should pull the right strings to get an account on the - server. This manual will tell you how to set up new projects and - build jobs within those projects and write a release.nix file to - describe the build process of your project to Hydra. You can - skip the next chapter. - - - - If your project does not yet have automatic builds within the - NixOS Hydra server, it may actually be eligible. We are in the - process of setting up a large buildfarm that should be able to - support open source and academic software projects. Get in - touch. - -
- -
- Hydra on your own buildfarm - - - If you need to run your own Hydra installation, explains how to download and - install the system on your own server. - -
- -
- - diff --git a/doc/manual/manual.xml b/doc/manual/manual.xml deleted file mode 100644 index 75e53152..00000000 --- a/doc/manual/manual.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - Hydra User's Guide - - Draft - - - - - Eelco - Dolstra - - - Delft University of Technology - Department of Software Technology - - Author - - - - Rob - Vermaas - - - Delft University of Technology - Department of Software Technology - - Author - - - - Eelco - Visser - - - Delft University of Technology - Department of Software Technology - - Author - - - - Ludovic - Courtès - - Author - - - - - - 2009-2013 - Eelco Dolstra - - - March 2010 - - - - - - - - - - - diff --git a/doc/manual/meson.build b/doc/manual/meson.build new file mode 100644 index 00000000..11178809 --- /dev/null +++ b/doc/manual/meson.build @@ -0,0 +1,36 @@ +srcs = files( + 'src/SUMMARY.md', + 'src/about.md', + 'src/api.md', + 'src/configuration.md', + 'src/hacking.md', + 'src/installation.md', + 'src/introduction.md', + 'src/jobs.md', + 'src/monitoring/README.md', + 'src/notifications.md', + 'src/plugins/README.md', + 'src/plugins/RunCommand.md', + 'src/plugins/declarative-projects.md', + 'src/projects.md', + 'src/webhooks.md', +) + +manual = custom_target( + 'manual', + command: [ + mdbook, + 'build', + '@SOURCE_ROOT@/doc/manual', + '-d', meson.current_build_dir() / 'html' + ], + depend_files: srcs, + output: ['html'], + build_by_default: true, +) + +install_subdir( + manual.full_path(), + install_dir: get_option('datadir') / 'doc/hydra', + strip_directory: true, +) diff --git a/doc/manual/projects.xml b/doc/manual/projects.xml deleted file mode 100644 index 05791765..00000000 --- a/doc/manual/projects.xml +++ /dev/null @@ -1,496 +0,0 @@ - - - Creating and Managing Projects - - - Once Hydra is installed and running, the next step is to add - projects to the build farm. We follow the example of the Patchelf - project, a software tool written in C and using the GNU - Build System (GNU Autoconf and GNU Automake). - - - - Log in to the web interface of your Hydra installation using the - user name and password you inserted in the database (by default, - Hydra's web server listens on localhost:3000). - Then follow the "Create Project" link to create a new project. - - -
- Project Information - - - A project definition consists of some general information and a - set of job sets. The general information identifies a project, - its owner, and current state of activity. - - Here's what we fill in for the patchelf project: - - -Identifier: patchelf - - - The identifier is the identity of the - project. It is used in URLs and in the names of build results. - - - - The identifier should be a unique name (it is the primary - database key for the project table in the database). If you try - to create a project with an already existing identifier you'd - get an error message from the database. - - So try to create the project after entering just the general - information to figure out if you have chosen a unique name. - Job sets can be added once the project has been created. - - -Display name: Patchelf - - - The display name is used in menus. - - -Description: A tool for modifying ELF binaries - - - The description is used as short - documentation of the nature of the project. - - -Owner: eelco - - - The owner of a project can create and edit - job sets. - - -Enabled: Yes - - - Only if the project is enabled are builds - performed. - - - - Once created there should be an entry for the project in the - sidebar. Go to the project page for the Patchelf - project. - -
- -
- Job Sets - - - A project can consist of multiple job sets - (hereafter jobsets), separate tasks that - can be built separately, but may depend on each other (without - cyclic dependencies, of course). Go to the Edit - page of the Patchelf project and "Add a new jobset" by providing - the following "Information": - - -Identifier: trunk -Description: Trunk -Nix expression: release.nix in input patchelfSrc - - - This states that in order to build the trunk - jobset, the Nix expression in the file - release.nix, which can be obtained from - input patchelfSrc, should be - evaluated. (We'll have a look at - release.nix later.) - - - - - To realize a job we probably need a number of inputs, which can - be declared in the table below. As many inputs as required can - be added. For patchelf we declare the following inputs. - - -patchelfSrc -'Git checkout' https://github.com/NixOS/patchelf - -nixpkgs 'Git checkout' https://github.com/NixOS/nixpkgs - -officialRelease Boolean false - -system String value "i686-linux" - - -
- -
- Building Jobs -
- -
- Build Recipes - - - Build jobs and build recipes for a jobset are - specified in a text file written in the Nix language. The - recipe is actually called a Nix expression in - Nix parlance. By convention this file is often called - release.nix. - - - - The release.nix file is typically kept under - version control, and the repository that contains it one of the - build inputs of the corresponding–often called - hydraConfig by convention. The repository for - that file and the actual file name are specified on the web - interface of Hydra under the Setup tab of the - jobset's overview page, under the Nix - expression heading. See, for example, the jobset - overview page of the PatchELF project, and - the corresponding Nix file. - - - - Knowledge of the Nix language is recommended, but the example - below should already give a good idea of how it works: - - - - <filename>release.nix</filename> file for GNU Hello - -let - pkgs = import <nixpkgs> {}; - - jobs = rec { - - tarball = - pkgs.releaseTools.sourceTarball { - name = "hello-tarball"; - src = <hello>; - buildInputs = (with pkgs; [ gettext texLive texinfo ]); - }; - - build = - { system ? builtins.currentSystem }: - - let pkgs = import <nixpkgs> { inherit system; }; in - pkgs.releaseTools.nixBuild { - name = "hello"; - src = jobs.tarball; - configureFlags = [ "--disable-silent-rules" ]; - }; - }; -in - jobs - - - - - shows what a - release.nix file for GNU Hello - would look like. GNU Hello is representative of many GNU - and non-GNU free software projects: - - - it uses the GNU Build System, namely GNU Autoconf, - and GNU Automake; for users, it means it can be installed - using the usual - ./configure && make install - procedure; - - it uses Gettext for internationalization; - it has a Texinfo manual, which can be rendered as PDF - with TeX. - - - The file defines a jobset consisting of two jobs: - tarball, and build. It - contains the following elements (referenced from the figure by - numbers): - - - - - - This defines a variable pkgs holding - the set of packages provided by Nixpkgs. - - - Since nixpkgs appears in angle brackets, - there must be a build input of that name in the Nix search - path. In this case, the web interface should show a - nixpkgs build input, which is a checkout - of the Nixpkgs source code repository; Hydra then adds this - and other build inputs to the Nix search path when - evaluating release.nix. - - - - - - This defines a variable holding the two Hydra - jobs–an attribute set in Nix. - - - - - - This is the definition of the first job, named - tarball. The purpose of this job is to - produce a usable source code tarball. - - - - - The tarball job calls the - sourceTarball function, which (roughly) - runs autoreconf && ./configure && - make dist on the checkout. The - buildInputs attribute specifies - additional software dependencies for the - jobThe package names used in - buildInputs–e.g., - texLive–are the names of the - attributes corresponding to these - packages in Nixpkgs, specifically in the all-packages.nix - file. See the section entitled “Package Naming” in the - Nixpkgs manual for more information.. - - - - - The tarball jobs expects a - hello build input to be available in the - Nix search path. Again, this input is passed by Hydra and - is meant to be a checkout of GNU Hello's source code - repository. - - - - - - This is the definition of the build - job, whose purpose is to build Hello from the tarball - produced above. - - - - - The build function takes one - parameter, system, which should be a string - defining the Nix system type–e.g., - "x86_64-linux". Additionally, it refers - to jobs.tarball, seen above. - - - Hydra inspects the formal argument list of the function - (here, the system argument) and passes it - the corresponding parameter specified as a build input on - Hydra's web interface. Here, system is - passed by Hydra when it calls build. - Thus, it must be defined as a build input of type string in - Hydra, which could take one of several values. - - - The question mark after system defines - the default value for this argument, and is only useful when - debugging locally. - - - - - The build job calls the - nixBuild function, which unpacks the - tarball, then runs ./configure && make - && make check && make install. - - - - - - Finally, the set of jobs is returned to Hydra, as a Nix - attribute set. - - - - -
- -
- Building from the Command Line - - - It is often useful to test a build recipe, for instance before - it is actually used by Hydra, when testing changes, or when - debugging a build issue. Since build recipes for Hydra jobsets - are just plain Nix expressions, they can be evaluated using the - standard Nix tools. - - - - To evaluate the tarball jobset of , just run: - - -$ nix-build release.nix -A tarball - - - However, doing this with as is will - probably yield an error like this: - - -error: user-thrown exception: file `hello' was not found in the Nix search path (add it using $NIX_PATH or -I) - - - The error is self-explanatory. Assuming - $HOME/src/hello points to a checkout of - Hello, this can be fixed this way: - - -$ nix-build -I ~/src release.nix -A tarball - - - Similarly, the build jobset can be evaluated: - - -$ nix-build -I ~/src release.nix -A build - - - The build job reuses the result of the - tarball job, rebuilding it only if it needs to. - - -
- -
- Adding More Jobs - - - illustrates how to write the most - basic jobs, tarball and - build. In practice, much more can be done by - using features readily provided by Nixpkgs or by creating new jobs - as customizations of existing jobs. - - - - For instance, test coverage report for projects compiled with GCC - can be automatically generated using the - coverageAnalysis function provided by Nixpkgs - instead of nixBuild. Back to our GNU Hello - example, we can define a coverage job that - produces an HTML code coverage report directly readable from the - corresponding Hydra build page: - - -coverage = - { system ? builtins.currentSystem }: - - let pkgs = import nixpkgs { inherit system; }; in - pkgs.releaseTools.coverageAnalysis { - name = "hello"; - src = jobs.tarball; - configureFlags = [ "--disable-silent-rules" ]; - }; - - - As can be seen, the only difference compared to - build is the use of - coverageAnalysis. - - - - Nixpkgs provides many more build tools, including the ability to - run build in virtual machines, which can themselves run another - GNU/Linux distribution, which allows for the creation of packages - for these distributions. Please see the - pkgs/build-support/release directory - of Nixpkgs for more. The NixOS manual also contains information - about whole-system testing in virtual machine. - - - - Now, assume we want to build Hello with an old version of GCC, and - with different configure flags. A new - build_exotic job can be written that simply - overrides the relevant arguments passed to - nixBuild: - - -build_exotic = - { system ? builtins.currentSystem }: - - let - pkgs = import nixpkgs { inherit system; }; - build = jobs.build { inherit system; }; - in - pkgs.lib.overrideDerivation build (attrs: { - buildInputs = [ pkgs.gcc33 ]; - preConfigure = "gcc --version"; - configureFlags = - attrs.configureFlags ++ [ "--disable-nls" ]; - }); - - - The build_exotic job reuses - build and overrides some of its arguments: it - adds a dependency on GCC 3.3, a pre-configure phase that runs - gcc --version, and adds the - --disable-nls configure flags. - - - - This customization mechanism is very powerful. For instance, it - can be used to change the way Hello and all - its dependencies–including the C library and compiler used to - build it–are built. See the Nixpkgs manual for more. - - -
- - - -
- Email Notifications - - Hydra can send email notifications when the status of a build changes. This provides - immediate feedback to maintainers or committers when a change causes build failures. - - - - The simplest approach to enable Email Notifications is to use the ssmtp package, which - simply hands off the emails to another SMTP server. For details on how to configure ssmtp, - see the documentation for the networking.defaultMailServer option. - To use ssmtp for the Hydra email notifications, add it to the path option of the Hydra services - in your /etc/nixos/configuration.nix file: - -systemd.services.hydra-queue-runner.path = [ pkgs.ssmtp ]; -systemd.services.hydra-server.path = [ pkgs.ssmtp ]; - - -
- -
- - diff --git a/doc/manual/src/SUMMARY.md b/doc/manual/src/SUMMARY.md new file mode 100644 index 00000000..5ef8ac90 --- /dev/null +++ b/doc/manual/src/SUMMARY.md @@ -0,0 +1,20 @@ +# Hydra User's Guide + +- [Introduction](introduction.md) +- [Installation](installation.md) +- [Configuration](configuration.md) +- [Creating and Managing Projects](projects.md) +- [Hydra jobs](./jobs.md) +- [Plugins](./plugins/README.md) + - [Declarative Projects](./plugins/declarative-projects.md) + - [RunCommand](./plugins/RunCommand.md) +- [Using the external API](api.md) +- [Webhooks](webhooks.md) + - [Webhook Authentication Migration Guide](webhook-migration-guide.md) +- [Monitoring Hydra](./monitoring/README.md) + +## Developer's Guide +- [Hacking](hacking.md) +- [Hydra Notifications](notifications.md) +----------- +[About](about.md) diff --git a/doc/manual/src/about.md b/doc/manual/src/about.md new file mode 100644 index 00000000..6e65c55c --- /dev/null +++ b/doc/manual/src/about.md @@ -0,0 +1,6 @@ +# Authors + +* Eelco Dolstra, Delft University of Technology, Department of Software Technology +* Rob Vermaas, Delft University of Technology, Department of Software Technology +* Eelco Visser, Delft University of Technology, Department of Software Technology +* Ludovic Courtès diff --git a/doc/manual/src/api.md b/doc/manual/src/api.md new file mode 100644 index 00000000..1e27c644 --- /dev/null +++ b/doc/manual/src/api.md @@ -0,0 +1,249 @@ +Using the external API +====================== + +To be able to create integrations with other services, Hydra exposes an +external API that you can manage projects with. + +The API is accessed over HTTP(s) where all data is sent and received as +JSON. + +Creating resources requires the caller to be authenticated, while +retrieving resources does not. + +The API does not have a separate URL structure for it\'s endpoints. +Instead you request the pages of the web interface as `application/json` +to use the API. + +List projects +------------- + +To list all the `projects` of the Hydra install: + + GET / + Accept: application/json + +This will give you a list of `projects`, where each `project` contains +general information and a list of its `job sets`. + +**Example** + + curl -i -H 'Accept: application/json' \ + https://hydra.nixos.org + +**Note:** this response is truncated + + GET https://hydra.nixos.org/ + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "displayname": "Acoda", + "name": "acoda", + "description": "Acoda is a tool set for automatic data migration along an evolving data model", + "enabled": 0, + "owner": "sander", + "hidden": 1, + "jobsets": [ + "trunk" + ] + }, + { + "displayname": "cabal2nix", + "name": "cabal2nix", + "description": "Convert Cabal files into Nix build instructions", + "enabled": 0, + "owner": "simons@cryp.to", + "hidden": 1, + "jobsets": [ + "master" + ] + } + ] + +Get a single project +-------------------- + +To get a single `project` by identifier: + + GET /project/:project-identifier + Accept: application/json + +**Example** + + curl -i -H 'Accept: application/json' \ + https://hydra.nixos.org/project/hydra + + GET https://hydra.nixos.org/project/hydra + HTTP/1.1 200 OK + Content-Type: application/json + + { + "description": "Hydra, the Nix-based continuous build system", + "hidden": 0, + "displayname": "Hydra", + "jobsets": [ + "hydra-master", + "hydra-ant-logger-trunk", + "master", + "build-ng" + ], + "name": "hydra", + "enabled": 1, + "owner": "eelco" + } + +Get a single job set +-------------------- + +To get a single `job set` by identifier: + + GET /jobset/:project-identifier/:jobset-identifier + Content-Type: application/json + +**Example** + + curl -i -H 'Accept: application/json' \ + https://hydra.nixos.org/jobset/hydra/build-ng + + GET https://hydra.nixos.org/jobset/hydra/build-ng + HTTP/1.1 200 OK + Content-Type: application/json + + { + "errormsg": "evaluation failed due to signal 9 (Killed)", + "fetcherrormsg": null, + "nixexprpath": "release.nix", + "nixexprinput": "hydraSrc", + "emailoverride": "rob.vermaas@gmail.com, eelco.dolstra@logicblox.com", + "jobsetinputs": { + "officialRelease": { + "jobsetinputalts": [ + "false" + ] + }, + "hydraSrc": { + "jobsetinputalts": [ + "https://github.com/NixOS/hydra.git build-ng" + ] + }, + "nixpkgs": { + "jobsetinputalts": [ + "https://github.com/NixOS/nixpkgs.git release-14.12" + ] + } + }, + "enabled": 0 + } + +List evaluations +---------------- + +To list the `evaluations` of a `job set` by identifier: + + GET /jobset/:project-identifier/:jobset-identifier/evals + Content-Type: application/json + +**Example** + + curl -i -H 'Accept: application/json' \ + https://hydra.nixos.org/jobset/hydra/build-ng/evals + +**Note:** this response is truncated + + GET https://hydra.nixos.org/jobset/hydra/build-ng/evals + HTTP/1.1 200 OK + Content-Type: application/json + + { + "evals": [ + { + "jobsetevalinputs": { + "nixpkgs": { + "dependency": null, + "type": "git", + "value": null, + "uri": "https://github.com/NixOS/nixpkgs.git", + "revision": "f60e48ce81b6f428d072d3c148f6f2e59f1dfd7a" + }, + "hydraSrc": { + "dependency": null, + "type": "git", + "value": null, + "uri": "https://github.com/NixOS/hydra.git", + "revision": "48d6f0de2ab94f728d287b9c9670c4d237e7c0f6" + }, + "officialRelease": { + "dependency": null, + "value": "false", + "type": "boolean", + "uri": null, + "revision": null + } + }, + "hasnewbuilds": 1, + "builds": [ + 24670686, + 24670684, + 24670685, + 24670687 + ], + "id": 1213758 + } + ], + "first": "?page=1", + "last": "?page=1" + } + +Get a single build +------------------ + +To get a single `build` by its id: + + GET /build/:build-id + Content-Type: application/json + +**Example** + + curl -i -H 'Accept: application/json' \ + https://hydra.nixos.org/build/24670686 + + GET /build/24670686 + HTTP/1.1 200 OK + Content-Type: application/json + + { + "job": "tests.api.x86_64-linux", + "jobsetevals": [ + 1213758 + ], + "buildstatus": 0, + "buildmetrics": null, + "project": "hydra", + "system": "x86_64-linux", + "priority": 100, + "releasename": null, + "starttime": 1439402853, + "nixname": "vm-test-run-unnamed", + "timestamp": 1439388618, + "id": 24670686, + "stoptime": 1439403403, + "jobset": "build-ng", + "buildoutputs": { + "out": { + "path": "/nix/store/lzrxkjc35mhp8w7r8h82g0ljyizfchma-vm-test-run-unnamed" + } + }, + "buildproducts": { + "1": { + "path": "/nix/store/lzrxkjc35mhp8w7r8h82g0ljyizfchma-vm-test-run-unnamed", + "defaultpath": "log.html", + "type": "report", + "sha256hash": null, + "filesize": null, + "name": "", + "subtype": "testlog" + } + }, + "finished": 1 + } diff --git a/doc/manual/src/configuration.md b/doc/manual/src/configuration.md new file mode 100644 index 00000000..491376b3 --- /dev/null +++ b/doc/manual/src/configuration.md @@ -0,0 +1,310 @@ +Configuration +============= + +This chapter is a collection of configuration snippets for different +scenarios. + +The configuration is parsed by `Config::General` which has [a pretty +thorough documentation on their file format](https://metacpan.org/pod/Config::General#CONFIG-FILE-FORMAT). +Hydra calls the parser with the following options: +- `-UseApacheInclude => 1` +- `-IncludeAgain => 1` +- `-IncludeRelative => 1` + +Including files +--------------- + +`hydra.conf` supports Apache-style includes. This is **IMPORTANT** +because that is how you keep your **secrets** out of the **Nix store**. +Hopefully this got your attention 😌 + +This: +``` + +NixOS = Bearer gha-secret😱secret😱secret😱 + +``` +should **NOT** be in `hydra.conf`. + +`hydra.conf` is rendered in the Nix store and is therefore world-readable. + +Instead, the above should be written to a file outside the Nix store by +other means (manually, using Nixops' secrets feature, etc) and included +like so: +``` +Include /run/keys/hydra/github_authorizations.conf +``` + +Serving behind reverse proxy +---------------------------- + +To serve hydra web server behind reverse proxy like *nginx* or *httpd* +some additional configuration must be made. + +Edit your `hydra.conf` file in a similar way to this example: + +```conf +using_frontend_proxy 1 +base_uri example.com +``` + +`base_uri` should be your hydra servers proxied URL. If you are using +Hydra nixos module then setting `hydraURL` option should be enough. + +You also need to configure your reverse proxy to pass `X-Request-Base` +to hydra, with the same value as `base_uri`. +This also covers the case of serving Hydra with a prefix path, +as in [http://example.com/hydra](). + +For example if you are using nginx, then use configuration similar to +following: + + server { + listen 433 ssl; + server_name example.com; + .. other configuration .. + location /hydra/ { + + proxy_pass http://127.0.0.1:3000/; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Request-Base /hydra; + } + } + +Note the trailing slash on the `proxy_pass` directive, which causes nginx to +strip off the `/hydra/` part of the URL before passing it to hydra. + +Populating a Cache +------------------ + +A common use for Hydra is to pre-build and cache derivations which +take a long time to build. While it is possible to direcly access the +Hydra server's store over SSH, a more scalable option is to upload +built derivations to a remote store like an [S3-compatible object +store](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-help-stores.html#s3-binary-cache-store). Setting +the `store_uri` parameter will cause Hydra to sign and upload +derivations as they are built: + +``` +store_uri = s3://cache-bucket-name?compression=zstd¶llel-compression=true&write-nar-listing=1&ls-compression=br&log-compression=br&secret-key=/path/to/cache/private/key +``` + +This example uses [Zstandard](https://github.com/facebook/zstd) +compression on derivations to reduce CPU usage on the server, but +[Brotli](https://brotli.org/) compression for derivation listings and +build logs because it has better browser support. + +See [`nix help +stores`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-help-stores.html) +for a description of the store URI format. + +Statsd Configuration +-------------------- + +By default, Hydra will send stats to statsd at `localhost:8125`. Point Hydra to a different server via: + +``` + + host = alternative.host + port = 18125 + +``` + +hydra-notify's Prometheus service +--------------------------------- + +hydra-notify supports running a Prometheus webserver for metrics. The +exporter does not run unless a listen address and port are specified +in the hydra configuration file, as below: + +```conf + + + listen_address = 127.0.0.1 + port = 9199 + + +``` + +hydra-queue-runner's Prometheus service +--------------------------------------- + +hydra-queue-runner supports running a Prometheus webserver for metrics. The +exporter's address defaults to exposing on `127.0.0.1:9198`, but is also +configurable through the hydra configuration file and a command line argument, +as below. A port of `:0` will make the exposer choose a random, available port. + +```conf +queue_runner_metrics_address = 127.0.0.1:9198 +# or +queue_runner_metrics_address = [::]:9198 +``` + +```shell +$ hydra-queue-runner --prometheus-address 127.0.0.1:9198 +# or +$ hydra-queue-runner --prometheus-address [::]:9198 +``` + +Using LDAP as authentication backend (optional) +----------------------------------------------- + +Instead of using Hydra's built-in user management you can optionally +use LDAP to manage roles and users. + +This is configured by defining the `` block in the configuration file. +In this block it's possible to configure the authentication plugin in the +`` block. All options are directly passed to `Catalyst::Authentication::Store::LDAP`. +The documentation for the available settings can be found +[here](https://metacpan.org/pod/Catalyst::Authentication::Store::LDAP#CONFIGURATION-OPTIONS). + +Note that the bind password (if needed) should be supplied as an included file to +prevent it from leaking to the Nix store. + +Roles can be assigned to users based on their LDAP group membership. For this +to work *use\_roles = 1* needs to be defined for the authentication plugin. +LDAP groups can then be mapped to Hydra roles using the `` block. + +Example configuration: +``` + + + + class = Password + password_field = password + password_type = self_check + + + class = LDAP + ldap_server = localhost + + timeout = 30 + + binddn = "cn=root,dc=example" + include ldap-password.conf + start_tls = 0 + + verify = none + + user_basedn = "ou=users,dc=example" + user_filter = "(&(objectClass=inetOrgPerson)(cn=%s))" + user_scope = one + user_field = cn + + deref = always + + # Important for role mappings to work: + use_roles = 1 + role_basedn = "ou=groups,dc=example" + role_filter = "(&(objectClass=groupOfNames)(member=%s))" + role_scope = one + role_field = cn + role_value = dn + + deref = always + + + + + # Make all users in the hydra_admin group Hydra admins + hydra_admin = admin + # Allow all users in the dev group to eval jobsets, restart jobs and cancel builds + dev = eval-jobset + dev = restart-jobs + dev = cancel-build + + +``` + +Then, place the password to your LDAP server in `/var/lib/hydra/ldap-password.conf`: + +``` +bindpw = the-ldap-password +``` + +### Debugging LDAP + +Set the `debug` parameter under `ldap.config.ldap_server_options.debug`: + +``` + + + + + debug = 2 + + + + +``` + +### Legacy LDAP Configuration + +Hydra used to load the LDAP configuration from a YAML file in the +`HYDRA_LDAP_CONFIG` environment variable. This behavior is deperecated +and will be removed. + +When Hydra uses the deprecated YAML file, Hydra applies the following +default role mapping: + +``` + + + hydra_admin = admin + hydra_bump-to-front = bump-to-front + hydra_cancel-build = cancel-build + hydra_create-projects = create-projects + hydra_restart-jobs = restart-jobs + + +``` + +Note that configuring both the LDAP parameters in the hydra.conf and via +the environment variable is a fatal error. + +Webhook Authentication +--------------------- + +Hydra supports authenticating webhook requests from GitHub and Gitea to prevent unauthorized job evaluations. +Webhook secrets should be stored in separate files outside the Nix store for security using Config::General's include mechanism. + +In your main `hydra.conf`: +```apache + + Include /var/lib/hydra/secrets/webhook-secrets.conf + +``` + +Then create `/var/lib/hydra/secrets/webhook-secrets.conf` with your actual secrets: +```apache + + secret = your-github-webhook-secret + + + secret = your-gitea-webhook-secret + +``` + +For multiple secrets (useful for rotation or multiple environments), use an array: +```apache + + secret = your-github-webhook-secret-prod + secret = your-github-webhook-secret-staging + +``` + +**Important**: The secrets file should have restricted permissions (e.g., 0600) to prevent unauthorized access. +See the [Webhooks documentation](webhooks.md) for detailed setup instructions. + +Embedding Extra HTML +-------------------- + +Embed an analytics widget or other HTML in the `` of each HTML document via: + +```conf +tracker = [% END %] diff --git a/src/root/bootstrap-2.3.1.zip b/src/root/bootstrap-2.3.1.zip deleted file mode 100644 index e4f27746..00000000 Binary files a/src/root/bootstrap-2.3.1.zip and /dev/null differ diff --git a/src/root/bootstrap-4.3.1-dist.zip b/src/root/bootstrap-4.3.1-dist.zip new file mode 100644 index 00000000..b9662093 Binary files /dev/null and b/src/root/bootstrap-4.3.1-dist.zip differ diff --git a/src/root/build-deps.tt b/src/root/build-deps.tt index 157b113e..f15342fa 100644 --- a/src/root/build-deps.tt +++ b/src/root/build-deps.tt @@ -4,6 +4,6 @@
    - [% INCLUDE renderNode node=buildTimeGraph %] + [% INCLUDE renderNode node=buildTimeGraph isRoot=1 %]
diff --git a/src/root/build.tt b/src/root/build.tt index 5a55844d..a5a817d1 100644 --- a/src/root/build.tt +++ b/src/root/build.tt @@ -1,4 +1,7 @@ -[% WRAPPER layout.tt title="Build $id of job $project.name:$jobset.name:$job" %] +[% WRAPPER layout.tt + title="Build $id of job " _ makeNameTextForJob(jobset, job) + titleHTML="Build $id of job " _ linkToJob(jobset, job) +%] [% PROCESS common.tt %] [% PROCESS "product-list.tt" %] [% USE HTML %] @@ -34,7 +37,7 @@ END; seen.${step.drvpath} = 1; log = c.uri_for('/build' build.id 'nixlog' step.stepnr); %] - [% step.stepnr %] + [% HTML.escape(step.stepnr) %] [% IF step.type == 0 %] Build of [% INCLUDE renderOutputs outputs=step.buildstepoutputs %] @@ -58,21 +61,7 @@ END; [% IF step.busy != 0 || ((step.machine || step.starttime) && (step.status == 0 || step.status == 1 || step.status == 3 || step.status == 4 || step.status == 7)); INCLUDE renderMachineName machine=step.machine; ELSE; "n/a"; END %] [% IF step.busy != 0 %] - [% IF step.busy == 1 %] - Preparing - [% ELSIF step.busy == 10 %] - Connecting - [% ELSIF step.busy == 20 %] - Sending inputs - [% ELSIF step.busy == 30 %] - Building - [% ELSIF step.busy == 40 %] - Receiving outputs - [% ELSIF step.busy == 50 %] - Post-processing - [% ELSE %] - Unknown state - [% END %] + [% INCLUDE renderBusyStatus %] [% ELSIF step.status == 0 %] [% IF step.isnondeterministic %] Succeeded with non-determistic result @@ -97,7 +86,7 @@ END; [% ELSIF step.status == 11 %] Output limit exceeded [% ELSIF step.status == 12 %] - Non-determinism detected [% IF step.timesbuilt %] after [% step.timesbuilt %] times[% END %] + Non-determinism detected [% IF step.timesbuilt %] after [% HTML.escape(step.timesbuilt) %] times[% END %] [% ELSIF step.errormsg %] Failed: [% HTML.escape(step.errormsg) %] [% ELSE %] @@ -114,41 +103,39 @@ END; [% END %]
@@ -164,7 +151,7 @@ END; - + @@ -181,9 +168,9 @@ END; END; %]; [%+ IF nrFinished == nrConstituents && nrFailedConstituents == 0 %] - all [% nrConstituents %] constituent builds succeeded + all [% HTML.escape(nrConstituents) %] constituent builds succeeded [% ELSE %] - [% nrFailedConstituents %] out of [% nrConstituents %] constituent builds failed + [% HTML.escape(nrFailedConstituents) %] out of [% HTML.escape(nrConstituents) %] constituent builds failed [% IF nrFinished < nrConstituents %] ([% nrConstituents - nrFinished %] still pending) [% END %] @@ -193,25 +180,25 @@ END; - + [% IF build.releasename %] - + [% ELSE %] - + [% END %] [% IF eval %] [% END %] @@ -239,9 +226,9 @@ END; [% END %] @@ -346,15 +333,15 @@ END; [% END %] - [% IF build.nixexprinput %] + [% IF eval.nixexprinput %] - + [% END %] - + @@ -374,11 +361,11 @@ END; - + - + @@ -389,14 +376,14 @@ END; + ( chartsURL) %]>history) [% END %] [% IF build.finished && build.closuresize %] + ( chartsURL) %]>history) [% END %] [% IF build.finished && build.buildproducts %] @@ -425,9 +412,9 @@ END; [% FOREACH metric IN build.buildmetrics %] - - - + + + [% END %] @@ -469,68 +456,144 @@ END; [% FOREACH input IN build.dependents %] - - + + [% END %]
Build ID:[% build.id %][% HTML.escape(build.id) %]
Status:
System:[% build.system %][% build.system | html %]
Release name:[% HTML.escape(build.releasename) %][% build.releasename | html %]
Nix name:[% build.nixname %][% build.nixname | html %]
Part of: - evaluation [% eval.id %] - [% IF nrEvals > 1 +%] (and [% nrEvals - 1 %] others)[% END %] + c.uri_for(c.controller('JobsetEval').action_for('view'), [eval.id])) %]>evaluation [% HTML.escape(eval.id) %] + [% IF nrEvals > 1 +%] (and c.uri_for('/build' build.id 'evals')) %]>[% nrEvals - 1 %] others)[% END %]
Logfile: [% actualLog = cachedBuildStep ? c.uri_for('/build' cachedBuild.id 'nixlog' cachedBuildStep.stepnr) : c.uri_for('/build' build.id 'log') %] - pretty - raw - tail + actualLog) %]>pretty + actualLog _ "/raw") %]>raw + actualLog _ "/tail") %]>tail
[% build.priority %]
Nix expression:file [% HTML.escape(build.nixexprpath) %] in input [% HTML.escape(build.nixexprinput) %]file [% eval.nixexprpath | html %] in input [% eval.nixexprinput | html %]
Nix name:[% build.nixname %][% build.nixname | html %]
Short description:
System:[% build.system %][% build.system | html %]
Derivation store path:[% build.drvpath %][% build.drvpath | html %]
Output store paths:
Closure size: [% mibs(build.closuresize / (1024 * 1024)) %] MiB - (history)
Output size: [% mibs(build.size / (1024 * 1024)) %] MiB - (history)
c.uri_for('/job' project.name jobset.name job 'metric' metric.name)) %]">[%HTML.escape(metric.name)%][%metric.value%][%metric.unit%] c.uri_for('/job' project.name jobset.name job 'metric' metric.name)) %]">[% metric.name | html %][% HTML.escape(metric.value) %][% HTML.escape(metric.unit) %]
[% INCLUDE renderFullBuildLink build=input.build %][% input.name %][% input.build.system %][% input.name | html %][% input.build.system | html %] [% INCLUDE renderDateTime timestamp = input.build.timestamp %]
- [% END %] +[% END %] [% IF drvAvailable %] - [% INCLUDE makeLazyTab tabName="tabs-build-deps" uri=c.uri_for('/build' build.id 'build-deps') %] + [% INCLUDE makeLazyTab tabName="tabs-build-deps" uri=c.uri_for('/build' build.id 'build-deps') callback="makeTreeCollapsible" %] [% END %] [% IF available %] - [% INCLUDE makeLazyTab tabName="tabs-runtime-deps" uri=c.uri_for('/build' build.id 'runtime-deps') %] + [% INCLUDE makeLazyTab tabName="tabs-runtime-deps" uri=c.uri_for('/build' build.id 'runtime-deps') callback="makeTreeCollapsible" %] [% END %] +
+ [% IF runcommandlogProblem %] + + [% END %] +
+ [% FOREACH runcommandlog IN runcommandlogs %] +
+
+
+ [% IF runcommandlog.did_succeed() %] + Succeeded + [% ELSIF runcommandlog.is_running() %] + + [% ELSE %] + Failed + [% END %] +
+ +
+
[% runcommandlog.command | html %]
+
+ [% IF not runcommandlog.is_running() %] + [% IF runcommandlog.did_fail_with_signal() %] + Exit signal: [% runcommandlog.signal | html %] + [% IF runcommandlog.core_dumped %] + (Core Dumped) + [% END %] + [% ELSIF runcommandlog.did_fail_with_exec_error() %] + Exec error: [% runcommandlog.error_number | html %] + [% ELSIF not runcommandlog.did_succeed() %] + Exit code: [% runcommandlog.exit_code | html %] + [% END %] + [% END %] +
+
+ +
+ [% IF runcommandlog.start_time != undef %] +
Started at [% INCLUDE renderDateTime timestamp = runcommandlog.start_time; %]
+
+ [% IF runcommandlog.end_time != undef %] + Ran for [% INCLUDE renderDuration duration = runcommandlog.end_time - runcommandlog.start_time %] + [% ELSE %] + Running for [% INCLUDE renderDuration duration = curTime - runcommandlog.start_time %] + [% END %] + [% IF runcommandlog.uuid != undef %] + [% runLog = c.uri_for('/build', build.id, 'runcommandlog', runcommandlog.uuid) %] + + [% END %] +
+ [% ELSE %] +
Pending
+ [% END %] +
+
+
+ [% END %] +
+
- diff --git a/src/root/channel-contents.tt b/src/root/channel-contents.tt index eb0d9460..11d0323d 100644 --- a/src/root/channel-contents.tt +++ b/src/root/channel-contents.tt @@ -6,21 +6,24 @@ href="http://nixos.org/">Nix package manager. If you have Nix installed, you can subscribe to this channel by once executing

-
-$ nix-channel --add [% curUri +%]
-$ nix-channel --update
+
+$ nix-channel --add [% HTML.escape(curUri) +%]
+$ nix-channel --update
+

You can then query and install packages in the normal way, e.g.,

-
-$ nix-env -qa '*'
-$ nix-env -i foo
+
+$ nix-env -qa '*'
+$ nix-env -i foo
+

You can update to the latest versions of the packages in this channel by executing

-
-$ nix-channel --update
-$ nix-env -u '*'
+
+$ nix-channel --update
+$ nix-env -u '*'
+
[% IF genericChannel %] @@ -46,9 +49,9 @@ $ nix-env -u '*' [% b = pkg.build %] - [% b.id %] - [% b.get_column('releasename') || b.nixname %] - [% b.system %] + c.uri_for('/build' b.id)) %]>[% HTML.escape(b.id) %] + [% b.get_column('releasename') || b.nixname | html %] + [% b.system | html %] [% IF b.homepage %] b.homepage) %]>[% HTML.escape(b.description) %] diff --git a/src/root/common.tt b/src/root/common.tt index bc777ede..b18d33f7 100644 --- a/src/root/common.tt +++ b/src/root/common.tt @@ -55,17 +55,17 @@ BLOCK renderRelativeDate %] [% END; BLOCK renderProjectName %] -[% project %] + c.uri_for('/project' project)) %]>[% project | html %] [% END; BLOCK renderJobsetName %] -[% jobset %] + c.uri_for('/jobset' project jobset)) %]>[% jobset | html %] [% END; BLOCK renderJobName %] -[% job %] + c.uri_for('/job' project jobset job)) %]>[% job | html %] [% END; @@ -80,7 +80,7 @@ BLOCK renderFullJobName %] BLOCK renderFullJobNameOfBuild; - INCLUDE renderFullJobName project=build.get_column("project") jobset = build.get_column("jobset") job = build.get_column("job"); + INCLUDE renderFullJobName project=build.jobset.get_column("project") jobset = build.jobset.get_column("name") job = build.get_column("job"); END; @@ -91,6 +91,17 @@ BLOCK renderDuration; duration % 60 %]s[% END; +BLOCK renderDrvInfo; + drvname = step.drvpath + .substr(11) # strip `/nix/store/` + .split('-').slice(1).join("-") # strip hash part + .substr(0, -4); # strip `.drv` + IF drvname != releasename; + IF step.type == 0; action = "Build"; ELSE; action = "Substitution"; END; + IF drvname; %] ([% HTML.escape(action) %] of [% HTML.escape(drvname) %])[% END; + END; +END; + BLOCK renderBuildListHeader %] @@ -127,22 +138,27 @@ BLOCK renderBuildListBody; [% END %] [% IF showSchedulingInfo %] - + [% END %] - + [% IF !hideJobName %] - + [% END %] - - + + [% IF showDescription %] - + [% END %] [% END; IF linkToAll %] - + [% END; END; @@ -160,11 +176,11 @@ BLOCK renderBuildList; END; -BLOCK renderLink %][% title %][% END; +BLOCK renderLink %] uri) %]>[% HTML.escape(title) %][% END; BLOCK maybeLink; - IF uri %] uri, class => class); IF confirmmsg +%] onclick="javascript:return confirm('[% confirmmsg %]')"[% END %]>[% content %][% ELSE; content; END; + IF uri %] uri, class => class); IF confirmmsg +%] onclick="javascript:return confirm('[% confirmmsg %]')"[% END %]>[% HTML.escape(content) %][% ELSE; HTML.escape(content); END; END; @@ -176,15 +192,15 @@ BLOCK renderSelection; [% END %] [% ELSE %] - param, name => param) %]> [% FOREACH name IN options.keys.sort %] - + [% END %] [% END; @@ -200,12 +216,12 @@ BLOCK editString; %] BLOCK renderFullBuildLink; - INCLUDE renderFullJobNameOfBuild build=build %] build [% build.id %][% + INCLUDE renderFullJobNameOfBuild build=build %] c.uri_for('/build' build.id)) %]>build [% HTML.escape(build.id) %][% END; BLOCK renderBuildIdLink; %] -build [% id %] + c.uri_for('/build' id)) %]>build [% HTML.escape(id) %] [% END; @@ -245,6 +261,27 @@ BLOCK renderBuildStatusIcon; END; +BLOCK renderBusyStatus; + IF step.busy == 1 %] + Preparing + [% ELSIF step.busy == 10 %] + Connecting + [% ELSIF step.busy == 20 %] + Sending inputs + [% ELSIF step.busy == 30 %] + Building + [% ELSIF step.busy == 35 %] + Waiting to receive outputs + [% ELSIF step.busy == 40 %] + Receiving outputs + [% ELSIF step.busy == 50 %] + Post-processing + [% ELSE %] + Unknown state + [% END; +END; + + BLOCK renderStatus; IF build.finished; buildstatus = build.buildstatus; @@ -283,7 +320,7 @@ END; BLOCK renderShortInputValue; IF input.type == "build" || input.type == "sysbuild" %] - [% input.dependency.id %] + c.uri_for('/build' input.dependency.id)) %]>[% HTML.escape(input.dependency.id) %] [% ELSIF input.type == "string" %] "[% HTML.escape(input.value) %]" [% ELSIF input.type == "nix" || input.type == "boolean" %] @@ -301,7 +338,7 @@ BLOCK renderDiffUri; url = bi1.uri; path = url.replace(base, ''); IF url.match(base) %] - [% contents %] + m.uri.replace('_path_', path).replace('_1_', bi1.revision).replace('_2_', bi2.revision)) %]>[% HTML.escape(contents) %] [% nouri = 0; END; END; @@ -310,13 +347,13 @@ BLOCK renderDiffUri; url = res.0; branch = res.1; IF bi1.type == "hg" || bi1.type == "git" %] - c.uri_for('/api/scmdiff', { uri = url, rev1 = bi1.revision, rev2 = bi2.revision, type = bi1.type, branch = branch - })) %]">[% contents %] + })) %]>[% HTML.escape(contents) %] [% ELSE; contents; END; @@ -332,8 +369,8 @@ BLOCK renderInputs; %] [% FOREACH input IN inputs %] - - + + - + [% END %] @@ -370,33 +407,33 @@ BLOCK renderInputDiff; %] IF bi1.name == bi2.name; IF bi1.type == bi2.type; IF bi1.value != bi2.value || bi1.uri != bi2.uri %] - + [% ELSIF bi1.uri == bi2.uri && bi1.revision != bi2.revision %] [% IF bi1.type == "git" %] [% ELSE %] [% END %] [% ELSIF bi1.dependency.id != bi2.dependency.id || bi1.path != bi2.path %] [% END %] [% ELSE %] - + [% END; deletedInput = 0; END; END; IF deletedInput == 1 %] - + [% END; END; END %] @@ -405,11 +442,11 @@ BLOCK renderInputDiff; %] BLOCK renderPager %] - - - + + @@ -461,46 +498,49 @@ BLOCK renderEvals %] eval = e.eval; link = c.uri_for(c.controller('JobsetEval').action_for('view'), [eval.id]) %] - + [% IF !jobset && !build %] - + [% END %] [% END; IF linkToAll %] - + [% END %]
[% IF busy %]Started[% ELSE %]Queued[% END %][% IF busy %]Started[% ELSE %]Queued[% END %][% build.id %] link) %]>[% HTML.escape(build.id) %][% IF !hideJobsetName %][%build.get_column("project")%]:[%build.get_column("jobset")%]:[% END %][%build.get_column("job")%] + link) %]>[% IF !hideJobsetName %][% HTML.escape(build.jobset.get_column("project")) %]:[% HTML.escape(build.jobset.get_column("name")) %]:[% END %][% HTML.escape(build.get_column("job")) %] + [% IF showStepName %] + [% INCLUDE renderDrvInfo step=build.buildsteps releasename=build.nixname %] + [% END %] + [% t = showSchedulingInfo ? build.timestamp : build.stoptime; IF t; INCLUDE renderRelativeDate timestamp=(showSchedulingInfo ? build.timestamp : build.stoptime); ELSE; "-"; END %][% !showSchedulingInfo and build.get_column('releasename') ? build.get_column('releasename') : build.nixname %][% build.system %][% !showSchedulingInfo and build.get_column('releasename') ? HTML.escape(build.get_column('releasename')) : HTML.escape(build.nixname) %][% build.system | html %][% build.description %][% HTML.escape(build.description) %]
More...
linkToAll) %]>More...
[% input.name %][% type = input.type; inputTypes.$type %][% input.name | html %][% type = input.type; HTML.escape(inputTypes.$type) %] [% IF input.type == "build" || input.type == "sysbuild" %] [% INCLUDE renderFullBuildLink build=input.dependency %] @@ -346,7 +383,7 @@ BLOCK renderInputs; %] [% END %] [% IF input.revision %][% HTML.escape(input.revision) %][% END %][% input.path %][% input.path | html %]
[% bi1.name %][% INCLUDE renderShortInputValue input=bi1 %] to [% INCLUDE renderShortInputValue input=bi2 %]
[% HTML.escape(bi1.name) %][% INCLUDE renderShortInputValue input=bi1 %] to [% INCLUDE renderShortInputValue input=bi2 %]
- [% bi1.name %][% INCLUDE renderDiffUri contents=(bi1.revision.substr(0, 6) _ ' to ' _ bi2.revision.substr(0, 6)) %] + [% HTML.escape(bi1.name) %][% INCLUDE renderDiffUri contents=(bi1.revision.substr(0, 12) _ ' to ' _ bi2.revision.substr(0, 12)) %]
- [% bi1.name %][% INCLUDE renderDiffUri contents=(bi1.revision _ ' to ' _ bi2.revision) %] + [% HTML.escape(bi1.name) %][% INCLUDE renderDiffUri contents=(bi1.revision _ ' to ' _ bi2.revision) %]
- [% bi1.name %][% INCLUDE renderShortInputValue input=bi1 %] to [% INCLUDE renderShortInputValue input=bi2 %] + [% HTML.escape(bi1.name) %][% INCLUDE renderShortInputValue input=bi1 %] to [% INCLUDE renderShortInputValue input=bi2 %]

[% INCLUDE renderInputDiff inputs1=bi1.dependency.inputs inputs2=bi2.dependency.inputs nestedDiff=1 nestLevel=nestLevel+1 %]
[% bi1.name %]Changed input type from '[% type = bi1.type; inputTypes.$type %]' to '[% type = bi2.type; inputTypes.$type %]'
[% HTML.escape(bi1.name) %]Changed input type from '[% type = bi1.type; HTML.escape(inputTypes.$type) %]' to '[% type = bi2.type; HTML.escape(inputTypes.$type) %]'
[% bi1.name %]Input not present in this build.
[% HTML.escape(bi1.name) %]Input not present in this build.
Date Input changesJobs - Delta - Jobs
@@ -454,6 +488,9 @@ BLOCK renderEvals %] Queued + Delta +
[% eval.id %] link) %]>[% HTML.escape(eval.id) %][% INCLUDE renderFullJobsetName project=eval.get_column('project') jobset=eval.get_column('jobset') %][% INCLUDE renderFullJobsetName project=eval.jobset.project.name jobset=eval.jobset.name %][% INCLUDE renderRelativeDate timestamp = eval.timestamp %] [% IF e.changedInputs.size > 0; sep=''; FOREACH input IN e.changedInputs; - sep; %] [% input.name %] → [% INCLUDE renderShortEvalInput input=input; + sep; %] [% HTML.escape(input.name) %] → [% INCLUDE renderShortEvalInput input=input; sep=', '; END; ELSE %] - [% END %] + [% IF eval.evaluationerror.has_error %] + Eval Errors + [% END %] - [% e.nrSucceeded %] + [% HTML.escape(e.nrSucceeded) %] [% IF e.nrFailed > 0 %] - [% e.nrFailed %] + [% HTML.escape(e.nrFailed) %] [% END %] [% IF e.nrScheduled > 0 %] - [% e.nrScheduled %] + [% HTML.escape(e.nrScheduled) %] [% END %] [% IF e.diff > 0 %] - +[% e.diff %] + +[% HTML.escape(e.diff) %] [% ELSIF e.diff < 0 && e.nrScheduled == 0 %] - [% e.diff %] + [% HTML.escape(e.diff) %] [% END %]
More...
linkToAll) %]>More...
@@ -508,32 +548,46 @@ BLOCK renderEvals %] BLOCK renderLogLinks %] -(log, raw, tail) +( url) %]>log, "$url/raw") %]>raw, "$url/tail") %]>tail) [% END; BLOCK makeLazyTab %] -
-
Loading...
+
tabName) %] class="tab-pane"> +
[% END; BLOCK makePopover %] -
content, 'data-placement' => placement || 'bottom') %]> +
+ [% END; BLOCK menuItem %] -
  • - uri) %] [%+ IF modal %]data-toggle="modal"[% END %]> - [% IF icon %] [%+ END %] - [% title %] + uri) %] [%+ IF modal %]data-toggle="modal"[% END %] + [% IF confirmmsg %]onclick="javascript:return confirm('[% confirmmsg %]')"[% END %]> + [% IF icon %] [%+ END %] + [% title %] + +[% END; + + +BLOCK navItem %] +
  • [% END; @@ -585,35 +639,35 @@ BLOCK renderJobsetOverview %] [% HTML.escape(j.description) %] [% IF j.lastcheckedtime; INCLUDE renderDateTime timestamp = j.lastcheckedtime; - IF j.errormsg || j.fetcherrormsg; %] Error[% END; + IF j.has_error || j.fetcherrormsg; %] Error[% END; ELSE; "-"; END %] [% IF j.get_column('nrtotal') > 0 %] [% successrate = ( j.get_column('nrsucceeded') / j.get_column('nrtotal') )*100 %] [% IF j.get_column('nrscheduled') > 0 %] - [% class = 'label' %] + [% class = 'badge badge-secondary' %] [% ELSIF successrate < 25 %] - [% class = 'label label-important' %] + [% class = 'badge badge-danger' %] [% ELSIF successrate < 75 %] - [% class = 'label label-warning' %] + [% class = 'badge badge-warning' %] [% ELSIF successrate <= 100 %] - [% class = 'label label-success' %] + [% class = 'badge badge-success' %] [% END %] [% END %] [% successrate FILTER format('%d') %]% [% IF j.get_column('nrsucceeded') > 0 %] - [% j.get_column('nrsucceeded') %] + [% HTML.escape(j.get_column('nrsucceeded')) %] [% END %] [% IF j.get_column('nrfailed') > 0 %] - [% j.get_column('nrfailed') %] + [% HTML.escape(j.get_column('nrfailed')) %] [% END %] [% IF j.get_column('nrscheduled') > 0 %] - [% j.get_column('nrscheduled') %] + [% HTML.escape(j.get_column('nrscheduled')) %] [% END %] @@ -631,14 +685,22 @@ BLOCK includeFlot %] [% END; +BLOCK renderYesNo %] +[% IF value %] +Yes +[% ELSE %] +No +[% END %] +[% END; + BLOCK createChart %] -
    -
    +
    +
    diff --git a/src/root/dashboard-my-jobs-tab.tt b/src/root/dashboard-my-jobs-tab.tt index a1e82612..470c174c 100644 --- a/src/root/dashboard-my-jobs-tab.tt +++ b/src/root/dashboard-my-jobs-tab.tt @@ -9,7 +9,7 @@ [% ELSE %] -

    Below are the most recent builds of the [% builds.size %] jobs of which you +

    Below are the most recent builds of the [% HTML.escape(builds.size) %] jobs of which you ([% HTML.escape(user.emailaddress) %]) are a maintainer.

    [% INCLUDE renderBuildList %] diff --git a/src/root/dashboard.tt b/src/root/dashboard.tt index 5411652e..0daf3dad 100644 --- a/src/root/dashboard.tt +++ b/src/root/dashboard.tt @@ -2,9 +2,9 @@ [% PROCESS common.tt %]
    @@ -24,7 +24,7 @@ [% INCLUDE renderFullJobName project=j.job.get_column('project') jobset=j.job.get_column('jobset') job=j.job.job %] [% FOREACH b IN j.builds %] - [% INCLUDE renderBuildStatusIcon size=16 build=b %] + c.uri_for('/build' b.id)) %]>[% INCLUDE renderBuildStatusIcon size=16 build=b %] [% END %] [% END %] diff --git a/src/root/deps.tt b/src/root/deps.tt index a2c1fbba..4cb49af4 100644 --- a/src/root/deps.tt +++ b/src/root/deps.tt @@ -3,25 +3,32 @@ [% BLOCK renderNode %]
  • [% IF done.${node.path} %] - [% node.name %] (repeated) + [% node.name | html %] ( "#" _ done.${node.path}) %]>repeated) [% ELSE %] [% done.${node.path} = global.nodeId; global.nodeId = global.nodeId + 1; %] [% IF node.refs.size > 0 %] [% END %] - + done.${node.path}) %]> [% IF node.buildStep %] - [% node.name %] [% + c.uri_for('/build' node.buildStep.get_column('build'))) %]>[% node.name %] [% IF buildStepLogExists(node.buildStep); INCLUDE renderLogLinks url=c.uri_for('/build' node.buildStep.get_column('build') 'nixlog' node.buildStep.stepnr); END %] [% ELSE %] - [% node.name %] (no info) + [% node.name | html %] (no info) [% END %] + [% IF isRoot %] + + (collapse all + – + expand all) + + [% END %] [% IF node.refs.size > 0 %]
      - [% FOREACH ref IN node.refs; INCLUDE renderNode node=ref; END %] + [% FOREACH ref IN node.refs; INCLUDE renderNode node=ref isRoot=0; END %]
    [% END %] [% END %] diff --git a/src/root/edit-jobset.tt b/src/root/edit-jobset.tt index 02391651..a3c1c9c5 100644 --- a/src/root/edit-jobset.tt +++ b/src/root/edit-jobset.tt @@ -1,23 +1,23 @@ [% WRAPPER layout.tt title= (create ? "Creating jobset in project $project.name" : - createFromEval ? "Creating jobset from evaluation $eval.id of $project.name:$jobset.name" : - cloneJobset ? "Cloning jobset $project.name:$jobset.name" : - "Editing jobset $project.name:$jobset.name") %] + createFromEval ? "Creating jobset from evaluation $eval.id of " _ makeNameTextForJobset(jobset) : + cloneJobset ? "Cloning jobset " _ makeNameTextForJobset(jobset) : + "Editing jobset " _ makeNameTextForJobset(jobset)) %] [% PROCESS common.tt %] [% USE format %] [% BLOCK renderJobsetInput %] - + id) %][% END %]> - + - input.name) %]/> + baseName _ "-name", name => baseName _ "-name", value => input.name) %] /> [% INCLUDE renderSelection curValue=input.type param="$baseName-type" options=inputTypes edit=1 %] - + baseName) %]> [% IF createFromEval %] [% value = (input.uri or input.value); IF input.revision; value = value _ " " _ input.revision; END; warn = input.altnr != 0; @@ -36,7 +36,7 @@ value, id => "$baseName-value", name => "$baseName-value") %]/> - + "$baseName-emailresponsible", name => "$baseName-emailresponsible") %] [% IF input.emailresponsible; 'checked="checked"'; END %]/> [% END %] @@ -51,130 +51,151 @@ [% INCLUDE renderJobsetInput input=input baseName="input-$input.name" %] [% END %] - + [% END %] -
    + -
    +
    + +
    + + + + +
    +
    -
    - -
    -
    - - - - - +
    + +
    + +
    +
    + +
    + +
    + edit ? jobset.name : "") %]/> +
    +
    + +
    + +
    + + +
    +
    + +
    + +
    + jobset.description) %]/> +
    +
    + +
    + +
    + jobset.flake) %]/> +
    +
    + +
    + +
    +
    + jobset.nixexprpath) %]/> +
    + in +
    + jobset.nixexprinput) %]/> +
    +
    +
    + +
    + +
    +
    + jobset.checkinterval) %]/> +
    + sec
    +
    -
    -
    - -
    +
    + +
    + jobset.schedulingshares) %]/>
    +
    -
    - -
    - edit ? jobset.name : "") %]/> -
    -
    - -
    - -
    - jobset.description) %]/> -
    -
    - -
    - -
    -
    - - - -
    -
    -
    - -
    - -
    - jobset.flake) %]/> -
    -
    - -
    - -
    - jobset.nixexprpath) %]/> - in - jobset.nixexprinput) %]/> -
    -
    - -
    - -
    -
    - jobset.checkinterval) %]/> - sec -
    - (0 to disable polling) -
    -
    - -
    - -
    -
    - jobset.schedulingshares) %]/> -
    - [% IF totalShares %] - ([% f = format("%.2f"); f(jobset.schedulingshares / totalShares * 100) %]% out of [% totalShares %] shares) +
    + +
    + + />
    +
    -
    -
    - -
    +
    + +
    +
    +
    -
    - -
    - jobset.emailoverride) %] [%IF !emailNotification%]disabled=1[%END%] /> -
    +
    + +
    + jobset.emailoverride) %] [% IF !emailNotification %]disabled[% END %]/>
    +
    -
    - -
    - jobset.keepnr) %]/> -
    +
    + +
    + jobset.keepnr) %]/>
    +
    - [% INCLUDE renderJobsetInputs %] + [% INCLUDE renderJobsetInputs %] -
    - -
    - -
    + [% INCLUDE renderJobsetInput input="" extraClass="template" id="input-template" baseName="input-template" %] @@ -187,7 +208,7 @@ var id = 0; function update() { - if ($("#type").val() == 0) { + if ($("input[type='radio'][name='type']:checked").val() == 0) { $(".show-on-legacy").show(); $(".show-on-flake").hide(); } else { @@ -196,8 +217,7 @@ } } - $("#type-flake").click(function() { update(); }); - $("#type-legacy").click(function() { update(); }); + $("input[type='radio'][name='type']").change(function() { update(); }); update(); diff --git a/src/root/edit-project.tt b/src/root/edit-project.tt index b2d7264a..7ee5331b 100644 --- a/src/root/edit-project.tt +++ b/src/root/edit-project.tt @@ -1,97 +1,120 @@ [% WRAPPER layout.tt title=(create ? "New project" : "Editing project $project.name") %] [% PROCESS common.tt %] - + -
    - -
    -
    - -
    -
    - -
    +
    + +
    +
    +
    -
    - -
    - project.name) %]/> -
    +
    + +
    +
    +
    -
    - -
    - project.displayname) %]/> -
    +
    + +
    + project.name) %]/>
    +
    -
    - -
    - project.description) %]/> -
    +
    + +
    + project.displayname) %]/>
    +
    -
    - -
    - project.homepage) %]/> -
    +
    + +
    + project.description) %]/>
    +
    -
    - -
    - project.owner.username || c.user.username) %]/> -
    +
    + +
    + project.homepage) %]/>
    +
    -
    - -
    -
    - project.declfile) %]/> -
    - (Leave blank for non-declarative project configuration) -
    +
    + +
    + project.owner.username || c.user.username) %]/>
    +
    -
    - -
    - [% INCLUDE renderSelection param="decltype" options=inputTypes edit=1 curValue=project.decltype %] - value - project.declvalue, name => "declvalue") %]/> -
    + +
    + +
    +
    +
    -
    - +
    + +
    + project.declfile) %]/>
    +
    -
    +
    + +
    + [% INCLUDE renderSelection param="decltype" options=inputTypes edit=1 curValue=project.decltype %] + project.declvalue) %]/> +
    +
    + + - - - - + [% INCLUDE style.tt %] [% IF c.config.enable_google_login %] @@ -46,27 +22,23 @@ -
    @@ -65,7 +65,7 @@ [% ELSE %]
    Hydra has no projects yet. Please - create a project.
    + c.uri_for(c.controller('Project').action_for('create'))) %]>create a project. [% END %] diff --git a/src/root/plain.tt b/src/root/plain.tt index 0b2db510..f5d7a96b 100644 --- a/src/root/plain.tt +++ b/src/root/plain.tt @@ -5,6 +5,6 @@ [% jobset = build.jobset %] [% job = build.job %] -
    [% HTML.escape(contents) %]
    +
    [% HTML.escape(contents) %]
    [% END %] diff --git a/src/root/product-list.tt b/src/root/product-list.tt index 298d0a66..4a745850 100644 --- a/src/root/product-list.tt +++ b/src/root/product-list.tt @@ -1,17 +1,17 @@ [% BLOCK renderProductLinks %]
    - + [% IF latestRoot %] [% END %] @@ -49,18 +49,18 @@ Error @@ -74,17 +74,17 @@ Nix package @@ -100,26 +100,26 @@ [% filename = build.nixname _ (product.subtype ? "-" _ product.subtype : "") _ ".closure.gz" %] [% uri = c.uri_for('/build' build.id 'nix' 'closure' filename ) %] - - [% product.path %] + uri) %]> + [% product.path | html %] [% ELSE %] - + [% END %] [% END %] @@ -212,15 +211,15 @@ [% CASE "coverage" %] [% CASE DEFAULT %] [% END %] @@ -241,7 +240,7 @@ Documentation diff --git a/src/root/project.tt b/src/root/project.tt index 1dd2b94b..5e8ec0c8 100644 --- a/src/root/project.tt +++ b/src/root/project.tt @@ -3,23 +3,20 @@
    @@ -50,12 +47,17 @@
    [% IF project.jobsets %] -

    This project has the following jobsets: - - [% IF isProjectOwner %] - - [% END %] -

    +
    +
    + This project has the following jobsets: +
    +
    + + [% IF isProjectOwner %] + + [% END %] +
    +
    [% INCLUDE renderJobsetOverview %] [% ELSE %]

    No jobsets have been defined yet.

    @@ -90,6 +92,10 @@
    + + + +
    URL:[% uri %] uri) %]>[% uri | html %]
    Links to latest: [% uri2 = "${c.uri_for(latestRoot.join('/') 'download-by-type' product.type product.subtype)}" %] - [% uri2 %] + uri2) %]>[% uri2 | html %]
    [% uri2 = "${c.uri_for(latestRoot.join('/') 'download' product.productnr)}" %] - [% uri2 %] + uri2) %]>[% uri2 | html %]
    - + contents) %]> Failed build produced output. Click here to inspect the output. - [% WRAPPER makePopover title="Help" classes="btn-mini" %] + [% WRAPPER makePopover title="Help" classes="btn-secondary btn-sm" %]

    If you have Nix installed on your machine, this failed build output and all its dependencies can be unpacked into your local Nix store by doing:

    -
    $ curl [% uri %] | gunzip | nix-store --import
    +
    $ curl [% HTML.escape(uri) %] | gunzip | nix-store --import
    -

    The build output can then be found in the path [% product.path %].

    +

    The build output can then be found in the path [% product.path | html %].

    [% END %]
    - [% HTML.escape(build.nixname) %] + [% build.nixname | html %] - [% WRAPPER makePopover title="Help" classes="btn-mini" + [% WRAPPER makePopover title="Help" classes="btn-secondary btn-sm" %]

    You can install this package using the Nix package manager from the command-line:

    -
    $ nix-env -i [%HTML.escape(product.path)%][% IF binaryCachePublicUri %] --option binary-caches [% HTML.escape(binaryCachePublicUri) %][% END %]
    +
    $ nix-env -i [% HTML.escape(product.path) %][% IF binaryCachePublicUri %] --option binary-caches [% HTML.escape(binaryCachePublicUri) %][% END %]
    [% END %] [% IF localStore %] - Contents + contents) %]>Contents [% END %]
    - [% WRAPPER makePopover title="Help" classes="btn-mini" %] + [% WRAPPER makePopover title="Help" classes="btn-secondary btn-sm" %]

    If you have Nix installed on your machine, this build and all its dependencies can be unpacked into your local Nix store by doing:

    -
    $ gunzip < [% filename %] | nix-store --import
    +
    $ gunzip < [% HTML.escape(filename) %] | nix-store --import

    or to download and unpack in one command:

    -
    $ curl [% uri %] | gunzip | nix-store --import
    +
    $ curl [% HTML.escape(uri) %] | gunzip | nix-store --import

    The package can then be found in the path [% - product.path %]. You’ll probably also want to do

    + product.path | html %]. You’ll probably also want to do

    -
    $ nix-env -i [% product.path %]
    +
    $ nix-env -i [% HTML.escape(product.path) %]

    to actually install the package in your Nix user environment.

    @@ -174,30 +174,29 @@
    Channel expression tarball - [% IF product.subtype != "-" %]for [% product.subtype %][% END %] + [% IF product.subtype != "-" %]for [% product.subtype | html %][% END %] File[% product.subtype %][% HTML.escape(product.subtype) %] - - [% product.name %] + uri) %]> + [% product.name | html %] - [% WRAPPER makePopover title="Details" classes="btn-mini" %] + [% WRAPPER makePopover title="Details" classes="btn-secondary btn-sm" %] [% INCLUDE renderProductLinks %] - - - + +
    File size:[% product.filesize %] bytes ([% mibs(product.filesize / (1024 * 1024)) %] MiB)
    SHA-1 hash:[% product.sha1hash %]
    SHA-256 hash:[% product.sha256hash %]
    Full path:[% product.path %]
    SHA-256 hash:[% product.sha256hash | html %]
    Full path:[% product.path | html %]
    [% END %] [% IF localStore %] - Contents + contents) %]>Contents [% END %]
    Code coverage - + uri) %]> Analysis report Report - - [% product.subtype %] + uri) %]> + [% product.subtype | html %] - + uri) %]> [% SWITCH product.subtype %] [% CASE "readme" %] Read Me! @@ -250,12 +249,12 @@ [% CASE "release-notes" %] Release notes [% CASE DEFAULT %] - [% product.subtype %] + [% HTML.escape(product.subtype) %] [% END %] - [% WRAPPER makePopover title="Details" classes="btn-mini" %] + [% WRAPPER makePopover title="Details" classes="btn-secondary btn-sm" %] [% INCLUDE renderProductLinks %]
    @@ -267,12 +266,12 @@
    - [% product.type %] + [% product.type | html %] - [% product %] + [% HTML.escape(product) %] Enabled: [% project.enabled ? "Yes" : "No" %]
    Enable Dynamic RunCommand Hooks:[% c.config.dynamicruncommand.enable ? project.enable_dynamic_run_command ? "Yes" : "No (not enabled by project)" : "No (not enabled by server)" %]
  • diff --git a/src/root/queue-runner-status.tt b/src/root/queue-runner-status.tt index d2896973..b887cae4 100644 --- a/src/root/queue-runner-status.tt +++ b/src/root/queue-runner-status.tt @@ -1,8 +1,6 @@ [% WRAPPER layout.tt title="Queue runner status" %] [% PROCESS common.tt %] -
    -[% HTML.escape(status) %]
    -
    +
    [% HTML.escape(status) %]
    [% END %] diff --git a/src/root/queue-summary.tt b/src/root/queue-summary.tt index b6cd4339..77ddc229 100644 --- a/src/root/queue-summary.tt +++ b/src/root/queue-summary.tt @@ -39,7 +39,7 @@ [% FOREACH s IN systems %] [% HTML.escape(s.system) %] - [% s.c %] + [% HTML.escape(s.c) %] [% END %] diff --git a/src/root/reproduce.tt b/src/root/reproduce.tt index 73bbaf11..d8a77518 100644 --- a/src/root/reproduce.tt +++ b/src/root/reproduce.tt @@ -7,7 +7,7 @@ main() { set -e -tmpDir=${TMPDIR:-/tmp}/build-[% build.id +%] +tmpDir=$(realpath "${TMPDIR:-/tmp}")/build-[% build.id +%] declare -a args extraArgs @@ -117,7 +117,7 @@ else revCount="$(cat "$tmpDir/[% input.name %]/rev-count")" fi -args+=(--arg '[% input.name %]' "{ outPath = $inputDir; rev = \"[% input.revision %]\"; shortRev = \"[% input.revision.substr(0, 7) %]\"; revCount = \"$revCount\"; }") +args+=(--arg '[% input.name %]' "{ outPath = $inputDir; rev = \"[% input.revision %]\"; shortRev = \"[% input.revision.substr(0, 7) %]\"; revCount = $revCount; }") [%+ ELSIF input.type == "hg" %] @@ -139,7 +139,7 @@ else revCount="$(cat "$tmpDir/[% input.name %]/rev-count")" fi -args+=(--arg '[% input.name %]' "{ outPath = $inputDir; rev = \"[% input.revision %]\"; revCount = \"$revCount\"; }") +args+=(--arg '[% input.name %]' "{ outPath = $inputDir; rev = \"[% input.revision %]\"; revCount = $revCount; }") [%+ ELSIF input.type == "svn" %] @@ -169,7 +169,7 @@ echo "$0: input ‘[% input.name %]’ has unsupported type ‘[% input.type %] exit 1 [% END %] -[% IF input.name == build.nixexprinput +%] +[% IF input.name == eval.nixexprinput +%] nixExprInputDir="$inputDir" [%+ END %] @@ -197,7 +197,7 @@ args+=(--option extra-binary-caches '[% c.uri_for('/') %]') # when evaluating jobs that rely on builtins.currentSystem. args+=(--option system x86_64-linux) -args+=("$nixExprInputDir/[% build.nixexprpath %]" -A '[% build.job.name %]') +args+=("$nixExprInputDir/[% eval.nixexprpath %]" -A '[% build.job.name %]') if [ -n "$printFlags" ]; then first=1 @@ -210,10 +210,9 @@ if [ -n "$printFlags" ]; then fi info "running nix-build..." -echo "using these flags: ${args[@]}" >&2 - -exec nix-build "${args[@]}" "${extraArgs[@]}" - +echo "using the following invocation:" >&2 +set -x +nix-build "${args[@]}" "${extraArgs[@]}" } main "$@" diff --git a/src/root/runcommand-log.tt b/src/root/runcommand-log.tt new file mode 100644 index 00000000..c1c214a2 --- /dev/null +++ b/src/root/runcommand-log.tt @@ -0,0 +1,49 @@ +[% WRAPPER layout.tt + titleHTML="RunCommand log of " _ (step ? " step $step.stepnr of " : "") _ "build ${build.id} of job " _ linkToJob(build.jobset, job) + title="RunCommand log of " _ (step ? " step $step.stepnr of " : "") _ "build ${build.id} of job " _ makeNameTextForJob(build.jobset, job) +%] +[% PROCESS common.tt %] + +

    + Below + [% IF tail %] + are the last lines of + [% ELSE %] + is + [% END %] + the output of a RunCommand execution of the command [% HTML.escape(runcommandlog.command) %] + on c.uri_for('/build', build.id)) %]>Build [% HTML.escape(build.id) %]. + [% IF tail %] + The c.uri_for('/build', build.id, 'runcommandlog', runcommandlog.uuid)) %]>full log is also available. + [% END %] +

    + +
    +Loading...
    +
    + + + +[% END %] diff --git a/src/root/search.tt b/src/root/search.tt index d2b2dece..1a8351c5 100644 --- a/src/root/search.tt +++ b/src/root/search.tt @@ -7,7 +7,7 @@ [% IF builds.size > 0 %] -

    The following builds match your query:[% IF builds.size > limit %] (first [% limit %] results only)[% END %]

    +

    The following builds match your query:[% IF builds.size > limit %] (first [% HTML.escape(limit) %] results only)[% END %]

    [% INCLUDE renderBuildList %] @@ -58,7 +58,7 @@ [% IF jobs.size > 0; matched = 1 %] -

    The following jobs match your query:[% IF jobs.size > limit %] (first [% limit %] results only)[% END %]

    +

    The following jobs match your query:[% IF jobs.size > limit %] (first [% HTML.escape(limit) %] results only)[% END %]

    @@ -67,7 +67,7 @@ [% FOREACH j IN jobs %] - + [% END %] @@ -81,7 +81,7 @@ [% IF builds.size > 0 || buildsdrv.size > 0 ; matched = 1 ; END %] [% IF !matched %] -
    Sorry! Nothing matches your query.
    +
    Sorry! Nothing matches your query.
    [% END %] [% END %] diff --git a/src/root/static/css/hydra.css b/src/root/static/css/hydra.css index 7e462f0b..3899e766 100644 --- a/src/root/static/css/hydra.css +++ b/src/root/static/css/hydra.css @@ -1,5 +1,5 @@ div.skip-topbar { - padding-top: 40px; + padding-top: 20px; margin-bottom: 1.5em; } @@ -33,10 +33,9 @@ span:target > span.dep-tree-line { font-weight: bold; } -:target { - padding-top: 40px; - margin-top: -40px; - display: inline-block; /* required for webkit browsers */ +span.dep-tree-buttons { + font-style: italic; + padding-left: 10px; } span.disabled-project, span.disabled-jobset, span.disabled-job { @@ -52,11 +51,6 @@ table.info-table th { padding-bottom: 0.2em; } -/* Missing in bootstrap 2.0.2 */ -.text-warning { - color: #c09853; -} - table.clickable-rows > tbody > tr { cursor: pointer; } @@ -149,6 +143,278 @@ td.step-status span.warn { } .date { - cursor: help; - border-bottom: 1px dotted #999; + cursor: help; + border-bottom: 1px dotted #999; +} + +.tab-content > .tab-pane { + padding-top: 1.5rem; +} + +.container { + max-width: 80%; +} + +.tab-content { + margin-right: 0 !important; +} + +body { + line-height: 1; +} + +.navbar-nav { + line-height: 1.5; +} + +.dropdown-item { + line-height: 1.5; +} + +a.squiggle:hover { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg id='squiggle-link' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:ev='http://www.w3.org/2001/xml-events' viewBox='0 0 10 18'%3E%3Cstyle type='text/css'%3E.squiggle{animation:shift .5s linear infinite;}@keyframes shift {from {transform:translateX(-10px);}to {transform:translateX(0);}}%3C/style%3E%3Cpath fill='none' stroke='%230056b3' stroke-width='0.65' class='squiggle' d='M0,17.5 c 2.5,0,2.5,-1.5,5,-1.5 s 2.5,1.5,5,1.5 c 2.5,0,2.5,-1.5,5,-1.5 s 2.5,1.5,5,1.5' /%3E%3C/svg%3E"); + background-position: 0 100%; + background-size: auto 24px; + background-repeat: repeat; + text-decoration: none; + border-bottom: none; + padding-bottom: 1px; +} + +table.pressureTable { + margin-left: 2em; +} + +table.pressureTable td { + padding: 0 .4em; +} + +@media (prefers-color-scheme: dark) { + /* Prevent some flickering */ + html { + background-color: #1f1f1f; + } + body, div.popover, div.popover-body { + background-color: #1f1f1f; + color: #fafafa !important; + } + + /* + Navbar + */ + nav.navbar { + background-color: #303030 !important; + border-bottom: solid 1px #404040; + } + + nav.navbar a { + color: #fafafa !important; + } + + .dropdown-menu { + background-color: #1f1f1f; + } + + .dropdown-header { + color: #999999; + font-weight: 700; + } + + .dropdown-menu .dropdown-item:hover, + .dropdown-menu .dropdown-item.active { + background-color: #2f2f30; + color: white; + } + + .dropdown-menu .dropdown-item.active { + font-weight: 700; + } + + /* + UI Elements + */ + label.btn-secondary, + button.btn, + a.btn-secondary { + background-color: #333; + border: none; + box-shadow: inset 0 0 0 1px #525252; + cursor: pointer; + } + + input, + select, + textarea { + color: white !important; + background-color: #404040 !important; + border-color: #c4c4c4; + box-shadow: inset 0 0 0 1px #4f4f4f; + border: none !important; + } + + div.card.bg-light, + div.card, + pre { + background-color: #333333 !important; + color: white; + } + + div.page-header { + color: white; + font-weight: 600; + } + + div.dropdown.show > div.dropdown-menu a { + color: white; + } + + span.muted { + color: #666666; + } + + /* + Tables + */ + table { + border: 1px solid #404040; + border-radius: 4px; + } + + table.info-table, #tabs-summary table:nth-of-type(1) { + border: none; + } + /* Weird fix */ + #tabs-summary table:nth-of-type(1) tr:hover a, + div.popover td:hover a, div.popover td:hover a tt { + text-decoration: none !important; + } + + th, + td { + color: white; + } + + thead tr th, table:not(.info-table) > tbody > tr > th { + background-color: #303030; + border-bottom: 1px solid #333 !important; + border-top: 1px solid #333; + } + + table.clickable-rows tbody tr:hover { + background-color: #033464 !important; + border-top: 1px solid #0b5cad; + border-bottom: 1px solid #0b5cad; + } + + tbody tr:hover a:not(.btn-secondary), + tbody tr:hover a > tt { + text-decoration: underline; + } + + tbody td { + border-color: #404040 !important; + } + + .table-striped tbody tr:nth-of-type(2n+1) { + background-color: initial; + } + + /* + Tabs + */ + ul.nav-tabs { + border-bottom: 1px solid #404040; + } + + ul.nav-tabs > li.nav-item a { + padding: 8px; + font-size: 14px; + line-height: 28px; + color: #999; + } + + ul.nav-tabs > li.nav-item a.active { + color: #fff !important; + border: none; + border-bottom: 2px solid #868686 !important; + background: transparent; + font-weight: 600; + } + + ul.nav-tabs a { + border: none !important; + transition: background-color 100ms linear, color 100ms linear, border 100ms linear; + border-bottom: 2px solid transparent !important; + } + + ul.nav-tabs li:not(.dropdown.show) a:hover { + color: #fff !important; + border-bottom: 2px solid #c4c4c4 !important; + } + + ul.nav-tabs a.dropdown-item { + color: white !important; + } + + /* + Pagination + */ + ul.pagination { + border: none; + } + + ul.pagination li.disabled a { + background-color: #1f1f1f; + border-color: #525252 !important; + color: #868686; + } + + ul.pagination li.disabled:hover a { + background-color: initial !important; + } + + ul.pagination li a { + color: rgb(250, 250, 250); + background-color: #1d1d1d !important; + border-color: #525252; + line-height: 1rem; + padding-left: 0.75rem; + padding-right: 0.75rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + } + + ul.pagination li:hover:not(.disabled) a { + background: #303030 !important; + box-shadow: inset 0 0 0 2px #868686,0 2px 2px 0 rgba(0,0,0,0.08); + border-color: #525252 !important; + color: rgb(250,250, 250); + } + + /* + Popover + */ + div.popover { + border: solid 1px white; + } + + /* + Modal Dialogs + */ + div.modal-content { + background-color: #1f1f1f; + } + + /* + Graphs + */ + div.flot-tooltip { + border: solid 1px white; + background-color: #1f1f1f; + color: #fafafa !important; + } + + div.flot-text { + color: #fafafa !important; + } } diff --git a/src/root/static/css/tree.css b/src/root/static/css/tree.css index 71df2f12..e80f6871 100644 --- a/src/root/static/css/tree.css +++ b/src/root/static/css/tree.css @@ -9,6 +9,7 @@ ul.tree, ul.subtree { ul.subtree > li { position: relative; padding-left: 2.0em; + line-height: 140%; border-left: 0.1em solid #6185a0; } diff --git a/src/root/static/images/ajax-loader.gif b/src/root/static/images/ajax-loader.gif deleted file mode 100644 index 3288d103..00000000 Binary files a/src/root/static/images/ajax-loader.gif and /dev/null differ diff --git a/src/root/static/js/bootbox.min.js b/src/root/static/js/bootbox.min.js index 3c75bb1e..9bea53bc 100644 --- a/src/root/static/js/bootbox.min.js +++ b/src/root/static/js/bootbox.min.js @@ -1,16 +1,6 @@ -/** - * bootbox.js v2.5.1 - * - * http://bootboxjs.com/license.txt - */ -var bootbox=window.bootbox||function(k){function h(b,a){null==a&&(a=m);return"string"==typeof i[a][b]?i[a][b]:a!=n?h(b,n):b}var m="en",n="en",s=!0,r="static",t="",j={},e={},i={en:{OK:"OK",CANCEL:"Cancel",CONFIRM:"OK"},fr:{OK:"OK",CANCEL:"Annuler",CONFIRM:"D'accord"},de:{OK:"OK",CANCEL:"Abbrechen",CONFIRM:"Akzeptieren"},es:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Aceptar"},br:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Sim"},nl:{OK:"OK",CANCEL:"Annuleren",CONFIRM:"Accepteren"},ru:{OK:"OK",CANCEL:"\u041e\u0442\u043c\u0435\u043d\u0430", -CONFIRM:"\u041f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c"},it:{OK:"OK",CANCEL:"Annulla",CONFIRM:"Conferma"}};e.setLocale=function(b){for(var a in i)if(a==b){m=b;return}throw Error("Invalid locale: "+b);};e.addLocale=function(b,a){"undefined"==typeof i[b]&&(i[b]={});for(var c in a)i[b][c]=a[c]};e.setIcons=function(b){j=b;if("object"!==typeof j||null==j)j={}};e.alert=function(){var b="",a=h("OK"),c=null;switch(arguments.length){case 1:b=arguments[0];break;case 2:b=arguments[0];"function"==typeof arguments[1]? -c=arguments[1]:a=arguments[1];break;case 3:b=arguments[0];a=arguments[1];c=arguments[2];break;default:throw Error("Incorrect number of arguments: expected 1-3");}return e.dialog(b,{label:a,icon:j.OK,callback:c},{onEscape:c})};e.confirm=function(){var b="",a=h("CANCEL"),c=h("CONFIRM"),f=null;switch(arguments.length){case 1:b=arguments[0];break;case 2:b=arguments[0];"function"==typeof arguments[1]?f=arguments[1]:a=arguments[1];break;case 3:b=arguments[0];a=arguments[1];"function"==typeof arguments[2]? -f=arguments[2]:c=arguments[2];break;case 4:b=arguments[0];a=arguments[1];c=arguments[2];f=arguments[3];break;default:throw Error("Incorrect number of arguments: expected 1-4");}return e.dialog(b,[{label:a,icon:j.CANCEL,callback:function(){"function"==typeof f&&f(!1)}},{label:c,icon:j.CONFIRM,callback:function(){"function"==typeof f&&f(!0)}}])};e.prompt=function(){var b="",a=h("CANCEL"),c=h("CONFIRM"),f=null,u="";switch(arguments.length){case 1:b=arguments[0];break;case 2:b=arguments[0];"function"== -typeof arguments[1]?f=arguments[1]:a=arguments[1];break;case 3:b=arguments[0];a=arguments[1];"function"==typeof arguments[2]?f=arguments[2]:c=arguments[2];break;case 4:b=arguments[0];a=arguments[1];c=arguments[2];f=arguments[3];break;case 5:b=arguments[0];a=arguments[1];c=arguments[2];f=arguments[3];u=arguments[4];break;default:throw Error("Incorrect number of arguments: expected 1-5");}var p=k("");p.append("");var d=e.dialog(p,[{label:a, -icon:j.CANCEL,callback:function(){"function"==typeof f&&f(null)}},{label:c,icon:j.CONFIRM,callback:function(){"function"==typeof f&&f(p.find("input[type=text]").val())}}],{header:b});d.on("shown",function(){p.find("input[type=text]").focus();p.on("submit",function(a){a.preventDefault();d.find(".btn-primary").click()})});return d};e.modal=function(){var b,a,c,f={onEscape:null,keyboard:!0,backdrop:r};switch(arguments.length){case 1:b=arguments[0];break;case 2:b=arguments[0];"object"==typeof arguments[1]? -c=arguments[1]:a=arguments[1];break;case 3:b=arguments[0];a=arguments[1];c=arguments[2];break;default:throw Error("Incorrect number of arguments: expected 1-3");}f.header=a;c="object"==typeof c?k.extend(f,c):f;return e.dialog(b,[],c)};e.dialog=function(b,a,c){var f=null,e="",j=[],c=c||{};null==a?a=[]:"undefined"==typeof a.length&&(a=[a]);for(var d=a.length;d--;){var h=null,i=null,l=null,m="",n=null;if("undefined"==typeof a[d].label&&"undefined"==typeof a[d]["class"]&&"undefined"==typeof a[d].callback){var h= -0,i=null,q;for(q in a[d])if(i=q,1<++h)break;1==h&&"function"==typeof a[d][q]&&(a[d].label=i,a[d].callback=a[d][q])}"function"==typeof a[d].callback&&(n=a[d].callback);a[d]["class"]?l=a[d]["class"]:d==a.length-1&&2>=a.length&&(l="btn-primary");h=a[d].label?a[d].label:"Option "+(d+1);a[d].icon&&(m=" ");i=a[d].href?a[d].href:"javascript:;";e+=""+m+""+h+"";j[d]=n}d=["");var g=k(d.join("\n"));("undefined"===typeof c.animate?s:c.animate)&&g.addClass("fade");(e="undefined"===typeof c.classes?t:c.classes)&&g.addClass(e);k(".modal-body",g).html(b);g.bind("hidden", -function(){g.remove()});g.bind("hide",function(){if("escape"==f&&"function"==typeof c.onEscape)c.onEscape()});k(document).bind("keyup.modal",function(a){27==a.which&&(f="escape")});g.bind("shown",function(){k("a.btn-primary:last",g).focus()});g.on("click",".modal-footer a, a.close",function(b){var c=k(this).data("handler"),d=j[c],e=null;"undefined"!==typeof c&&"undefined"!==typeof a[c].href||(b.preventDefault(),"function"==typeof d&&(e=d()),!1!==e&&(f="button",g.modal("hide")))});null==c.keyboard&& -(c.keyboard="function"==typeof c.onEscape);k("body").append(g);g.modal({backdrop:"undefined"===typeof c.backdrop?r:c.backdrop,keyboard:c.keyboard});return g};e.hideAll=function(){k(".bootbox").modal("hide")};e.animate=function(b){s=b};e.backdrop=function(b){r=b};e.classes=function(b){t=b};return e}(window.jQuery);window.bootbox=bootbox; +/** + * bootbox.js 5.2.0 + * + * http://bootboxjs.com/license.txt + */ +!function(e,t){'use strict';'function'==typeof define&&define.amd?define(['jquery'],t):'object'==typeof exports?module.exports=t(require('jquery')):e.bootbox=t(e.jQuery)}(this,function t(p,u){'use strict';var r,n,i,l;Object.keys||(Object.keys=(r=Object.prototype.hasOwnProperty,n=!{toString:null}.propertyIsEnumerable('toString'),l=(i=['toString','toLocaleString','valueOf','hasOwnProperty','isPrototypeOf','propertyIsEnumerable','constructor']).length,function(e){if('function'!=typeof e&&('object'!=typeof e||null===e))throw new TypeError('Object.keys called on non-object');var t,o,a=[];for(t in e)r.call(e,t)&&a.push(t);if(n)for(o=0;o
    ",header:"
    ",footer:'',closeButton:'',form:'
    ',button:'',option:'',promptMessage:'
    ',inputs:{text:'',textarea:'',email:'',select:'',checkbox:'
    ',radio:'
    ',date:'',time:'',number:'',password:'',range:''}},m={locale:'en',backdrop:'static',animate:!0,className:null,closeButton:!0,show:!0,container:'body',value:'',inputType:'text',swapButtonOrder:!1,centerVertical:!1,multiple:!1,scrollable:!1};function c(e,t,o){return p.extend(!0,{},e,function(e,t){var o=e.length,a={};if(o<1||2').attr('label',t.group)),o=i[t.group]);var a=p(f.option);a.attr('value',t.value).text(t.text),o.append(a)}),v(i,function(e,t){n.append(t)}),n.val(r.value);break;case'checkbox':var l=p.isArray(r.value)?r.value:[r.value];if(!(a=r.inputOptions||[]).length)throw new Error('prompt with "inputType" set to "checkbox" requires at least one option');n=p('
    '),v(a,function(e,o){if(o.value===u||o.text===u)throw new Error('each option needs a "value" property and a "text" property');var a=p(f.inputs[r.inputType]);a.find('input').attr('value',o.value),a.find('label').append('\n'+o.text),v(l,function(e,t){t===o.value&&a.find('input').prop('checked',!0)}),n.append(a)});break;case'radio':if(r.value!==u&&p.isArray(r.value))throw new Error('prompt with "inputType" set to "radio" requires a single, non-array value for "value"');if(!(a=r.inputOptions||[]).length)throw new Error('prompt with "inputType" set to "radio" requires at least one option');n=p('
    ');var s=!0;v(a,function(e,t){if(t.value===u||t.text===u)throw new Error('each option needs a "value" property and a "text" property');var o=p(f.inputs[r.inputType]);o.find('input').attr('value',t.value),o.find('label').append('\n'+t.text),r.value!==u&&t.value===r.value&&(o.find('input').prop('checked',!0),s=!1),n.append(o)}),s&&n.find('input[type="radio"]').first().prop('checked',!0)}if(e.append(n),e.on('submit',function(e){e.preventDefault(),e.stopPropagation(),t.find('.bootbox-accept').trigger('click')}),''!==p.trim(r.message)){var c=p(f.promptMessage).html(r.message);e.prepend(c),r.message=e}else r.message=e;return(t=d.dialog(r)).off('shown.bs.modal'),t.on('shown.bs.modal',function(){n.focus()}),!0===o&&t.modal('show'),t},d.addLocale('en',{OK:'OK',CANCEL:'Cancel',CONFIRM:'OK'}),d}); \ No newline at end of file diff --git a/src/root/static/js/common.js b/src/root/static/js/common.js index e9c5e576..9f31d1e6 100644 --- a/src/root/static/js/common.js +++ b/src/root/static/js/common.js @@ -1,10 +1,9 @@ -$(document).ready(function() { - +function makeTreeCollapsible(tab) { /*** Tree toggles in logfiles. ***/ /* Set the appearance of the toggle depending on whether the corresponding subtree is initially shown or hidden. */ - $(".tree-toggle").map(function() { + tab.find(".tree-toggle").map(function() { if ($(this).siblings("ul:hidden").length == 0) { $(this).text("-"); } else { @@ -13,7 +12,7 @@ $(document).ready(function() { }); /* When a toggle is clicked, show or hide the subtree. */ - $(".tree-toggle").click(function() { + tab.find(".tree-toggle").click(function() { if ($(this).siblings("ul:hidden").length != 0) { $(this).siblings("ul").show(); $(this).text("-"); @@ -24,21 +23,23 @@ $(document).ready(function() { }); /* Implementation of the expand all link. */ - $(".tree-expand-all").click(function() { - $(".tree-toggle", $(this).parent().siblings(".tree")).map(function() { + tab.find(".tree-expand-all").click(function() { + tab.find(".tree-toggle", $(this).parent().siblings(".tree")).map(function() { $(this).siblings("ul").show(); $(this).text("-"); }); }); /* Implementation of the collapse all link. */ - $(".tree-collapse-all").click(function() { - $(".tree-toggle", $(this).parent().siblings(".tree")).map(function() { + tab.find(".tree-collapse-all").click(function() { + tab.find(".tree-toggle", $(this).parent().siblings(".tree")).map(function() { $(this).siblings("ul").hide(); $(this).text("+"); }); }); +} +$(document).ready(function() { $("table.clickable-rows").click(function(event) { if ($(event.target).closest("a").length) return; link = $(event.target).parents("tr").find("a.row-link"); @@ -46,29 +47,31 @@ $(document).ready(function() { window.location = link.attr("href"); }); - bootbox.animate(false); + bootbox.setDefaults({ animate: false }); + /* Enable popovers (and allow table and teletype elements in them). */ + $.fn.popover.Constructor.Default.whiteList.table = []; + $.fn.popover.Constructor.Default.whiteList.thead = [] + $.fn.popover.Constructor.Default.whiteList.tbody = []; + $.fn.popover.Constructor.Default.whiteList.tr = []; + $.fn.popover.Constructor.Default.whiteList.th = []; + $.fn.popover.Constructor.Default.whiteList.td = []; + $.fn.popover.Constructor.Default.whiteList.tt = []; $(".hydra-popover").popover({}); - $(function() { - if (window.location.hash) { - $(".nav-tabs a[href='" + window.location.hash + "']").tab('show'); - } + /* Activates tab according to URL anchor. */ + if (window.location.hash) { + setTimeout(function () { $('.nav-tabs > .nav-item:not(.dropdown) a[href="' + window.location.hash + '"]').trigger('show.bs.tab'); }, 0); + $('.nav-tabs > .nav-item:not(.dropdown) a[href="' + window.location.hash + '"]').tab('show'); + } - /* If no tab is active, show the first one. */ - $(".nav-tabs").each(function() { - if ($("li.active", this).length > 0) return; - $("a", $(this).children("li:not(.dropdown)").first()).tab('show'); - }); + $('.nav-tabs').each(function() { + if ($('.nav-item:not(.dropdown) a.active',this).length == 0) + $('.nav-item:not(.dropdown) a',this).first().tab('show'); + }); - /* Ensure that pressing the back button on another page - navigates back to the previously selected tab on this - page. */ - $('.nav-tabs').bind('show', function(e) { - var pattern = /#.+/gi; - var id = e.target.toString().match(pattern)[0]; - history.replaceState(null, "", id); - }); + $('.nav-tabs > .nav-item:not(.dropdown) a[href^="#"]').on('click', function() { + history.replaceState(null, null, window.location.href.split("#")[0] + $(this).attr("href")); }); /* Automatically set Bootstrap radio buttons from hidden form controls. */ @@ -126,23 +129,33 @@ $(document).ready(function() { el.addClass("is-local"); } }); + + [...document.getElementsByTagName("iframe")].forEach((element) => { + element.contentWindow.addEventListener("DOMContentLoaded", (_) => { + element.style.height = element.contentWindow.document.body.scrollHeight + 'px'; + }) + }) }); var tabsLoaded = {}; -function makeLazyTab(tabName, uri) { - $('.nav-tabs').bind('show', function(e) { +function makeLazyTab(tabName, uri, callback) { + $('.nav-tabs').bind('show.bs.tab', function(e) { var pattern = /#.+/gi; var id = e.target.toString().match(pattern)[0]; if (id == '#' + tabName && !tabsLoaded[id]) { tabsLoaded[id] = 1; $('#' + tabName).load(uri, function(response, status, xhr) { var lazy = xhr.getResponseHeader("X-Hydra-Lazy") === "Yes"; + var tab = $('#' + tabName); if (status == "error" && !lazy) { - $('#' + tabName).html("
    Error loading tab: " + xhr.status + " " + xhr.statusText + "
    "); + tab.html("
    Error loading tab: " + xhr.status + " " + xhr.statusText + "
    "); } else { - $('#' + tabName).html(response); + tab.html(response); + if (callback) { + callback(tab); + } } }); } diff --git a/src/root/static/js/jquery/jquery-1.12.3.min.js b/src/root/static/js/jquery/jquery-1.12.3.min.js deleted file mode 100644 index dad4f0af..00000000 --- a/src/root/static/js/jquery/jquery-1.12.3.min.js +++ /dev/null @@ -1,5 +0,0 @@ -/*! jQuery v1.12.3 | (c) jQuery Foundation | jquery.org/license */ -!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="1.12.3",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(!l.ownFirst)for(b in a)return k.call(a,b);for(b in a);return void 0===b||k.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(h)return h.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=e.call(arguments,2),d=function(){return a.apply(b||this,c.concat(e.call(arguments)))},d.guid=a.guid=a.guid||n.guid++,d):void 0},now:function(){return+new Date},support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}if(f=d.getElementById(e[2]),f&&f.parentNode){if(f.id!==e[2])return A.find(a);this.length=1,this[0]=f}return this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||(e=n.uniqueSort(e)),D.test(a)&&(e=e.reverse())),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=!0,c||j.disable(),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.addEventListener?(d.removeEventListener("DOMContentLoaded",K),a.removeEventListener("load",K)):(d.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(d.addEventListener||"load"===a.event.type||"complete"===d.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll)a.setTimeout(n.ready);else if(d.addEventListener)d.addEventListener("DOMContentLoaded",K),a.addEventListener("load",K);else{d.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&d.documentElement}catch(e){}c&&c.doScroll&&!function f(){if(!n.isReady){try{c.doScroll("left")}catch(b){return a.setTimeout(f,50)}J(),n.ready()}}()}return I.promise(b)},n.ready.promise();var L;for(L in n(l))break;l.ownFirst="0"===L,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c,e;c=d.getElementsByTagName("body")[0],c&&c.style&&(b=d.createElement("div"),e=d.createElement("div"),e.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(e).appendChild(b),"undefined"!=typeof b.style.zoom&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",l.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(e))}),function(){var a=d.createElement("div");l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}a=null}();var M=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b},N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0; -}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function R(a,b,d,e){if(M(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),"object"!=typeof b&&"function"!=typeof b||(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f}}function S(a,b,c){if(M(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=void 0)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},Z=/^(?:checkbox|radio)$/i,$=/<([\w:-]+)/,_=/^$|\/(?:java|ecma)script/i,aa=/^\s+/,ba="abbr|article|aside|audio|bdi|canvas|data|datalist|details|dialog|figcaption|figure|footer|header|hgroup|main|mark|meter|nav|output|picture|progress|section|summary|template|time|video";function ca(a){var b=ba.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}!function(){var a=d.createElement("div"),b=d.createDocumentFragment(),c=d.createElement("input");a.innerHTML="
    [% INCLUDE renderFullJobName project=j.get_column('project') jobset=j.get_column('jobset') job=j.job inRow=1 %][% INCLUDE renderFullJobName project=j.jobset.get_column('project') jobset=j.jobset.get_column('name') job=j.job inRow=1 %]
    a",l.leadingWhitespace=3===a.firstChild.nodeType,l.tbody=!a.getElementsByTagName("tbody").length,l.htmlSerialize=!!a.getElementsByTagName("link").length,l.html5Clone="<:nav>"!==d.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,b.appendChild(c),l.appendChecked=c.checked,a.innerHTML="",l.noCloneChecked=!!a.cloneNode(!0).lastChild.defaultValue,b.appendChild(a),c=d.createElement("input"),c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),a.appendChild(c),l.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!!a.addEventListener,a[n.expando]=1,l.attributes=!a.getAttribute(n.expando)}();var da={option:[1,""],legend:[1,"
    ","
    "],area:[1,"",""],param:[1,"",""],thead:[1,"","
    "],tr:[2,"","
    "],col:[2,"","
    "],td:[3,"","
    "],_default:l.htmlSerialize?[0,"",""]:[1,"X
    ","
    "]};da.optgroup=da.option,da.tbody=da.tfoot=da.colgroup=da.caption=da.thead,da.th=da.td;function ea(a,b){var c,d,e=0,f="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,ea(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function fa(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}var ga=/<|&#?\w+;/,ha=/r;r++)if(g=a[r],g||0===g)if("object"===n.type(g))n.merge(q,g.nodeType?[g]:g);else if(ga.test(g)){i=i||p.appendChild(b.createElement("div")),j=($.exec(g)||["",""])[1].toLowerCase(),m=da[j]||da._default,i.innerHTML=m[1]+n.htmlPrefilter(g)+m[2],f=m[0];while(f--)i=i.lastChild;if(!l.leadingWhitespace&&aa.test(g)&&q.push(b.createTextNode(aa.exec(g)[0])),!l.tbody){g="table"!==j||ha.test(g)?""!==m[1]||ha.test(g)?0:i:i.firstChild,f=g&&g.childNodes.length;while(f--)n.nodeName(k=g.childNodes[f],"tbody")&&!k.childNodes.length&&g.removeChild(k)}n.merge(q,i.childNodes),i.textContent="";while(i.firstChild)i.removeChild(i.firstChild);i=p.lastChild}else q.push(b.createTextNode(g));i&&p.removeChild(i),l.appendChecked||n.grep(ea(q,"input"),ia),r=0;while(g=q[r++])if(d&&n.inArray(g,d)>-1)e&&e.push(g);else if(h=n.contains(g.ownerDocument,g),i=ea(p.appendChild(g),"script"),h&&fa(i),c){f=0;while(g=i[f++])_.test(g.type||"")&&c.push(g)}return i=null,p}!function(){var b,c,e=d.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b]=c in a)||(e.setAttribute(c,"t"),l[b]=e.attributes[c].expando===!1);e=null}();var ka=/^(?:input|select|textarea)$/i,la=/^key/,ma=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,na=/^(?:focusinfocus|focusoutblur)$/,oa=/^([^.]*)(?:\.(.+)|)/;function pa(){return!0}function qa(){return!1}function ra(){try{return d.activeElement}catch(a){}}function sa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)sa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=qa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return"undefined"==typeof n||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(G)||[""],h=b.length;while(h--)f=oa.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=oa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,e,f){var g,h,i,j,l,m,o,p=[e||d],q=k.call(b,"type")?b.type:b,r=k.call(b,"namespace")?b.namespace.split("."):[];if(i=m=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!na.test(q+n.event.triggered)&&(q.indexOf(".")>-1&&(r=q.split("."),q=r.shift(),r.sort()),h=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=r.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:n.makeArray(c,[b]),l=n.event.special[q]||{},f||!l.trigger||l.trigger.apply(e,c)!==!1)){if(!f&&!l.noBubble&&!n.isWindow(e)){for(j=l.delegateType||q,na.test(j+q)||(i=i.parentNode);i;i=i.parentNode)p.push(i),m=i;m===(e.ownerDocument||d)&&p.push(m.defaultView||m.parentWindow||a)}o=0;while((i=p[o++])&&!b.isPropagationStopped())b.type=o>1?j:l.bindType||q,g=(n._data(i,"events")||{})[b.type]&&n._data(i,"handle"),g&&g.apply(i,c),g=h&&i[h],g&&g.apply&&M(i)&&(b.result=g.apply(i,c),b.result===!1&&b.preventDefault());if(b.type=q,!f&&!b.isDefaultPrevented()&&(!l._default||l._default.apply(p.pop(),c)===!1)&&M(e)&&h&&e[q]&&!n.isWindow(e)){m=e[h],m&&(e[h]=null),n.event.triggered=q;try{e[q]()}catch(s){}n.event.triggered=void 0,m&&(e[h]=m)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())a.rnamespace&&!a.rnamespace.test(g.namespace)||(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]","i"),va=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,wa=/\s*$/g,Aa=ca(d),Ba=Aa.appendChild(d.createElement("div"));function Ca(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function Da(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function Ea(a){var b=ya.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Fa(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Ga(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(Da(b).text=a.text,Ea(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&Z.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}}function Ha(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&xa.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),Ha(f,b,c,d)});if(o&&(k=ja(b,a[0].ownerDocument,!1,a,d),e=k.firstChild,1===k.childNodes.length&&(k=e),e||d)){for(i=n.map(ea(k,"script"),Da),h=i.length;o>m;m++)g=k,m!==p&&(g=n.clone(g,!0,!0),h&&n.merge(i,ea(g,"script"))),c.call(a[m],g,m);if(h)for(j=i[i.length-1].ownerDocument,n.map(i,Ea),m=0;h>m;m++)g=i[m],_.test(g.type||"")&&!n._data(g,"globalEval")&&n.contains(j,g)&&(g.src?n._evalUrl&&n._evalUrl(g.src):n.globalEval((g.text||g.textContent||g.innerHTML||"").replace(za,"")));k=e=null}return a}function Ia(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(ea(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&fa(ea(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(va,"<$1>")},clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!ua.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(Ba.innerHTML=a.outerHTML,Ba.removeChild(f=Ba.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=ea(f),h=ea(a),g=0;null!=(e=h[g]);++g)d[g]&&Ga(e,d[g]);if(b)if(c)for(h=h||ea(a),d=d||ea(f),g=0;null!=(e=h[g]);g++)Fa(e,d[g]);else Fa(a,f);return d=ea(f,"script"),d.length>0&&fa(d,!i&&ea(a,"script")),d=h=e=null,f},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.attributes,m=n.event.special;null!=(d=a[h]);h++)if((b||M(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k||"undefined"==typeof d.removeAttribute?d[i]=void 0:d.removeAttribute(i),c.push(f))}}}),n.fn.extend({domManip:Ha,detach:function(a){return Ia(this,a,!0)},remove:function(a){return Ia(this,a)},text:function(a){return Y(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||d).createTextNode(a))},null,a,arguments.length)},append:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.appendChild(a)}})},prepend:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(ea(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return Y(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(ta,""):void 0;if("string"==typeof a&&!wa.test(a)&&(l.htmlSerialize||!ua.test(a))&&(l.leadingWhitespace||!aa.test(a))&&!da[($.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ea(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return Ha(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(ea(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],f=n(a),h=f.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(f[d])[b](c),g.apply(e,c.get());return this.pushStack(e)}});var Ja,Ka={HTML:"block",BODY:"block"};function La(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function Ma(a){var b=d,c=Ka[a];return c||(c=La(a,b),"none"!==c&&c||(Ja=(Ja||n("