- 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:
@@ -0,0 +1,2 @@
|
||||
# List Views module initialization
|
||||
# This module contains utility functions for handling list views
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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')]
|
||||
}
|
||||
324
eveai_app/views/list_views/document_list_views.py
Normal file
324
eveai_app/views/list_views/document_list_views.py
Normal 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.'
|
||||
}
|
||||
@@ -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')]
|
||||
}
|
||||
164
eveai_app/views/list_views/entitlement_list_views.py
Normal file
164
eveai_app/views/list_views/entitlement_list_views.py
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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')]
|
||||
}
|
||||
198
eveai_app/views/list_views/interaction_list_views.py
Normal file
198
eveai_app/views/list_views/interaction_list_views.py
Normal 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
|
||||
}
|
||||
|
||||
92
eveai_app/views/list_views/list_view_utils.py
Normal file
92
eveai_app/views/list_views/list_view_utils.py
Normal 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)
|
||||
129
eveai_app/views/list_views/partner_list_views.py
Normal file
129
eveai_app/views/list_views/partner_list_views.py
Normal 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.'
|
||||
}
|
||||
234
eveai_app/views/list_views/user_list_views.py
Normal file
234
eveai_app/views/list_views/user_list_views.py
Normal 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}'
|
||||
}
|
||||
Reference in New Issue
Block a user