mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-09-04 06:58:26 -04:00
Merge branch 'main' into 2171-clean-up-titles
This commit is contained in:
@@ -1024,3 +1024,7 @@ nav.nav {
|
||||
font-size: units(3);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-control-error {
|
||||
border: 4px solid #b10e1e
|
||||
}
|
||||
|
||||
@@ -401,7 +401,9 @@ def get_job_partials(job):
|
||||
)
|
||||
|
||||
if request.referrer is not None:
|
||||
session["arrived_from_preview_page"] = ("check" in request.referrer) or ("help=0" in request.referrer)
|
||||
session["arrived_from_preview_page"] = ("check" in request.referrer) or (
|
||||
"help=0" in request.referrer
|
||||
)
|
||||
else:
|
||||
session["arrived_from_preview_page"] = False
|
||||
|
||||
|
||||
@@ -116,6 +116,70 @@ def download_all_users():
|
||||
return response
|
||||
|
||||
|
||||
@main.route("/platform-admin/get-redis-report")
|
||||
@user_is_platform_admin
|
||||
def get_redis_report():
|
||||
|
||||
memory_info = redis_client.info("memory")
|
||||
memory_used = memory_info.get("used_memory_human", "N/A")
|
||||
max_memory = memory_info.get("maxmemory_human", "N/A")
|
||||
if max_memory == "0B":
|
||||
max_memory = "No set limit"
|
||||
mem_fragmentation = memory_info.get("mem_fragmentation_ratio", "N/A")
|
||||
frag_quality = "Swapping (bad)"
|
||||
if mem_fragmentation >= 1.0:
|
||||
frag_quality = "Healthy"
|
||||
if mem_fragmentation > 1.5:
|
||||
frag_quality = "Problematic"
|
||||
if mem_fragmentation > 2.0:
|
||||
frag_quality = "Severe fragmentation"
|
||||
|
||||
frag_note = ""
|
||||
if mem_fragmentation > 2.0:
|
||||
frag_note = "Use MEMORY PURGE.\nReplace multiple small keys with hashes.\nAvoid long keys.\nSet max_memory."
|
||||
elif mem_fragmentation < 1.0:
|
||||
frag_note = "Allocate more RAM.\nSet max_memory."
|
||||
|
||||
keys = redis_client.keys("*")
|
||||
key_details = []
|
||||
|
||||
for key in keys:
|
||||
key_type = redis_client.type(key).decode("utf-8")
|
||||
ttl = redis_client.ttl(key)
|
||||
ttl_str = "No Expiry" if ttl == -1 else f"{ttl} seconds"
|
||||
key_details.append(
|
||||
{"Key": key.decode("utf-8"), "Type": key_type, "TTL": ttl_str}
|
||||
)
|
||||
output = StringIO()
|
||||
writer = csv.writer(
|
||||
output,
|
||||
)
|
||||
writer.writerow(["Redis Report"])
|
||||
writer.writerow([])
|
||||
|
||||
writer.writerow(["Memory"])
|
||||
writer.writerow(["", "Memory Used", memory_used])
|
||||
writer.writerow(["", "Max Memory", max_memory])
|
||||
writer.writerow(["", "Memory Fragmentation Ratio", mem_fragmentation])
|
||||
writer.writerow(["", "Memory Fragmentation Quality", frag_quality, frag_note])
|
||||
writer.writerow([])
|
||||
|
||||
writer.writerow(["Keys Overview"])
|
||||
writer.writerow(["", "TTL", "Type", "Key"])
|
||||
for key_detail in key_details:
|
||||
writer.writerow(
|
||||
["", key_detail["TTL"], key_detail["Type"], key_detail["Key"][0:50]]
|
||||
)
|
||||
|
||||
csv_data = output.getvalue()
|
||||
|
||||
# Create a direct download response with the CSV data and appropriate headers
|
||||
response = Response(csv_data, content_type="text/csv; charset=utf-8")
|
||||
response.headers["Content-Disposition"] = "attachment; filename=redis.csv"
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def is_over_threshold(number, total, threshold):
|
||||
percentage = number / total * 100 if total else 0
|
||||
return percentage > threshold
|
||||
|
||||
@@ -68,11 +68,12 @@ def _get_access_token(code): # pragma: no cover
|
||||
id_token = get_id_token(response_json)
|
||||
nonce = id_token["nonce"]
|
||||
nonce_key = f"login-nonce-{unquote(nonce)}"
|
||||
stored_nonce = redis_client.get(nonce_key).decode("utf8")
|
||||
if not os.getenv("NOTIFY_ENVIRONMENT") == "development":
|
||||
stored_nonce = redis_client.get(nonce_key).decode("utf8")
|
||||
|
||||
if nonce != stored_nonce:
|
||||
current_app.logger.error(f"Nonce Error: {nonce} != {stored_nonce}")
|
||||
abort(403)
|
||||
if nonce != stored_nonce:
|
||||
current_app.logger.error(f"Nonce Error: {nonce} != {stored_nonce}")
|
||||
abort(403)
|
||||
|
||||
try:
|
||||
access_token = response_json["access_token"]
|
||||
@@ -112,7 +113,7 @@ def _do_login_dot_gov(): # $ pragma: no cover
|
||||
verify_key = f"login-verify_email-{unquote(state)}"
|
||||
verify_path = bool(redis_client.get(verify_key))
|
||||
|
||||
if not verify_path:
|
||||
if not verify_path and not os.getenv("NOTIFY_ENVIRONMENT") == "development":
|
||||
state_key = f"login-state-{unquote(state)}"
|
||||
stored_state = unquote(redis_client.get(state_key).decode("utf8"))
|
||||
if state != stored_state:
|
||||
|
||||
@@ -6,17 +6,36 @@ from app.notify_client import NotifyAdminAPIClient
|
||||
|
||||
class BillingAPIClient(NotifyAdminAPIClient):
|
||||
def get_monthly_usage_for_service(self, service_id, year):
|
||||
return self.get(
|
||||
monthly_usage = redis_client.get(f"monthly-usage-summary-{service_id}-{year}")
|
||||
if monthly_usage is not None:
|
||||
return json.loads(monthly_usage.decode("utf-8"))
|
||||
result = self.get(
|
||||
"/service/{0}/billing/monthly-usage".format(service_id),
|
||||
params=dict(year=year),
|
||||
)
|
||||
redis_client.set(
|
||||
f"monthly-usage-summary-{service_id}-{year}",
|
||||
json.dumps(result),
|
||||
ex=30,
|
||||
)
|
||||
return result
|
||||
|
||||
def get_annual_usage_for_service(self, service_id, year=None):
|
||||
return self.get(
|
||||
annual_usage = redis_client.get(f"yearly-usage-summary-{service_id}-{year}")
|
||||
if annual_usage is not None:
|
||||
return json.loads(annual_usage.decode("utf-8"))
|
||||
result = self.get(
|
||||
"/service/{0}/billing/yearly-usage-summary".format(service_id),
|
||||
params=dict(year=year),
|
||||
)
|
||||
|
||||
redis_client.set(
|
||||
f"yearly-usage-summary-{service_id}-{year}",
|
||||
json.dumps(result),
|
||||
ex=30,
|
||||
)
|
||||
return result
|
||||
|
||||
def get_free_sms_fragment_limit_for_year(self, service_id, year=None):
|
||||
frag_limit = redis_client.get(f"free-sms-fragment-limit-{service_id}-{year}")
|
||||
if frag_limit is not None:
|
||||
@@ -48,13 +67,28 @@ class BillingAPIClient(NotifyAdminAPIClient):
|
||||
)
|
||||
|
||||
def get_data_for_billing_report(self, start_date, end_date):
|
||||
return self.get(
|
||||
x_start_date = str(start_date)
|
||||
x_start_date = x_start_date.replace(" ", "_")
|
||||
x_end_date = str(end_date)
|
||||
x_end_date = x_end_date.replace(" ", "_")
|
||||
billing_data = redis_client.get(
|
||||
f"get-data-for-billing-report-{x_start_date}-{x_end_date}"
|
||||
)
|
||||
if billing_data is not None:
|
||||
return json.loads(billing_data.decode("utf-8"))
|
||||
result = self.get(
|
||||
url="/platform-stats/data-for-billing-report",
|
||||
params={
|
||||
"start_date": str(start_date),
|
||||
"end_date": str(end_date),
|
||||
},
|
||||
)
|
||||
redis_client.set(
|
||||
f"get-data-for-billing-report-{x_start_date}-{x_end_date}",
|
||||
json.dumps(result),
|
||||
ex=30,
|
||||
)
|
||||
return result
|
||||
|
||||
def get_data_for_volumes_by_service_report(self, start_date, end_date):
|
||||
return self.get(
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import json
|
||||
|
||||
from app.extensions import redis_client
|
||||
from app.notify_client import NotifyAdminAPIClient, _attach_current_user
|
||||
|
||||
|
||||
@@ -41,7 +44,7 @@ class NotificationApiClient(NotifyAdminAPIClient):
|
||||
if job_id:
|
||||
return method(
|
||||
url="/service/{}/job/{}/notifications".format(service_id, job_id),
|
||||
**kwargs
|
||||
**kwargs,
|
||||
)
|
||||
else:
|
||||
if limit_days is not None:
|
||||
@@ -96,9 +99,20 @@ class NotificationApiClient(NotifyAdminAPIClient):
|
||||
)
|
||||
|
||||
def get_notification_count_for_job_id(self, *, service_id, job_id):
|
||||
return self.get(
|
||||
counts = redis_client.get(
|
||||
f"notification-count-for-job-id-{service_id}-{job_id}"
|
||||
)
|
||||
if counts is not None:
|
||||
return json.loads(counts.decode("utf-8"))
|
||||
result = self.get(
|
||||
url="/service/{}/job/{}/notification_count".format(service_id, job_id)
|
||||
)["count"]
|
||||
)
|
||||
redis_client.set(
|
||||
f"notification-count-for-job-id-{service_id}-{job_id}",
|
||||
json.dumps(result["count"]),
|
||||
ex=30,
|
||||
)
|
||||
return result["count"]
|
||||
|
||||
|
||||
notification_api_client = NotificationApiClient()
|
||||
|
||||
@@ -14,10 +14,8 @@
|
||||
<script nonce="{{ csp_nonce() }}">document.body.className = ((document.body.className) ? document.body.className + ' js-enabled' : 'js-enabled');</script>
|
||||
{% block bodyStart %}
|
||||
{% block extra_javascripts_before_body %}
|
||||
<!-- Google Tag Manager (noscript) -->
|
||||
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-WX5NGWF"
|
||||
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
|
||||
<!-- End Google Tag Manager (noscript) -->
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -145,10 +143,8 @@
|
||||
{% block bodyEnd %}
|
||||
{% block extra_javascripts %}
|
||||
{% endblock %}
|
||||
<!--[if gt IE 8]><!-->
|
||||
<script type="text/javascript" src="{{ asset_url('javascripts/all.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ asset_url('js/uswds.min.js') }}"></script>
|
||||
<!--<![endif]-->
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
attributes: params.errorMessage.attributes,
|
||||
html: params.errorMessage.html,
|
||||
text: params.errorMessage.text,
|
||||
visuallyHiddenText: params.errorMessage.visuallyHiddenText
|
||||
visuallyHiddenText: params.errorMessage.visuallyHiddenText,
|
||||
}) | indent(2) | trim }}
|
||||
{% endif %}
|
||||
<input class="usa-input {%- if params.classes %} {{ params.classes }}{% endif %} {%- if params.errorMessage %} usa-input--error{% endif %}" id="{{ params.id }}" name="{{ params.name }}" type="{{ params.type | default('text') }}"
|
||||
@@ -42,5 +42,7 @@
|
||||
{%- if describedBy %} aria-describedby="{{ describedBy }}"{% endif %}
|
||||
{%- if params.autocomplete %} autocomplete="{{ params.autocomplete}}"{% endif %}
|
||||
{%- if params.pattern %} pattern="{{ params.pattern }}"{% endif %}
|
||||
{%- for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor -%}>
|
||||
{%- for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor -%}
|
||||
{%- if params.required %} required{% endif %}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -16,19 +16,9 @@
|
||||
placeholder=''
|
||||
) %}
|
||||
<div
|
||||
class="form-group{% if field.errors %} form-group-error{% endif %} {{ extra_form_group_classes }}"
|
||||
class="usa-form-group{% if field.errors %} usa-form-group--error{% endif %} {{ extra_form_group_classes }}"
|
||||
data-module="{% if autofocus %}autofocus{% elif colour_preview %}colour-preview{% endif %}"
|
||||
>
|
||||
{% if field.errors %}
|
||||
<div class="usa-alert usa-alert--error edit-textbox-error-mt" role="alert">
|
||||
<div class="usa-alert__body">
|
||||
<h4 class="usa-alert__heading">Error message</h4>
|
||||
<p class="usa-alert__text" data-module="track-error" data-error-type="{{ field.errors[0] }}" data-error-label="{{ field.name }}">
|
||||
{% if not safe_error_message %}{{ field.errors[0] }}{% else %}{{ field.errors[0]|safe }}{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<label class="usa-label" for="{{ field.name }}">
|
||||
{% if label %}
|
||||
{{ label }}
|
||||
@@ -41,6 +31,12 @@
|
||||
{{ hint }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if field.errors %}
|
||||
<span id="{{ field.name}}-error" class="usa-error-message" data-module="track-error" data-error-type="{{ field.errors[0] }}" data-error-label="{{ field.name }}" tabindex="-1" aria-live="assertive" role="alert">
|
||||
<span class="usa-sr-only">Error:</span>
|
||||
{% if not safe_error_message %}{{ field.errors[0] }}{% else %}{{ field.errors[0]|safe }}{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
{%
|
||||
if highlight_placeholders or autosize
|
||||
%}
|
||||
@@ -59,6 +55,8 @@
|
||||
data_highlight_placeholders='true' if highlight_placeholders else 'false',
|
||||
rows=rows|string,
|
||||
placeholder=placeholder,
|
||||
aria_describedby=field.name+"-error",
|
||||
required='required' if required else None,
|
||||
**kwargs
|
||||
) }}
|
||||
{% if suffix %}
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
<div class="tablet:grid-col-9 mobile-lg:grid-col-12">
|
||||
{{ form.name(param_extensions={
|
||||
"extra_form_group_classes": "margin-bottom-2",
|
||||
"id": "name",
|
||||
"required": True,
|
||||
"hint": {"text": "Your recipients will not see this"}
|
||||
}) }}
|
||||
{{ textbox(
|
||||
@@ -41,7 +43,8 @@
|
||||
hint=content_hint,
|
||||
rows=5,
|
||||
extra_form_group_classes='margin-bottom-1',
|
||||
placeholder='Edit me! Check out the Personalization section below for details on cool ((stuff)) you can do with your messages!'
|
||||
placeholder='Edit me! Check out the Personalization section below for details on cool ((stuff)) you can do with your messages!',
|
||||
required=True
|
||||
) }}
|
||||
{% if current_user.platform_admin %}
|
||||
{{ form.process_type }}
|
||||
|
||||
@@ -34,5 +34,8 @@
|
||||
<p>
|
||||
<a class="usa-link" href="{{ url_for('main.download_all_users') }}">Download All Users</a>
|
||||
</p>
|
||||
<p>
|
||||
<a class="usa-link" href="{{ url_for('main.get_redis_report') }}">Get Redis Report</a>
|
||||
</p>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
data_kwargs={'force-focus': True}
|
||||
) %}
|
||||
<div class="grid-row">
|
||||
<div class="grid-col-12 {% if form.placeholder_value.label.text == 'phone number' %}extra-tracking{% endif %}" aria-live="polite" role="alert">
|
||||
{{ form.placeholder_value(param_extensions={"classes": ""}) }}
|
||||
<div class="grid-col-12 {% if form.placeholder_value.label.text == 'phone number' %}extra-tracking{% endif %}">
|
||||
{{ form.placeholder_value(param_extensions={"classes": "", "id": "phone-number"}) }}
|
||||
</div>
|
||||
{% if skip_link or link_to_upload %}
|
||||
<div class="grid-col-12 margin-top-1">
|
||||
|
||||
@@ -90,8 +90,4 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!--<div class="">
|
||||
{{ copy_to_clipboard(template.id, name="Template ID", thing='template ID') }}
|
||||
</div>-->
|
||||
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user