changeset 142:6d3fb8592ff4

merged
author Benoît Allard <benoit.allard@greenbone.net>
date Tue, 28 Oct 2014 09:55:28 +0100
parents ce39a5267998 (diff) 81b6b71de62f (current diff)
children 97fafc195ca0
files
diffstat 16 files changed, 335 insertions(+), 189 deletions(-) [+]
line wrap: on
line diff
--- a/CHANGES	Tue Oct 28 09:54:00 2014 +0100
+++ b/CHANGES	Tue Oct 28 09:55:28 2014 +0100
@@ -1,3 +1,12 @@
+Farol next
+==========
+
+Main changes since 0.2.2:
+-------------------------
+* Improve styling of the welcome page
+* Add more client-side input validation (dates, version numbers)
+* Add possibility to delete the current document
+
 Farol 0.2.2 (2014-10-17)
 ========================
 
--- a/farol/controller.py	Tue Oct 28 09:54:00 2014 +0100
+++ b/farol/controller.py	Tue Oct 28 09:55:28 2014 +0100
@@ -35,10 +35,10 @@
 except ImportError:
     from farolluz.py2 import FixedTimeZone as timezone
 
-from flask import request
+from flask import request, flash
 
 from farolluz.cvrf import CVRFNote, CVRFReference, CVRFAcknowledgment
-from farolluz.parsers.cvrf import parseDate as parseXMLDate
+from farolluz.parsers.cvrf import parseDate as parseXMLDate, parseVersion as parseXMLVersion
 
 def split_fields(field, separator=','):
     if not field:
@@ -89,5 +89,15 @@
     except AttributeError: pass
     # Absorb AttributeError, and try to parse it a second time ...
     m = re.match('(\d{4})-(\d{2})-(\d{2})', string)
+    if m is None:
+        flash('Cannot parse date: "%s"' % string, 'warning')
+        return None
     return datetime(int(m.group(1)), int(m.group(2)), int(m.group(3)),
                     tzinfo=timezone(timedelta(hours=0, minutes=0)))
+
+def parseVersion(string):
+    """ An extended version, one that doesn't throw exceptions """
+    try: return parseXMLVersion(string)
+    except ValueError:
+        flash('Cannot parse Version string: "%s"' % string)
+        return None
--- a/farol/document.py	Tue Oct 28 09:54:00 2014 +0100
+++ b/farol/document.py	Tue Oct 28 09:55:28 2014 +0100
@@ -25,7 +25,6 @@
 from flask import (Blueprint, render_template, abort, redirect, request,
     url_for, flash)
 
-from farolluz.parsers.cvrf import parseVersion
 from farolluz.cvrf import (CVRFNote, CVRFReference, CVRFPublisher,
     CVRFTracking, CVRFTrackingID, CVRFGenerator, CVRFRevision,
     CVRFAggregateSeverity)
@@ -34,8 +33,8 @@
 from .controller import (update_note_from_request, create_note_from_request,
     update_reference_from_request, create_reference_from_request,
     update_acknowledgment_from_request, create_acknowledgment_from_request,
-    split_fields, parseDate)
-from .session import document_required, get_current
+    split_fields, parseDate, parseVersion)
+from .session import document_required, get_current, del_current
 
 
 document = Blueprint('document', __name__)
@@ -46,6 +45,11 @@
     cvrf = get_current()
     return render_template('document/view.j2', cvrf=cvrf)
 
+@document.route('/delete', methods=['POST'])
+def delete():
+    del_current()
+    return redirect(url_for('welcome'))
+
 @document.route('/title/edit', methods=['GET', 'POST'])
 @document_required
 def edit_title():
@@ -87,7 +91,9 @@
     aliases = split_fields(request.form['id_aliases'])
     tracking._identification._aliases = aliases
     tracking._status = request.form['status']
-    tracking._version = parseVersion(request.form['version'])
+    version = parseVersion(request.form['version'])
+    if version is not None:
+        tracking._version = version
     tracking._initialDate = parseDate(request.form['initial'])
     tracking._currentDate = parseDate(request.form['current'])
     if wasNone:
@@ -116,7 +122,9 @@
     if request.method != 'POST':
         return render_template('document/edit_revision.j2', number='.'.join('%s'%v for v in revision._number), date=revision._date, description=revision._description, action='Update')
 
-    revision._number = parseVersion(request.form['number'])
+    version = parseVersion(request.form['number'])
+    if version is not None:
+        revision._number = version
     revision._date = parseDate(request.form['date'])
     revision._description = request.form['description']
     return redirect(url_for('.view'))
@@ -133,7 +141,7 @@
         version = version[:-1] + (version[-1] + 1,)
         return render_template('document/edit_revision.j2', number='.'.join("%d"%v for v in version), date=utcnow(), action='Add')
 
-    version = parseVersion(request.form['number'])
+    version = parseVersion(request.form['number']) or (0,0)
     date = parseDate(request.form['date'])
     revision = CVRFRevision(version, date, request.form['description'])
     tracking.addRevision(revision)
--- a/farol/main.py	Tue Oct 28 09:54:00 2014 +0100
+++ b/farol/main.py	Tue Oct 28 09:55:28 2014 +0100
@@ -38,7 +38,7 @@
 
 import flask
 from flask import (Flask, request, render_template, redirect, url_for, flash,
-     make_response)
+     make_response, abort)
 from werkzeug import secure_filename
 
 from . import __version__, cache
@@ -87,9 +87,26 @@
 def makeId(string):
     return secure_filename(string)
 
+@app.errorhandler(400)
+@app.errorhandler(404)
+@app.errorhandler(405)
+@app.errorhandler(500)
+def error_page(error):
+    return render_template('error.j2', e=error), getattr(error, 'code', 500)
+
+@app.route('/500')
+def boom():
+    abort(500)
+
 @app.route('/')
 def welcome():
-    return render_template('welcome.j2')
+    return render_template('welcome.j2',
+        version=__version__,
+        imports=[('New', 100), ('CVRF', 100)],
+        exports=[('CVRF', 100), ('OpenVAS NASL from RHSA', 85), ('OVAL', 5) ],
+        use_cases=[('Create a security advisory and publish as CVRF', 100),
+                   ('Edit a security advisory in CVRF format', 100)]
+    )
 
 def set_url(url):
     try: content = urlopen(url).read()
@@ -156,9 +173,6 @@
         set_url(request.form['url'])
     elif 'local' in request.files:
         upload = request.files['local']
-        if not upload.filename.endswith('.xml'):
-            flash('Uploaded files should end in .xml', 'danger')
-            return redirect(url_for('new'))
         fpath = os.path.join(app.instance_path, 'tmp',
                              secure_filename(upload.filename))
         if not os.path.exists(os.path.dirname(fpath)):
--- a/farol/producttree.py	Tue Oct 28 09:54:00 2014 +0100
+++ b/farol/producttree.py	Tue Oct 28 09:55:28 2014 +0100
@@ -62,7 +62,9 @@
 @producttree.route('/delete', methods=['POST'])
 @producttree_required
 def delete():
-    # XXX: We should first check if no PID and GID is used ...
+    if not cvrf.isProductTreeOrphan():
+        flash('Not deleting the Product Tree, some Products are mentionned in the document', 'danger')
+        return redirect(url_for('.view'))
     get_current()._producttree = None
     return redirect(url_for('document.view'))
 
@@ -142,7 +144,7 @@
     cvrf = get_current()
     try:
         product = cvrf.getProductForID(productid)
-    except IndexError:
+    except KeyError:
         abort(404)
     return render_template('producttree/view_product.j2',
         product=product, groups=[g for g in cvrf._producttree._groups if productid in g._productids],
@@ -194,6 +196,9 @@
             # Link again
             product.link(ptree)
 
+    if (request.form['productid'] != product._productid) and not cvrf.isProductOrphan(product._productid):
+        flash('Also updating the ProductID for %s in this Document' % request.form['name'], 'info')
+        cvrf.changeProductID(product._productid, request.form['productid'])
     product._productid = request.form['productid']
     product._name = request.form['name']
     product._cpe = request.form['cpe'] or None
@@ -237,6 +242,9 @@
         product = cvrf.getProductForID(productid)
     except KeyError:
         abort(404)
+    if not cvrf.isProductOrphan(product._productid):
+        flash('Not deleting the Product, it is used in the Document.', 'danger')
+        return redirect(url_for('.view_product', productid=product._productid))
     product.unlink()
     ptree._products.remove(product)
     del product
@@ -308,13 +316,17 @@
 @document_required
 @producttree_required
 def edit_group(groupid):
+    cvrf = get_current()
     try:
-        group = get_current().getGroupForID(groupid)
+        group = cvrf.getGroupForID(groupid)
     except KeyError:
         abort(404)
     if request.method != 'POST':
         return render_template('producttree/edit_group.j2', groupid=group._groupid, description=group._description, productids=group._productids)
 
+    if (request.form['groupid'] != group._groupid) and not cvrf.isGroupOrphan(group._groupid):
+        flash('Also updating the groupid in the whole document.', 'info')
+        cvrf.changeGroupID(group._groupid, request.form['groupid'])
     group._groupid = request.form['groupid']
     group.setDescription(request.form['description'] or None)
     group._productids = []
@@ -347,6 +359,10 @@
         flash('Group not found', 'danger')
         abort(404)
 
+    if not cvrf.isGroupOrphan(group._groupid):
+        flash('Not deleting group, it is mentionned in the document.', 'danger')
+        return redirect(url_for('.view'))
+
     cvrf._producttree._groups.remove(group)
     return redirect(url_for('.view'))
 
--- a/farol/templates/about.j2	Tue Oct 28 09:54:00 2014 +0100
+++ b/farol/templates/about.j2	Tue Oct 28 09:55:28 2014 +0100
@@ -30,14 +30,12 @@
 
 {% block content %}
 <div class="page-header">
-<h1>Farol <small>A Security Advisory Management (Web) Platform</small></h1>
+<h1>Farol <small>The Security Advisory Management Platform</small></h1>
 </div>
 
 <div>
-  <p>Security Advisories have existed for ever. Whenever someone discovered a danger, a vulnerability, he immediately started spreading the words about it.</p>
-  <p>In the IT World, each Party involved with security vulnerabilities have its own way of dealing with the matter, and although standards exist they aren't used much to their full extend.<p>
-  <p>This Platform is an attempt at bringing all those worlds together.<p>
-  <p>In the current version, Advisories not currently saved are kept in memory of the running process. If the process terminates, and they are not saved, documents are lost.</p>
+  <p>This web platform offers to review, create, edit and transform security advisories supporting various input and output formats.</p>
+  <p>During your session the advisory is stored in a cache from which you should save your changes to your local file system.</p>
   {% if config.DEBUG and not config.DEBUG_SURE %}
     <hr>
     <h3 id="debug">Debug Mode</h3>
--- a/farol/templates/base.j2	Tue Oct 28 09:54:00 2014 +0100
+++ b/farol/templates/base.j2	Tue Oct 28 09:55:28 2014 +0100
@@ -23,107 +23,83 @@
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 -#}
 
+{% extends "layout.j2" %}
+
 {% from "macros.j2" import modal, POST_button -%}
 
-<!doctype html>
+{% block navbar %}
+  {% if has_current %}
+    <li{% if active == 'document' %} class="active"{% endif %}><a href="{{ url_for('document.view') }}">Document</a></li>
+  {% endif %}
+  {% if products %}
+    <li class="dropdown{{ ' active' if active == 'product' }}">
+      <a href="#" class="dropdown-toggle" data-toggle="dropdown">Products <span class="caret"></span></a>
+      <ul class="dropdown-menu" role="menu">
+        <li role="presentation">
+          <a role="menuitem" tabindex="-1" href="{{ url_for('producttree.view') }}">View Product Tree</a>
+        </li>
+        <li role="presentation" class="divider"></li>
+        {% for name, productid in products | sort %}
+          <li><a href="{{ url_for('producttree.view_product', productid=productid) }}">{{ name }}</a></li>
+        {% endfor %}
+      </ul>
+    </li>
+  {% endif %}
+  {% if vulnerabilities %}
+    <li class="dropdown{{ ' active' if active == 'vulnerability' }}">
+      <a href="#" class="dropdown-toggle" data-toggle="dropdown">Vulnerabilities <span class="caret"></span></a>
+      <ul class="dropdown-menu" role="menu">
+        {% for name, ord in vulnerabilities %}
+          <li><a href="{{ url_for('vulnerability.view', ordinal=ord) }}">{{ name }}</a></li>
+        {% endfor %}
+      </ul>
+    </li>
+  {% endif %}
+  {% if has_current %}
+    <li>
+      {% if error %}
+        <p class="navbar-text">Document is <a id="error-popover" href="#" tabindex="0" class="navbar-link" data-toggle="popover" data-trigger="focus" data-placement="bottom" title="First Error:" data-content="{{ error }}"><strong>invalid</strong></a> <span class="badge progress-bar-danger"><strong>&#x2717;</strong></span></p>
+      {% else %}
+        <p class="navbar-text">Document looks valid <span class="badge progress-bar-success"><strong>&#x2713;</strong></span></p>
+      {% endif %}
+    </li>
+  {% endif %}
+{% endblock %}
 
-<html lang="en">
-<head>
-  <meta charset="utf-8">
-  <title>Farol - {% block title %}{% endblock %}</title>
-  <link rel="stylesheet" href="{{ url_for('static', filename='bootstrap.css') }}">
-  <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
-</head>
-<body>
-  <script src="{{ url_for('static', filename='jquery-2.1.1.min.js') }}"></script>
-  <script src="{{ url_for('static', filename='bootstrap.js') }}"></script>
-  <nav class="navbar navbar-inverse" role="navigation">
-    <div class="container">
-      {# Brand and toggle get grouped for better mobile display #}
-      <div class="navbar-header">
-        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
-          <span class="sr-only">Toggle navigation</span>
-          <span class="icon-bar"></span>
-          <span class="icon-bar"></span>
-          <span class="icon-bar"></span>
-        </button>
-        <a class="navbar-brand" href="{{ url_for('welcome') }}" title="A Security Advisory Management Platform">Farol</a>
-      </div>
-
-      {# Collect the nav links, forms, and other content for toggling #}
-      <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
-        <ul class="nav navbar-nav">
-          <li{% if active == 'new' %} class="active"{% endif %}><a href="{{ url_for('new') }}">New</a></li>
-          {% if has_current %}
-          <li{% if active == 'document' %} class="active"{% endif %}><a href="{{ url_for('document.view') }}">Document</a></li>
-          {% endif %}
-          {% if products %}
-          <li class="dropdown{{ ' active' if active == 'product' }}">
-            <a href="#" class="dropdown-toggle" data-toggle="dropdown">Products <span class="caret"></span></a>
-            <ul class="dropdown-menu" role="menu">
-              <li role="presentation">
-                <a role="menuitem" tabindex="-1" href="{{ url_for('producttree.view') }}">View Product Tree</a>
-              </li>
-              <li role="presentation" class="divider"></li>
-              {% for name, productid in products %}
-                <li><a href="{{ url_for('producttree.view_product', productid=productid) }}">{{ name }}</a></li>
-              {% endfor %}
-            </ul>
+{% block navbar_right %}
+  {% if has_current %}
+    <li class="dropdown{{ ' active' if active == 'render' }}">
+      <a href="#" class="dropdown-toggle" data-toggle="dropdown">Export <span class="caret"></span></a>
+      <ul class="dropdown-menu" role="menu">
+        {% for format in ('cvrf', 'nasl', 'oval') %}<li><a href="{{ url_for('render', format_=format)}}">as {{ format | upper }}</a></li>{% endfor %}
+      </ul>
+    </li>
+  {% endif %}
+  {% if caching %}
+    <li class="dropdown">
+      <a href="#" class="dropdown-toggle" data-toggle="dropdown">Cache <span class="caret"></span></a>
+      <ul class="dropdown-menu" role="menu">
+        <li role="presentation"{{ ' class="disabled"' if not has_current }}>
+          <a role="menuitem" tabindex="-1" href="{{ url_for('cache.save') }}">Save {{ current_id }}</a>
+        </li>
+        <li role="presentation" class="divider"></li>
+        {% for element in cache | sort %}
+          <li role="presentation">
+            {% if has_current %}
+              <a href="#{{element}}_modal" data-toggle="modal">Load {{ element }}</a>
+            {% else %}
+              {% call(selector) POST_button(url_for('cache.load', element=element), out=True) %}
+                <a role="menuitem" href="#" onclick="{{ selector }}.submit();return false;">Load {{ element }}</a>
+              {% endcall %}
+            {% endif %}
           </li>
-          {% endif %}
-          {% if vulnerabilities %}
-          <li class="dropdown{{ ' active' if active == 'vulnerability' }}">
-            <a href="#" class="dropdown-toggle" data-toggle="dropdown">Vulnerabilities <span class="caret"></span></a>
-            <ul class="dropdown-menu" role="menu">
-              {% for name, ord in vulnerabilities %}
-                <li><a href="{{ url_for('vulnerability.view', ordinal=ord) }}">{{ name }}</a></li>
-              {% endfor %}
-            </ul>
-          </li>
-          {% endif %}
-        </ul>
-        {% if has_current %}
-          {% if error %}
-            <p class="navbar-text">Document is <a id="error-popover" href="#" tabindex="0" class="navbar-link" data-toggle="popover" data-trigger="focus" data-placement="bottom" title="First Error:" data-content="{{ error }}"><strong>invalid</strong></a></p>
-          {% else %}
-            <p class="navbar-text">Document looks valid</p>
-          {% endif %}
-        {% endif %}
-        <ul class="nav navbar-nav navbar-right">
-          {% if has_current %}
-          <li class="dropdown{{ ' active' if active == 'render' }}">
-            <a href="#" class="dropdown-toggle" data-toggle="dropdown">Render <span class="caret"></span></a>
-            <ul class="dropdown-menu" role="menu">
-              {% for format in ('cvrf', 'nasl', 'oval') %}<li><a href="{{ url_for('render', format_=format)}}">as {{ format | upper }}</a></li>{% endfor %}
-            </ul>
-          </li>
-          {% endif %}
-          {% if caching %}
-          <li class="dropdown">
-            <a href="#" class="dropdown-toggle" data-toggle="dropdown">Cache <span class="caret"></span></a>
-            <ul class="dropdown-menu" role="menu">
-              <li role="presentation"{{ ' class="disabled"' if not has_current }}>
-                <a role="menuitem" tabindex="-1" href="{{ url_for('cache.save') }}">Save {{ current_id }}</a>
-              </li>
-              <li role="presentation" class="divider"></li>
-              {% for element in cache | sort %}
-                <li role="presentation">
-                  {% if has_current %}
-                    <a href="#{{element}}_modal" data-toggle="modal">Load {{ element }}</a>
-                  {% else %}
-                    {% call(selector) POST_button(url_for('cache.load', element=element), out=True) %}
-                      <a role="menuitem" href="#" onclick="{{ selector }}.submit();return false;">Load {{ element }}</a>
-                    {% endcall %}
-                  {% endif %}
-                </li>
-              {% endfor %}
-            </ul>
-          </li>
-          {% endif %}
-        </ul>
-      </div>{# /.navbar-collapse #}
-    </div>{# /.container-fluid #}
-  </nav>
+        {% endfor %}
+      </ul>
+    </li>
+  {% endif %}
+{% endblock %}
+
+{% block pre_content %}
   {% if has_current %}
     {% for element in cache %}
       {# Put the modals for the load action here #}
@@ -137,32 +113,20 @@
       {% endcall %}
     {% endfor %}
   {% endif %}
-  <div class="main container">
-    {% with messages = get_flashed_messages(with_categories=True) %}
-      {% if messages %}
-      <div class="flashes">
-      {% for category, message in messages %}
-        {% if category == 'message' %}{% set category = "info" %}{% endif %}
-        <div class="alert alert-{{ category }}">{{ message }}</div>
-      {% endfor %}
-      </div>
-      {% endif %}
-    {% endwith %}
-    <div>
-      <script>$("#error-popover").popover();</script>
-      {% block content %}{% endblock %}
+  {% with messages = get_flashed_messages(with_categories=True) %}
+    {% if messages %}
+    <div class="flashes">
+    {% for category, message in messages %}
+      {% if category == 'message' %}{% set category = "info" %}{% endif %}
+      <div class="alert alert-{{ category }}">{{ message }}</div>
+    {% endfor %}
     </div>
-    {% if config.DEBUG and not config.DEBUG_SURE %}
-    <div class="alert alert-danger"><strong>DEBUG:</strong> This application is running in debug mode. See the <a href="{{ url_for('about') }}#debug">about page</a> for more Details</div>
     {% endif %}
-  </div>
-  <footer class="footer container-fluid navbar-inverse">
-    <div class="text-center">
-      <span class="text-muted">Copyright &copy; 2014 Greenbone Networks GmbH</span>
-      |
-      <span><a href="{{ url_for('about') }}">About Farol</a></span>
-    </div>
-    <a href="http://greenbone.net/" id="greenbone" class="logo_img text-hide center-block">Greenbone Networks GmbH</a>
-  </footer>
-</body>
-</html>
+  {% endwith %}
+{% endblock %}
+
+{% block post_content %}
+  {% if config.DEBUG and not config.DEBUG_SURE %}
+    <div class="alert alert-danger"><strong>DEBUG:</strong> This application is running in debug mode. See the <a href="{{ url_for('about') }}#debug">about page</a> for more Details</div>
+  {% endif %}
+{% endblock %}
--- a/farol/templates/document/edit_revision.j2	Tue Oct 28 09:54:00 2014 +0100
+++ b/farol/templates/document/edit_revision.j2	Tue Oct 28 09:55:28 2014 +0100
@@ -30,7 +30,7 @@
 {% block content %}
 <p><strong>Revision</strong> contains all the elements required to track the evolution of a CVRF document. Each change to a CVRF document should be accompanied by <strong>Number</strong>, <strong>Date</strong>, and <strong>Description</strong> elements.</p>
 <form role="form" method="POST">
-  {% call textinput("number", "Number", "a.b.c.d", number, required=True) %}
+  {% call textinput("number", "Number", "a.b.c.d", number, required=True, regex='(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*)){0,3}') %}
   <p><strong>Number</strong> should contain the numeric version of the document. Like the <strong>Version</strong> element above, it is a numeric tokenized field of the format “nn” with up to four fields “nn.nn.nn.nn”. It is recommended that this be a monotonically increasing value. Minor revisions should be used for less-significant changes (for example, <samp>1.0.0.0</samp> to <samp>1.0.0.1</samp>). Major, actionable changes should lead to a major increase of the version number (for example, <samp>1.0</samp> to <samp>2.0</samp>).</p>
   <p>Examples of such changes include:</p>
   <ul>
--- a/farol/templates/document/edit_tracking.j2	Tue Oct 28 09:54:00 2014 +0100
+++ b/farol/templates/document/edit_tracking.j2	Tue Oct 28 09:55:28 2014 +0100
@@ -52,7 +52,7 @@
 </dl>
 <p>Issuing parties are strongly recommended to set <strong>Status</strong> to <samp>Draft</samp> when initiating a new document and to implement procedures to ensure that the status is changed to the appropriate value before the document is released.</p>
 {% endcall %}
-{% call textinput("version", "Version", value=version, required=True) %}
+{% call textinput("version", "Version", value=version, required=True, regex='(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*)){0,3}') %}
 <p>Version is a simple counter to track the version of the document. This is a numeric tokenized field of the format “nn” – “nn.nn.nn.nn”. It may be incremented in either major or minor notation to denote clearly the evolution of the content of the document. Issuing parties must ensure that this field is incremented appropriately, even for the least editorial or grammatical changes, when the field is used. It is validated using the following regular expression: <code>(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*)){0,3}</code>.</p>
 {{ examples(['1.0', '1.0.1', '1.0.0.1']) }}
 {% endcall %}
--- a/farol/templates/document/view.j2	Tue Oct 28 09:54:00 2014 +0100
+++ b/farol/templates/document/view.j2	Tue Oct 28 09:55:28 2014 +0100
@@ -169,4 +169,13 @@
     {% endcall %}
   </div>
 </div>
+<div class="pull-right"><a href="#delete_modal" data-toggle="modal" class="btn btn-danger btn-xs" role="btn">delete</a></div>
+{% call modal('delete_modal', 'Delete document') %}
+  <p>This will delete the document <strong>{{ current_id }}</strong>.</p>
+  <p>Are you sure ?</p>
+</div>
+<div class="modal-footer">
+  <button type="button" class="btn btn-link" data-dismiss="modal">Cancel</button>
+  {{ POST_button(url_for('.delete'), text="Delete " + cvrf.getDocId(), style="btn-danger") }}
+{% endcall %}
 {% endblock %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/farol/templates/error.j2	Tue Oct 28 09:55:28 2014 +0100
@@ -0,0 +1,27 @@
+{% extends "layout.j2" %}
+
+{% block title %}{{ e }}{% endblock %}
+
+{% block content %}
+<div class="page-header">
+  <h1>{{ e }}</h1>
+</div>
+{% if e.description %}<p>{{ e.description }}</p><hr>{% endif %}
+{% if e.code != 404 %}
+  <p>Software are not without bugs. Looks like, you found one ... A trace has been written to the logs.</p>
+  <div class="row">
+    <div class="col-lg-6">
+      <div class="thumbnail clearfix">
+        <p>If you keep coming to this page, you might want to delete your document, and start again with a fresh one ...</p>
+        <div class="pull-right">{{ POST_button(url_for('document.delete'), text="Delete document", style="btn-danger") }}</div>
+      </div>
+    </div>
+    <div class="col-lg-6">
+    <div class="thumbnail clearfix">
+      <p><strong>First</strong>, you might want to download your document (if it works !)</p>
+      <p><a class="btn btn-success pull-right" href="{{ url_for('render', format_='cvrf', raw=1) }}">Download document</a></p>
+    </div>
+    </div>
+  </div>
+{% endif %}
+{% endblock %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/farol/templates/layout.j2	Tue Oct 28 09:55:28 2014 +0100
@@ -0,0 +1,83 @@
+{#
+# Description:
+# Web Template used in Farol Design
+#
+# Authors:
+# Benoît Allard <benoit.allard@greenbone.net>
+#
+# Copyright:
+# Copyright (C) 2014 Greenbone Networks GmbH
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+-#}
+
+{% from "macros.j2" import modal, POST_button -%}
+
+<!doctype html>
+
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <title>Farol - {% block title %}{% endblock %}</title>
+  <link rel="stylesheet" href="{{ url_for('static', filename='bootstrap.css') }}">
+  <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
+</head>
+<body>
+  <script src="{{ url_for('static', filename='jquery-2.1.1.min.js') }}"></script>
+  <script src="{{ url_for('static', filename='bootstrap.js') }}"></script>
+  <nav class="navbar navbar-inverse" role="navigation">
+    <div class="container">
+      {# Brand and toggle get grouped for better mobile display #}
+      <div class="navbar-header">
+        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
+          <span class="sr-only">Toggle navigation</span>
+          <span class="icon-bar"></span>
+          <span class="icon-bar"></span>
+          <span class="icon-bar"></span>
+        </button>
+        <a class="navbar-brand" href="{{ url_for('welcome') }}" title="A Security Advisory Management Platform">Farol</a>
+      </div>
+
+      {# Collect the nav links, forms, and other content for toggling #}
+      <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
+        <ul class="nav navbar-nav">
+          <li{% if active == 'new' %} class="active"{% endif %}><a href="{{ url_for('new') }}">New</a></li>
+          {% block navbar %}
+          {% endblock %}
+        </ul>
+        <ul class="nav navbar-nav navbar-right">
+          {% block navbar_right %}
+          {% endblock %}
+        </ul>
+      </div>{# /.navbar-collapse #}
+    </div>{# /.container-fluid #}
+  </nav>
+  <div class="main container">
+    {% block pre_content %}{% endblock %}
+    <div>
+      {% block content %}{% endblock %}
+    </div>
+    {% block post_content %}{% endblock %}
+  </div>
+  <footer class="footer container-fluid navbar-inverse">
+    <div class="text-center">
+      <span class="text-muted">Copyright &copy; 2014 Greenbone Networks GmbH</span>
+      |
+      <span><a href="{{ url_for('about') }}">About Farol</a></span>
+    </div>
+    <a href="http://greenbone.net/" id="greenbone" class="logo_img text-hide center-block">Greenbone Networks GmbH</a>
+  </footer>
+</body>
+</html>
--- a/farol/templates/macros.j2	Tue Oct 28 09:54:00 2014 +0100
+++ b/farol/templates/macros.j2	Tue Oct 28 09:55:28 2014 +0100
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 -#}
 
-{% macro textinput(name, label, placeholder="", value=None, required=False, type="text", extras={}, help='') %}
+{% macro textinput(name, label, placeholder="", value=None, required=False, type="text", extras={}, help='', regex=None) %}
 <div class="form-group">
   {% if caller %}
     {% set content=caller () %}
@@ -42,6 +42,9 @@
      id="{{ name }}" name="{{ name }}"
      {%- if placeholder %} placeholder="{{ placeholder }}"{% endif %}
      {%- if value %} value="{{ value }}"{% endif %}
+     {%- if regex %} pattern="{{ regex }}"
+     {%- elif type == "datetime" %} pattern="\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?([+-]\d+(:\d+)?|Z)?)?"
+     {%- endif %}
      {%- if required %} required{% endif %}
      {{- extras | xmlattr }}>
   {% if type == "datetime" %}
--- a/farol/templates/producttree/view.j2	Tue Oct 28 09:54:00 2014 +0100
+++ b/farol/templates/producttree/view.j2	Tue Oct 28 09:55:28 2014 +0100
@@ -120,7 +120,7 @@
 {% if cvrf.isProductTreeOrphan() %}
   {{ delete_button(url_for('.delete'), text="delete whole Product Tree") }}
 {% else %}
-  <p class="text-danger"><small>The Product Treecannot be deleted as some of its elements are referenced in the Document</small></p>
+  <p class="text-danger"><small>The Product Tree cannot be deleted as some of its elements are referenced in the Document</small></p>
 {% endif %}
 </div>
 {% endblock %}
--- a/farol/templates/producttree/view_product.j2	Tue Oct 28 09:54:00 2014 +0100
+++ b/farol/templates/producttree/view_product.j2	Tue Oct 28 09:55:28 2014 +0100
@@ -76,22 +76,20 @@
 {% call panel(heading="Vulnerabilities", title="3", collapsible=False) %}
   <p>The following Vulneralibities are mentionning this product:</p>
   <ul>
-    {% for vulnerability in cvrf._vulnerabilities if (
-               vulnerability.isMentioningProdId(product._productid) or
-               vulnerability.isMentioningGroupId(groups | map(attribute="_groupid") | list)) %}
-      <li>
-        <a href="{{ url_for('vulnerability.view', ordinal=vulnerability._ordinal) }}">{{ vulnerability.getTitle() }}</a>
-        {%- set elements = vulnerability.mentionsProdId(product._productid) | list %}
-        {%- for group in groups %}
-          {{- elements.extend(vulnerability.mentionsGroupId(group._groupid) | list) or '' }}
-        {%- endfor %}
-        {%- set comma = joiner(', ') %}
-        ({% for grouper, list in elements | groupby('NAME') %}{{ comma() -}}
-          {{ grouper }}{% if list | length > 1 %} (x{{ list | length }}){% endif %}
-        {%- endfor %})
-      </li>
-    {% else %}
-      <li><em>None</em></li>
+    {% for vulnerability in cvrf._vulnerabilities %}
+      {%- set elements = vulnerability.mentionsProdId(product._productid) | list %}
+      {%- for group in groups %}
+        {{- elements.extend(vulnerability.mentionsGroupId(group._groupid) | list) or '' }}
+      {%- endfor %}
+      {% if elements %}
+        <li>
+          <a href="{{ url_for('vulnerability.view', ordinal=vulnerability._ordinal) }}">{{ vulnerability.getTitle() }}</a>
+          {%- set comma = joiner(', ') %}
+          ({% for grouper, list in elements | groupby('NAME') %}{{ comma() -}}
+            {{ grouper }}{% if list | length > 1 %} (x{{ list | length }}){% endif %}
+          {%- endfor %})
+        </li>
+      {% endif %}
     {% endfor %}
   </ul>
 {% endcall %}
--- a/farol/templates/welcome.j2	Tue Oct 28 09:54:00 2014 +0100
+++ b/farol/templates/welcome.j2	Tue Oct 28 09:55:28 2014 +0100
@@ -24,36 +24,43 @@
 -#}
 
 {% extends "base.j2" %}
+{% from "macros.j2" import panel %}
+
+{% macro progress_label(progress) -%}
+<span class="label label-
+{%- if progress < 70 %}danger{% elif progress < 95 %}warning{% else %}success{% endif -%}
+">{{ progress }}%</span>
+{%- endmacro %}
 
 {% block title %}Welcome{% endblock %}
 
 {% block content %}
-<div class="jumbotron">
-  <h1>Farol <small>A Security Advisory Management Platform</small></h1>
-  <p>Farol is a web platform to manipulate Security Advisories. The main structure is highly inspired from the structure of a CVRF document.</p>
-  <p>This platform is meant as a way to review / create / edit / publish Security Advisories in an accessible way.</p>
-  <p><a class="btn btn-primary btn-lg" role="button" href="{{ url_for('new') }}">Start !</a></p>
+<div class="well well-lg">
+  <h1>Farol <small>The Security Advisory Management Platform</small></h1>
+  <div class="pull-right"><a class="btn btn-primary btn-lg" role="button" href="{{ url_for('new') }}" >Start !</a></div>
+  <p>This web platform offers to review, create, edit and transform security advisories supporting various input and output formats. During your session the advisory is stored in a cache from which you should save your changes to your local file system.</p>
 </div>
-<img src="{{ url_for('static', filename="flower.png") }}" class="img-responsive img-thumbnail" alt="Security Advisories interactions">
 <div class="row">
-  <div class="col-sm-6">
-    <div class="thumbnail">
-      <h3>Security Advisories</h3>
-      <p>A Security Advisory is about the <em>communication</em> of the information that some <em>vulnerability</em> is present in some <em>product</em>.</p>
-      <dl>
-        <dt>communication</dt>
-        <dd>In order to be fully effective, Security Advisories should be sahred.</dd>
-        <dt>vulnerability</dt>
-        <dd>A vulnerability is a weakness which allows an attacker to reduce a system's information assurance. <cite>(Wikipedia)</cite></dd>
-        <dt>product</dt>
-        <dd>A product contains vulnerabilities.</dd>
-    </div>
-  </div>
-  <div class="col-sm-6">
-    <div class="thumbnail">
-      <h3>Advisory formats</h3>
-      <p>Each Party publish Advisories in a format that fit them ...</p>
-    </div>
+  <div class="col-sm-9"><img src="{{ url_for('static', filename="flower.png") }}" class="img-responsive img-thumbnail" alt="Security Advisories interactions"></div>
+  <div class="col-sm-3">
+    {% call panel(heading="Platform status", collapsible=False) %}
+    <div><span class="pull-right badge">{{ version }}</span>Farol version:</div>
+    {% endcall %}
+    {% call panel(heading="Supported input formats", collapsible=False) %}
+      {% for format, progress in imports %}
+        <div>{{ format }} <span class="pull-right">{{ progress_label(progress) }}</span></div>
+      {% endfor %}
+    {% endcall %}
+    {% call panel(heading="Supported output formats", collapsible=False) %}
+      {% for format, progress in exports %}
+        <div>{{ format }} <span class="pull-right">{{ progress_label(progress) }}</span></div>
+      {% endfor %}
+    {% endcall %}
+    {% call panel(heading="Supported use cases", collapsible=False) %}
+      {% for use_case, progress in use_cases %}
+        <div><span class="pull-right">{{ progress_label(progress) }}</span>{{ use_case }}</div>
+      {% endfor %}
+    {% endcall %}
   </div>
 </div>
 {% endblock %}

http://farol.wald.intevation.org