- Changes to the list views - now using tabulator with filtering and sorting, client-side pagination, ...

- Adaptation of all list views in the app
This commit is contained in:
Josako
2025-07-14 18:58:54 +02:00
parent acad28b623
commit 000636a229
50 changed files with 2162 additions and 2174 deletions

View File

@@ -25,9 +25,10 @@ from common.utils.middleware import mw_before_request
from common.utils.celery_utils import current_celery
from common.utils.nginx_utils import prefixed_url_for
from common.utils.view_assistants import form_validation_failed, prepare_table_for_macro
from eveai_app.views.list_views.document_list_view import DocumentListView
from eveai_app.views.list_views.document_version_list_view import DocumentVersionListView
from eveai_app.views.list_views.full_document_list_view import FullDocumentListView
from eveai_app.views.list_views.list_view_utils import render_list_view
from eveai_app.views.list_views.document_list_views import get_catalogs_list_view, get_processors_list_view, \
get_retrievers_list_view, get_documents_list_view, get_documents_processing_list_view
from eveai_app.views.list_views.list_view_utils import render_list_view
document_bp = Blueprint('document_bp', __name__, url_prefix='/document')
@@ -86,19 +87,9 @@ def catalog():
@document_bp.route('/catalogs', methods=['GET', 'POST'])
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def catalogs():
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
query = Catalog.query.order_by(Catalog.id)
pagination = query.paginate(page=page, per_page=per_page)
the_catalogs = pagination.items
# prepare table data
rows = prepare_table_for_macro(the_catalogs, [('id', ''), ('name', ''), ('type', '')])
# Render the catalogs in a template
return render_template('document/catalogs.html', rows=rows, pagination=pagination)
# Haal configuratie op en render de lijst-weergave
config = get_catalogs_list_view()
return render_list_view('list_view.html', **config)
@document_bp.route('/handle_catalog_selection', methods=['POST'])
@@ -234,25 +225,14 @@ def edit_processor(processor_id):
@document_bp.route('/processors', methods=['GET', 'POST'])
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def processors():
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
catalog_id = session.get('catalog_id', None)
if not catalog_id:
flash('You need to set a Session Catalog before adding Documents or URLs', 'warning')
return redirect(prefixed_url_for('document_bp.catalogs'))
query = Processor.query.filter_by(catalog_id=catalog_id).order_by(Processor.id)
pagination = query.paginate(page=page, per_page=per_page)
the_processors = pagination.items
# prepare table data
rows = prepare_table_for_macro(the_processors,
[('id', ''), ('name', ''), ('type', ''), ('active', '')])
# Render the catalogs in a template
return render_template('document/processors.html', rows=rows, pagination=pagination)
# Get configuration and render the list view
config = get_processors_list_view(catalog_id)
return render_list_view('list_view.html', **config)
@document_bp.route('/handle_processor_selection', methods=['POST'])
@@ -345,25 +325,14 @@ def edit_retriever(retriever_id):
@document_bp.route('/retrievers', methods=['GET', 'POST'])
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def retrievers():
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
catalog_id = session.get('catalog_id', None)
if not catalog_id:
flash('You need to set a Session Catalog before adding Documents or URLs', 'warning')
return redirect(prefixed_url_for('document_bp.catalogs'))
query = Retriever.query.filter_by(catalog_id=catalog_id).order_by(Retriever.id)
pagination = query.paginate(page=page, per_page=per_page)
the_retrievers = pagination.items
# prepare table data
rows = prepare_table_for_macro(the_retrievers,
[('id', ''), ('name', ''), ('type', '')])
# Render the catalogs in a template
return render_template('document/retrievers.html', rows=rows, pagination=pagination)
# Get configuration and render the list view
config = get_retrievers_list_view(catalog_id)
return render_list_view('list_view.html', **config)
@document_bp.route('/handle_retriever_selection', methods=['POST'])
@@ -502,20 +471,20 @@ def documents():
flash('You need to set a Session Catalog before adding Documents or URLs', 'warning')
return redirect(prefixed_url_for('document_bp.catalogs'))
view = DocumentListView(Document, 'document/documents.html', per_page=10)
return view.get()
config = get_documents_list_view(catalog_id)
return render_list_view('list_view.html', **config)
@document_bp.route('/full_documents', methods=['GET', 'POST'])
@document_bp.route('/documents_processing', methods=['GET', 'POST'])
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def full_documents():
def documents_processing():
catalog_id = session.get('catalog_id', None)
if not catalog_id:
flash('You need to set a Session Catalog before viewing Full Documents', 'warning')
flash('You need to set a Session Catalog before adding Documents or URLs', 'warning')
return redirect(prefixed_url_for('document_bp.catalogs'))
view = FullDocumentListView(Document, 'document/full_documents.html', per_page=10)
return view.get()
config = get_documents_processing_list_view(catalog_id)
return render_list_view('list_view.html', **config)
@document_bp.route('/handle_document_selection', methods=['POST'])
@@ -536,14 +505,23 @@ def handle_document_selection():
match action:
case 'edit_document':
return redirect(prefixed_url_for('document_bp.edit_document_view', document_id=doc_id))
case 'document_versions':
return redirect(prefixed_url_for('document_bp.document_versions', document_id=doc_id))
case 'refresh_document':
return redirect(prefixed_url_for('document_bp.edit_document', document_id=doc_id))
case 'refresh':
refresh_document_view(doc_id)
return redirect(prefixed_url_for('document_bp.document_versions', document_id=doc_id))
case 're_embed_latest_versions':
re_embed_latest_versions()
return redirect(prefixed_url_for('document_bp.documents', document_id=doc_id))
case 're_process':
document = Document.query.get_or_404(doc_id)
doc_vers_id = document.latest_version.id
process_version(doc_vers_id)
case 'view_document_markdown':
document = Document.query.get_or_404(doc_id)
doc_vers_id = document.latest_version.id
return redirect(prefixed_url_for('document_bp.view_document_version_markdown',
document_version_id=doc_vers_id))
case 'edit_document_version':
document = Document.query.get_or_404(doc_id)
doc_vers_id = document.latest_version.id
return redirect(prefixed_url_for('document_bp.edit_document_version', document_version_id=doc_vers_id))
# Add more conditions for other actions
return redirect(prefixed_url_for('document_bp.documents'))
@@ -551,7 +529,7 @@ def handle_document_selection():
@document_bp.route('/edit_document/<int:document_id>', methods=['GET', 'POST'])
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def edit_document_view(document_id):
def edit_document(document_id):
# Use an alias for the Catalog to avoid column name conflicts
CatalogAlias = aliased(Catalog)
@@ -592,7 +570,7 @@ def edit_document_view(document_id):
@document_bp.route('/edit_document_version/<int:document_version_id>', methods=['GET', 'POST'])
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def edit_document_version_view(document_version_id):
def edit_document_version(document_version_id):
doc_vers = DocumentVersion.query.get_or_404(document_version_id)
form = EditDocumentVersionForm(request.form, obj=doc_vers)
@@ -630,119 +608,6 @@ def edit_document_version_view(document_version_id):
doc_details=f'Document {doc_vers.document.name}')
@document_bp.route('/document_versions/<int:document_id>', methods=['GET', 'POST'])
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def document_versions(document_id):
doc = Document.query.get_or_404(document_id)
doc_desc = f'{doc.name}'
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
query = (DocumentVersion.query.filter_by(doc_id=document_id)
.order_by(DocumentVersion.language)
.order_by(desc(DocumentVersion.id)))
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
doc_langs = pagination.items
rows = prepare_table_for_macro(doc_langs, [('id', ''), ('file_type', ''), ('file_size', ''),
('processing', ''), ('processing_started_at', ''),
('processing_finished_at', ''), ('processing_error', '')])
return render_template('document/document_versions.html', rows=rows, pagination=pagination, document=doc_desc)
@document_bp.route('/handle_document_version_selection', methods=['POST'])
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def handle_document_version_selection():
document_version_identification = request.form['selected_row']
if isinstance(document_version_identification, int) or document_version_identification.isdigit():
doc_vers_id = int(document_version_identification)
else:
# If it's not an integer, assume it's a string representation of a dictionary
try:
doc_vers_id = ast.literal_eval(document_version_identification).get('value')
except (ValueError, AttributeError):
flash('Invalid document version selection.', 'error')
return redirect(prefixed_url_for('document_bp.document_versions_list'))
action = request.form['action']
match action:
case 'edit_document_version':
return redirect(prefixed_url_for('document_bp.edit_document_version_view', document_version_id=doc_vers_id))
case 'process_document_version':
process_version(doc_vers_id)
# Add more conditions for other actions
case 'view_document_version_markdown':
return redirect(prefixed_url_for('document_bp.view_document_version_markdown',
document_version_id=doc_vers_id))
doc_vers = DocumentVersion.query.get_or_404(doc_vers_id)
return redirect(prefixed_url_for('document_bp.document_versions', document_id=doc_vers.doc_id))
@document_bp.route('/handle_full_document_selection', methods=['POST'])
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def handle_full_document_selection():
selected_row = request.form['selected_row']
action = request.form['action']
try:
# Parse the selected row to get document ID (first column) and version ID (fifth column)
row_data = ast.literal_eval(selected_row)
selected_doc_id = row_data.get('value')
# We need to retrieve the corresponding row data to get the version ID
# This is a bit complex with the current structure, so we'll use a different approach
if action in ['edit_document', 'document_versions', 'refresh_document']:
# Actions that need document ID
match action:
case 'edit_document':
return redirect(prefixed_url_for('document_bp.edit_document_view', document_id=selected_doc_id))
case 'document_versions':
return redirect(prefixed_url_for('document_bp.document_versions', document_id=selected_doc_id))
case 'refresh_document':
refresh_document_view(selected_doc_id)
return redirect(prefixed_url_for('document_bp.full_documents'))
else:
# Actions that need version ID
# We need to get the version ID from the selected row in the table
# We'll extract it from the form data and the version ID is in the 5th cell (index 4)
version_id_cell = int(request.form.get('version_id', 0))
# If we couldn't get a version ID, try to find the latest version for this document
if not version_id_cell:
doc_version = DocumentVersion.query.filter_by(doc_id=selected_doc_id).order_by(desc(DocumentVersion.id)).first()
if doc_version:
version_id_cell = doc_version.id
else:
flash('No document version found for this document.', 'error')
return redirect(prefixed_url_for('document_bp.full_documents'))
match action:
case 'edit_document_version':
return redirect(prefixed_url_for('document_bp.edit_document_version_view', document_version_id=version_id_cell))
case 'process_document_version':
process_version(version_id_cell)
return redirect(prefixed_url_for('document_bp.full_documents'))
case 'view_document_version_markdown':
return redirect(prefixed_url_for('document_bp.view_document_version_markdown', document_version_id=version_id_cell))
except (ValueError, AttributeError, KeyError) as e:
current_app.logger.error(f"Error processing full document selection: {str(e)}")
flash('Invalid selection or action. Please try again.', 'error')
return redirect(prefixed_url_for('document_bp.full_documents'))
@document_bp.route('/document_versions_list', methods=['GET'])
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def document_versions_list():
view = DocumentVersionListView(DocumentVersion, 'document/document_versions_list_view.html', per_page=20)
return view.get()
@document_bp.route('/view_document_version_markdown/<int:document_version_id>', methods=['GET'])
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def view_document_version_markdown(document_version_id):

View File

@@ -17,6 +17,8 @@ from .entitlements_forms import LicenseTierForm, LicenseForm
from common.utils.view_assistants import prepare_table_for_macro, form_validation_failed
from common.utils.nginx_utils import prefixed_url_for
from common.utils.document_utils import set_logging_information, update_logging_information
from .list_views.entitlement_list_views import get_license_tiers_list_view, get_license_list_view
from .list_views.list_view_utils import render_list_view
entitlements_bp = Blueprint('entitlements_bp', __name__, url_prefix='/entitlements')
@@ -45,48 +47,23 @@ def license_tier():
current_app.logger.info(f"Successfully created license tier {new_license_tier.id}")
flash(f"Successfully created tenant license tier {new_license_tier.id}", 'success')
return redirect(prefixed_url_for('entitlements_bp.view_license_tiers'))
return redirect(prefixed_url_for('entitlements_bp.license_tiers'))
else:
form_validation_failed(request, form)
return render_template('entitlements/license_tier.html', form=form)
@entitlements_bp.route('/view_license_tiers', methods=['GET', 'POST'])
@entitlements_bp.route('/license_tiers', methods=['GET', 'POST'])
@roles_accepted('Super User', 'Partner Admin')
def view_license_tiers():
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
today = dt.now(tz.utc)
def license_tiers():
config = get_license_tiers_list_view()
query = LicenseTier.query.filter(
or_(
LicenseTier.end_date == None,
LicenseTier.end_date >= today
)
)
if current_user_has_role('Partner Admin'):
try:
license_tier_ids = PartnerServices.get_allowed_license_tier_ids()
except EveAIException as e:
flash(f"Cannot retrieve License Tiers: {str(e)}", 'danger')
current_app.logger.error(f'Cannot retrieve License Tiers for partner: {str(e)}')
return render_template("index.html")
if license_tier_ids and len(license_tier_ids) > 0:
query = query.filter(LicenseTier.id.in_(license_tier_ids))
# Check if there was an error in getting the configuration
if config.get('error'):
return render_template("index.html")
query = query.order_by(LicenseTier.start_date.desc(), LicenseTier.id)
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
license_tiers = pagination.items
rows = prepare_table_for_macro(license_tiers, [('id', ''), ('name', ''), ('version', ''), ('start_date', ''),
('end_date', '')])
return render_template('entitlements/view_license_tiers.html',
rows=rows,
pagination=pagination,
can_assign_license=UserServices.can_user_assign_license())
return render_list_view('list_view.html', **config)
@entitlements_bp.route('/handle_license_tier_selection', methods=['POST'])
@@ -110,7 +87,7 @@ def handle_license_tier_selection():
LicenseTierServices.associate_license_tier_with_partner(license_tier_id)
# Add more conditions for other actions
return redirect(prefixed_url_for('entitlements_bp.view_license_tiers'))
return redirect(prefixed_url_for('entitlements_bp.license_tiers'))
@entitlements_bp.route('/license_tier/<int:license_tier_id>', methods=['GET', 'POST'])
@@ -253,79 +230,16 @@ def edit_license(license_id):
return render_template('entitlements/license.html', form=form, ext_readonly_fields=readonly_fields)
@entitlements_bp.route('/view_usages')
@entitlements_bp.route('/licenses')
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def view_usages():
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
def licenses():
config = get_license_list_view()
if not session.get('tenant', None):
flash('You can only view usage for a Tenant. Select a Tenant to continue!', 'danger')
return redirect(prefixed_url_for('user_bp.select_tenant'))
# Check if there was an error in getting the configuration
if config.get('error'):
return render_template("index.html")
tenant_id = session.get('tenant').get('id')
query = LicenseUsage.query.filter_by(tenant_id=tenant_id).order_by(desc(LicenseUsage.id))
pagination = query.paginate(page=page, per_page=per_page)
lus = pagination.items
# prepare table data
rows = prepare_table_for_macro(lus, [('id', ''), ('period_start_date', ''), ('period_end_date', ''),
('storage_mb_used', ''), ('embedding_mb_used', ''),
('interaction_total_tokens_used', '')])
# Render the users in a template
return render_template('entitlements/view_usages.html', rows=rows, pagination=pagination)
@entitlements_bp.route('/handle_usage_selection', methods=['POST'])
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def handle_usage_selection():
usage_identification = request.form['selected_row']
usage_id = ast.literal_eval(usage_identification).get('value')
the_usage = LicenseUsage.query.get_or_404(usage_id)
action = request.form['action']
pass # Currently, no actions are defined
@entitlements_bp.route('/view_licenses')
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def view_licenses():
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
tenant_id = session.get('tenant').get('id')
# Get current date in UTC
current_date = dt.now(tz=tz.utc).date()
# Query licenses for the tenant, with ordering and active status
# TODO - Check validity
query = (
License.query
.join(LicenseTier) # Join with LicenseTier
.filter(License.tenant_id == tenant_id)
.add_columns(
License.id,
License.start_date,
License.nr_of_periods,
LicenseTier.name.label('license_tier_name'), # Access name through LicenseTier
(License.start_date <= current_date).label('active')
)
.order_by(License.start_date.desc())
)
pagination = query.paginate(page=page, per_page=per_page)
lics = pagination.items
# prepare table data
rows = prepare_table_for_macro(lics, [('id', ''), ('license_tier_name', ''), ('start_date', ''),
('nr_of_periods', ''), ('active', '')])
# Render the licenses in a template
return render_template('entitlements/view_licenses.html', rows=rows, pagination=pagination)
return render_list_view('list_view.html', **config)
@entitlements_bp.route('/handle_license_selection', methods=['POST'])
@@ -343,7 +257,7 @@ def handle_license_selection():
case 'view_periods':
return redirect(prefixed_url_for('entitlements_bp.view_license_periods', license_id=license_id))
case _:
return redirect(prefixed_url_for('entitlements_bp.view_licenses'))
return redirect(prefixed_url_for('entitlements_bp.licenses'))
@entitlements_bp.route('/license/<int:license_id>/periods')
@@ -356,7 +270,7 @@ def view_license_periods(license_id):
tenant_id = session.get('tenant').get('id')
if license.tenant_id != tenant_id:
flash('Access denied to this license', 'danger')
return redirect(prefixed_url_for('entitlements_bp.view_licenses'))
return redirect(prefixed_url_for('entitlements_bp.licenses'))
# Get all periods for this license
periods = (LicensePeriod.query
@@ -402,6 +316,13 @@ def transition_period_status(license_id, period_id):
return redirect(prefixed_url_for('entitlements_bp.view_license_periods', license_id=license_id))
@entitlements_bp.route('/view_licenses')
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def view_licenses_redirect():
# Redirect to the new licenses route
return redirect(prefixed_url_for('entitlements_bp.licenses'))
@entitlements_bp.route('/active_usage')
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def active_license_usage():
@@ -409,7 +330,7 @@ def active_license_usage():
tenant_id = session.get('tenant', {}).get('id')
if not tenant_id:
flash('No active or pending license period found for this tenant', 'warning')
return redirect(prefixed_url_for('user_bp.select_tenant'))
return redirect(prefixed_url_for('user_bp.tenants'))
active_period = LicensePeriod.query \
.join(License) \

View File

@@ -31,6 +31,10 @@ from .interaction_forms import (SpecialistForm, EditSpecialistForm, EditEveAIAge
EditEveAIToolForm, ExecuteSpecialistForm,
SpecialistMagicLinkForm, EditSpecialistMagicLinkForm)
from eveai_app.views.list_views.interaction_list_views import (get_specialists_list_view, get_assets_list_view,
get_magic_links_list_view, get_chat_sessions_list_view)
from eveai_app.views.list_views.list_view_utils import render_list_view
interaction_bp = Blueprint('interaction_bp', __name__, url_prefix='/interaction')
@@ -56,17 +60,9 @@ def before_request():
@interaction_bp.route('/chat_sessions', methods=['GET', 'POST'])
def chat_sessions():
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
query = ChatSession.query.order_by(desc(ChatSession.session_start))
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
docs = pagination.items
rows = prepare_table_for_macro(docs, [('id', ''), ('session_id', ''), ('session_start', ''), ('session_end', '')])
return render_template('interaction/chat_sessions.html', rows=rows, pagination=pagination)
# Get configuration and render the list view
config = get_chat_sessions_list_view()
return render_list_view('list_view.html', **config)
@interaction_bp.route('/handle_chat_session_selection', methods=['POST'])
@@ -288,45 +284,10 @@ def edit_specialist(specialist_id):
@interaction_bp.route('/specialists', methods=['GET', 'POST'])
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def specialists():
# Get all specialists (no pagination needed for client-side)
specialists_query = Specialist.query.order_by(Specialist.id)
all_specialists = specialists_query.all()
# Prepare data for Tabulator
specialists_data = []
for specialist in all_specialists:
specialists_data.append({
'id': specialist.id,
'name': specialist.name,
'type': specialist.type,
'type_version': specialist.type_version,
'active': specialist.active
})
# Column definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Name', 'field': 'name'},
{'title': 'Type', 'field': 'type'},
{'title': 'Type Version', 'field': 'type_version'},
{'title': 'Active', 'field': 'active', 'formatter': 'tickCross'}
]
# Action definitions
actions = [
{'value': 'edit_specialist', 'text': 'Edit Specialist', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'execute_specialist', 'text': 'Execute Specialist', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'create_specialist', 'text': 'Register Specialist', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False}
]
# Initial sort configuration
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return render_template('interaction/specialists.html',
specialists_data=specialists_data,
columns=columns,
actions=actions,
initial_sort=initial_sort)
# Get configuration and render the list view
config = get_specialists_list_view()
return render_list_view('list_view.html', **config)
@interaction_bp.route('/handle_specialist_selection', methods=['POST'])
@@ -763,19 +724,9 @@ def edit_specialist_magic_link(specialist_magic_link_id):
@interaction_bp.route('/specialist_magic_links', methods=['GET', 'POST'])
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def specialist_magic_links():
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
query = SpecialistMagicLink.query.order_by(SpecialistMagicLink.id)
pagination = query.paginate(page=page, per_page=per_page)
the_specialist_magic_links = pagination.items
# prepare table data
rows = prepare_table_for_macro(the_specialist_magic_links, [('id', ''), ('name', ''), ('magic_link_code', ''),])
# Render the catalogs in a template
return render_template('interaction/specialist_magic_links.html', rows=rows, pagination=pagination)
# Get configuration and render the list view
config = get_magic_links_list_view()
return render_list_view('list_view.html', **config)
@interaction_bp.route('/handle_specialist_magic_link_selection', methods=['POST'])
@@ -798,19 +749,15 @@ def handle_specialist_magic_link_selection():
# Routes for Asset Management ---------------------------------------------------------------------
@interaction_bp.route('/assets', methods=['GET', 'POST'])
def assets():
from eveai_app.views.list_views.assets_list_view import AssetsListView
view = AssetsListView(
model=EveAIAsset,
template='interaction/assets.html',
per_page=10
)
return view.get()
config = get_assets_list_view()
return render_list_view('list_view.html', **config)
@interaction_bp.route('/handle_asset_selection', methods=['POST'])
def handle_asset_selection():
action = request.form.get('action')
asset_id = request.form.get('selected_row')
asset_identification = request.form.get('selected_row')
asset_id = ast.literal_eval(asset_identification).get('value')
if action == 'edit_asset':
return redirect(prefixed_url_for('interaction_bp.edit_asset', asset_id=asset_id))

View File

@@ -0,0 +1,2 @@
# List Views module initialization
# This module contains utility functions for handling list views

View File

@@ -1,83 +0,0 @@
from datetime import datetime as dt, timezone as tz
from flask import request, render_template, current_app
from sqlalchemy import desc, asc
from common.models.interaction import EveAIAsset
from eveai_app.views.list_views.filtered_list_view import FilteredListView
class AssetsListView(FilteredListView):
allowed_filters = ['type', 'file_type']
allowed_sorts = ['id', 'last_used_at']
def get_query(self):
return EveAIAsset.query
def apply_filters(self, query):
filters = request.args.to_dict(flat=False)
if 'type' in filters and filters['type']:
query = query.filter(EveAIAsset.type.in_(filters['type']))
if 'file_type' in filters and filters['file_type']:
query = query.filter(EveAIAsset.file_type.in_(filters['file_type']))
return query
def apply_sorting(self, query):
sort_by = request.args.get('sort_by', 'id')
sort_order = request.args.get('sort_order', 'asc')
if sort_by in self.allowed_sorts:
column = getattr(EveAIAsset, sort_by)
if sort_order == 'asc':
query = query.order_by(asc(column))
elif sort_order == 'desc':
query = query.order_by(desc(column))
return query
def get(self):
query = self.get_query()
query = self.apply_filters(query)
query = self.apply_sorting(query)
pagination = self.paginate(query)
def format_date(date):
if isinstance(date, dt):
return date.strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(date, str):
return date
else:
return ''
rows = [
[
{'value': item.id, 'class': '', 'type': 'text'},
{'value': item.name, 'class': '', 'type': 'text'},
{'value': item.type, 'class': '', 'type': 'text'},
{'value': item.type_version, 'class': '', 'type': 'text'},
{'value': item.file_type or '', 'class': '', 'type': 'text'},
{'value': format_date(item.last_used_at), 'class': '', 'type': 'text'}
] for item in pagination.items
]
context = {
'rows': rows,
'pagination': pagination,
'filters': request.args.to_dict(flat=False),
'sort_by': request.args.get('sort_by', 'id'),
'sort_order': request.args.get('sort_order', 'asc'),
'filter_options': self.get_filter_options()
}
return render_template(self.template, **context)
def get_filter_options(self):
# Haal unieke waarden op voor filters
types = [t[0] for t in EveAIAsset.query.with_entities(EveAIAsset.type).distinct().all() if t[0]]
file_types = [f[0] for f in EveAIAsset.query.with_entities(EveAIAsset.file_type).distinct().all() if f[0]]
return {
'type': [(t, t) for t in types],
'file_type': [(f, f) for f in file_types]
}

View File

@@ -1,81 +0,0 @@
from datetime import datetime as dt, timezone as tz
from flask import request, render_template, session, current_app
from sqlalchemy import desc, asc, or_, and_
from common.models.document import Document
from eveai_app.views.list_views.filtered_list_view import FilteredListView
class DocumentListView(FilteredListView):
allowed_filters = ['validity']
allowed_sorts = ['id', 'name', 'valid_from', 'valid_to']
def get_query(self):
catalog_id = session.get('catalog_id')
return Document.query.filter_by(catalog_id=catalog_id)
def apply_filters(self, query):
filters = request.args.to_dict(flat=False)
if 'validity' in filters:
now = dt.now(tz.utc).date()
if 'valid' in filters['validity']:
query = query.filter(
and_(
or_(Document.valid_from.is_(None), Document.valid_from <= now),
or_(Document.valid_to.is_(None), Document.valid_to >= now)
)
)
return query
def apply_sorting(self, query):
sort_by = request.args.get('sort_by', 'id')
sort_order = request.args.get('sort_order', 'asc')
if sort_by in self.allowed_sorts:
column = getattr(Document, sort_by)
if sort_order == 'asc':
query = query.order_by(asc(column))
elif sort_order == 'desc':
query = query.order_by(desc(column))
return query
def get(self):
query = self.get_query()
# query = self.apply_filters(query)
# query = self.apply_sorting(query)
pagination = self.paginate(query)
def format_date(date):
if isinstance(date, dt):
return date.strftime('%Y-%m-%d')
elif isinstance(date, str):
return date
else:
return ''
rows = [
[
{'value': item.id, 'class': '', 'type': 'text'},
{'value': item.name, 'class': '', 'type': 'text'},
{'value': format_date(item.valid_from), 'class': '', 'type': 'text'},
{'value': format_date(item.valid_to), 'class': '', 'type': 'text'}
] for item in pagination.items
]
context = {
'rows': rows,
'pagination': pagination,
'filters': request.args.to_dict(flat=False),
'sort_by': request.args.get('sort_by', 'id'),
'sort_order': request.args.get('sort_order', 'asc'),
'filter_options': self.get_filter_options()
}
return render_template(self.template, **context)
def get_filter_options(self):
return {
'validity': [('valid', 'Valid'), ('all', 'All')]
}

View File

@@ -0,0 +1,324 @@
from flask import current_app, url_for
from sqlalchemy.exc import SQLAlchemyError
from common.extensions import db
from common.models.document import Catalog, Document, DocumentVersion, Processor, Retriever
def get_catalogs_list_view():
"""Genereer de catalogi lijst-weergave configuratie"""
# Haal alle catalogi op (geen server-side filtering - wordt client-side afgehandeld)
catalog_query = Catalog.query.order_by(Catalog.id)
all_catalogs = catalog_query.all()
# Bereid data voor voor Tabulator
data = []
for catalog in all_catalogs:
data.append({
'id': catalog.id,
'name': catalog.name,
'type': catalog.type
})
# Kolomdefinities
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Naam', 'field': 'name'},
{'title': 'Type', 'field': 'type'},
]
actions = [
{'value': 'set_session_catalog', 'text': 'Set Session Catalog', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'edit_catalog', 'text': 'Edit Catalog', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'create_catalog', 'text': 'Register Catalog', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False},
]
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Catalogi',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'catalogs_table',
'form_action': url_for('document_bp.handle_catalog_selection'),
'description': 'Bekijk en beheer catalogi'
}
def get_processors_list_view(catalog_id):
"""Generate the processors list view configuration"""
# Get all processors for this catalog
processor_query = Processor.query.filter_by(catalog_id=catalog_id).order_by(Processor.id)
all_processors = processor_query.all()
# Prepare data for Tabulator
data = []
for processor in all_processors:
data.append({
'id': processor.id,
'name': processor.name,
'type': processor.type,
'active': processor.active
})
# Column Definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Name', 'field': 'name'},
{'title': 'Type', 'field': 'type'},
{'title': 'Active', 'field': 'active'}
]
actions = [
{'value': 'edit_processor', 'text': 'Edit Processor', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'create_processor', 'text': 'Register Processor', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False},
]
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Processors',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'processors_table',
'form_action': url_for('document_bp.handle_processor_selection'),
'description': 'View and manage processors'
}
def get_retrievers_list_view(catalog_id):
"""Generate the retrievers list view configuration"""
# Get all retrievers for this catalog
retriever_query = Retriever.query.filter_by(catalog_id=catalog_id).order_by(Retriever.id)
all_retrievers = retriever_query.all()
# Prepare data for Tabulator
data = []
for retriever in all_retrievers:
data.append({
'id': retriever.id,
'name': retriever.name,
'type': retriever.type
})
# Column Definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Name', 'field': 'name'},
{'title': 'Type', 'field': 'type'}
]
actions = [
{'value': 'edit_retriever', 'text': 'Edit Retriever', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'create_retriever', 'text': 'Register Retriever', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False},
]
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Retrievers',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'retrievers_table',
'form_action': url_for('document_bp.handle_retriever_selection'),
'description': 'View and manage retrievers'
}
def get_documents_list_view(catalog_id):
# Query all documents for the given catalog_id, along with their latest version
# We use a subquery to get the latest document version for each document
from sqlalchemy import desc, func
from sqlalchemy.orm import aliased
# Subquery to get the max version id for each document
latest_version_subquery = db.session.query(
DocumentVersion.doc_id,
func.max(DocumentVersion.id).label('max_id')
).group_by(DocumentVersion.doc_id).subquery()
# Alias for the latest document version
LatestVersion = aliased(DocumentVersion)
# Main query with join to get documents with their latest version
document_query = db.session.query(
Document.id,
Document.name,
Document.valid_from,
Document.valid_to,
LatestVersion.file_type,
LatestVersion.file_size,
LatestVersion.processing
).join(
latest_version_subquery,
Document.id == latest_version_subquery.c.doc_id
).join(
LatestVersion,
(LatestVersion.doc_id == latest_version_subquery.c.doc_id) &
(LatestVersion.id == latest_version_subquery.c.max_id)
).filter(
Document.catalog_id == catalog_id
).order_by(Document.id)
# Execute the query
try:
documents_with_latest_versions = document_query.all()
# Prepare data for Tabulator
data = []
for doc in documents_with_latest_versions:
data.append({
'id': doc.id,
'name': doc.name,
'valid_from': doc.valid_from.strftime('%Y-%m-%d') if doc.valid_from else '',
'valid_to': doc.valid_to.strftime('%Y-%m-%d') if doc.valid_to else '',
'file_type': doc.file_type,
'file_size': f"{doc.file_size:.2f}" if doc.file_size else '',
})
# Column definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Name', 'field': 'name'},
{'title': 'Valid From', 'field': 'valid_from'},
{'title': 'Valid To', 'field': 'valid_to'},
{'title': 'File Type', 'field': 'file_type'},
{'title': 'File Size', 'field': 'file_size'},
]
actions = [
{'value': 'edit_document', 'text': 'Edit Document', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'edit_document_version', 'text': 'Edit Document Version', 'class': 'btn-secondary',
'requiresSelection': True},
{'value': 'refresh', 'text': 'Refresh', 'class': 'btn-secondary', 'requiresSelection': True},
{'value': 're_process', 'text': 'Re-Process', 'class': 'btn-secondary', 'requiresSelection': True},
{'value': 'view_document_markdown', 'text': 'View Document', 'class': 'btn-secondary', 'requiresSelection': True},
]
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Documents',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'documents_table',
'form_action': url_for('document_bp.handle_document_selection'),
'description': 'Manage Documents and Document Versions'
}
except SQLAlchemyError as e:
current_app.logger.error(f"Error querying documents with latest versions: {str(e)}")
return {
'title': 'Documents',
'data': [],
'columns': [],
'actions': [],
'initial_sort': [],
'table_id': 'documents_table',
'form_action': url_for('document_bp.handle_document_selection'),
'description': 'There was an error while retrieving the documents. Please try again later.'
}
def get_documents_processing_list_view(catalog_id):
# Query all documents for the given catalog_id, along with their latest version
# We use a subquery to get the latest document version for each document
from sqlalchemy import desc, func
from sqlalchemy.orm import aliased
# Subquery to get the max version id for each document
latest_version_subquery = db.session.query(
DocumentVersion.doc_id,
func.max(DocumentVersion.id).label('max_id')
).group_by(DocumentVersion.doc_id).subquery()
# Alias for the latest document version
LatestVersion = aliased(DocumentVersion)
# Main query with join to get documents with their latest version
document_query = db.session.query(
Document.id,
Document.name,
LatestVersion.processing,
LatestVersion.processing_started_at,
LatestVersion.processing_finished_at,
LatestVersion.processing_error,
).join(
latest_version_subquery,
Document.id == latest_version_subquery.c.doc_id
).join(
LatestVersion,
(LatestVersion.doc_id == latest_version_subquery.c.doc_id) &
(LatestVersion.id == latest_version_subquery.c.max_id)
).filter(
Document.catalog_id == catalog_id
).order_by(Document.id)
# Execute the query
try:
documents_with_latest_versions = document_query.all()
# Prepare data for Tabulator
data = []
for doc in documents_with_latest_versions:
data.append({
'id': doc.id,
'name': doc.name,
'processing': doc.processing,
'processing_started_at': doc.processing_started_at.strftime('%Y-%m-%d %H:%M:%S') if doc.processing_started_at else '',
'processing_finished_at': doc.processing_finished_at.strftime('%Y-%m-%d %H:%M:%S') if doc.processing_finished_at else '',
'processing_error': doc.processing_error,
})
# Column definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Name', 'field': 'name'},
{'title': 'Processing', 'field': 'processing', 'formatter': 'tickCross'},
{'title': 'Start', 'field': 'processing_started_at'},
{'title': 'Finish', 'field': 'processing_finished_at'},
{'title': 'Error', 'field': 'processing_error'},
]
actions = [
{'value': 'edit_document', 'text': 'Edit Document', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'edit_document_version', 'text': 'Edit Document Version', 'class': 'btn-secondary',
'requiresSelection': True},
{'value': 'refresh', 'text': 'Refresh', 'class': 'btn-secondary', 'requiresSelection': True},
{'value': 're_process', 'text': 'Re-Process', 'class': 'btn-secondary', 'requiresSelection': True},
{'value': 'view_document_markdown', 'text': 'View Document', 'class': 'btn-secondary', 'requiresSelection': True},
]
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Document Processing Status',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'documents_table',
'form_action': url_for('document_bp.handle_document_selection'),
'description': 'View Processing Status of Document Versions'
}
except SQLAlchemyError as e:
current_app.logger.error(f"Error querying documents with latest versions: {str(e)}")
return {
'title': 'Document Processing Status',
'data': [],
'columns': [],
'actions': [],
'initial_sort': [],
'table_id': 'documents_table',
'form_action': url_for('document_bp.handle_document_selection'),
'description': 'An error occurred while retrieving the documents. Please try again later.'
}

View File

@@ -1,83 +0,0 @@
from datetime import datetime
from flask import request, render_template
from sqlalchemy import desc, asc
from common.models.document import DocumentVersion, Document
from eveai_app.views.list_views.filtered_list_view import FilteredListView
from common.utils.view_assistants import prepare_table_for_macro
class DocumentVersionListView(FilteredListView):
allowed_filters = ['file_type', 'processing', 'processing_error']
allowed_sorts = ['id', 'processing_started_at', 'processing_finished_at', 'processing_error']
def get_query(self):
return DocumentVersion.query.join(Document)
def apply_filters(self, query):
filters = request.args.to_dict()
if filters.get('file_type'):
query = query.filter(DocumentVersion.file_type == filters['file_type'])
if filters.get('processing'):
query = query.filter(DocumentVersion.processing == (filters['processing'] == 'true'))
if filters.get('processing_error'):
if filters['processing_error'] == 'true':
query = query.filter(DocumentVersion.processing_error.isnot(None))
elif filters['processing_error'] == 'false':
query = query.filter(DocumentVersion.processing_error.is_(None))
if filters.get('start_date'):
query = query.filter(
DocumentVersion.processing_started_at >= datetime.strptime(filters['start_date'], '%Y-%m-%d'))
if filters.get('end_date'):
query = query.filter(
DocumentVersion.processing_finished_at <= datetime.strptime(filters['end_date'], '%Y-%m-%d'))
return query
def apply_sorting(self, query):
sort_by = request.args.get('sort_by', 'id')
sort_order = request.args.get('sort_order', 'asc')
if sort_by in self.allowed_sorts:
column = getattr(DocumentVersion, sort_by)
if sort_order == 'asc':
query = query.order_by(asc(column))
elif sort_order == 'desc':
query = query.order_by(desc(column))
return query
def get(self):
query = self.get_query()
query = self.apply_filters(query)
query = self.apply_sorting(query)
pagination = self.paginate(query)
rows = prepare_table_for_macro(
pagination.items,
[('id', ''), ('file_type', ''), ('processing', ''),
('processing_started_at', ''), ('processing_finished_at', ''),
('processing_error', '')]
)
context = {
'rows': rows,
'pagination': pagination,
'filters': request.args.to_dict(),
'sort_by': request.args.get('sort_by', 'id'),
'sort_order': request.args.get('sort_order', 'asc'),
'filter_options': self.get_filter_options()
}
return render_template(self.template, **context)
def get_filter_options(self):
return {
'file_type': [('pdf', 'PDF'), ('docx', 'DOCX')],
'processing': [('true', 'Processing'), ('false', 'Not Processing')],
'processing_error': [('true', 'With Errors'), ('false', 'Without Errors')]
}

View File

@@ -0,0 +1,164 @@
from flask import url_for, current_app, session
from datetime import datetime as dt, timezone as tz
from flask import flash
from sqlalchemy import or_, desc
from common.models.entitlements import LicenseTier, License
from common.services.user import PartnerServices, UserServices
from common.utils.eveai_exceptions import EveAIException
from common.utils.security_utils import current_user_has_role
def get_license_tiers_list_view():
"""Generate the license tiers list view configuration"""
today = dt.now(tz.utc)
# Build the query
query = LicenseTier.query.filter(
or_(
LicenseTier.end_date == None,
LicenseTier.end_date >= today
)
)
# Apply partner-specific filtering if needed
if current_user_has_role('Partner Admin'):
try:
license_tier_ids = PartnerServices.get_allowed_license_tier_ids()
except EveAIException as e:
flash(f"Cannot retrieve License Tiers: {str(e)}", 'danger')
current_app.logger.error(f'Cannot retrieve License Tiers for partner: {str(e)}')
return {
'title': 'License Tiers',
'data': [],
'columns': [],
'actions': [],
'initial_sort': [],
'table_id': 'license_tiers_table',
'form_action': url_for('entitlements_bp.handle_license_tier_selection'),
'description': 'View and manage license tiers',
'table_height': 700,
'error': True
}
if license_tier_ids and len(license_tier_ids) > 0:
query = query.filter(LicenseTier.id.in_(license_tier_ids))
# Order the results
query = query.order_by(LicenseTier.start_date.desc(), LicenseTier.id)
# Get all license tiers
license_tiers = query.all()
# Prepare data for Tabulator
data = []
for tier in license_tiers:
data.append({
'id': tier.id,
'name': tier.name,
'version': tier.version,
'start_date': tier.start_date.strftime('%Y-%m-%d') if tier.start_date else '',
'end_date': tier.end_date.strftime('%Y-%m-%d') if tier.end_date else ''
})
# Column definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80, 'type': 'number'},
{'title': 'Name', 'field': 'name'},
{'title': 'Version', 'field': 'version', 'width': 120},
{'title': 'Start Date', 'field': 'start_date', 'width': 120},
{'title': 'End Date', 'field': 'end_date', 'width': 120}
]
# Action definitions
actions = [
{'value': 'edit_license_tier', 'text': 'Edit License Tier', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'create_license_tier', 'text': 'Create License Tier', 'class': 'btn-secondary', 'position': 'right',
'requiresSelection': False}
]
# Add assign license action if user has permission
if UserServices.can_user_assign_license():
actions.insert(1, {'value': 'assign_license', 'text': 'Assign License', 'class': 'btn-info',
'requiresSelection': True})
# Initial sort configuration
initial_sort = [{'column': 'start_date', 'dir': 'desc'}, {'column': 'id', 'dir': 'asc'}]
return {
'title': 'License Tiers',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'license_tiers_table',
'form_action': url_for('entitlements_bp.handle_license_tier_selection'),
'description': 'View and manage license tiers',
'table_height': 700
}
def get_license_list_view():
"""Generate the licenses list view configuration"""
tenant_id = session.get('tenant').get('id')
# Get current date in UTC
current_date = dt.now(tz=tz.utc).date()
# Query licenses for the tenant, with ordering and active status
query = (
License.query
.join(LicenseTier) # Join with LicenseTier
.filter(License.tenant_id == tenant_id)
.add_columns(
License.id,
License.start_date,
License.nr_of_periods,
LicenseTier.name.label('license_tier_name'), # Access name through LicenseTier
(License.start_date <= current_date).label('active')
)
.order_by(License.start_date.desc())
)
# Get all licenses
licenses_list = query.all()
# Prepare data for Tabulator
data = []
for license in licenses_list:
data.append({
'id': license.id,
'license_tier_name': license.license_tier_name,
'start_date': license.start_date.strftime('%Y-%m-%d') if license.start_date else '',
'nr_of_periods': license.nr_of_periods,
'active': license.active
})
# Column definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80, 'type': 'number'},
{'title': 'License Tier', 'field': 'license_tier_name'},
{'title': 'Start Date', 'field': 'start_date', 'width': 120},
{'title': 'Nr of Periods', 'field': 'nr_of_periods', 'width': 120},
{'title': 'Active', 'field': 'active', 'formatter': 'tickCross', 'width': 100}
]
# Action definitions
actions = [
{'value': 'edit_license', 'text': 'Edit License', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'view_periods', 'text': 'View Periods', 'class': 'btn-secondary', 'requiresSelection': True}
]
# Initial sort configuration
initial_sort = [{'column': 'start_date', 'dir': 'desc'}]
return {
'title': 'Licenses',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'licenses_table',
'form_action': url_for('entitlements_bp.handle_license_selection'),
'description': 'View and manage licenses',
'table_height': 700
}

View File

@@ -1,54 +0,0 @@
from flask import request, render_template, abort
from sqlalchemy import desc, asc
class FilteredListView:
def __init__(self, model, template, per_page=10):
self.model = model
self.template = template
self.per_page = per_page
def get_query(self):
return self.model.query
def apply_filters(self, query):
filters = request.args.get('filters', {})
for key, value in filters.items():
if hasattr(self.model, key):
column = getattr(self.model, key)
if value.startswith('like:'):
query = query.filter(column.like(f"%{value[5:]}%"))
else:
query = query.filter(column == value)
return query
def apply_sorting(self, query):
sort_by = request.args.get('sort_by')
if sort_by and hasattr(self.model, sort_by):
sort_order = request.args.get('sort_order', 'asc')
column = getattr(self.model, sort_by)
if sort_order == 'desc':
query = query.order_by(desc(column))
else:
query = query.order_by(asc(column))
return query
def paginate(self, query):
page = request.args.get('page', 1, type=int)
return query.paginate(page=page, per_page=self.per_page, error_out=False)
def get(self):
query = self.get_query()
query = self.apply_filters(query)
query = self.apply_sorting(query)
pagination = self.paginate(query)
context = {
'items': pagination.items,
'pagination': pagination,
'model': self.model.__name__,
'filters': request.args.get('filters', {}),
'sort_by': request.args.get('sort_by'),
'sort_order': request.args.get('sort_order', 'asc')
}
return render_template(self.template, **context)

View File

@@ -1,178 +0,0 @@
from datetime import datetime as dt, timezone as tz
from flask import request, render_template, session, current_app
from sqlalchemy import desc, asc, or_, and_
from sqlalchemy.orm import aliased
from common.models.document import Document, DocumentVersion
from eveai_app.views.list_views.filtered_list_view import FilteredListView
from common.utils.view_assistants import prepare_table_for_macro
class FullDocumentListView(FilteredListView):
allowed_filters = ['validity', 'file_type', 'processing', 'processing_error']
allowed_sorts = ['id', 'name', 'valid_from', 'valid_to', 'file_type', 'processing_started_at',
'processing_finished_at', 'processing_error']
def __init__(self, model, template, per_page=10):
super().__init__(model, template, per_page)
self.version_alias = None
def get_query(self):
catalog_id = session.get('catalog_id')
# Fix: Selecteer alleen de id kolom in de subquery
latest_version_subquery = (
DocumentVersion.query
.with_entities(DocumentVersion.id, DocumentVersion.doc_id, DocumentVersion.url,
DocumentVersion.bucket_name, DocumentVersion.object_name,
DocumentVersion.file_type, DocumentVersion.sub_file_type,
DocumentVersion.file_size, DocumentVersion.language,
DocumentVersion.user_context, DocumentVersion.system_context,
DocumentVersion.user_metadata, DocumentVersion.system_metadata,
DocumentVersion.catalog_properties, DocumentVersion.created_at,
DocumentVersion.created_by, DocumentVersion.updated_at,
DocumentVersion.updated_by, DocumentVersion.processing,
DocumentVersion.processing_started_at, DocumentVersion.processing_finished_at,
DocumentVersion.processing_error)
.filter(DocumentVersion.id == (
DocumentVersion.query
.with_entities(DocumentVersion.id) # Selecteer alleen de id kolom
.filter(DocumentVersion.doc_id == Document.id)
.order_by(DocumentVersion.id.desc())
.limit(1)
.scalar_subquery()
))
.subquery()
)
self.version_alias = aliased(DocumentVersion, latest_version_subquery)
return Document.query.filter_by(catalog_id=catalog_id).outerjoin(
self.version_alias, Document.id == self.version_alias.doc_id
)
def apply_filters(self, query):
filters = request.args.to_dict(flat=False)
# Document filters
if 'validity' in filters:
now = dt.now(tz.utc).date()
if 'valid' in filters['validity']:
query = query.filter(
and_(
or_(Document.valid_from.is_(None), Document.valid_from <= now),
or_(Document.valid_to.is_(None), Document.valid_to >= now)
)
)
# DocumentVersion filters - use the same alias from get_query
if filters.get('file_type') and self.version_alias is not None:
query = query.filter(self.version_alias.file_type == filters['file_type'][0])
if filters.get('processing') and self.version_alias is not None:
query = query.filter(self.version_alias.processing == (filters['processing'][0] == 'true'))
if filters.get('processing_error') and self.version_alias is not None:
if filters['processing_error'][0] == 'true':
query = query.filter(self.version_alias.processing_error.isnot(None))
elif filters['processing_error'][0] == 'false':
query = query.filter(self.version_alias.processing_error.is_(None))
# Controleer of start_date een waarde heeft voordat we proberen te parsen
if filters.get('start_date') and self.version_alias is not None and filters['start_date'][0].strip():
query = query.filter(
self.version_alias.processing_started_at >= dt.strptime(filters['start_date'][0], '%Y-%m-%d'))
# Controleer of end_date een waarde heeft voordat we proberen te parsen
if filters.get('end_date') and self.version_alias is not None and filters['end_date'][0].strip():
query = query.filter(
self.version_alias.processing_finished_at <= dt.strptime(filters['end_date'][0], '%Y-%m-%d'))
return query
def apply_sorting(self, query):
sort_by = request.args.get('sort_by', 'id')
sort_order = request.args.get('sort_order', 'asc')
document_columns = ['id', 'name', 'valid_from', 'valid_to']
version_columns = ['file_type', 'processing', 'processing_started_at', 'processing_finished_at',
'processing_error']
if sort_by in self.allowed_sorts:
if sort_by in document_columns:
column = getattr(Document, sort_by)
elif sort_by in version_columns and self.version_alias is not None:
column = getattr(self.version_alias, sort_by)
else:
column = Document.id
if sort_order == 'asc':
query = query.order_by(asc(column))
elif sort_order == 'desc':
query = query.order_by(desc(column))
return query
def get(self):
query = self.get_query()
query = self.apply_filters(query)
query = self.apply_sorting(query)
pagination = self.paginate(query)
# Haal de laatste versies op voor elke document
items_with_versions = []
for doc in pagination.items:
latest_version = DocumentVersion.query.filter_by(doc_id=doc.id).order_by(desc(DocumentVersion.id)).first()
items_with_versions.append((doc, latest_version))
def format_date(date):
if isinstance(date, dt):
return date.strftime('%Y-%m-%d')
elif isinstance(date, str):
return date
else:
return ''
# Maak rijen voor de tabel met document en versie informatie
rows = []
for doc, version in items_with_versions:
if version:
row = [
{'value': doc.id, 'class': '', 'type': 'text'},
{'value': doc.name, 'class': '', 'type': 'text'},
{'value': format_date(doc.valid_from), 'class': '', 'type': 'text'},
{'value': format_date(doc.valid_to), 'class': '', 'type': 'text'},
{'value': version.id, 'class': '', 'type': 'text'},
{'value': version.file_type, 'class': '', 'type': 'text'},
{'value': 'Ja' if version.processing else 'Nee', 'class': '', 'type': 'text'},
{'value': version.processing_error or '', 'class': '', 'type': 'text'}
]
else:
row = [
{'value': doc.id, 'class': '', 'type': 'text'},
{'value': doc.name, 'class': '', 'type': 'text'},
{'value': format_date(doc.valid_from), 'class': '', 'type': 'text'},
{'value': format_date(doc.valid_to), 'class': '', 'type': 'text'},
{'value': '', 'class': '', 'type': 'text'},
{'value': '', 'class': '', 'type': 'text'},
{'value': '', 'class': '', 'type': 'text'},
{'value': '', 'class': '', 'type': 'text'}
]
rows.append(row)
context = {
'rows': rows,
'pagination': pagination,
'filters': request.args.to_dict(flat=False),
'sort_by': request.args.get('sort_by', 'id'),
'sort_order': request.args.get('sort_order', 'asc'),
'filter_options': self.get_filter_options()
}
return render_template(self.template, **context)
def get_filter_options(self):
return {
'validity': [('valid', 'Valid'), ('all', 'All')],
'file_type': [('pdf', 'PDF'), ('docx', 'DOCX')],
'processing': [('true', 'Processing'), ('false', 'Not Processing')],
'processing_error': [('true', 'With Errors'), ('false', 'Without Errors')]
}

View File

@@ -0,0 +1,198 @@
from flask import redirect, flash, current_app, session, url_for
from flask_security import roles_accepted
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import desc
import ast
from common.models.interaction import Specialist, SpecialistMagicLink, ChatSession
from common.utils.nginx_utils import prefixed_url_for
from eveai_app.views.list_views.list_view_utils import render_list_view
# Specialists list view helper
def get_specialists_list_view():
"""Generate the specialists list view configuration"""
# Get all specialists
specialists_query = Specialist.query.order_by(Specialist.id)
all_specialists = specialists_query.all()
# Prepare data for Tabulator
data = []
for specialist in all_specialists:
data.append({
'id': specialist.id,
'name': specialist.name,
'type': specialist.type,
'type_version': specialist.type_version,
'active': specialist.active
})
# Column definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Name', 'field': 'name'},
{'title': 'Type', 'field': 'type'},
{'title': 'Type Version', 'field': 'type_version'},
{'title': 'Active', 'field': 'active', 'formatter': 'tickCross'}
]
# Action definitions
actions = [
{'value': 'edit_specialist', 'text': 'Edit Specialist', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'execute_specialist', 'text': 'Execute Specialist', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'create_specialist', 'text': 'Register Specialist', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False}
]
# Initial sort configuration
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Specialists',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'specialists_table',
'form_action': url_for('interaction_bp.handle_specialist_selection'),
'description': 'View and manage specialists',
'table_height': 800 # Hogere tabel voor specialists view
}
def get_assets_list_view():
"""Generate the assets list view configuration"""
# Get all assets
from common.models.interaction import EveAIAsset
assets_query = EveAIAsset.query.order_by(EveAIAsset.id)
all_assets = assets_query.all()
# Prepare data for Tabulator
data = []
for asset in all_assets:
data.append({
'id': asset.id,
'name': asset.name,
'type': asset.type,
'type_version': asset.type_version,
})
# Column definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Name', 'field': 'name'},
{'title': 'Type', 'field': 'type'},
{'title': 'Type Version', 'field': 'type_version'}
]
# Action definitions
actions = [
{'value': 'edit_asset', 'text': 'Edit Asset', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'create_asset', 'text': 'Register Asset', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False}
]
# Initial sort configuration
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Assets',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'assets_table',
'form_action': url_for('interaction_bp.handle_asset_selection'),
'description': 'View and manage assets',
'table_height': 800
}
def get_magic_links_list_view():
"""Generate the specialist magic links list view configuration"""
# Get all specialist magic links
magic_links_query = SpecialistMagicLink.query.order_by(SpecialistMagicLink.id)
all_magic_links = magic_links_query.all()
# Prepare data for Tabulator
data = []
for magic_link in all_magic_links:
data.append({
'id': magic_link.id,
'name': magic_link.name,
'magic_link_code': magic_link.magic_link_code,
'specialist_id': magic_link.specialist_id
})
# Column definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Name', 'field': 'name'},
{'title': 'Magic Link Code', 'field': 'magic_link_code'},
{'title': 'Specialist ID', 'field': 'specialist_id'}
]
# Action definitions
actions = [
{'value': 'edit_specialist_magic_link', 'text': 'Edit Magic Link', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'create_specialist_magic_link', 'text': 'Create Magic Link', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False}
]
# Initial sort configuration
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Specialist Magic Links',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'specialist_magic_links_table',
'form_action': url_for('interaction_bp.handle_specialist_magic_link_selection'),
'description': 'View and manage specialist magic links',
'table_height': 800
}
def get_chat_sessions_list_view():
"""Generate the chat sessions list view configuration"""
# Get all chat sessions ordered by session_start (descending)
chat_sessions_query = ChatSession.query.order_by(desc(ChatSession.session_start))
all_chat_sessions = chat_sessions_query.all()
# Prepare data for Tabulator
data = []
for chat_session in all_chat_sessions:
data.append({
'id': chat_session.id,
'session_id': chat_session.session_id,
'session_start': chat_session.session_start.strftime('%Y-%m-%d %H:%M:%S') if chat_session.session_start else '',
'session_end': chat_session.session_end.strftime('%Y-%m-%d %H:%M:%S') if chat_session.session_end else '',
})
# Column definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Session ID', 'field': 'session_id'},
{'title': 'Start Time', 'field': 'session_start'},
{'title': 'End Time', 'field': 'session_end'},
]
# Action definitions
actions = [
{'value': 'view_chat_session', 'text': 'View Details', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'chat_session_interactions', 'text': 'View Interactions', 'class': 'btn-secondary', 'requiresSelection': True}
]
# Initial sort configuration
initial_sort = [{'column': 'session_start', 'dir': 'desc'}]
return {
'title': 'Chat Sessions',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'chat_sessions_table',
'form_action': url_for('interaction_bp.handle_chat_session_selection'),
'description': 'View all chat sessions',
'table_height': 800
}

View File

@@ -0,0 +1,92 @@
from flask import render_template, current_app
def get_list_view_config(title, data, columns, actions, initial_sort=None, table_id=None, additional_config=None):
"""
Creates a standardized configuration dictionary for list views.
Args:
title (str): The title of the page
data (list): The data to display in the table
columns (list): Column definitions for the table
actions (list): Action button definitions
initial_sort (list, optional): Initial sort configuration
table_id (str, optional): Custom table ID, generated from title if not provided
additional_config (dict, optional): Any additional configuration to include
Returns:
dict: A standardized configuration dictionary
"""
# Generate table_id from title if not provided
if not table_id:
table_id = f"{title.lower().replace(' ', '_')}_table"
# Create the base configuration
config = {
'title': title,
'data': data, # Consistent data parameter name
'columns': columns,
'actions': actions,
'initial_sort': initial_sort or [],
'table_id': table_id
}
# Add any additional configuration
if additional_config:
config.update(additional_config)
return config
def render_list_view(template_name, title, data, columns, actions, form_action, initial_sort=None,
table_id=None, additional_config=None, **kwargs):
"""
Renders a list view template with standardized configuration.
Args:
template_name (str): The name of the template to render
title (str): The title of the page
data (list): The data to display in the table
columns (list): Column definitions for the table
actions (list): Action button definitions
form_action (str): Form action URL for the table
initial_sort (list, optional): Initial sort configuration
table_id (str, optional): Custom table ID
additional_config (dict, optional): Any additional configuration
**kwargs: Additional template variables
Returns:
str: The rendered template
"""
# Zorg ervoor dat table_id altijd een string is zonder spaties of speciale tekens
if not table_id:
table_id = f"{title.lower().replace(' ', '_').replace('-', '_')}_table"
# Zorg ervoor dat initial_sort altijd een lijst is
if initial_sort is None:
initial_sort = []
# Zorg ervoor dat actions altijd een lijst is
if actions is None:
actions = []
# Maak config dictionary
config = {
'title': title,
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': table_id,
'form_action': form_action
}
# Voeg extra configuratie toe indien aanwezig
if additional_config:
config.update(additional_config)
# Voeg eventuele extra template variabelen toe
config.update(kwargs)
current_app.logger.debug(f"List view config: {config}")
return render_template(template_name, **config)

View File

@@ -0,0 +1,129 @@
from flask import current_app, url_for
from sqlalchemy.exc import SQLAlchemyError
from common.extensions import db
from common.models.user import Partner, Tenant, PartnerService
def get_partners_list_view():
"""Genereer de partners lijst-weergave configuratie"""
# Haal alle partners op met hun tenant informatie
query = (db.session.query(
Partner.id,
Partner.code,
Partner.active,
Partner.logo_url,
Tenant.name.label('name')
).join(Tenant, Partner.tenant_id == Tenant.id).order_by(Partner.id))
try:
all_partners = query.all()
# Bereid data voor voor Tabulator
data = []
for partner in all_partners:
data.append({
'id': partner.id,
'code': partner.code,
'name': partner.name,
'active': partner.active
})
# Kolomdefinities
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Code', 'field': 'code'},
{'title': 'Name', 'field': 'name'},
{'title': 'Active', 'field': 'active', 'formatter': 'tickCross'}
]
actions = [
{'value': 'set_session_partner', 'text': 'Set Session Partner', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'edit_partner', 'text': 'Edit Partner', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'create_partner', 'text': 'Register Partner', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False},
]
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Partners',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'partners_table',
'form_action': url_for('partner_bp.handle_partner_selection'),
'description': 'Manage partners'
}
except SQLAlchemyError as e:
current_app.logger.error(f"Error bij het ophalen van partners: {str(e)}")
return {
'title': 'Partners',
'data': [],
'columns': [],
'actions': [],
'initial_sort': [],
'table_id': 'partners_table',
'form_action': url_for('partner_bp.handle_partner_selection'),
'description': 'Er is een fout opgetreden bij het ophalen van partners. Probeer het later opnieuw.'
}
def get_partner_services_list_view(partner_id):
"""Genereer de partner services lijst-weergave configuratie"""
# Haal alle partner services op voor deze partner
query = PartnerService.query.filter(PartnerService.partner_id == partner_id)
try:
all_partner_services = query.all()
# Bereid data voor voor Tabulator
data = []
for service in all_partner_services:
data.append({
'id': service.id,
'name': service.name,
'type': service.type,
'type_version': service.type_version
})
# Kolomdefinities
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Name', 'field': 'name'},
{'title': 'Type', 'field': 'type'},
{'title': 'Version', 'field': 'type_version'}
]
actions = [
{'value': 'edit_partner_service', 'text': 'Edit Service', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'add_partner_service_for_tenant', 'text': 'Assign to Tenant', 'class': 'btn-secondary', 'requiresSelection': True},
{'value': 'create_partner_service', 'text': 'Register Service', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False},
]
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Partner Services',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'partner_services_table',
'form_action': url_for('partner_bp.handle_partner_service_selection'),
'description': 'Manage Partner Services'
}
except SQLAlchemyError as e:
current_app.logger.error(f"Error bij het ophalen van partner services: {str(e)}")
return {
'title': 'Partner Services',
'data': [],
'columns': [],
'actions': [],
'initial_sort': [],
'table_id': 'partner_services_table',
'form_action': url_for('partner_bp.handle_partner_service_selection'),
'description': 'Er is een fout opgetreden bij het ophalen van partner services. Probeer het later opnieuw.'
}

View File

@@ -0,0 +1,234 @@
from flask import redirect, flash, current_app, session, url_for
from flask_security import roles_accepted
from sqlalchemy.exc import SQLAlchemyError
import ast
from common.models.user import Tenant, User, TenantDomain, TenantProject, TenantMake
from common.services.user import UserServices
from eveai_app.views.list_views.list_view_utils import render_list_view
# Tenant list view helper
def get_tenants_list_view():
"""Generate the tenants list view configuration"""
# Get all tenants (no server side filtering - handled client-side)
tenant_query = Tenant.query.order_by(Tenant.id)
all_tenants = tenant_query.all()
# Prepare data for Tabulator
data = []
for tenant in all_tenants:
data.append({
'id': tenant.id,
'name': tenant.name,
'type': tenant.type
})
# Column Definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Name', 'field': 'name'},
{'title': 'Type', 'field': 'type'},
]
actions = [
{'value': 'select_tenant', 'text': 'Set Session Tenant', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'edit_tenant', 'text': 'Edit tenant', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'create_tenant', 'text': 'Register tenant', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False},
]
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Tenants',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'tenants_table',
'form_action': url_for('user_bp.handle_tenant_selection'),
'description': 'View and manage tenants'
}
# Users list view helper
def get_users_list_view(tenant_id):
"""Generate the users list view configuration for a specific tenant"""
# Get users for the tenant
query = User.query.filter_by(tenant_id=tenant_id).order_by(User.user_name)
users = query.all()
# Prepare data for Tabulator
data = []
for user in users:
data.append({
'id': user.id,
'user_name': user.user_name,
'email': user.email,
'first_name': user.first_name,
'last_name': user.last_name,
'active': user.active
})
# Column Definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'User Name', 'field': 'user_name'},
{'title': 'Email', 'field': 'email'},
{'title': 'First Name', 'field': 'first_name'},
{'title': 'Last Name', 'field': 'last_name'},
{'title': 'Active', 'field': 'active', 'formatter': 'tickCross'}
]
actions = [
{'value': 'edit_user', 'text': 'Edit User', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'resend_confirmation_email', 'text': 'Resend Confirmation', 'class': 'btn-secondary', 'requiresSelection': True},
{'value': 'send_password_reset_email', 'text': 'Send Password Reset', 'class': 'btn-secondary', 'requiresSelection': True},
{'value': 'reset_uniquifier', 'text': 'Reset Uniquifier', 'class': 'btn-secondary', 'requiresSelection': True},
{'value': 'create_user', 'text': 'Register User', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False},
]
initial_sort = [{'column': 'user_name', 'dir': 'asc'}]
return {
'title': 'Users',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'users_table',
'form_action': url_for('user_bp.handle_user_action'),
'description': f'Users for tenant {tenant_id}'
}
# Tenant Domains list view helper
def get_tenant_domains_list_view(tenant_id):
"""Generate the tenant domains list view configuration for a specific tenant"""
# Get domains for the tenant
query = TenantDomain.query.filter_by(tenant_id=tenant_id).order_by(TenantDomain.domain)
domains = query.all()
# Prepare data for Tabulator
data = []
for domain in domains:
data.append({
'id': domain.id,
'domain': domain.domain,
})
# Column Definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Domain', 'field': 'domain'},
]
actions = [
{'value': 'edit_tenant_domain', 'text': 'Edit Domain', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'create_tenant_domain', 'text': 'Register Domain', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False},
]
initial_sort = [{'column': 'domain', 'dir': 'asc'}]
return {
'title': 'Domains',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'tenant_domains_table',
'form_action': url_for('user_bp.handle_tenant_domain_action'),
'description': f'Domains for tenant {tenant_id}'
}
# Tenant Projects list view helper
def get_tenant_projects_list_view(tenant_id):
"""Generate the tenant projects list view configuration for a specific tenant"""
# Get projects for the tenant
query = TenantProject.query.filter_by(tenant_id=tenant_id).order_by(TenantProject.id)
projects = query.all()
# Prepare data for Tabulator
data = []
for project in projects:
data.append({
'id': project.id,
'name': project.name,
'visual_api_key': project.visual_api_key,
'responsible_email': project.responsible_email,
'active': project.active
})
# Column Definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Name', 'field': 'name'},
{'title': 'API Key', 'field': 'visual_api_key'},
{'title': 'Responsible', 'field': 'responsible_email'},
{'title': 'Active', 'field': 'active', 'formatter': 'tickCross'}
]
actions = [
{'value': 'edit_tenant_project', 'text': 'Edit Project', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'invalidate_tenant_project', 'text': 'Invalidate Project', 'class': 'btn-secondary', 'requiresSelection': True},
{'value': 'delete_tenant_project', 'text': 'Delete Project', 'class': 'btn-secondary', 'requiresSelection': True},
{'value': 'create_tenant_project', 'text': 'Register Project', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False},
]
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Projects',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'tenant_projects_table',
'form_action': url_for('user_bp.handle_tenant_project_selection'),
'description': f'Projects for tenant {tenant_id}'
}
# Tenant Makes list view helper
def get_tenant_makes_list_view(tenant_id):
"""Generate the tenant makes list view configuration for a specific tenant"""
# Get makes for the tenant
query = TenantMake.query.filter_by(tenant_id=tenant_id).order_by(TenantMake.id)
makes = query.all()
# Prepare data for Tabulator
data = []
for make in makes:
data.append({
'id': make.id,
'name': make.name,
'website': make.website,
'active': make.active
})
# Column Definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Name', 'field': 'name'},
{'title': 'Website', 'field': 'website'},
{'title': 'Active', 'field': 'active', 'formatter': 'tickCross'}
]
actions = [
{'value': 'edit_tenant_make', 'text': 'Edit Make', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'set_as_default', 'text': 'Set as Default', 'class': 'btn-secondary', 'requiresSelection': True},
{'value': 'create_tenant_make', 'text': 'Create Make', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False},
]
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Makes',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'tenant_makes_table',
'form_action': url_for('user_bp.handle_tenant_make_selection'),
'description': f'Makes for tenant {tenant_id}'
}

View File

@@ -12,9 +12,11 @@ from common.utils.celery_utils import current_celery
from common.utils.eveai_exceptions import EveAIException
from common.utils.log_utils import format_query_results
from common.utils.model_logging_utils import update_logging_information, set_logging_information
from common.utils.view_assistants import prepare_table_for_macro, form_validation_failed
from common.utils.view_assistants import form_validation_failed
from common.utils.nginx_utils import prefixed_url_for
from .partner_forms import TriggerActionForm, EditPartnerForm, PartnerServiceForm, EditPartnerServiceForm
from eveai_app.views.list_views.partner_list_views import get_partners_list_view, get_partner_services_list_view
from eveai_app.views.list_views.list_view_utils import render_list_view
partner_bp = Blueprint('partner_bp', __name__, url_prefix='/partner')
@@ -75,26 +77,8 @@ def edit_partner(partner_id):
@partner_bp.route('/partners', methods=['GET', 'POST'])
@roles_accepted('Super User')
def partners():
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
query = (db.session.query(
Partner.id,
Partner.code,
Partner.active,
Partner.logo_url,
# Include all needed Partner columns here
Tenant.name.label('name')
).join(Tenant, Partner.tenant_id == Tenant.id).order_by(Partner.id))
pagination = query.paginate(page=page, per_page=per_page)
the_partners = pagination.items
# prepare table data
rows = prepare_table_for_macro(the_partners, [('id', ''), ('name', '')])
# Render the catalogs in a template
return render_template('partner/partners.html', rows=rows, pagination=pagination)
config = get_partners_list_view()
return render_list_view('list_view.html', **config)
@partner_bp.route('/handle_partner_selection', methods=['POST'])
@@ -199,23 +183,14 @@ def edit_partner_service(partner_service_id):
@partner_bp.route('/partner_services', methods=['GET', 'POST'])
@roles_accepted('Super User')
def partner_services():
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
partner = session.get('partner', None)
if not partner:
flash('No partner has been selected. Set partner before adding services.', 'warning')
return redirect(prefixed_url_for('partner_bp.partners'))
partner_id = session['partner']['id']
query = PartnerService.query.filter(PartnerService.partner_id == partner_id)
pagination = query.paginate(page=page, per_page=per_page)
the_partner_services = pagination.items
# prepare table data
rows = prepare_table_for_macro(the_partner_services, [('id', ''), ('name', ''), ('type', '')])
return render_template('partner/partner_services.html', rows=rows, pagination=pagination)
config = get_partner_services_list_view(partner_id)
return render_list_view('list_view.html', **config)
@partner_bp.route('/handle_partner_service_selection', methods=['POST'])

View File

@@ -55,7 +55,7 @@ def login():
current_app.logger.info(f'Login successful! Current User is {current_user.email}')
db.session.commit()
if current_user.has_roles('Super User'):
return redirect(prefixed_url_for('user_bp.select_tenant'))
return redirect(prefixed_url_for('user_bp.tenants'))
else:
return redirect(prefixed_url_for('user_bp.tenant_overview'))
else:

View File

@@ -23,6 +23,10 @@ from common.services.user import TenantServices
from common.services.user import UserServices
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
from eveai_app.views.list_views.list_view_utils import render_list_view
user_bp = Blueprint('user_bp', __name__, url_prefix='/user')
@@ -43,7 +47,7 @@ def tenant():
if not UserServices.can_user_create_tenant():
current_app.logger.error(f'User {current_user.email} cannot create tenant')
flash(f"You don't have the appropriate permissions to create a tenant", 'danger')
return redirect(prefixed_url_for('user_bp.select_tenant'))
return redirect(prefixed_url_for('user_bp.tenants'))
form = TenantForm()
if request.method == 'GET':
code = f"TENANT-{str(uuid.uuid4())}"
@@ -102,7 +106,7 @@ def tenant():
current_app.logger.info(f"Creating MinIO bucket for tenant {new_tenant.id}")
minio_client.create_tenant_bucket(new_tenant.id)
return redirect(prefixed_url_for('user_bp.select_tenant'))
return redirect(prefixed_url_for('user_bp.tenants'))
else:
form_validation_failed(request, form)
@@ -137,56 +141,12 @@ def edit_tenant(tenant_id):
return render_template('user/tenant.html', form=form, tenant_id=tenant_id)
@user_bp.route('/select_tenant', methods=['GET', 'POST'])
@user_bp.route('/tenants', methods=['GET', 'POST'])
@roles_accepted('Super User', 'Partner Admin') # Allow both roles
def select_tenant():
filter_form = TenantSelectionForm(request.form)
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
# Start with a base query
query = Tenant.query
# Apply different filters based on user role
if current_user.has_roles('Partner Admin') and 'partner' in session:
# Get the partner's management service
management_service = next((service for service in session['partner']['services']
if service.get('type') == 'MANAGEMENT_SERVICE'), None)
if management_service:
# Get the partner's own tenant
partner_tenant_id = session['partner']['tenant_id']
# Get tenants managed by this partner through PartnerTenant relationships
managed_tenant_ids = db.session.query(PartnerTenant.tenant_id).filter_by(
partner_service_id=management_service['id']
).all()
# Convert list of tuples to flat list
managed_tenant_ids = [tenant_id for (tenant_id,) in managed_tenant_ids]
# Include partner's own tenant in the list
allowed_tenant_ids = [partner_tenant_id] + managed_tenant_ids
# Filter query to only show allowed tenants
query = query.filter(Tenant.id.in_(allowed_tenant_ids))
# Apply form filters (for both Super User and Partner Admin)
if filter_form.validate_on_submit():
if filter_form.types.data:
query = query.filter(Tenant.type.in_(filter_form.types.data))
if filter_form.search.data:
search = f"%{filter_form.search.data}%"
query = query.filter(Tenant.name.ilike(search))
# Finalize query
query = query.order_by(Tenant.name)
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
tenants = pagination.items
rows = prepare_table_for_macro(tenants, [('id', ''), ('name', ''), ('website', ''), ('type', '')])
return render_template('user/select_tenant.html', rows=rows, pagination=pagination, filter_form=filter_form)
def tenants():
# Get configuration and render the list view
config = get_tenants_list_view()
return render_list_view('list_view.html', **config)
@user_bp.route('/handle_tenant_selection', methods=['POST'])
@@ -201,7 +161,7 @@ def handle_tenant_selection():
if not UserServices.can_user_edit_tenant(tenant_id):
current_app.logger.info(f"User not authenticated to edit tenant {tenant_id}.")
flash(f"You are not authenticated to manage tenant {tenant_id}", 'danger')
return redirect(prefixed_url_for('user_bp.select_tenant'))
return redirect(prefixed_url_for('user_bp.tenants'))
the_tenant = Tenant.query.get(tenant_id)
# set tenant information in the session
@@ -217,7 +177,7 @@ def handle_tenant_selection():
return redirect(prefixed_url_for('user_bp.tenant_overview'))
# Add more conditions for other actions
return redirect(prefixed_url_for('select_tenant'))
return redirect(prefixed_url_for('tenants'))
@user_bp.route('/tenant_overview', methods=['GET'])
@@ -342,21 +302,10 @@ def edit_user(user_id):
@user_bp.route('/view_users')
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def view_users():
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
tenant_id = session.get('tenant').get('id')
query = User.query.filter_by(tenant_id=tenant_id).order_by(User.user_name)
pagination = query.paginate(page=page, per_page=per_page)
users = pagination.items
# prepare table data
rows = prepare_table_for_macro(users, [('id', ''), ('user_name', ''), ('email', '')])
# Render the users in a template
return render_template('user/view_users.html', rows=rows, pagination=pagination)
config = get_users_list_view(tenant_id)
return render_list_view('list_view.html', **config)
@user_bp.route('/handle_user_action', methods=['POST'])
@@ -385,24 +334,13 @@ def handle_user_action():
return redirect(prefixed_url_for('user_bp.view_users'))
# Tenant Domain Management ------------------------------------------------------------------------
@user_bp.route('/view_tenant_domains')
# Tenant Domain Management (Probably obsolete )------------------------------------------------------------------------
@user_bp.route('/tenant_domains')
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def view_tenant_domains():
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
tenant_id = session.get('tenant').get('id')
query = TenantDomain.query.filter_by(tenant_id=tenant_id).order_by(TenantDomain.domain)
pagination = query.paginate(page=page, per_page=per_page)
tenant_domains = pagination.items
# prepare table data
rows = prepare_table_for_macro(tenant_domains, [('id', ''), ('domain', ''), ('valid_to', '')])
# Render the users in a template
return render_template('user/view_tenant_domains.html', rows=rows, pagination=pagination)
def tenant_domains():
tenant_id = session['tenant']['id']
config = get_tenant_domains_list_view(tenant_id)
return render_list_view('list_view.html', **config)
@user_bp.route('/handle_tenant_domain_action', methods=['POST'])
@@ -418,7 +356,7 @@ def handle_tenant_domain_action():
if action == 'edit_tenant_domain':
return redirect(prefixed_url_for('user_bp.edit_tenant_domain', tenant_domain_id=tenant_domain_id))
# Add more conditions for other actions
return redirect(prefixed_url_for('view_tenant_domains'))
return redirect(prefixed_url_for('tenant_domains'))
@user_bp.route('/tenant_domain', methods=['GET', 'POST'])
@@ -470,7 +408,7 @@ def edit_tenant_domain(tenant_domain_id):
f'for tenant {session["tenant"]["id"]}'
f'Error: {str(e)}')
return redirect(
prefixed_url_for('user_bp.view_tenant_domains',
prefixed_url_for('user_bp.tenant_domains',
tenant_id=session['tenant']['id'])) # Assuming there's a user profile view to redirect to
else:
form_validation_failed(request, form)
@@ -535,21 +473,9 @@ def tenant_project():
@user_bp.route('/tenant_projects', methods=['GET', 'POST'])
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def tenant_projects():
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
tenant_id = session['tenant']['id']
query = TenantProject.query.filter_by(tenant_id=tenant_id).order_by(TenantProject.id)
pagination = query.paginate(page=page, per_page=per_page)
the_tenant_projects = pagination.items
# prepare table data
rows = prepare_table_for_macro(the_tenant_projects, [('id', ''), ('name', ''), ('visual_api_key', ''),
('responsible_email', ''), ('active', '')])
# Render the catalogs in a template
return render_template('user/tenant_projects.html', rows=rows, pagination=pagination)
config = get_tenant_projects_list_view(tenant_id)
return render_list_view('list_view.html', **config)
@user_bp.route('/handle_tenant_project_selection', methods=['POST'])
@@ -677,21 +603,9 @@ def tenant_make():
@user_bp.route('/tenant_makes', methods=['GET', 'POST'])
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def tenant_makes():
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
tenant_id = session['tenant']['id']
query = TenantMake.query.filter_by(tenant_id=tenant_id).order_by(TenantMake.id)
pagination = query.paginate(page=page, per_page=per_page)
tenant_makes = pagination.items
# prepare table data
rows = prepare_table_for_macro(tenant_makes,
[('id', ''), ('name', ''), ('website', ''), ('active', '')])
# Render the tenant makes in a template
return render_template('user/tenant_makes.html', rows=rows, pagination=pagination)
config = get_tenant_makes_list_view(tenant_id)
return render_list_view('list_view.html', **config)
@user_bp.route('/tenant_make/<int:tenant_make_id>', methods=['GET', 'POST'])
@@ -763,16 +677,13 @@ def handle_tenant_make_selection():
# Update session data if necessary
if 'tenant' in session:
session['tenant'] = tenant.to_dict()
return None
return None
except SQLAlchemyError as e:
db.session.rollback()
flash(f'Failed to update default tenant make. Error: {str(e)}', 'danger')
current_app.logger.error(f'Failed to update default tenant make. Error: {str(e)}')
return redirect(prefixed_url_for('user_bp.tenant_makes'))
return None
# Altijd teruggaan naar de tenant_makes pagina
return redirect(prefixed_url_for('user_bp.tenant_makes'))
def reset_uniquifier(user):