about summary refs log tree commit diff
path: root/uploader/templates
diff options
context:
space:
mode:
Diffstat (limited to 'uploader/templates')
-rw-r--r--uploader/templates/base.html51
-rw-r--r--uploader/templates/cli-output.html8
-rw-r--r--uploader/templates/continue_from_create_dataset.html52
-rw-r--r--uploader/templates/continue_from_create_study.html52
-rw-r--r--uploader/templates/data_review.html85
-rw-r--r--uploader/templates/dbupdate_error.html12
-rw-r--r--uploader/templates/dbupdate_hidden_fields.html29
-rw-r--r--uploader/templates/errors_display.html47
-rw-r--r--uploader/templates/final_confirmation.html47
-rw-r--r--uploader/templates/flash_messages.html25
-rw-r--r--uploader/templates/http-error.html18
-rw-r--r--uploader/templates/index.html81
-rw-r--r--uploader/templates/insert_error.html32
-rw-r--r--uploader/templates/insert_progress.html46
-rw-r--r--uploader/templates/insert_success.html19
-rw-r--r--uploader/templates/job_progress.html40
-rw-r--r--uploader/templates/no_such_job.html14
-rw-r--r--uploader/templates/parse_failure.html26
-rw-r--r--uploader/templates/parse_results.html30
-rw-r--r--uploader/templates/rqtl2/create-geno-dataset-success.html55
-rw-r--r--uploader/templates/rqtl2/create-probe-dataset-success.html59
-rw-r--r--uploader/templates/rqtl2/create-probe-study-success.html49
-rw-r--r--uploader/templates/rqtl2/create-tissue-success.html106
-rw-r--r--uploader/templates/rqtl2/index.html36
-rw-r--r--uploader/templates/rqtl2/no-such-job.html13
-rw-r--r--uploader/templates/rqtl2/rqtl2-job-error.html39
-rw-r--r--uploader/templates/rqtl2/rqtl2-job-results.html24
-rw-r--r--uploader/templates/rqtl2/rqtl2-job-status.html20
-rw-r--r--uploader/templates/rqtl2/rqtl2-qc-job-error.html120
-rw-r--r--uploader/templates/rqtl2/rqtl2-qc-job-results.html66
-rw-r--r--uploader/templates/rqtl2/rqtl2-qc-job-status.html41
-rw-r--r--uploader/templates/rqtl2/rqtl2-qc-job-success.html37
-rw-r--r--uploader/templates/rqtl2/select-geno-dataset.html144
-rw-r--r--uploader/templates/rqtl2/select-population.html136
-rw-r--r--uploader/templates/rqtl2/select-probeset-dataset.html191
-rw-r--r--uploader/templates/rqtl2/select-probeset-study-id.html143
-rw-r--r--uploader/templates/rqtl2/select-tissue.html115
-rw-r--r--uploader/templates/rqtl2/summary-info.html65
-rw-r--r--uploader/templates/rqtl2/upload-rqtl2-bundle-step-01.html276
-rw-r--r--uploader/templates/rqtl2/upload-rqtl2-bundle-step-02.html33
-rw-r--r--uploader/templates/samples/select-population.html99
-rw-r--r--uploader/templates/samples/select-species.html30
-rw-r--r--uploader/templates/samples/upload-failure.html27
-rw-r--r--uploader/templates/samples/upload-progress.html22
-rw-r--r--uploader/templates/samples/upload-samples.html139
-rw-r--r--uploader/templates/samples/upload-success.html18
-rw-r--r--uploader/templates/select_dataset.html161
-rw-r--r--uploader/templates/select_platform.html82
-rw-r--r--uploader/templates/select_species.html92
-rw-r--r--uploader/templates/select_study.html108
-rw-r--r--uploader/templates/stdout_output.html8
-rw-r--r--uploader/templates/unhandled_exception.html21
-rw-r--r--uploader/templates/upload_progress_indicator.html35
-rw-r--r--uploader/templates/worker_failure.html24
54 files changed, 3348 insertions, 0 deletions
diff --git a/uploader/templates/base.html b/uploader/templates/base.html
new file mode 100644
index 0000000..eb5e6b7
--- /dev/null
+++ b/uploader/templates/base.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta application-name="GeneNetwork Quality-Control Application" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    {%block extrameta%}{%endblock%}
+
+    <title>GN Uploader: {%block title%}{%endblock%}</title>
+
+    <link rel="stylesheet" type="text/css"
+	  href="{{url_for('base.bootstrap',
+                filename='css/bootstrap.min.css')}}" />
+    <link rel="stylesheet" type="text/css"
+	  href="{{url_for('base.bootstrap',
+                filename='css/bootstrap-theme.min.css')}}" />
+
+
+    <link rel="shortcut icon" type="image/png" sizes="64x64"
+	  href="{{url_for('static', filename='images/CITGLogo.png')}}" />
+
+    <link rel="stylesheet" type="text/css" href="/static/css/custom-bootstrap.css" />
+    <link rel="stylesheet" type="text/css" href="/static/css/styles.css" />
+
+    {%block css%}{%endblock%}
+  </head>
+
+  <body>
+    <div class="navbar navbar-inverse navbar-static-top pull-left"
+         role="navigation"
+         style="width: 100%;min-width: 850px;white-space: nowrap;">
+      <div class="container-fluid" style="width: 100%">
+        <ul class="nav navbar-nav">
+          <li><a href="/" style="font-weight: bold">GN Uploader</a></li>
+          <li>
+            <a href="{{gnuri or 'https://genenetwork.org'}}">GeneNetwork</a>
+          </li>
+        </ul>
+      </div>
+    </div>
+    <div class="container">
+      {%block contents%}{%endblock%}
+    </div>
+
+    <script src="{{url_for('base.jquery',
+                 filename='jquery.min.js')}}"></script>
+    <script src="{{url_for('base.bootstrap',
+                 filename='js/bootstrap.min.js')}}"></script>
+    {%block javascript%}{%endblock%}
+  </body>
+</html>
diff --git a/uploader/templates/cli-output.html b/uploader/templates/cli-output.html
new file mode 100644
index 0000000..33fb73b
--- /dev/null
+++ b/uploader/templates/cli-output.html
@@ -0,0 +1,8 @@
+{%macro cli_output(job, stream)%}
+
+<h4>{{stream | upper}} Output</h4>
+<div class="cli-output">
+  <pre>{{job.get(stream, "")}}</pre>
+</div>
+
+{%endmacro%}
diff --git a/uploader/templates/continue_from_create_dataset.html b/uploader/templates/continue_from_create_dataset.html
new file mode 100644
index 0000000..03bb49c
--- /dev/null
+++ b/uploader/templates/continue_from_create_dataset.html
@@ -0,0 +1,52 @@
+{%extends "base.html"%}
+{%from "dbupdate_hidden_fields.html" import hidden_fields%}
+
+{%block title%}Create Study{%endblock%}
+
+{%block css%}
+<link rel="stylesheet" href="/static/css/two-column-with-separator.css" />
+{%endblock%}
+
+{%block contents%}
+<h2 class="heading">{{filename}}: create study</h2>
+
+{%with messages = get_flashed_messages(with_categories=true)%}
+{%if messages:%}
+<ul>
+  {%for category, message in messages:%}
+  <li class="{{category}}">{{message}}</li>
+  {%endfor%}
+</ul>
+{%endif%}
+{%endwith%}
+
+<div class="row">
+  <form method="POST" action="{{url_for('dbinsert.final_confirmation')}}"
+	id="select-platform-form" data-genechips="{{genechips_data}}"
+	class="two-col-sep-col1">
+    <legend>continue with new dataset</legend>
+    {{hidden_fields(
+    filename, filetype, species=species, genechipid=genechipid,
+    studyid=studyid, datasetid=datasetid, totallines=totallines)}}
+
+    <button type="submit" class="btn btn-primary">continue</button>
+  </form>
+</div>
+
+<div class="row">
+  <p class="two-col-sep-separator">OR</p>
+</div>
+
+<div class="row">
+  <form method="POST" action="{{url_for('dbinsert.select_dataset')}}"
+	id="select-platform-form" data-genechips="{{genechips_data}}"
+	class="two-col-sep-col2">
+    <legend>Select from existing dataset</legend>
+    {{hidden_fields(
+    filename, filetype, species=species, genechipid=genechipid,
+    studyid=studyid, datasetid=datasetid, totallines=totallines)}}
+
+    <button type="submit" class="btn btn-primary">go back</button>
+  </form>
+</div>
+{%endblock%}
diff --git a/uploader/templates/continue_from_create_study.html b/uploader/templates/continue_from_create_study.html
new file mode 100644
index 0000000..34e6e5e
--- /dev/null
+++ b/uploader/templates/continue_from_create_study.html
@@ -0,0 +1,52 @@
+{%extends "base.html"%}
+{%from "dbupdate_hidden_fields.html" import hidden_fields%}
+
+{%block title%}Create Study{%endblock%}
+
+{%block css%}
+<link rel="stylesheet" href="/static/css/two-column-with-separator.css" />
+{%endblock%}
+
+{%block contents%}
+<h2 class="heading">{{filename}}: create study</h2>
+
+{%with messages = get_flashed_messages(with_categories=true)%}
+{%if messages:%}
+<ul>
+  {%for category, message in messages:%}
+  <li class="{{category}}">{{message}}</li>
+  {%endfor%}
+</ul>
+{%endif%}
+{%endwith%}
+
+<div class="row">
+  <form method="POST" action="{{url_for('dbinsert.select_dataset')}}"
+	id="select-platform-form" data-genechips="{{genechips_data}}"
+	class="two-col-sep-col1">
+    <legend>continue with new study</legend>
+    {{hidden_fields(
+    filename, filetype, species=species, genechipid=genechipid,
+    studyid=studyid, totallines=totallines)}}
+
+    <button type="submit" class="btn btn-primary">continue</button>
+  </form>
+</div>
+
+<div class="row">
+  <p class="two-col-sep-separator">OR</p>
+</div>
+
+<div class="row">
+  <form method="POST" action="{{url_for('dbinsert.select_study')}}"
+	id="select-platform-form" data-genechips="{{genechips_data}}"
+	class="two-col-sep-col2">
+    <legend>Select from existing study</legend>
+    {{hidden_fields(
+    filename, filetype, species=species, genechipid=genechipid,
+    studyid=studyid, totallines=totallines)}}
+
+    <button type="submit" class="btn btn-primary">go back</button>
+  </form>
+</div>
+{%endblock%}
diff --git a/uploader/templates/data_review.html b/uploader/templates/data_review.html
new file mode 100644
index 0000000..b7528fd
--- /dev/null
+++ b/uploader/templates/data_review.html
@@ -0,0 +1,85 @@
+{%extends "base.html"%}
+
+{%block title%}Data Review{%endblock%}
+
+{%block contents%}
+<h1 class="heading">data review</h1>
+
+<div class="row">
+  <h2 id="data-concerns">Data Concerns</h2>
+  <p>The following are some of the requirements that the data in your file
+    <strong>MUST</strong> fulfil before it is considered valid for this system:
+  </p>
+
+  <ol>
+    <li>File headings
+      <ul>
+	<li>The first row in the file should contains the headings. The number of
+	  headings in this first row determines the number of columns expected for
+	  all other lines in the file.</li>
+	<li>Each heading value in the first row MUST appear in the first row
+	  <strong>ONE AND ONLY ONE</strong> time</li>
+	<li>The sample/cases (previously 'strains') headers in your first row will be
+          against those in the <a href="https://genenetwork.org"
+                                  title="Link to the GeneNetwork service">
+            GeneNetwork</a> database.<br />
+          <small class="text-muted">
+            If you encounter an error saying your sample(s)/case(s) do not exist
+            in the GeneNetwork database, then you will have to use the
+            <a href="{{url_for('samples.select_species')}}"
+               title="Upload samples/cases feature">Upload Samples/Cases</a>
+            option on this system to upload them.
+          </small>
+      </ul>
+    </li>
+
+    <li>Data
+      <ol>
+	<li><strong>NONE</strong> of the data cells/fields is allowed to be empty.
+	  All fields/cells <strong>MUST</strong> contain a value.</li>
+	<li>The first column of the data rows will be considered a textual field,
+	  holding the "identifier" for that row<li>
+	<li>Except for the first column/field for each data row,
+	  <strong>NONE</strong> of the data columns/cells/fields should contain
+	  spurious characters like `eeeee`, `5.555iloveguix`, etc...<br />
+	  All of them should be decimal values</li>
+	<li>decimal numbers must conform to the following criteria:
+	  <ul>
+	    <li>when checking an average file decimal numbers must have exactly three
+	      decimal places to the right of the decimal point.</li>
+	    <li>when checking a standard error file decimal numbers must have six or
+	      greater decimal places to the right of the decimal point.</li>
+	    <li>there must be a number to the left side of the decimal place
+	      (e.g. 0.55555 is allowed but .55555 is not).</li>
+	  </ul>
+	</li>
+      </ol>
+    </li>
+  </ol>
+</div>
+
+
+<div class="row">
+  <h2 id="file-types">Supported File Types</h2>
+  We support the following file types:
+
+  <ul>
+    <li>Tab-Separated value files (.tsv)
+      <ul>
+	<li>The <strong>TAB</strong> character is used to separate the fields of each
+	  column</li>
+	<li>The values of each field <strong>ARE NOT</strong> quoted.</li>
+	<li>Here is an
+	  <a href="https://gitlab.com/fredmanglis/gnqc_py/-/blob/main/tests/test_data/no_data_errors.tsv">
+	    example file</a> with a single data row.</li>
+      </ul>
+    </li>
+    <li>.txt files: Content has the same format as .tsv file above</li>
+    <li>.zip files: each zip file should contain
+      <strong>ONE AND ONLY ONE</strong> file of the .tsv or .txt type above.
+      <br />Any zip file with more than one file is invalid, and so is an empty
+      zip file.</li>
+  </ul>
+
+</div>
+{%endblock%}
diff --git a/uploader/templates/dbupdate_error.html b/uploader/templates/dbupdate_error.html
new file mode 100644
index 0000000..e1359d2
--- /dev/null
+++ b/uploader/templates/dbupdate_error.html
@@ -0,0 +1,12 @@
+{%extends "base.html"%}
+
+{%block title%}DB Update Error{%endblock%}
+
+{%block contents%}
+<h1 class="heading">database update error</h2>
+
+<p class="alert-danger">
+  <strong>Database Update Error</strong>: {{error_message}}
+</p>
+
+{%endblock%}
diff --git a/uploader/templates/dbupdate_hidden_fields.html b/uploader/templates/dbupdate_hidden_fields.html
new file mode 100644
index 0000000..ccbc299
--- /dev/null
+++ b/uploader/templates/dbupdate_hidden_fields.html
@@ -0,0 +1,29 @@
+{%macro hidden_fields(filename, filetype):%}
+
+<!-- {{kwargs}}: mostly for accessing the kwargs in macro -->
+
+<input type="hidden" name="filename" value="{{filename}}" />
+<input type="hidden" name="filetype" value="{{filetype}}" />
+{%if kwargs.get("totallines")%}
+<input type="hidden" name="totallines" value="{{kwargs['totallines']}}" />
+{%endif%}
+{%if kwargs.get("species"):%}
+<input type="hidden" name="species" value="{{kwargs['species']}}" />
+{%endif%}
+{%if kwargs.get("genechipid"):%}
+<input type="hidden" name="genechipid" value="{{kwargs['genechipid']}}" />
+{%endif%}
+{%if kwargs.get("inbredsetid"):%}
+<input type="hidden" name="inbredsetid" value="{{kwargs['inbredsetid']}}" />
+{%endif%}
+{%if kwargs.get("tissueid"):%}
+<input type="hidden" name="tissueid" value="{{kwargs['tissueid']}}" />
+{%endif%}
+{%if kwargs.get("studyid"):%}
+<input type="hidden" name="studyid" value="{{kwargs['studyid']}}" />
+{%endif%}
+{%if kwargs.get("datasetid"):%}
+<input type="hidden" name="datasetid" value="{{kwargs['datasetid']}}" />
+{%endif%}
+
+{%endmacro%}
diff --git a/uploader/templates/errors_display.html b/uploader/templates/errors_display.html
new file mode 100644
index 0000000..715cfcf
--- /dev/null
+++ b/uploader/templates/errors_display.html
@@ -0,0 +1,47 @@
+{%macro errors_display(errors, no_error_msg, error_message, complete)%}
+
+{%if errors | length == 0 %}
+<span {%if complete%}class="alert-success"{%endif%}>{{no_error_msg}}</span>
+{%else %}
+<p class="alert-danger">{{error_message}}</p>
+
+<table class="table reports-table">
+  <thead>
+    <tr>
+      <th>line number</th>
+      <th>column(s)</th>
+      <th>error</th>
+      <th>error message</th>
+    </tr>
+  </thead>
+
+  <tbody>
+    {%for error in errors%}
+    <tr>
+      <td>{{error["line"]}}</td>
+      <td>
+	{%if isinvalidvalue(error):%}
+	{{error.column}}
+	{%elif isduplicateheading(error): %}
+	{{error.columns}}
+	{%else: %}
+	-
+	{%endif %}
+      </td>
+      <td>
+	{%if isinvalidvalue(error):%}
+	Invalid Value
+	{%elif isduplicateheading(error): %}
+	Duplicate Header
+	{%else%}
+	Inconsistent Columns
+	{%endif %}
+      </td>
+      <td>{{error["message"]}}</td>
+    </tr>
+    {%endfor%}
+  </tbody>
+</table>
+{%endif%}
+
+{%endmacro%}
diff --git a/uploader/templates/final_confirmation.html b/uploader/templates/final_confirmation.html
new file mode 100644
index 0000000..0727fc8
--- /dev/null
+++ b/uploader/templates/final_confirmation.html
@@ -0,0 +1,47 @@
+{%extends "base.html"%}
+{%from "dbupdate_hidden_fields.html" import hidden_fields%}
+
+{%block title%}Confirmation{%endblock%}
+
+{%macro display_item(item_name, item_data):%}
+<li>
+  <strong>{{item_name}}</strong>
+  {%if item_data%}
+  <ul>
+    {%for term,value in item_data.items():%}
+    <li><strong>{{term}}:</strong> {{value}}</li>
+    {%endfor%}
+  </ul>
+  {%endif%}
+</li>
+{%endmacro%}
+
+{%block contents%}
+<h2 class="heading">Final Confirmation</h2>
+
+<div  class="two-col-sep-col1">
+  <p><strong>Selected Data</strong></p>
+  <ul>
+    <li><strong>File</strong>
+      <ul>
+	<li><strong>Filename</strong>: {{filename}}</li>
+	<li><strong>File Type</strong>: {{filetype}}</li>
+      </ul>
+    </li>
+    {{display_item("Species", the_species)}}
+    {{display_item("Platform", platform)}}
+    {{display_item("Study", study)}}
+    {{display_item("Dataset", dataset)}}
+  </ul>
+</div>
+
+<form method="POST" action="{{url_for('dbinsert.insert_data')}}">
+  {{hidden_fields(
+  filename, filetype, species=species, genechipid=genechipid,
+  studyid=studyid,datasetid=datasetid, totallines=totallines)}}
+  <fieldset>
+    <input type="submit" class="btn btn-primary" value="confirm" />
+  </fieldset>
+</form>
+</div>
+{%endblock%}
diff --git a/uploader/templates/flash_messages.html b/uploader/templates/flash_messages.html
new file mode 100644
index 0000000..b7af178
--- /dev/null
+++ b/uploader/templates/flash_messages.html
@@ -0,0 +1,25 @@
+{%macro flash_all_messages()%}
+{%with messages = get_flashed_messages(with_categories=true)%}
+{%if messages:%}
+<ul>
+  {%for category, message in messages:%}
+  <li class="{{category}}">{{message}}</li>
+  {%endfor%}
+</ul>
+{%endif%}
+{%endwith%}
+{%endmacro%}
+
+{%macro flash_messages(filter_class)%}
+{%with messages = get_flashed_messages(with_categories=true)%}
+{%if messages:%}
+<ul>
+  {%for category, message in messages:%}
+  {%if filter_class in category%}
+  <li class="{{category}}">{{message}}</li>
+  {%endif%}
+  {%endfor%}
+</ul>
+{%endif%}
+{%endwith%}
+{%endmacro%}
diff --git a/uploader/templates/http-error.html b/uploader/templates/http-error.html
new file mode 100644
index 0000000..374fb86
--- /dev/null
+++ b/uploader/templates/http-error.html
@@ -0,0 +1,18 @@
+{%extends "base.html"%}
+
+{%block title%}HTTP Error: {{exc.code}}{%endblock%}
+
+{%block contents%}
+<h1>{{exc.code}}: {{exc.description}}</h1>
+
+<div class="row">
+  <p>
+    You attempted to access {{request_url}} which failed with the following
+    error:
+  </p>
+</div>
+
+<div class="row">
+  <pre>{{"\n".join(trace)}}</pre>
+</div>
+{%endblock%}
diff --git a/uploader/templates/index.html b/uploader/templates/index.html
new file mode 100644
index 0000000..89d2ae9
--- /dev/null
+++ b/uploader/templates/index.html
@@ -0,0 +1,81 @@
+{%extends "base.html"%}
+
+{%block title%}Data Upload{%endblock%}
+
+{%block contents%}
+<div class="row">
+  <h1 class="heading">data upload</h1>
+
+  <div class="explainer">
+    <p>Each of the sections below gives you a different option for data upload.
+      Please read the documentation for each section carefully to understand what
+      each section is about.</p>
+  </div>
+</div>
+
+<div class="row">
+  <h2 class="heading">R/qtl2 Bundles</h2>
+
+  <div class="explainer">
+    <p>This feature combines and extends the two upload methods below. Instead of
+      uploading one item at a time, the R/qtl2 bundle you upload can contain both
+      the genotypes data (samples/individuals/cases and their data) and the
+      expression data.</p>
+    <p>The R/qtl2 bundle, additionally, can contain extra metadata, that neither
+      of the methods below can handle.</p>
+
+    <a href="{{url_for('upload.rqtl2.select_species')}}"
+       title="Upload a zip bundle of R/qtl2 files">
+      <button class="btn btn-primary">upload R/qtl2 bundle</button></a>
+  </div>
+</div>
+
+
+<div class="row">
+  <h2 class="heading">Expression Data</h2>
+
+  <div class="explainer">
+    <p>This feature enables you to upload expression data. It expects the data to
+      be in <strong>tab-separated values (TSV)</strong> files. The data should be
+      a simple matrix of <em>phenotype × sample</em>, i.e. The first column is a
+      list of the <em>phenotypes</em> and the first row is a list of
+      <em>samples/cases</em>.</p>
+
+    <p>If you haven't done so please go to this page to learn the requirements for
+      file formats and helpful suggestions to enter your data in a fast and easy
+      way.</p>
+
+    <ol>
+      <li><strong>PLEASE REVIEW YOUR DATA.</strong>Make sure your data complies
+        with our system requirements. (
+        <a href="{{url_for('entry.data_review')}}#data-concerns"
+	   title="Details for the data expectations.">Help</a>
+        )</li>
+      <li><strong>UPLOAD YOUR DATA FOR DATA VERIFICATION.</strong> We accept
+        <strong>.csv</strong>, <strong>.txt</strong> and <strong>.zip</strong>
+        files (<a href="{{url_for('entry.data_review')}}#file-types"
+	          title="Details for the data expectations.">Help</a>)</li>
+    </ol>
+  </div>
+
+  <a href="{{url_for('entry.upload_file')}}"
+     title="Upload your expression data"
+     class="btn btn-primary">upload expression data</a>
+</div>
+
+<div class="row">
+  <h2 class="heading">samples/cases</h2>
+
+  <div class="explainer">
+    <p>For the expression data above, you need the samples/cases in your file to
+      already exist in the GeneNetwork database. If there are any samples that do
+      not already exist the upload of the expression data will fail.</p>
+    <p>This section gives you the opportunity to upload any missing samples</p>
+  </div>
+
+  <a href="{{url_for('samples.select_species')}}"
+     title="Upload samples/cases/individuals for your data"
+     class="btn btn-primary">upload Samples/Cases</a>
+</div>
+
+{%endblock%}
diff --git a/uploader/templates/insert_error.html b/uploader/templates/insert_error.html
new file mode 100644
index 0000000..5301288
--- /dev/null
+++ b/uploader/templates/insert_error.html
@@ -0,0 +1,32 @@
+{%extends "base.html"%}
+
+{%block title%}Data Insertion Failure{%endblock%}
+
+{%block contents%}
+<h1 class="heading">Insertion Failure</h1>
+
+<div class="row">
+  <p>
+    There was an error inserting data into the database
+  </p>
+
+  <p>
+    Please notify the developers of this issue when you encounter it,
+    providing the information below.
+  </p>
+
+  <h4>Debugging Information</h4>
+
+  <ul>
+    <li><strong>job id</strong>: {{job["jobid"]}}</li>
+  </ul>
+</div>
+
+<div class="row">
+  <h4>STDERR Output</h4>
+  <pre class="cli-output">
+    {{job["stderr"]}}
+  </pre>
+</div>
+
+{%endblock%}
diff --git a/uploader/templates/insert_progress.html b/uploader/templates/insert_progress.html
new file mode 100644
index 0000000..52177d6
--- /dev/null
+++ b/uploader/templates/insert_progress.html
@@ -0,0 +1,46 @@
+{%extends "base.html"%}
+{%from "stdout_output.html" import stdout_output%}
+
+{%block extrameta%}
+<meta http-equiv="refresh" content="5">
+{%endblock%}
+
+{%block title%}Job Status{%endblock%}
+
+{%block contents%}
+<h1 class="heading">{{job_name}}</h1>
+
+<div class="row">
+  <form>
+    <div class="form-group">
+      <label for="job_status" class="form-label">status:</label>
+      <span class="form-text">{{job_status}}: {{message}}</span>
+    </div>
+
+{%if job.get("stdout", "").split("\n\n") | length < 3 %}
+{%set lines = 0%}
+{%else%}
+{%set lines = (job.get("stdout", "").split("\n\n") | length / 3) %}
+{%endif%}
+{%set totallines = job.get("totallines", lines+3) | int %}
+{%if totallines > 1000 %}
+{%set fraction = ((lines*1000)/totallines) %}
+{%else%}
+{%set fraction = (lines/totallines)%}
+{%endif%}
+
+    <div class="form-group">
+      <label for="job_{{job_id}}" class="form-label">inserting: </label>
+      <progress id="jobs_{{job_id}}"
+                value="{{(fraction)}}"
+                class="form-control">{{fraction*100}}</progress>
+      <span class="form-text text-muted">
+        {{"%.2f" | format(fraction * 100 | float)}}%</span>
+    </div>
+  </form>
+</div>
+
+
+{{stdout_output(job)}}
+
+{%endblock%}
diff --git a/uploader/templates/insert_success.html b/uploader/templates/insert_success.html
new file mode 100644
index 0000000..7e1fa8d
--- /dev/null
+++ b/uploader/templates/insert_success.html
@@ -0,0 +1,19 @@
+{%extends "base.html"%}
+{%from "stdout_output.html" import stdout_output%}
+
+{%block title%}Insertion Success{%endblock%}
+
+{%block contents%}
+<h1 class="heading">Insertion Success</h1>
+
+<div class="row">
+<p>Data inserted successfully!</p>
+
+<p>The following queries were run:</p>
+</div>
+
+<div class="row">
+  {{stdout_output(job)}}
+</div>
+
+{%endblock%}
diff --git a/uploader/templates/job_progress.html b/uploader/templates/job_progress.html
new file mode 100644
index 0000000..1af0763
--- /dev/null
+++ b/uploader/templates/job_progress.html
@@ -0,0 +1,40 @@
+{%extends "base.html"%}
+{%from "errors_display.html" import errors_display%}
+
+{%block extrameta%}
+<meta http-equiv="refresh" content="5">
+{%endblock%}
+
+{%block title%}Job Status{%endblock%}
+
+{%block contents%}
+<h1 class="heading">{{job_name}}</h2>
+
+<div class="row">
+  <form action="{{url_for('parse.abort')}}" method="POST">
+    <legend class="heading">Status</legend>
+    <div class="form-group">
+      <label for="job_status" class="form-label">status:</label>
+      <span class="form-text">{{job_status}}: {{message}}</span><br />
+    </div>
+
+    <div class="form-group">
+      <label for="job_{{job_id}}" class="form-label">parsing: </label>
+      <progress id="job_{{job_id}}"
+                value="{{progress/100}}"
+                class="form-control">
+        {{progress}}</progress>
+      <span class="form-text text-muted">{{"%.2f" | format(progress)}}%</span>
+    </div>
+
+    <input type="hidden" name="job_id" value="{{job_id}}" />
+
+    <button type="submit" class="btn btn-danger">Abort</button>
+  </form>
+</div>
+
+<div class="row">
+  {{errors_display(errors, "No errors found so far", "We have found the following errors so far", False)}}
+</div>
+
+{%endblock%}
diff --git a/uploader/templates/no_such_job.html b/uploader/templates/no_such_job.html
new file mode 100644
index 0000000..42a2d48
--- /dev/null
+++ b/uploader/templates/no_such_job.html
@@ -0,0 +1,14 @@
+{%extends "base.html"%}
+
+{%block extrameta%}
+<meta http-equiv="refresh" content="5;url={{url_for('entry.upload_file')}}">
+{%endblock%}
+
+{%block title%}No Such Job{%endblock%}
+
+{%block contents%}
+<h1 class="heading">No Such Job: {{job_id}}</h2>
+
+<p>No job, with the id '<em>{{job_id}}</em>' was found!</p>
+
+{%endblock%}
diff --git a/uploader/templates/parse_failure.html b/uploader/templates/parse_failure.html
new file mode 100644
index 0000000..31f6be8
--- /dev/null
+++ b/uploader/templates/parse_failure.html
@@ -0,0 +1,26 @@
+{%extends "base.html"%}
+
+{%block title%}Worker Failure{%endblock%}
+
+{%block contents%}
+<h1 class="heading">Worker Failure</h1>
+
+<p>
+  There was an error while parsing your file.
+</p>
+
+<p>
+  Please notify the developers of this issue when you encounter it,
+  providing the information below.
+</p>
+
+<h4>Debugging Information</h4>
+
+<ul>
+  <li><strong>job id</strong>: {{job["job_id"]}}</li>
+  <li><strong>filename</strong>: {{job["filename"]}}</li>
+  <li><strong>line number</strong>: {{job["line_number"]}}</li>
+  <li><strong>Progress</strong>: {{job["percent"]}} %</li>
+</ul>
+
+{%endblock%}
diff --git a/uploader/templates/parse_results.html b/uploader/templates/parse_results.html
new file mode 100644
index 0000000..e2bf7f0
--- /dev/null
+++ b/uploader/templates/parse_results.html
@@ -0,0 +1,30 @@
+{%extends "base.html"%}
+{%from "errors_display.html" import errors_display%}
+
+{%block title%}Parse Results{%endblock%}
+
+{%block contents%}
+<h1 class="heading">{{job_name}}: parse results</h2>
+
+{%if user_aborted%}
+<span class="alert-warning">Job aborted by the user</span>
+{%endif%}
+
+{{errors_display(errors, "No errors found in the file", "We found the following errors", True)}}
+
+{%if errors | length == 0 and not user_aborted %}
+<form method="post" action="{{url_for('dbinsert.select_platform')}}">
+  <input type="hidden" name="job_id" value="{{job_id}}" />
+  <input type="submit" value="update database" class="btn btn-primary" />
+</form>
+{%endif%}
+
+{%if errors | length > 0 or user_aborted %}
+<br />
+<a href="{{url_for('entry.upload_file')}}" title="Back to index page."
+   class="btn btn-primary">
+  Go back
+</a>
+{%endif%}
+
+{%endblock%}
diff --git a/uploader/templates/rqtl2/create-geno-dataset-success.html b/uploader/templates/rqtl2/create-geno-dataset-success.html
new file mode 100644
index 0000000..1b50221
--- /dev/null
+++ b/uploader/templates/rqtl2/create-geno-dataset-success.html
@@ -0,0 +1,55 @@
+{%extends "base.html"%}
+{%from "flash_messages.html" import flash_messages%}
+
+{%block title%}Upload R/qtl2 Bundle{%endblock%}
+
+{%block contents%}
+<h2 class="heading">Select Genotypes Dataset</h2>
+
+<div class="explainer">
+  <p>You successfully created the genotype dataset with the following
+    information.
+    <dl>
+      <dt>ID</dt>
+      <dd>{{geno_dataset.id}}</dd>
+
+      <dt>Name</dt>
+      <dd>{{geno_dataset.name}}</dd>
+
+      <dt>Full Name</dt>
+      <dd>{{geno_dataset.fname}}</dd>
+
+      <dt>Short Name</dt>
+      <dd>{{geno_dataset.sname}}</dd>
+
+      <dt>Created On</dt>
+      <dd>{{geno_dataset.today}}</dd>
+
+      <dt>Public?</dt>
+      <dd>{%if geno_dataset.public == 0%}No{%else%}Yes{%endif%}</dd>
+    </dl>
+  </p>
+</div>
+
+<div class="row">
+  <form id="frm-upload-rqtl2-bundle"
+        action="{{url_for('upload.rqtl2.select_dataset_info',
+	        species_id=species.SpeciesId,
+	        population_id=population.InbredSetId)}}"
+        method="POST"
+        enctype="multipart/form-data">
+    <legend class="heading">select from existing genotype datasets</legend>
+
+    <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+    <input type="hidden" name="population_id"
+	   value="{{population.InbredSetId}}" />
+    <input type="hidden" name="rqtl2_bundle_file"
+	   value="{{rqtl2_bundle_file}}" />
+    <input type="hidden" name="geno-dataset-id"
+	   value="{{geno_dataset.id}}" />
+
+    <button type="submit" class="btn btn-primary">continue</button>
+  </form>
+</div>
+
+{%endblock%}
diff --git a/uploader/templates/rqtl2/create-probe-dataset-success.html b/uploader/templates/rqtl2/create-probe-dataset-success.html
new file mode 100644
index 0000000..790d174
--- /dev/null
+++ b/uploader/templates/rqtl2/create-probe-dataset-success.html
@@ -0,0 +1,59 @@
+{%extends "base.html"%}
+{%from "flash_messages.html" import flash_messages%}
+
+{%block title%}Upload R/qtl2 Bundle{%endblock%}
+
+{%block contents%}
+<h2 class="heading">Create ProbeSet Dataset</h2>
+
+<div class="row">
+  <p>You successfully created the ProbeSet dataset with the following
+    information.
+    <dl>
+      <dt>Averaging Method</dt>
+      <dd>{{avgmethod.Name}}</dd>
+
+      <dt>ID</dt>
+      <dd>{{dataset.datasetid}}</dd>
+
+      <dt>Name</dt>
+      <dd>{{dataset.name2}}</dd>
+
+      <dt>Full Name</dt>
+      <dd>{{dataset.fname}}</dd>
+
+      <dt>Short Name</dt>
+      <dd>{{dataset.sname}}</dd>
+
+      <dt>Created On</dt>
+      <dd>{{dataset.today}}</dd>
+
+      <dt>DataScale</dt>
+      <dd>{{dataset.datascale}}</dd>
+    </dl>
+  </p>
+</div>
+
+<div class="row">
+  <form id="frm-upload-rqtl2-bundle"
+        action="{{url_for('upload.rqtl2.select_dataset_info',
+	        species_id=species.SpeciesId,
+	        population_id=population.InbredSetId)}}"
+        method="POST"
+        enctype="multipart/form-data">
+    <legend class="heading">Create ProbeSet dataset</legend>
+
+    <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+    <input type="hidden" name="population_id"
+	   value="{{population.InbredSetId}}" />
+    <input type="hidden" name="rqtl2_bundle_file" value="{{rqtl2_bundle_file}}" />
+    <input type="hidden" name="geno-dataset-id" value="{{geno_dataset.Id}}" />
+    <input type="hidden" name="tissueid" value="{{tissue.Id}}" />
+    <input type="hidden" name="probe-study-id" value="{{study.Id}}" />
+    <input type="hidden" name="probe-dataset-id" value="{{dataset.datasetid}}" />
+
+    <button type="submit" class="btn btn-primary">continue</button>
+  </form>
+</div>
+
+{%endblock%}
diff --git a/uploader/templates/rqtl2/create-probe-study-success.html b/uploader/templates/rqtl2/create-probe-study-success.html
new file mode 100644
index 0000000..d0ee508
--- /dev/null
+++ b/uploader/templates/rqtl2/create-probe-study-success.html
@@ -0,0 +1,49 @@
+{%extends "base.html"%}
+{%from "flash_messages.html" import flash_messages%}
+
+{%block title%}Upload R/qtl2 Bundle{%endblock%}
+
+{%block contents%}
+<h2 class="heading">Create ProbeSet Study</h2>
+
+<div class="row">
+  <p>You successfully created the ProbeSet study with the following
+    information.
+    <dl>
+      <dt>ID</dt>
+      <dd>{{study.id}}</dd>
+
+      <dt>Name</dt>
+      <dd>{{study.name}}</dd>
+
+      <dt>Full Name</dt>
+      <dd>{{study.fname}}</dd>
+
+      <dt>Short Name</dt>
+      <dd>{{study.sname}}</dd>
+
+      <dt>Created On</dt>
+      <dd>{{study.today}}</dd>
+    </dl>
+  </p>
+
+  <form id="frm-upload-rqtl2-bundle"
+        action="{{url_for('upload.rqtl2.select_dataset_info',
+	        species_id=species.SpeciesId,
+	        population_id=population.InbredSetId)}}"
+        method="POST"
+        enctype="multipart/form-data">
+    <legend class="heading">Create ProbeSet study</legend>
+
+    <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+    <input type="hidden" name="population_id"
+	   value="{{population.InbredSetId}}" />
+    <input type="hidden" name="rqtl2_bundle_file" value="{{rqtl2_bundle_file}}" />
+    <input type="hidden" name="geno-dataset-id" value="{{geno_dataset.Id}}" />
+    <input type="hidden" name="probe-study-id" value="{{study.studyid}}" />
+
+    <button type="submit" class="btn btn-primary">continue</button>
+  </form>
+</div>
+
+{%endblock%}
diff --git a/uploader/templates/rqtl2/create-tissue-success.html b/uploader/templates/rqtl2/create-tissue-success.html
new file mode 100644
index 0000000..5f2c5a0
--- /dev/null
+++ b/uploader/templates/rqtl2/create-tissue-success.html
@@ -0,0 +1,106 @@
+{%extends "base.html"%}
+{%from "flash_messages.html" import flash_all_messages%}
+
+{%block title%}Upload R/qtl2 Bundle{%endblock%}
+
+{%block contents%}
+<h2 class="heading">Select Tissue</h2>
+
+<div class="row">
+  <p>You have successfully added a new tissue, organ or biological material with
+    the following details:</p>
+</div>
+
+<div class="row">
+  {{flash_all_messages()}}
+
+  <form id="frm-create-tissue-display"
+        method="POST"
+        action="#">
+    <legend class="heading">Create Tissue</legend>
+
+    <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+    <input type="hidden" name="population_id"
+	   value="{{population.InbredSetId}}" />
+    <input type="hidden" name="rqtl2_bundle_file" value="{{rqtl2_bundle_file}}" />
+    <input type="hidden" name="geno-dataset-id" value="{{geno_dataset.Id}}" />
+    <input type="hidden" name="tissueid" value="{{tissue.Id}}" />
+
+    <div class="form-group">
+      <label>Name</label>
+      <label>{{tissue.TissueName}}</label>
+    </div>
+
+    <div class="form-group">
+      <label>Short Name</label>
+      <label>{{tissue.Short_Name}}</label>
+    </div>
+
+    {%if tissue.BIRN_lex_ID%}
+    <div class="form-group">
+      <label>BIRN Lex ID</label>
+      <label>{{tissue.BIRN_lex_ID}}</label>
+    </div>
+    {%endif%}
+
+    {%if tissue.BIRN_lex_Name%}
+    <div class="form-group">
+      <label>BIRN Lex Name</label>
+      <label>{{tissue.BIRN_lex_Name}}</label>
+    </div>
+    {%endif%}
+  </form>
+
+  <div id="action-buttons"
+       style="width:65ch;display:inline-grid;column-gap:5px;">
+
+    <form id="frm-create-tissue-success-continue"
+          method="POST"
+          action="{{url_for('upload.rqtl2.select_dataset_info',
+	          species_id=species.SpeciesId,
+	          population_id=population.InbredSetId)}}"
+          style="display: inline; width: 100%; grid-column: 1 / 2;
+                 padding-top: 0.5em; text-align: center; border: none;
+                 background-color: inherit;">
+
+      <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+      <input type="hidden" name="population_id"
+	     value="{{population.InbredSetId}}" />
+      <input type="hidden" name="rqtl2_bundle_file" value="{{rqtl2_bundle_file}}" />
+      <input type="hidden" name="geno-dataset-id" value="{{geno_dataset.Id}}" />
+      <input type="hidden" name="tissueid" value="{{tissue.Id}}" />
+
+      <button type="submit" class="btn btn-primary">continue</button>
+    </form>
+  </div>
+</div>
+
+<div class="row">
+  <p style="display:inline;width:100%;grid-column:2/3;text-align:center;
+            color:#336699;font-weight:bold;">
+    OR
+  </p>
+</div>
+
+<div class="row">
+  <form id="frm-create-tissue-success-select-existing"
+        method="POST"
+        action="{{url_for('upload.rqtl2.select_tissue',
+	        species_id=species.SpeciesId,
+	        population_id=population.InbredSetId)}}"
+        style="display: inline; width: 100%; grid-column: 3 / 4;
+               padding-top: 0.5em; text-align: center; border: none;
+               background-color: inherit;">
+
+    <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+    <input type="hidden" name="population_id"
+	   value="{{population.InbredSetId}}" />
+    <input type="hidden" name="rqtl2_bundle_file" value="{{rqtl2_bundle_file}}" />
+    <input type="hidden" name="geno-dataset-id" value="{{geno_dataset.Id}}" />
+
+    <button type="submit" class="btn btn-primary">
+      select from existing tissues</button>
+  </form>
+</div>
+
+{%endblock%}
diff --git a/uploader/templates/rqtl2/index.html b/uploader/templates/rqtl2/index.html
new file mode 100644
index 0000000..f3329c2
--- /dev/null
+++ b/uploader/templates/rqtl2/index.html
@@ -0,0 +1,36 @@
+{%extends "base.html"%}
+{%from "flash_messages.html" import flash_messages%}
+
+{%block title%}Data Upload{%endblock%}
+
+{%block contents%}
+<h1 class="heading">R/qtl2 data upload</h1>
+
+<h2>R/qtl2 Upload</h2>
+
+<form method="POST" action="{{url_for('upload.rqtl2.select_species')}}"
+      id="frm-rqtl2-upload">
+  <legend class="heading">upload R/qtl2 bundle</legend>
+  {{flash_messages("error-rqtl2")}}
+
+  <div class="form-group">
+    <label for="select:species" class="form-label">Species</label>
+    <select id="select:species"
+            name="species_id"
+            required="required"
+            class="form-control">
+      <option value="">Select species</option>
+      {%for spec in species%}
+      <option value="{{spec.SpeciesId}}">{{spec.MenuName}}</option>
+      {%endfor%}
+    </select>
+    <small class="form-text text-muted">
+      Data that you upload to the system should belong to a know species.
+      Here you can select the species that you wish to upload data for.
+    </small>
+  </div>
+
+  <button type="submit" class="btn btn-primary" />submit</button>
+</form>
+
+{%endblock%}
diff --git a/uploader/templates/rqtl2/no-such-job.html b/uploader/templates/rqtl2/no-such-job.html
new file mode 100644
index 0000000..b17004f
--- /dev/null
+++ b/uploader/templates/rqtl2/no-such-job.html
@@ -0,0 +1,13 @@
+{%extends "base.html"%}
+{%from "flash_messages.html" import flash_messages%}
+
+{%block title%}Job Status{%endblock%}
+
+{%block contents%}
+<h1 class="heading">R/qtl2 job status</h1>
+
+<h2>R/qtl2 Upload: No Such Job</h2>
+
+<p class="alert-danger">No job with ID {{jobid}} was found.</p>
+
+{%endblock%}
diff --git a/uploader/templates/rqtl2/rqtl2-job-error.html b/uploader/templates/rqtl2/rqtl2-job-error.html
new file mode 100644
index 0000000..9817518
--- /dev/null
+++ b/uploader/templates/rqtl2/rqtl2-job-error.html
@@ -0,0 +1,39 @@
+{%extends "base.html"%}
+{%from "cli-output.html" import cli_output%}
+
+{%block title%}Job Status{%endblock%}
+
+{%block contents%}
+<h1 class="heading">R/qtl2 job status</h1>
+
+<h2>R/qtl2 Upload: Job Status</h2>
+
+<div class="explainer">
+  <p>The processing of the R/qtl2 bundle you uploaded has failed. We have
+    provided some information below to help you figure out what the problem
+    could be.</p>
+  <p>If you find that you cannot figure out what the problem is on your own,
+    please contact the team running the system for assistance, providing the
+    following details:
+    <ul>
+      <li>R/qtl2 bundle you uploaded</li>
+      <li>This URL: <strong>{{request_url()}}</strong></li>
+      <li>(maybe) a screenshot of this page</li>
+    </ul>
+  </p>
+</div>
+
+<h4>stdout</h4>
+{{cli_output(job, "stdout")}}
+
+<h4>stderr</h4>
+{{cli_output(job, "stderr")}}
+
+<h4>Log</h4>
+<div class="cli-output">
+  {%for msg in messages%}
+  {{msg}}<br />
+  {%endfor%}
+</div>
+
+{%endblock%}
diff --git a/uploader/templates/rqtl2/rqtl2-job-results.html b/uploader/templates/rqtl2/rqtl2-job-results.html
new file mode 100644
index 0000000..4ecd415
--- /dev/null
+++ b/uploader/templates/rqtl2/rqtl2-job-results.html
@@ -0,0 +1,24 @@
+{%extends "base.html"%}
+{%from "cli-output.html" import cli_output%}
+
+{%block title%}Job Status{%endblock%}
+
+{%block contents%}
+<h1 class="heading">R/qtl2 job status</h1>
+
+<h2>R/qtl2 Upload: Job Status</h2>
+
+<div class="explainer">
+  <p>The processing of the R/qtl2 bundle you uploaded has completed
+    successfully.</p>
+  <p>You should now be able to use GeneNetwork to run analyses on your data.</p>
+</div>
+
+<h4>Log</h4>
+<div class="cli-output">
+  {%for msg in messages%}
+  {{msg}}<br />
+  {%endfor%}
+</div>
+
+{%endblock%}
diff --git a/uploader/templates/rqtl2/rqtl2-job-status.html b/uploader/templates/rqtl2/rqtl2-job-status.html
new file mode 100644
index 0000000..e896f88
--- /dev/null
+++ b/uploader/templates/rqtl2/rqtl2-job-status.html
@@ -0,0 +1,20 @@
+{%extends "base.html"%}
+{%from "flash_messages.html" import flash_messages%}
+
+{%block title%}Job Status{%endblock%}
+
+{%block extrameta%}
+<meta http-equiv="refresh" content="3">
+{%endblock%}
+
+{%block contents%}
+<h1 class="heading">R/qtl2 job status</h1>
+
+<h2>R/qtl2 Upload: Job Status</h2>
+
+<h4>Log</h4>
+<div class="cli-output">
+  <pre>{{"\n".join(messages)}}</pre>
+</div>
+
+{%endblock%}
diff --git a/uploader/templates/rqtl2/rqtl2-qc-job-error.html b/uploader/templates/rqtl2/rqtl2-qc-job-error.html
new file mode 100644
index 0000000..90e8887
--- /dev/null
+++ b/uploader/templates/rqtl2/rqtl2-qc-job-error.html
@@ -0,0 +1,120 @@
+{%extends "base.html"%}
+{%from "cli-output.html" import cli_output%}
+
+{%block title%}R/qtl2 bundle: QC Job Error{%endblock%}
+
+{%macro errors_table(tableid, errors)%}
+<table id="{{tableid}}" class="table error-table">
+  <caption>{{caption}}</caption>
+  <thead>
+    <tr>
+      <th>Line</th>
+      <th>Column</th>
+      <th>Value</th>
+      <th>Message</th>
+    </tr>
+  </thead>
+  <tbody>
+    {%for error in errors%}
+    <tr>
+      <td>{{error.line}}</td>
+      <td>{{error.column}}</td>
+      <td>{{error.value}}</td>
+      <td>{{error.message}}</td>
+    </tr>
+    {%else%}
+    <tr>
+      <td colspan="4">No errors to display here.</td>
+    </tr>
+    {%endfor%}
+  </tbody>
+</table>
+{%endmacro%}
+
+{%block contents%}
+<h1 class="heading">R/qtl2 bundle: QC job Error</h1>
+
+<div class="explainer">
+  <p>The R/qtl2 bundle has failed some <emph>Quality Control</emph> checks.</p>
+  <p>We list below some of the errors that need to be fixed before the data can
+    be uploaded onto GeneNetwork.</p>
+</div>
+
+{%if errorsgeneric | length > 0%}
+<h2 class="heading">Generic Errors ({{errorsgeneric | length}})</h3>
+<div class="explainer">
+  We found the following generic errors in your R/qtl2 bundle:
+</div>
+
+<h3>Missing Files</h3>
+<div class="explainer">
+  <p>These files are listed in the bundle's control file, but do not actually
+    exist in the bundle</p>
+</div>
+<table id="tbl-errors-missing-files" class="table error-table">
+  <thead>
+    <tr>
+      <th>Control File Key</th>
+      <th>Bundle File Name</th>
+      <th>Message</th>
+    </tr>
+  </thead>
+  <tbody>
+    {%for error in (errorsgeneric | selectattr("type", "equalto", "MissingFile"))%}
+    <tr>
+      <td>{{error.controlfilekey}}</td>
+      <td>{{error.filename}}</td>
+      <td>{{error.message}}</td>
+    </tr>
+    {%endfor%}
+  </tbody>
+</table>
+
+<h3>Other Generic Errors</h3>
+{{errors_table("tbl-errors-generic", errorsgeneric| selectattr("type", "ne", "MissingFile"))}}
+{%endif%}
+
+{%if errorsgeno | length > 0%}
+<h2 class="heading">Geno Errors ({{errorsgeno | length}})</h3>
+<div class="explainer">
+  We found the following errors in the 'geno' file in your R/qtl2 bundle:
+</div>
+{{errors_table("tbl-errors-geno", errorsgeno[0:50])}}
+{%endif%}
+
+{%if errorspheno | length > 0%}
+<h2 class="heading">Pheno Errors ({{errorspheno | length}})</h3>
+<div class="explainer">
+  We found the following errors in the 'pheno' file in your R/qtl2 bundle:
+</div>
+{{errors_table("tbl-errors-pheno", errorspheno[0:50])}}
+{%endif%}
+
+{%if errorsphenose | length > 0%}
+<h2 class="heading">Phenose Errors ({{errorsphenose | length}})</h3>
+<div class="explainer">
+  We found the following errors in the 'phenose' file in your R/qtl2 bundle:
+</div>
+{{errors_table("tbl-errors-phenose", errorsphenose[0:50])}}
+{%endif%}
+
+{%if errorsphenocovar | length > 0%}
+<h2 class="heading">Phenocovar Errors ({{errorsphenocovar | length}})</h3>
+<div class="explainer">
+  We found the following errors in the 'phenocovar' file in your R/qtl2 bundle:
+</div>
+{{errorsphenocovar}}
+{%endif%}
+
+<h4>stdout</h4>
+{{cli_output(job, "stdout")}}
+
+<h4>stderr</h4>
+{{cli_output(job, "stderr")}}
+
+<h4>Log</h4>
+<div class="cli-output">
+  <pre>{{"\n".join(messages)}}</pre>
+</div>
+
+{%endblock%}
diff --git a/uploader/templates/rqtl2/rqtl2-qc-job-results.html b/uploader/templates/rqtl2/rqtl2-qc-job-results.html
new file mode 100644
index 0000000..59bc8cd
--- /dev/null
+++ b/uploader/templates/rqtl2/rqtl2-qc-job-results.html
@@ -0,0 +1,66 @@
+{%extends "base.html"%}
+{%from "cli-output.html" import cli_output%}
+
+{%block title%}R/qtl2 bundle: QC job results{%endblock%}
+
+{%block contents%}
+<h1 class="heading">R/qtl2 bundle: QC job results</h1>
+
+<div class="row">
+  <p>The R/qtl2 bundle you uploaded has passed all automated quality-control
+    checks successfully.</p>
+  <p>You may now continue to load the data into GeneNetwork for the bundle, with
+    the following details:</p>
+</div>
+
+<div class="row">
+  <form id="form-qc-job-results"
+        action="{{url_for('upload.rqtl2.select_dataset_info',
+	        species_id=species.SpeciesId,
+	        population_id=population.Id)}}"
+        method="POST">
+    <div class="form-group">
+      <legend>Species</legend>
+      <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+
+      <span class="form-label">Name</span>
+      <span class="form-text">{{species.Name | capitalize}}</span>
+
+      <span class="form-label">Scientific</span>
+      <span class="form-text">{{species.FullName | capitalize}}</span>
+    </div>
+
+    <div class="form-group">
+      <legend>population</legend>
+      <input type="hidden" name="population_id" value="{{population.Id}}" />
+
+      <span class="form-label">Name</span>
+      <span class="form-text">{{population.InbredSetName}}</span>
+
+      <span class="form-label">Full Name</span>
+      <span class="form-text">{{population.FullName}}</span>
+
+      <span class="form-label">Genetic Type</span>
+      <span class="form-text">{{population.GeneticType}}</span>
+
+      <span class="form-label">Description</span>
+      <span class="form-text">{{population.Description or "-"}}</span>
+    </div>
+
+    <div class="form-group">
+      <legend>R/qtl2 Bundle File</legend>
+      <input type="hidden" name="rqtl2_bundle_file" value="{{rqtl2bundle}}" />
+      <input type="hidden" name="original-filename" value="{{rqtl2bundleorig}}" />
+
+      <span class="form-label">Original Name</span>
+      <span class="form-text">{{rqtl2bundleorig}}</span>
+
+      <span class="form-label">Internal Name</span>
+      <span class="form-text">{{rqtl2bundle[0:25]}}&hellip;</span>
+    </div>
+
+    <button type="submit" class="btn btn-primary">continue</button>
+  </form>
+</div>
+
+{%endblock%}
diff --git a/uploader/templates/rqtl2/rqtl2-qc-job-status.html b/uploader/templates/rqtl2/rqtl2-qc-job-status.html
new file mode 100644
index 0000000..f4a6266
--- /dev/null
+++ b/uploader/templates/rqtl2/rqtl2-qc-job-status.html
@@ -0,0 +1,41 @@
+{%extends "base.html"%}
+{%from "flash_messages.html" import flash_messages%}
+
+{%block title%}Job Status{%endblock%}
+
+{%block extrameta%}
+<meta http-equiv="refresh" content="3">
+{%endblock%}
+
+{%block contents%}
+<h1 class="heading">R/qtl2 bundle: QC job status</h1>
+
+{%if geno_percent%}
+<p>
+  <h2>Checking 'geno' file:</h2>
+  <progress id="prg-geno-checking" value="{{geno_percent}}" max="100">
+    {{geno_percent}}%</progress>
+  {{geno_percent}}%</p>
+{%endif%}
+
+{%if pheno_percent%}
+<p>
+  <h2>Checking 'pheno' file:</h2>
+  <progress id="prg-pheno-checking" value="{{pheno_percent}}" max="100">
+    {{pheno_percent}}%</progress>
+  {{pheno_percent}}%</p>
+{%endif%}
+
+{%if phenose_percent%}
+<p>
+  <h2>Checking 'phenose' file:</h2>
+  <progress id="prg-phenose-checking" value="{{phenose_percent}}" max="100">
+    {{phenose_percent}}%</progress>
+  {{phenose_percent}}%</p>
+{%endif%}
+
+<h4>Log</h4>
+<div class="cli-output">
+  <pre>{{"\n".join(messages)}}</pre>
+</div>
+{%endblock%}
diff --git a/uploader/templates/rqtl2/rqtl2-qc-job-success.html b/uploader/templates/rqtl2/rqtl2-qc-job-success.html
new file mode 100644
index 0000000..2861a04
--- /dev/null
+++ b/uploader/templates/rqtl2/rqtl2-qc-job-success.html
@@ -0,0 +1,37 @@
+{%extends "base.html"%}
+{%from "flash_messages.html" import flash_all_messages%}
+
+{%block title%}R/qtl2 Bundle: Quality Control Successful{%endblock%}
+
+{%block contents%}
+<h2 class="heading">R/qtl2 Bundle: Quality Control Successful</h2>
+
+<div class="row">
+  <p>The R/qtl2 bundle you uploaded has passed <emph>all</emph> quality control
+    checks successfully, and is now ready for uploading into the database.</p>
+  <p>Click "Continue" below to proceed.</p>
+</div>
+
+<!--
+    The "action" on this form takes us to the next step, where we can
+    select all the other data necessary to enter the data into the database.
+  -->
+<div class="row">
+  <form id="frm-upload-rqtl2-bundle"
+        action="{{url_for('upload.rqtl2.select_dataset_info',
+	        species_id=species.SpeciesId,
+	        population_id=population.InbredSetId)}}"
+        method="POST"
+        enctype="multipart/form-data">
+    {{flash_all_messages()}}
+    <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+    <input type="hidden" name="population_id"
+	   value="{{population.InbredSetId}}" />
+    <input type="hidden" name="rqtl2_bundle_file"
+	   value="{{rqtl2_bundle_file}}" />
+
+    <button type="submit" class="btn btn-primary">continue</button>
+  </form>
+</div>
+
+{%endblock%}
diff --git a/uploader/templates/rqtl2/select-geno-dataset.html b/uploader/templates/rqtl2/select-geno-dataset.html
new file mode 100644
index 0000000..873f9c3
--- /dev/null
+++ b/uploader/templates/rqtl2/select-geno-dataset.html
@@ -0,0 +1,144 @@
+{%extends "base.html"%}
+{%from "flash_messages.html" import flash_messages%}
+
+{%block title%}Upload R/qtl2 Bundle{%endblock%}
+
+{%block contents%}
+<h2 class="heading">Select Genotypes Dataset</h2>
+
+<div class="row">
+  <p>Your R/qtl2 files bundle contains a "geno" specification. You will
+    therefore need to select from one of the existing Genotype datasets or
+    create a new one.</p>
+  <p>This is the dataset where your data will be organised under.</p>
+</div>
+
+<div class="row">
+  <form id="frm-upload-rqtl2-bundle"
+        action="{{url_for('upload.rqtl2.select_geno_dataset',
+	        species_id=species.SpeciesId,
+	        population_id=population.InbredSetId)}}"
+        method="POST"
+        enctype="multipart/form-data">
+    <legend class="heading">select from existing genotype datasets</legend>
+
+    <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+    <input type="hidden" name="population_id"
+	   value="{{population.InbredSetId}}" />
+    <input type="hidden" name="rqtl2_bundle_file"
+	   value="{{rqtl2_bundle_file}}" />
+
+    {{flash_messages("error-rqtl2-select-geno-dataset")}}
+
+    <div class="form-group">
+      <legend>Datasets</legend>
+      <label for="select:geno-datasets" class="form-label">Dataset</label>
+      <select id="select:geno-datasets"
+	      name="geno-dataset-id"
+	      required="required"
+	      {%if datasets | length == 0%}
+	      disabled="disabled"
+	      {%endif%}
+              class="form-control"
+              aria-describedby="help-geno-dataset-select-dataset">
+        <option value="">Select dataset</option>
+        {%for dset in datasets%}
+        <option value="{{dset['Id']}}">{{dset["Name"]}} ({{dset["FullName"]}})</option>
+        {%endfor%}
+      </select>
+      <span id="help-geno-dataset-select-dataset" class="form-text text-muted">
+        Select from the existing genotype datasets for species
+        {{species.SpeciesName}} ({{species.FullName}}).
+      </span>
+    </div>
+
+    <button type="submit" class="btn btn-primary">select dataset</button>
+  </form>
+</div>
+
+<div class="row">
+  <p style="color:#FE3535; padding-left:20em; font-weight:bolder;">OR</p>
+</div>
+
+<div class="row">
+  <form id="frm-upload-rqtl2-bundle"
+        action="{{url_for('upload.rqtl2.create_geno_dataset',
+	        species_id=species.SpeciesId,
+	        population_id=population.InbredSetId)}}"
+        method="POST"
+        enctype="multipart/form-data">
+    <legend class="heading">create a new genotype dataset</legend>
+
+    <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+    <input type="hidden" name="population_id"
+	   value="{{population.InbredSetId}}" />
+    <input type="hidden" name="rqtl2_bundle_file"
+	   value="{{rqtl2_bundle_file}}" />
+
+    {{flash_messages("error-rqtl2-create-geno-dataset")}}
+
+    <div class="form-group">
+      <label for="txt:dataset-name" class="form-label">Name</label>
+      <input type="text"
+	     id="txt:dataset-name"
+	     name="dataset-name"
+	     maxlength="100"
+	     required="required"
+             class="form-control"
+             aria-describedby="help-geno-dataset-name" />
+      <span id="help-geno-dataset-name" class="form-text text-muted">
+        Provide the new name for the genotype dataset, e.g. "BXDGeno"
+      </span>
+    </div>
+
+    <div class="form-group">
+      <label for="txt:dataset-fullname" class="form-label">Full Name</label>
+      <input type="text"
+	     id="txt:dataset-fullname"
+	     name="dataset-fullname"
+	     required="required"
+	     maxlength="100"
+             class="form-control"
+             aria-describedby="help-geno-dataset-fullname" />
+
+      <span id="help-geno-dataset-fullname" class="form-text text-muted">
+        Provide a longer name that better describes the genotype dataset, e.g.
+        "BXD Genotypes"
+      </span>
+    </div>
+
+    <div class="form-group">
+      <label for="txt:dataset-shortname" class="form-label">Short Name</label>
+      <input type="text"
+	     id="txt:dataset-shortname"
+	     name="dataset-shortname"
+	     maxlength="100"
+             class="form-control"
+             aria-describedby="help-geno-dataset-shortname" />
+
+      <span id="help-geno-dataset-shortname" class="form-text text-muted">
+        Provide a short name for the genotype dataset. This is optional. If not
+        provided, we'll default to the same value as the "Name" above.
+      </span>
+    </div>
+
+    <div class="form-group">
+      <input type="checkbox"
+	     id="chk:dataset-public"
+	     name="dataset-public"
+	     checked="checked"
+             class="form-check"
+             aria-describedby="help-geno-datasent-public" />
+      <label for="chk:dataset-public" class="form-check-label">Public?</label>
+
+      <span id="help-geno-dataset-public" class="form-text text-muted">
+        Specify whether the dataset will be available publicly. Check to make the
+        dataset publicly available and uncheck to limit who can access the dataset.
+      </span>
+    </div>
+
+    <button type="submit" class="btn btn-primary">create new dataset</button>
+  </form>
+</div>
+
+{%endblock%}
diff --git a/uploader/templates/rqtl2/select-population.html b/uploader/templates/rqtl2/select-population.html
new file mode 100644
index 0000000..37731f0
--- /dev/null
+++ b/uploader/templates/rqtl2/select-population.html
@@ -0,0 +1,136 @@
+{%extends "base.html"%}
+{%from "flash_messages.html" import flash_messages%}
+
+{%block title%}Select Grouping/Population{%endblock%}
+
+{%block contents%}
+<h1 class="heading">Select grouping/population</h1>
+
+<div class="explainer">
+  <p>The data is organised in a hierarchical form, beginning with
+    <em>species</em> at the very top. Under <em>species</em> the data is
+    organised by <em>population</em>, sometimes referred to as <em>grouping</em>.
+    (In some really old documents/systems, you might see this referred to as
+    <em>InbredSet</em>.)</p>
+  <p>In this section, you get to define what population your data is to be
+    organised by.</p>
+</div>
+
+<form method="POST"
+      action="{{url_for('upload.rqtl2.select_population', species_id=species.SpeciesId)}}">
+  <legend class="heading">select grouping/population</legend>
+  {{flash_messages("error-select-population")}}
+
+  <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+
+  <div class="form-group">
+    <label for="select:inbredset" class="form-label">population</label>
+    <select id="select:inbredset"
+	    name="inbredset_id"
+	    required="required"
+	    class="form-control">
+      <option value="">Select a grouping/population</option>
+      {%for pop in populations%}
+      <option value="{{pop.InbredSetId}}">
+	{{pop.InbredSetName}} ({{pop.FullName}})</option>
+      {%endfor%}
+    </select>
+    <span class="form-text text-muted">If you are adding data to an already existing
+      population, simply pick the population from this drop-down selector. If
+      you cannot find your population from this list, try the form below to
+      create a new one..</span>
+  </div>
+
+  <button type="submit" class="btn btn-primary" />select population</button>
+</form>
+
+<p style="color:#FE3535; padding-left:20em; font-weight:bolder;">OR</p>
+
+<form method="POST"
+      action="{{url_for('upload.rqtl2.create_population', species_id=species.SpeciesId)}}">
+  <legend class="heading">create new grouping/population</legend>
+  {{flash_messages("error-create-population")}}
+
+  <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+
+  <div class="form-group">
+    <legend class="heading">mandatory</legend>
+
+    <div class="form-group">
+      <label for="txt:inbredset-name" class="form-label">name</label>
+      <input id="txt:inbredset-name"
+	     name="inbredset_name"
+	     type="text"
+	     required="required"
+	     maxlength="30"
+	     placeholder="Enter grouping/population name"
+	     class="form-control" />
+      <span class="form-text text-muted">This is a short name that identifies the
+        population. Useful for menus, and quick scanning.</span>
+    </div>
+
+    <div class="form-group">
+      <label for="txt:" class="form-label">full name</label>
+      <input id="txt:inbredset-fullname"
+	     name="inbredset_fullname"
+	     type="text"
+	     required="required"
+	     maxlength="100"
+	     placeholder="Enter the grouping/population's full name"
+	     class="form-control" />
+      <span class="form-text text-muted">This can be the same as the name above, or can
+        be longer. Useful for documentation, and human communication.</span>
+    </div>
+  </div>
+
+  <div class="form-group">
+    <legend class="heading">optional</legend>
+
+    <div class="form-group">
+      <label for="num:public" class="form-label">public?</label>
+      <select id="num:public"
+	      name="public"
+	      class="form-control">
+        <option value="0">0 - Only accessible to authorised users</option>
+        <option value="1">1 - Publicly accessible to all users</option>
+        <option value="2" selected>
+	  2 - Publicly accessible to all users</option>
+      </select>
+      <span class="form-text text-muted">This determines whether the
+        population/grouping will appear on the menus for users.</span>
+    </div>
+
+    <div class="form-group">
+      <label for="txt:inbredset-family" class="form-label">family</label>
+      <input id="txt:inbredset-family"
+	     name="inbredset_family"
+	     type="text"
+	     placeholder="I am not sure what this is about."
+	     class="form-control" />
+      <span class="form-text text-muted">I do not currently know what this is about.
+        This is a failure on my part to figure out what this is and provide a
+        useful description. Please feel free to remind me.</span>
+    </div>
+
+    <div class="form-group">
+    <label for="txtarea:" class="form-label">Description</label>
+    <textarea id="txtarea:description"
+	      name="description"
+	      rows="5"
+	      placeholder="Enter a description of this grouping/population"
+	      class="form-control"></textarea>
+    <span class="form-text text-muted">
+      A long-form description of what the population consists of. Useful for
+      humans.</span>
+    </div>
+  </div>
+
+  <button type="submit" class="btn btn-primary" />
+  create grouping/population</button>
+</form>
+
+{%endblock%}
+
+
+{%block javascript%}
+{%endblock%}
diff --git a/uploader/templates/rqtl2/select-probeset-dataset.html b/uploader/templates/rqtl2/select-probeset-dataset.html
new file mode 100644
index 0000000..26f52ed
--- /dev/null
+++ b/uploader/templates/rqtl2/select-probeset-dataset.html
@@ -0,0 +1,191 @@
+{%extends "base.html"%}
+{%from "flash_messages.html" import flash_messages%}
+
+{%block title%}Upload R/qtl2 Bundle{%endblock%}
+
+{%block contents%}
+<h2 class="heading">Phenotype(ProbeSet) Dataset</h2>
+
+<div class="row">
+  <p>The R/qtl2 bundle you uploaded contains (a) "<strong>pheno</strong>"
+    file(s). This data needs to be organised under a dataset.</p>
+  <p>This page gives you the ability to do that.</p>
+</div>
+
+{%if datasets | length > 0%}
+<div class="row">
+  <form method="POST"
+        action="{{url_for('upload.rqtl2.select_probeset_dataset',
+	        species_id=species.SpeciesId, population_id=population.Id)}}"
+        id="frm:select-probeset-dataset">
+    <legend class="heading">Select from existing ProbeSet datasets</legend>
+    {{flash_messages("error-rqtl2")}}
+
+    <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+    <input type="hidden" name="population_id"
+	   value="{{population.InbredSetId}}" />
+    <input type="hidden" name="rqtl2_bundle_file"
+	   value="{{rqtl2_bundle_file}}" />
+    <input type="hidden" name="geno-dataset-id" value="{{geno_dataset.Id}}" />
+    <input type="hidden" name="tissueid" value="{{tissue.Id}}" />
+    <input type="hidden" name="probe-study-id" value="{{probe_study.Id}}" />
+
+    <div class="form-group">
+      <label class="form-label" for="select:probe-dataset">Dataset</label>
+      <select id="select:probe-dataset"
+	      name="probe-dataset-id"
+	      required="required"
+	      {%if datasets | length == 0%}disabled="disabled"{%endif%}
+              class="form-control"
+              aria-describedby="help-probe-dataset">
+        <option value="">Select a dataset</option>
+        {%for dataset in datasets%}
+        <option value={{dataset.Id}}>
+	  {{dataset.Name}}
+	  {%if dataset.FullName%}
+	  -- ({{dataset.FullName}})
+	  {%endif%}
+        </option>
+        {%endfor%}
+      </select>
+
+      <span id="help-probe-dataset" class="form-text text-muted">
+        Select from existing ProbeSet datasets.</span>
+    </div>
+
+    <button type="submit" class="btn btn-primary" />select dataset</button>
+</form>
+</div>
+
+<div class="row">
+  <p style="color:#FE3535; padding-left:20em; font-weight:bolder;">OR</p>
+</div>
+{%endif%}
+
+<div class="row">
+  <p>Create an entirely new ProbeSet dataset for your data.</p>
+</div>
+
+<div class="row">
+  <form method="POST"
+        action="{{url_for('upload.rqtl2.create_probeset_dataset',
+	        species_id=species.SpeciesId, population_id=population.Id)}}"
+        id="frm:create-probeset-dataset">
+    <legend class="heading">Create a new ProbeSet dataset</legend>
+    {{flash_messages("error-rqtl2-create-probeset-dataset")}}
+
+    <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+    <input type="hidden" name="population_id"
+	   value="{{population.InbredSetId}}" />
+    <input type="hidden" name="rqtl2_bundle_file"
+	   value="{{rqtl2_bundle_file}}" />
+    <input type="hidden" name="geno-dataset-id" value="{{geno_dataset.Id}}" />
+    <input type="hidden" name="tissueid" value="{{tissue.Id}}" />
+    <input type="hidden" name="probe-study-id" value="{{probe_study.Id}}" />
+
+    <div class="form-group">
+      <label class="form-label" for="select:average">averaging method</label>
+      <select id="select:average"
+	      name="averageid"
+	      required="required"
+              class="form-control"
+              aria-describedby="help-average">
+        <option value="">Select averaging method</option>
+        {%for avgmethod in avgmethods%}
+        <option value="{{avgmethod.Id}}">
+	  {{avgmethod.Name}}
+	  {%if avgmethod.Normalization%}
+	  ({{avgmethod.Normalization}})
+	  {%endif%}
+        </option>
+        {%endfor%}
+      </select>
+
+      <span id="help-average" class="form-text text-muted">
+        Select the averaging method used for your data.
+      </span>
+    </div>
+
+    <div class="form-group">
+      <label class="form-label" for="txt:datasetname">Name</label>
+      <input type="text" id="txt:datasetname" name="datasetname"
+	     required="required"
+	     maxlength="40"
+	     title="Name of the dataset, e.g 'BXDMicroArray_ProbeSet_June03'. (Required)"
+             class="form-control"
+             aria-describedby="help-dataset-name" />
+
+      <span id="help-dataset-name" class="form-text text-muted">
+        Provide a name for the dataset e.g. "BXDMicroArray_ProbeSet_June03". This
+        is mandatory <strong>MUST</strong> be provided.
+      </span>
+    </div>
+
+    <div class="form-group">
+      <label class="form-label" for="txt:datasetfullname">Full Name</label>
+      <input type="text" id="txt:datasetfullname" name="datasetfullname"
+	     required="required"
+	     maxlength="100"
+	     title="A longer name for the dataset, e.g. 'UTHSC Brain mRNA U74Av2 (Jun03) MAS5'. (Required)"
+             class="form-control"
+             aria-describedby="help-dataset-fullname" />
+
+      <span id="help-dataset-fullname" class="form-text text-muted">
+        Provide a longer, more descriptive name for the dataset e.g.
+        "UTHSC Brain mRNA U74Av2 (Jun03) MAS5". This is mandatory and
+        <strong>MUST</strong> be provided.
+      </span>
+    </div>
+
+    <div class="form-group">
+      <label class="form-label" for="txt:datasetshortname">Short Name</label>
+      <input type="text" id="txt:datasetshortname" name="datasetshortname"
+	     maxlength="100"
+	     title="An abbreviated name for the dataset, e.g 'Br_U_0603_M'. (Optional)"
+             class="form-control"
+             aria-describedby="help-dataset-shortname" />
+
+      <span id="help-dataset-shortname" class="form-text text-muted">
+        Provide a longer, more descriptive name for the dataset e.g. "Br_U_0603_M".
+        This is optional.
+      </span>
+    </div>
+
+    <div class="form-check">
+      <input type="checkbox" id="chk:public" name="datasetpublic"
+	     checked="checked"
+	     title="Whether or not the dataset is accessible by the general public."
+             class="form-check-input"
+             aria-describedby="help-public" />
+      <label class="form-check-label" for="chk:datasetpublic">Public?</label>
+
+      <span id="help-public" class="form-text text-muted">
+        Check to specify that the dataset will be publicly available. Uncheck to
+        limit access to the dataset.
+      </span>
+    </div>
+
+    <div class="form-group">
+      <label class="form-label" for="select:datasetdatascale">Data Scale</label>
+      <select id="select:datasetdatascale"
+	      name="datasetdatascale"
+	      required="required"
+              class="form-control"
+              aria-describedby="help-dataset-datascale">
+        <option value="log2" selected="selected">log2</option>
+        <option value="z_score">z_score</option>
+        <option value="log2_ratio">log2_ratio</option>
+        <option value="linear">linear</option>
+        <option value="linear_positive">linear_positive</option>
+      </select>
+
+      <span id="help-dataset-datascale" class="form-text text-muted">
+        Select from a list of scaling methods.
+      </span>
+    </div>
+
+    <button type="submit" class="btn btn-primary">create dataset</button>
+  </form>
+</div>
+
+{%endblock%}
diff --git a/uploader/templates/rqtl2/select-probeset-study-id.html b/uploader/templates/rqtl2/select-probeset-study-id.html
new file mode 100644
index 0000000..b9bf52e
--- /dev/null
+++ b/uploader/templates/rqtl2/select-probeset-study-id.html
@@ -0,0 +1,143 @@
+{%extends "base.html"%}
+{%from "flash_messages.html" import flash_messages %}
+
+{%block title%}Upload R/qtl2 Bundle{%endblock%}
+
+{%block contents%}
+<h2 class="heading">Phenotype(ProbeSet) Study</h2>
+
+<div class="row">
+  <p>The R/qtl2 bundle you uploaded contains (a) "<strong>pheno</strong>"
+    file(s). This data needs to be organised under a study.</p>
+  <p>In this page, you can either select from a existing dataset:</p>
+
+  <form method="POST"
+        action="{{url_for('upload.rqtl2.select_probeset_study',
+	        species_id=species.SpeciesId, population_id=population.Id)}}"
+        id="frm:select-probeset-study">
+    <legend class="heading">Select from existing ProbeSet studies</legend>
+    {{flash_messages("error-rqtl2-select-probeset-study")}}
+
+    <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+    <input type="hidden" name="population_id"
+	   value="{{population.InbredSetId}}" />
+    <input type="hidden" name="rqtl2_bundle_file"
+	   value="{{rqtl2_bundle_file}}" />
+    <input type="hidden" name="geno-dataset-id" value="{{geno_dataset.Id}}" />
+    <input type="hidden" name="tissueid" value="{{tissue.Id}}" />
+
+    <div>
+      <label for="select:probe-study" class="form-label">Study</label>
+      <select id="select:probe-study"
+	      name="probe-study-id"
+	      required="required"
+              aria-describedby="help-select-probeset-study"
+	      {%if studies | length == 0%}disabled="disabled"{%endif%}
+              class="form-control">
+        <option value="">Select a study</option>
+        {%for study in studies%}
+        <option value={{study.Id}}>
+	  {{study.Name}}
+	  {%if study.FullName%}
+	  -- ({{study.FullName}})
+	  {%endif%}
+        </option>
+        {%endfor%}
+      </select>
+      <small id="help-select-probeset-study" class="form-text text-muted">
+        Select from existing ProbeSet studies.
+      </small>
+    </div>
+
+    <button type="submit" class="btn btn-primary">select study</button>
+  </form>
+</div>
+
+<div class="row">
+  <p style="color:#FE3535; padding-left:20em; font-weight:bolder;">OR</p>
+</div>
+
+<div class="row">
+
+  <p>Create a new ProbeSet dataset below:</p>
+
+  <form method="POST"
+        action="{{url_for('upload.rqtl2.create_probeset_study',
+	        species_id=species.SpeciesId, population_id=population.Id)}}"
+        id="frm:create-probeset-study">
+    <legend class="heading">Create new ProbeSet study</legend>
+
+    {{flash_messages("error-rqtl2-create-probeset-study")}}
+
+    <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+    <input type="hidden" name="population_id"
+	   value="{{population.InbredSetId}}" />
+    <input type="hidden" name="rqtl2_bundle_file"
+	   value="{{rqtl2_bundle_file}}" />
+    <input type="hidden" name="geno-dataset-id" value="{{geno_dataset.Id}}" />
+    <input type="hidden" name="tissueid" value="{{tissue.Id}}" />
+
+    <div>
+      <label for="select:platform" class="form-label">Platform</label>
+      <select id="select:platform"
+	      name="platformid"
+	      required="required"
+              aria-describedby="help-select-platform"
+	      {%if platforms | length == 0%}disabled="disabled"{%endif%}
+              class="form-control">
+        <option value="">Select a platform</option>
+        {%for platform in platforms%}
+        <option value="{{platform.GeneChipId}}">
+	  {{platform.GeneChipName}} ({{platform.Name}})
+        </option>
+        {%endfor%}
+      </select>
+      <small id="help-select-platform" class="form-text text-muted">
+        Select from a list of known genomics platforms.
+      </small>
+    </div>
+
+    <div class="form-group">
+      <label for="txt:studyname" class="form-label">Study name</label>
+      <input type="text" id="txt:studyname" name="studyname"
+	     placeholder="Name of the study. (Required)"
+	     required="required"
+	     maxlength="100"
+             class="form-control" />
+      <span class="form-text text-muted" id="help-study-name">
+        Provide a name for the study.</span>
+    </div>
+
+    <div class="form-group">
+      <label for="txt:studyfullname" class="form-label">Full Study Name</label>
+      <input type="text"
+             id="txt:studyfullname"
+             name="studyfullname"
+	     placeholder="Longer name of the study. (Optional)"
+	     maxlength="100"
+             class="form-control" />
+      <span class="form-text text-muted" id="help-study-full-name">
+        Provide a longer, more descriptive name for the study. This is optional
+        and you can leave it blank.
+      </span>
+    </div>
+
+    <div class="form-group">
+      <label for="txt:studyshortname" class="form-label">Short Study Name</label>
+      <input type="text"
+             id="txt:studyshortname"
+             name="studyshortname"
+	     placeholder="Shorter name of the study. (Optional)"
+	     maxlength="100"
+             class="form-control" />
+      <span class="form-text text-muted" id="help-study-short-name">
+        Provide a shorter name for the study. This is optional and you can leave
+        it blank.
+      </span>
+    </div>
+
+    <button type="submit" class="btn btn-primary">create study</button>
+  </form>
+</div>
+
+{%endblock%}
diff --git a/uploader/templates/rqtl2/select-tissue.html b/uploader/templates/rqtl2/select-tissue.html
new file mode 100644
index 0000000..34e1758
--- /dev/null
+++ b/uploader/templates/rqtl2/select-tissue.html
@@ -0,0 +1,115 @@
+{%extends "base.html"%}
+{%from "flash_messages.html" import flash_messages%}
+
+{%block title%}Upload R/qtl2 Bundle{%endblock%}
+
+{%block contents%}
+<h2 class="heading">Tissue</h2>
+
+<div class="row">
+  <p>The data you are uploading concerns a tissue, cell, organ, or other
+    biological material used in an experiment.</p>
+  <p>Select the appropriate biological material below</p>
+</div>
+
+{%if tissues | length > 0%}
+<div class="row">
+  <form method="POST"
+        action="{{url_for('upload.rqtl2.select_tissue',
+	        species_id=species.SpeciesId, population_id=population.Id)}}"
+        id="frm:select-probeset-dataset">
+    <legend class="heading">Select from existing ProbeSet datasets</legend>
+    {{flash_messages("error-select-tissue")}}
+
+    <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+    <input type="hidden" name="population_id"
+	   value="{{population.InbredSetId}}" />
+    <input type="hidden" name="rqtl2_bundle_file"
+	   value="{{rqtl2_bundle_file}}" />
+    <input type="hidden" name="geno-dataset-id" value="{{geno_dataset.Id}}" />
+
+    <div class="form-group">
+      <label class="form-label" for="select-tissue">Tissue</label>
+      <select id="select-tissue"
+	      name="tissueid"
+	      required="required"
+	      {%if tissues | length == 0%}disabled="disabled"{%endif%}
+              class="form-control"
+              aria-describedby="help-select-tissue">
+        <option value="">Select a tissue</option>
+        {%for tissue in tissues%}
+        <option value={{tissue.Id}}>
+	  {{tissue.Name}}
+	  {%if tissue.Short_Name%}
+	  -- ({{tissue.Short_Name}})
+	  {%endif%}
+        </option>
+        {%endfor%}
+      </select>
+
+      <span id="help-select-tissue" class="form-text text-muted">
+        Select from existing biological material.</span>
+    </div>
+
+    <button type="submit" class="btn btn-primary">use selected</button>
+  </form>
+</div>
+
+<div class="row">
+  <p style="color:#FE3535; padding-left:20em; font-weight:bolder;">OR</p>
+</div>
+{%endif%}
+
+<div class="row">
+  <p>If you cannot find the biological material in the drop-down above, add it
+    to the system below.</p>
+
+  <form method="POST"
+        action="{{url_for('upload.rqtl2.create_tissue',
+	        species_id=species.SpeciesId, population_id=population.Id)}}"
+        id="frm:create-probeset-dataset">
+    <legend class="heading">Add new tissue, organ or biological material</legend>
+    {{flash_messages("error-create-tissue")}}
+
+    <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+    <input type="hidden" name="population_id"
+	   value="{{population.InbredSetId}}" />
+    <input type="hidden" name="rqtl2_bundle_file"
+	   value="{{rqtl2_bundle_file}}" />
+    <input type="hidden" name="geno-dataset-id" value="{{geno_dataset.Id}}" />
+
+    <div class="form-group">
+      <label class="form-label" for="tissue-name">name</label>
+      <input type="text"
+             id="txt-tissuename"
+             name="tissuename"
+             required="required"
+             title = "A name to identify the tissue, organ or biological material."
+             class="form-control"
+             aria-describedby="help-tissue-name" />
+
+      <span class="form-text text-muted" id="help-tissue-name">
+        A name to identify the tissue, organ or biological material.
+      </span>
+    </div>
+
+    <div class="form-group">
+      <label for="txt-shortname" class="form-label">short name</label>
+      <input type="text" id="txt-tissueshortname" name="tissueshortname"
+	     required="required"
+	     maxlength="7"
+	     title="A short name (e.g. 'Mam') for the biological material."
+             class="form-control"
+             aria-describedby="help-tissue-short-name" />
+
+      <span class="form-text text-muted" id="help-tissue-short-name">
+        Provide a short name for the tissue, organ or biological material used in
+        the experiment.
+      </span>
+    </div>
+
+    <button type="submit" class="btn btn-primary" />add new material</button>
+</form>
+</div>
+
+{%endblock%}
diff --git a/uploader/templates/rqtl2/summary-info.html b/uploader/templates/rqtl2/summary-info.html
new file mode 100644
index 0000000..1be87fa
--- /dev/null
+++ b/uploader/templates/rqtl2/summary-info.html
@@ -0,0 +1,65 @@
+{%extends "base.html"%}
+{%from "flash_messages.html" import flash_messages%}
+
+{%block title%}Upload R/qtl2 Bundle{%endblock%}
+
+{%block contents%}
+<h2 class="heading">Summary</h2>
+
+<div class="row">
+  <p>This is the information you have provided to accompany the R/qtl2 bundle
+    you have uploaded. Please verify the information is correct before
+    proceeding.</p>
+</div>
+
+<div class="row">
+  <dl>
+    <dt>Species</dt>
+    <dd>{{species.SpeciesName}} ({{species.FullName}})</dd>
+
+    <dt>Population</dt>
+    <dd>{{population.InbredSetName}}</dd>
+
+    {%if geno_dataset%}
+    <dt>Genotype Dataset</dt>
+    <dd>{{geno_dataset.Name}} ({{geno_dataset.FullName}})</dd>
+    {%endif%}
+
+    {%if tissue%}
+    <dt>Tissue</dt>
+    <dd>{{tissue.TissueName}} ({{tissue.Name}}, {{tissue.Short_Name}})</dd>
+    {%endif%}
+
+    {%if probe_study%}
+    <dt>ProbeSet Study</dt>
+    <dd>{{probe_study.Name}} ({{probe_study.FullName}})</dd>
+    {%endif%}
+
+    {%if probe_dataset%}
+    <dt>ProbeSet Dataset</dt>
+    <dd>{{probe_dataset.Name2}} ({{probe_dataset.FullName}})</dd>
+    {%endif%}
+  </dl>
+</div>
+
+<div class="row">
+  <form id="frm:confirm-rqtl2bundle-details"
+        action="{{url_for('upload.rqtl2.confirm_bundle_details',
+	        species_id=species.SpeciesId,
+	        population_id=population.InbredSetId)}}"
+        method="POST"
+        enctype="multipart/form-data">
+    <legend class="heading">Create ProbeSet dataset</legend>
+
+    <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+    <input type="hidden" name="population_id"
+	   value="{{population.InbredSetId}}" />
+    <input type="hidden" name="rqtl2_bundle_file" value="{{rqtl2_bundle_file}}" />
+    <input type="hidden" name="geno-dataset-id" value="{{geno_dataset.Id}}" />
+    <input type="hidden" name="probe-study-id" value="{{probe_study.Id}}" />
+    <input type="hidden" name="probe-dataset-id" value="{{probe_dataset.Id}}" />
+
+    <button type="submit" class="btn btn-primary">continue</button>
+  </form>
+</div>
+{%endblock%}
diff --git a/uploader/templates/rqtl2/upload-rqtl2-bundle-step-01.html b/uploader/templates/rqtl2/upload-rqtl2-bundle-step-01.html
new file mode 100644
index 0000000..07c240f
--- /dev/null
+++ b/uploader/templates/rqtl2/upload-rqtl2-bundle-step-01.html
@@ -0,0 +1,276 @@
+{%extends "base.html"%}
+{%from "flash_messages.html" import flash_all_messages%}
+{%from "upload_progress_indicator.html" import upload_progress_indicator%}
+
+{%block title%}Upload R/qtl2 Bundle{%endblock%}
+
+{%block contents%}
+{%macro rqtl2_file_help()%}
+<span class="form-text text-muted">
+  <p>
+    Provide a valid R/qtl2 zip file here. In particular, ensure your zip bundle
+    contains exactly one control file and the corresponding files mentioned in
+    the control file.
+  </p>
+  <p>
+    The control file can be either a YAML or JSON file. <em>ALL</em> other data
+    files in the zip bundle should be CSV files.
+  </p>
+  <p>See the
+    <a href="https://kbroman.org/qtl2/assets/vignettes/input_files.html"
+       target="_blank">
+      R/qtl2 file format specifications
+    </a>
+    for more details.
+  </p>
+</span>
+{%endmacro%}
+{{upload_progress_indicator()}}
+
+<div id="resumable-file-display-template"
+     class="panel panel-info"
+     style="display: none">
+  <div class="panel-heading"></div>
+  <div class="panel-body"></div>
+</div>
+
+
+<h2 class="heading">Upload R/qtl2 Bundle</h2>
+
+<div id="resumable-drop-area"
+     style="display:none;background:#eeeeee;min-height:12em;border-radius:0.5em;padding:1em;">
+  <p>
+    <a id="resumable-browse-button" href="#"
+       class="btn btn-info">Browse</a>
+  </p>
+  <p class="form-text text-muted">
+    You can drag and drop your file here, or click the browse button.
+    Click on the file to remove it.
+  </p>
+  {{rqtl2_file_help()}}
+  <div id="resumable-selected-files"
+       style="display:flex;flex-direction:row;flex-wrap: wrap;justify-content:space-around;gap:10px 20px;"></div>
+  <div id="resumable-class-buttons" style="text-align: right;">
+    <button id="resumable-upload-button"
+            class="btn btn-primary"
+            style="display: none">start upload</button>
+    <button id="resumable-cancel-upload-button"
+            class="btn btn-danger"
+            style="display: none">cancel upload</button>
+  </div>
+  <div id="resumable-progress-bar" class="progress" style="display: none">
+    <div class="progress-bar"
+         role="progress-bar"
+         aria-valuenow="60"
+         aria-valuemin="0"
+         aria-valuemax="100"
+         style="width: 0%;">
+      Uploading: 60%
+    </div>
+  </div>
+</div>
+
+<form id="frm-upload-rqtl2-bundle"
+      action="{{url_for('upload.rqtl2.upload_rqtl2_bundle',
+	      species_id=species.SpeciesId,
+	      population_id=population.InbredSetId)}}"
+      method="POST"
+      enctype="multipart/form-data"
+      data-resumable-target="{{url_for(
+                             'upload.rqtl2.upload_rqtl2_bundle_chunked_post',
+                             species_id=species.SpeciesId,
+                             population_id=population.InbredSetId)}}">
+  <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+  <input type="hidden" name="population_id"
+	 value="{{population.InbredSetId}}" />
+
+  {{flash_all_messages()}}
+
+  <div class="form-group">
+    <legend class="heading">file upload</legend>
+    <label for="file-rqtl2-bundle" class="form-label">R/qtl2 bundle</label>
+    <input type="file" id="file-rqtl2-bundle" name="rqtl2_bundle_file"
+	   accept="application/zip, .zip"
+	   required="required"
+           class="form-control" />
+    {{rqtl2_file_help()}}
+  </div>
+
+  <button type="submit"
+          class="btn btn-primary"
+          data-toggle="modal"
+          data-target="#upload-progress-indicator">upload R/qtl2 bundle</button>
+</form>
+
+{%endblock%}
+
+{%block javascript%}
+<script src="{{url_for('base.node_modules',
+             filename='resumablejs/resumable.js')}}"></script>
+<script type="text/javascript" src="/static/js/upload_progress.js"></script>
+<script type="text/javascript">
+  function readBinaryFile(file) {
+      return new Promise((resolve, reject) => {
+          var _reader = new FileReader();
+          _reader.onload = (event) => {resolve(_reader.result);};
+          _reader.readAsArrayBuffer(file);
+      });
+  }
+
+  function computeFileChecksum(file) {
+      return readBinaryFile(file)
+          .then((content) => {
+              return window.crypto.subtle.digest(
+                  "SHA-256", new Uint8Array(content));
+          }).then((digest) => {
+              return Uint8ArrayToHex(new Uint8Array(digest))
+          });
+  }
+
+  function Uint8ArrayToHex(arr) {
+      var toHex = (val) => {
+          _hex = val.toString(16);
+          if(_hex.length < 2) {
+              return "0" + val;
+          }
+          return _hex;
+      };
+      _hexstr = ""
+      arr.forEach((val) => {_hexstr += toHex(val)});
+      return _hexstr
+  }
+
+  var r = Resumable({
+      target: $("#frm-upload-rqtl2-bundle").attr("data-resumable-target"),
+      fileType: ["zip"],
+      maxFiles: 1,
+      forceChunkSize: true,
+      generateUniqueIdentifier: (file, event) => {
+          return computeFileChecksum(file).then((checksum) => {
+              var _relativePath = (file.webkitRelativePath
+                                   || file.relativePath
+                                   || file.fileName
+                                   || file.name);
+              return checksum + "-" + _relativePath.replace(
+                  /[^a-zA-Z0-9_-]/img, "");
+          });
+      }
+  });
+
+  if(r.support) {
+      //Hide form and display drag&drop UI
+      $("#frm-upload-rqtl2-bundle").css("display", "none");
+      $("#resumable-drop-area").css("display", "block");
+
+      // Define UI elements for browse and drag&drop
+      r.assignDrop(document.getElementById("resumable-drop-area"));
+      r.assignBrowse(document.getElementById("resumable-browse-button"));
+
+      // Event handlers
+
+      function display_files(files) {
+          displayArea = $("#resumable-selected-files")
+          displayArea.empty();
+          files.forEach((file) => {
+              var displayElement = $(
+                  "#resumable-file-display-template").clone();
+              displayElement.removeAttr("id");
+              displayElement.css("display", "");
+              displayElement.find(".panel-heading").text(file.fileName);
+              list = $("<ul></ul>");
+              list.append($("<li><strong>Name</strong>: "
+                            + (file.name
+                               || file.fileName
+                               || file.relativePath
+                               || file.webkitRelativePath)
+                            + "</li>"));
+              list.append($("<li><strong>Size</strong>: "
+                            + (file.size / (1024*1024)).toFixed(2)
+                            + " MB</li>"));
+              list.append($("<li><strong>Unique Identifier</strong>: "
+                            + file.uniqueIdentifier + "</li>"));
+              list.append($("<li><strong>Mime</strong>: "
+                            + file.file.type
+                            + "</li>"));
+              displayElement.find(".panel-body").append(list);
+              displayElement.appendTo("#resumable-selected-files");
+          });
+      }
+
+      r.on("filesAdded", function(files) {
+          display_files(files);
+          $("#resumable-upload-button").css("display", "");
+          $("#resumable-upload-button").on("click", (event) => {
+              r.upload();
+          });
+      });
+
+      r.on("uploadStart", (event) => {
+          $("#resumable-upload-button").css("display", "none");
+          $("#resumable-cancel-upload-button").css("display", "");
+          $("#resumable-cancel-upload-button").on("click", (event) => {
+              r.files.forEach((file) => {
+                  if(file.isUploading()) {
+                      file.abort();
+                  }
+              });
+              $("#resumable-cancel-upload-button").css("display", "none");
+              $("#resumable-upload-button").on("click", (event) => {
+                  r.files.forEach((file) => {file.retry();});
+              });
+              $("#resumable-upload-button").css("display", "");
+          });
+      });
+
+      r.on("progress", () => {
+          var progress = (r.progress() * 100).toFixed(2);
+          var pbar = $("#resumable-progress-bar > .progress-bar");
+          $("#resumable-progress-bar").css("display", "");
+          pbar.css("width", progress+"%");
+          pbar.attr("aria-valuenow", progress);
+          pbar.text("Uploading: " + progress + "%");
+      })
+
+      r.on("fileSuccess", (file, message) => {
+          if(message != "OK") {
+              var uri = (window.location.protocol
+                         + "//"
+                         + window.location.host
+                         + message);
+              window.location.replace(uri);
+          }
+      });
+
+      r.on("error", (message, file) => {
+          filename = (file.webkitRelativePath
+                      || file.relativePath
+                      || file.fileName
+                      || file.name);
+          jsonmsg = JSON.parse(message);
+          alert("There was an error while uploading your file '"
+                + filename
+                + "'. The error message was:\n\n\t"
+                + jsonmsg.error
+                + " ("
+                + jsonmsg.statuscode
+                + "): " + jsonmsg.message);
+      })
+  } else {
+      setup_upload_handlers(
+          "frm-upload-rqtl2-bundle", make_data_uploader(
+	      function (form) {
+	          var formdata = new FormData();
+	          formdata.append(
+		      "species_id",
+		      form.querySelector('input[name="species_id"]').value);
+	          formdata.append(
+		      "population_id",
+		      form.querySelector('input[name="population_id"]').value);
+	          formdata.append(
+		      "rqtl2_bundle_file",
+		      form.querySelector("#file-rqtl2-bundle").files[0]);
+	          return formdata;
+	      }));
+  }
+</script>
+{%endblock%}
diff --git a/uploader/templates/rqtl2/upload-rqtl2-bundle-step-02.html b/uploader/templates/rqtl2/upload-rqtl2-bundle-step-02.html
new file mode 100644
index 0000000..93b1dc9
--- /dev/null
+++ b/uploader/templates/rqtl2/upload-rqtl2-bundle-step-02.html
@@ -0,0 +1,33 @@
+{%extends "base.html"%}
+{%from "flash_messages.html" import flash_all_messages%}
+
+{%block title%}Upload R/qtl2 Bundle{%endblock%}
+
+{%block contents%}
+<h2 class="heading">Upload R/qtl2 Bundle</h2>
+
+<div class="row">
+  <p>You have successfully uploaded the zipped bundle of R/qtl2 files.</p>
+  <p>The next step is to select the various extra information we need to figure
+    out what to do with the data. You will select/create the relevant studies
+    and/or datasets to organise the data in the steps that follow.</p>
+  <p>Click "Continue" below to proceed.</p>
+
+  <form id="frm-upload-rqtl2-bundle"
+        action="{{url_for('upload.rqtl2.select_dataset_info',
+	        species_id=species.SpeciesId,
+	        population_id=population.InbredSetId)}}"
+        method="POST"
+        enctype="multipart/form-data">
+    {{flash_all_messages()}}
+    <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+    <input type="hidden" name="population_id"
+	   value="{{population.InbredSetId}}" />
+    <input type="hidden" name="rqtl2_bundle_file"
+	   value="{{rqtl2_bundle_file}}" />
+
+    <button type="submit" class="btn btn-primary">continue</button>
+  </form>
+</div>
+
+{%endblock%}
diff --git a/uploader/templates/samples/select-population.html b/uploader/templates/samples/select-population.html
new file mode 100644
index 0000000..da19ddc
--- /dev/null
+++ b/uploader/templates/samples/select-population.html
@@ -0,0 +1,99 @@
+{%extends "base.html"%}
+{%from "flash_messages.html" import flash_messages%}
+
+{%block title%}Select Grouping/Population{%endblock%}
+
+{%block contents%}
+<h1 class="heading">Select grouping/population</h1>
+
+<div>
+  <p>We organise the samples/cases/strains in a hierarchichal form, starting
+    with <strong>species</strong> at the very top. Under species, we have a
+    grouping in terms of the relevant population
+    (e.g. Inbred populations, cell tissue, etc.)</p>
+</div>
+
+<form method="POST" action="{{url_for('samples.select_population',
+                            species_id=species.SpeciesId)}}">
+  <legend class="heading">select grouping/population</legend>
+  {{flash_messages("error-select-population")}}
+
+  <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+
+  <div class="form-group">
+    <label for="select:inbredset" class="form-label">grouping/population</label>
+    <select id="select:inbredset"
+	    name="inbredset_id"
+	    required="required"
+	    class="form-control">
+      <option value="">Select a grouping/population</option>
+      {%for pop in populations%}
+      <option value="{{pop.InbredSetId}}">
+	{{pop.InbredSetName}} ({{pop.FullName}})</option>
+      {%endfor%}
+    </select>
+  </div>
+
+  <button type="submit" class="btn btn-primary">select population</button>
+</form>
+
+<p style="color:#FE3535; padding-left:20em; font-weight:bolder;">OR</p>
+
+<form method="POST" action="{{url_for('samples.create_population',
+                            species_id=species.SpeciesId)}}">
+  <legend class="heading">create new grouping/population</legend>
+  {{flash_messages("error-create-population")}}
+
+  <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+  <div class="form-group">
+    <legend>mandatory</legend>
+
+    <label for="txt:inbredset-name" class="form-label">name</label>
+    <input id="txt:inbredset-name"
+	   name="inbredset_name"
+	   type="text"
+	   required="required"
+	   placeholder="Enter grouping/population name"
+	   class="form-control" />
+
+    <label for="txt:" class="form-label">full name</label>
+    <input id="txt:inbredset-fullname"
+	   name="inbredset_fullname"
+	   type="text"
+	   required = "required"
+	   placeholder="Enter the grouping/population's full name"
+	   class="form-control" />
+  </div>
+  <div class="form-group">
+    <legend>Optional</legend>
+
+    <label for="num:public" class="form-label">public?</label>
+    <input id="num:public"
+	   name="public"
+	   type="number"
+	   min="0" max="2" value="2"
+	   class="form-control" />
+
+    <label for="txt:inbredset-family" class="form-label">family</label>
+    <input id="txt:inbredset-family"
+	   name="inbredset_family"
+	   type="text"
+	   placeholder="I am not sure what this is about."
+	   class="form-control" />
+
+    <label for="txtarea:" class="form-label">Description</label>
+    <textarea id="txtarea:description"
+	      name="description"
+	      rows="5"
+	      placeholder="Enter a description of this grouping/population"
+	      class="form-control"></textarea>
+  </div>
+
+  <button type="submit" class="btn btn-primary">create grouping/population</button>
+</form>
+
+{%endblock%}
+
+
+{%block javascript%}
+{%endblock%}
diff --git a/uploader/templates/samples/select-species.html b/uploader/templates/samples/select-species.html
new file mode 100644
index 0000000..edadc61
--- /dev/null
+++ b/uploader/templates/samples/select-species.html
@@ -0,0 +1,30 @@
+{%extends "base.html"%}
+{%from "flash_messages.html" import flash_all_messages%}
+
+{%block title%}Select Grouping/Population{%endblock%}
+
+{%block contents%}
+<h2 class="heading">upload samples/cases</h2>
+
+<p>We need to know what species your data belongs to.</p>
+
+{{flash_all_messages()}}
+
+<form method="POST" action="{{url_for('samples.select_species')}}">
+  <legend class="heading">upload samples</legend>
+  <div class="form-group">
+    <label for="select_species02" class="form-label">Species</label>
+    <select id="select_species02"
+            name="species_id"
+            required="required"
+            class="form-control">
+      <option value="">Select species</option>
+      {%for spec in species%}
+      <option value="{{spec.SpeciesId}}">{{spec.MenuName}}</option>
+      {%endfor%}
+    </select>
+  </div>
+
+  <button type="submit" class="btn btn-primary">submit</button>
+</form>
+{%endblock%}
diff --git a/uploader/templates/samples/upload-failure.html b/uploader/templates/samples/upload-failure.html
new file mode 100644
index 0000000..09e2ecf
--- /dev/null
+++ b/uploader/templates/samples/upload-failure.html
@@ -0,0 +1,27 @@
+{%extends "base.html"%}
+{%from "cli-output.html" import cli_output%}
+
+{%block title%}Samples Upload Failure{%endblock%}
+
+{%block contents%}
+<h1 class="heading">{{job.job_name}}</h2>
+
+<p>There was a failure attempting to upload the samples.</p>
+
+<p>Here is some information to help with debugging the issue. Provide this
+  information to the developer/maintainer.</p>
+
+<h3>Debugging Information</h3>
+<ul>
+  <li><strong>job id</strong>: {{job.job_id}}</li>
+  <li><strong>status</strong>: {{job.status}}</li>
+  <li><strong>job type</strong>: {{job["job-type"]}}</li>
+</ul>
+
+<h4>stdout</h4>
+{{cli_output(job, "stdout")}}
+
+<h4>stderr</h4>
+{{cli_output(job, "stderr")}}
+
+{%endblock%}
diff --git a/uploader/templates/samples/upload-progress.html b/uploader/templates/samples/upload-progress.html
new file mode 100644
index 0000000..7bb02be
--- /dev/null
+++ b/uploader/templates/samples/upload-progress.html
@@ -0,0 +1,22 @@
+{%extends "base.html"%}
+{%from "cli-output.html" import cli_output%}
+
+{%block extrameta%}
+<meta http-equiv="refresh" content="5">
+{%endblock%}
+
+{%block title%}Job Status{%endblock%}
+
+{%block contents%}
+<h1 class="heading">{{job.job_name}}</h2>
+
+<p>
+<strong>status</strong>:
+<span>{{job["status"]}} ({{job.get("message", "-")}})</span><br />
+</p>
+
+<p>saving to database...</p>
+
+{{cli_output(job, "stdout")}}
+
+{%endblock%}
diff --git a/uploader/templates/samples/upload-samples.html b/uploader/templates/samples/upload-samples.html
new file mode 100644
index 0000000..e62de57
--- /dev/null
+++ b/uploader/templates/samples/upload-samples.html
@@ -0,0 +1,139 @@
+{%extends "base.html"%}
+{%from "flash_messages.html" import flash_messages%}
+
+{%block title%}Upload Samples{%endblock%}
+
+{%block css%}{%endblock%}
+
+{%block contents%}
+<h1 class="heading">upload samples</h1>
+
+{{flash_messages("alert-success")}}
+
+<p>You can now upload a character-separated value (CSV) file that contains
+  details about your samples. The CSV file should have the following fields:
+  <dl>
+    <dt>Name</dt>
+    <dd>The primary name for the sample</dd>
+
+    <dt>Name2</dt>
+    <dd>A secondary name for the sample. This can simply be the same as
+      <strong>Name</strong> above. This field <strong>MUST</strong> contain a
+      value.</dd>
+
+    <dt>Symbol</dt>
+    <dd>A symbol for the sample. Can be an empty field.</dd>
+
+    <dt>Alias</dt>
+    <dd>An alias for the sample. Can be an empty field.</dd>
+  </dl>
+</p>
+
+<form id="form-samples"
+      method="POST"
+      action="{{url_for('samples.upload_samples',
+              species_id=species.SpeciesId,
+              population_id=population.InbredSetId)}}"
+      enctype="multipart/form-data">
+  <legend class="heading">upload samples</legend>
+
+  <div class="form-group">
+    <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
+    <label class="form-label">species:</label>
+    <span class="form-text">{{species.SpeciesName}} [{{species.MenuName}}]</span>
+  </div>
+
+  <div class="form-group">
+    <input type="hidden" name="inbredset_id" value="{{population.InbredSetId}}" />
+    <label class="form-label">grouping/population:</label>
+    <span class="form-text">{{population.Name}} [{{population.FullName}}]</span>
+  </div>
+
+  <div class="form-group">
+    <label for="file-samples" class="form-label">select file</label>
+    <input type="file" name="samples_file" id="file:samples"
+	   accept="text/csv, text/tab-separated-values"
+	   class="form-control" />
+  </div>
+
+  <div class="form-group">
+    <label for="select:separator" class="form-label">field separator</label>
+    <select id="select:separator"
+	    name="separator"
+	    required="required"
+	    class="form-control">
+      <option value="">Select separator for your file: (default is comma)</option>
+      <option value="&#x0009;">TAB</option>
+      <option value="&#x0020;">Space</option>
+      <option value=",">Comma</option>
+      <option value=";">Semicolon</option>
+      <option value="other">Other</option>
+    </select>
+    <input id="txt:separator"
+	   type="text"
+	   name="other_separator"
+	   class="form-control" />
+    <small class="form-text text-muted">
+      If you select '<strong>Other</strong>' for the field separator value,
+      enter the character that separates the fields in your CSV file in the form
+      field below.
+    </small>
+  </div>
+
+  <div class="form-group form-check">
+    <input id="chk:heading"
+	   type="checkbox"
+	   name="first_line_heading"
+	   class="form-check-input" />
+    <label for="chk:heading" class="form-check-label">
+      first line is a heading?</label>
+    <small class="form-text text-muted">
+      Select this if the first line in your file contains headings for the
+      columns.
+    </small>
+  </div>
+
+  <div class="form-group">
+    <label for="txt:delimiter" class="form-label">field delimiter</label>
+    <input id="txt:delimiter"
+	   type="text"
+	   name="field_delimiter"
+	   maxlength="1"
+	   class="form-control" />
+    <small class="form-text text-muted">
+      If there is a character delimiting the string texts within particular
+      fields in your CSV, provide the character here. This can be left blank if
+      no such delimiters exist in your file.
+    </small>
+  </div>
+
+  <button type="submit"
+	  class="btn btn-primary">upload samples file</button>
+</form>
+
+<table id="tbl:samples-preview" class="table">
+  <caption class="heading">preview content</caption>
+
+  <thead>
+    <tr>
+      <th>Name</th>
+      <th>Name2</th>
+      <th>Symbol</th>
+      <th>Alias</th>
+    </tr>
+  </thead>
+
+  <tbody>
+    <tr id="default-row">
+      <td colspan="4">
+	Please make some selections to preview the data.</td>
+    </tr>
+  </tbody>
+</table>
+
+{%endblock%}
+
+
+{%block javascript%}
+<script src="/static/js/upload_samples.js" type="text/javascript"></script>
+{%endblock%}
diff --git a/uploader/templates/samples/upload-success.html b/uploader/templates/samples/upload-success.html
new file mode 100644
index 0000000..cb745c3
--- /dev/null
+++ b/uploader/templates/samples/upload-success.html
@@ -0,0 +1,18 @@
+{%extends "base.html"%}
+{%from "cli-output.html" import cli_output%}
+
+{%block title%}Job Status{%endblock%}
+
+{%block contents%}
+<h1 class="heading">{{job.job_name}}</h2>
+
+<p>
+<strong>status</strong>:
+<span>{{job["status"]}} ({{job.get("message", "-")}})</span><br />
+</p>
+
+<p>Successfully uploaded the samples.</p>
+
+{{cli_output(job, "stdout")}}
+
+{%endblock%}
diff --git a/uploader/templates/select_dataset.html b/uploader/templates/select_dataset.html
new file mode 100644
index 0000000..2f07de8
--- /dev/null
+++ b/uploader/templates/select_dataset.html
@@ -0,0 +1,161 @@
+{%extends "base.html"%}
+{%from "dbupdate_hidden_fields.html" import hidden_fields%}
+
+{%block title%}Select Dataset{%endblock%}
+
+{%block css%}
+<link rel="stylesheet" href="/static/css/two-column-with-separator.css" />
+{%endblock%}
+
+{%block contents%}
+<h2 class="heading">{{filename}}: select dataset</h2>
+
+<div class="row">
+  <form method="POST" action="{{url_for('dbinsert.final_confirmation')}}"
+	id="select-dataset-form" class="two-col-sep-col1">
+    <legend class="heading">choose existing dataset</legend>
+    {{hidden_fields(
+    filename, filetype, species=species, genechipid=genechipid,
+    studyid=studyid, totallines=totallines)}}
+
+    <div class="form-group">
+      <label for="datasetid" class="form-label">dataset:</label>
+      <select id="datasetid" name="datasetid" class="form-control"
+	      {%if datasets | length == 0:%}
+	      disabled="disabled"
+	      {%endif%}>
+	{%for dataset in datasets:%}
+	<option value="{{dataset['Id']}}">
+	  [{{dataset["Name"]}}] - {{dataset["FullName"]}}
+	</option>
+	{%endfor%}
+      </select>
+    </div>
+
+    <button type="submit" class="btn btn-primary"
+	    {%if datasets | length == 0:%}
+	    disabled="disabled"
+	    {%endif%} />update database</button>
+</form>
+</div>
+
+<div class="row">
+  <p class="two-col-sep-separator">OR</p>
+</div>
+
+<div class="row">
+  <form method="POST" id="create-dataset-form"
+	action="{{url_for('dbinsert.create_dataset')}}"
+	class="two-col-sep-col2">
+    <legend class="heading">create new dataset</legend>
+    {{hidden_fields(
+    filename, filetype, species=species, genechipid=genechipid,
+    studyid=studyid, totallines=totallines)}}
+
+    {%with messages = get_flashed_messages(with_categories=true)%}
+    {%if messages:%}
+    <ul>
+      {%for category, message in messages:%}
+      <li class="{{category}}">{{message}}</li>
+      {%endfor%}
+    </ul>
+    {%endif%}
+    {%endwith%}
+
+    <div class="form-group">
+      <label for="avgid" class="form-label">average:</label>
+      <select id="avgid" name="avgid" required="required" class="form-control">
+	<option value="">Select averaging method</option>
+	{%for method in avgmethods:%}
+	<option value="{{method['AvgMethodId']}}"
+		{%if avgid is defined and method['AvgMethodId'] | int == avgid | int%}
+		selected="selected"
+		{%endif%}>
+	  {{method["Name"]}}
+	</option>
+	{%endfor%}
+      </select>
+    </div>
+
+    <div class="form-group">
+      <label for="datasetname" class="form-label">name:</label>
+      <input id="datasetname" name="datasetname" type="text"
+	     class="form-control"
+	     {%if datasetname is defined %}
+	     value="{{datasetname}}"
+	     {%endif%} />
+    </div>
+
+    <div class="form-group">
+      <label for="datasetname2" class="form-label">name 2:</label>
+      <input id="datasetname2" name="datasetname2" type="text"
+	     required="required" class="form-control"
+	     {%if datasetname2 is defined %}
+	     value="{{datasetname2}}"
+	     {%endif%} />
+    </div>
+
+    <div class="form-group">
+      <label for="datasetfullname" class="form-label">full name:</label>
+      <input id="datasetfullname" name="datasetfullname" type="text"
+	     required="required" class="form-control"
+	     {%if datasetfullname is defined %}
+	     value="{{datasetfullname}}"
+	     {%endif%} />
+    </div>
+
+    <div class="form-group">
+      <label for="datasetshortname" class="form-label">short name:</label>
+      <input id="datasetshortname" name="datasetshortname" type="text"
+	     required="required" class="form-control"
+	     {%if datasetshortname is defined %}
+	     value="{{datasetshortname}}"
+	     {%endif%} />
+    </div>
+
+    <div class="form-group">
+      <label for="datasetpublic" class="form-label">public:</label>
+      <input id="datasetpublic" name="datasetpublic" type="number"
+	     required="required" min="0" max="2"
+	     {%if datasetpublic is defined %}
+	     value="{{datasetpublic | int}}"
+	     {%else%}
+	     value="0"
+	     {%endif%}
+	     class="form-control" />
+    </div>
+
+    <div class="form-group">
+      <label for="datasetconfidentiality">confidentiality:</label>
+      <input id="datasetconfidentiality" name="datasetconfidentiality"
+	     type="number" required="required" min="0" max="2"
+	     {%if datasetconfidentiality is defined %}
+	     value="{{datasetconfidentiality | int}}"
+	     {%else%}
+	     value="0"
+	     {%endif%}
+	     class="form-control" />
+    </div>
+
+    <div class="form-group">
+      <label for="datasetdatascale" class="form-label">data scale:</label>
+      <select id="datasetdatascale" name="datasetdatascale" class="form-control">
+	<option value="">None</option>
+	{%for dscale in datascales:%}
+	<option value="{{dscale}}"
+		{%if datasetdatascale is defined and dscale == datasetdatascale%}
+		selected="selected"
+		{%elif dscale == "log2":%}
+		selected="selected"
+		{%endif%}>
+	  {{dscale}}
+	</option>
+	{%endfor%}
+      </select>
+    </div>
+
+    <button type="submit" class="btn btn-primary">create new dataset</button>
+  </form>
+</div>
+
+{%endblock%}
diff --git a/uploader/templates/select_platform.html b/uploader/templates/select_platform.html
new file mode 100644
index 0000000..d9bc68f
--- /dev/null
+++ b/uploader/templates/select_platform.html
@@ -0,0 +1,82 @@
+{%extends "base.html"%}
+
+{%block title%}Select Dataset{%endblock%}
+
+{%block contents%}
+<h2 class="heading">{{filename}}: select platform</h2>
+
+<div class="row">
+  <form method="POST" action="{{url_for('dbinsert.select_study')}}"
+        id="select-platform-form" data-genechips="{{genechips_data}}">
+    <input type="hidden" name="filename" value="{{filename}}" />
+    <input type="hidden" name="filetype" value="{{filetype}}" />
+    <input type="hidden" name="totallines" value="{{totallines}}" />
+
+    <div class="form-group">
+      <label for="species" class="form-label">species</label>
+      <select id="species" name="species" class="form-control">
+        {%for row in species:%}
+        <option value="{{row['SpeciesId']}}"
+	        {%if row["Name"] == default_species:%}
+	        selected="selected"
+	        {%endif%}>
+	  {{row["MenuName"]}}
+        </option>
+        {%endfor%}
+      </select>
+    </div>
+
+    <table id="genechips-table" class="table">
+      <caption>select platform</caption>
+      <thead>
+        <tr>
+	  <th>Select</th>
+	  <th>GeneChip ID</th>
+	  <th>GeneChip Name</th>
+        </tr>
+      </thead>
+
+      <tbody>
+        {%for chip in genechips:%}
+        <tr>
+	  <td>
+	    <input type="radio" name="genechipid" value="{{chip['GeneChipId']}}"
+		   required="required" />
+	  </td>
+	  <td>{{chip["GeneChipId"]}}</td>
+	  <td>{{chip["GeneChipName"]}}</td>
+        </tr>
+        {%else%}
+        <tr>
+	  <td colspan="5">No chips found for selected species</td>
+        </tr>
+        {%endfor%}
+      </tbody>
+    </table>
+
+    <button type="submit" class="btn btn-primary">submit platform</button>
+  </form>
+</div>
+{%endblock%}
+
+{%block javascript%}
+<script type="text/javascript" src="/static/js/utils.js"></script>
+<script type="text/javascript" src="/static/js/select_platform.js"></script>
+<script type="text/javascript">
+  document.getElementById(
+      "species").addEventListener("change", update_genechips);
+  document.getElementById(
+      "genechips-table").getElementsByTagName(
+	  "tbody")[0].addEventListener(
+	      "click",
+	      function(event) {
+		  if(event.target.tagName.toLowerCase() == "td") {
+		      return select_row_radio(event.target.parentElement);
+		  }
+		  if(event.target.tagName.toLowerCase() == "td") {
+		      return select_row_radio(event.target);
+		  }
+		  return false;
+	      });
+</script>
+{%endblock%}
diff --git a/uploader/templates/select_species.html b/uploader/templates/select_species.html
new file mode 100644
index 0000000..3b1a8a9
--- /dev/null
+++ b/uploader/templates/select_species.html
@@ -0,0 +1,92 @@
+{%extends "base.html"%}
+{%from "flash_messages.html" import flash_messages%}
+{%from "upload_progress_indicator.html" import upload_progress_indicator%}
+
+{%block title%}expression data: select species{%endblock%}
+
+{%block contents%}
+{{upload_progress_indicator()}}
+
+<h2 class="heading">expression data: select species</h2>
+
+<div class="row">
+  <form action="{{url_for('entry.upload_file')}}"
+        method="POST"
+        enctype="multipart/form-data"
+        id="frm-upload-expression-data">
+    <legend class="heading">upload expression data</legend>
+    {{flash_messages("error-expr-data")}}
+
+    <div class="form-group">
+      <label for="select_species01" class="form-label">Species</label>
+      <select id="select_species01"
+	      name="speciesid"
+	      required="required"
+              class="form-control">
+        <option value="">Select species</option>
+        {%for aspecies in species%}
+        <option value="{{aspecies.SpeciesId}}">{{aspecies.MenuName}}</option>
+        {%endfor%}
+      </select>
+    </div>
+
+    <div class="form-group">
+      <legend class="heading">file type</legend>
+
+      <div class="form-check">
+        <input type="radio" name="filetype" value="average" id="filetype_average"
+	       required="required" class="form-check-input" />
+        <label for="filetype_average" class="form-check-label">average</label>
+      </div>
+
+      <div class="form-check">
+        <input type="radio" name="filetype" value="standard-error"
+	       id="filetype_standard_error" required="required"
+	       class="form-check-input" />
+        <label for="filetype_standard_error" class="form-check-label">
+          standard error
+        </label>
+      </div>
+    </div>
+
+    <div class="form-group">
+      <span id="no-file-error" class="alert-danger" style="display: none;">
+        No file selected
+      </span>
+      <label for="file_upload" class="form-label">select file</label>
+      <input type="file" name="qc_text_file" id="file_upload"
+	     accept="text/plain, text/tab-separated-values, application/zip"
+	     class="form-control"/>
+    </div>
+
+    <button type="submit"
+            class="btn btn-primary"
+            data-toggle="modal"
+            data-target="#upload-progress-indicator">upload file</button>
+  </form>
+</div>
+{%endblock%}
+
+
+{%block javascript%}
+<script type="text/javascript" src="static/js/upload_progress.js"></script>
+<script type="text/javascript">
+  function setup_formdata(form) {
+      var formdata = new FormData();
+      formdata.append(
+	  "speciesid",
+	  form.querySelector("#select_species01").value)
+      formdata.append(
+	  "qc_text_file",
+	  form.querySelector("input[type='file']").files[0]);
+      formdata.append(
+	  "filetype",
+	  selected_filetype(
+	      Array.from(form.querySelectorAll("input[type='radio']"))));
+      return formdata;
+  }
+
+  setup_upload_handlers(
+      "frm-upload-expression-data", make_data_uploader(setup_formdata));
+</script>
+{%endblock%}
diff --git a/uploader/templates/select_study.html b/uploader/templates/select_study.html
new file mode 100644
index 0000000..648ad4c
--- /dev/null
+++ b/uploader/templates/select_study.html
@@ -0,0 +1,108 @@
+{%extends "base.html"%}
+{%from "dbupdate_hidden_fields.html" import hidden_fields%}
+
+{%block title%}Select Dataset{%endblock%}
+
+{%block css%}
+<link rel="stylesheet" href="/static/css/two-column-with-separator.css" />
+{%endblock%}
+
+{%block contents%}
+<h2 class="heading">{{filename}}: select study</h2>
+
+<div class="row">
+  <form method="POST" action="{{url_for('dbinsert.select_dataset')}}"
+	id="select-platform-form" data-genechips="{{genechips_data}}"
+	class="two-col-sep-col1">
+    <legend class="heading">Select from existing study</legend>
+    {{hidden_fields(filename, filetype, species=species, genechipid=genechipid,
+    totallines=totallines)}}
+
+    <div class="form-group">
+      <label class="form-label" for="study">study:</label>
+      <select id="study" name="studyid" class="form-control">
+	{%for study in studies:%}
+	<option value="{{study['Id']}}">{{study["Name"]}}</option>
+	{%endfor%}
+      </select>
+    </div>
+
+    <button type="submit"
+	    class="btn btn-primary"
+	    {%if studies | length == 0:%}
+	    disabled="disabled"
+	    {%endif%} />submit selected study</button>
+</form>
+</div>
+
+<div class="row">
+  <p class="two-col-sep-separator">OR</p>
+</div>
+
+<div class="row">
+  <form method="POST" action="{{url_for('dbinsert.create_study')}}"
+	id="select-platform-form" data-genechips="{{genechips_data}}"
+	class="two-col-sep-col2">
+    {%with messages = get_flashed_messages(with_categories=true)%}
+    {%if messages:%}
+    <ul>
+      {%for category, message in messages:%}
+      <li class="{{category}}">{{message}}</li>
+      {%endfor%}
+    </ul>
+    {%endif%}
+    {%endwith%}
+    <legend class="heading">Create new study</legend>
+    {{hidden_fields(filename, filetype, species=species, genechipid=genechipid,
+    totallines=totallines)}}
+
+    <div class="form-group">
+      <label class="form-label" for="studyname">name:</label>
+      <input type="text" id="studyname" name="studyname" class="form-control"
+	     required="required"
+	     {%if studyname:%}
+	     value="{{studyname}}"
+	     {%endif%} />
+    </div>
+
+    <div class="form-group">
+      <label class="form-label" for="group">group:</label>
+      <select id="group" name="inbredsetid" class="form-control"
+	      required="required">
+	<option value="">Select group</option>
+	{%for family in groups:%}
+	<optgroup label="{{family}}">
+	  {%for group in groups[family]:%}
+	  <option value="{{group['InbredSetId']}}"
+		  {%if group["InbredSetId"] == selected_group:%}
+		  selected="selected"
+		  {%endif%}>
+	    {{group["FullName"]}}
+	  </option>
+	  {%endfor%}
+	</optgroup>
+	{%endfor%}
+      </select>
+    </div>
+
+    <div class="form-group">
+      <label class="form-label" for="tissue">tissue:</label>
+      <select id="tissue" name="tissueid" class="form-control"
+	      required="required">
+	<option value="">Select type</option>
+	{%for tissue in tissues:%}
+	<option value="{{tissue['TissueId']}}"
+		{%if tissue["TissueId"] == selected_tissue:%}
+		selected="selected"
+		{%endif%}>
+	  {{tissue["Name"]}}
+	</option>
+	{%endfor%}
+      </select>
+    </div>
+
+    <button type="submit" class="btn btn-primary">create study</button>
+  </form>
+</div>
+
+{%endblock%}
diff --git a/uploader/templates/stdout_output.html b/uploader/templates/stdout_output.html
new file mode 100644
index 0000000..85345a9
--- /dev/null
+++ b/uploader/templates/stdout_output.html
@@ -0,0 +1,8 @@
+{%macro stdout_output(job)%}
+
+<h4>STDOUT Output</h4>
+<div class="cli-output">
+  <pre>{{job.get("stdout", "")}}</pre>
+</div>
+
+{%endmacro%}
diff --git a/uploader/templates/unhandled_exception.html b/uploader/templates/unhandled_exception.html
new file mode 100644
index 0000000..6e6a051
--- /dev/null
+++ b/uploader/templates/unhandled_exception.html
@@ -0,0 +1,21 @@
+{%extends "base.html"%}
+
+{%block title%}System Error{%endblock%}
+
+{%block css%}
+<link rel="stylesheet" href="/static/css/two-column-with-separator.css" />
+{%endblock%}
+
+{%block contents%}
+<p>
+  An error has occured, and your request has been aborted. Please notify the
+  administrator to try and get this sorted.
+</p>
+<p>
+  Provide the following information to help the administrator figure out and fix
+  the issue:<br />
+  <hr /><br />
+  {{trace}}
+  <hr /><br />
+</p>
+{%endblock%}
diff --git a/uploader/templates/upload_progress_indicator.html b/uploader/templates/upload_progress_indicator.html
new file mode 100644
index 0000000..e274e83
--- /dev/null
+++ b/uploader/templates/upload_progress_indicator.html
@@ -0,0 +1,35 @@
+{%macro upload_progress_indicator()%}
+<div id="upload-progress-indicator" class="modal fade" tabindex="-1" role="dialog">
+  <div class="modal-dialog" role="document">
+    <div class="modal-content">
+      <div class="modal-header">
+        <h3 class="modal-title">Uploading file</h3>
+      </div>
+
+      <div class="modal-body">
+        <form id="frm-cancel-upload" style="border-style: none;">
+          <div class="form-group">
+            <span id="progress-filename" class="form-text">No file selected!</span>
+            <progress id="progress-bar" value="0" max="100" class="form-control">
+              0</progress>
+          </div>
+
+          <div class="form-group">
+            <span class="form-text text-muted" id="progress-text">
+              Uploading 0%</span>
+            <span class="form-text text-muted" id="progress-extra-text">
+              Processing</span>
+          </div>
+        </form>
+      </div>
+
+      <div class="modal-footer">
+        <button id="btn-cancel-upload"
+                type="button"
+                class="btn btn-danger"
+                data-dismiss="modal">Cancel</button>
+      </div>
+    </div>
+  </div>
+</div>
+{%endmacro%}
diff --git a/uploader/templates/worker_failure.html b/uploader/templates/worker_failure.html
new file mode 100644
index 0000000..b65b140
--- /dev/null
+++ b/uploader/templates/worker_failure.html
@@ -0,0 +1,24 @@
+{%extends "base.html"%}
+
+{%block title%}Worker Failure{%endblock%}
+
+{%block contents%}
+<h1 class="heading">Worker Failure</h1>
+
+<p>
+  There was a critical failure launching the job to parse your file.
+  This is our fault and (probably) has nothing to do with the file you uploaded.
+</p>
+
+<p>
+  Please notify the developers of this issue when you encounter it,
+  providing the link to this page, or the information below.
+</p>
+
+<h4>Debugging Information</h4>
+
+<ul>
+  <li><strong>job id</strong>: {{job_id}}</li>
+</ul>
+
+{%endblock%}