mirror of
https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit.git
synced 2025-06-14 16:19:59 +00:00
Hapi Config Page. retrieve bundles from server split bundles to resources. new upload page, and Package Registry CACHE
234 lines
10 KiB
HTML
234 lines
10 KiB
HTML
{% extends "base.html" %}
|
|
{% block content %}
|
|
<div class="container mt-4">
|
|
<h1 class="mb-4">HAPI FHIR Configuration Manager</h1>
|
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
{% if messages %}
|
|
{% for category, message in messages %}
|
|
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
|
|
{{ message }}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% endwith %}
|
|
<div id="loading" class="text-center my-4">
|
|
<p>Loading configuration...</p>
|
|
<div class="spinner-border" role="status">
|
|
<span class="visually-hidden">Loading...</span>
|
|
</div>
|
|
</div>
|
|
<div id="error" class="alert alert-danger d-none" role="alert"></div>
|
|
<div id="config-form" class="d-none">
|
|
<div class="mb-3">
|
|
<button id="save-btn" class="btn btn-primary me-2">Save Configuration</button>
|
|
<button id="restart-btn" class="btn btn-success">Restart HAPI Server</button>
|
|
<span id="restart-status" class="ms-3 text-success d-none"></span>
|
|
</div>
|
|
<div id="config-sections"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Configuration descriptions from HAPI FHIR JPA documentation
|
|
const CONFIG_DESCRIPTIONS = {
|
|
'hapi.fhir.narrative_enabled': 'Enables or disables the generation of FHIR resource narratives (human-readable summaries). Set to false to reduce processing overhead. Default: false.',
|
|
'hapi.fhir.mdm_enabled': 'Enables Master Data Management (MDM) features for linking and matching resources (e.g., Patient records). Requires mdm_rules_json_location to be set. Default: false.',
|
|
'hapi.fhir.mdm_rules_json_location': 'Path to the JSON file defining MDM rules for resource matching. Example: "mdm-rules.json".',
|
|
'hapi.fhir.cors.allow_Credentials': 'Allows credentials (e.g., cookies, authorization headers) in CORS requests. Set to true to support authenticated cross-origin requests. Default: true.',
|
|
'hapi.fhir.cors.allowed_origin': 'List of allowed origins for CORS requests. Use ["*"] to allow all origins or specify domains (e.g., ["https://example.com"]). Default: ["*"].',
|
|
'hapi.fhir.tester.home.name': 'Name of the local tester configuration for HAPI FHIR testing UI. Example: "Local Tester".',
|
|
'hapi.fhir.tester.home.server_address': 'URL of the local FHIR server for testing. Example: "http://localhost:8080/fhir".',
|
|
'hapi.fhir.tester.home.refuse_to_fetch_third_party_urls': 'Prevents the tester from fetching third-party URLs. Set to true for security. Default: false.',
|
|
'hapi.fhir.tester.home.fhir_version': 'FHIR version for the local tester (e.g., "R4", "R5"). Default: R4.',
|
|
'hapi.fhir.tester.global.name': 'Name of the global tester configuration for HAPI FHIR testing UI. Example: "Global Tester".',
|
|
'hapi.fhir.tester.global.server_address': 'URL of the global FHIR server for testing. Example: "http://hapi.fhir.org/baseR4".',
|
|
'hapi.fhir.tester.global.refuse_to_fetch_third_party_urls': 'Prevents the global tester from fetching third-party URLs. Default: false.',
|
|
'hapi.fhir.tester.global.fhir_version': 'FHIR version for the global tester (e.g., "R4"). Default: R4.',
|
|
'hapi.fhir.cr.enabled': 'Enables Clinical Reasoning (CR) module for operations like evaluate-measure. Default: false.',
|
|
'hapi.fhir.cr.caregaps.reporter': 'Reporter mode for care gaps in the CR module. Default: "default".',
|
|
'hapi.fhir.cr.caregaps.section_author': 'Author identifier for care gap sections. Default: "default".',
|
|
'hapi.fhir.cr.cql.use_embedded_libraries': 'Uses embedded CQL libraries for Clinical Quality Language processing. Default: true.',
|
|
'hapi.fhir.inline_resource_storage_below_size': 'Maximum size (in bytes) for storing FHIR resources inline in the database. Default: 4000.',
|
|
'hapi.fhir.advanced_lucene_indexing': 'Enables experimental advanced Lucene/Elasticsearch indexing for enhanced search capabilities. Default: false. Note: May not support all FHIR features like _total=accurate.',
|
|
'hapi.fhir.enable_index_of_type': 'Enables indexing of resource types for faster searches. Default: true.',
|
|
// Add more descriptions as needed based on your application.yaml
|
|
};
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const configForm = document.getElementById('config-form');
|
|
const loading = document.getElementById('loading');
|
|
const errorDiv = document.getElementById('error');
|
|
const configSections = document.getElementById('config-sections');
|
|
const saveBtn = document.getElementById('save-btn');
|
|
const restartBtn = document.getElementById('restart-btn');
|
|
const restartStatus = document.getElementById('restart-status');
|
|
let configData = {};
|
|
|
|
// Fetch initial configuration
|
|
fetch('/api/config')
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
configData = data;
|
|
renderConfigSections();
|
|
loading.classList.add('d-none');
|
|
configForm.classList.remove('d-none');
|
|
// Initialize Bootstrap tooltips
|
|
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(el => {
|
|
new bootstrap.Tooltip(el);
|
|
});
|
|
})
|
|
.catch(err => {
|
|
showError('Failed to load configuration');
|
|
loading.classList.add('d-none');
|
|
});
|
|
|
|
// Render configuration sections
|
|
function renderConfigSections() {
|
|
configSections.innerHTML = '';
|
|
Object.entries(configData).forEach(([section, values]) => {
|
|
const sectionDiv = document.createElement('div');
|
|
sectionDiv.className = 'card mb-3';
|
|
sectionDiv.innerHTML = `
|
|
<div class="card-header">
|
|
<h3 class="card-title">${section.charAt(0).toUpperCase() + section.slice(1)}</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
${renderInputs(values, [section])}
|
|
</div>
|
|
`;
|
|
configSections.appendChild(sectionDiv);
|
|
});
|
|
}
|
|
|
|
// Render inputs recursively with descriptions
|
|
function renderInputs(obj, path) {
|
|
let html = '';
|
|
Object.entries(obj).forEach(([key, value]) => {
|
|
const inputId = path.concat(key).join('-');
|
|
const configPath = path.concat(key).join('.');
|
|
const description = CONFIG_DESCRIPTIONS[configPath] || 'No description available.';
|
|
if (typeof value === 'boolean') {
|
|
html += `
|
|
<div class="form-check mb-2">
|
|
<input class="form-check-input" type="checkbox" id="${inputId}" ${value ? 'checked' : ''}>
|
|
<label class="form-check-label" for="${inputId}">
|
|
${key}
|
|
<i class="fas fa-info-circle ms-1" data-bs-toggle="tooltip" data-bs-placement="top" title="${description}"></i>
|
|
</label>
|
|
</div>
|
|
`;
|
|
} else if (typeof value === 'string' || typeof value === 'number') {
|
|
html += `
|
|
<div class="mb-2">
|
|
<label for="${inputId}" class="form-label">
|
|
${key}
|
|
<i class="fas fa-info-circle ms-1" data-bs-toggle="tooltip" data-bs-placement="top" title="${description}"></i>
|
|
</label>
|
|
<input type="text" class="form-control" id="${inputId}" value="${value}">
|
|
</div>
|
|
`;
|
|
} else if (Array.isArray(value)) {
|
|
html += `
|
|
<div class="mb-2">
|
|
<label for="${inputId}" class="form-label">
|
|
${key} (comma-separated)
|
|
<i class="fas fa-info-circle ms-1" data-bs-toggle="tooltip" data-bs-placement="top" title="${description}"></i>
|
|
</label>
|
|
<input type="text" class="form-control" id="${inputId}" value="${value.join(',')}">
|
|
</div>
|
|
`;
|
|
} else if (typeof value === 'object' && value !== null) {
|
|
html += `
|
|
<div class="border-start ps-3 ms-3">
|
|
<h4>${key}</h4>
|
|
${renderInputs(value, path.concat(key))}
|
|
</div>
|
|
`;
|
|
}
|
|
});
|
|
return html;
|
|
}
|
|
|
|
// Update config data on input change
|
|
configSections.addEventListener('change', (e) => {
|
|
const path = e.target.id.split('-');
|
|
let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
|
|
if (e.target.type === 'text' && e.target.value.includes(',')) {
|
|
value = e.target.value.split(',').map(v => v.trim());
|
|
}
|
|
updateConfig(path, value);
|
|
});
|
|
|
|
// Update config object
|
|
function updateConfig(path, value) {
|
|
let current = configData;
|
|
for (let i = 0; i < path.length - 1; i++) {
|
|
current = current[path[i]];
|
|
}
|
|
current[path[path.length - 1]] = value;
|
|
}
|
|
|
|
// Save configuration
|
|
saveBtn.addEventListener('click', () => {
|
|
fetch('/api/config', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(configData)
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
showError(data.error);
|
|
} else {
|
|
showSuccess('Configuration saved successfully');
|
|
}
|
|
})
|
|
.catch(() => showError('Failed to save configuration'));
|
|
});
|
|
|
|
// Restart HAPI server
|
|
restartBtn.addEventListener('click', () => {
|
|
restartStatus.textContent = 'Restarting...';
|
|
restartStatus.classList.remove('d-none');
|
|
fetch('/api/restart-tomcat', { method: 'POST' })
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
showError(data.error);
|
|
restartStatus.classList.add('d-none');
|
|
} else {
|
|
restartStatus.textContent = 'HAPI server restarted successfully';
|
|
setTimeout(() => {
|
|
restartStatus.classList.add('d-none');
|
|
restartStatus.textContent = '';
|
|
}, 3000);
|
|
}
|
|
})
|
|
.catch(() => {
|
|
showError('Failed to restart HAPI server');
|
|
restartStatus.classList.add('d-none');
|
|
});
|
|
});
|
|
|
|
// Show error message
|
|
function showError(message) {
|
|
errorDiv.textContent = message;
|
|
errorDiv.classList.remove('d-none');
|
|
setTimeout(() => errorDiv.classList.add('d-none'), 5000);
|
|
}
|
|
|
|
// Show success message
|
|
function showSuccess(message) {
|
|
const alert = document.createElement('div');
|
|
alert.className = 'alert alert-success alert-dismissible fade show';
|
|
alert.innerHTML = `
|
|
${message}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
`;
|
|
document.querySelector('.container').insertBefore(alert, configForm);
|
|
setTimeout(() => alert.remove(), 5000);
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %} |