mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-19 14:09:20 -04:00
urllib3 update
This commit is contained in:
@@ -134,11 +134,17 @@ def template_usage(service_id):
|
||||
months=months,
|
||||
stats=stats,
|
||||
most_used_template_count=max(
|
||||
max(
|
||||
(template["requested_count"] for template in month["templates_used"]),
|
||||
default=0,
|
||||
)
|
||||
for month in months
|
||||
(
|
||||
max(
|
||||
(
|
||||
template["requested_count"]
|
||||
for template in month["templates_used"]
|
||||
),
|
||||
default=0,
|
||||
)
|
||||
for month in months
|
||||
),
|
||||
default=0,
|
||||
),
|
||||
years=get_tuples_of_financial_years(
|
||||
partial(url_for, ".template_usage", service_id=service_id),
|
||||
@@ -155,31 +161,16 @@ def usage(service_id):
|
||||
year, current_financial_year = requested_and_current_financial_year(request)
|
||||
|
||||
free_sms_allowance = billing_api_client.get_free_sms_fragment_limit_for_year(
|
||||
service_id, year
|
||||
service_id
|
||||
)
|
||||
|
||||
units = billing_api_client.get_monthly_usage_for_service(service_id, year)
|
||||
|
||||
yearly_usage = billing_api_client.get_annual_usage_for_service(service_id, year)
|
||||
|
||||
more_stats = format_monthly_stats_to_list(
|
||||
service_api_client.get_monthly_notification_stats(service_id, year)["data"]
|
||||
)
|
||||
if year == current_financial_year:
|
||||
# This includes Oct, Nov, Dec
|
||||
# but we don't need next year's data yet
|
||||
more_stats = [
|
||||
month
|
||||
for month in more_stats
|
||||
if month["name"] in ["October", "November", "December"]
|
||||
]
|
||||
elif year == (current_financial_year + 1):
|
||||
# This is all the other months
|
||||
# and we need last year's data
|
||||
more_stats = [
|
||||
month
|
||||
for month in more_stats
|
||||
if month["name"] not in ["October", "November", "December"]
|
||||
]
|
||||
|
||||
return render_template(
|
||||
"views/usage.html",
|
||||
months=list(get_monthly_usage_breakdown(year, units, more_stats)),
|
||||
@@ -341,8 +332,15 @@ def get_dashboard_partials(service_id):
|
||||
dashboard_totals = (get_dashboard_totals(stats),)
|
||||
free_sms_allowance = billing_api_client.get_free_sms_fragment_limit_for_year(
|
||||
current_service.id,
|
||||
get_current_financial_year(),
|
||||
)
|
||||
# These 2 calls will update the dashboard sms allowance count while in trial mode.
|
||||
billing_api_client.get_monthly_usage_for_service(
|
||||
service_id, get_current_financial_year()
|
||||
)
|
||||
billing_api_client.create_or_update_free_sms_fragment_limit(
|
||||
service_id, free_sms_fragment_limit=free_sms_allowance
|
||||
)
|
||||
|
||||
yearly_usage = billing_api_client.get_annual_usage_for_service(
|
||||
service_id,
|
||||
get_current_financial_year(),
|
||||
@@ -433,13 +431,7 @@ def aggregate_status_types(counts_dict):
|
||||
|
||||
|
||||
def get_months_for_financial_year(year, time_format="%B"):
|
||||
return [
|
||||
month.strftime(time_format)
|
||||
for month in (
|
||||
get_months_for_year(10, 13, year) + get_months_for_year(1, 10, year + 1)
|
||||
)
|
||||
if month < datetime.now()
|
||||
]
|
||||
return [month.strftime(time_format) for month in (get_months_for_year(1, 13, year))]
|
||||
|
||||
|
||||
def get_months_for_year(start, end, year):
|
||||
|
||||
@@ -5,6 +5,7 @@ import uuid
|
||||
import jwt
|
||||
import requests
|
||||
from flask import (
|
||||
Response,
|
||||
abort,
|
||||
current_app,
|
||||
flash,
|
||||
@@ -26,6 +27,7 @@ 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
|
||||
from app.utils.time import is_less_than_days_ago
|
||||
|
||||
|
||||
def _reformat_keystring(orig):
|
||||
@@ -63,6 +65,10 @@ def _get_access_token(code, state):
|
||||
url = f"{base_url}{cli_assert}&{cli_assert_type}&{code_param}&grant_type=authorization_code"
|
||||
headers = {"Authorization": "Bearer %s" % token}
|
||||
response = requests.post(url, headers=headers)
|
||||
if response.json().get("access_token") is None:
|
||||
# Capture the response json here so it hopefully shows up in error reports
|
||||
current_app.logger.error(f"Error when getting access token {response.json()}")
|
||||
raise KeyError(f"'access_token' {response.json()}")
|
||||
access_token = response.json()["access_token"]
|
||||
return access_token
|
||||
|
||||
@@ -84,31 +90,59 @@ def _do_login_dot_gov():
|
||||
code = request.args.get("code")
|
||||
state = request.args.get("state")
|
||||
login_gov_error = request.args.get("error")
|
||||
if code and state:
|
||||
access_token = _get_access_token(code, state)
|
||||
user_email, user_uuid = _get_user_email_and_uuid(access_token)
|
||||
redirect_url = request.args.get("next")
|
||||
|
||||
if login_gov_error:
|
||||
current_app.logger.error(f"login.gov error: {login_gov_error}")
|
||||
raise Exception(f"Could not login with login.gov {login_gov_error}")
|
||||
elif code and state:
|
||||
|
||||
# activate the user
|
||||
try:
|
||||
access_token = _get_access_token(code, state)
|
||||
user_email, user_uuid = _get_user_email_and_uuid(access_token)
|
||||
redirect_url = request.args.get("next")
|
||||
user = user_api_client.get_user_by_uuid_or_email(user_uuid, user_email)
|
||||
activate_user(user["id"])
|
||||
|
||||
# Check if the email needs to be revalidated
|
||||
is_fresh_email = is_less_than_days_ago(
|
||||
user["email_access_validated_at"], 90
|
||||
)
|
||||
if not is_fresh_email:
|
||||
return verify_email(user, redirect_url)
|
||||
|
||||
usr = User.from_email_address(user["email_address"])
|
||||
activate_user(usr.id)
|
||||
except BaseException as be: # noqa B036
|
||||
current_app.logger.error(be)
|
||||
error(401)
|
||||
|
||||
return redirect(url_for("main.show_accounts_or_dashboard", next=redirect_url))
|
||||
|
||||
elif login_gov_error:
|
||||
current_app.logger.error(f"login.gov error: {login_gov_error}")
|
||||
raise Exception(f"Could not login with login.gov {login_gov_error}")
|
||||
# end login.gov
|
||||
|
||||
|
||||
def verify_email(user, redirect_url):
|
||||
user_api_client.send_verify_code(user["id"], "email", None, redirect_url)
|
||||
title = "Email resent" if request.args.get("email_resent") else "Check your email"
|
||||
redirect_url = request.args.get("next")
|
||||
return render_template(
|
||||
"views/re-validate-email-sent.html", title=title, redirect_url=redirect_url
|
||||
)
|
||||
|
||||
|
||||
@main.route("/sign-in", methods=(["GET", "POST"]))
|
||||
@hide_from_search_engines
|
||||
def sign_in():
|
||||
_do_login_dot_gov()
|
||||
# If we have to revalidated the email, send the message
|
||||
# via email and redirect to the "verify your email page"
|
||||
# and don't proceed further with login
|
||||
email_verify_template = _do_login_dot_gov()
|
||||
if (
|
||||
email_verify_template
|
||||
and not isinstance(email_verify_template, Response)
|
||||
and "Check your email" in email_verify_template
|
||||
):
|
||||
return email_verify_template
|
||||
|
||||
redirect_url = request.args.get("next")
|
||||
|
||||
if os.getenv("NOTIFY_E2E_TEST_EMAIL"):
|
||||
@@ -192,7 +226,6 @@ def sign_in():
|
||||
form=form,
|
||||
again=bool(redirect_url),
|
||||
other_device=other_device,
|
||||
login_gov_enabled=True,
|
||||
password_reset_url=password_reset_url,
|
||||
initial_signin_url=url,
|
||||
)
|
||||
|
||||
@@ -5,10 +5,10 @@ from itsdangerous import SignatureExpired
|
||||
from notifications_utils.url_safe_token import check_token
|
||||
|
||||
from app import user_api_client
|
||||
from app.extensions import redis_client
|
||||
from app.main import main
|
||||
from app.main.forms import TwoFactorForm
|
||||
from app.models.user import InvitedOrgUser, InvitedUser, User
|
||||
from app.models.user import User
|
||||
from app.notify_client import service_api_client
|
||||
from app.utils.login import redirect_to_sign_in
|
||||
|
||||
|
||||
@@ -67,48 +67,49 @@ def activate_user(user_id):
|
||||
user = User.from_id(user_id)
|
||||
|
||||
# This is the login.gov path
|
||||
login_gov_invite_data = redis_client.get(f"service-invite-{user.email_address}")
|
||||
try:
|
||||
login_gov_invite_data = service_api_client.retrieve_service_invite_data(
|
||||
f"service-invite-{user.email_address}"
|
||||
)
|
||||
except BaseException: # noqa
|
||||
# We will hit an exception if we can't find invite data,
|
||||
# but that will be the normal sign in use case
|
||||
login_gov_invite_data = None
|
||||
if login_gov_invite_data:
|
||||
login_gov_invite_data = json.loads(login_gov_invite_data.decode("utf8"))
|
||||
|
||||
# This is the deprecated path for organization invites where we get id from session
|
||||
session["current_session_id"] = user.current_session_id
|
||||
organization_id = session.get("organization_id")
|
||||
|
||||
activated_user = user.activate()
|
||||
activated_user.login()
|
||||
|
||||
# TODO when login.gov is mandatory, get rid of the if clause, it is deprecated.
|
||||
invited_user = InvitedUser.from_session()
|
||||
if invited_user:
|
||||
service_id = _add_invited_user_to_service(invited_user)
|
||||
return redirect(url_for("main.service_dashboard", service_id=service_id))
|
||||
elif login_gov_invite_data:
|
||||
login_gov_invite_data = json.loads(login_gov_invite_data)
|
||||
service_id = login_gov_invite_data["service_id"]
|
||||
user_id = user_id
|
||||
permissions = login_gov_invite_data["permissions"]
|
||||
folder_permissions = login_gov_invite_data["folder_permissions"]
|
||||
|
||||
user.add_to_service(
|
||||
service_id,
|
||||
login_gov_invite_data["permissions"],
|
||||
login_gov_invite_data["folder_permissions"],
|
||||
login_gov_invite_data["from_user_id"],
|
||||
)
|
||||
# Actually call the back end and add the user to the service
|
||||
try:
|
||||
user_api_client.add_user_to_service(
|
||||
service_id, user_id, permissions, folder_permissions
|
||||
)
|
||||
except BaseException as be: # noqa
|
||||
# TODO if the user is already part of service we should ignore
|
||||
current_app.logger.warning(f"Exception adding user to service {be}")
|
||||
|
||||
activated_user = user.activate()
|
||||
activated_user.login()
|
||||
return redirect(url_for("main.service_dashboard", service_id=service_id))
|
||||
|
||||
# TODO when login.gov is mandatory, git rid of the if clause, it is deprecated.
|
||||
invited_org_user = InvitedOrgUser.from_session()
|
||||
if invited_org_user:
|
||||
user_api_client.add_user_to_organization(invited_org_user.organization, user_id)
|
||||
elif redis_client.get(f"organization-invite-{user.email_address}"):
|
||||
organization_id = redis_client.raw_get(
|
||||
f"organization-invite-{user.email_address}"
|
||||
)
|
||||
user_api_client.add_user_to_organization(
|
||||
organization_id.decode("utf8"), user_id
|
||||
)
|
||||
# TODO add org invites back in the new way
|
||||
# organization_id = redis_client.raw_get(
|
||||
# f"organization-invite-{user.email_address}"
|
||||
# )
|
||||
# user_api_client.add_user_to_organization(
|
||||
# organization_id.decode("utf8"), user_id
|
||||
# )
|
||||
organization_id = None
|
||||
|
||||
if organization_id:
|
||||
return redirect(url_for("main.organization_dashboard", org_id=organization_id))
|
||||
else:
|
||||
activated_user = user.activate()
|
||||
activated_user.login()
|
||||
|
||||
return redirect(url_for("main.add_service", first="first"))
|
||||
|
||||
|
||||
|
||||
@@ -497,5 +497,18 @@ class ServiceAPIClient(NotifyAdminAPIClient):
|
||||
def get_global_notification_count(self, service_id):
|
||||
return self.get("/service/{}/notification-count".format(service_id))
|
||||
|
||||
def get_service_invite_data(self, redis_key):
|
||||
"""
|
||||
Retrieve service invite_data.
|
||||
"""
|
||||
return self.get("/service/invite/redis/{0}".format(redis_key))
|
||||
|
||||
|
||||
service_api_client = ServiceAPIClient()
|
||||
|
||||
|
||||
# TODO, if we try to call get_service_invite_data directly
|
||||
# from verify, app complains the method is not defined
|
||||
# If we wrap it like this, the app can find it.
|
||||
def retrieve_service_invite_data(redis_key):
|
||||
return service_api_client.get_service_invite_data(redis_key)
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
Sorry, we can't deliver what you asked for right now.
|
||||
</h1>
|
||||
<p class="usa-body">
|
||||
Please try again later or <a class="usa-link" href="mailto:notify-support@gsa.gov"></a>email us</a> for more information.</p>
|
||||
Please try again later or <a class="usa-link" href="mailto:notify-support@gsa.gov">email us</a> for more information.</p>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
30
app/templates/new/components/main_nav.html
Normal file
30
app/templates/new/components/main_nav.html
Normal file
@@ -0,0 +1,30 @@
|
||||
{% if help %}
|
||||
{% include 'partials/tour.html' %}
|
||||
{% else %}
|
||||
<nav class="nav margin-bottom-4">
|
||||
<a class="usa-button margin-top-1 margin-bottom-5 width-full"
|
||||
href="{{ url_for('.choose_template', service_id=current_service.id) }}">Send messages</a>
|
||||
<ul class="usa-sidenav">
|
||||
{% if current_user.has_permissions() %}
|
||||
{% if current_user.has_permissions('view_activity') %}
|
||||
<li class="usa-sidenav__item"><a class="{{ main_navigation.is_selected('dashboard') }}" href="{{ url_for('.service_dashboard', service_id=current_service.id) }}">Dashboard</a></li>
|
||||
{% endif %}
|
||||
{% if not current_user.has_permissions('view_activity') %}
|
||||
<li class="usa-sidenav__item"><a class="{{ casework_navigation.is_selected('sent-messages') }}" href="{{ url_for('.view_notifications', service_id=current_service.id, status='sending,delivered,failed') }}">Sent messages</a></li>
|
||||
{% endif %}
|
||||
{% if current_user.has_permissions('manage_service', allow_org_user=True) %}
|
||||
{# <li class="usa-sidenav__item"><a class="{{ main_navigation.is_selected('usage') }}" href="{{ url_for('.usage', service_id=current_service.id) }}">Usage</a></li> #}
|
||||
{% endif %}
|
||||
<!-- {% if current_user.has_permissions('manage_api_keys', 'manage_service') %}
|
||||
<li class="usa-sidenav__item"><a class="{{ main_navigation.is_selected('settings') }}" href="{{ url_for('.service_settings', service_id=current_service.id) }}">Settings</a></li>
|
||||
{% endif %} -->
|
||||
{% if current_user.has_permissions('manage_api_keys') %}
|
||||
<!-- <li><a class="usa-link{{ main_navigation.is_selected('api-integration') }}" href="{{ url_for('.api_integration', service_id=current_service.id) }}">API integration</a></li> -->
|
||||
{% endif %}
|
||||
{% elif current_user.has_permissions(allow_org_user=True) %}
|
||||
<li class="usa-sidenav__item"><a class="usa-link{{ main_navigation.is_selected('usage') }}" href="{{ url_for('.usage', service_id=current_service.id) }}">Usage</a></li>
|
||||
<li class="usa-sidenav__item"><a class="usa-link{{ main_navigation.is_selected('team-members') }}" href="{{ url_for('.manage_users', service_id=current_service.id) }}">Team members</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
11
app/templates/new/components/org_nav.html
Normal file
11
app/templates/new/components/org_nav.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<nav class="nav margin-bottom-4">
|
||||
<ul class="usa-sidenav">
|
||||
<li class="usa-sidenav__item"><a class="usa-link{{ org_navigation.is_selected('dashboard') }}" href="{{ url_for('.organization_dashboard', org_id=current_org.id) }}">Usage</a></li>
|
||||
<li class="usa-sidenav__item"><a class="usa-link{{ org_navigation.is_selected('team-members') }}" href="{{ url_for('.manage_org_users', org_id=current_org.id) }}">Team members</a></li>
|
||||
{% if current_user.platform_admin %}
|
||||
<li class="usa-sidenav__item"><a class="usa-link{{ org_navigation.is_selected('settings') }}" href="{{ url_for('.organization_settings', org_id=current_org.id) }}">Settings</a></li>
|
||||
<li class="usa-sidenav__item"><a class="usa-link{{ org_navigation.is_selected('trial-services') }}" href="{{ url_for('.organization_trial_mode_services', org_id=current_org.id) }}">Trial mode services</a></li>
|
||||
<li class="usa-sidenav__item"><a class="usa-link{{ org_navigation.is_selected('billing') }}" href="{{ url_for('.organization_billing', org_id=current_org.id) }}">Billing</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
11
app/templates/new/components/org_nav_breadcrumb.html
Normal file
11
app/templates/new/components/org_nav_breadcrumb.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<nav class="navigation-service usa-breadcrumb">
|
||||
<ol class="usa-breadcrumb__list">
|
||||
<li class="usa-breadcrumb__list-item">
|
||||
<span class="usa-breadcrumb__label"><a href="{{ url_for('.organizations') }}" class="usa-link navigation-organization-link">Organizations:</a></span>
|
||||
</li>
|
||||
<li class="usa-breadcrumb__list-item">
|
||||
<span class="usa-breadcrumb__label">{{ current_org.name }}</span>
|
||||
</li>
|
||||
<a href="{{ url_for('main.choose_account') }}" class="usa-link navigation-service">Switch service</a>
|
||||
</ol>
|
||||
</nav>
|
||||
15
app/templates/new/components/service_navigation.html
Normal file
15
app/templates/new/components/service_navigation.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<div class="navigation-service margin-top-5 display-flex flex-align-end flex-justify border-bottom padding-bottom-1">
|
||||
{% if current_service.organization_id %}
|
||||
{% if current_user.platform_admin or
|
||||
(current_user.belongs_to_organization(current_service.organization_id) and current_service.live) %}
|
||||
<a href="{{ url_for('.organization_dashboard', org_id=current_service.organization_id) }}" class="usa-link navigation-organization-link">{{ current_service.organization_name }}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<div class="font-body-2xl text-bold">
|
||||
{{ current_service.name }}
|
||||
{% if not current_service.active %}
|
||||
<span class="navigation-service-name navigation-service-type--suspended">Suspended</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<a href="{{ url_for('main.choose_account') }}" class="usa-link">Switch service</a>
|
||||
</div>
|
||||
16
app/templates/new/components/settings_navigation.html
Normal file
16
app/templates/new/components/settings_navigation.html
Normal file
@@ -0,0 +1,16 @@
|
||||
{% if help %}
|
||||
{% include 'partials/tour.html' %}
|
||||
{% else %}
|
||||
<nav class="nav">
|
||||
<ul class="usa-sidenav">
|
||||
{# {% if current_user.has_permissions() %} #}
|
||||
<li class="usa-sidenav__item"><a class="{{ main_navigation.is_selected('settings') }}"
|
||||
href="{{ url_for('main.service_settings', service_id=current_service.id) }}">General</a></li>
|
||||
<li class="usa-sidenav__item"><a class="{{ main_navigation.is_selected('user-profile') }}"
|
||||
href="{{ url_for('main.user_profile', service_id=current_service.id) }}">User profile</a></li>
|
||||
<li class="usa-sidenav__item"><a class="{{ main_navigation.is_selected('team-members') }}"
|
||||
href="{{ url_for('main.manage_users', service_id=current_service.id) }}">Team members</a></li>
|
||||
{# {% endif %} #}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
@@ -1,35 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block per_page_title %}
|
||||
{% block org_page_title %}{% endblock %} – {{ current_org.name }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="grid-container">
|
||||
<div class="navigation-service usa-breadcrumb">
|
||||
{% if current_user.platform_admin %}
|
||||
<a href="{{ url_for('.organizations') }}" class="usa-link navigation-organization-link">Organizations</a>
|
||||
{% endif %}
|
||||
<div class="navigation-service">
|
||||
{{ current_org.name }}
|
||||
</div>
|
||||
<a href="{{ url_for('main.choose_account') }}" class="usa-link navigation-service">Switch service</a>
|
||||
</div>
|
||||
<div class="grid-row">
|
||||
<div class="grid-col-3">
|
||||
{% include "org_nav.html" %}
|
||||
</div>
|
||||
<div class="grid-col-9">
|
||||
{% block beforeContent %}
|
||||
{% block backLink %}{% endblock %}
|
||||
{% endblock %}
|
||||
<main class="main" id="main-content" role="main" >
|
||||
{% block content %}
|
||||
{% include 'flash_messages.html' %}
|
||||
{% block maincolumn_content %}{% endblock %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,31 +1,47 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "/new/base.html" %}
|
||||
|
||||
{% block per_page_title %}
|
||||
{% block service_page_title %}{% endblock %} – {{ current_service.name }}
|
||||
{% block service_page_title %}{% endblock %}{% if current_service.name %} – {{ current_service.name }}{% endif %}
|
||||
{% block org_page_title %}{% endblock %}{% if current_org.name %} – {{ current_org.name }}{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="grid-container">
|
||||
{% block serviceNavigation %}
|
||||
{% include "service_navigation.html" %}
|
||||
{% if current_org.name %}
|
||||
{% else %}
|
||||
{% include "new/components/service_navigation.html" %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
<!-- The withnav_template can be used to replace the settings_template. when it comes to setting_template and withnav_template, this service_navigation.html is only used in withnav_template and in settings_template, it was not used. That is one out of the two differences between settings template and withnav template. Child templates that extends settings_template, include the block serviceNavigation but leave it empty. Within the app, the only pages settings_template.html is used: manage-users.html, service-settings.html, and user-profile.html -->
|
||||
{#
|
||||
The withnav_template can serve as a replacement for both settings_template and org_template.html.
|
||||
|
||||
The file service_navigation.html is included only in withnav_template. It's not used in settings_template. That is one out of the two differences between settings template and withnav template. As a result, when other templates extend settings_template, they include the serviceNavigation block but keep it empty. The settings_template.html is specifically used for these pages in the app: manage-users.html, service-settings.html, and user-profile.html.
|
||||
|
||||
In addition, serviceNavigation should be empty on templates that previously extended org_template. For templates that previously extended org_template.html, there's an addition of the orgNavBreadcrumb block.
|
||||
{% block orgNavBreadcrumb %}
|
||||
{% include "/new/components/org_nav_breadcrumb.html" %}
|
||||
{% endblock %}
|
||||
#}
|
||||
{% if current_org.name %}
|
||||
{% block orgNavBreadcrumb %}{% include "/new/components/org_nav_breadcrumb.html" %}{% endblock %}
|
||||
{% endif %}
|
||||
<div class="grid-row margin-top-5">
|
||||
{% if help %}
|
||||
<div class="grid-col-3">
|
||||
{% else %}
|
||||
<div class="grid-col-3">
|
||||
{% endif %}
|
||||
<div class="tablet:grid-col-3">
|
||||
{% block sideNavigation %}
|
||||
{% include "main_nav.html" %}
|
||||
<!-- include settings_nav.html for child templates that used settings_template -->
|
||||
{% if org_navigation_links %}
|
||||
{% include "/new/components/org_nav.html" %}
|
||||
{% else %}
|
||||
{% include "/new/components/main_nav.html" %}
|
||||
{% endif %}
|
||||
{#
|
||||
Include settings_nav.html for child templates that previously extended settings_template.
|
||||
|
||||
Include "org_nav.html" for child templates that previously extended org_template html
|
||||
#}
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% if help %}
|
||||
<div class="grid-col-8">
|
||||
{% else %}
|
||||
<div class="grid-col-9 padding-left-4">
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="tablet:grid-col-9 tablet:padding-left-4">
|
||||
{% block beforeContent %}
|
||||
{% block backLink %}{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -14,6 +14,10 @@ This document serves as a glossary for the templates directory structure of the
|
||||
- `head.html`: Template for the site's <head>, included in `base.html`.
|
||||
- `header.html`: Template for the site's header, included in `base.html`.
|
||||
- `footer.html`: Template for the site's footer, included in `base.html`.
|
||||
- `settings_navigation.html`: The settings navigation used in `withnav_template.html` that previously extended `settings_template.html`.
|
||||
- `org_nav.html`: The organization's navigation used solely in `org_template.html`.
|
||||
- `main_nav.html`: The main navigation used in `withnav_template.html`
|
||||
- `service_navigation.html`: The service navigation used in `withnav_template.html`. In withnav_template.html, the `serviceNavigation` block will be left empty in any child templates that previously extended `settings_template.html`.
|
||||
- **/views** (or **/pages**): Individual page templates that use the base layouts, components, and partials to present content.
|
||||
|
||||
### Best Practices
|
||||
@@ -30,9 +34,9 @@ This document serves as a glossary for the templates directory structure of the
|
||||
- withoutnav_template.html Delete
|
||||
- main_template.html Delete
|
||||
- settings_templates.html `withnav_template` can be used to replace `settings_template`.
|
||||
- settings_nav.html (move to /new/navigation directory)
|
||||
- main_nav.html (move to /new/navigation directory)
|
||||
- service_navigation.html (move to /new/navigation directory)
|
||||
- settings_nav.html (move to /components/ directory)
|
||||
- main_nav.html (move to /components/ directory)
|
||||
- service_navigation.html (move to /components/ directory)
|
||||
- org_template, could be under it's own directory called /layout/organization
|
||||
- org_nav.html (move to /new/navigation directory)
|
||||
- org_nav.html (move to /components/ directory)
|
||||
- content_template.html Delete
|
||||
|
||||
@@ -31,67 +31,70 @@
|
||||
|
||||
<div class="user-list">
|
||||
{% for user in users %}
|
||||
<div class="user-list-item">
|
||||
<h2 class="user-list-item-heading font-body-lg margin-top-0" title="{{ user.email_address }}">
|
||||
{%- if user.name -%}
|
||||
<span class="heading-small live-search-relevant">{{ user.name }}</span>
|
||||
{%- endif -%}
|
||||
{%- if user.status == 'pending' -%}
|
||||
<span class="live-search-relevant">{{ user.email_address }}</span><span class="hint">(invited)</span>
|
||||
{%- elif user.status == 'cancelled' -%}
|
||||
<span class="live-search-relevant">{{ user.email_address }}</span><span class="hint">(cancelled invite)</span>
|
||||
{%- elif user.status == 'expired' -%}
|
||||
<span class="live-search-relevant">{{ user.email_address }}</span><span class="hint">(expired invite)</span>
|
||||
{%- elif user.id == current_user.id -%}
|
||||
<span class="live-search-relevant"></span><span class="hint">(you)</span>
|
||||
{% else %}
|
||||
<span class="live-search-relevant">{{ user.email_address }}</span>
|
||||
{% endif %}
|
||||
</h2>
|
||||
<h3 class="margin-bottom-0">Permissions</h3>
|
||||
<ul class="tick-cross-list-permissions">
|
||||
{% for permission, label in permissions %}
|
||||
{{ tick_cross(
|
||||
user.has_permission_for_service(current_service.id, permission),
|
||||
label
|
||||
) }}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{# only show if the service has folders #}
|
||||
{% if current_service.all_template_folders %}
|
||||
<p class="usa-body tick-cross-list-hint">
|
||||
{% set folder_count = user.template_folders_for_service(current_service) | length %}
|
||||
{% if folder_count == 0 %}
|
||||
Cannot see any folders
|
||||
{% elif folder_count != current_service.all_template_folders | length %}
|
||||
Can see {{ folder_count }} folder{% if folder_count > 1 %}s{% endif %}
|
||||
{% if user.status != 'cancelled' %}
|
||||
<div class="user-list-item">
|
||||
<h2 class="user-list-item-heading font-body-lg margin-top-0" title="{{ user.email_address }}">
|
||||
{%- if user.name -%}
|
||||
<span class="heading-small live-search-relevant">{{ user.name }}</span>
|
||||
{%- endif -%}
|
||||
{%- if user.status == 'pending' -%}
|
||||
<span class="live-search-relevant">{{ user.email_address }}</span><span class="hint">(invited)</span>
|
||||
{%- elif user.status == 'cancelled' -%}
|
||||
<span class="live-search-relevant">{{ user.email_address }}</span><span class="hint">(cancelled invite)</span>
|
||||
{%- elif user.status == 'expired' -%}
|
||||
<span class="live-search-relevant">{{ user.email_address }}</span><span class="hint">(expired invite)</span>
|
||||
{%- elif user.id == current_user.id -%}
|
||||
<span class="live-search-relevant"></span><span class="hint">(you)</span>
|
||||
{% else %}
|
||||
Can see all folders
|
||||
{% endif%}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if current_service.has_permission('email_auth') %}
|
||||
<p class="usa-body tick-cross-list-hint">
|
||||
Signs in with
|
||||
{{ user.auth_type | format_auth_type(with_indefinite_article=True) }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if current_service.has_permission('email_auth') %}
|
||||
<p class="usa-body tick-cross-list-hint">
|
||||
Signs in with
|
||||
{{ user.auth_type | format_auth_type(with_indefinite_article=True) }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if current_user.has_permissions('manage_service') %}
|
||||
{% if user.status == 'pending' %}
|
||||
<a class="user-list-edit-link usa-link" href="{{ url_for('.cancel_invited_user', service_id=current_service.id, invited_user_id=user.id)}}">Cancel invitation<span class="usa-sr-only"> for {{ user.email_address }}</span></a>
|
||||
{% elif user.status == 'expired' %}
|
||||
<a class="user-list-edit-link usa-link" href="{{ url_for('.resend_invite', service_id=current_service.id, invited_user_id=user.id)}}">Resend invite<span class="usa-sr-only"> for {{ user.email_address }}</span></a>
|
||||
{% elif user.is_editable_by(current_user) %}
|
||||
<a class="user-list-edit-link usa-link" href="{{ url_for('.edit_user_permissions', service_id=current_service.id, user_id=user.id)}}">Change details<span class="usa-sr-only"> for {{ user.name }} {{ user.email_address }}</span></a>
|
||||
<span class="live-search-relevant">{{ user.email_address }}</span>
|
||||
{% endif %}
|
||||
</h2>
|
||||
<h3 class="margin-bottom-0">Permissions</h3>
|
||||
<ul class="tick-cross-list-permissions">
|
||||
{% for permission, label in permissions %}
|
||||
{{ tick_cross(
|
||||
user.has_permission_for_service(current_service.id, permission),
|
||||
label
|
||||
) }}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{# only show if the service has folders #}
|
||||
{% if current_service.all_template_folders %}
|
||||
<p class="usa-body tick-cross-list-hint">
|
||||
{% set folder_count = user.template_folders_for_service(current_service) | length %}
|
||||
{% if folder_count == 0 %}
|
||||
Cannot see any folders
|
||||
{% elif folder_count != current_service.all_template_folders | length %}
|
||||
Can see {{ folder_count }} folder{% if folder_count > 1 %}s{% endif %}
|
||||
{% else %}
|
||||
Can see all folders
|
||||
{% endif%}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if current_service.has_permission('email_auth') %}
|
||||
<p class="usa-body tick-cross-list-hint">
|
||||
Signs in with
|
||||
{{ user.auth_type | format_auth_type(with_indefinite_article=True) }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if current_service.has_permission('email_auth') %}
|
||||
<p class="usa-body tick-cross-list-hint">
|
||||
Signs in with
|
||||
{{ user.auth_type | format_auth_type(with_indefinite_article=True) }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if current_user.has_permissions('manage_service') %}
|
||||
{% if user.status == 'pending' or user.status == 'expired' %}
|
||||
<a class="user-list-edit-link usa-link" href="{{ url_for('.cancel_invited_user', service_id=current_service.id, invited_user_id=user.id)}}">Cancel invitation<span class="usa-sr-only"> for {{ user.email_address }}</span></a>
|
||||
{% endif %}
|
||||
{% if user.status == 'expired' %}
|
||||
<a class="user-list-edit-link usa-link" href="{{ url_for('.resend_invite', service_id=current_service.id, invited_user_id=user.id)}}">Resend invite<span class="usa-sr-only"> for {{ user.email_address }}</span></a>
|
||||
{% elif user.is_editable_by(current_user) %}
|
||||
<a class="user-list-edit-link usa-link" href="{{ url_for('.edit_user_permissions', service_id=current_service.id, user_id=user.id)}}">Change details<span class="usa-sr-only"> for {{ user.name }} {{ user.email_address }}</span></a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -30,22 +30,6 @@
|
||||
}}
|
||||
{% endcall %}
|
||||
|
||||
{% call row() %}
|
||||
{{ text_field('Sign-in method') }}
|
||||
{{ text_field(
|
||||
'Email link or text message code'
|
||||
if 'email_auth' in current_service.permissions
|
||||
else 'Text message code'
|
||||
) }}
|
||||
{{ edit_field(
|
||||
'Change',
|
||||
url_for('.service_set_auth_type', service_id=current_service.id),
|
||||
permissions=['manage_service'],
|
||||
suffix='sign-in method',
|
||||
)
|
||||
}}
|
||||
{% endcall %}
|
||||
|
||||
{% call row() %}
|
||||
{{ text_field('Send text messages') }}
|
||||
{{ boolean_field('sms' in current_service.permissions) }}
|
||||
|
||||
@@ -16,28 +16,9 @@
|
||||
<div class="grid-row">
|
||||
<div class="grid-col-10">
|
||||
{{ page_header('Sign-in method') }}
|
||||
{% if 'email_auth' in current_service.permissions %}
|
||||
<p class="heading-small bottom-gutter-2-3">
|
||||
Email link or text message code
|
||||
</p>
|
||||
<p>
|
||||
Your team members can sign in with either a text message code
|
||||
or an email link.
|
||||
</p>
|
||||
<p>
|
||||
You can <a class="usa-link" href="{{ url_for('.manage_users', service_id=current_service.id) }}">set the sign-in method for individual team members</a>.
|
||||
</p>
|
||||
{% else %}
|
||||
<p class="heading-small bottom-gutter-2-3">
|
||||
Text message code
|
||||
</p>
|
||||
<p>
|
||||
Your team members sign in with a text message code.
|
||||
</p>
|
||||
<p>
|
||||
<a class="usa-link" href="{{ url_for('.support') }}">Contact us</a> if signing in with a text message is a problem for your team.
|
||||
</p>
|
||||
{% endif %}
|
||||
<p>Your username, password, and multi-factor authentication options are handled by Login.gov.</p>
|
||||
<p>To make changes, head to <a href="https://login.gov/">Login.gov</a> and sign-in with your credentials.</p>
|
||||
<p>Any changes made to your Login.gov account will automatically be synced with Notify.gov.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ Set up your profile
|
||||
{{ form.name(param_extensions={}) }}
|
||||
<div class="extra-tracking">
|
||||
{{ form.mobile_number(param_extensions={
|
||||
"hint": {"text": "We'll send you a security code by text message"},
|
||||
"hint": {"text": "We need your number so you can send yourself test texts"},
|
||||
}) }}
|
||||
</div>
|
||||
<!--{{ usaSelect({
|
||||
|
||||
@@ -12,18 +12,6 @@
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
{% if login_gov_enabled %}
|
||||
<div class="grid-row">
|
||||
<div id="countdown-container" class="usa-alert usa-alert--warning width-full margin-bottom-4">
|
||||
<div class="usa-alert__body">
|
||||
<h4 class="usa-alert__heading">Login.gov is required by April 16, 2024</h4>
|
||||
<p class="usa-alert__text">
|
||||
You have <span id="countdown"></span> left to use Login.gov to sign in
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="grid-row margin-bottom-4">
|
||||
<div class="tablet:grid-col-5">
|
||||
{% if again %}
|
||||
@@ -37,24 +25,15 @@
|
||||
We signed you out because you have not used Notify for a while.
|
||||
</p>
|
||||
{% endif %}
|
||||
<a class="usa-link usa-button" href="{{ initial_signin_url }}">Sign in with Login.gov</a>
|
||||
{% else %}
|
||||
<h1 class="font-body-2xl margin-bottom-3">Sign in</h1>
|
||||
{% if login_gov_enabled %}
|
||||
<p>You can access your account by signing in with one of the options below:</p>
|
||||
<a class="usa-link usa-button usa-button--outline" href="{{ initial_signin_url }}">Sign in with Login.gov</a>
|
||||
<p class="margin-y-3"><strong>Or:</strong></p>
|
||||
{% endif %}
|
||||
<p>Access your Notify.gov account by signing in with Login.gov:</p>
|
||||
<a class="usa-link usa-button" href="{{ initial_signin_url }}">Sign in with Login.gov</a>
|
||||
{% endif %}
|
||||
|
||||
{% call form_wrapper(autocomplete=True) %}
|
||||
{{ form.email_address(param_extensions={"autocomplete": "email"}) }}
|
||||
{{ form.password(param_extensions={"autocomplete": "current-password"}) }}
|
||||
{{ page_footer("Continue", secondary_link=password_reset_url, secondary_link_text="Forgot your password?") }}
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% if login_gov_enabled %}
|
||||
<div class="tablet:grid-col-6 tablet:grid-offset-1 margin-top-2 padding-y-2 padding-x-4 bg-base-lightest">
|
||||
<h2 class="font-body-lg">Notify.gov is changing the sign-in experience to Login.gov effective<br>April 16, 2024</h2>
|
||||
<h2 class="font-body-lg">Effective April 16, 2024 Notify.gov requires you sign-in through Login.gov</h2>
|
||||
<p>Why are we doing this?</p>
|
||||
<ul class="usa-list">
|
||||
<li><strong>Enhanced security:</strong> Login.gov is really secure and trustworthy</li>
|
||||
@@ -64,12 +43,11 @@
|
||||
<p>What do I need to do?</p>
|
||||
<ul class="usa-list">
|
||||
<li>If you have a Login.gov account, start using it to sign in to Notify today.</li>
|
||||
<li>If you don’t have a Login.gov account, you must create one by April 16, 2024 to continue to access Notify.</li>
|
||||
<li>If you don’t have a Login.gov account, you must create one to continue to access Notify.</li>
|
||||
</ul>
|
||||
<div class="border-bottom border-base-lighter margin-y-4"></div>
|
||||
<a class="usa-link usa-button margin-bottom-3" href="{{ initial_signin_url }}">Create Login.gov account</a>
|
||||
<a class="usa-link usa-button usa-button--outline margin-bottom-3" href="{{ initial_signin_url }}">Create Login.gov account</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -27,21 +27,6 @@
|
||||
}}
|
||||
{% endcall %}
|
||||
|
||||
{% call row() %}
|
||||
{{ text_field('Email address') }}
|
||||
{{ text_field(current_user.email_address) }}
|
||||
{% if can_see_edit %}
|
||||
{{ edit_field(
|
||||
'Change',
|
||||
url_for('.user_profile_email'),
|
||||
suffix='email address'
|
||||
)
|
||||
}}
|
||||
{% else %}
|
||||
{{ text_field('') }}
|
||||
{% endif %}
|
||||
{% endcall %}
|
||||
|
||||
{% call row() %}
|
||||
{{ text_field('Mobile number') }}
|
||||
{{ optional_text_field(current_user.mobile_number) }}
|
||||
@@ -53,16 +38,6 @@
|
||||
}}
|
||||
{% endcall %}
|
||||
|
||||
{% call row() %}
|
||||
{{ text_field('Password') }}
|
||||
{{ text_field('Last changed ' + current_user.password_changed_at|format_delta) }}
|
||||
{{ edit_field(
|
||||
'Change',
|
||||
url_for('.user_profile_password'),
|
||||
suffix='password'
|
||||
)
|
||||
}}
|
||||
{% endcall %}
|
||||
{% call row() %}
|
||||
{{ text_field('Preferred Timezone') }}
|
||||
{{ optional_text_field(current_user.preferred_timezone) }}
|
||||
@@ -90,4 +65,9 @@
|
||||
{% endcall %}
|
||||
</div>
|
||||
|
||||
<h2>Sign-in method</h2>
|
||||
<p>Your username, password, and multi-factor authentication options are handled by Login.gov. </p>
|
||||
<p>To make changes, head to <a href="https://secure.login.gov/">Login.gov</a>
|
||||
and sign-in with your credentials. Any changes made to your Login.gov account will automatically be synced with Notify.gov.</p>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -11,7 +11,7 @@ def get_current_financial_year():
|
||||
now = datetime.now(preferred_tz)
|
||||
current_month = int(now.strftime("%-m"))
|
||||
current_year = int(now.strftime("%Y"))
|
||||
return current_year if current_month > 9 else current_year - 1
|
||||
return current_year if current_month < 10 else current_year + 1
|
||||
|
||||
|
||||
def is_less_than_days_ago(date_from_db, number_of_days):
|
||||
|
||||
Reference in New Issue
Block a user