mirror of
https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit.git
synced 2025-11-05 17:45:14 +00:00
Compare commits
2 Commits
f4c457043a
...
f7063484d9
| Author | SHA1 | Date | |
|---|---|---|---|
| f7063484d9 | |||
| b8014de7c3 |
@ -3,7 +3,9 @@ FROM mcr.microsoft.com/dotnet/sdk:9.0
|
|||||||
|
|
||||||
# Install build dependencies, Node.js 18, and coreutils (for stdbuf)
|
# Install build dependencies, Node.js 18, and coreutils (for stdbuf)
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
python3 python3-pip python3-venv curl coreutils git \
|
python3 python3-pip python3-venv \
|
||||||
|
default-jre-headless \
|
||||||
|
curl coreutils git \
|
||||||
&& curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
&& curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
||||||
&& apt-get install -y --no-install-recommends nodejs \
|
&& apt-get install -y --no-install-recommends nodejs \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
# Base image with Python and Node.js
|
# Base image with Python and Node.js
|
||||||
FROM python:3.9-slim
|
FROM python:3.9-slim
|
||||||
|
|
||||||
# Install Node.js 18 and npm
|
# Install JRE and other dependencies for the validator
|
||||||
|
# We need to install `default-jre-headless` to get a minimal Java Runtime Environment
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
default-jre-headless \
|
||||||
curl coreutils git \
|
curl coreutils git \
|
||||||
&& curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
&& curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
||||||
&& apt-get install -y --no-install-recommends nodejs \
|
&& apt-get install -y --no-install-recommends nodejs \
|
||||||
|
|||||||
17
app.py
17
app.py
@ -3185,14 +3185,23 @@ def ig_configurator():
|
|||||||
@app.route('/download/<path:filename>')
|
@app.route('/download/<path:filename>')
|
||||||
def download_file(filename):
|
def download_file(filename):
|
||||||
"""
|
"""
|
||||||
Serves a file from the temporary directory.
|
Serves a file from the temporary directory created during validation.
|
||||||
This route is necessary to allow the user to download the validator reports.
|
The filename includes the unique temporary directory name to prevent
|
||||||
|
unauthorized file access and to correctly locate the file.
|
||||||
"""
|
"""
|
||||||
# The temporary directory path is hardcoded for security
|
# Use the stored temporary directory path from app.config
|
||||||
temp_dir = tempfile.gettempdir()
|
temp_dir = current_app.config.get('VALIDATION_TEMP_DIR')
|
||||||
|
|
||||||
|
# If no temporary directory is set, or it's a security risk, return a 404.
|
||||||
|
if not temp_dir or not os.path.exists(temp_dir):
|
||||||
|
logger.error(f"Download request failed: Temporary directory not found or expired. Path: {temp_dir}")
|
||||||
|
return jsonify({"error": "File not found or validation session expired."}), 404
|
||||||
|
|
||||||
|
# Construct the full file path.
|
||||||
file_path = os.path.join(temp_dir, filename)
|
file_path = os.path.join(temp_dir, filename)
|
||||||
|
|
||||||
if not os.path.exists(file_path):
|
if not os.path.exists(file_path):
|
||||||
|
logger.error(f"File not found for download: {file_path}")
|
||||||
return jsonify({"error": "File not found."}), 404
|
return jsonify({"error": "File not found."}), 404
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -10,7 +10,6 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
<div class="card shadow-sm mb-4">
|
<div class="card shadow-sm mb-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@ -28,7 +27,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form id="retrieveForm" method="POST">
|
<form id="retrieveForm" method="POST">
|
||||||
{{ form.hidden_tag() }}
|
{{ form.csrf_token(id='retrieve_csrf_token') }}
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label fw-bold">FHIR Server</label>
|
<label class="form-label fw-bold">FHIR Server</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@ -39,7 +38,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<small id="fhirServerHelp" class="form-text text-muted">Toggle to use local Fhir Server(/fhir proxy) or enter a custom FHIR server URL.</small>
|
<small id="fhirServerHelp" class="form-text text-muted">Toggle to use local Fhir Server(/fhir proxy) or enter a custom FHIR server URL.</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Authentication Section (Shown for Custom URL) #}
|
{# Authentication Section (Shown for Custom URL) #}
|
||||||
<div class="mb-3" id="authSection" style="display: none;">
|
<div class="mb-3" id="authSection" style="display: none;">
|
||||||
<label class="form-label fw-bold">Authentication</label>
|
<label class="form-label fw-bold">Authentication</label>
|
||||||
@ -68,7 +66,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<small class="form-text text-muted">Select authentication method for the custom FHIR server.</small>
|
<small class="form-text text-muted">Select authentication method for the custom FHIR server.</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Checkbox Row #}
|
{# Checkbox Row #}
|
||||||
<div class="row g-3 mb-3 align-items-center">
|
<div class="row g-3 mb-3 align-items-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
@ -78,7 +75,6 @@
|
|||||||
{{ render_field(form.fetch_reference_bundles, id='fetch_reference_bundles_checkbox') }}
|
{{ render_field(form.fetch_reference_bundles, id='fetch_reference_bundles_checkbox') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="button" class="btn btn-primary mb-3" id="fetchMetadata">Fetch Metadata</button>
|
<button type="button" class="btn btn-primary mb-3" id="fetchMetadata">Fetch Metadata</button>
|
||||||
<div class="banner3 mt-3" id="resourceTypes" style="display: none;">
|
<div class="banner3 mt-3" id="resourceTypes" style="display: none;">
|
||||||
<h5>Resource Types</h5>
|
<h5>Resource Types</h5>
|
||||||
@ -102,14 +98,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card shadow-sm">
|
<div class="card shadow-sm">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h4 class="my-0 fw-normal"><i class="bi bi-scissors me-2"></i>Split Bundles</h4>
|
<h4 class="my-0 fw-normal"><i class="bi bi-scissors me-2"></i>Split Bundles</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form id="splitForm" method="POST" enctype="multipart/form-data">
|
<form id="splitForm" method="POST" enctype="multipart/form-data">
|
||||||
{{ form.hidden_tag() }}
|
{{ form.csrf_token(id='split_csrf_token') }}
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label fw-bold">Bundle Source</label>
|
<label class="form-label fw-bold">Bundle Source</label>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
@ -141,7 +136,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// --- DOM Element References ---
|
// --- DOM Element References ---
|
||||||
@ -172,7 +166,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const basicAuthInputs = document.getElementById('basicAuthInputs');
|
const basicAuthInputs = document.getElementById('basicAuthInputs');
|
||||||
const usernameInput = document.getElementById('username');
|
const usernameInput = document.getElementById('username');
|
||||||
const passwordInput = document.getElementById('password');
|
const passwordInput = document.getElementById('password');
|
||||||
|
|
||||||
// --- State & Config Variables ---
|
// --- State & Config Variables ---
|
||||||
const appMode = '{{ app_mode | default("standalone") | lower }}';
|
const appMode = '{{ app_mode | default("standalone") | lower }}';
|
||||||
const apiKey = '{{ api_key }}';
|
const apiKey = '{{ api_key }}';
|
||||||
@ -181,11 +174,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
let retrieveZipPath = null;
|
let retrieveZipPath = null;
|
||||||
let splitZipPath = null;
|
let splitZipPath = null;
|
||||||
let fetchedMetadataCache = null;
|
let fetchedMetadataCache = null;
|
||||||
let localHapiBaseUrl = ''; // NEW: To store the fetched URL
|
let localHapiBaseUrl = ''; // To store the fetched URL
|
||||||
|
|
||||||
// --- Helper Functions ---
|
// --- Helper Functions ---
|
||||||
const sanitizeText = (str) => str ? String(str).replace(/</g, "<").replace(/>/g, ">") : "";
|
const sanitizeText = (str) => str ? String(str).replace(/</g, "<").replace(/>/g, ">") : "";
|
||||||
|
|
||||||
// --- UI Update Functions ---
|
// --- UI Update Functions ---
|
||||||
function updateBundleSourceUI() {
|
function updateBundleSourceUI() {
|
||||||
const selectedSource = Array.from(bundleSourceRadios).find(radio => radio.checked)?.value;
|
const selectedSource = Array.from(bundleSourceRadios).find(radio => radio.checked)?.value;
|
||||||
@ -196,13 +187,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
splitBundleZipInput.required = selectedSource === 'upload';
|
splitBundleZipInput.required = selectedSource === 'upload';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchLocalUrlAndSetUI() {
|
async function fetchLocalUrlAndSetUI() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/get-local-server-url');
|
const response = await fetch('/api/get-local-server-url');
|
||||||
if (!response.ok) throw new Error('Failed to fetch local server URL.');
|
if (!response.ok) throw new Error('Failed to fetch local server URL.');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
localHapiBaseUrl = data.url;
|
localHapiBaseUrl = data.url.replace(/\/+$/, ''); // Normalize by removing trailing slashes
|
||||||
console.log(`Local HAPI URL fetched: ${localHapiBaseUrl}`);
|
console.log(`Local HAPI URL fetched: ${localHapiBaseUrl}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -212,10 +202,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
updateServerToggleUI();
|
updateServerToggleUI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateServerToggleUI() {
|
function updateServerToggleUI() {
|
||||||
if (!toggleLabel || !fhirServerUrlInput || !toggleServerButton || !authSection) return;
|
if (!toggleLabel || !fhirServerUrlInput || !toggleServerButton || !authSection) return;
|
||||||
|
|
||||||
if (appMode === 'lite') {
|
if (appMode === 'lite') {
|
||||||
useLocalHapi = false;
|
useLocalHapi = false;
|
||||||
toggleServerButton.disabled = true;
|
toggleServerButton.disabled = true;
|
||||||
@ -243,7 +231,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
updateAuthInputsUI();
|
updateAuthInputsUI();
|
||||||
console.log(`Server toggle UI updated: useLocalHapi=${useLocalHapi}, customUrl=${fhirServerUrlInput.value}`);
|
console.log(`Server toggle UI updated: useLocalHapi=${useLocalHapi}, customUrl=${fhirServerUrlInput.value}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateAuthInputsUI() {
|
function updateAuthInputsUI() {
|
||||||
if (!authTypeSelect || !authInputsGroup || !bearerTokenInput || !basicAuthInputs) return;
|
if (!authTypeSelect || !authInputsGroup || !bearerTokenInput || !basicAuthInputs) return;
|
||||||
const authType = authTypeSelect.value;
|
const authType = authTypeSelect.value;
|
||||||
@ -254,7 +241,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (authType !== 'basic' && usernameInput) usernameInput.value = '';
|
if (authType !== 'basic' && usernameInput) usernameInput.value = '';
|
||||||
if (authType !== 'basic' && passwordInput) passwordInput.value = '';
|
if (authType !== 'basic' && passwordInput) passwordInput.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleFetchReferenceBundles() {
|
function toggleFetchReferenceBundles() {
|
||||||
if (validateReferencesCheckbox && fetchReferenceBundlesGroup) {
|
if (validateReferencesCheckbox && fetchReferenceBundlesGroup) {
|
||||||
fetchReferenceBundlesGroup.style.display = validateReferencesCheckbox.checked ? 'block' : 'none';
|
fetchReferenceBundlesGroup.style.display = validateReferencesCheckbox.checked ? 'block' : 'none';
|
||||||
@ -265,12 +251,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
console.warn("Could not find checkbox elements needed for toggling.");
|
console.warn("Could not find checkbox elements needed for toggling.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Event Listeners ---
|
// --- Event Listeners ---
|
||||||
bundleSourceRadios.forEach(radio => {
|
bundleSourceRadios.forEach(radio => {
|
||||||
radio.addEventListener('change', updateBundleSourceUI);
|
radio.addEventListener('change', updateBundleSourceUI);
|
||||||
});
|
});
|
||||||
|
|
||||||
toggleServerButton.addEventListener('click', () => {
|
toggleServerButton.addEventListener('click', () => {
|
||||||
if (appMode === 'lite') return;
|
if (appMode === 'lite') return;
|
||||||
useLocalHapi = !useLocalHapi;
|
useLocalHapi = !useLocalHapi;
|
||||||
@ -280,24 +264,20 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
fetchedMetadataCache = null;
|
fetchedMetadataCache = null;
|
||||||
console.log(`Server toggled: useLocalHapi=${useLocalHapi}`);
|
console.log(`Server toggled: useLocalHapi=${useLocalHapi}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (authTypeSelect) {
|
if (authTypeSelect) {
|
||||||
authTypeSelect.addEventListener('change', updateAuthInputsUI);
|
authTypeSelect.addEventListener('change', updateAuthInputsUI);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (validateReferencesCheckbox) {
|
if (validateReferencesCheckbox) {
|
||||||
validateReferencesCheckbox.addEventListener('change', toggleFetchReferenceBundles);
|
validateReferencesCheckbox.addEventListener('change', toggleFetchReferenceBundles);
|
||||||
} else {
|
} else {
|
||||||
console.warn("Validate references checkbox not found.");
|
console.warn("Validate references checkbox not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchMetadataButton.addEventListener('click', async () => {
|
fetchMetadataButton.addEventListener('click', async () => {
|
||||||
resourceButtonsContainer.innerHTML = '<span class="text-muted">Fetching...</span>';
|
resourceButtonsContainer.innerHTML = '<span class="text-muted">Fetching...</span>';
|
||||||
resourceTypesDiv.style.display = 'block';
|
resourceTypesDiv.style.display = 'block';
|
||||||
|
|
||||||
let customUrl = null;
|
let customUrl = null;
|
||||||
if (!useLocalHapi) {
|
if (!useLocalHapi) {
|
||||||
customUrl = fhirServerUrlInput.value.trim().replace(/\/+$/, '');
|
customUrl = fhirServerUrlInput.value.trim().replace(/\/+$/, ''); // Normalize custom URL
|
||||||
if (!customUrl) {
|
if (!customUrl) {
|
||||||
fhirServerUrlInput.classList.add('is-invalid');
|
fhirServerUrlInput.classList.add('is-invalid');
|
||||||
alert('Please enter a valid FHIR server URL.');
|
alert('Please enter a valid FHIR server URL.');
|
||||||
@ -312,20 +292,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fhirServerUrlInput.classList.remove('is-invalid');
|
fhirServerUrlInput.classList.remove('is-invalid');
|
||||||
|
|
||||||
fetchMetadataButton.disabled = true;
|
fetchMetadataButton.disabled = true;
|
||||||
fetchMetadataButton.textContent = 'Fetching...';
|
fetchMetadataButton.textContent = 'Fetching...';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// REVISED LOGIC: Build URL and headers based on the toggle state
|
// Build URL and headers based on the toggle state
|
||||||
let fetchUrl;
|
let fetchUrl;
|
||||||
const headers = { 'Accept': 'application/fhir+json' };
|
const headers = { 'Accept': 'application/fhir+json' };
|
||||||
|
|
||||||
if (useLocalHapi) {
|
if (useLocalHapi) {
|
||||||
if (!localHapiBaseUrl) {
|
if (!localHapiBaseUrl) {
|
||||||
throw new Error("Local HAPI URL not available. Please refresh.");
|
throw new Error("Local HAPI URL not available. Please refresh.");
|
||||||
}
|
}
|
||||||
fetchUrl = `${localHapiBaseUrl}/metadata`;
|
fetchUrl = `${localHapiBaseUrl}/metadata`; // localHapiBaseUrl is already normalized
|
||||||
console.log(`Fetching metadata directly from local URL: ${fetchUrl}`);
|
console.log(`Fetching metadata directly from local URL: ${fetchUrl}`);
|
||||||
} else {
|
} else {
|
||||||
fetchUrl = `${customUrl}/metadata`;
|
fetchUrl = `${customUrl}/metadata`;
|
||||||
@ -339,11 +316,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
console.log(`Fetching metadata directly from custom URL: ${fetchUrl}`);
|
console.log(`Fetching metadata directly from custom URL: ${fetchUrl}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Request Headers sent: ${JSON.stringify(headers)}`);
|
console.log(`Request Headers sent: ${JSON.stringify(headers)}`);
|
||||||
|
|
||||||
const response = await fetch(fetchUrl, { method: 'GET', headers: headers });
|
const response = await fetch(fetchUrl, { method: 'GET', headers: headers });
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
let errorText = `Request failed with status ${response.status} ${response.statusText}`;
|
let errorText = `Request failed with status ${response.status} ${response.statusText}`;
|
||||||
try { const errorBody = await response.text(); errorText += ` - Body: ${errorBody.substring(0, 200)}...`; } catch (e) {}
|
try { const errorBody = await response.text(); errorText += ` - Body: ${errorBody.substring(0, 200)}...`; } catch (e) {}
|
||||||
@ -353,7 +327,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log("Metadata received:", JSON.stringify(data, null, 2).substring(0, 500));
|
console.log("Metadata received:", JSON.stringify(data, null, 2).substring(0, 500));
|
||||||
fetchedMetadataCache = data;
|
fetchedMetadataCache = data;
|
||||||
|
|
||||||
const resourceTypes = data.rest?.[0]?.resource?.map(r => r.type)?.filter(Boolean) || [];
|
const resourceTypes = data.rest?.[0]?.resource?.map(r => r.type)?.filter(Boolean) || [];
|
||||||
resourceButtonsContainer.innerHTML = '';
|
resourceButtonsContainer.innerHTML = '';
|
||||||
if (!resourceTypes.length) {
|
if (!resourceTypes.length) {
|
||||||
@ -384,7 +357,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
fetchMetadataButton.textContent = 'Fetch Metadata';
|
fetchMetadataButton.textContent = 'Fetch Metadata';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
retrieveForm.addEventListener('submit', async (e) => {
|
retrieveForm.addEventListener('submit', async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const spinner = retrieveButton.querySelector('.spinner-border');
|
const spinner = retrieveButton.querySelector('.spinner-border');
|
||||||
@ -395,7 +367,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
downloadRetrieveButton.style.display = 'none';
|
downloadRetrieveButton.style.display = 'none';
|
||||||
retrieveZipPath = null;
|
retrieveZipPath = null;
|
||||||
retrieveConsole.innerHTML = `<div>${new Date().toLocaleTimeString()} [INFO] Starting bundle retrieval...</div>`;
|
retrieveConsole.innerHTML = `<div>${new Date().toLocaleTimeString()} [INFO] Starting bundle retrieval...</div>`;
|
||||||
|
|
||||||
const selectedResources = Array.from(resourceButtonsContainer.querySelectorAll('.btn.active')).map(btn => btn.dataset.resource);
|
const selectedResources = Array.from(resourceButtonsContainer.querySelectorAll('.btn.active')).map(btn => btn.dataset.resource);
|
||||||
if (!selectedResources.length) {
|
if (!selectedResources.length) {
|
||||||
alert('Please select at least one resource type.');
|
alert('Please select at least one resource type.');
|
||||||
@ -404,8 +375,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (icon) icon.style.display = 'inline-block';
|
if (icon) icon.style.display = 'inline-block';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const currentFhirServerUrl = useLocalHapi ? localHapiBaseUrl : fhirServerUrlInput.value.trim().replace(/\/+$/, '');
|
||||||
const currentFhirServerUrl = useLocalHapi ? localHapiBaseUrl : fhirServerUrlInput.value.trim();
|
|
||||||
if (!currentFhirServerUrl) {
|
if (!currentFhirServerUrl) {
|
||||||
alert('FHIR Server URL is required.');
|
alert('FHIR Server URL is required.');
|
||||||
fhirServerUrlInput.classList.add('is-invalid');
|
fhirServerUrlInput.classList.add('is-invalid');
|
||||||
@ -414,14 +384,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (icon) icon.style.display = 'inline-block';
|
if (icon) icon.style.display = 'inline-block';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
const csrfTokenInput = retrieveForm.querySelector('input[name="csrf_token"]');
|
const csrfTokenInput = retrieveForm.querySelector('input[name="csrf_token"]');
|
||||||
if (csrfTokenInput) formData.append('csrf_token', csrfTokenInput.value);
|
if (csrfTokenInput) formData.append('csrf_token', csrfTokenInput.value);
|
||||||
selectedResources.forEach(res => formData.append('resources', res));
|
selectedResources.forEach(res => formData.append('resources', res));
|
||||||
|
|
||||||
formData.append('fhir_server_url', currentFhirServerUrl);
|
formData.append('fhir_server_url', currentFhirServerUrl);
|
||||||
|
|
||||||
if (validateReferencesCheckbox) {
|
if (validateReferencesCheckbox) {
|
||||||
formData.append('validate_references', validateReferencesCheckbox.checked ? 'true' : 'false');
|
formData.append('validate_references', validateReferencesCheckbox.checked ? 'true' : 'false');
|
||||||
}
|
}
|
||||||
@ -434,7 +401,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
} else {
|
} else {
|
||||||
formData.append('fetch_reference_bundles', 'false');
|
formData.append('fetch_reference_bundles', 'false');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!useLocalHapi && authTypeSelect) {
|
if (!useLocalHapi && authTypeSelect) {
|
||||||
const authType = authTypeSelect.value;
|
const authType = authTypeSelect.value;
|
||||||
formData.append('auth_type', authType);
|
formData.append('auth_type', authType);
|
||||||
@ -459,24 +425,20 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
formData.append('password', passwordInput.value);
|
formData.append('password', passwordInput.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
'Accept': 'application/x-ndjson',
|
'Accept': 'application/x-ndjson',
|
||||||
'X-CSRFToken': csrfTokenInput ? csrfTokenInput.value : '',
|
'X-CSRFToken': csrfTokenInput ? csrfTokenInput.value : '',
|
||||||
'X-API-Key': apiKey
|
'X-API-Key': apiKey
|
||||||
};
|
};
|
||||||
console.log(`Submitting retrieve request. Server: ${currentFhirServerUrl}, ValidateRefs: ${formData.get('validate_references')}, FetchRefBundles: ${formData.get('fetch_reference_bundles')}, AuthType: ${formData.get('auth_type')}`);
|
console.log(`Submitting retrieve request. Server: ${currentFhirServerUrl}, ValidateRefs: ${formData.get('validate_references')}, FetchRefBundles: ${formData.get('fetch_reference_bundles')}, AuthType: ${formData.get('auth_type')}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/retrieve-bundles', { method: 'POST', headers: headers, body: formData });
|
const response = await fetch('/api/retrieve-bundles', { method: 'POST', headers: headers, body: formData });
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({ message: 'Failed to parse error response.' }));
|
const errorData = await response.json().catch(() => ({ message: 'Failed to parse error response.' }));
|
||||||
throw new Error(`HTTP ${response.status}: ${errorData.message || 'Unknown error during retrieval'}`);
|
throw new Error(`HTTP ${response.status}: ${errorData.message || 'Unknown error during retrieval'}`);
|
||||||
}
|
}
|
||||||
retrieveZipPath = response.headers.get('X-Zip-Path');
|
retrieveZipPath = response.headers.get('X-Zip-Path');
|
||||||
console.log(`Received X-Zip-Path: ${retrieveZipPath}`);
|
console.log(`Received X-Zip-Path: ${retrieveZipPath}`);
|
||||||
|
|
||||||
const reader = response.body.getReader();
|
const reader = response.body.getReader();
|
||||||
const decoder = new TextDecoder();
|
const decoder = new TextDecoder();
|
||||||
let buffer = '';
|
let buffer = '';
|
||||||
@ -498,13 +460,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
else if (data.type === 'success') { prefix = '[SUCCESS]'; messageClass = 'text-success'; }
|
else if (data.type === 'success') { prefix = '[SUCCESS]'; messageClass = 'text-success'; }
|
||||||
else if (data.type === 'progress') { prefix = '[PROGRESS]'; messageClass = 'text-light'; }
|
else if (data.type === 'progress') { prefix = '[PROGRESS]'; messageClass = 'text-light'; }
|
||||||
else if (data.type === 'complete') { prefix = '[COMPLETE]'; messageClass = 'text-primary'; }
|
else if (data.type === 'complete') { prefix = '[COMPLETE]'; messageClass = 'text-primary'; }
|
||||||
|
|
||||||
const messageDiv = document.createElement('div');
|
const messageDiv = document.createElement('div');
|
||||||
messageDiv.className = messageClass;
|
messageDiv.className = messageClass;
|
||||||
messageDiv.innerHTML = `${timestamp} ${prefix} ${sanitizeText(data.message)}`;
|
messageDiv.innerHTML = `${timestamp} ${prefix} ${sanitizeText(data.message)}`;
|
||||||
retrieveConsole.appendChild(messageDiv);
|
retrieveConsole.appendChild(messageDiv);
|
||||||
retrieveConsole.scrollTop = retrieveConsole.scrollHeight;
|
retrieveConsole.scrollTop = retrieveConsole.scrollHeight;
|
||||||
|
|
||||||
if (data.type === 'complete' && retrieveZipPath) {
|
if (data.type === 'complete' && retrieveZipPath) {
|
||||||
const completeData = data.data || {};
|
const completeData = data.data || {};
|
||||||
const bundlesFound = (completeData.total_initial_bundles > 0) || (completeData.fetched_individual_references > 0) || (completeData.fetched_type_bundles > 0);
|
const bundlesFound = (completeData.total_initial_bundles > 0) || (completeData.fetched_individual_references > 0) || (completeData.fetched_type_bundles > 0);
|
||||||
@ -522,7 +482,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (buffer.trim()) { /* Handle final buffer */ }
|
if (buffer.trim()) { /* Handle final buffer */ }
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Retrieval error:', e);
|
console.error('Retrieval error:', e);
|
||||||
const ts = new Date().toLocaleTimeString();
|
const ts = new Date().toLocaleTimeString();
|
||||||
@ -534,20 +493,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (icon) icon.style.display = 'inline-block';
|
if (icon) icon.style.display = 'inline-block';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
downloadRetrieveButton.addEventListener('click', () => {
|
downloadRetrieveButton.addEventListener('click', () => {
|
||||||
if (retrieveZipPath) {
|
if (retrieveZipPath) {
|
||||||
const filename = retrieveZipPath.split('/').pop() || 'retrieved_bundles.zip';
|
const filename = retrieveZipPath.split('/').pop() || 'retrieved_bundles.zip';
|
||||||
const downloadUrl = `/tmp/${filename}`;
|
const downloadUrl = `/tmp/${filename}`;
|
||||||
console.log(`Attempting to download ZIP from Flask endpoint: ${downloadUrl}`);
|
console.log(`Attempting to download ZIP from Flask endpoint: ${downloadUrl}`);
|
||||||
|
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = downloadUrl;
|
link.href = downloadUrl;
|
||||||
link.download = 'retrieved_bundles.zip';
|
link.download = 'retrieved_bundles.zip';
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const csrfToken = retrieveForm.querySelector('input[name="csrf_token"]')?.value || '';
|
const csrfToken = retrieveForm.querySelector('input[name="csrf_token"]')?.value || '';
|
||||||
fetch('/clear-session', { method: 'POST', headers: { 'X-CSRFToken': csrfToken }})
|
fetch('/clear-session', { method: 'POST', headers: { 'X-CSRFToken': csrfToken }})
|
||||||
@ -565,7 +521,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
alert("Download error: No file path available.");
|
alert("Download error: No file path available.");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
splitForm.addEventListener('submit', async (e) => {
|
splitForm.addEventListener('submit', async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const spinner = splitButton.querySelector('.spinner-border');
|
const spinner = splitButton.querySelector('.spinner-border');
|
||||||
@ -576,13 +531,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
downloadSplitButton.style.display = 'none';
|
downloadSplitButton.style.display = 'none';
|
||||||
splitZipPath = null;
|
splitZipPath = null;
|
||||||
splitConsole.innerHTML = `<div>${new Date().toLocaleTimeString()} [INFO] Starting bundle splitting...</div>`;
|
splitConsole.innerHTML = `<div>${new Date().toLocaleTimeString()} [INFO] Starting bundle splitting...</div>`;
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
const csrfTokenInput = splitForm.querySelector('input[name="csrf_token"]');
|
const csrfTokenInput = splitForm.querySelector('input[name="csrf_token"]');
|
||||||
if (csrfTokenInput) formData.append('csrf_token', csrfTokenInput.value);
|
if (csrfTokenInput) formData.append('csrf_token', csrfTokenInput.value);
|
||||||
|
|
||||||
const bundleSource = Array.from(bundleSourceRadios).find(radio => radio.checked)?.value;
|
const bundleSource = Array.from(bundleSourceRadios).find(radio => radio.checked)?.value;
|
||||||
|
|
||||||
if (bundleSource === 'upload') {
|
if (bundleSource === 'upload') {
|
||||||
if (!splitBundleZipInput || splitBundleZipInput.files.length === 0) {
|
if (!splitBundleZipInput || splitBundleZipInput.files.length === 0) {
|
||||||
alert('Please select a ZIP file to upload for splitting.');
|
alert('Please select a ZIP file to upload for splitting.');
|
||||||
@ -614,16 +566,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (icon) icon.style.display = 'inline-block';
|
if (icon) icon.style.display = 'inline-block';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
'Accept': 'application/x-ndjson',
|
'Accept': 'application/x-ndjson',
|
||||||
'X-CSRFToken': csrfTokenInput ? csrfTokenInput.value : '',
|
'X-CSRFToken': csrfTokenInput ? csrfTokenInput.value : '',
|
||||||
'X-API-Key': apiKey
|
'X-API-Key': apiKey
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/split-bundles', { method: 'POST', headers: headers, body: formData });
|
const response = await fetch('/api/split-bundles', { method: 'POST', headers: headers, body: formData });
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorMsg = await response.text();
|
const errorMsg = await response.text();
|
||||||
let detail = errorMsg;
|
let detail = errorMsg;
|
||||||
@ -632,7 +581,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
splitZipPath = response.headers.get('X-Zip-Path');
|
splitZipPath = response.headers.get('X-Zip-Path');
|
||||||
console.log(`Received X-Zip-Path for split: ${splitZipPath}`);
|
console.log(`Received X-Zip-Path for split: ${splitZipPath}`);
|
||||||
|
|
||||||
const reader = response.body.getReader();
|
const reader = response.body.getReader();
|
||||||
const decoder = new TextDecoder();
|
const decoder = new TextDecoder();
|
||||||
let buffer = '';
|
let buffer = '';
|
||||||
@ -665,7 +613,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (buffer.trim()) { /* Handle final buffer */ }
|
if (buffer.trim()) { /* Handle final buffer */ }
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Splitting error:', e);
|
console.error('Splitting error:', e);
|
||||||
const ts = new Date().toLocaleTimeString();
|
const ts = new Date().toLocaleTimeString();
|
||||||
@ -677,7 +624,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (icon) icon.style.display = 'inline-block';
|
if (icon) icon.style.display = 'inline-block';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
downloadSplitButton.addEventListener('click', () => {
|
downloadSplitButton.addEventListener('click', () => {
|
||||||
if (splitZipPath) {
|
if (splitZipPath) {
|
||||||
const filename = splitZipPath.split('/').pop() || 'split_resources.zip';
|
const filename = splitZipPath.split('/').pop() || 'split_resources.zip';
|
||||||
@ -702,7 +648,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
alert("Download error: No file path available.");
|
alert("Download error: No file path available.");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Initial Setup Calls ---
|
// --- Initial Setup Calls ---
|
||||||
updateBundleSourceUI();
|
updateBundleSourceUI();
|
||||||
fetchLocalUrlAndSetUI();
|
fetchLocalUrlAndSetUI();
|
||||||
|
|||||||
@ -275,6 +275,7 @@
|
|||||||
clearResultsButton.classList.remove('d-none');
|
clearResultsButton.classList.remove('d-none');
|
||||||
|
|
||||||
if (data.file_paths) {
|
if (data.file_paths) {
|
||||||
|
// Fix: Extract just the filename to construct the download URL
|
||||||
const jsonFileName = data.file_paths.json.split('/').pop();
|
const jsonFileName = data.file_paths.json.split('/').pop();
|
||||||
const htmlFileName = data.file_paths.html.split('/').pop();
|
const htmlFileName = data.file_paths.html.split('/').pop();
|
||||||
const downloadDiv = document.createElement('div');
|
const downloadDiv = document.createElement('div');
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user