FHIRFLARE-IG-Toolkit/templates/cp_push_igs.html

369 lines
18 KiB
HTML

{% extends "base.html" %}
{% block content %}
<div class="container-fluid">
<div class="row">
<!-- Left Column: List of Downloaded IGs -->
<div class="col-md-6">
<h2>Downloaded IGs</h2>
{% if packages %}
<table class="table table-striped">
<thead>
<tr>
<th>Package Name</th>
<th>Version</th>
<!--------------------------------------------------------------------------------------------------------------if you unhide buttons unhide this--------------------------------------------
<th>Actions</th>
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------->
</tr>
</thead>
<tbody>
{% for pkg in packages %}
{% set name = pkg.name %}
{% set version = pkg.version %}
{% set filename = pkg.filename %}
{% set processed = (name, version) in processed_ids %}
{% set duplicate_group = duplicate_groups.get(name) %}
<tr {% if duplicate_group %}class="{{ group_colors[name] }}"{% endif %}>
<td>{{ name }}</td>
<td>{{ version }}</td>
<!--------------------------------------------------------------------------------------------------------------Dont need the buttons here--------------------------------------------------
<td>
{% if not processed %}
<form method="POST" action="{{ url_for('process_ig') }}" style="display:inline;">
<input type="hidden" name="filename" value="{{ filename }}">
<button type="submit" class="btn btn-primary btn-sm">Process</button>
</form>
{% endif %}
<form method="POST" action="{{ url_for('delete_ig') }}" style="display:inline;" onsubmit="return confirm('Are you sure you want to delete {{ name }}#{{ version }}?');">
<input type="hidden" name="filename" value="{{ filename }}">
<button type="submit" class="btn btn-danger btn-sm">Delete</button>
</form>
</td>
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------->
</tr>
{% endfor %}
</tbody>
</table>
{% if duplicate_groups %}
<div class="alert alert-warning">
<strong>Duplicate Packages Detected:</strong>
<ul>
{% for name, versions in duplicate_groups.items() %}
<li>{{ name }} (Versions: {{ versions | join(', ') }})</li>
{% endfor %}
</ul>
<p>Please resolve duplicates by deleting older versions to avoid conflicts.</p>
</div>
{% endif %}
{% else %}
<p>No packages downloaded yet. Use the "Import IG" tab to download packages.</p>
{% endif %}
</div>
<!-- Right Column: Push IGs Form -->
<div class="col-md-6">
<h2>Push IGs to FHIR Server</h2>
<form id="pushIgForm" method="POST">
<div class="mb-3">
<label for="packageSelect" class="form-label">Select Package to Push</label>
<select class="form-select" id="packageSelect" name="package_id" required>
<option value="" disabled selected>Select a package...</option>
{% for pkg in packages %}
<option value="{{ pkg.name }}#{{ pkg.version }}">{{ pkg.name }}#{{ pkg.version }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label for="dependencyMode" class="form-label">Dependency Mode Used During Import</label>
<input type="text" class="form-control" id="dependencyMode" readonly>
</div>
<div class="mb-3">
<label for="fhirServerUrl" class="form-label">FHIR Server URL</label>
<input type="url" class="form-control" id="fhirServerUrl" name="fhir_server_url" placeholder="e.g., http://hapi.fhir.org/baseR4" required>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="includeDependencies" name="include_dependencies" checked>
<label class="form-check-label" for="includeDependencies">Include Dependencies</label>
</div>
<button type="submit" class="btn btn-primary" id="pushButton">Push to FHIR Server</button>
</form>
<!-- Live Console -->
<div class="mt-3">
<h4>Live Console</h4>
<div id="liveConsole" class="border p-3 bg-dark text-light" style="height: 300px; overflow-y: auto; font-family: monospace; font-size: 14px;">
<div>Console output will appear here...</div>
</div>
</div>
<!-- Response Area -->
<div id="pushResponse" class="mt-3">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}"> {{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const packageSelect = document.getElementById('packageSelect');
const dependencyModeField = document.getElementById('dependencyMode');
// Update dependency mode when package selection changes
packageSelect.addEventListener('change', function() {
const packageId = this.value;
if (packageId) {
const [packageName, version] = packageId.split('#');
fetch(`/get-package-metadata?package_name=${packageName}&version=${version}`)
.then(response => response.json())
.then(data => {
if (data.dependency_mode) {
dependencyModeField.value = data.dependency_mode;
} else {
dependencyModeField.value = 'Unknown';
}
})
.catch(error => {
console.error('Error fetching metadata:', error);
dependencyModeField.value = 'Error';
});
} else {
dependencyModeField.value = '';
}
});
// Trigger change event on page load if a package is pre-selected
if (packageSelect.value) {
packageSelect.dispatchEvent(new Event('change'));
}
});
document.getElementById('pushIgForm').addEventListener('submit', async function(event) {
event.preventDefault();
const form = event.target;
const pushButton = document.getElementById('pushButton');
const formData = new FormData(form);
const packageId = formData.get('package_id');
const [packageName, version] = packageId.split('#');
const fhirServerUrl = formData.get('fhir_server_url');
const includeDependencies = formData.get('include_dependencies') === 'on';
// Disable the button to prevent multiple submissions
pushButton.disabled = true;
pushButton.textContent = 'Pushing...';
// Clear the console and response area
const liveConsole = document.getElementById('liveConsole');
const responseDiv = document.getElementById('pushResponse');
liveConsole.innerHTML = '<div>Starting push operation...</div>';
responseDiv.innerHTML = '';
try {
const response = await fetch('/api/push-ig', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/x-ndjson'
},
body: JSON.stringify({
package_name: packageName,
version: version,
fhir_server_url: fhirServerUrl,
include_dependencies: includeDependencies,
api_key: '{{ api_key | safe }}' // Use the API key passed from the server
})
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'Failed to push package');
}
// Stream the response
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
// Decode the chunk and append to buffer
buffer += decoder.decode(value, { stream: true });
// Process each line (newline-separated JSON)
const lines = buffer.split('\n');
buffer = lines.pop(); // Keep the last (possibly incomplete) line in the buffer
for (const line of lines) {
if (!line.trim()) continue; // Skip empty lines
try {
const data = JSON.parse(line);
const timestamp = new Date().toLocaleTimeString();
let messageClass = '';
let prefix = '';
switch (data.type) {
case 'start':
case 'progress':
messageClass = 'text-info';
prefix = '[INFO]';
break;
case 'success':
messageClass = 'text-success';
prefix = '[SUCCESS]';
break;
case 'error':
messageClass = 'text-danger';
prefix = '[ERROR]';
break;
case 'warning':
messageClass = 'text-warning';
prefix = '[WARNING]';
break;
case 'complete':
// Display the final summary in the response area
if (data.data.status === 'success') {
responseDiv.innerHTML = `
<div class="alert alert-success">
<strong>Success:</strong> ${data.data.message}<br>
<strong>Pushed Packages:</strong> ${data.data.pushed_packages.join(', ')}<br>
<strong>Server Response:</strong> ${data.data.server_response}
</div>
`;
} else if (data.data.status === 'partial') {
responseDiv.innerHTML = `
<div class="alert alert-warning">
<strong>Partial Success:</strong> ${data.data.message}<br>
<strong>Pushed Packages:</strong> ${data.data.pushed_packages.join(', ')}<br>
<strong>Server Response:</strong> ${data.data.server_response}
</div>
`;
} else {
responseDiv.innerHTML = `
<div class="alert alert-danger">
<strong>Error:</strong> ${data.data.message}
</div>
`;
}
// Also log the completion in the console
messageClass = data.data.status === 'success' ? 'text-success' : (data.data.status === 'partial' ? 'text-warning' : 'text-danger');
prefix = data.data.status === 'success' ? '[SUCCESS]' : (data.data.status === 'partial' ? '[PARTIAL]' : '[ERROR]');
break;
default:
messageClass = 'text-light';
prefix = '[INFO]';
}
const messageDiv = document.createElement('div');
messageDiv.className = messageClass;
messageDiv.textContent = `${timestamp} ${prefix} ${data.message || (data.data && data.data.message) || 'Unknown message'}`;
liveConsole.appendChild(messageDiv);
// Auto-scroll to the bottom
liveConsole.scrollTop = liveConsole.scrollHeight;
} catch (parseError) {
console.error('Error parsing streamed message:', parseError, 'Line:', line);
}
}
}
// Process any remaining buffer
if (buffer.trim()) {
try {
const data = JSON.parse(buffer);
const timestamp = new Date().toLocaleTimeString();
let messageClass = '';
let prefix = '';
switch (data.type) {
case 'start':
case 'progress':
messageClass = 'text-info';
prefix = '[INFO]';
break;
case 'success':
messageClass = 'text-success';
prefix = '[SUCCESS]';
break;
case 'error':
messageClass = 'text-danger';
prefix = '[ERROR]';
break;
case 'warning':
messageClass = 'text-warning';
prefix = '[WARNING]';
break;
case 'complete':
if (data.data.status === 'success') {
responseDiv.innerHTML = `
<div class="alert alert-success">
<strong>Success:</strong> ${data.data.message}<br>
<strong>Pushed Packages:</strong> ${data.data.pushed_packages.join(', ')}<br>
<strong>Server Response:</strong> ${data.data.server_response}
</div>
`;
} else if (data.data.status === 'partial') {
responseDiv.innerHTML = `
<div class="alert alert-warning">
<strong>Partial Success:</strong> ${data.data.message}<br>
<strong>Pushed Packages:</strong> ${data.data.pushed_packages.join(', ')}<br>
<strong>Server Response:</strong> ${data.data.server_response}
</div>
`;
} else {
responseDiv.innerHTML = `
<div class="alert alert-danger">
<strong>Error:</strong> ${data.data.message}
</div>
`;
}
messageClass = data.data.status === 'success' ? 'text-success' : (data.data.status === 'partial' ? 'text-warning' : 'text-danger');
prefix = data.data.status === 'success' ? '[SUCCESS]' : (data.data.status === 'partial' ? '[PARTIAL]' : '[ERROR]');
break;
default:
messageClass = 'text-light';
prefix = '[INFO]';
}
const messageDiv = document.createElement('div');
messageDiv.className = messageClass;
messageDiv.textContent = `${timestamp} ${prefix} ${data.message || (data.data && data.data.message) || 'Unknown message'}`;
liveConsole.appendChild(messageDiv);
// Auto-scroll to the bottom
liveConsole.scrollTop = liveConsole.scrollHeight;
} catch (parseError) {
console.error('Error parsing final streamed message:', parseError, 'Buffer:', buffer);
}
}
} catch (error) {
const timestamp = new Date().toLocaleTimeString();
const errorDiv = document.createElement('div');
errorDiv.className = 'text-danger';
errorDiv.textContent = `${timestamp} [ERROR] Failed to push package: ${error.message}`;
liveConsole.appendChild(errorDiv);
liveConsole.scrollTop = liveConsole.scrollHeight;
responseDiv.innerHTML = `
<div class="alert alert-danger">
<strong>Error:</strong> Failed to push package: ${error.message}
</div>
`;
} finally {
// Re-enable the button
pushButton.disabled = false;
pushButton.textContent = 'Push to FHIR Server';
}
});
</script>
{% endblock %}