Merge branch 'main' into 42-badges-fixes
Browse files- CHANGELOG.md +22 -0
- static/css/custom.css +7 -0
- templates/attestations.html +8 -2
- templates/policy_results.html +12 -8
- templates/products.html +6 -3
- version.py +1 -1
- web/products.py +4 -2
- web/routes.py +23 -15
CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
| 2 |
|
| 3 |
<!-- version list -->
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
## v1.0.5 (2025-09-29)
|
| 6 |
|
| 7 |
### Bug Fixes
|
|
|
|
| 2 |
|
| 3 |
<!-- version list -->
|
| 4 |
|
| 5 |
+
## v1.1.1 (2025-10-05)
|
| 6 |
+
|
| 7 |
+
### Bug Fixes
|
| 8 |
+
|
| 9 |
+
- Add content_type to attestation table
|
| 10 |
+
([`2fd68a5`](https://github.com/scribe-security/attstore/commit/2fd68a52b8284049026d4a64545675343a3b8116))
|
| 11 |
+
|
| 12 |
+
- Lock colors
|
| 13 |
+
([`ee842bb`](https://github.com/scribe-security/attstore/commit/ee842bbb817fba4747d18fa334f5947e6097dc30))
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
## v1.1.0 (2025-10-05)
|
| 17 |
+
|
| 18 |
+
### Features
|
| 19 |
+
|
| 20 |
+
- Filter products by version substrings
|
| 21 |
+
([`aa9e4d9`](https://github.com/scribe-security/attstore/commit/aa9e4d95741fc70f1801d517af9e8a72637a6538))
|
| 22 |
+
|
| 23 |
+
- Support filtering attestations and policy results by version.
|
| 24 |
+
([`735e852`](https://github.com/scribe-security/attstore/commit/735e85285c74372db35d6e0237b15ae3e0b83ede))
|
| 25 |
+
|
| 26 |
+
|
| 27 |
## v1.0.5 (2025-09-29)
|
| 28 |
|
| 29 |
### Bug Fixes
|
static/css/custom.css
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
/* Custom CSS for Attestation Store */
|
| 2 |
|
| 3 |
/* Root variables for consistency */
|
|
|
|
| 1 |
+
/* Custom lock icon colors for attestation page */
|
| 2 |
+
.icon-lock-locked {
|
| 3 |
+
color: #4fc3f7 !important; /* Light blue */
|
| 4 |
+
}
|
| 5 |
+
.icon-lock-unlocked {
|
| 6 |
+
color: #ba68c8 !important; /* Purple */
|
| 7 |
+
}
|
| 8 |
/* Custom CSS for Attestation Store */
|
| 9 |
|
| 10 |
/* Root variables for consistency */
|
templates/attestations.html
CHANGED
|
@@ -32,6 +32,9 @@
|
|
| 32 |
{% endfor %}
|
| 33 |
</select>
|
| 34 |
</div>
|
|
|
|
|
|
|
|
|
|
| 35 |
<div class="col-md-3">
|
| 36 |
<select class="form-select" name="target_type" aria-label="Filter by target type">
|
| 37 |
<option value="">Target Type (all)</option>
|
|
@@ -123,11 +126,14 @@
|
|
| 123 |
<td>
|
| 124 |
<div class="d-inline-flex align-items-center">
|
| 125 |
{% if attestation.sig_status == 'signed' %}
|
| 126 |
-
<i data-feather="lock" class="{% if attestation.deleted %}text-secondary{% else %}
|
| 127 |
{% else %}
|
| 128 |
-
<i data-feather="unlock" class="{% if attestation.deleted %}text-secondary{% else %}
|
| 129 |
{% endif %}
|
| 130 |
{{ attestation.target_type }}
|
|
|
|
|
|
|
|
|
|
| 131 |
</div>
|
| 132 |
</td>
|
| 133 |
<td>{{ attestation.tool or 'Unknown' }}</td>
|
|
|
|
| 32 |
{% endfor %}
|
| 33 |
</select>
|
| 34 |
</div>
|
| 35 |
+
<div class="col-md-2">
|
| 36 |
+
<input type="text" class="form-control" name="version" placeholder="Version (substring)" value="{{ version_filter }}">
|
| 37 |
+
</div>
|
| 38 |
<div class="col-md-3">
|
| 39 |
<select class="form-select" name="target_type" aria-label="Filter by target type">
|
| 40 |
<option value="">Target Type (all)</option>
|
|
|
|
| 126 |
<td>
|
| 127 |
<div class="d-inline-flex align-items-center">
|
| 128 |
{% if attestation.sig_status == 'signed' %}
|
| 129 |
+
<i data-feather="lock" class="{% if attestation.deleted %}text-secondary{% else %}icon-lock-locked{% endif %} me-2" style="width: 16px; height: 16px;" title="Signed attestation"></i>
|
| 130 |
{% else %}
|
| 131 |
+
<i data-feather="unlock" class="{% if attestation.deleted %}text-secondary{% else %}icon-lock-unlocked{% endif %} me-2" style="width: 16px; height: 16px;" title="Unsigned attestation"></i>
|
| 132 |
{% endif %}
|
| 133 |
{{ attestation.target_type }}
|
| 134 |
+
{% if attestation.content_type %}
|
| 135 |
+
<span class="badge bg-secondary ms-2">{{ attestation.content_type }}</span>
|
| 136 |
+
{% endif %}
|
| 137 |
</div>
|
| 138 |
</td>
|
| 139 |
<td>{{ attestation.tool or 'Unknown' }}</td>
|
templates/policy_results.html
CHANGED
|
@@ -70,6 +70,9 @@
|
|
| 70 |
{% endfor %}
|
| 71 |
</select>
|
| 72 |
</div>
|
|
|
|
|
|
|
|
|
|
| 73 |
<div class="col-md-3">
|
| 74 |
<input type="text" class="form-control" name="rule_id" placeholder="Rule ID (substring)" value="{{ rule_id_filter }}">
|
| 75 |
</div>
|
|
@@ -194,14 +197,15 @@
|
|
| 194 |
<!-- Pagination -->
|
| 195 |
{% if pagination.pages > 1 %}
|
| 196 |
{% macro page_url(page_num) -%}
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
|
|
|
| 205 |
{%- endmacro %}
|
| 206 |
<nav class="mt-4">
|
| 207 |
<ul class="pagination justify-content-center">
|
|
|
|
| 70 |
{% endfor %}
|
| 71 |
</select>
|
| 72 |
</div>
|
| 73 |
+
<div class="col-md-2">
|
| 74 |
+
<input type="text" class="form-control" name="version" placeholder="Version (substring)" value="{{ version_filter }}">
|
| 75 |
+
</div>
|
| 76 |
<div class="col-md-3">
|
| 77 |
<input type="text" class="form-control" name="rule_id" placeholder="Rule ID (substring)" value="{{ rule_id_filter }}">
|
| 78 |
</div>
|
|
|
|
| 197 |
<!-- Pagination -->
|
| 198 |
{% if pagination.pages > 1 %}
|
| 199 |
{% macro page_url(page_num) -%}
|
| 200 |
+
{{ url_for('web.policy_results') }}?
|
| 201 |
+
{%- if product_filter %}product={{ product_filter|urlencode }}&{% endif -%}
|
| 202 |
+
{%- if version_filter %}version={{ version_filter|urlencode }}&{% endif -%}
|
| 203 |
+
{%- if rule_id_filter %}rule_id={{ rule_id_filter|urlencode }}&{% endif -%}
|
| 204 |
+
{%- if result_kind_filter %}result_kind={{ result_kind_filter|urlencode }}&{% endif -%}
|
| 205 |
+
{%- if result_level_filter %}result_level={{ result_level_filter|urlencode }}&{% endif -%}
|
| 206 |
+
{%- if show_all_users and is_admin %}show_all_users=on&{% endif -%}
|
| 207 |
+
{%- if user_filter and show_all_users and is_admin %}user_filter={{ user_filter }}&{% endif -%}
|
| 208 |
+
page={{ page_num }}
|
| 209 |
{%- endmacro %}
|
| 210 |
<nav class="mt-4">
|
| 211 |
<ul class="pagination justify-content-center">
|
templates/products.html
CHANGED
|
@@ -19,9 +19,12 @@
|
|
| 19 |
<div class="card mb-4">
|
| 20 |
<div class="card-body">
|
| 21 |
<form method="GET" class="row g-3 align-items-center">
|
| 22 |
-
<div class="col-md-
|
| 23 |
<input type="text" class="form-control" name="filter" placeholder="Products substring" value="{{ filter_query }}">
|
| 24 |
</div>
|
|
|
|
|
|
|
|
|
|
| 25 |
{% if is_admin %}
|
| 26 |
<div class="col-md-3 form-check align-items-center d-flex">
|
| 27 |
<input class="form-check-input me-2" type="checkbox" id="showAllCheckbox" name="show_all" value="1" {% if show_all %}checked{% endif %} onchange="this.form.submit()">
|
|
@@ -85,10 +88,10 @@
|
|
| 85 |
{% endfor %}
|
| 86 |
</td>
|
| 87 |
<td class="text-center">
|
| 88 |
-
<a href="/products/{{ p.product }}/attestations" class="btn btn-sm btn-outline-primary me-1">
|
| 89 |
<i data-feather="file-text" class="me-1"></i> Attestations
|
| 90 |
</a>
|
| 91 |
-
<a href="/products/{{ p.product }}/policy_results" class="btn btn-sm btn-outline-info">
|
| 92 |
<i data-feather="shield" class="me-1"></i> Policy Results
|
| 93 |
</a>
|
| 94 |
</td>
|
|
|
|
| 19 |
<div class="card mb-4">
|
| 20 |
<div class="card-body">
|
| 21 |
<form method="GET" class="row g-3 align-items-center">
|
| 22 |
+
<div class="col-md-3">
|
| 23 |
<input type="text" class="form-control" name="filter" placeholder="Products substring" value="{{ filter_query }}">
|
| 24 |
</div>
|
| 25 |
+
<div class="col-md-3">
|
| 26 |
+
<input type="text" class="form-control" name="version" placeholder="Version substring" value="{{ version_filter }}">
|
| 27 |
+
</div>
|
| 28 |
{% if is_admin %}
|
| 29 |
<div class="col-md-3 form-check align-items-center d-flex">
|
| 30 |
<input class="form-check-input me-2" type="checkbox" id="showAllCheckbox" name="show_all" value="1" {% if show_all %}checked{% endif %} onchange="this.form.submit()">
|
|
|
|
| 88 |
{% endfor %}
|
| 89 |
</td>
|
| 90 |
<td class="text-center">
|
| 91 |
+
<a href="/products/{{ p.product }}/{{ p.version }}/attestations" class="btn btn-sm btn-outline-primary me-1">
|
| 92 |
<i data-feather="file-text" class="me-1"></i> Attestations
|
| 93 |
</a>
|
| 94 |
+
<a href="/products/{{ p.product }}/{{ p.version }}/policy_results" class="btn btn-sm btn-outline-info">
|
| 95 |
<i data-feather="shield" class="me-1"></i> Policy Results
|
| 96 |
</a>
|
| 97 |
</td>
|
version.py
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
__version__ = "1.
|
|
|
|
| 1 |
+
__version__ = "1.1.1"
|
web/products.py
CHANGED
|
@@ -2,7 +2,7 @@ from flask import Blueprint, render_template, request, redirect, url_for, sessio
|
|
| 2 |
from models import db, Attestation, PolicyResult, Product
|
| 3 |
from sqlalchemy import func, text
|
| 4 |
|
| 5 |
-
def get_products_with_stats(filter_query=None, user_id=None, show_all=False):
|
| 6 |
# Only consume data from products table
|
| 7 |
query = Product.query.filter(Product.deleted == False)
|
| 8 |
if not show_all and user_id is not None:
|
|
@@ -12,6 +12,9 @@ def get_products_with_stats(filter_query=None, user_id=None, show_all=False):
|
|
| 12 |
query = query.filter(
|
| 13 |
Product.product_key.ilike(like_pattern)
|
| 14 |
)
|
|
|
|
|
|
|
|
|
|
| 15 |
# Paging support
|
| 16 |
page = request.args.get('page', 1, type=int)
|
| 17 |
per_page = request.args.get('per_page', 20, type=int)
|
|
@@ -48,7 +51,6 @@ def get_products_with_stats(filter_query=None, user_id=None, show_all=False):
|
|
| 48 |
'user_id': product.user_id
|
| 49 |
})
|
| 50 |
return products, pagination
|
| 51 |
-
return products
|
| 52 |
|
| 53 |
# Function to update product data
|
| 54 |
def update_product_data(product_key, product_version, name=None, description=None, metadata=None, badges=None, user_id=None):
|
|
|
|
| 2 |
from models import db, Attestation, PolicyResult, Product
|
| 3 |
from sqlalchemy import func, text
|
| 4 |
|
| 5 |
+
def get_products_with_stats(filter_query=None, version_filter=None, user_id=None, show_all=False):
|
| 6 |
# Only consume data from products table
|
| 7 |
query = Product.query.filter(Product.deleted == False)
|
| 8 |
if not show_all and user_id is not None:
|
|
|
|
| 12 |
query = query.filter(
|
| 13 |
Product.product_key.ilike(like_pattern)
|
| 14 |
)
|
| 15 |
+
if version_filter:
|
| 16 |
+
version_like = f'%{version_filter}%'
|
| 17 |
+
query = query.filter(Product.product_version.ilike(version_like))
|
| 18 |
# Paging support
|
| 19 |
page = request.args.get('page', 1, type=int)
|
| 20 |
per_page = request.args.get('per_page', 20, type=int)
|
|
|
|
| 51 |
'user_id': product.user_id
|
| 52 |
})
|
| 53 |
return products, pagination
|
|
|
|
| 54 |
|
| 55 |
# Function to update product data
|
| 56 |
def update_product_data(product_key, product_version, name=None, description=None, metadata=None, badges=None, user_id=None):
|
web/routes.py
CHANGED
|
@@ -224,6 +224,7 @@ def login_required(f):
|
|
| 224 |
@login_required
|
| 225 |
def products_page():
|
| 226 |
filter_query = request.args.get('filter', '')
|
|
|
|
| 227 |
page = request.args.get('page', 1, type=int)
|
| 228 |
per_page = request.args.get('per_page', 20, type=int)
|
| 229 |
user_id = session.get('user_id')
|
|
@@ -234,8 +235,8 @@ def products_page():
|
|
| 234 |
is_admin = _is_admin_user_web(user_id)
|
| 235 |
if is_admin:
|
| 236 |
show_all = request.args.get('show_all', '0') == '1'
|
| 237 |
-
products, pagination = get_products_with_stats(filter_query, user_id=user_id, show_all=show_all)
|
| 238 |
-
return render_template('products.html', products=products, pagination=pagination, filter_query=filter_query, show_all=show_all, is_admin=is_admin)
|
| 239 |
|
| 240 |
# --- Products CSV Download ---
|
| 241 |
@web_bp.route('/products/download-csv')
|
|
@@ -243,10 +244,11 @@ def products_page():
|
|
| 243 |
def download_products_csv():
|
| 244 |
"""Download products table as CSV, one line per product version and badge."""
|
| 245 |
filter_query = request.args.get('filter', '')
|
|
|
|
| 246 |
page = request.args.get('page', 1, type=int)
|
| 247 |
per_page = request.args.get('per_page', 20, type=int)
|
| 248 |
custom_filename = request.args.get('filename', '')
|
| 249 |
-
products, pagination = get_products_with_stats(filter_query)
|
| 250 |
from models import Badge
|
| 251 |
all_badges = Badge.query.order_by(Badge.name).all()
|
| 252 |
output = io.StringIO()
|
|
@@ -677,6 +679,7 @@ def attestations():
|
|
| 677 |
|
| 678 |
# Get filter parameters
|
| 679 |
product_filter = request.args.get('product', '')
|
|
|
|
| 680 |
target_type_filter = request.args.get('target_type', '')
|
| 681 |
tool_filter = request.args.get('tool', '')
|
| 682 |
search_filter = request.args.get('search', '')
|
|
@@ -693,13 +696,12 @@ def attestations():
|
|
| 693 |
# Apply filters
|
| 694 |
if product_filter and product_filter.strip():
|
| 695 |
query = query.filter(Attestation.product_key == product_filter.strip())
|
| 696 |
-
|
|
|
|
| 697 |
if target_type_filter:
|
| 698 |
query = query.filter(Attestation.target_type == target_type_filter)
|
| 699 |
-
|
| 700 |
if tool_filter:
|
| 701 |
query = query.filter(Attestation.tool.ilike(f"%{tool_filter}%"))
|
| 702 |
-
|
| 703 |
if id_filter and id_filter.strip():
|
| 704 |
try:
|
| 705 |
attestation_id = int(id_filter.strip())
|
|
@@ -707,7 +709,6 @@ def attestations():
|
|
| 707 |
except ValueError:
|
| 708 |
# If not a valid integer, ignore the filter
|
| 709 |
pass
|
| 710 |
-
|
| 711 |
if search_filter:
|
| 712 |
query = query.filter(
|
| 713 |
db.or_(
|
|
@@ -774,6 +775,7 @@ def attestations():
|
|
| 774 |
target_types=target_types,
|
| 775 |
tools=tools,
|
| 776 |
product_filter=product_filter,
|
|
|
|
| 777 |
target_type_filter=target_type_filter,
|
| 778 |
tool_filter=tool_filter,
|
| 779 |
search_filter=search_filter,
|
|
@@ -1823,6 +1825,7 @@ def policy_results():
|
|
| 1823 |
# Filtering
|
| 1824 |
rule_id_filter = request.args.get('rule_id', '')
|
| 1825 |
product_filter = request.args.get('product', '')
|
|
|
|
| 1826 |
result_kind_filter = request.args.get('result_kind', '')
|
| 1827 |
result_level_filter = request.args.get('result_level', '')
|
| 1828 |
# Explicitly default show_all_users to False when not provided
|
|
@@ -1855,6 +1858,8 @@ def policy_results():
|
|
| 1855 |
|
| 1856 |
if product_filter and product_filter.strip():
|
| 1857 |
query = query.filter(PolicyResult.product_key == product_filter.strip())
|
|
|
|
|
|
|
| 1858 |
|
| 1859 |
if result_kind_filter:
|
| 1860 |
query = query.filter(PolicyResult.result_kind == result_kind_filter)
|
|
@@ -1919,6 +1924,7 @@ def policy_results():
|
|
| 1919 |
distinct_rule_count=distinct_rule_count,
|
| 1920 |
rule_id_filter=rule_id_filter,
|
| 1921 |
product_filter=product_filter,
|
|
|
|
| 1922 |
result_kind_filter=result_kind_filter,
|
| 1923 |
result_level_filter=result_level_filter,
|
| 1924 |
product_keys=product_keys,
|
|
@@ -2430,11 +2436,13 @@ def update_product():
|
|
| 2430 |
return jsonify({'success': True, 'product': product.to_dict()})
|
| 2431 |
|
| 2432 |
# Product attestations redirect
|
| 2433 |
-
|
| 2434 |
-
|
| 2435 |
-
|
| 2436 |
-
|
| 2437 |
-
|
| 2438 |
-
|
| 2439 |
-
|
| 2440 |
-
|
|
|
|
|
|
|
|
|
| 224 |
@login_required
|
| 225 |
def products_page():
|
| 226 |
filter_query = request.args.get('filter', '')
|
| 227 |
+
version_filter = request.args.get('version', '')
|
| 228 |
page = request.args.get('page', 1, type=int)
|
| 229 |
per_page = request.args.get('per_page', 20, type=int)
|
| 230 |
user_id = session.get('user_id')
|
|
|
|
| 235 |
is_admin = _is_admin_user_web(user_id)
|
| 236 |
if is_admin:
|
| 237 |
show_all = request.args.get('show_all', '0') == '1'
|
| 238 |
+
products, pagination = get_products_with_stats(filter_query, version_filter=version_filter, user_id=user_id, show_all=show_all)
|
| 239 |
+
return render_template('products.html', products=products, pagination=pagination, filter_query=filter_query, version_filter=version_filter, show_all=show_all, is_admin=is_admin)
|
| 240 |
|
| 241 |
# --- Products CSV Download ---
|
| 242 |
@web_bp.route('/products/download-csv')
|
|
|
|
| 244 |
def download_products_csv():
|
| 245 |
"""Download products table as CSV, one line per product version and badge."""
|
| 246 |
filter_query = request.args.get('filter', '')
|
| 247 |
+
version_filter = request.args.get('version', '')
|
| 248 |
page = request.args.get('page', 1, type=int)
|
| 249 |
per_page = request.args.get('per_page', 20, type=int)
|
| 250 |
custom_filename = request.args.get('filename', '')
|
| 251 |
+
products, pagination = get_products_with_stats(filter_query, version_filter=version_filter)
|
| 252 |
from models import Badge
|
| 253 |
all_badges = Badge.query.order_by(Badge.name).all()
|
| 254 |
output = io.StringIO()
|
|
|
|
| 679 |
|
| 680 |
# Get filter parameters
|
| 681 |
product_filter = request.args.get('product', '')
|
| 682 |
+
version_filter = request.args.get('version', '')
|
| 683 |
target_type_filter = request.args.get('target_type', '')
|
| 684 |
tool_filter = request.args.get('tool', '')
|
| 685 |
search_filter = request.args.get('search', '')
|
|
|
|
| 696 |
# Apply filters
|
| 697 |
if product_filter and product_filter.strip():
|
| 698 |
query = query.filter(Attestation.product_key == product_filter.strip())
|
| 699 |
+
if version_filter and version_filter.strip():
|
| 700 |
+
query = query.filter(Attestation.product_version.ilike(f"%{version_filter.strip()}%"))
|
| 701 |
if target_type_filter:
|
| 702 |
query = query.filter(Attestation.target_type == target_type_filter)
|
|
|
|
| 703 |
if tool_filter:
|
| 704 |
query = query.filter(Attestation.tool.ilike(f"%{tool_filter}%"))
|
|
|
|
| 705 |
if id_filter and id_filter.strip():
|
| 706 |
try:
|
| 707 |
attestation_id = int(id_filter.strip())
|
|
|
|
| 709 |
except ValueError:
|
| 710 |
# If not a valid integer, ignore the filter
|
| 711 |
pass
|
|
|
|
| 712 |
if search_filter:
|
| 713 |
query = query.filter(
|
| 714 |
db.or_(
|
|
|
|
| 775 |
target_types=target_types,
|
| 776 |
tools=tools,
|
| 777 |
product_filter=product_filter,
|
| 778 |
+
version_filter=version_filter,
|
| 779 |
target_type_filter=target_type_filter,
|
| 780 |
tool_filter=tool_filter,
|
| 781 |
search_filter=search_filter,
|
|
|
|
| 1825 |
# Filtering
|
| 1826 |
rule_id_filter = request.args.get('rule_id', '')
|
| 1827 |
product_filter = request.args.get('product', '')
|
| 1828 |
+
version_filter = request.args.get('version', '')
|
| 1829 |
result_kind_filter = request.args.get('result_kind', '')
|
| 1830 |
result_level_filter = request.args.get('result_level', '')
|
| 1831 |
# Explicitly default show_all_users to False when not provided
|
|
|
|
| 1858 |
|
| 1859 |
if product_filter and product_filter.strip():
|
| 1860 |
query = query.filter(PolicyResult.product_key == product_filter.strip())
|
| 1861 |
+
if version_filter and version_filter.strip():
|
| 1862 |
+
query = query.filter(PolicyResult.product_version.ilike(f"%{version_filter.strip()}%"))
|
| 1863 |
|
| 1864 |
if result_kind_filter:
|
| 1865 |
query = query.filter(PolicyResult.result_kind == result_kind_filter)
|
|
|
|
| 1924 |
distinct_rule_count=distinct_rule_count,
|
| 1925 |
rule_id_filter=rule_id_filter,
|
| 1926 |
product_filter=product_filter,
|
| 1927 |
+
version_filter=version_filter,
|
| 1928 |
result_kind_filter=result_kind_filter,
|
| 1929 |
result_level_filter=result_level_filter,
|
| 1930 |
product_keys=product_keys,
|
|
|
|
| 2436 |
return jsonify({'success': True, 'product': product.to_dict()})
|
| 2437 |
|
| 2438 |
# Product attestations redirect
|
| 2439 |
+
|
| 2440 |
+
# Product attestations redirect (with version)
|
| 2441 |
+
@web_bp.route('/products/<product>/<version>/attestations')
|
| 2442 |
+
def product_attestations(product, version):
|
| 2443 |
+
return redirect(url_for('web.attestations', product=product, version=version))
|
| 2444 |
+
|
| 2445 |
+
# Product policy results redirect (with version)
|
| 2446 |
+
@web_bp.route('/products/<product>/<version>/policy_results')
|
| 2447 |
+
def product_policy_results(product, version):
|
| 2448 |
+
return redirect(url_for('web.policy_results', product=product, version=version))
|