FHIRFLARE-IG-Toolkit/templates/config_hapi.html
Sudo-JHare ca8668e5e5 V2.0 release.
Hapi Config Page.
retrieve bundles from server
split bundles to resources.
new upload page, and Package Registry CACHE
2025-05-04 22:55:36 +10:00

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 %}