Merge branch 'master' into celery-jobs

Conflicts:
	app/main/views/sms.py
This commit is contained in:
Martyn Inglis
2016-02-24 15:46:38 +00:00
49 changed files with 708 additions and 381 deletions

View File

@@ -155,7 +155,7 @@ def useful_headers_after_request(response):
response.headers.add('X-Content-Type-Options', 'nosniff')
response.headers.add('X-XSS-Protection', '1; mode=block')
response.headers.add('Content-Security-Policy',
"default-src 'self' 'unsafe-inline'; font-src 'self' data:;") # noqa
"default-src 'self' 'unsafe-inline'; font-src 'self' data:; img-src 'self' data:;") # noqa
if 'Cache-Control' in response.headers:
del response.headers['Cache-Control']
response.headers.add(

Binary file not shown.

After

Width:  |  Height:  |  Size: 622 B

BIN
app/assets/images/tick.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

BIN
app/assets/images/tick.psd Normal file

Binary file not shown.

View File

@@ -3,22 +3,16 @@
Modules.FileUpload = function() {
let $field, $button, $filename;
let $form;
this.update = function() {
$filename.text($field.val().split('\\').pop());
};
this.submit = () => $form.trigger('submit');
this.start = function(component) {
$field = $('.file-upload-field', component);
$button = $('.file-upload-button', component);
$filename = $('.file-upload-filename', component);
$form = $(component);
// Need to put the event on the container, not the input for it to work properly
$(component).on('change', '.file-upload-field', this.update);
$form.on('change', '.file-upload-field', this.submit);
};

View File

@@ -1,3 +1,5 @@
$(() => GOVUK.modules.start());
$(() => new GOVUK.SelectionButtons('.block-label input, .sms-message-option input'));
$(() => $('.error-message').eq(0).parent('label').next('input').trigger('focus'));

View File

@@ -66,3 +66,16 @@ a {
font-family: monospace;
overflow-x: scroll;
}
.inline {
.block-label {
@include media(tablet) {
float: none;
display: inline-block;
}
}
}

View File

@@ -16,19 +16,13 @@
.banner-with-tick,
.banner-default-with-tick {
@extend %banner;
padding: $gutter-half ($gutter + $gutter-half);
&:before {
@include core-24;
content: '';
position: absolute;
top: $gutter-half;
left: $gutter-half;
margin-top: -2px;
}
background-image: file-url('tick-white.png');
background-size: 19px;
background-repeat: no-repeat;
background-position: $gutter-half $gutter-half;
font-weight: bold;
}
.banner-dangerous {
@@ -70,13 +64,18 @@
}
.banner-info,
.banner-important {
@extend %banner;
background: $white;
color: $text-colour;
background-image: file-url('icon-important-2x.png');
background-size: 34px 34px;
background-position: 0 0;
background-position: 0 0px;
background-repeat: no-repeat;
padding: 7px 0 5px 50px;
}
.banner-info {
background-image: file-url('icon-information-2x.png');
}

View File

@@ -3,9 +3,15 @@
.file-upload {
&-label {
@include bold-19;
display: block;
margin: 0 0 10px 0;
.error-message {
padding: 0;
}
}
&-field {
@@ -23,7 +29,7 @@
}
&-button {
@include button($panel-colour);
@include button($button-colour);
display: inline-block;
}
@@ -33,6 +39,10 @@
padding-left: $gutter-half;
}
&-submit {
display: none;
}
}
}

View File

@@ -12,7 +12,7 @@
&-delete-link {
line-height: 40px;
padding: 0 0 0 5px;
padding: 1px 0 0 15px;
a {

View File

@@ -7,6 +7,12 @@
margin: 40px 0 5px 0;
}
.table-field-headings {
th {
padding: 0 0 5px 0;
}
}
%table-field,
.table-field {
@@ -36,6 +42,19 @@
}
&-yes,
&-no {
display: block;
text-indent: -999em;
background-size: 19px 19px;
background-repeat: no-repeat;
background-position: 50% 50%;
}
&-yes {
background-image: file-url('tick.png');
}
&-missing {
color: $error-colour;
font-weight: bold;
@@ -77,4 +96,5 @@
margin-top: -20px;
border-bottom: 1px solid $border-colour;
padding-bottom: 10px;
text-align: center;
}

View File

@@ -0,0 +1,36 @@
.yes-no-wrapper {
border-bottom: 1px solid $border-colour;
margin: 0 0 $gutter 0;
}
.yes-no {
border-top: 1px solid $border-colour;
padding: 10px 0;
&-label {
padding-top: 19px;
float: left;
}
&-fields {
text-align: right;
.block-label {
@include media(tablet) {
margin-bottom: 0;
&:last-child {
margin-right: 0;
}
}
}
}
}

View File

@@ -47,6 +47,7 @@ $path: '/static/images/';
@import 'components/browse-list';
@import 'components/email-message';
@import 'components/api-key';
@import 'components/yes-no';
@import 'views/job';
@import 'views/edit-template';

View File

@@ -5,5 +5,5 @@ main = Blueprint('main', __name__)
from app.main.views import (
index, sign_in, sign_out, register, two_factor, verify, sms, add_service,
code_not_received, jobs, dashboard, templates, service_settings, forgot_password,
new_password, styleguide, user_profile, choose_service, api_keys
new_password, styleguide, user_profile, choose_service, api_keys, manage_users
)

View File

@@ -1,7 +1,7 @@
from flask import url_for
from flask import url_for, abort
from app import notifications_api_client
from notifications_python_client.errors import HTTPError
from app.utils import BrowsableItem
from notifications_python_client.errors import HTTPError
def insert_new_service(service_name, user_id):
@@ -29,7 +29,9 @@ def get_service_by_id(id_):
def get_service_by_id_or_404(id_):
try:
return get_service_by_id(id_)
return notifications_api_client.get_service(id_)['data']
except KeyError:
abort(404)
except HTTPError as e:
if e.status_code == 404:
abort(404)

View File

@@ -21,10 +21,10 @@ from app.utils import (
)
def email_address():
def email_address(label='Email address'):
gov_uk_email \
= "(^[^@^\\s]+@[^@^\\.^\\s]+(\\.[^@^\\.^\\s]*)*.gov.uk)"
return EmailField('Email address', validators=[
return EmailField(label, validators=[
Length(min=5, max=255),
DataRequired(message='Email cannot be empty'),
Email(message='Enter a valid email address'),
@@ -96,6 +96,10 @@ class RegisterUserForm(Form):
password = password()
class InviteUserForm(Form):
email_address = email_address('Their email address')
class TwoFactorForm(Form):
def __init__(self, validate_code_func, *args, **kwargs):
'''

View File

@@ -34,63 +34,3 @@ def send_email(service_id):
@login_required
def check_email(service_id):
return render_template('views/check-email.html')
@main.route("/services/<service_id>/manage-users")
@login_required
def manage_users(service_id):
users = [
{
'name': 'Henry Hadlow',
'permission_send_messages': True,
'permission_manage_service': False,
'permission_manage_api_keys': False
},
{
'name': 'Pete Herlihy',
'permission_send_messages': False,
'permission_manage_service': False,
'permission_manage_api_keys': False,
},
{
'name': 'Chris Hill-Scott',
'permission_send_messages': True,
'permission_manage_service': True,
'permission_manage_api_keys': True
},
{
'name': 'Martyn Inglis',
'permission_send_messages': True,
'permission_manage_service': True,
'permission_manage_api_keys': True
}
]
invited_users = [
{
'email_localpart': 'caley.smolska',
'permission_send_messages': True,
'permission_manage_service': False,
'permission_manage_api_keys': False
},
{
'email_localpart': 'ash.stephens',
'permission_send_messages': False,
'permission_manage_service': False,
'permission_manage_api_keys': False
},
{
'email_localpart': 'nicholas.staples',
'permission_send_messages': True,
'permission_manage_service': True,
'permission_manage_api_keys': True
},
{
'email_localpart': 'adam.shimali',
'permission_send_messages': True,
'permission_manage_service': True,
'permission_manage_api_keys': True
}
]
return render_template('views/manage-users.html', service_id=service_id, users=users, invited_users=invited_users)

View File

@@ -0,0 +1,96 @@
from flask import (
request,
render_template,
redirect,
abort,
url_for,
flash
)
from flask_login import login_required, current_user
from app.main import main
from app.main.dao import users_dao
from app.main.forms import InviteUserForm
from app.main.dao.services_dao import get_service_by_id_or_404
from app import user_api_client
fake_users = [
{
'name': '',
'permission_send_messages': True,
'permission_manage_service': True,
'permission_manage_api_keys': True,
'active': True
}
]
@main.route("/services/<service_id>/users")
@login_required
def manage_users(service_id):
return render_template(
'views/manage-users.html',
service_id=service_id,
users=fake_users,
current_user=current_user,
invited_users=[]
)
@main.route("/services/<service_id>/users/invite", methods=['GET', 'POST'])
@login_required
def invite_user(service_id):
form = InviteUserForm()
if form.validate_on_submit():
flash('Invite sent to {}'.format(form.email_address.data), 'default_with_tick')
return redirect(url_for('.manage_users', service_id=service_id))
return render_template(
'views/invite-user.html',
user={},
service=get_service_by_id_or_404(service_id),
service_id=service_id,
form=form
)
@main.route("/services/<service_id>/users/<user_id>", methods=['GET', 'POST'])
@login_required
def edit_user(service_id, user_id):
if request.method == 'POST':
return redirect(url_for('.manage_users', service_id=service_id))
return render_template(
'views/invite-user.html',
user=fake_users[int(user_id)],
user_id=user_id,
service=get_service_by_id_or_404(service_id),
service_id=service_id
)
@main.route("/services/<service_id>/users/<user_id>/delete", methods=['GET', 'POST'])
@login_required
def delete_user(service_id, user_id):
if request.method == 'POST':
return redirect(url_for('.manage_users', service_id=service_id))
user = fake_users[int(user_id)]
flash(
'Are you sure you want to delete {}s account?'.format(user.get('name') or user['email_localpart']),
'delete'
)
return render_template(
'views/invite-user.html',
user=user,
user_id=user_id,
service=get_service_by_id_or_404(service_id),
service_id=service_id
)

View File

@@ -37,11 +37,23 @@ from app.utils import (
@main.route("/services/<service_id>/sms/send", methods=['GET'])
def choose_sms_template(service_id):
try:
jobs = job_api_client.get_job(service_id)['data']
except HTTPError as e:
if e.status_code == 404:
abort(404)
else:
raise e
print("="*80)
print(jobs)
print(len(jobs))
print(bool(len(jobs)))
return render_template(
'views/choose-sms-template.html',
templates=[
Template(template) for template in templates_dao.get_service_templates(service_id)['data']
],
has_jobs=len(jobs),
service_id=service_id
)
@@ -127,6 +139,7 @@ def check_sms(service_id, upload_id):
template_id = upload_data.get('template_id')
raw_template = templates_dao.get_service_template_or_404(service_id, template_id)['data']
upload_result = _get_rows(contents, raw_template)
session['upload_data']['notification_count'] = len(upload_result['rows'])
template = Template(
raw_template,
values=upload_result['rows'][0] if upload_result['valid'] else {},
@@ -147,9 +160,10 @@ def check_sms(service_id, upload_id):
upload_data = session['upload_data']
original_file_name = upload_data.get('original_file_name')
template_id = upload_data.get('template_id')
notification_count = upload_data.get('notification_count')
session.pop('upload_data')
try:
job_api_client.create_job(upload_id, service_id, template_id, original_file_name)
job_api_client.create_job(upload_id, service_id, template_id, original_file_name, notification_count)
except HTTPError as e:
if e.status_code == 404:
abort(404)

View File

@@ -1,6 +1,7 @@
from flask import render_template, current_app, abort
from flask_wtf import Form
from wtforms import StringField, PasswordField, TextAreaField, FileField, validators
from utils.template import Template
from app.main import main
@@ -17,13 +18,16 @@ def styleguide():
message = TextAreaField(u'Message')
file_upload = FileField('Upload a CSV file to add your recipients details')
sms = "Your vehicle tax for ((registration number)) is due on ((date)). Renew online at www.gov.uk/vehicle-tax"
form = FormExamples()
form.message.data = "Your vehicle tax for ((registration number)) is due on ((date)). Renew online at www.gov.uk/vehicle-tax" # noqa
form.message.data = sms
form.validate()
template = Template({'content': sms})
return render_template(
'views/styleguide.html',
form=form
form=form,
template=template
)

View File

@@ -15,22 +15,10 @@ from app.main.dao import services_dao as sdao
@main.route("/services/<service_id>/templates")
@login_required
def manage_service_templates(service_id):
try:
jobs = job_api_client.get_job(service_id)['data']
except HTTPError as e:
if e.status_code == 404:
abort(404)
else:
raise e
return render_template(
'views/manage-templates.html',
service_id=service_id,
has_jobs=bool(jobs),
templates=[
Template(template)
for template in tdao.get_service_templates(service_id)['data']
]
)
return redirect(url_for(
'.choose_sms_template',
service_id=service_id
))
@main.route("/services/<service_id>/templates/add", methods=['GET', 'POST'])
@@ -50,10 +38,10 @@ def add_service_template(service_id):
tdao.insert_service_template(
form.name.data, form.template_content.data, service_id)
return redirect(url_for(
'.manage_service_templates', service_id=service_id))
'.choose_sms_template', service_id=service_id))
return render_template(
'views/edit-template.html',
h1='Add template',
h1='Add a text message template',
form=form,
service_id=service_id)
@@ -69,7 +57,7 @@ def edit_service_template(service_id, template_id):
tdao.update_service_template(
template_id, form.name.data,
form.template_content.data, service_id)
return redirect(url_for('.manage_service_templates', service_id=service_id))
return redirect(url_for('.choose_sms_template', service_id=service_id))
return render_template(
'views/edit-template.html',

View File

@@ -19,13 +19,14 @@ class JobApiClient(BaseAPIClient):
else:
return self.get(url='/service/{}/job'.format(service_id))
def create_job(self, job_id, service_id, template_id, original_file_name):
def create_job(self, job_id, service_id, template_id, original_file_name, notification_count):
data = {
"id": job_id,
"template": template_id,
"original_file_name": original_file_name,
"bucket_name": "service-{}-notify".format(service_id),
"file_name": "{}.csv".format(job_id)
"file_name": "{}.csv".format(job_id),
"notification_count": notification_count
}
resp = self.post(url='/service/{}/job'.format(service_id), data=data)

View File

@@ -1,8 +1,5 @@
from notifications_python_client.notifications import BaseAPIClient
from notifications_python_client.errors import (
HTTPError,
InvalidResponse
)
from notifications_python_client.errors import HTTPError
from flask.ext.login import UserMixin
@@ -34,6 +31,11 @@ class UserApiClient(BaseAPIClient):
user_data = self.get(url)
return User(user_data['data'], max_failed_login_count=self.max_failed_login_count)
def get_user_by_email(self, email_address):
params = {'email': email_address}
user_data = self.get('/user/email', params=params)
return User(user_data['data'], max_failed_login_count=self.max_failed_login_count)
def get_users(self):
users_data = self.get("/user")['data']
users = []
@@ -57,13 +59,6 @@ class UserApiClient(BaseAPIClient):
if e.status_code == 400 or e.status_code == 404:
return False
def get_user_by_email(self, email_address):
users = self.get_users()
user = [u for u in users if u.email_address == email_address]
if len(user) == 1:
return user[0]
return None
def send_verify_code(self, user_id, code_type, to):
data = {'to': to}
endpoint = '/user/{0}/{1}-code'.format(user_id, code_type)

View File

@@ -1,7 +1,7 @@
{% macro file_upload(field, button_text="Choose file") %}
<div class="form-group{% if field.errors %} error{% endif %}" data-module="file-upload">
<form method="post" enctype="multipart/form-data" class="form-group{% if field.errors %} error{% endif %}" data-module="file-upload">
<label class="file-upload-label" for="{{ field.name }}">
{{ field.label }}
<span class="visually-hidden">{{ field.label }}</span>
{% if hint %}
<span class="form-hint">
{{ hint }}
@@ -20,5 +20,7 @@
{{ button_text }}
</label>
<label class="file-upload-filename" for="{{ field.name }}"></label>
</div>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input type="submit" class="file-upload-submit" value="Submit" />
</form>
{% endmacro %}

View File

@@ -13,14 +13,14 @@
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input type="submit" class="button{% if destructive %}-destructive{% endif %}" value="{{ button_text }}" />
{% endif %}
{% if delete_link %}
<span class="page-footer-delete-link">
or <a href="{{ delete_link }}">{{ delete_link_text }}</a>
</span>
{% endif %}
{% if back_link %}
<a class="page-footer-back-link" href="{{ back_link }}">{{ back_link_text }}</a>
{% endif %}
{% if delete_link %}
<span class="page-footer-delete-link">
<a href="{{ delete_link }}">{{ delete_link_text }}</a>
</span>
{% endif %}
{% if secondary_link and secondary_link_text %}
<a class="page-footer-secondary-link" href="{{ secondary_link }}">{{ secondary_link_text }}</a>
{% endif %}

View File

@@ -55,6 +55,18 @@
</td>
{%- endmacro %}
{% macro text_field(text) -%}
{% call field() %}
{{ text }}
{% endcall %}
{%- endmacro %}
{% macro boolean_field(yes) -%}
{% call field(status='yes' if yes else 'no') %}
{{ "Yes" if yes else "No" }}
{% endcall %}
{%- endmacro %}
{% macro right_aligned_field_heading(text) %}
<span class="table-field-heading-right-aligned">{{ text }}</span>
{%- endmacro %}

View File

@@ -0,0 +1,17 @@
{% macro yes_no(name, label, current_value=None) %}
<fieldset class='yes-no'>
<legend class='yes-no-label'>
{{ label }}
</legend>
<div class='yes-no-fields inline'>
<label class='block-label'>
<input type='radio' name='{{ name }}' {% if current_value == True %}checked{% endif %} />
Yes
</label>
<label class='block-label'>
<input type='radio' name='{{ name }}' {% if current_value == False %}checked{% endif %} />
No
</label>
</div>
</fieldset>
{% endmacro %}

View File

@@ -5,7 +5,7 @@
{{ banner(
message,
'default' if ((category == 'default') or (category == 'default_with_tick')) else 'dangerous',
delete_button="Yes, delete this template" if 'delete' == category else None,
delete_button="Yes, delete" if 'delete' == category else None,
with_tick=True if category == 'default_with_tick' else False
)}}
{% endfor %}

View File

@@ -7,12 +7,11 @@
<li><a href="{{ url_for('.send_email', service_id=service_id) }}">Send emails</a></li>
</ul>
<ul>
<li><a href="{{ url_for('.manage_service_templates', service_id=service_id) }}">Manage templates</a></li>
<li><a href="{{ url_for('.manage_users', service_id=service_id) }}">Manage users</a></li>
<li><a href="{{ url_for('.service_settings', service_id=service_id) }}">Manage service settings</a></li>
<li><a href="{{ url_for('.manage_users', service_id=service_id) }}">Manage team</a></li>
<li><a href="{{ url_for('.service_settings', service_id=service_id) }}">Manage settings</a></li>
</ul>
<ul>
<li><a href="{{ url_for('.documentation', service_id=service_id) }}">Developer documentation</a></li>
<li><a href="{{ url_for('.api_keys', service_id=service_id) }}">API keys</a></li>
<li><a href="{{ url_for('.documentation', service_id=service_id) }}">Developer documentation</a></li>
</ul>
</nav>

View File

@@ -57,7 +57,7 @@
{% endif %}
{% endcall %}
<p>
<p class='table-show-more-link'>
<a href="{{ url_for('.create_api_key', service_id=service_id) }}">Create a new API key</a>
</p>

View File

@@ -45,12 +45,7 @@
<a href="{{url_for('.send_sms', service_id=service_id, template_id=template.id)}}" class="page-footer-back-link">Back</a>
</form>
{% else %}
<form method="post" action="{{ url_for('.send_sms', service_id=service_id, template_id=template.id) }}" enctype="multipart/form-data">
{{file_upload(form.file, button_text='Choose a CSV file')}}
{{ page_footer(
"Upload"
) }}
</form>
{{file_upload(form.file, button_text='Upload a CSV file')}}
{% endif %}
{% call(item) list_table(

View File

@@ -14,6 +14,15 @@
<form method="POST" enctype="multipart/form-data">
{% if templates %}
{% if not has_jobs %}
{{ banner(
"""
Send yourself a test message
""",
subhead='Next step',
type="tip"
)}}
{% endif %}
<div class="grid-row">
{% for template in templates %}
<div class="column-two-thirds">
@@ -23,18 +32,16 @@
<div class="sms-message-use-links">
<a href="{{ url_for(".send_sms", service_id=service_id, template_id=template.id) }}">Add recipients</a>
<a href="{{ url_for(".send_sms_to_self", service_id=service_id, template_id=template.id) }}">Send yourself a test</a>
<a href="{{ url_for(".edit_service_template", service_id=service_id, template_id=template.id) }}">Edit template</a>
</div>
</div>
{% endfor %}
</div>
{% else %}
{{ banner(
'<a href="{}">Add a text message template</a> to start sending messages'.format(
url_for(".add_service_template", service_id=service_id)
)|safe,
type="tip"
)}}
{% endif %}
<p>
<a href="{{ url_for('.add_service_template', service_id=service_id) }}" class="button">Add a new template</a>
</p>
</form>
{% endblock %}

View File

@@ -30,9 +30,9 @@
{{ page_footer(
'Save',
delete_link=url_for('.delete_service_template', service_id=service_id, template_id=template_id) if template_id or None,
delete_link_text='delete this template',
secondary_link=url_for('.manage_service_templates', service_id=service_id),
secondary_link_text='Back to templates'
delete_link_text='Delete this template',
back_link=url_for('.choose_sms_template', service_id=service_id),
back_link_text='Cancel'
) }}
</form>

View File

@@ -0,0 +1,50 @@
{% extends "withnav_template.html" %}
{% from "components/yes-no.html" import yes_no %}
{% from "components/textbox.html" import textbox %}
{% from "components/page-footer.html" import page_footer %}
{% block page_title %}
Manage users GOV.UK Notify
{% endblock %}
{% block maincolumn_content %}
<h1 class="heading-large">
{{ user.name or user.email_localpart or "Add a new team member" }}
</h1>
<div class="grid-row">
<form method="post" class="column-three-quarters">
{% if user %}
<p class='bottom-gutter'>
{{ current_user.email_address }}
</p>
{% else %}
{{ textbox(form.email_address, hint='Email address must end in .gov.uk', width='1-1') }}
{% endif %}
<fieldset class='yes-no-wrapper'>
<legend class='heading-small'>
Permissions
</legend>
{{ yes_no('send_messages', 'Send messages', user.permission_send_messages) }}
{{ yes_no('manage_service', 'Manage service', user.permission_manage_service) }}
{{ yes_no('manage_api_keys', 'Manage API keys', user.permission_manage_api_keys) }}
</fieldset>
{% if user %}
{{ page_footer(
'Save',
delete_link=url_for('.delete_user', service_id=service_id, user_id=user_id),
delete_link_text='Delete this account',
back_link=url_for('.manage_users', service_id=service_id),
back_link_text='Cancel'
) }}
{% else %}
{{ page_footer('Send invitation email') }}
{% endif %}
</form>
</div>
{% endblock %}

View File

@@ -1,53 +0,0 @@
{% extends "withnav_template.html" %}
{% from "components/sms-message.html" import sms_message %}
{% from "components/email-message.html" import email_message %}
{% from "components/browse-list.html" import browse_list %}
{% block page_title %}
Manage templates GOV.UK Notify
{% endblock %}
{% block maincolumn_content %}
<h1 class="heading-large">Manage templates</h1>
{% if not has_jobs %}
{{ banner(
'<a href="{}">Send yourself a text message</a>'.format(
url_for(".choose_sms_template", service_id=service_id)
)|safe,
subhead='Next step',
type="tip"
)}}
{% endif %}
<div class="grid-row">
<div class="column-two-thirds">
{% for template in templates %}
{% if template.template_type == 'email' %}
{{ email_message(
template.get_field('subject'),
template.get_field('content'),
name=template.get_field('name'),
edit_link=url_for('.edit_service_template', service_id=service_id, template_id=template.id)
) }}
{% else %}
{{ sms_message(
template.formatted_as_markup,
name=template.name,
id=template.id,
edit_link=url_for('.edit_service_template', service_id=service_id, template_id=template.id)
) }}
{% endif %}
{% endfor %}
<p>
<a href="{{ url_for('.add_service_template', service_id=service_id) }}" class="button">Add new template</a>
</p>
</div>
</div>
{% endblock %}

View File

@@ -1,65 +1,55 @@
{% extends "withnav_template.html" %}
{% from "components/table.html" import list_table, row, field %}
{% from "components/table.html" import list_table, row, field, boolean_field, hidden_field_heading %}
{% from "components/page-footer.html" import page_footer %}
{% set table_options = {
'field_headings': [
'Name', 'Send messages', 'Manage service', 'Manage API keys', hidden_field_heading('Link to change')
],
'field_headings_visible': True,
'caption_visible': True
} %}
{% block page_title %}
Manage users GOV.UK Notify
{% endblock %}
{% block maincolumn_content %}
<h1 class="heading-large">Manage users</h1>
<h1 class="heading-large">
Manage team
</h1>
<p>
<a href="#" class="button">Invite users</a>
</p>
<a href="{{ url_for('.invite_user', service_id=service_id) }}" class="button">Invite a team member</a>
{% call(item) list_table(
users,
caption='Active users',
field_headings=['Name', 'Send messages', 'Manage Service', 'Manage API keys', 'Link to change'],
field_headings_visible=True,
caption_visible=True
) %}
{% call field() %}
{{ item.name }}
{% call(item) list_table(
users, caption='Active', **table_options
) %}
{% call field() %}
{{ current_user.name }}
{% endcall %}
{{ boolean_field(item.permission_send_messages) }}
{{ boolean_field(item.permission_manage_service) }}
{{ boolean_field(item.permission_manage_api_keys) }}
{% call field(align='right') %}
<a href="{{ url_for('.edit_user', service_id=service_id, user_id=0)}}">Change</a>
{% endcall %}
{% endcall %}
{% call field() %}
{{ "✔" if item.permission_send_messages else "❌" }}
{% endcall %}
{% call field() %}
{{ "✔" if item.permission_manage_service else "❌" }}
{% endcall %}
{% call field() %}
{{ "✔" if item.permission_manage_api_keys else "❌" }}
{% endcall %}
{% call field(align='right') %}
<a href="#">Change</a>
{% endcall %}
{% endcall %}
{% call(item) list_table(
invited_users,
caption='Invited users',
field_headings=['Name', 'Send messages', 'Manage Service', 'Manage API keys', 'Link to change'],
field_headings_visible=True,
caption_visible=True
) %}
{% call field() %}
{{ item.email_localpart }}
{% endcall %}
{% call field() %}
{{ "✔" if item.permission_send_messages else "❌" }}
{% endcall %}
{% call field() %}
{{ "✔" if item.permission_manage_service else "❌" }}
{% endcall %}
{% call field() %}
{{ "✔" if item.permission_manage_api_keys else "❌" }}
{% endcall %}
{% call field(align='right') %}
<a href="#">Change</a>
{% endcall %}
{% endcall %}
{% if invited_users %}
{% call(item) list_table(
invited_users, caption='Invited', **table_options
) %}
{% call field() %}
{{ item.email_localpart }}
{% endcall %}
{{ boolean_field(item.permission_send_messages) }}
{{ boolean_field(item.permission_manage_service) }}
{{ boolean_field(item.permission_manage_api_keys) }}
{% call field(align='right') %}
<a href="{{ url_for('.edit_user', service_id=service_id, user_id=item.id)}}">Change</a>
{% endcall %}
{% endcall %}
{% endif %}
{% endblock %}

View File

@@ -12,24 +12,23 @@
<h1 class="heading-large">Add recipients</h1>
<form method="POST" enctype="multipart/form-data">
<div class="grid-row">
<div class="column-two-thirds">
{{ sms_message(template.formatted_as_markup) }}
{{ banner(
'You can upload real data, but well only send to your mobile number until you <a href="{}">request to go live</a>'|safe,
'info'
)}}
<div class="grid-row">
<div class="column-two-thirds">
{{ sms_message(template.formatted_as_markup) }}
</div>
</div>
</div>
{{file_upload(form.file, button_text='Choose a CSV file')}}
{{file_upload(form.file, button_text='Upload a CSV file')}}
<p>
<a href="{{ url_for('.get_example_csv', service_id=service_id, template_id=template.id) }}">Download an example CSV file</a>
</p>
<p>
<a href="{{ url_for('.get_example_csv', service_id=service_id, template_id=template.id) }}">Download an example CSV file</a>
</p>
{{ page_footer(
"Continue to preview"
) }}
</form>
{% endblock %}

View File

@@ -23,7 +23,7 @@
</li>
</ul>
{% if not jobs %}
{% if not template_count and not jobs %}
{{ banner(
"""
<ol>
@@ -41,6 +41,16 @@
subhead='Get started',
type="tip"
)}}
{% elif not jobs %}
{{ banner(
"""
<a href='{}'>Send yourself a text message</a>
""".format(
url_for(".choose_sms_template", service_id=service_id)
)|safe,
subhead='Next step',
type="tip"
)}}
{% else %}
{% call(item) list_table(
jobs,

View File

@@ -5,9 +5,11 @@
{% from "components/browse-list.html" import browse_list %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/sms-message.html" import sms_message %}
{% from "components/table.html" import mapping_table, list_table, row, field, right_aligned_field_heading %}
{% from "components/email-message.html" import email_message %}
{% from "components/table.html" import mapping_table, list_table, row, field, text_field, boolean_field, right_aligned_field_heading %}
{% from "components/textbox.html" import textbox %}
{% from "components/file-upload.html" import file_upload %}
{% from "components/yes-no.html" import yes_no %}
{% from "components/api-key.html" import api_key %}
{% block page_title %}
@@ -25,21 +27,26 @@
</p>
<h2 class="heading-large">Banner</h2>
<p>Used to show the status of a thing or action.</p>
<div class="grid-row">
<div class="column-three-quarters">
<p>Used to show the status of a thing or action.</p>
{{ banner("You sent 1,234 text messages", with_tick=True) }}
{{ banner("You sent 1,234 text messages", with_tick=True) }}
{{ banner('Youre not allowed to do this', 'dangerous')}}
{{ banner('Youre not allowed to do this', 'dangerous')}}
{{ banner('Are you sure you want to delete?', 'dangerous', delete_button="Yes, delete this thing")}}
{{ banner('Are you sure you want to delete?', 'dangerous', delete_button="Yes, delete this thing")}}
{{ banner(
'<a href="#">Send your first message</a>'|safe,
subhead='Get started',
type='tip'
)}}
{{ banner(
'<a href="#">Send your first message</a>'|safe,
subhead='Get started',
type='tip'
)}}
{{ banner('You could go to jail', 'important')}}
</div>
</div>
{{ banner('You could go to jail', 'important')}}
<h2 class="heading-large">Big number</h2>
@@ -118,7 +125,7 @@
<h2 class="heading-large">SMS message</h2>
<p>Used to show, preview or choose an SMS message.</p>
<p>Used to show, preview or choose an SMS template.</p>
<div class="grid-row">
<div class="column-half">
@@ -127,82 +134,105 @@
name='Two week reminder',
) }}
{{ sms_message(
'Your vehicle tax for ((registration number)) is due on ((date)). Renew online at www.gov.uk/vehicle-tax'
template.formatted_as_markup
) }}
{{ sms_message(
'Your vehicle tax for registration number is due on date. Renew online at www.gov.uk/vehicle-tax',
'Your vehicle tax for LC12 BFL is due on 1 March 2016. Renew online at www.gov.uk/vehicle-tax',
'+44 7700 900 306'
) }}
{{ sms_message(
'Your vehicle tax for ((registration number)) is due on ((date)). Renew online at www.gov.uk/vehicle-tax',
template.formatted_as_markup,
name='Two week reminder',
edit_link='#'
) }}
</div>
</div>
<h2 class="heading-large">Email message</h2>
<p>Used to show, preview or choose an email template.</p>
<div class="grid-row">
<div class="column-two-thirds">
{{ email_message(
subject="Vehicle tax reminder",
body="Dear Alice Smith,\n\nYour vehicle tax for LC12 BFL is due on 1 March 2016.\n\nRenew online at www.gov.uk/vehicle-tax",
from_name="Vehicle tax",
from_address="vehicle.tax@notifications.service.gov.uk",
name="Two week reminder",
) }}
</div>
</div>
<h2 class="heading-large">Tables</h2>
{% call mapping_table(
caption='Account settings',
field_headings=['Label', 'Value', 'Action'],
field_headings_visible=False,
caption_visible=True
) %}
{% call row() %}
{% call field() %}
Username
<div class="grid-row">
<div class="column-three-quarters">
<p>
Used for comparing rows of data.
</p>
{% call mapping_table(
caption='Account settings',
field_headings=['Label', 'True', 'False', 'Action'],
field_headings_visible=False,
caption_visible=True
) %}
{% call row() %}
{{ text_field('Username' )}}
{{ boolean_field(True) }}
{{ boolean_field(False) }}
{% call field(align='right') %}
<a href="#">Change</a>
{% endcall %}
{% endcall %}
{% endcall %}
{% call field() %}
admin
{% endcall %}
{% call field(align='right') %}
<a href="#">Change</a>
{% endcall %}
{% endcall %}
{% endcall %}
{% call(item) list_table(
[
{
'file': 'dispatch_20151114.csv', 'status': 'Queued'
},
{
'file': 'dispatch_20151117.csv', 'status': 'Delivered'
},
{
'file': 'remdinder_monday.csv', 'status': 'Failed'
}
],
caption='Messages',
field_headings=['File', right_aligned_field_heading('Status')],
field_headings_visible=True,
caption_visible=False
) %}
{% call field() %}
{{ item.file }}
{% endcall %}
{% call field(
align='right',
status='error' if item.status == 'Failed' else 'default'
) %}
{{ item.status }}
{% endcall %}
{% endcall %}
{% call(item) list_table(
[
{
'file': 'dispatch_20151114.csv', 'status': 'Queued'
},
{
'file': 'dispatch_20151117.csv', 'status': 'Delivered'
},
{
'file': 'remdinder_monday.csv', 'status': 'Failed'
}
],
caption='Messages',
field_headings=['File', right_aligned_field_heading('Status')],
field_headings_visible=True,
caption_visible=False
) %}
{% call field() %}
{{ item.file }}
{% endcall %}
{% call field(
align='right',
status='error' if item.status == 'Failed' else 'default'
) %}
{{ item.status }}
{% endcall %}
{% endcall %}
{% call(item) list_table(
[],
caption='Jobs',
field_headings=['Job', 'Time'],
caption_visible=True,
empty_message='You havent scheduled any jobs yet'
) %}
{% call field() %}
{{ item.job }}
{% endcall %}
{% call field() %}
{{ item.time }}
{% endcall %}
{% endcall %}
{% call(item) list_table(
[],
caption='Jobs',
field_headings=['Job', 'Time'],
caption_visible=True,
empty_message='You havent scheduled any jobs yet'
) %}
{% call field() %}
{{ item.job }}
{% endcall %}
{% call field() %}
{{ item.time }}
{% endcall %}
{% endcall %}
<p class="table-show-more-link">
<a href="#">Add a job now</a>
</p>
</div>
</div>
<h2 class="heading-large">Textbox</h2>
{{ textbox(form.username) }}
@@ -213,6 +243,17 @@
<h2 class="heading-large">File upload</h2>
{{ file_upload(form.file_upload) }}
<h2 class="heading-large">Yes/no</h2>
<div class="grid-row">
<div class='column-half'>
<fieldset class='yes-no-wrapper'>
{{ yes_no('manage_service', 'Manage service', True) }}
{{ yes_no('templates', 'Create templates', True) }}
</fieldset>
</div>
</div>
<h2 class="heading-large">API key</h2>
{{ api_key('d30512af92e1386d63b90e5973b49a10') }}