This commit is contained in:
Joshua Hare 2025-08-12 23:10:52 +10:00
parent a76180fd48
commit 27f9a397b2
2 changed files with 47 additions and 55 deletions

17
app.py
View File

@ -1907,23 +1907,22 @@ def proxy_hapi(subpath):
return jsonify({'resourceType': 'OperationOutcome', 'issue': [{'severity': 'error', 'code': 'exception', 'diagnostics': 'An unexpected error occurred within the FHIR proxy.', 'details': {'text': str(e)}}]}), 500 return jsonify({'resourceType': 'OperationOutcome', 'issue': [{'severity': 'error', 'code': 'exception', 'diagnostics': 'An unexpected error occurred within the FHIR proxy.', 'details': {'text': str(e)}}]}), 500
@app.route('/api/stream-retrieve', methods=['GET']) @app.route('/api/stream-retrieve', methods=['POST'])
def stream_retrieve_bundles(): def stream_retrieve_bundles():
""" """
Handles streaming FHIR bundle retrieval. This endpoint directly calls the service function, Handles streaming FHIR bundle retrieval. This endpoint directly calls the service function,
bypassing the proxy to avoid conflicts. It receives the target URL, bypassing the proxy to avoid conflicts. It receives the target URL,
resources, and other parameters from the front-end via URL query parameters. resources, and other parameters from the front-end via a JSON body.
""" """
def generate_stream(): def generate_stream():
# Push the application context manually for the generator's lifetime
with app.app_context(): with app.app_context():
# Extract parameters from query string # Extract parameters from JSON body
fhir_server_url = request.args.get('fhir_server_url') data = request.json
resources = request.args.getlist('resources') fhir_server_url = data.get('fhir_server_url')
validate_references = request.args.get('validate_references', 'false').lower() == 'true' resources = data.get('resources')
fetch_reference_bundles = request.args.get('fetch_reference_bundles', 'false').lower() == 'true' validate_references = data.get('validate_references', False)
fetch_reference_bundles = data.get('fetch_reference_bundles', False)
# Extract authentication headers
auth_token = request.headers.get('Authorization') auth_token = request.headers.get('Authorization')
auth_type = 'bearer' if auth_token and auth_token.lower().startswith('bearer') else 'basic' if auth_token and auth_token.lower().startswith('basic') else 'none' auth_type = 'bearer' if auth_token and auth_token.lower().startswith('bearer') else 'basic' if auth_token and auth_token.lower().startswith('basic') else 'none'

View File

@ -301,10 +301,10 @@ document.addEventListener('DOMContentLoaded', () => {
fetchMetadataButton.textContent = 'Fetching...'; fetchMetadataButton.textContent = 'Fetching...';
try { try {
const fetchUrl = '/fhir/metadata'; // Updated metadata fetch logic to handle both local and custom URLs
const fetchUrl = useLocalHapi ? '/fhir/metadata' : `${customUrl}/metadata`;
const headers = { 'Accept': 'application/fhir+json' }; const headers = { 'Accept': 'application/fhir+json' };
if (!useLocalHapi && customUrl) { if (!useLocalHapi) {
headers['X-Target-FHIR-Server'] = customUrl;
if (authTypeSelect && authTypeSelect.value !== 'none') { if (authTypeSelect && authTypeSelect.value !== 'none') {
const authType = authTypeSelect.value; const authType = authTypeSelect.value;
if (authType === 'bearer' && bearerTokenInput && bearerTokenInput.value) { if (authType === 'bearer' && bearerTokenInput && bearerTokenInput.value) {
@ -314,12 +314,10 @@ document.addEventListener('DOMContentLoaded', () => {
headers['Authorization'] = `Basic ${credentials}`; headers['Authorization'] = `Basic ${credentials}`;
} }
} }
console.log(`Fetching metadata via proxy with X-Target-FHIR-Server: ${customUrl}`); console.log(`Fetching metadata directly from: ${customUrl}`);
} else { } else {
console.log("Fetching metadata via proxy for local HAPI server"); console.log("Fetching metadata from local HAPI server via proxy");
} }
console.log(`Proxy Fetch URL: ${fetchUrl}`);
console.log(`Request Headers sent TO PROXY: ${JSON.stringify(headers)}`);
const response = await fetch(fetchUrl, { method: 'GET', headers: headers }); const response = await fetch(fetchUrl, { method: 'GET', headers: headers });
@ -383,17 +381,8 @@ document.addEventListener('DOMContentLoaded', () => {
if (icon) icon.style.display = 'inline-block'; if (icon) icon.style.display = 'inline-block';
return; return;
} }
const currentFhirServerUrl = useLocalHapi ? null : fhirServerUrlInput.value.trim().replace(/\/+$/, ''); const currentFhirServerUrl = useLocalHapi ? null : fhirServerUrlInput.value.trim().replace(/\/+$/, '');
if (!useLocalHapi && !currentFhirServerUrl) {
alert('Custom FHIR Server URL is required.');
fhirServerUrlInput.classList.add('is-invalid');
retrieveButton.disabled = false;
if (spinner) spinner.style.display = 'none';
if (icon) icon.style.display = 'inline-block';
return;
}
const authType = authTypeSelect?.value; const authType = authTypeSelect?.value;
const authHeader = (authType === 'bearer' && bearerTokenInput?.value) ? `Bearer ${bearerTokenInput.value}` const authHeader = (authType === 'bearer' && bearerTokenInput?.value) ? `Bearer ${bearerTokenInput.value}`
: (authType === 'basic' && usernameInput?.value && passwordInput?.value) ? `Basic ${btoa(`${usernameInput.value}:${passwordInput.value}`)}` : (authType === 'basic' && usernameInput?.value && passwordInput?.value) ? `Basic ${btoa(`${usernameInput.value}:${passwordInput.value}`)}`
@ -402,16 +391,19 @@ document.addEventListener('DOMContentLoaded', () => {
const validateReferences = validateReferencesCheckbox?.checked ? 'true' : 'false'; const validateReferences = validateReferencesCheckbox?.checked ? 'true' : 'false';
const fetchReferenceBundles = validateReferences === 'true' && fetchReferenceBundlesCheckbox?.checked ? 'true' : 'false'; const fetchReferenceBundles = validateReferences === 'true' && fetchReferenceBundlesCheckbox?.checked ? 'true' : 'false';
// --- Stream the logs directly from the server --- // --- Stream the logs directly from the new backend endpoint ---
const url = new URL('/api/stream-retrieve', window.location.origin); const url = new URL('/api/stream-retrieve', window.location.origin);
if (currentFhirServerUrl) {
url.searchParams.set('proxy-target', currentFhirServerUrl); // Use POST for the new endpoint
} const formData = {
selectedResources.forEach(res => url.searchParams.append('resources', res)); fhir_server_url: currentFhirServerUrl,
url.searchParams.set('validate_references', validateReferences); resources: selectedResources,
url.searchParams.set('fetch_reference_bundles', fetchReferenceBundles); validate_references: validateReferences,
fetch_reference_bundles: fetchReferenceBundles
};
const headers = { const headers = {
'Content-Type': 'application/json',
'Accept': 'application/x-ndjson', 'Accept': 'application/x-ndjson',
}; };
if (authHeader) { if (authHeader) {
@ -425,10 +417,10 @@ document.addEventListener('DOMContentLoaded', () => {
headers['X-API-Key'] = apiKey; headers['X-API-Key'] = apiKey;
} }
console.log(`Submitting retrieve request. URL: ${url.toString()}, Headers: ${JSON.stringify(headers)}`); console.log(`Submitting retrieve request. URL: ${url.toString()}, Headers: ${JSON.stringify(headers)}, Body: ${JSON.stringify(formData)}`);
try { try {
const response = await fetch(url.toString(), { method: 'GET', headers: headers }); const response = await fetch(url.toString(), { method: 'POST', headers: headers, body: JSON.stringify(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.' }));
@ -647,23 +639,24 @@ document.addEventListener('DOMContentLoaded', () => {
link.download = 'split_resources.zip'; link.download = 'split_resources.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 = splitForm.querySelector('input[name="csrf_token"]')?.value || ''; const csrfToken = splitForm.querySelector('input[name="csrf_token"]')?.value || '';
fetch('/clear-session', { method: 'POST', headers: { 'X-CSRFToken': csrfToken }}) fetch('/clear-session', { method: 'POST', headers: { 'X-CSRFToken': csrfToken }})
.then(() => console.log("Session clear requested after split download.")) .then(() => console.log("Session clear requested after split download."))
.catch(err => console.error("Session clear failed:", err)); .catch(err => console.error("Session clear failed:", err));
downloadSplitButton.style.display = 'none'; downloadSplitButton.style.display = 'none';
splitZipPath = null; splitZipPath = null;
}, 500); }, 500);
} else { } else {
console.error("No split ZIP path available for download"); console.error("No split ZIP path available for download");
alert("Download error: No file path available."); alert("Download error: No file path available.");
} }
});
// --- Initial Setup Calls ---
updateBundleSourceUI();
updateServerToggleUI();
toggleFetchReferenceBundles();
}); });
updateBundleSourceUI(); </script>
updateServerToggleUI();
toggleFetchReferenceBundles();
});
</script>
{% endblock %}