Merge branch 'main' of https://github.com/GSA/notifications-admin into notify-678

This commit is contained in:
Andrew Shumway
2023-09-27 11:15:46 -06:00
81 changed files with 2019 additions and 1298 deletions

View File

@@ -2,6 +2,7 @@ import os
import pathlib
from functools import partial
from time import monotonic
from urllib.parse import urlparse, urlunparse
import jinja2
from flask import (
@@ -36,7 +37,6 @@ from werkzeug.local import LocalProxy
from app import proxy_fix
from app.asset_fingerprinter import asset_fingerprinter
from app.config import configs
from app.custom_auth import CustomBasicAuth
from app.extensions import redis_client, zendesk_client
from app.formatters import (
convert_to_boolean,
@@ -121,7 +121,6 @@ from app.url_converters import (
login_manager = LoginManager()
csrf = CSRFProtect()
talisman = Talisman()
basic_auth = CustomBasicAuth()
# The current service attached to the request stack.
@@ -234,8 +233,6 @@ def create_app(application):
login_manager.session_protection = None
login_manager.anonymous_user = AnonymousUser
setup_basic_auth(application)
# make sure we handle unicode correctly
redis_client.redis_store.decode_responses = True
@@ -254,6 +251,7 @@ def create_app(application):
def init_app(application):
application.before_request(redirect_notify_to_beta)
application.before_request(load_service_before_request)
application.before_request(load_organization_before_request)
application.before_request(request_helper.check_proxy_header_before_request)
@@ -332,6 +330,23 @@ def make_session_permanent():
session.permanent = True
def create_beta_url(url):
url_created = urlparse(url)
url_list = list(url_created)
url_list[1] = "beta.notify.gov"
url_for_redirect = urlunparse(url_list)
return url_for_redirect
def redirect_notify_to_beta():
if (
current_app.config["NOTIFY_ENVIRONMENT"] == "production"
and "beta.notify.gov" not in request.url
):
url_to_beta = create_beta_url(request.url)
return redirect(url_to_beta, 302)
def load_service_before_request():
if "/static/" in request.url:
request_ctx.service = None
@@ -565,7 +580,3 @@ def init_jinja(application):
]
jinja_loader = jinja2.FileSystemLoader(template_folders)
application.jinja_loader = jinja_loader
def setup_basic_auth(application):
application.basic_auth = CustomBasicAuth(application)

View File

@@ -12,23 +12,21 @@
<link rel="shortcut icon" sizes="16x16 32x32 48x48" href="/static/images/favicon.ico?de7abc5226925203ac10b0a4a94af949"
type="image/x-icon" />
<link rel="mask-icon" href="/static/images/govuk-mask-icon.svg?2afd125b1db96109388d4a0727312f3e" color="#0b0c0c">
<link rel="mask-icon" href="/static/images/usa-mask-icon.svg?2afd125b1db96109388d4a0727312f3e" color="#F0F0F0">
<link rel="apple-touch-icon" sizes="180x180"
href="/static/images/govuk-apple-touch-icon-180x180.png?a0f7e1b728a42016b247dc54ee40d055">
<link rel="apple-touch-icon" sizes="167x167"
href="/static/images/govuk-apple-touch-icon-167x167.png?f636cb7d471fc6239d2241d75cbdabcb">
<link rel="apple-touch-icon" sizes="152x152"
href="/static/images/govuk-apple-touch-icon-152x152.png?40846d46aa37232e2b35065769ce567c">
<link rel="apple-touch-icon" href="/static/images/govuk-apple-touch-icon.png?3c45daa1f1e716458e4da954b973002e">
href="/static/images/apple-touch-icon.png?a0f7e1b728a42016b247dc54ee40d055">
<link rel="icon" type="image/png" sizes="32x32" href="/static/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/images/favicon-16x16.png">
<link rel="manifest" href="/static/images/site.webmanifest">
<link rel="stylesheet" media="screen" href="/static/stylesheets/main.css?d077f86473501ab244de0b30600536ee" />
<link rel="stylesheet" media="print" href="/static/stylesheets/print.css?28010888ca5719dc83d7c2c80f6ed2b2" />
<meta property="og:image" content="/static/images/govuk-opengraph-image.png">
<meta property="og:image" content="/static/images/usa-opengraph-image.png">
</head>
<body class="usa-template__body">
<a href="#main-content" class="govuk-skip-link">Skip to main content</a>
<a href="#main-content" class="usa-skip-link">Skip to main content</a>
<header class="usa-header " role="banner" data-module="header">
<div class="usa-header__container usa-width-container">

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

View File

@@ -0,0 +1,4 @@
<svg width="680" height="680" viewBox="0 0 680 680" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M672 211H332V161L672 161V211ZM672 294H332V244H672V294ZM672 377H332V327H672V377ZM672 410V460H8V410H672ZM672 493V543H8V493H672Z" fill="#D83933"/>
<path d="M8 161H299V377H8V161Z" fill="#3C3B6E"/>
</svg>

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -16,7 +16,7 @@
// The label gets styled like a button and is used to hide the native file upload control. This is so that
// users see a button that looks like the others on the site.
//
this.$form.find('label.file-upload-button').addClass('usa-button');
this.$form.find('label.file-upload-button').addClass('usa-button margin-bottom-1');
// Clear the form if the user navigates back to the page
$(window).on("pageshow", () => this.$form[0].reset());

View File

@@ -9,48 +9,48 @@
let states = {
'initial': Hogan.compile(`
{{#showNowAsDefault}}
<div class="radio-select__column">
<div class="radio-select__column margin-y-2">
<div class="usa-radio">
<input class="usa-radio__input" checked="checked" id="{{name}}-0" name="{{name}}" type="radio" value="">
<label class="usa-radio__label" for="{{name}}-0">Now</label>
</div>
</div>
{{/showNowAsDefault}}
<div class="radio-select__column">
<div class="radio-select__column margin-y-2">
{{#categories}}
<input type='button' class='govuk-button govuk-button--secondary radio-select__button--category' aria-expanded="false" value='{{.}}' />
<input type='button' class='usa-button usa-button--outline radio-select__button--category' aria-expanded="false" value='{{.}}' />
{{/categories}}
</div>
`),
'choose': Hogan.compile(`
{{#showNowAsDefault}}
<div class="radio-select__column">
<div class="radio-select__column margin-y-2">
<div class="usa-radio">
<input class="usa-radio__input" checked="checked" id="{{name}}-0" name="{{name}}" type="radio" value="">
<label class="usa-radio__label" for="{{name}}-0">Now</label>
</div>
</div>
{{/showNowAsDefault}}
<div class="radio-select__column">
<div class="radio-select__column margin-y-2">
{{#choices}}
<div class="usa-radio js-option">
<input class="usa-radio__input" type="radio" value="{{value}}" id="{{id}}" name="{{name}}" />
<label class="usa-radio__label" for="{{id}}">{{label}}</label>
</div>
{{/choices}}
<input type='button' class='govuk-button govuk-button--secondary radio-select__button--done' aria-expanded='true' value='Done' />
<input type='button' class='usa-button usa-button--outline radio-select__button--done' aria-expanded='true' value='Done' />
</div>
`),
'chosen': Hogan.compile(`
{{#showNowAsDefault}}
<div class="radio-select__column">
<div class="radio-select__column margin-y-2">
<div class="usa-radio">
<input class="usa-radio__input" id="{{name}}-0" name="{{name}}" type="radio" value="">
<label class="usa-radio__label" for="{{name}}-0">Now</label>
</div>
</div>
{{/showNowAsDefault}}
<div class="radio-select__column">
<div class="radio-select__column margin-y-2">
{{#choices}}
<div class="usa-radio">
<input class="usa-radio__input" checked="checked" type="radio" value="{{value}}" id="{{id}}" name="{{name}}" />
@@ -58,8 +58,8 @@
</div>
{{/choices}}
</div>
<div class="radio-select__column">
<input type='button' class='govuk-button govuk-button--secondary radio-select__button--reset' aria-expanded='false' value='Choose a different time' />
<div class="radio-select__column margin-y-2">
<input type='button' class='usa-button usa-button--outline radio-select__button--reset' aria-expanded='false' value='Choose a different time' />
</div>
`)
};

View File

@@ -0,0 +1,342 @@
@use "uswds-core" as *;
// SMS Box Style
.sms-message-wrapper {
position: relative;
width: 100%;
max-width: 464px;
box-sizing: border-box;
padding: units(1);
background: color('gray-cool-10');
border: 1px solid color('gray-cool-10');
border-radius: 5px;
white-space: normal;
margin: 0 0 units(1) 0;
clear: both;
word-wrap: break-word;
&:after {
content: "";
display: block;
position: absolute;
bottom: -5px;
right: -20px;
border: 10px solid transparent;
border-left-width: 13px;
border-right-width: 13px;
border-bottom-color: color('gray-cool-10');
border-left-color: color('gray-cool-10');
transform: rotate(17deg);
}
}
.sms-message-inbound {
.sms-message-wrapper {
&:after {
border-left-color: transparent;
border-bottom-color: color('gray-cool-10');
border-right-color: color('gray-cool-10');
right: auto;
left: -20px;
transform: rotate(17deg);
}
}
}
.sms-message-sender {
margin: units(1) 0 0;
}
.sms-message-recipient {
color: color('gray-cool-90');
margin: 0 0 units(1);
}
.sms-message-status {
color: color('gray-cool-90');
margin: -20px units(1) 20px units(1);
}
.sms-message-status-outbound {
text-align: right;
}
.sms-message-row {
&:focus {
outline: none;
padding-top: 120px;
margin-top: -120px;
}
}
.sms-message-reply-link {
text-align: right;
}
// File upload area
.page-footer {
position: relative;
margin-bottom: 30px;
&-link {
line-height: 40px;
padding: 1px 0 0 15px;
font-weight: normal;
&:first-of-type {
padding-left: 0px;
}
}
&-delete-link-without-button {
padding: 0;
display: inline-block;
}
&-secondary-link {
display: block;
margin-top: units(1);
}
&-right-aligned-link {
position: absolute;
right: 0;
top: 9px; // align baseline with buttons
&-without-button {
position: absolute;
right: 0;
top: 0;
}
}
&__button {
margin-right: 10px;
}
.js-cancel {
margin: 0;
}
&__button--centred {
display: block;
margin: 0 auto;
}
}
.file-upload {
&-label,
&-button-label {
font-weight: bold;
font-size: 19px;
display: block;
margin: 0 0 10px 0;
}
&-label .error-message,
&-button-label.error-message {
padding: 0;
}
&-field {
margin-bottom: 10px;
}
// Hide normal upload form if we're adding a custom version with JS
.js-enabled &-label,
.js-enabled &-field,
.js-enabled &-submit {
display: none;
}
&-alternate-link {
display: inline-block;
line-height: 35px;
a {
font-weight: bold;
}
}
}
.spreadsheet {
margin-bottom: units (1);
.table {
margin-bottom: 0;
}
.usa-table--borderless thead th {
border-top: 1px solid color('gray-cool-10');
}
th,
.table-field-index {
background: color('gray-cool-10');
border: 1px solid color('gray-cool-10');
font-weight: bold;
text-align: center;
}
th, td {
padding-left: 10px;
padding-right: 10px;
border: 1px solid color('gray-cool-10');
}
td {
border-top: 0;
// 194 is the width of the table * 1/3.5, so the overflow cuts off
// at 3.5 columns wide.
// 11 accounts for the padding of the table cell
min-width: 194px - 11px;
&:first-child {
min-width: auto;
}
}
.fullscreen-fixed-table {
z-index: 1000;
.table-field-heading-first {
background: color('gray-cool-10');
}
}
}
.fullscreen {
&-content {
background: #FFFFFF;
z-index: 10;
overflow-y: hidden;
box-sizing: border-box;
margin: 0 0 units(1) 0;
padding: 0 0 0 0;
overflow: hidden;
border-bottom: 1px solid color('gray-cool-10');
.table {
margin-bottom: 0;
tr:last-child {
td {
border-bottom: 1px solid #FFFFFF;
}
}
}
th,
.table-field-error-label,
.table-field-left-aligned {
white-space: nowrap;
}
}
&-right-shadow {
position: absolute;
top: 0;
right: 0;
width: 4px;
height: 100%;
z-index: 200;
&.visible {
&.with-transition {
transition: box-shadow 0.6s ease-out;
}
box-shadow: inset -1px 0 0 0 color('gray-cool-10'), inset -3px 0 0 0 rgba(color('gray-cool-10'), 0.2);
}
}
&-scrollable-table {
overflow-x: auto;
overflow-y: hidden;
.table-field-heading-first,
.table-field-index {
display: none;
}
.table-field-left-aligned {
position: relative;
z-index: 150;
background: #FFFFFF;
}
&::-webkit-scrollbar {
-webkit-appearance: none;
}
&::-webkit-scrollbar:horizontal {
height: 11px;
background-color: #FFFFFF;
}
&::-webkit-scrollbar-thumb {
border-radius: 8px;
border: 2px solid #FFFFFF;
background-color: rgba(0, 0, 0, .5);
}
&::-webkit-scrollbar-track {
background-color: #FFFFFF;
border-radius: 8px;
}
}
&-fixed-table {
position: absolute;
top: 0;
overflow: hidden;
.table-field-heading {
visibility: hidden;
}
.table-field-left-aligned {
width: 0;
position: relative;
z-index: 100;
visibility: hidden;
}
.table-field-heading-first,
.table-field-index {
transition: none;
position: relative;
z-index: 200;
background: #FFFFFF;
}
}
&-scrolled-table {
padding-bottom: 20px;
.table-field-heading-first,
.table-field-index {
transition: box-shadow 0.3s ease-in-out;
box-shadow: 1px 0 0 0 color('gray-cool-10'), 3px 0 0 0 rgba(color('gray-cool-10'), 0.2);
}
}
&-shim {
width: 100%;
position: relative;
z-index: 9;
margin-bottom: 30px;
pointer-events: none;
& + .table-show-more-link {
margin-top: -20px;
}
}
}
.js-focus-style {
outline: 3px solid color("blue-40v");
box-shadow: 0 0 0 7px color("blue-40v");
*:focus {
outline: none;
}
}

View File

@@ -25,6 +25,9 @@ i.e.
.usa-header--extended .usa-logo {
font-family: family("sans");
margin: units(4) 0;
@include at-media-max('mobile-lg') {
margin: units(4) 0 units(4) units(2);
}
img {
@include at-media($theme-header-min-width) {
width: 80px;
@@ -33,10 +36,23 @@ i.e.
}
}
.usa-nav__primary > .usa-nav__primary-item > a {
font-size: size("body", 4);
.usa-banner__header-action {
font-size: size("body", 2);
}
.usa-nav__primary {
&> .usa-nav__primary-item > a {
font-size: size("body", 4);
}
&> .usa-nav__primary-item:last-child {
margin-left: auto;
@include u-margin-right(-4);
}
}
.usa-nav__primary
h1 {
font-weight: bold !important;
}
@@ -250,6 +266,19 @@ td.table-empty-message {
background: color("green-cool-40v");
display: flex;
padding: units(1) units(2);
&--failing {
padding: 0;
a.usa-link {
color: white;
background: color("red-warm-50v");
padding: units(1) units(2);
margin: 0;
width: 100%;
&:hover {
background: color("red-warm-60v");
}
}
}
}
}
.usa-table {
@@ -443,16 +472,3 @@ details form {
padding-left: 5px;
letter-spacing: 0.04em;
}
// .textbox-colour-preview {
// @include govuk-media-query($from: desktop) {
// width: 38px;
// height: 38px;
// margin-left: 5px;
// border-radius: 50%;
// box-shadow: inset 0 0 0 1px rgba(govuk-colour("black"), 0.2);
// display: inline-block;
// vertical-align: top;
// transition: background 0.3s ease-out;
// }
// }

View File

@@ -1,3 +1,4 @@
@forward "uswds-theme";
@forward "uswds";
@forward "uswds-theme-custom-styles";
@forward "uswds-theme-custom-styles";
@forward "legacy-styles";

View File

@@ -28,8 +28,6 @@ class Config(object):
# ZENDESK_API_KEY = getenv('ZENDESK_API_KEY')
ROUTE_SECRET_KEY_1 = getenv("ROUTE_SECRET_KEY_1", "dev-route-secret-key-1")
ROUTE_SECRET_KEY_2 = getenv("ROUTE_SECRET_KEY_2", "dev-route-secret-key-2")
BASIC_AUTH_USERNAME = getenv("BASIC_AUTH_USERNAME")
BASIC_AUTH_PASSWORD = getenv("BASIC_AUTH_PASSWORD")
NR_ACCOUNT_ID = getenv("NR_ACCOUNT_ID")
NR_TRUST_KEY = getenv("NR_TRUST_KEY")
@@ -101,7 +99,6 @@ def _s3_credentials_from_env(bucket_prefix):
class Development(Config):
BASIC_AUTH_FORCE = False
DEBUG = True
SESSION_COOKIE_SECURE = False
SESSION_PROTECTION = None
@@ -138,7 +135,6 @@ class Test(Development):
class Production(Config):
HEADER_COLOUR = "#005EA5" # $govuk-blue
HTTP_PROTOCOL = "https"
BASIC_AUTH_FORCE = True
ASSET_DOMAIN = "" # TODO use a CDN
ASSET_PATH = "/static/" # TODO use a CDN
DEBUG = False
@@ -153,7 +149,6 @@ class Production(Config):
class Staging(Production):
BASIC_AUTH_FORCE = True
HEADER_COLOUR = "#00ff00" # $green
@@ -166,7 +161,6 @@ class Sandbox(Staging):
class Scanning(Production):
BASIC_AUTH_FORCE = False
HTTP_PROTOCOL = "http"
API_HOST_NAME = "https://notify-api-staging.app.cloud.gov/"
SECRET_KEY = "dev-notify-secret-key" # nosec B105 - only used in development

View File

@@ -1,18 +0,0 @@
from flask import jsonify, request
from flask_basicauth import BasicAuth
class CustomBasicAuth(BasicAuth):
"""
Description:
Override BasicAuth to permit anonymous healthcheck at /_status?simple=true
"""
def challenge(self):
if "/_status" in request.url:
if request.args.get("elb", None) or request.args.get("simple", None):
return jsonify(status="ok"), 200
return super(CustomBasicAuth, self).challenge()
custom_basic_auth = CustomBasicAuth()

View File

@@ -45,10 +45,11 @@ def _create_example_template(service_id):
@user_is_gov_user
def add_service():
default_organization_type = current_user.default_organization_type
if default_organization_type is None:
default_organization_type = "federal"
form = CreateServiceForm(
# avoid setting a default for now; the US gov email addresses aren't as useful as the UK
# ones for guessing the org type
organization_type=None
# This value is currently not useful but if it is not set it will result in a bug
organization_type=default_organization_type
)
if form.validate_on_submit():

View File

@@ -1,3 +1,5 @@
import os
from flask import (
Markup,
abort,
@@ -10,9 +12,10 @@ from flask import (
)
from flask_login import current_user
from app import login_manager
from app import login_manager, user_api_client
from app.main import main
from app.main.forms import LoginForm
from app.main.views.verify import activate_user
from app.models.user import InvitedUser, User
from app.utils import hide_from_search_engines
from app.utils.login import is_safe_redirect_url
@@ -22,6 +25,12 @@ from app.utils.login import is_safe_redirect_url
@hide_from_search_engines
def sign_in():
redirect_url = request.args.get("next")
if os.getenv("NOTIFY_E2E_TEST_EMAIL"):
user = user_api_client.get_user_by_email(os.getenv("NOTIFY_E2E_TEST_EMAIL"))
activate_user(user["id"])
return redirect(url_for("main.show_accounts_or_dashboard", next=redirect_url))
if current_user and current_user.is_authenticated:
if redirect_url and is_safe_redirect_url(redirect_url):
return redirect(redirect_url)
@@ -58,6 +67,7 @@ def sign_in():
if user.sms_auth:
return redirect(url_for(".two_factor_sms", next=redirect_url))
if user.email_auth:
return redirect(
url_for(".two_factor_email_sent", next=redirect_url)

View File

@@ -3,23 +3,15 @@
{% block headIcons %}
<link rel="shortcut icon" sizes="16x16 32x32 48x48" href="{{ asset_url('images/favicon.ico') }}" type="image/x-icon" />
<link rel="mask-icon" href="{{ asset_url('images/govuk-mask-icon.svg') }}" color="{{ themeColor | default('#0b0c0c') }}"> {# Hardcoded value of $govuk-black #}
<link rel="apple-touch-icon" sizes="180x180" href="{{ asset_url('images/govuk-apple-touch-icon-180x180.png') }}">
<link rel="apple-touch-icon" sizes="167x167" href="{{ asset_url('images/govuk-apple-touch-icon-167x167.png') }}">
<link rel="apple-touch-icon" sizes="152x152" href="{{ asset_url('images/govuk-apple-touch-icon-152x152.png') }}">
<link rel="apple-touch-icon" href="{{ asset_url('images/govuk-apple-touch-icon.png') }}">
<link rel="mask-icon" href="{{ asset_url('images/usa-mask-icon.svg') }}" color="{{ themeColor | default('#F0F0F0') }}">
<link rel="apple-touch-icon" sizes="180x180" href="{{ asset_url('images/apple-touch-icon.png') }}">
<link rel="apple-touch-icon" href="{{ asset_url('images/apple-touch-icon.png') }}">
{% endblock %}
{% block head %}
{%- for font in font_paths %}
<link rel="preload" href="{{ asset_url(font, with_querystring_hash=False) }}" as="font" type="font/woff2" crossorigin>
{%- endfor %}
<link rel="stylesheet" media="screen" href="{{ asset_url('css/styles.css') }}" />
{% block extra_stylesheets %}
{% endblock %}
<style nonce="{{ csp_nonce() }}">
.govuk-header__container { border-color: {{header_colour}} }
</style>
{% if g.hide_from_search_engines %}
<meta name="robots" content="noindex" />
{% endif %}
@@ -28,6 +20,8 @@
<meta name="format-detection" content="telephone=no">
{% endblock %}
{% block meta %}
<meta property="og:site_name" content="Notify.gov">
<meta property="og:image" content="{{ asset_url('images/usa-opengraph-image.png') }}">
{% endblock %}
<!-- <script type="text/javascript" src="{{ asset_url('js/gtm_head.js') }}"></script> -->
{% endblock %}

View File

@@ -39,7 +39,7 @@
<span class="big-number-with-status">
{{ big_number(number, label, link=link, smaller=smaller, smallest=smallest) }}
{% if show_failures %}
<span class="big-number-status{% if danger_zone %}-failing{% endif %}">
<span class="big-number-status{% if danger_zone %} big-number-status--failing{% endif %}">
{% if failures %}
{% if failure_link %}
<a class="usa-link" href="{{ failure_link }}">

View File

@@ -55,7 +55,9 @@
<a href="/" title="notify.gov">notify.gov</a>
</em>
</div>
<button type="button" class="usa-menu-btn">Menu</button>
{% if current_user.is_authenticated %}
<button type="button" class="usa-menu-btn">Menu</button>
{% endif %}
</div>
<nav aria-label="Primary navigation" class="usa-nav">
<div class="usa-nav__inner">

View File

@@ -6,7 +6,7 @@
link_current_item=False,
root_element='h1'
) %}
<{{ root_element }} class="font-body-lg folder-heading"{% if root_element == 'h1' %} id="page-header"{% endif %}>
<{{ root_element }} class="font-body-lg folder-heading margin-bottom-0"{% if root_element == 'h1' %} id="page-header"{% endif %}>
{% for folder in folders %}
{% if loop.last and not link_current_item %}
{% if folder.template_type or not folder.id %}

View File

@@ -1,6 +1,6 @@
{% macro status_box(number, label, failing=false, percentage=None, url=None) %}
<div class="big-number-with-status">
<div class="big-number-status{% if failing %}-failing{% endif %}">
<div class="big-number-status{% if failing %} big-number-status--failing{% endif %}">
{% if url %}
<a class="usa-link" href="{{ url }}">{{ number }} {{ label }}</a>
{% else %}

View File

@@ -20,17 +20,17 @@
{% block headIcons %}
<link rel="shortcut icon" sizes="16x16 32x32 48x48" href="{{ assetPath | default('/assets') }}/images/favicon.ico" type="image/x-icon" />
<link rel="mask-icon" href="{{ assetPath | default('/assets') }}/images/govuk-mask-icon.svg" color="{{ themeColor | default('#0b0c0c') }}"> {# Hardcoded value of $govuk-black #}
<link rel="apple-touch-icon" sizes="180x180" href="{{ assetPath | default('/assets') }}/images/govuk-apple-touch-icon-180x180.png">
<link rel="apple-touch-icon" sizes="167x167" href="{{ assetPath | default('/assets') }}/images/govuk-apple-touch-icon-167x167.png">
<link rel="apple-touch-icon" sizes="152x152" href="{{ assetPath | default('/assets') }}/images/govuk-apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" href="{{ assetPath | default('/assets') }}/images/govuk-apple-touch-icon.png">
<link rel="mask-icon" href="{{ assetPath | default('/assets') }}/images/usa-mask-icon.svg" color="{{ themeColor | default('#F0F0F0') }}">
<link rel="apple-touch-icon" sizes="180x180" href="{{ assetPath | default('/assets') }}/images/apple-touch-icon.png">
<link rel="apple-touch-icon" href="{{ assetPath | default('/assets') }}/images/apple-touch-icon.png">
{% endblock %}
{% block head %}{% endblock %}
{# The default og:image is added below head so that scrapers see any custom metatags first, and this is just a fallback #}
{# image url needs to be absolute e.g. http://wwww.domain.com/.../govuk-opengraph-image.png #}
<!-- <meta property="og:image" content="{{ assetUrl | default('/assets') }}/images/govuk-opengraph-image.png"> -->
{% block meta %}
<meta property="og:site_name" content="Notify.gov">
<meta property="og:image" content="{{ asset_url('images/usa-opengraph-image.png') }}">
{% endblock %}
</head>
<body class="usa-template__body {{ bodyClasses }}">
<script nonce="{{ csp_nonce() }}">document.body.className = ((document.body.className) ? document.body.className + ' js-enabled' : 'js-enabled');</script>

View File

@@ -3,7 +3,7 @@
<div class="ajax-block-container">
{% if notifications_deleted %}
<div class="grid-row bottom-gutter-1-2">
<div class="grid-row ">
{% for label, query_param, url, count in counts %}
{% if query_param == 'pending' %}
<div class="grid-col-3">{{ big_number(count, query_param, smaller=True) }}</div>
@@ -14,7 +14,7 @@
{% endfor %}
</div>
{% else %}
<div class="bottom-gutter">
<div class="tabs">
{{ pill(counts, request.args.get('status', '')) }}
</div>
{% endif %}

View File

@@ -18,9 +18,9 @@
{{ form.name(param_extensions={"hint": {"text": "You can change this later"}}) }}
{% if not default_organization_type %}
<!--{% if not default_organization_type %}
{{ form.organization_type }}
{% endif %}
{% endif %}-->
{{ page_footer('Add service') }}

View File

@@ -12,7 +12,7 @@
API integration
</h1>
<nav class="grid-row bottom-gutter-1-2">
<nav class="grid-row">
<div class="grid-col-4">
<a class="usa-link pill-separate-item" href="{{ url_for('.api_keys', service_id=current_service.id) }}">API keys</a>
</div>

View File

@@ -15,7 +15,7 @@
{% block maincolumn_content %}
<div class="bottom-gutter-1-2">
<div class="">
{% call banner_wrapper(type='dangerous') %}
{% if recipients.too_many_rows %}

View File

@@ -21,11 +21,6 @@
{{ page_header('Preview of {}'.format(template.name)) }}
{{ usaSkipLink({
"text": "Skip to file contents",
"href": "#" + file_contents_header_id
}) }}
{{ template|string }}
<div class="bottom-gutter-3-2">
<form method="post" enctype="multipart/form-data" action="{{url_for('main.start_job', service_id=current_service.id, upload_id=upload_id)}}" class='page-footer'>

View File

@@ -4,6 +4,7 @@
{% from "components/table.html" import mapping_table, row, field, text_field, index_field, hidden_field_heading %}
{% from "components/file-upload.html" import file_upload %}
{% from "components/components/back-link/macro.njk" import usaBackLink %}
{% from "components/components/alert/macro.njk" import usaAlert %}
{% block service_page_title %}
Error
@@ -15,27 +16,31 @@
{% block maincolumn_content %}
<div class="bottom-gutter-1-2">
<div class="">
{% call banner_wrapper(type='dangerous') %}
{% if row_errors|length == 1 %}
<h1 class='banner-title' data-module="track-error" data-error-type="Bad rows" data-error-label="{{ upload_id }}">
Theres a problem with {{ original_file_name }}
</h1>
<p>
You need to {{ row_errors[0] }}.
</p>
<div class="usa-alert usa-alert--error" role="alert">
<div class="usa-alert__body">
<h1 class="usa-alert__heading">Theres a problem with {{ original_file_name }}</h1>
<p class="usa-alert__text">
You need to {{ row_errors[0] }}.
</p>
</div>
</div>
{% else %}
<h1 class='banner-title' data-module="track-error" data-error-type="Bad rows" data-error-label="{{ upload_id }}">
There are some problems with {{ original_file_name }}
</h1>
<p>
You need to:
</p>
<ul class="list-bullet">
{% for error in row_errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
<div class="usa-alert usa-alert--error" role="alert">
<div class="usa-alert__body">
<h1 class="usa-alert__heading">Theres a problem with {{ original_file_name }}</h1>
<p class="usa-alert__text">
You need to:
<ul class="list-bullet">
{% for error in row_errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
</p>
</div>
</div>
{% endif %}
{% endcall %}
</div>
@@ -49,7 +54,6 @@
button_text='Upload your file again'
) }}
</div>
<a href="#content" class="usa-link back-to-top-link">Back to top</a>
</div>
<div class="fullscreen-content" data-module="fullscreen-table">

View File

@@ -3,10 +3,10 @@
<div class="ajax-block-container">
{% if current_service.scheduled_job_stats.count %}
<h2 class="heading-medium heading-upcoming-jobs">
<h2 class="font-body-lg margin-y-0">
In the next few days
</h2>
<a class="usa-link banner-dashboard" href="{{ url_for('.uploads', service_id=current_service.id) }}">
<a class="usa-link banner-dashboard margin-bottom-2 display-block" href="{{ url_for('.uploads', service_id=current_service.id) }}">
<span class="banner-dashboard-count">
{{ current_service.scheduled_job_stats.count }}
</span>
@@ -18,7 +18,7 @@
{% endif %}
</span>
<span class="banner-dashboard-meta">
sending starts
- sending starts
{{ current_service.scheduled_job_stats.soonest_scheduled_for|format_datetime_relative }}
</span>
</a>

View File

@@ -19,7 +19,7 @@
{{ ajax_block(partials, updates_url, 'upcoming') }}
<h2 class="font-body-lg margin-top-0">
<h2 class="font-body-lg margin-top-0 margin-bottom-1">
In the last seven days
</h2>
@@ -30,7 +30,7 @@
{{ ajax_block(partials, updates_url, 'template-statistics') }}
{% if current_user.has_permissions('manage_service') %}
<h2 class='font-body-lg'>This year</h2>
<h2 class='font-body-lg margin-bottom-0'>This year</h2>
{{ ajax_block(partials, updates_url, 'usage') }}
{{ show_more(
url_for(".usage", service_id=current_service['id']),

View File

@@ -58,7 +58,7 @@
caption_visible=False
) %}
{% for message_status, description in [
('Sending', 'Notify has sent the message to the provider. The provider will try to deliver the message to the recipient for up to 72 hours. Notify is waiting for delivery information.'),
('Pending', 'Notify has sent the message to the provider. The provider will try to deliver the message to the recipient for up to 72 hours. Notify is waiting for delivery information.'),
('Sent', 'The mobile networks may not provide any more delivery information.'),
('Delivered', 'The message was successfully delivered. Notify cannot tell you if a user has opened or read a message.'),
('Not delivered', ('The provider could not deliver the message. This can happen if the phone number was wrong or if the network operator rejects the message. If youre sure that these phone numbers are correct, you should <a class="usa-link" href="' + url_for(".support") + '">contact us</a>. If not, you should remove them from your database. Youll still be charged for text messages that cannot be delivered.')|safe),

View File

@@ -20,7 +20,11 @@ data={
) }}
<p>During pilot period, each service has an allowance of 250,000 free text messages. Once this allowance is met, the
application will stop delivering messages. Theres no monthly charge, no setup fee and no procurement cost.</p>
application will stop delivering messages. There's no monthly charge, no setup fee and no procurement cost.</p>
<p>When a new service is added, it will start in <a class="usa-link" href="{{ url_for('main.trial_mode_new') }}">trial mode</a>. Moving a service out of trial mode is subject to approval by
the Notify.gov team.</p>
<p>You'll use more messages towards the allowance if you send text messages longer than 160 characters.
</p>
<p>
{% if not current_user.is_authenticated %}

View File

@@ -28,9 +28,9 @@
{{ form.placeholder_value(param_extensions={"classes": ""}) }}
</div>
{% if skip_link or link_to_upload %}
<div class="grid-col-12">
<div class="grid-col-12 margin-top-1">
{% if link_to_upload %}
<a class="usa-link" href="{{ url_for('.send_messages', service_id=current_service.id, template_id=template.id) }}">Upload a list of {{ 999|recipient_count_label(template.template_type) }}</a>
<a class="usa-link margin-right-2" href="{{ url_for('.send_messages', service_id=current_service.id, template_id=template.id) }}">Upload a list of {{ 999|recipient_count_label(template.template_type) }}</a>
{% endif %}
{% if skip_link %}
<a href="{{ skip_link[1] }}" class="usa-link">{{ skip_link[0] }}</a>

View File

@@ -53,7 +53,7 @@
<a class="usa-link" href="{{ url_for('.get_example_csv', service_id=current_service.id, template_id=template.id) }}" download>Download this example (<abbr title="Comma separated values">CSV</abbr>)</a>
</p>
<h2 class="font-body-lg">Your file will populate this template ({{ template.name }})</h2>
<h2 class="font-body-lg margin-bottom-1">Your file will populate this template:<br><span class="font-body-lg">({{ template.name }})</span></h2>
{{ template|string }}
{% endblock %}

View File

@@ -373,7 +373,7 @@
{% endif %}
{% if (not current_service.active) and current_user.platform_admin %}
<p>
<div class="hint bottom-gutter-1-2">
<div class="hint ">
Service suspended
</div>

View File

@@ -10,7 +10,7 @@
{% block maincolumn_content %}
<div class="bottom-gutter-1-2">
<div class="">
<h1 class="font-body-2xl margin-bottom-3">Choose a template</h1>
{{ folder_path(template_folder_path, current_service.id, template_type, current_user, root_element='h2') }}
</div>

View File

@@ -10,7 +10,7 @@
{% block maincolumn_content %}
<div class="bottom-gutter-1-2">
<div class="">
<h1 class="font-body-2xl margin-bottom-3">{{ page_title }}</h1>
{{ copy_folder_path(template_folder_path, current_service.id, from_service, current_user) }}
</div>

View File

@@ -9,7 +9,7 @@
{% block maincolumn_content %}
<div class="grid-row bottom-gutter-1-2">
<div class="grid-row ">
<div class="grid-col-12">
{{ folder_path(
folders=template_folder_path,

View File

@@ -32,7 +32,7 @@
{% else %}
<div class="grid-row">
<div class="grid-col-12">
<h1 class="font-body-lg folder-heading">
<h1 class="font-body-xl folder-heading margin-top-0">
Review your message
</h1>
{{ folder_path(
@@ -70,7 +70,7 @@
{% endif %}
</div>
<!--<div class="bottom-gutter-1-2">
<!--<div class="">
{{ copy_to_clipboard(template.id, name="Template ID", thing='template ID') }}
</div>-->

View File

@@ -1,3 +1,4 @@
from flask import current_app
from notifications_utils.recipients import RecipientCSV
from app.models.spreadsheet import Spreadsheet
@@ -75,7 +76,7 @@ def generate_notifications_csv(**kwargs):
fieldnames = (
["Row number"]
+ original_column_headers
+ ["Template", "Type", "Job", "Status", "Time"]
+ ["Template", "Type", "Sent by", "Job", "Status", "Time"]
)
else:
fieldnames = [
@@ -95,6 +96,7 @@ def generate_notifications_csv(**kwargs):
**kwargs
)
for notification in notifications_resp["notifications"]:
current_app.logger.info(notification)
if kwargs.get("job_id"):
values = (
[
@@ -107,6 +109,7 @@ def generate_notifications_csv(**kwargs):
+ [
notification["template_name"],
notification["template_type"],
notification["created_by_name"],
notification["job_name"],
notification["status"],
notification["created_at"],