- 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:
@@ -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):
|
||||
|
||||
@@ -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) \
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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}'
|
||||
}
|
||||
@@ -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'])
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user