mirror of
https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit.git
synced 2025-06-15 17:20:00 +00:00
cleanup
This commit is contained in:
parent
6c76b47f04
commit
18202a9b99
@ -1,8 +0,0 @@
|
|||||||
# app/auth/__init__.py
|
|
||||||
from flask import Blueprint
|
|
||||||
|
|
||||||
# Define the auth blueprint
|
|
||||||
bp = Blueprint('auth', __name__, template_folder='templates')
|
|
||||||
|
|
||||||
# Import routes at the bottom
|
|
||||||
from app.auth import routes
|
|
@ -1,13 +0,0 @@
|
|||||||
# app/auth/forms.py
|
|
||||||
from flask_wtf import FlaskForm
|
|
||||||
from wtforms import StringField, PasswordField, BooleanField, SubmitField
|
|
||||||
from wtforms.validators import DataRequired
|
|
||||||
|
|
||||||
# Moved from control_panel forms
|
|
||||||
class LoginForm(FlaskForm):
|
|
||||||
username = StringField('Username', validators=[DataRequired()])
|
|
||||||
password = PasswordField('Password', validators=[DataRequired()])
|
|
||||||
remember_me = BooleanField('Remember Me')
|
|
||||||
submit = SubmitField('Sign In')
|
|
||||||
|
|
||||||
# Add RegistrationForm, ResetPasswordRequestForm etc. here later
|
|
@ -1,59 +0,0 @@
|
|||||||
# app/auth/routes.py
|
|
||||||
from flask import render_template, flash, redirect, url_for, request
|
|
||||||
from flask_login import current_user, login_user, logout_user # Keep current_user for checking auth status
|
|
||||||
from app import db
|
|
||||||
from app.models import User
|
|
||||||
from app.auth import bp # Import the auth blueprint
|
|
||||||
from .forms import LoginForm # Import LoginForm from within auth blueprint
|
|
||||||
|
|
||||||
@bp.route('/login', methods=['GET', 'POST'])
|
|
||||||
def login():
|
|
||||||
if current_user.is_authenticated:
|
|
||||||
# Redirect authenticated users away from login page
|
|
||||||
# Maybe check role here too? Or just send to core index.
|
|
||||||
if current_user.role == 'admin':
|
|
||||||
return redirect(url_for('control_panel.index'))
|
|
||||||
else:
|
|
||||||
return redirect(url_for('core.index'))
|
|
||||||
|
|
||||||
form = LoginForm()
|
|
||||||
if form.validate_on_submit():
|
|
||||||
user = User.query.filter_by(username=form.username.data).first()
|
|
||||||
if user is None or not user.check_password(form.password.data):
|
|
||||||
flash('Invalid username or password', 'danger')
|
|
||||||
return redirect(url_for('auth.login'))
|
|
||||||
|
|
||||||
# Log the user in
|
|
||||||
login_user(user, remember=form.remember_me.data)
|
|
||||||
flash(f'Welcome back, {user.username}!', 'success')
|
|
||||||
|
|
||||||
# --- Redirect Logic Modified ---
|
|
||||||
next_page = request.args.get('next')
|
|
||||||
|
|
||||||
# IMPORTANT: Validate next_page to prevent Open Redirect attacks
|
|
||||||
# Ensure it's a relative path within our site
|
|
||||||
if next_page and not next_page.startswith('/'):
|
|
||||||
flash('Invalid redirect specified.', 'warning') # Optional feedback
|
|
||||||
next_page = None # Discard invalid or external URLs
|
|
||||||
|
|
||||||
# If no valid 'next' page was provided, determine default based on role
|
|
||||||
if not next_page:
|
|
||||||
if user.role == 'admin':
|
|
||||||
# Default redirect for admins
|
|
||||||
next_page = url_for('control_panel.index')
|
|
||||||
else:
|
|
||||||
# Default redirect for non-admins (e.g., 'user' role)
|
|
||||||
next_page = url_for('core.index')
|
|
||||||
# --- End of Modified Redirect Logic ---
|
|
||||||
|
|
||||||
return redirect(next_page)
|
|
||||||
|
|
||||||
# Render login template (GET request or failed POST validation)
|
|
||||||
# Assuming template is directly in blueprint's template folder
|
|
||||||
return render_template('login.html', title='Sign In', form=form)
|
|
||||||
|
|
||||||
@bp.route('/logout')
|
|
||||||
def logout():
|
|
||||||
logout_user()
|
|
||||||
flash('You have been logged out.', 'info')
|
|
||||||
return redirect(url_for('core.index'))
|
|
@ -1,43 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container mt-4">
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<h1>Sign In</h1>
|
|
||||||
<hr>
|
|
||||||
<form action="" method="post" novalidate>
|
|
||||||
{{ form.hidden_tag() }} <div class="mb-3">
|
|
||||||
{{ form.username.label(class="form-label") }}
|
|
||||||
{{ form.username(class="form-control" + (" is-invalid" if form.username.errors else ""), size=32) }}
|
|
||||||
{% if form.username.errors %}
|
|
||||||
<div class="invalid-feedback">
|
|
||||||
{% for error in form.username.errors %}{{ error }}{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
{{ form.password.label(class="form-label") }}
|
|
||||||
{{ form.password(class="form-control" + (" is-invalid" if form.password.errors else ""), size=32) }}
|
|
||||||
{% if form.password.errors %}
|
|
||||||
<div class="invalid-feedback">
|
|
||||||
{% for error in form.password.errors %}{{ error }}{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3 form-check">
|
|
||||||
{{ form.remember_me(class="form-check-input") }}
|
|
||||||
{{ form.remember_me.label(class="form-check-label") }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
{{ form.submit(class="btn btn-primary") }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -5,47 +5,6 @@ from werkzeug.security import generate_password_hash, check_password_hash
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import json
|
import json
|
||||||
|
|
||||||
class User(UserMixin, db.Model):
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
|
||||||
username = db.Column(db.String(64), index=True, unique=True)
|
|
||||||
email = db.Column(db.String(120), index=True, unique=True)
|
|
||||||
password_hash = db.Column(db.String(256))
|
|
||||||
# --- ADDED ROLE COLUMN ---
|
|
||||||
role = db.Column(db.String(20), index=True, default='user', nullable=False)
|
|
||||||
|
|
||||||
# Optional: Add a helper property for easy checking
|
|
||||||
@property
|
|
||||||
def is_admin(self):
|
|
||||||
return self.role == 'admin'
|
|
||||||
# --- END ROLE COLUMN ---
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
# You might want to include the role in the representation
|
|
||||||
return f'<User {self.username} ({self.role})>'
|
|
||||||
|
|
||||||
def set_password(self, password):
|
|
||||||
self.password_hash = generate_password_hash(password)
|
|
||||||
|
|
||||||
def check_password(self, password):
|
|
||||||
# Ensure password_hash is not None before checking
|
|
||||||
if self.password_hash is None:
|
|
||||||
return False
|
|
||||||
return check_password_hash(self.password_hash, password)
|
|
||||||
|
|
||||||
# Add this new model
|
|
||||||
class ModuleRegistry(db.Model):
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
|
||||||
module_id = db.Column(db.String(100), unique=True, nullable=False, index=True) # Matches folder name
|
|
||||||
is_enabled = db.Column(db.Boolean, default=False, nullable=False)
|
|
||||||
display_name = db.Column(db.String(100), nullable=True) # Optional override from metadata
|
|
||||||
description = db.Column(db.Text, nullable=True) # Optional override from metadata
|
|
||||||
version = db.Column(db.String(30), nullable=True) # Store version discovered
|
|
||||||
# Add timestamp for when it was first discovered or last updated?
|
|
||||||
# last_seen = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"<ModuleRegistry {self.module_id} (Enabled: {self.is_enabled})>"
|
|
||||||
|
|
||||||
# --- ProcessedIg Model (MODIFIED for Examples) ---
|
# --- ProcessedIg Model (MODIFIED for Examples) ---
|
||||||
class ProcessedIg(db.Model):
|
class ProcessedIg(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
@ -1,135 +1,135 @@
|
|||||||
{# app/control_panel/templates/cp_downloaded_igs.html #}
|
{# app/control_panel/templates/cp_downloaded_igs.html #}
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2><i class="bi bi-journal-arrow-down me-2"></i>Manage FHIR Packages</h2>
|
<h2><i class="bi bi-journal-arrow-down me-2"></i>Manage FHIR Packages</h2>
|
||||||
<div>
|
<div>
|
||||||
<a href="{{ url_for('fhir_ig_importer.import_ig') }}" class="btn btn-success"><i class="bi bi-download me-1"></i> Import More IGs</a>
|
<a href="{{ url_for('fhir_ig_importer.import_ig') }}" class="btn btn-success"><i class="bi bi-download me-1"></i> Import More IGs</a>
|
||||||
<a href="{{ url_for('control_panel.index') }}" class="btn btn-secondary"><i class="bi bi-arrow-left"></i> Back to CP Index</a>
|
<a href="{{ url_for('control_panel.index') }}" class="btn btn-secondary"><i class="bi bi-arrow-left"></i> Back to CP Index</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if error_message %}
|
{% if error_message %}
|
||||||
<div class="alert alert-danger" role="alert">
|
<div class="alert alert-danger" role="alert">
|
||||||
<h5 class="alert-heading">Error</h5>
|
<h5 class="alert-heading">Error</h5>
|
||||||
{{ error_message }}
|
{{ error_message }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# NOTE: The block calculating processed_ids set using {% set %} was REMOVED from here #}
|
{# NOTE: The block calculating processed_ids set using {% set %} was REMOVED from here #}
|
||||||
|
|
||||||
{# --- Start Two Column Layout --- #}
|
{# --- Start Two Column Layout --- #}
|
||||||
<div class="row g-4">
|
<div class="row g-4">
|
||||||
|
|
||||||
{# --- Left Column: Downloaded Packages (Horizontal Buttons) --- #}
|
{# --- Left Column: Downloaded Packages (Horizontal Buttons) --- #}
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="card-header"><i class="bi bi-folder-symlink me-1"></i> Downloaded Packages ({{ packages|length }})</div>
|
<div class="card-header"><i class="bi bi-folder-symlink me-1"></i> Downloaded Packages ({{ packages|length }})</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if packages %}
|
{% if packages %}
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-sm table-hover">
|
<table class="table table-sm table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<p class="mb-2"><small><span class="badge bg-danger text-dark border me-1">Risk:</span>= Duplicate Dependancy with different versions</small></p>
|
<p class="mb-2"><small><span class="badge bg-danger text-dark border me-1">Risk:</span>= Duplicate Dependancy with different versions</small></p>
|
||||||
<tr><th>Package Name</th><th>Version</th><th>Actions</th></tr>
|
<tr><th>Package Name</th><th>Version</th><th>Actions</th></tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for pkg in packages %}
|
{% for pkg in packages %}
|
||||||
{% set is_processed = (pkg.name, pkg.version) in processed_ids %}
|
{% set is_processed = (pkg.name, pkg.version) in processed_ids %}
|
||||||
{# --- ADDED: Check for duplicate name --- #}
|
{# --- ADDED: Check for duplicate name --- #}
|
||||||
{% set is_duplicate = pkg.name in duplicate_names %}
|
{% set is_duplicate = pkg.name in duplicate_names %}
|
||||||
{# --- ADDED: Assign row class based on duplicate group --- #}
|
{# --- ADDED: Assign row class based on duplicate group --- #}
|
||||||
<tr class="{{ duplicate_groups.get(pkg.name, '') if is_duplicate else '' }}">
|
<tr class="{{ duplicate_groups.get(pkg.name, '') if is_duplicate else '' }}">
|
||||||
<td>
|
<td>
|
||||||
{# --- ADDED: Risk Badge for duplicates --- #}
|
{# --- ADDED: Risk Badge for duplicates --- #}
|
||||||
{% if is_duplicate %}
|
{% if is_duplicate %}
|
||||||
<span class="badge bg-danger mb-1 d-block">Duplicate</span>
|
<span class="badge bg-danger mb-1 d-block">Duplicate</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{# --- End Add --- #}
|
{# --- End Add --- #}
|
||||||
<code>{{ pkg.name }}</code>
|
<code>{{ pkg.name }}</code>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ pkg.version }}</td>
|
<td>{{ pkg.version }}</td>
|
||||||
<td> {# Actions #}
|
<td> {# Actions #}
|
||||||
<div class="btn-group btn-group-sm" role="group">
|
<div class="btn-group btn-group-sm" role="group">
|
||||||
{% if is_processed %}
|
{% if is_processed %}
|
||||||
<span class="btn btn-success disabled"><i class="bi bi-check-lg"></i> Processed</span>
|
<span class="btn btn-success disabled"><i class="bi bi-check-lg"></i> Processed</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<form action="{{ url_for('control_panel.process_ig') }}" method="POST" style="display: inline-block;">
|
<form action="{{ url_for('control_panel.process_ig') }}" method="POST" style="display: inline-block;">
|
||||||
<input type="hidden" name="package_name" value="{{ pkg.name }}">
|
<input type="hidden" name="package_name" value="{{ pkg.name }}">
|
||||||
<input type="hidden" name="package_version" value="{{ pkg.version }}">
|
<input type="hidden" name="package_version" value="{{ pkg.version }}">
|
||||||
<button type="submit" class="btn btn-outline-primary" title="Mark as processed"><i class="bi bi-gear"></i> Process</button>
|
<button type="submit" class="btn btn-outline-primary" title="Mark as processed"><i class="bi bi-gear"></i> Process</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form action="{{ url_for('control_panel.delete_ig_file') }}" method="POST" style="display: inline-block;" onsubmit="return confirm('Delete file \'{{ pkg.filename }}\'?');">
|
<form action="{{ url_for('control_panel.delete_ig_file') }}" method="POST" style="display: inline-block;" onsubmit="return confirm('Delete file \'{{ pkg.filename }}\'?');">
|
||||||
<input type="hidden" name="filename" value="{{ pkg.filename }}">
|
<input type="hidden" name="filename" value="{{ pkg.filename }}">
|
||||||
<button type="submit" class="btn btn-outline-danger" title="Delete File"><i class="bi bi-trash"></i> Delete</button>
|
<button type="submit" class="btn btn-outline-danger" title="Delete File"><i class="bi bi-trash"></i> Delete</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% elif not error_message %}<p class="text-muted">No downloaded FHIR packages found.</p>{% endif %}
|
{% elif not error_message %}<p class="text-muted">No downloaded FHIR packages found.</p>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>{# --- End Left Column --- #}
|
</div>{# --- End Left Column --- #}
|
||||||
|
|
||||||
|
|
||||||
{# --- Right Column: Processed Packages (Vertical Buttons) --- #}
|
{# --- Right Column: Processed Packages (Vertical Buttons) --- #}
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="card-header"><i class="bi bi-check-circle me-1"></i> Processed Packages ({{ processed_list|length }})</div>
|
<div class="card-header"><i class="bi bi-check-circle me-1"></i> Processed Packages ({{ processed_list|length }})</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if processed_list %}
|
{% if processed_list %}
|
||||||
<p class="mb-2"><small><span class="badge bg-warning text-dark border me-1">MS</span> = Contains Must Support Elements</small></p>
|
<p class="mb-2"><small><span class="badge bg-warning text-dark border me-1">MS</span> = Contains Must Support Elements</small></p>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-sm table-hover">
|
<table class="table table-sm table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr><th>Package Name</th><th>Version</th><th>Resource Types</th><th>Actions</th></tr>
|
<tr><th>Package Name</th><th>Version</th><th>Resource Types</th><th>Actions</th></tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for processed_ig in processed_list %}
|
{% for processed_ig in processed_list %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{# Tooltip for Processed At / Status #}
|
<td>{# Tooltip for Processed At / Status #}
|
||||||
{% set tooltip_title_parts = [] %}
|
{% set tooltip_title_parts = [] %}
|
||||||
{% if processed_ig.processed_at %}{% set _ = tooltip_title_parts.append("Processed: " + processed_ig.processed_at.strftime('%Y-%m-%d %H:%M')) %}{% endif %}
|
{% if processed_ig.processed_at %}{% set _ = tooltip_title_parts.append("Processed: " + processed_ig.processed_at.strftime('%Y-%m-%d %H:%M')) %}{% endif %}
|
||||||
{% if processed_ig.status %}{% set _ = tooltip_title_parts.append("Status: " + processed_ig.status) %}{% endif %}
|
{% if processed_ig.status %}{% set _ = tooltip_title_parts.append("Status: " + processed_ig.status) %}{% endif %}
|
||||||
{% set tooltip_text = tooltip_title_parts | join('\n') %}
|
{% set tooltip_text = tooltip_title_parts | join('\n') %}
|
||||||
<code data-bs-toggle="tooltip" data-bs-placement="top" title="{{ tooltip_text }}">{{ processed_ig.package_name }}</code>
|
<code data-bs-toggle="tooltip" data-bs-placement="top" title="{{ tooltip_text }}">{{ processed_ig.package_name }}</code>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ processed_ig.package_version }}</td>
|
<td>{{ processed_ig.package_version }}</td>
|
||||||
<td> {# Resource Types Cell w/ Badges #}
|
<td> {# Resource Types Cell w/ Badges #}
|
||||||
{% set types_info = processed_ig.resource_types_info %}
|
{% set types_info = processed_ig.resource_types_info %}
|
||||||
{% if types_info %}<div class="d-flex flex-wrap gap-1">{% for type_info in types_info %}{% if type_info.must_support %}<span class="badge bg-warning text-dark border" title="Has Must Support">{{ type_info.name }}</span>{% else %}<span class="badge bg-light text-dark border">{{ type_info.name }}</span>{% endif %}{% endfor %}</div>{% else %}<small class="text-muted">N/A</small>{% endif %}
|
{% if types_info %}<div class="d-flex flex-wrap gap-1">{% for type_info in types_info %}{% if type_info.must_support %}<span class="badge bg-warning text-dark border" title="Has Must Support">{{ type_info.name }}</span>{% else %}<span class="badge bg-light text-dark border">{{ type_info.name }}</span>{% endif %}{% endfor %}</div>{% else %}<small class="text-muted">N/A</small>{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{# Vertical Button Group #}
|
{# Vertical Button Group #}
|
||||||
<div class="btn-group-vertical btn-group-sm w-100" role="group" aria-label="Processed Package Actions for {{ processed_ig.package_name }}">
|
<div class="btn-group-vertical btn-group-sm w-100" role="group" aria-label="Processed Package Actions for {{ processed_ig.package_name }}">
|
||||||
<a href="{{ url_for('control_panel.view_processed_ig', processed_ig_id=processed_ig.id) }}" class="btn btn-outline-info w-100" title="View Details"><i class="bi bi-search"></i> View</a>
|
<a href="{{ url_for('control_panel.view_processed_ig', processed_ig_id=processed_ig.id) }}" class="btn btn-outline-info w-100" title="View Details"><i class="bi bi-search"></i> View</a>
|
||||||
<form action="{{ url_for('control_panel.unload_ig') }}" method="POST" onsubmit="return confirm('Unload record for \'{{ processed_ig.package_name }}#{{ processed_ig.package_version }}\'?');">
|
<form action="{{ url_for('control_panel.unload_ig') }}" method="POST" onsubmit="return confirm('Unload record for \'{{ processed_ig.package_name }}#{{ processed_ig.package_version }}\'?');">
|
||||||
<input type="hidden" name="processed_ig_id" value="{{ processed_ig.id }}">
|
<input type="hidden" name="processed_ig_id" value="{{ processed_ig.id }}">
|
||||||
<button type="submit" class="btn btn-outline-warning w-100" title="Unload/Remove Processed Record"><i class="bi bi-x-lg"></i> Unload</button>
|
<button type="submit" class="btn btn-outline-warning w-100" title="Unload/Remove Processed Record"><i class="bi bi-x-lg"></i> Unload</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% elif not error_message %}<p class="text-muted">No packages recorded as processed yet.</p>{% endif %}
|
{% elif not error_message %}<p class="text-muted">No packages recorded as processed yet.</p>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>{# --- End Right Column --- #}
|
</div>{# --- End Right Column --- #}
|
||||||
|
|
||||||
</div>{# --- End Row --- #}
|
</div>{# --- End Row --- #}
|
||||||
|
|
||||||
</div>{# End container #}
|
</div>{# End container #}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{# Tooltip JS Initializer should be in base.html #}
|
{# Tooltip JS Initializer should be in base.html #}
|
||||||
{% block scripts %}{{ super() }}{% endblock %}
|
{% block scripts %}{{ super() }}{% endblock %}
|
@ -1,405 +1,405 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% from "_form_helpers.html" import render_field %}
|
{% from "_form_helpers.html" import render_field %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
<h2><i class="bi bi-info-circle me-2"></i>{{ title }}</h2>
|
<h2><i class="bi bi-info-circle me-2"></i>{{ title }}</h2>
|
||||||
<a href="{{ url_for('control_panel.list_downloaded_igs') }}" class="btn btn-secondary"><i class="bi bi-arrow-left"></i> Back to Package List</a>
|
<a href="{{ url_for('control_panel.list_downloaded_igs') }}" class="btn btn-secondary"><i class="bi bi-arrow-left"></i> Back to Package List</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if processed_ig %}
|
{% if processed_ig %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">Package Details</div>
|
<div class="card-header">Package Details</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
<dt class="col-sm-3">Package Name</dt>
|
<dt class="col-sm-3">Package Name</dt>
|
||||||
<dd class="col-sm-9"><code>{{ processed_ig.package_name }}</code></dd>
|
<dd class="col-sm-9"><code>{{ processed_ig.package_name }}</code></dd>
|
||||||
<dt class="col-sm-3">Package Version</dt>
|
<dt class="col-sm-3">Package Version</dt>
|
||||||
<dd class="col-sm-9">{{ processed_ig.package_version }}</dd>
|
<dd class="col-sm-9">{{ processed_ig.package_version }}</dd>
|
||||||
<dt class="col-sm-3">Processed At</dt>
|
<dt class="col-sm-3">Processed At</dt>
|
||||||
<dd class="col-sm-9">{{ processed_ig.processed_at.strftime('%Y-%m-%d %H:%M:%S UTC') if processed_ig.processed_at else 'N/A' }}</dd>
|
<dd class="col-sm-9">{{ processed_ig.processed_at.strftime('%Y-%m-%d %H:%M:%S UTC') if processed_ig.processed_at else 'N/A' }}</dd>
|
||||||
<dt class="col-sm-3">Processing Status</dt>
|
<dt class="col-sm-3">Processing Status</dt>
|
||||||
<dd class="col-sm-9">
|
<dd class="col-sm-9">
|
||||||
<span class="badge rounded-pill text-bg-{{ 'success' if processed_ig.status == 'processed' else ('warning' if processed_ig.status == 'processed_with_errors' else 'secondary') }}">{{ processed_ig.status or 'N/A' }}</span>
|
<span class="badge rounded-pill text-bg-{{ 'success' if processed_ig.status == 'processed' else ('warning' if processed_ig.status == 'processed_with_errors' else 'secondary') }}">{{ processed_ig.status or 'N/A' }}</span>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card mt-4">
|
<div class="card mt-4">
|
||||||
<div class="card-header">Resource Types Found / Defined </div>
|
<div class="card-header">Resource Types Found / Defined </div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if profile_list or base_list %}
|
{% if profile_list or base_list %}
|
||||||
<p class="mb-2"><small><span class="badge bg-warning text-dark border me-1">MS</span> = Contains Must Support Elements</small></p>
|
<p class="mb-2"><small><span class="badge bg-warning text-dark border me-1">MS</span> = Contains Must Support Elements</small></p>
|
||||||
{% if profile_list %}
|
{% if profile_list %}
|
||||||
<h6>Profiles Defined ({{ profile_list|length }}):</h6>
|
<h6>Profiles Defined ({{ profile_list|length }}):</h6>
|
||||||
<div class="d-flex flex-wrap gap-1 mb-3 resource-type-list">
|
<div class="d-flex flex-wrap gap-1 mb-3 resource-type-list">
|
||||||
{% for type_info in profile_list %}
|
{% for type_info in profile_list %}
|
||||||
<a href="#" class="resource-type-link text-decoration-none"
|
<a href="#" class="resource-type-link text-decoration-none"
|
||||||
data-package-name="{{ processed_ig.package_name }}"
|
data-package-name="{{ processed_ig.package_name }}"
|
||||||
data-package-version="{{ processed_ig.package_version }}"
|
data-package-version="{{ processed_ig.package_version }}"
|
||||||
data-resource-type="{{ type_info.name }}"
|
data-resource-type="{{ type_info.name }}"
|
||||||
aria-label="View structure for {{ type_info.name }}">
|
aria-label="View structure for {{ type_info.name }}">
|
||||||
{% if type_info.must_support %}
|
{% if type_info.must_support %}
|
||||||
<span class="badge bg-warning text-dark border" title="Contains Must Support elements">{{ type_info.name }}</span>
|
<span class="badge bg-warning text-dark border" title="Contains Must Support elements">{{ type_info.name }}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge bg-light text-dark border">{{ type_info.name }}</span>
|
<span class="badge bg-light text-dark border">{{ type_info.name }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if base_list %}
|
{% if base_list %}
|
||||||
<p class="mb-2"><small><span class="badge bg-primary text-light border me-1">Examples</span> = - Examples will be displayed when selecting Base Types if contained in the IG</small></p>
|
<p class="mb-2"><small><span class="badge bg-primary text-light border me-1">Examples</span> = - Examples will be displayed when selecting Base Types if contained in the IG</small></p>
|
||||||
<h6>Base Resource Types Referenced ({{ base_list|length }}):</h6>
|
<h6>Base Resource Types Referenced ({{ base_list|length }}):</h6>
|
||||||
<div class="d-flex flex-wrap gap-1 resource-type-list">
|
<div class="d-flex flex-wrap gap-1 resource-type-list">
|
||||||
{% for type_info in base_list %}
|
{% for type_info in base_list %}
|
||||||
<a href="#" class="resource-type-link text-decoration-none"
|
<a href="#" class="resource-type-link text-decoration-none"
|
||||||
data-package-name="{{ processed_ig.package_name }}"
|
data-package-name="{{ processed_ig.package_name }}"
|
||||||
data-package-version="{{ processed_ig.package_version }}"
|
data-package-version="{{ processed_ig.package_version }}"
|
||||||
data-resource-type="{{ type_info.name }}"
|
data-resource-type="{{ type_info.name }}"
|
||||||
aria-label="View structure for {{ type_info.name }}">
|
aria-label="View structure for {{ type_info.name }}">
|
||||||
{% if type_info.must_support %}
|
{% if type_info.must_support %}
|
||||||
<span class="badge bg-warning text-dark border" title="Contains Must Support elements">{{ type_info.name }}</span>
|
<span class="badge bg-warning text-dark border" title="Contains Must Support elements">{{ type_info.name }}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge bg-light text-dark border">{{ type_info.name }}</span>
|
<span class="badge bg-light text-dark border">{{ type_info.name }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="text-muted"><em>No resource type information extracted or stored.</em></p>
|
<p class="text-muted"><em>No resource type information extracted or stored.</em></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="structure-display-wrapper" class="mt-4" style="display: none;">
|
<div id="structure-display-wrapper" class="mt-4" style="display: none;">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<span>Structure Definition for: <code id="structure-title"></code></span>
|
<span>Structure Definition for: <code id="structure-title"></code></span>
|
||||||
<button type="button" class="btn-close" aria-label="Close Details View" onclick="document.getElementById('structure-display-wrapper').style.display='none'; document.getElementById('example-display-wrapper').style.display='none'; document.getElementById('raw-structure-wrapper').style.display='none';"></button>
|
<button type="button" class="btn-close" aria-label="Close Details View" onclick="document.getElementById('structure-display-wrapper').style.display='none'; document.getElementById('example-display-wrapper').style.display='none'; document.getElementById('raw-structure-wrapper').style.display='none';"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div id="structure-loading" class="text-center py-3" style="display: none;">
|
<div id="structure-loading" class="text-center py-3" style="display: none;">
|
||||||
<div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div>
|
<div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="structure-content"></div>
|
<div id="structure-content"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="raw-structure-wrapper" class="mt-4" style="display: none;">
|
<div id="raw-structure-wrapper" class="mt-4" style="display: none;">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">Raw Structure Definition for <code id="raw-structure-title"></code></div>
|
<div class="card-header">Raw Structure Definition for <code id="raw-structure-title"></code></div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div id="raw-structure-loading" class="text-center py-3" style="display: none;">
|
<div id="raw-structure-loading" class="text-center py-3" style="display: none;">
|
||||||
<div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div>
|
<div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div>
|
||||||
</div>
|
</div>
|
||||||
<pre><code id="raw-structure-content" class="p-2 d-block border bg-light" style="max-height: 400px; overflow-y: auto;"></code></pre>
|
<pre><code id="raw-structure-content" class="p-2 d-block border bg-light" style="max-height: 400px; overflow-y: auto;"></code></pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="example-display-wrapper" class="mt-4" style="display: none;">
|
<div id="example-display-wrapper" class="mt-4" style="display: none;">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">Examples for <code id="example-resource-type-title"></code></div>
|
<div class="card-header">Examples for <code id="example-resource-type-title"></code></div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="mb-3" id="example-selector-wrapper" style="display: none;">
|
<div class="mb-3" id="example-selector-wrapper" style="display: none;">
|
||||||
<label for="example-select" class="form-label">Select Example:</label>
|
<label for="example-select" class="form-label">Select Example:</label>
|
||||||
<select class="form-select form-select-sm" id="example-select"><option selected value="">-- Select --</option></select>
|
<select class="form-select form-select-sm" id="example-select"><option selected value="">-- Select --</option></select>
|
||||||
</div>
|
</div>
|
||||||
<div id="example-loading" class="text-center py-3" style="display: none;">
|
<div id="example-loading" class="text-center py-3" style="display: none;">
|
||||||
<div class="spinner-border spinner-border-sm text-secondary" role="status"><span class="visually-hidden">Loading...</span></div>
|
<div class="spinner-border spinner-border-sm text-secondary" role="status"><span class="visually-hidden">Loading...</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="example-content-wrapper" style="display: none;">
|
<div id="example-content-wrapper" style="display: none;">
|
||||||
<h6 id="example-filename" class="mt-2 small text-muted"></h6>
|
<h6 id="example-filename" class="mt-2 small text-muted"></h6>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h6>Raw Content</h6>
|
<h6>Raw Content</h6>
|
||||||
<pre><code id="example-content-raw" class="p-2 d-block border bg-light" style="max-height: 400px; overflow-y: auto;"></code></pre>
|
<pre><code id="example-content-raw" class="p-2 d-block border bg-light" style="max-height: 400px; overflow-y: auto;"></code></pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h6>Pretty-Printed JSON</h6>
|
<h6>Pretty-Printed JSON</h6>
|
||||||
<pre><code id="example-content-json" class="p-2 d-block border bg-light" style="max-height: 400px; overflow-y: auto;"></code></pre>
|
<pre><code id="example-content-json" class="p-2 d-block border bg-light" style="max-height: 400px; overflow-y: auto;"></code></pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="alert alert-warning" role="alert">Processed IG details not available.</div>
|
<div class="alert alert-warning" role="alert">Processed IG details not available.</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
<script>const examplesData = {{ examples_by_type | tojson | safe }};</script>
|
<script>const examplesData = {{ examples_by_type | tojson | safe }};</script>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const structureDisplayWrapper = document.getElementById('structure-display-wrapper');
|
const structureDisplayWrapper = document.getElementById('structure-display-wrapper');
|
||||||
const structureDisplay = document.getElementById('structure-content');
|
const structureDisplay = document.getElementById('structure-content');
|
||||||
const structureTitle = document.getElementById('structure-title');
|
const structureTitle = document.getElementById('structure-title');
|
||||||
const structureLoading = document.getElementById('structure-loading');
|
const structureLoading = document.getElementById('structure-loading');
|
||||||
const exampleDisplayWrapper = document.getElementById('example-display-wrapper');
|
const exampleDisplayWrapper = document.getElementById('example-display-wrapper');
|
||||||
const exampleSelectorWrapper = document.getElementById('example-selector-wrapper');
|
const exampleSelectorWrapper = document.getElementById('example-selector-wrapper');
|
||||||
const exampleSelect = document.getElementById('example-select');
|
const exampleSelect = document.getElementById('example-select');
|
||||||
const exampleResourceTypeTitle = document.getElementById('example-resource-type-title');
|
const exampleResourceTypeTitle = document.getElementById('example-resource-type-title');
|
||||||
const exampleLoading = document.getElementById('example-loading');
|
const exampleLoading = document.getElementById('example-loading');
|
||||||
const exampleContentWrapper = document.getElementById('example-content-wrapper');
|
const exampleContentWrapper = document.getElementById('example-content-wrapper');
|
||||||
const exampleFilename = document.getElementById('example-filename');
|
const exampleFilename = document.getElementById('example-filename');
|
||||||
const exampleContentRaw = document.getElementById('example-content-raw');
|
const exampleContentRaw = document.getElementById('example-content-raw');
|
||||||
const exampleContentJson = document.getElementById('example-content-json');
|
const exampleContentJson = document.getElementById('example-content-json');
|
||||||
const rawStructureWrapper = document.getElementById('raw-structure-wrapper');
|
const rawStructureWrapper = document.getElementById('raw-structure-wrapper');
|
||||||
const rawStructureTitle = document.getElementById('raw-structure-title');
|
const rawStructureTitle = document.getElementById('raw-structure-title');
|
||||||
const rawStructureContent = document.getElementById('raw-structure-content');
|
const rawStructureContent = document.getElementById('raw-structure-content');
|
||||||
const rawStructureLoading = document.getElementById('raw-structure-loading');
|
const rawStructureLoading = document.getElementById('raw-structure-loading');
|
||||||
|
|
||||||
const structureBaseUrl = "/control-panel/fhir/get-structure";
|
const structureBaseUrl = "/control-panel/fhir/get-structure";
|
||||||
const exampleBaseUrl = "/control-panel/fhir/get-example";
|
const exampleBaseUrl = "/control-panel/fhir/get-example";
|
||||||
|
|
||||||
let currentPkgName = null;
|
let currentPkgName = null;
|
||||||
let currentPkgVersion = null;
|
let currentPkgVersion = null;
|
||||||
let currentRawStructureData = null;
|
let currentRawStructureData = null;
|
||||||
|
|
||||||
document.body.addEventListener('click', function(event) {
|
document.body.addEventListener('click', function(event) {
|
||||||
const link = event.target.closest('.resource-type-link');
|
const link = event.target.closest('.resource-type-link');
|
||||||
if (!link) return;
|
if (!link) return;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
currentPkgName = link.dataset.packageName;
|
currentPkgName = link.dataset.packageName;
|
||||||
currentPkgVersion = link.dataset.packageVersion;
|
currentPkgVersion = link.dataset.packageVersion;
|
||||||
const resourceType = link.dataset.resourceType;
|
const resourceType = link.dataset.resourceType;
|
||||||
if (!currentPkgName || !currentPkgVersion || !resourceType) return;
|
if (!currentPkgName || !currentPkgVersion || !resourceType) return;
|
||||||
|
|
||||||
const structureParams = new URLSearchParams({
|
const structureParams = new URLSearchParams({
|
||||||
package_name: currentPkgName,
|
package_name: currentPkgName,
|
||||||
package_version: currentPkgVersion,
|
package_version: currentPkgVersion,
|
||||||
resource_type: resourceType
|
resource_type: resourceType
|
||||||
});
|
});
|
||||||
const structureFetchUrl = `${structureBaseUrl}?${structureParams.toString()}`;
|
const structureFetchUrl = `${structureBaseUrl}?${structureParams.toString()}`;
|
||||||
|
|
||||||
structureTitle.textContent = `${resourceType} (${currentPkgName}#${currentPkgVersion})`;
|
structureTitle.textContent = `${resourceType} (${currentPkgName}#${currentPkgVersion})`;
|
||||||
rawStructureTitle.textContent = `${resourceType} (${currentPkgName}#${currentPkgVersion})`;
|
rawStructureTitle.textContent = `${resourceType} (${currentPkgName}#${currentPkgVersion})`;
|
||||||
structureDisplay.innerHTML = '';
|
structureDisplay.innerHTML = '';
|
||||||
rawStructureContent.textContent = '';
|
rawStructureContent.textContent = '';
|
||||||
structureLoading.style.display = 'block';
|
structureLoading.style.display = 'block';
|
||||||
rawStructureLoading.style.display = 'block';
|
rawStructureLoading.style.display = 'block';
|
||||||
structureDisplayWrapper.style.display = 'block';
|
structureDisplayWrapper.style.display = 'block';
|
||||||
rawStructureWrapper.style.display = 'block';
|
rawStructureWrapper.style.display = 'block';
|
||||||
exampleDisplayWrapper.style.display = 'none';
|
exampleDisplayWrapper.style.display = 'none';
|
||||||
|
|
||||||
fetch(structureFetchUrl)
|
fetch(structureFetchUrl)
|
||||||
.then(response => response.json().then(data => ({ ok: response.ok, status: response.status, data })))
|
.then(response => response.json().then(data => ({ ok: response.ok, status: response.status, data })))
|
||||||
.then(result => {
|
.then(result => {
|
||||||
if (!result.ok) throw new Error(result.data.error || `HTTP error ${result.status}`);
|
if (!result.ok) throw new Error(result.data.error || `HTTP error ${result.status}`);
|
||||||
currentRawStructureData = result.data;
|
currentRawStructureData = result.data;
|
||||||
renderStructureTree(result.data.elements, result.data.must_support_paths || []);
|
renderStructureTree(result.data.elements, result.data.must_support_paths || []);
|
||||||
rawStructureContent.textContent = JSON.stringify(result.data, null, 2);
|
rawStructureContent.textContent = JSON.stringify(result.data, null, 2);
|
||||||
populateExampleSelector(resourceType);
|
populateExampleSelector(resourceType);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error fetching structure:', error);
|
console.error('Error fetching structure:', error);
|
||||||
structureDisplay.innerHTML = `<div class="alert alert-danger">Error: ${error.message}</div>`;
|
structureDisplay.innerHTML = `<div class="alert alert-danger">Error: ${error.message}</div>`;
|
||||||
rawStructureContent.textContent = `Error: ${error.message}`;
|
rawStructureContent.textContent = `Error: ${error.message}`;
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
structureLoading.style.display = 'none';
|
structureLoading.style.display = 'none';
|
||||||
rawStructureLoading.style.display = 'none';
|
rawStructureLoading.style.display = 'none';
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function populateExampleSelector(resourceOrProfileIdentifier) {
|
function populateExampleSelector(resourceOrProfileIdentifier) {
|
||||||
exampleResourceTypeTitle.textContent = resourceOrProfileIdentifier;
|
exampleResourceTypeTitle.textContent = resourceOrProfileIdentifier;
|
||||||
const availableExamples = examplesData[resourceOrProfileIdentifier] || [];
|
const availableExamples = examplesData[resourceOrProfileIdentifier] || [];
|
||||||
exampleSelect.innerHTML = '<option selected value="">-- Select --</option>';
|
exampleSelect.innerHTML = '<option selected value="">-- Select --</option>';
|
||||||
exampleContentWrapper.style.display = 'none';
|
exampleContentWrapper.style.display = 'none';
|
||||||
exampleFilename.textContent = '';
|
exampleFilename.textContent = '';
|
||||||
exampleContentRaw.textContent = '';
|
exampleContentRaw.textContent = '';
|
||||||
exampleContentJson.textContent = '';
|
exampleContentJson.textContent = '';
|
||||||
if (availableExamples.length > 0) {
|
if (availableExamples.length > 0) {
|
||||||
availableExamples.forEach(filePath => {
|
availableExamples.forEach(filePath => {
|
||||||
const filename = filePath.split('/').pop();
|
const filename = filePath.split('/').pop();
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.value = filePath;
|
option.value = filePath;
|
||||||
option.textContent = filename;
|
option.textContent = filename;
|
||||||
exampleSelect.appendChild(option);
|
exampleSelect.appendChild(option);
|
||||||
});
|
});
|
||||||
exampleSelectorWrapper.style.display = 'block';
|
exampleSelectorWrapper.style.display = 'block';
|
||||||
exampleDisplayWrapper.style.display = 'block';
|
exampleDisplayWrapper.style.display = 'block';
|
||||||
} else {
|
} else {
|
||||||
exampleSelectorWrapper.style.display = 'none';
|
exampleSelectorWrapper.style.display = 'none';
|
||||||
exampleDisplayWrapper.style.display = 'none';
|
exampleDisplayWrapper.style.display = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(exampleSelect) {
|
if(exampleSelect) {
|
||||||
exampleSelect.addEventListener('change', function(event) {
|
exampleSelect.addEventListener('change', function(event) {
|
||||||
const selectedFilePath = this.value;
|
const selectedFilePath = this.value;
|
||||||
exampleContentRaw.textContent = '';
|
exampleContentRaw.textContent = '';
|
||||||
exampleContentJson.textContent = '';
|
exampleContentJson.textContent = '';
|
||||||
exampleFilename.textContent = '';
|
exampleFilename.textContent = '';
|
||||||
exampleContentWrapper.style.display = 'none';
|
exampleContentWrapper.style.display = 'none';
|
||||||
if (!selectedFilePath || !currentPkgName || !currentPkgVersion) return;
|
if (!selectedFilePath || !currentPkgName || !currentPkgVersion) return;
|
||||||
|
|
||||||
const exampleParams = new URLSearchParams({
|
const exampleParams = new URLSearchParams({
|
||||||
package_name: currentPkgName,
|
package_name: currentPkgName,
|
||||||
package_version: currentPkgVersion,
|
package_version: currentPkgVersion,
|
||||||
filename: selectedFilePath
|
filename: selectedFilePath
|
||||||
});
|
});
|
||||||
const exampleFetchUrl = `${exampleBaseUrl}?${exampleParams.toString()}`;
|
const exampleFetchUrl = `${exampleBaseUrl}?${exampleParams.toString()}`;
|
||||||
console.log("Fetching example from:", exampleFetchUrl);
|
console.log("Fetching example from:", exampleFetchUrl);
|
||||||
exampleLoading.style.display = 'block';
|
exampleLoading.style.display = 'block';
|
||||||
fetch(exampleFetchUrl)
|
fetch(exampleFetchUrl)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return response.text().then(errText => { throw new Error(errText || `HTTP error ${response.status}`); });
|
return response.text().then(errText => { throw new Error(errText || `HTTP error ${response.status}`); });
|
||||||
}
|
}
|
||||||
return response.text();
|
return response.text();
|
||||||
})
|
})
|
||||||
.then(content => {
|
.then(content => {
|
||||||
exampleFilename.textContent = `Source: ${selectedFilePath.split('/').pop()}`;
|
exampleFilename.textContent = `Source: ${selectedFilePath.split('/').pop()}`;
|
||||||
exampleContentRaw.textContent = content;
|
exampleContentRaw.textContent = content;
|
||||||
try {
|
try {
|
||||||
const jsonContent = JSON.parse(content);
|
const jsonContent = JSON.parse(content);
|
||||||
exampleContentJson.textContent = JSON.stringify(jsonContent, null, 2);
|
exampleContentJson.textContent = JSON.stringify(jsonContent, null, 2);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
exampleContentJson.textContent = 'Not valid JSON';
|
exampleContentJson.textContent = 'Not valid JSON';
|
||||||
}
|
}
|
||||||
exampleContentWrapper.style.display = 'block';
|
exampleContentWrapper.style.display = 'block';
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
exampleFilename.textContent = 'Error';
|
exampleFilename.textContent = 'Error';
|
||||||
exampleContentRaw.textContent = `Error: ${error.message}`;
|
exampleContentRaw.textContent = `Error: ${error.message}`;
|
||||||
exampleContentJson.textContent = `Error: ${error.message}`;
|
exampleContentJson.textContent = `Error: ${error.message}`;
|
||||||
exampleContentWrapper.style.display = 'block';
|
exampleContentWrapper.style.display = 'block';
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
exampleLoading.style.display = 'none';
|
exampleLoading.style.display = 'none';
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildTreeData(elements) {
|
function buildTreeData(elements) {
|
||||||
const treeRoot = { children: {}, element: null, name: 'Root' };
|
const treeRoot = { children: {}, element: null, name: 'Root' };
|
||||||
const nodeMap = {'Root': treeRoot};
|
const nodeMap = {'Root': treeRoot};
|
||||||
elements.forEach(el => {
|
elements.forEach(el => {
|
||||||
const path = el.path;
|
const path = el.path;
|
||||||
if (!path) return;
|
if (!path) return;
|
||||||
const parts = path.split('.');
|
const parts = path.split('.');
|
||||||
let currentPath = '';
|
let currentPath = '';
|
||||||
let parentNode = treeRoot;
|
let parentNode = treeRoot;
|
||||||
for(let i = 0; i < parts.length; i++) {
|
for(let i = 0; i < parts.length; i++) {
|
||||||
const part = parts[i];
|
const part = parts[i];
|
||||||
const currentPartName = part.includes('[') ? part.substring(0, part.indexOf('[')) : part;
|
const currentPartName = part.includes('[') ? part.substring(0, part.indexOf('[')) : part;
|
||||||
currentPath = i === 0 ? currentPartName : `${currentPath}.${part}`;
|
currentPath = i === 0 ? currentPartName : `${currentPath}.${part}`;
|
||||||
if (!nodeMap[currentPath]) {
|
if (!nodeMap[currentPath]) {
|
||||||
const newNode = { children: {}, element: null, name: part, path: currentPath };
|
const newNode = { children: {}, element: null, name: part, path: currentPath };
|
||||||
parentNode.children[part] = newNode;
|
parentNode.children[part] = newNode;
|
||||||
nodeMap[currentPath] = newNode;
|
nodeMap[currentPath] = newNode;
|
||||||
}
|
}
|
||||||
if (i === parts.length - 1) {
|
if (i === parts.length - 1) {
|
||||||
nodeMap[currentPath].element = el;
|
nodeMap[currentPath].element = el;
|
||||||
}
|
}
|
||||||
parentNode = nodeMap[currentPath];
|
parentNode = nodeMap[currentPath];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return Object.values(treeRoot.children);
|
return Object.values(treeRoot.children);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderNodeAsLi(node, mustSupportPathsSet, level = 0) {
|
function renderNodeAsLi(node, mustSupportPathsSet, level = 0) {
|
||||||
if (!node || !node.element) return '';
|
if (!node || !node.element) return '';
|
||||||
const el = node.element;
|
const el = node.element;
|
||||||
const path = el.path || 'N/A';
|
const path = el.path || 'N/A';
|
||||||
const min = el.min !== undefined ? el.min : '';
|
const min = el.min !== undefined ? el.min : '';
|
||||||
const max = el.max || '';
|
const max = el.max || '';
|
||||||
const short = el.short || '';
|
const short = el.short || '';
|
||||||
const definition = el.definition || '';
|
const definition = el.definition || '';
|
||||||
const isMustSupport = mustSupportPathsSet.has(path);
|
const isMustSupport = mustSupportPathsSet.has(path);
|
||||||
const mustSupportDisplay = isMustSupport ? '<i class="bi bi-check-circle-fill text-warning"></i>' : '';
|
const mustSupportDisplay = isMustSupport ? '<i class="bi bi-check-circle-fill text-warning"></i>' : '';
|
||||||
const hasChildren = Object.keys(node.children).length > 0;
|
const hasChildren = Object.keys(node.children).length > 0;
|
||||||
const collapseId = `collapse-${path.replace(/[\.\:]/g, '-')}`;
|
const collapseId = `collapse-${path.replace(/[\.\:]/g, '-')}`;
|
||||||
const padding = level * 20;
|
const padding = level * 20;
|
||||||
const pathStyle = `padding-left: ${padding}px; white-space: nowrap;`;
|
const pathStyle = `padding-left: ${padding}px; white-space: nowrap;`;
|
||||||
let typeString = 'N/A';
|
let typeString = 'N/A';
|
||||||
if (el.type && el.type.length > 0) {
|
if (el.type && el.type.length > 0) {
|
||||||
typeString = el.type.map(t => {
|
typeString = el.type.map(t => {
|
||||||
let s = t.code || '';
|
let s = t.code || '';
|
||||||
if (t.targetProfile && t.targetProfile.length > 0) {
|
if (t.targetProfile && t.targetProfile.length > 0) {
|
||||||
const targetTypes = t.targetProfile.map(p => p.split('/').pop());
|
const targetTypes = t.targetProfile.map(p => p.split('/').pop());
|
||||||
s += `(<span class="text-muted">${targetTypes.join('|')}</span>)`;
|
s += `(<span class="text-muted">${targetTypes.join('|')}</span>)`;
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}).join(' | ');
|
}).join(' | ');
|
||||||
}
|
}
|
||||||
const liClass = isMustSupport ? 'list-group-item py-1 px-2 list-group-item-warning' : 'list-group-item py-1 px-2';
|
const liClass = isMustSupport ? 'list-group-item py-1 px-2 list-group-item-warning' : 'list-group-item py-1 px-2';
|
||||||
let childrenHtml = '';
|
let childrenHtml = '';
|
||||||
if (hasChildren) {
|
if (hasChildren) {
|
||||||
childrenHtml += `<ul class="collapse list-group list-group-flush" id="${collapseId}">`;
|
childrenHtml += `<ul class="collapse list-group list-group-flush" id="${collapseId}">`;
|
||||||
Object.values(node.children).sort((a,b) => a.name.localeCompare(b.name)).forEach(childNode => {
|
Object.values(node.children).sort((a,b) => a.name.localeCompare(b.name)).forEach(childNode => {
|
||||||
childrenHtml += renderNodeAsLi(childNode, mustSupportPathsSet, level + 1);
|
childrenHtml += renderNodeAsLi(childNode, mustSupportPathsSet, level + 1);
|
||||||
});
|
});
|
||||||
childrenHtml += `</ul>`;
|
childrenHtml += `</ul>`;
|
||||||
}
|
}
|
||||||
let itemHtml = `<li class="${liClass}">`;
|
let itemHtml = `<li class="${liClass}">`;
|
||||||
itemHtml += `<div class="row gx-2 align-items-center ${hasChildren ? 'collapse-toggle' : ''}" ${hasChildren ? `data-bs-toggle="collapse" href="#${collapseId}" role="button" aria-expanded="false" aria-controls="${collapseId}"` : ''}>`;
|
itemHtml += `<div class="row gx-2 align-items-center ${hasChildren ? 'collapse-toggle' : ''}" ${hasChildren ? `data-bs-toggle="collapse" href="#${collapseId}" role="button" aria-expanded="false" aria-controls="${collapseId}"` : ''}>`;
|
||||||
itemHtml += `<div class="col-lg-4 col-md-3 text-truncate" style="${pathStyle}"><span style="display: inline-block; width: 1.2em; text-align: center;">`;
|
itemHtml += `<div class="col-lg-4 col-md-3 text-truncate" style="${pathStyle}"><span style="display: inline-block; width: 1.2em; text-align: center;">`;
|
||||||
if (hasChildren) {
|
if (hasChildren) {
|
||||||
itemHtml += `<i class="bi bi-chevron-right small toggle-icon"></i>`;
|
itemHtml += `<i class="bi bi-chevron-right small toggle-icon"></i>`;
|
||||||
}
|
}
|
||||||
itemHtml += `</span><code class="fw-bold ms-1">${node.name}</code></div>`;
|
itemHtml += `</span><code class="fw-bold ms-1">${node.name}</code></div>`;
|
||||||
itemHtml += `<div class="col-lg-1 col-md-1 text-center text-muted small"><code>${min}..${max}</code></div>`;
|
itemHtml += `<div class="col-lg-1 col-md-1 text-center text-muted small"><code>${min}..${max}</code></div>`;
|
||||||
itemHtml += `<div class="col-lg-3 col-md-3 text-truncate small">${typeString}</div>`;
|
itemHtml += `<div class="col-lg-3 col-md-3 text-truncate small">${typeString}</div>`;
|
||||||
itemHtml += `<div class="col-lg-1 col-md-1 text-center">${mustSupportDisplay}</div>`;
|
itemHtml += `<div class="col-lg-1 col-md-1 text-center">${mustSupportDisplay}</div>`;
|
||||||
let descriptionTooltipAttrs = '';
|
let descriptionTooltipAttrs = '';
|
||||||
if (definition) {
|
if (definition) {
|
||||||
const escapedDefinition = definition.replace(/"/g, '"').replace(/'/g, ''');
|
const escapedDefinition = definition.replace(/"/g, '"').replace(/'/g, ''');
|
||||||
descriptionTooltipAttrs = `data-bs-toggle="tooltip" data-bs-placement="top" title="${escapedDefinition}"`;
|
descriptionTooltipAttrs = `data-bs-toggle="tooltip" data-bs-placement="top" title="${escapedDefinition}"`;
|
||||||
}
|
}
|
||||||
itemHtml += `<div class="col-lg-3 col-md-4 text-muted text-truncate small" ${descriptionTooltipAttrs}>${short}</div>`;
|
itemHtml += `<div class="col-lg-3 col-md-4 text-muted text-truncate small" ${descriptionTooltipAttrs}>${short}</div>`;
|
||||||
itemHtml += `</div>`;
|
itemHtml += `</div>`;
|
||||||
itemHtml += childrenHtml;
|
itemHtml += childrenHtml;
|
||||||
itemHtml += `</li>`;
|
itemHtml += `</li>`;
|
||||||
return itemHtml;
|
return itemHtml;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderStructureTree(elements, mustSupportPaths) {
|
function renderStructureTree(elements, mustSupportPaths) {
|
||||||
if (!elements || elements.length === 0) {
|
if (!elements || elements.length === 0) {
|
||||||
structureDisplay.innerHTML = '<p class="text-muted"><em>No elements found.</em></p>';
|
structureDisplay.innerHTML = '<p class="text-muted"><em>No elements found.</em></p>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const mustSupportPathsSet = new Set(mustSupportPaths || []);
|
const mustSupportPathsSet = new Set(mustSupportPaths || []);
|
||||||
console.log("Rendering tree. MS Set:", mustSupportPathsSet);
|
console.log("Rendering tree. MS Set:", mustSupportPathsSet);
|
||||||
const treeData = buildTreeData(elements);
|
const treeData = buildTreeData(elements);
|
||||||
let html = '<p><small><span class="badge bg-warning text-dark border me-1">MS</span> = Must Support (Row Highlighted)</small></p>';
|
let html = '<p><small><span class="badge bg-warning text-dark border me-1">MS</span> = Must Support (Row Highlighted)</small></p>';
|
||||||
html += `<div class="row gx-2 small fw-bold border-bottom mb-1 d-none d-md-flex"><div class="col-lg-4 col-md-3">Path</div><div class="col-lg-1 col-md-1 text-center">Card.</div><div class="col-lg-3 col-md-3">Type(s)</div><div class="col-lg-1 col-md-1 text-center">MS</div><div class="col-lg-3 col-md-4">Description</div></div>`;
|
html += `<div class="row gx-2 small fw-bold border-bottom mb-1 d-none d-md-flex"><div class="col-lg-4 col-md-3">Path</div><div class="col-lg-1 col-md-1 text-center">Card.</div><div class="col-lg-3 col-md-3">Type(s)</div><div class="col-lg-1 col-md-1 text-center">MS</div><div class="col-lg-3 col-md-4">Description</div></div>`;
|
||||||
html += '<ul class="list-group list-group-flush">';
|
html += '<ul class="list-group list-group-flush">';
|
||||||
treeData.sort((a,b) => a.name.localeCompare(b.name)).forEach(rootNode => {
|
treeData.sort((a,b) => a.name.localeCompare(b.name)).forEach(rootNode => {
|
||||||
html += renderNodeAsLi(rootNode, mustSupportPathsSet, 0);
|
html += renderNodeAsLi(rootNode, mustSupportPathsSet, 0);
|
||||||
});
|
});
|
||||||
html += '</ul>';
|
html += '</ul>';
|
||||||
structureDisplay.innerHTML = html;
|
structureDisplay.innerHTML = html;
|
||||||
var tooltipTriggerList = [].slice.call(structureDisplay.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
var tooltipTriggerList = [].slice.call(structureDisplay.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||||||
tooltipTriggerList.forEach(function (tooltipTriggerEl) {
|
tooltipTriggerList.forEach(function (tooltipTriggerEl) {
|
||||||
new bootstrap.Tooltip(tooltipTriggerEl);
|
new bootstrap.Tooltip(tooltipTriggerEl);
|
||||||
});
|
});
|
||||||
structureDisplay.querySelectorAll('.collapse').forEach(collapseEl => {
|
structureDisplay.querySelectorAll('.collapse').forEach(collapseEl => {
|
||||||
collapseEl.addEventListener('show.bs.collapse', event => {
|
collapseEl.addEventListener('show.bs.collapse', event => {
|
||||||
event.target.previousElementSibling.querySelector('.toggle-icon')?.classList.replace('bi-chevron-right', 'bi-chevron-down');
|
event.target.previousElementSibling.querySelector('.toggle-icon')?.classList.replace('bi-chevron-right', 'bi-chevron-down');
|
||||||
});
|
});
|
||||||
collapseEl.addEventListener('hide.bs.collapse', event => {
|
collapseEl.addEventListener('hide.bs.collapse', event => {
|
||||||
event.target.previousElementSibling.querySelector('.toggle-icon')?.classList.replace('bi-chevron-down', 'bi-chevron-right');
|
event.target.previousElementSibling.querySelector('.toggle-icon')?.classList.replace('bi-chevron-down', 'bi-chevron-right');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||||||
tooltipList.map(function (tooltipTriggerEl) { return new bootstrap.Tooltip(tooltipTriggerEl) });
|
tooltipList.map(function (tooltipTriggerEl) { return new bootstrap.Tooltip(tooltipTriggerEl) });
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock scripts %}
|
{% endblock scripts %}
|
||||||
{% endblock content %} {# Main content block END #}
|
{% endblock content %} {# Main content block END #}
|
@ -1 +0,0 @@
|
|||||||
Single-database configuration for Flask.
|
|
@ -1,50 +0,0 @@
|
|||||||
# A generic, single database configuration.
|
|
||||||
|
|
||||||
[alembic]
|
|
||||||
# template used to generate migration files
|
|
||||||
# file_template = %%(rev)s_%%(slug)s
|
|
||||||
|
|
||||||
# set to 'true' to run the environment during
|
|
||||||
# the 'revision' command, regardless of autogenerate
|
|
||||||
# revision_environment = false
|
|
||||||
|
|
||||||
|
|
||||||
# Logging configuration
|
|
||||||
[loggers]
|
|
||||||
keys = root,sqlalchemy,alembic,flask_migrate
|
|
||||||
|
|
||||||
[handlers]
|
|
||||||
keys = console
|
|
||||||
|
|
||||||
[formatters]
|
|
||||||
keys = generic
|
|
||||||
|
|
||||||
[logger_root]
|
|
||||||
level = WARN
|
|
||||||
handlers = console
|
|
||||||
qualname =
|
|
||||||
|
|
||||||
[logger_sqlalchemy]
|
|
||||||
level = WARN
|
|
||||||
handlers =
|
|
||||||
qualname = sqlalchemy.engine
|
|
||||||
|
|
||||||
[logger_alembic]
|
|
||||||
level = INFO
|
|
||||||
handlers =
|
|
||||||
qualname = alembic
|
|
||||||
|
|
||||||
[logger_flask_migrate]
|
|
||||||
level = INFO
|
|
||||||
handlers =
|
|
||||||
qualname = flask_migrate
|
|
||||||
|
|
||||||
[handler_console]
|
|
||||||
class = StreamHandler
|
|
||||||
args = (sys.stderr,)
|
|
||||||
level = NOTSET
|
|
||||||
formatter = generic
|
|
||||||
|
|
||||||
[formatter_generic]
|
|
||||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
|
||||||
datefmt = %H:%M:%S
|
|
@ -1,113 +0,0 @@
|
|||||||
import logging
|
|
||||||
from logging.config import fileConfig
|
|
||||||
|
|
||||||
from flask import current_app
|
|
||||||
|
|
||||||
from alembic import context
|
|
||||||
|
|
||||||
# this is the Alembic Config object, which provides
|
|
||||||
# access to the values within the .ini file in use.
|
|
||||||
config = context.config
|
|
||||||
|
|
||||||
# Interpret the config file for Python logging.
|
|
||||||
# This line sets up loggers basically.
|
|
||||||
fileConfig(config.config_file_name)
|
|
||||||
logger = logging.getLogger('alembic.env')
|
|
||||||
|
|
||||||
|
|
||||||
def get_engine():
|
|
||||||
try:
|
|
||||||
# this works with Flask-SQLAlchemy<3 and Alchemical
|
|
||||||
return current_app.extensions['migrate'].db.get_engine()
|
|
||||||
except (TypeError, AttributeError):
|
|
||||||
# this works with Flask-SQLAlchemy>=3
|
|
||||||
return current_app.extensions['migrate'].db.engine
|
|
||||||
|
|
||||||
|
|
||||||
def get_engine_url():
|
|
||||||
try:
|
|
||||||
return get_engine().url.render_as_string(hide_password=False).replace(
|
|
||||||
'%', '%%')
|
|
||||||
except AttributeError:
|
|
||||||
return str(get_engine().url).replace('%', '%%')
|
|
||||||
|
|
||||||
|
|
||||||
# add your model's MetaData object here
|
|
||||||
# for 'autogenerate' support
|
|
||||||
# from myapp import mymodel
|
|
||||||
# target_metadata = mymodel.Base.metadata
|
|
||||||
config.set_main_option('sqlalchemy.url', get_engine_url())
|
|
||||||
target_db = current_app.extensions['migrate'].db
|
|
||||||
|
|
||||||
# other values from the config, defined by the needs of env.py,
|
|
||||||
# can be acquired:
|
|
||||||
# my_important_option = config.get_main_option("my_important_option")
|
|
||||||
# ... etc.
|
|
||||||
|
|
||||||
|
|
||||||
def get_metadata():
|
|
||||||
if hasattr(target_db, 'metadatas'):
|
|
||||||
return target_db.metadatas[None]
|
|
||||||
return target_db.metadata
|
|
||||||
|
|
||||||
|
|
||||||
def run_migrations_offline():
|
|
||||||
"""Run migrations in 'offline' mode.
|
|
||||||
|
|
||||||
This configures the context with just a URL
|
|
||||||
and not an Engine, though an Engine is acceptable
|
|
||||||
here as well. By skipping the Engine creation
|
|
||||||
we don't even need a DBAPI to be available.
|
|
||||||
|
|
||||||
Calls to context.execute() here emit the given string to the
|
|
||||||
script output.
|
|
||||||
|
|
||||||
"""
|
|
||||||
url = config.get_main_option("sqlalchemy.url")
|
|
||||||
context.configure(
|
|
||||||
url=url, target_metadata=get_metadata(), literal_binds=True
|
|
||||||
)
|
|
||||||
|
|
||||||
with context.begin_transaction():
|
|
||||||
context.run_migrations()
|
|
||||||
|
|
||||||
|
|
||||||
def run_migrations_online():
|
|
||||||
"""Run migrations in 'online' mode.
|
|
||||||
|
|
||||||
In this scenario we need to create an Engine
|
|
||||||
and associate a connection with the context.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# this callback is used to prevent an auto-migration from being generated
|
|
||||||
# when there are no changes to the schema
|
|
||||||
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
|
|
||||||
def process_revision_directives(context, revision, directives):
|
|
||||||
if getattr(config.cmd_opts, 'autogenerate', False):
|
|
||||||
script = directives[0]
|
|
||||||
if script.upgrade_ops.is_empty():
|
|
||||||
directives[:] = []
|
|
||||||
logger.info('No changes in schema detected.')
|
|
||||||
|
|
||||||
conf_args = current_app.extensions['migrate'].configure_args
|
|
||||||
if conf_args.get("process_revision_directives") is None:
|
|
||||||
conf_args["process_revision_directives"] = process_revision_directives
|
|
||||||
|
|
||||||
connectable = get_engine()
|
|
||||||
|
|
||||||
with connectable.connect() as connection:
|
|
||||||
context.configure(
|
|
||||||
connection=connection,
|
|
||||||
target_metadata=get_metadata(),
|
|
||||||
**conf_args
|
|
||||||
)
|
|
||||||
|
|
||||||
with context.begin_transaction():
|
|
||||||
context.run_migrations()
|
|
||||||
|
|
||||||
|
|
||||||
if context.is_offline_mode():
|
|
||||||
run_migrations_offline()
|
|
||||||
else:
|
|
||||||
run_migrations_online()
|
|
@ -1,24 +0,0 @@
|
|||||||
"""${message}
|
|
||||||
|
|
||||||
Revision ID: ${up_revision}
|
|
||||||
Revises: ${down_revision | comma,n}
|
|
||||||
Create Date: ${create_date}
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
${imports if imports else ""}
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = ${repr(up_revision)}
|
|
||||||
down_revision = ${repr(down_revision)}
|
|
||||||
branch_labels = ${repr(branch_labels)}
|
|
||||||
depends_on = ${repr(depends_on)}
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
${upgrades if upgrades else "pass"}
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
${downgrades if downgrades else "pass"}
|
|
@ -1,32 +0,0 @@
|
|||||||
"""Add must_support_elements_json to ProcessedIg
|
|
||||||
|
|
||||||
Revision ID: 5e6021b572ee
|
|
||||||
Revises: 8809253da459
|
|
||||||
Create Date: 2025-04-08 11:41:38.532125
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '5e6021b572ee'
|
|
||||||
down_revision = '8809253da459'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('processed_ig', schema=None) as batch_op:
|
|
||||||
batch_op.add_column(sa.Column('must_support_elements_json', sa.Text(), nullable=True))
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('processed_ig', schema=None) as batch_op:
|
|
||||||
batch_op.drop_column('must_support_elements_json')
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,80 +0,0 @@
|
|||||||
"""Initial migration with User, ModuleRegistry, ProcessedIg
|
|
||||||
|
|
||||||
Revision ID: 7d0cdff4c7ad
|
|
||||||
Revises:
|
|
||||||
Create Date: 2025-04-08 08:35:33.204706
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '7d0cdff4c7ad'
|
|
||||||
down_revision = None
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.create_table('module_registry',
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('module_id', sa.String(length=100), nullable=False),
|
|
||||||
sa.Column('is_enabled', sa.Boolean(), nullable=False),
|
|
||||||
sa.Column('display_name', sa.String(length=100), nullable=True),
|
|
||||||
sa.Column('description', sa.Text(), nullable=True),
|
|
||||||
sa.Column('version', sa.String(length=30), nullable=True),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
with op.batch_alter_table('module_registry', schema=None) as batch_op:
|
|
||||||
batch_op.create_index(batch_op.f('ix_module_registry_module_id'), ['module_id'], unique=True)
|
|
||||||
|
|
||||||
op.create_table('processed_ig',
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('package_name', sa.String(length=150), nullable=False),
|
|
||||||
sa.Column('package_version', sa.String(length=50), nullable=False),
|
|
||||||
sa.Column('processed_at', sa.DateTime(), nullable=False),
|
|
||||||
sa.Column('status', sa.String(length=50), nullable=True),
|
|
||||||
sa.Column('resource_types_json', sa.Text(), nullable=True),
|
|
||||||
sa.PrimaryKeyConstraint('id'),
|
|
||||||
sa.UniqueConstraint('package_name', 'package_version', name='uq_processed_ig_name_version')
|
|
||||||
)
|
|
||||||
with op.batch_alter_table('processed_ig', schema=None) as batch_op:
|
|
||||||
batch_op.create_index(batch_op.f('ix_processed_ig_package_name'), ['package_name'], unique=False)
|
|
||||||
batch_op.create_index(batch_op.f('ix_processed_ig_package_version'), ['package_version'], unique=False)
|
|
||||||
|
|
||||||
op.create_table('user',
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('username', sa.String(length=64), nullable=True),
|
|
||||||
sa.Column('email', sa.String(length=120), nullable=True),
|
|
||||||
sa.Column('password_hash', sa.String(length=256), nullable=True),
|
|
||||||
sa.Column('role', sa.String(length=20), nullable=False),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
with op.batch_alter_table('user', schema=None) as batch_op:
|
|
||||||
batch_op.create_index(batch_op.f('ix_user_email'), ['email'], unique=True)
|
|
||||||
batch_op.create_index(batch_op.f('ix_user_role'), ['role'], unique=False)
|
|
||||||
batch_op.create_index(batch_op.f('ix_user_username'), ['username'], unique=True)
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('user', schema=None) as batch_op:
|
|
||||||
batch_op.drop_index(batch_op.f('ix_user_username'))
|
|
||||||
batch_op.drop_index(batch_op.f('ix_user_role'))
|
|
||||||
batch_op.drop_index(batch_op.f('ix_user_email'))
|
|
||||||
|
|
||||||
op.drop_table('user')
|
|
||||||
with op.batch_alter_table('processed_ig', schema=None) as batch_op:
|
|
||||||
batch_op.drop_index(batch_op.f('ix_processed_ig_package_version'))
|
|
||||||
batch_op.drop_index(batch_op.f('ix_processed_ig_package_name'))
|
|
||||||
|
|
||||||
op.drop_table('processed_ig')
|
|
||||||
with op.batch_alter_table('module_registry', schema=None) as batch_op:
|
|
||||||
batch_op.drop_index(batch_op.f('ix_module_registry_module_id'))
|
|
||||||
|
|
||||||
op.drop_table('module_registry')
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,34 +0,0 @@
|
|||||||
"""Rename resource_types_json to resource_types_info_json in ProcessedIg
|
|
||||||
|
|
||||||
Revision ID: 8809253da459
|
|
||||||
Revises: 7d0cdff4c7ad
|
|
||||||
Create Date: 2025-04-08 09:20:55.669990
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '8809253da459'
|
|
||||||
down_revision = '7d0cdff4c7ad'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('processed_ig', schema=None) as batch_op:
|
|
||||||
batch_op.add_column(sa.Column('resource_types_info_json', sa.Text(), nullable=True))
|
|
||||||
batch_op.drop_column('resource_types_json')
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('processed_ig', schema=None) as batch_op:
|
|
||||||
batch_op.add_column(sa.Column('resource_types_json', sa.TEXT(), nullable=True))
|
|
||||||
batch_op.drop_column('resource_types_info_json')
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,32 +0,0 @@
|
|||||||
"""Add examples_json to ProcessedIg
|
|
||||||
|
|
||||||
Revision ID: d8f620f74fbe
|
|
||||||
Revises: 5e6021b572ee
|
|
||||||
Create Date: 2025-04-08 12:45:21.475913
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = 'd8f620f74fbe'
|
|
||||||
down_revision = '5e6021b572ee'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('processed_ig', schema=None) as batch_op:
|
|
||||||
batch_op.add_column(sa.Column('examples_json', sa.Text(), nullable=True))
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('processed_ig', schema=None) as batch_op:
|
|
||||||
batch_op.drop_column('examples_json')
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
Loading…
x
Reference in New Issue
Block a user