FHIRFLARE-IG-Toolkit/templates/validate_sample.html
Sudo-JHare c2e805b29a incremental update
Updated, cliboard copy buttons on examples, added new UI elements, updated and added more validation for samples, and switched validator to ajax
2025-04-14 16:21:12 +10:00

282 lines
13 KiB
HTML

<!-- templates/validate_sample.html -->
{% extends "base.html" %}
{% block content %}
<div class="px-4 py-5 my-5 text-center">
<img class="d-block mx-auto mb-4" src="{{ url_for('static', filename='FHIRFLARE.png') }}" alt="FHIRFLARE IG Toolkit" width="192" height="192">
<h1 class="display-5 fw-bold text-body-emphasis">Validate FHIR Sample</h1>
<div class="col-lg-6 mx-auto">
<p class="lead mb-4">
Validate a FHIR resource or bundle against a selected Implementation Guide. (ALPHA - TEST Fhir pathing is complex and the logic is WIP, please report anomalies you find in Github issues alongside your sample json REMOVE PHI)
</p>
<div class="d-grid gap-2 d-sm-flex justify-content-sm-center">
<a href="{{ url_for('index') }}" class="btn btn-primary btn-lg px-4 gap-3">Back to Home</a>
<a href="{{ url_for('view_igs') }}" class="btn btn-outline-secondary btn-lg px-4">Manage FHIR Packages</a>
</div>
</div>
</div>
<div class="container mt-4">
<div class="card">
<div class="card-header">Validation Form</div>
<div class="card-body">
<form id="validationForm">
{{ form.hidden_tag() }}
<div class="mb-3">
<small class="form-text text-muted">
Select a package from the list below or enter a new one (e.g., <code>hl7.fhir.us.core</code>).
</small>
<div class="mt-2">
<select class="form-select" id="packageSelect">
<option value="">-- Select a Package --</option>
{% if packages %}
{% for pkg in packages %}
<option value="{{ pkg.name }}#{{ pkg.version }}">{{ pkg.name }}#{{ pkg.version }}</option>
{% endfor %}
{% else %}
<option value="" disabled>No packages available</option>
{% endif %}
</select>
</div>
<label for="{{ form.package_name.id }}" class="form-label">Package Name</label>
{{ form.package_name(class="form-control", id=form.package_name.id) }}
{% for error in form.package_name.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
</div>
<div class="mb-3">
<label for="{{ form.version.id }}" class="form-label">Package Version</label>
{{ form.version(class="form-control", id=form.version.id) }}
{% for error in form.version.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
</div>
<div class="mb-3">
<label for="{{ form.include_dependencies.id }}" class="form-label">Include Dependencies</label>
<div class="form-check">
{{ form.include_dependencies(class="form-check-input") }}
{{ form.include_dependencies.label(class="form-check-label") }}
</div>
</div>
<div class="mb-3">
<label for="{{ form.mode.id }}" class="form-label">Validation Mode</label>
{{ form.mode(class="form-select") }}
</div>
<div class="mb-3">
<label for="{{ form.sample_input.id }}" class="form-label">Sample FHIR Resource/Bundle (JSON)</label>
{{ form.sample_input(class="form-control", rows=10, placeholder="Paste your FHIR JSON here...") }}
{% for error in form.sample_input.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
</div>
<button type="button" class="btn btn-primary" id="validateButton">Validate</button>
</form>
</div>
</div>
<div class="card mt-4" id="validationResult" style="display: none;">
<div class="card-header">Validation Report</div>
<div class="card-body" id="validationContent">
<!-- Results will be populated dynamically -->
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const packageSelect = document.getElementById('packageSelect');
const packageNameInput = document.getElementById('{{ form.package_name.id }}');
const versionInput = document.getElementById('{{ form.version.id }}');
const validateButton = document.getElementById('validateButton');
const sampleInput = document.getElementById('{{ form.sample_input.id }}');
const validationResult = document.getElementById('validationResult');
const validationContent = document.getElementById('validationContent');
// Debug DOM elements
console.log('Package Select:', packageSelect ? 'Found' : 'Missing');
console.log('Package Name Input:', packageNameInput ? 'Found' : 'Missing');
console.log('Version Input:', versionInput ? 'Found' : 'Missing');
// Update package_name and version fields from dropdown
function updatePackageFields(select) {
const value = select.value;
console.log('Selected package:', value);
if (!packageNameInput || !versionInput) {
console.error('Input fields not found');
return;
}
if (value) {
const [name, version] = value.split('#');
if (name && version) {
packageNameInput.value = name;
versionInput.value = version;
console.log('Updated fields:', { packageName: name, version });
} else {
console.warn('Invalid package format:', value);
packageNameInput.value = '';
versionInput.value = '';
}
} else {
packageNameInput.value = '';
versionInput.value = '';
console.log('Cleared fields: no package selected');
}
}
// Initialize dropdown based on form values
if (packageNameInput && versionInput && packageNameInput.value && versionInput.value) {
const currentValue = `${packageNameInput.value}#${versionInput.value}`;
if (packageSelect.querySelector(`option[value="${currentValue}"]`)) {
packageSelect.value = currentValue;
console.log('Initialized dropdown with:', currentValue);
}
}
// Debug dropdown options
console.log('Dropdown options:', Array.from(packageSelect.options).map(opt => opt.value));
// Attach event listener for package selection
if (packageSelect) {
packageSelect.addEventListener('change', function() {
updatePackageFields(this);
});
}
// Client-side JSON validation preview
if (sampleInput) {
sampleInput.addEventListener('input', function() {
try {
if (this.value.trim()) {
JSON.parse(this.value);
this.classList.remove('is-invalid');
this.classList.add('is-valid');
} else {
this.classList.remove('is-valid', 'is-invalid');
}
} catch (e) {
this.classList.remove('is-valid');
this.classList.add('is-invalid');
}
});
}
// Validate button handler
if (validateButton) {
validateButton.addEventListener('click', function() {
const packageName = packageNameInput.value;
const version = versionInput.value;
const sampleData = sampleInput.value.trim();
if (!packageName || !version) {
validationResult.style.display = 'block';
validationContent.innerHTML = '<div class="alert alert-danger">Please select a package and version.</div>';
return;
}
if (!sampleData) {
validationResult.style.display = 'block';
validationContent.innerHTML = '<div class="alert alert-danger">Please provide FHIR sample JSON.</div>';
return;
}
try {
JSON.parse(sampleData);
} catch (e) {
validationResult.style.display = 'block';
validationContent.innerHTML = '<div class="alert alert-danger">Invalid JSON: ' + e.message + '</div>';
return;
}
validationResult.style.display = 'block';
validationContent.innerHTML = '<div class="text-center py-3"><div class="spinner-border text-primary" role="status"><span class="visually-hidden">Validating...</span></div></div>';
console.log('Sending request to /api/validate-sample with:', {
package_name: packageName,
version: version,
sample_data_length: sampleData.length
});
fetch('/api/validate-sample', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ form.csrf_token._value() }}'
},
body: JSON.stringify({
package_name: packageName,
version: version,
sample_data: sampleData
})
})
.then(response => {
console.log('Server response status:', response.status, response.statusText);
if (!response.ok) {
return response.text().then(text => {
console.error('Server response text:', text.substring(0, 200) + (text.length > 200 ? '...' : ''));
throw new Error(`HTTP error ${response.status}: ${text.substring(0, 100)}...`);
});
}
return response.json();
})
.then(data => {
console.log('Validation response:', data);
validationContent.innerHTML = '';
let hasContent = false;
if (data.errors && data.errors.length > 0) {
hasContent = true;
const errorDiv = document.createElement('div');
errorDiv.className = 'alert alert-danger';
errorDiv.innerHTML = '<h5>Errors</h5><ul>' + data.errors.map(e => `<li>${e}</li>`).join('') + '</ul>';
validationContent.appendChild(errorDiv);
}
if (data.warnings && data.warnings.length > 0) {
hasContent = true;
const warningDiv = document.createElement('div');
warningDiv.className = 'alert alert-warning';
warningDiv.innerHTML = '<h5>Warnings</h5><ul>' + data.warnings.map(w => `<li>${w}</li>`).join('') + '</ul>';
validationContent.appendChild(warningDiv);
}
if (data.results) {
hasContent = true;
const resultsDiv = document.createElement('div');
resultsDiv.innerHTML = '<h5>Detailed Results</h5>';
for (const [resourceId, result] of Object.entries(data.results)) {
resultsDiv.innerHTML += `
<h6>${resourceId}</h6>
<p><strong>Valid:</strong> ${result.valid ? 'Yes' : 'No'}</p>
`;
if (result.errors && result.errors.length > 0) {
resultsDiv.innerHTML += '<ul class="text-danger">' +
result.errors.map(e => `<li>${e}</li>`).join('') + '</ul>';
}
if (result.warnings && result.warnings.length > 0) {
resultsDiv.innerHTML += '<ul class="text-warning">' +
result.warnings.map(w => `<li>${w}</li>`).join('') + '</ul>';
}
}
validationContent.appendChild(resultsDiv);
}
const summaryDiv = document.createElement('div');
summaryDiv.className = 'alert alert-info';
summaryDiv.innerHTML = `<h5>Summary</h5><p>Valid: ${data.valid ? 'Yes' : 'No'}</p>`;
validationContent.appendChild(summaryDiv);
if (!hasContent && data.valid) {
validationContent.innerHTML = '<div class="alert alert-success">Validation passed with no errors or warnings.</div>';
}
})
.catch(error => {
console.error('Validation error:', error);
validationContent.innerHTML = '<div class="alert alert-danger">Error during validation: ' + error.message + '</div>';
});
});
}
});
</script>
{% endblock %}