- Correctie reset password en confirm email adress by adapting the prefixed_url_for to use config setting
- Adaptation of DPA and T&Cs - Refer to privacy statement as DPA, not a privacy statement - Startup of enforcing signed DPA and T&Cs - Adaptation of eveai_chat_client to ensure we retrieve correct DPA & T&Cs
This commit is contained in:
@@ -70,6 +70,7 @@
|
||||
{% if current_user.is_authenticated %}
|
||||
{{ dropdown('Tenants', 'source_environment', [
|
||||
{'name': 'Tenants', 'url': 'user/tenants', 'roles': ['Super User', 'Partner Admin']},
|
||||
{'name': 'Consent Versions', 'url': 'user/consent_versions', 'roles': ['Super User']},
|
||||
{'name': 'Tenant Overview', 'url': 'user/tenant_overview', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']},
|
||||
{'name': 'Edit Tenant', 'url': 'user/tenant/' ~ session['tenant'].get('id'), 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']},
|
||||
{'name': 'Tenant Partner Services', 'url': 'user/tenant_partner_services', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']},
|
||||
|
||||
75
eveai_app/templates/user/consent_version.html
Normal file
75
eveai_app/templates/user/consent_version.html
Normal file
@@ -0,0 +1,75 @@
|
||||
{% extends 'base.html' %}
|
||||
{% from "macros.html" import render_field, render_included_field %}
|
||||
|
||||
{% block title %}Create or Edit Consent Version{% endblock %}
|
||||
|
||||
{% block content_title %}Create or Edit Consent Version{% endblock %}
|
||||
{% block content_description %}Create or Edit Consent Version{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{{ form.hidden_tag() }}
|
||||
{% set disabled_fields = [] %}
|
||||
{% set exclude_fields = [] %}
|
||||
{% for field in form %}
|
||||
{{ render_field(field, disabled_fields, exclude_fields) }}
|
||||
{% endfor %}
|
||||
|
||||
<button type="submit" class="btn btn-primary">Save Consent Version</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content_footer %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// JavaScript om de gebruiker's timezone te detecteren
|
||||
document.addEventListener('DOMContentLoaded', (event) => {
|
||||
// Detect timezone
|
||||
const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
|
||||
// Send timezone to the server via a POST request
|
||||
fetch('set_user_timezone', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ timezone: userTimezone })
|
||||
}).then(response => {
|
||||
if (response.ok) {
|
||||
console.log('Timezone sent to server successfully');
|
||||
} else {
|
||||
console.error('Failed to send timezone to server');
|
||||
}
|
||||
});
|
||||
|
||||
$('#timezone').select2({
|
||||
placeholder: 'Selecteer een timezone...',
|
||||
allowClear: true,
|
||||
theme: 'bootstrap',
|
||||
width: '100%',
|
||||
dropdownAutoWidth: true,
|
||||
dropdownCssClass: 'timezone-dropdown', // Een custom class voor specifieke styling
|
||||
scrollAfterSelect: false,
|
||||
// Verbeterd scroll gedrag
|
||||
dropdownParent: $('body')
|
||||
});
|
||||
|
||||
// Stel de huidige waarde in als de dropdown wordt geopend
|
||||
$('#timezone').on('select2:open', function() {
|
||||
if ($(this).val()) {
|
||||
setTimeout(function() {
|
||||
let selectedOption = $('.select2-results__option[aria-selected=true]');
|
||||
if (selectedOption.length) {
|
||||
selectedOption[0].scrollIntoView({ behavior: 'auto', block: 'center' });
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
75
eveai_app/templates/user/edit_consent_version.html
Normal file
75
eveai_app/templates/user/edit_consent_version.html
Normal file
@@ -0,0 +1,75 @@
|
||||
{% extends 'base.html' %}
|
||||
{% from "macros.html" import render_field, render_included_field %}
|
||||
|
||||
{% block title %}Create or Edit Consent Version{% endblock %}
|
||||
|
||||
{% block content_title %}Create or Edit Consent Version{% endblock %}
|
||||
{% block content_description %}Create or Edit Consent Version{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{{ form.hidden_tag() }}
|
||||
{% set disabled_fields = ["consent_type"] %}
|
||||
{% set exclude_fields = [] %}
|
||||
{% for field in form %}
|
||||
{{ render_field(field, disabled_fields, exclude_fields) }}
|
||||
{% endfor %}
|
||||
|
||||
<button type="submit" class="btn btn-primary">Save Consent Version</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content_footer %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// JavaScript om de gebruiker's timezone te detecteren
|
||||
document.addEventListener('DOMContentLoaded', (event) => {
|
||||
// Detect timezone
|
||||
const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
|
||||
// Send timezone to the server via a POST request
|
||||
fetch('set_user_timezone', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ timezone: userTimezone })
|
||||
}).then(response => {
|
||||
if (response.ok) {
|
||||
console.log('Timezone sent to server successfully');
|
||||
} else {
|
||||
console.error('Failed to send timezone to server');
|
||||
}
|
||||
});
|
||||
|
||||
$('#timezone').select2({
|
||||
placeholder: 'Selecteer een timezone...',
|
||||
allowClear: true,
|
||||
theme: 'bootstrap',
|
||||
width: '100%',
|
||||
dropdownAutoWidth: true,
|
||||
dropdownCssClass: 'timezone-dropdown', // Een custom class voor specifieke styling
|
||||
scrollAfterSelect: false,
|
||||
// Verbeterd scroll gedrag
|
||||
dropdownParent: $('body')
|
||||
});
|
||||
|
||||
// Stel de huidige waarde in als de dropdown wordt geopend
|
||||
$('#timezone').on('select2:open', function() {
|
||||
if ($(this).val()) {
|
||||
setTimeout(function() {
|
||||
let selectedOption = $('.select2-results__option[aria-selected=true]');
|
||||
if (selectedOption.length) {
|
||||
selectedOption[0].scrollIntoView({ behavior: 'auto', block: 'center' });
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
@@ -118,7 +118,7 @@ def view_content(content_type):
|
||||
Show content like release notes, terms of use, etc.
|
||||
|
||||
Args:
|
||||
content_type (str): Type content (eg. 'changelog', 'terms', 'privacy')
|
||||
content_type (str): Type content (eg. 'changelog', 'terms', 'dpa')
|
||||
"""
|
||||
try:
|
||||
major_minor = request.args.get('version')
|
||||
@@ -135,14 +135,14 @@ def view_content(content_type):
|
||||
titles = {
|
||||
'changelog': 'Release Notes',
|
||||
'terms': 'Terms & Conditions',
|
||||
'privacy': 'Privacy Statement',
|
||||
'dpadpa': 'Data Privacy Agreement',
|
||||
# Voeg andere types toe indien nodig
|
||||
}
|
||||
|
||||
descriptions = {
|
||||
'changelog': 'EveAI Release Notes',
|
||||
'terms': "Terms & Conditions for using AskEveAI's Evie",
|
||||
'privacy': "Privacy Statement for AskEveAI's Evie",
|
||||
'dpadpa': "Data Privacy Agreement for AskEveAI's Evie",
|
||||
# Voeg andere types toe indien nodig
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ from flask_security import roles_accepted
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
import ast
|
||||
|
||||
from common.models.user import Tenant, User, TenantDomain, TenantProject, TenantMake, PartnerTenant, PartnerService
|
||||
from common.models.user import Tenant, User, TenantDomain, TenantProject, TenantMake, PartnerTenant, PartnerService, \
|
||||
ConsentVersion
|
||||
from common.services.user import UserServices, PartnerServices
|
||||
from common.utils.eveai_exceptions import EveAINoSessionPartner, EveAINoManagementPartnerService
|
||||
from common.utils.security_utils import current_user_has_role
|
||||
@@ -287,6 +288,8 @@ def get_tenant_makes_list_view(tenant_id):
|
||||
|
||||
|
||||
# Tenant Partner Services list view helper
|
||||
|
||||
|
||||
def get_tenant_partner_services_list_view(tenant_id):
|
||||
"""Generate the tenant partner services list view configuration for a specific tenant"""
|
||||
# Get partner services for the tenant through PartnerTenant association
|
||||
@@ -328,3 +331,48 @@ def get_tenant_partner_services_list_view(tenant_id):
|
||||
'form_action': url_for('user_bp.tenant_partner_services'),
|
||||
'description': f'Partner Services for tenant {tenant_id}'
|
||||
}
|
||||
|
||||
|
||||
def get_consent_versions_list_view():
|
||||
"""Generate the tenant makes list view configuration for a specific tenant"""
|
||||
# Get makes for the tenant
|
||||
query = ConsentVersion.query.filter_by().order_by(ConsentVersion.id)
|
||||
consent_versions = query.all()
|
||||
|
||||
# Prepare data for Tabulator
|
||||
data = []
|
||||
for cv in consent_versions:
|
||||
data.append({
|
||||
'id': cv.id,
|
||||
'consent_type': cv.consent_type,
|
||||
'consent_version': cv.consent_version,
|
||||
'consent_valid_from': cv.consent_valid_from.strftime('%Y-%m-%d') if cv.consent_valid_from else '',
|
||||
'consent_valid_to': cv.consent_valid_to.strftime('%Y-%m-%d') if cv.consent_valid_to else '',
|
||||
})
|
||||
|
||||
# Column Definitions
|
||||
columns = [
|
||||
{'title': 'ID', 'field': 'id', 'width': 80},
|
||||
{'title': 'Consent Type', 'field': 'consent_type'},
|
||||
{'title': 'From', 'field': 'consent_valid_from'},
|
||||
{'title': 'To', 'field': 'consent_valid_to'}
|
||||
]
|
||||
|
||||
actions = [
|
||||
{'value': 'edit_consent_version', 'text': 'Edit Consent Version', 'class': 'btn-primary', 'requiresSelection': True},
|
||||
{'value': 'create_consent_version', 'text': 'Create Consent Version', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False},
|
||||
]
|
||||
|
||||
initial_sort = [{'column': 'id', 'dir': 'asc'}]
|
||||
|
||||
return {
|
||||
'title': 'Consent Versions',
|
||||
'data': data,
|
||||
'columns': columns,
|
||||
'actions': actions,
|
||||
'initial_sort': initial_sort,
|
||||
'table_id': 'consent_versions_table',
|
||||
'form_action': url_for('user_bp.handle_consent_version_selection'),
|
||||
'description': f'Consent Versions'
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ from datetime import datetime as dt, timezone as tz
|
||||
from itsdangerous import URLSafeTimedSerializer
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from common.models.user import User
|
||||
from common.models.user import User, ConsentStatus
|
||||
from common.services.user import TenantServices
|
||||
from common.utils.eveai_exceptions import EveAIException, EveAINoActiveLicense
|
||||
from common.utils.nginx_utils import prefixed_url_for
|
||||
from eveai_app.views.security_forms import SetPasswordForm, ResetPasswordForm, ForgotPasswordForm
|
||||
@@ -56,8 +57,24 @@ def login():
|
||||
db.session.commit()
|
||||
if current_user.has_roles('Super User'):
|
||||
return redirect(prefixed_url_for('user_bp.tenants', for_redirect=True))
|
||||
else:
|
||||
return redirect(prefixed_url_for('user_bp.tenant_overview', for_redirect=True))
|
||||
if current_user.has_roles('Partner Admin'):
|
||||
return redirect(prefixed_url_for('user_bp.tenants', for_redirect=True))
|
||||
consent_status = TenantServices.get_consent_status(user.tenant_id)
|
||||
match consent_status:
|
||||
case ConsentStatus.CONSENTED:
|
||||
return redirect(prefixed_url_for('user_bp.tenant_overview', for_redirect=True))
|
||||
case ConsentStatus.NOT_CONSENTED:
|
||||
if current_user.has_roles('Tenant Admin'):
|
||||
return redirect(prefixed_url_for('user_bp.tenant_consent', for_redirect=True))
|
||||
else:
|
||||
return redirect(prefixed_url_for('user_bp.no_consent', for_redirect=True))
|
||||
case ConsentStatus.RENEWAL_REQUIRED:
|
||||
if current_user.has_roles('Tenant Admin'):
|
||||
return redirect(prefixed_url_for('user_bp.tenant_consent_renewal', for_redirect=True))
|
||||
else:
|
||||
return redirect(prefixed_url_for('user_bp.consent_renewal', for_redirect=True))
|
||||
case _:
|
||||
return redirect(prefixed_url_for('basic_bp.index', for_redirect=True))
|
||||
else:
|
||||
flash('Invalid username or password', 'danger')
|
||||
current_app.logger.error(f'Invalid username or password for given email: {user.email}')
|
||||
|
||||
@@ -192,6 +192,7 @@ class TenantMakeForm(DynamicFormBase):
|
||||
self.allowed_languages.choices = [(details['iso 639-1'], f"{details['flag']} {details['iso 639-1']}")
|
||||
for name, details in lang_details.items()]
|
||||
|
||||
|
||||
class EditTenantMakeForm(DynamicFormBase):
|
||||
id = IntegerField('ID', widget=HiddenInput())
|
||||
name = StringField('Name', validators=[DataRequired(), Length(max=50), validate_make_name])
|
||||
@@ -212,5 +213,22 @@ class EditTenantMakeForm(DynamicFormBase):
|
||||
self.default_language.choices = choices
|
||||
|
||||
|
||||
class ConsentVersionForm(FlaskForm):
|
||||
consent_type = SelectField('Consent Type', choices=[], validators=[DataRequired()])
|
||||
consent_version = StringField('Consent Version', validators=[DataRequired(), Length(max=20)])
|
||||
consent_valid_from = DateField('Consent Valid From', id='form-control datepicker', validators=[DataRequired()])
|
||||
consent_valid_to = DateField('Consent Valid To', id='form-control datepicker', validators=[Optional()])
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ConsentVersionForm, self).__init__(*args, **kwargs)
|
||||
# Initialise consent types
|
||||
self.consent_type.choices = [(t, t) for t in current_app.config['CONSENT_TYPES']]
|
||||
|
||||
|
||||
class EditConsentVersionForm(FlaskForm):
|
||||
consent_type = StringField('Consent Type', validators=[DataRequired()])
|
||||
consent_version = StringField('Consent Version', validators=[DataRequired(), Length(max=20)])
|
||||
consent_valid_from = DateField('Consent Valid From', id='form-control datepicker', validators=[DataRequired()])
|
||||
consent_valid_to = DateField('Consent Valid To', id='form-control datepicker', validators=[Optional()])
|
||||
|
||||
|
||||
|
||||
@@ -6,13 +6,15 @@ from flask_security import roles_accepted, current_user
|
||||
from sqlalchemy.exc import SQLAlchemyError, IntegrityError
|
||||
import ast
|
||||
|
||||
from common.models.user import User, Tenant, Role, TenantDomain, TenantProject, PartnerTenant, TenantMake
|
||||
from common.models.user import User, Tenant, Role, TenantDomain, TenantProject, PartnerTenant, TenantMake, \
|
||||
ConsentVersion
|
||||
from common.extensions import db, security, minio_client, simple_encryption, cache_manager
|
||||
from common.utils.dynamic_field_utils import create_default_config_from_type_config
|
||||
from common.utils.security_utils import send_confirmation_email, send_reset_email
|
||||
from config.type_defs.service_types import SERVICE_TYPES
|
||||
from .user_forms import TenantForm, CreateUserForm, EditUserForm, TenantDomainForm, TenantSelectionForm, \
|
||||
TenantProjectForm, EditTenantProjectForm, TenantMakeForm, EditTenantForm, EditTenantMakeForm
|
||||
TenantProjectForm, EditTenantProjectForm, TenantMakeForm, EditTenantForm, EditTenantMakeForm, ConsentVersionForm, \
|
||||
EditConsentVersionForm
|
||||
from common.utils.database import Database
|
||||
from common.utils.view_assistants import prepare_table_for_macro, form_validation_failed
|
||||
from common.utils.simple_encryption import generate_api_key
|
||||
@@ -25,7 +27,7 @@ from common.utils.mail_utils import send_email
|
||||
|
||||
from eveai_app.views.list_views.user_list_views import get_tenants_list_view, get_users_list_view, \
|
||||
get_tenant_domains_list_view, get_tenant_projects_list_view, get_tenant_makes_list_view, \
|
||||
get_tenant_partner_services_list_view
|
||||
get_tenant_partner_services_list_view, get_consent_versions_list_view
|
||||
from eveai_app.views.list_views.list_view_utils import render_list_view
|
||||
|
||||
user_bp = Blueprint('user_bp', __name__, url_prefix='/user')
|
||||
@@ -693,6 +695,87 @@ def tenant_partner_services():
|
||||
return render_list_view('list_view.html', **config)
|
||||
|
||||
|
||||
@user_bp.route('/consent_versions', methods=['GET', 'POST'])
|
||||
@roles_accepted('Super User')
|
||||
def consent_versions():
|
||||
config = get_consent_versions_list_view()
|
||||
return render_list_view('list_view.html', **config)
|
||||
|
||||
|
||||
@user_bp.route('/handle_consent_version_selection', methods=['POST'])
|
||||
@roles_accepted('Super User')
|
||||
def handle_consent_version_selection():
|
||||
action = request.form['action']
|
||||
if action == 'create_consent_version':
|
||||
return redirect(prefixed_url_for('user_bp.consent_version', for_redirect=True))
|
||||
consent_version_identification = request.form.get('selected_row')
|
||||
consent_version_id = ast.literal_eval(consent_version_identification).get('value')
|
||||
|
||||
if action == 'edit_consent_version':
|
||||
return redirect(prefixed_url_for('user_bp.edit_consent_version', consent_version_id=consent_version_id, for_redirect=True))
|
||||
|
||||
# Altijd teruggaan naar de tenant_makes pagina
|
||||
return redirect(prefixed_url_for('user_bp.consent_versions', for_redirect=True))
|
||||
|
||||
|
||||
@user_bp.route('/consent_version', methods=['GET', 'POST'])
|
||||
@roles_accepted('Super User')
|
||||
def consent_version():
|
||||
form = ConsentVersionForm()
|
||||
if form.validate_on_submit():
|
||||
new_consent_version = ConsentVersion()
|
||||
form.populate_obj(new_consent_version)
|
||||
set_logging_information(new_consent_version, dt.now(tz.utc))
|
||||
|
||||
try:
|
||||
db.session.add(new_consent_version)
|
||||
db.session.commit()
|
||||
flash('Consent Version successfully added!', 'success')
|
||||
current_app.logger.info(f'Consent Version {new_consent_version.consent_type}, version {new_consent_version.consent_version} successfully added ')
|
||||
# Enable step 2 of creation of retriever - add configuration of the retriever (dependent on type)
|
||||
return redirect(prefixed_url_for('user_bp.consent_versions', for_redirect=True))
|
||||
except SQLAlchemyError as e:
|
||||
db.session.rollback()
|
||||
flash(f'Failed to add Consent Version. Error: {e}', 'danger')
|
||||
current_app.logger.error(f'Failed to add Consent Version. Error: {str(e)}')
|
||||
|
||||
return render_template('user/consent_version.html', form=form)
|
||||
|
||||
|
||||
@user_bp.route('/consent_version/<int:consent_version_id>', methods=['GET', 'POST'])
|
||||
@roles_accepted('Super User')
|
||||
def edit_consent_version(consent_version_id):
|
||||
"""Edit an existing Consent Version."""
|
||||
# Get the Consent Version or return 404
|
||||
cv = ConsentVersion.query.get_or_404(consent_version_id)
|
||||
|
||||
# Create form instance with the tenant make
|
||||
form = EditConsentVersionForm(request.form, obj=cv)
|
||||
|
||||
if form.validate_on_submit():
|
||||
# Update basic fields
|
||||
form.populate_obj(cv)
|
||||
# Update logging information
|
||||
update_logging_information(cv, dt.now(tz.utc))
|
||||
|
||||
# Save changes to database
|
||||
try:
|
||||
db.session.add(cv)
|
||||
db.session.commit()
|
||||
flash('Consent Version updated successfully!', 'success')
|
||||
current_app.logger.info(f'Consent Version {cv.id} updated successfully')
|
||||
except SQLAlchemyError as e:
|
||||
db.session.rollback()
|
||||
flash(f'Failed to update Consent Version. Error: {str(e)}', 'danger')
|
||||
current_app.logger.error(f'Failed to update Consent Version {consent_version_id}. Error: {str(e)}')
|
||||
return render_template('user/consent_version.html', form=form, consent_version_id=consent_version_id)
|
||||
|
||||
return redirect(prefixed_url_for('user_bp.consent_versions', for_redirect=True))
|
||||
else:
|
||||
form_validation_failed(request, form)
|
||||
|
||||
return render_template('user/edit_consent_version.html', form=form, consent_version_id=consent_version_id)
|
||||
|
||||
|
||||
def reset_uniquifier(user):
|
||||
security.datastore.set_uniquifier(user)
|
||||
|
||||
Reference in New Issue
Block a user