573 lines
31 KiB
HTML
573 lines
31 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}{% if object %}Edit Configuration - {{ object.key }}{% else %}Add New Configuration{% endif %}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<!-- Breadcrumb -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="page-title-box d-sm-flex align-items-center justify-content-between">
|
|
<h4 class="mb-sm-0">{% if object %}Edit Configuration{% else %}Add New Configuration{% endif %}</h4>
|
|
<div class="page-title-right">
|
|
<ol class="breadcrumb m-0">
|
|
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'core:system_configuration' %}">System Configuration</a></li>
|
|
<li class="breadcrumb-item active">{% if object %}Edit{% else %}Add{% endif %}</li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<!-- Main Form -->
|
|
<div class="col-lg-8">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-{% if object %}edit{% else %}plus{% endif %} me-2"></i>
|
|
Configuration Information
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<form method="post" id="configForm">
|
|
{% csrf_token %}
|
|
|
|
<!-- Basic Information -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-6">
|
|
<div class="form-floating mb-3">
|
|
{{ form.key }}
|
|
<label for="{{ form.key.id_for_label }}">Key *</label>
|
|
{% if form.key.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.key.errors.0 }}</div>
|
|
{% endif %}
|
|
<div class="form-text">Unique identifier for this configuration</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-floating mb-3">
|
|
{{ form.category }}
|
|
<label for="{{ form.category.id_for_label }}">Category *</label>
|
|
{% if form.category.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.category.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="form-floating mb-3">
|
|
{{ form.description }}
|
|
<label for="{{ form.description.id_for_label }}">Description</label>
|
|
{% if form.description.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.description.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Data Type and Value -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-6">
|
|
<div class="form-floating mb-3">
|
|
{{ form.data_type }}
|
|
<label for="{{ form.data_type.id_for_label }}">Data Type *</label>
|
|
{% if form.data_type.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.data_type.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-check form-switch mt-4">
|
|
{{ form.is_active }}
|
|
<label class="form-check-label" for="{{ form.is_active.id_for_label }}">
|
|
Active Configuration
|
|
</label>
|
|
{% if form.is_active.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.is_active.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Dynamic Value Fields -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h6 class="card-title mb-0">Configuration Value</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Text Value -->
|
|
<div id="text-value-field" class="value-field">
|
|
<div class="form-floating mb-3">
|
|
{{ form.value_text }}
|
|
<label for="{{ form.value_text.id_for_label }}">Text Value</label>
|
|
{% if form.value_text.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.value_text.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Integer Value -->
|
|
<div id="integer-value-field" class="value-field" style="display: none;">
|
|
<div class="form-floating mb-3">
|
|
{{ form.value_integer }}
|
|
<label for="{{ form.value_integer.id_for_label }}">Integer Value</label>
|
|
{% if form.value_integer.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.value_integer.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Float Value -->
|
|
<div id="float-value-field" class="value-field" style="display: none;">
|
|
<div class="form-floating mb-3">
|
|
{{ form.value_float }}
|
|
<label for="{{ form.value_float.id_for_label }}">Float Value</label>
|
|
{% if form.value_float.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.value_float.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Boolean Value -->
|
|
<div id="boolean-value-field" class="value-field" style="display: none;">
|
|
<div class="form-check form-switch mb-3">
|
|
{{ form.value_boolean }}
|
|
<label class="form-check-label" for="{{ form.value_boolean.id_for_label }}">
|
|
Boolean Value
|
|
</label>
|
|
{% if form.value_boolean.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.value_boolean.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Date Value -->
|
|
<div id="date-value-field" class="value-field" style="display: none;">
|
|
<div class="form-floating mb-3">
|
|
{{ form.value_date }}
|
|
<label for="{{ form.value_date.id_for_label }}">Date Value</label>
|
|
{% if form.value_date.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.value_date.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- DateTime Value -->
|
|
<div id="datetime-value-field" class="value-field" style="display: none;">
|
|
<div class="form-floating mb-3">
|
|
{{ form.value_datetime }}
|
|
<label for="{{ form.value_datetime.id_for_label }}">DateTime Value</label>
|
|
{% if form.value_datetime.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.value_datetime.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- JSON Value -->
|
|
<div id="json-value-field" class="value-field" style="display: none;">
|
|
<div class="mb-3">
|
|
<label for="{{ form.value_json.id_for_label }}" class="form-label">JSON Value</label>
|
|
{{ form.value_json }}
|
|
{% if form.value_json.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.value_json.errors.0 }}</div>
|
|
{% endif %}
|
|
<div class="form-text">Enter valid JSON format</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="validateJson()">
|
|
<i class="fas fa-check me-1"></i>
|
|
Validate JSON
|
|
</button>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary ms-2" onclick="formatJson()">
|
|
<i class="fas fa-indent me-1"></i>
|
|
Format JSON
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Advanced Options -->
|
|
<div class="card mb-4">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h6 class="card-title mb-0">Advanced Options</h6>
|
|
<button class="btn btn-sm btn-link" type="button" data-bs-toggle="collapse" data-bs-target="#advancedOptions">
|
|
<i class="fas fa-chevron-down"></i>
|
|
</button>
|
|
</div>
|
|
<div class="collapse" id="advancedOptions">
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="form-floating mb-3">
|
|
{{ form.validation_regex }}
|
|
<label for="{{ form.validation_regex.id_for_label }}">Validation Regex</label>
|
|
{% if form.validation_regex.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.validation_regex.errors.0 }}</div>
|
|
{% endif %}
|
|
<div class="form-text">Regular expression for validation</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-floating mb-3">
|
|
{{ form.validation_message }}
|
|
<label for="{{ form.validation_message.id_for_label }}">Validation Message</label>
|
|
{% if form.validation_message.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.validation_message.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="form-floating mb-3">
|
|
{{ form.min_value }}
|
|
<label for="{{ form.min_value.id_for_label }}">Minimum Value</label>
|
|
{% if form.min_value.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.min_value.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-floating mb-3">
|
|
{{ form.max_value }}
|
|
<label for="{{ form.max_value.id_for_label }}">Maximum Value</label>
|
|
{% if form.max_value.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.max_value.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="form-floating mb-3">
|
|
{{ form.allowed_values }}
|
|
<label for="{{ form.allowed_values.id_for_label }}">Allowed Values</label>
|
|
{% if form.allowed_values.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.allowed_values.errors.0 }}</div>
|
|
{% endif %}
|
|
<div class="form-text">Comma-separated list of allowed values</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Form Actions -->
|
|
<div class="d-flex justify-content-between">
|
|
<a href="{% url 'core:system_configuration' %}" class="btn btn-outline-secondary">
|
|
<i class="fas fa-arrow-left me-1"></i>
|
|
Cancel
|
|
</a>
|
|
<div>
|
|
<button type="button" class="btn btn-outline-primary me-2" onclick="testConfiguration()">
|
|
<i class="fas fa-flask me-1"></i>
|
|
Test Configuration
|
|
</button>
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="fas fa-save me-1"></i>
|
|
{% if object %}Update Configuration{% else %}Create Configuration{% endif %}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Help Sidebar -->
|
|
<div class="col-lg-4">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-question-circle me-2"></i>
|
|
Help & Guidelines
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="accordion" id="helpAccordion">
|
|
<!-- Key Naming -->
|
|
<div class="accordion-item">
|
|
<h2 class="accordion-header" id="keyNamingHelp">
|
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#keyNamingContent">
|
|
Key Naming Conventions
|
|
</button>
|
|
</h2>
|
|
<div id="keyNamingContent" class="accordion-collapse collapse" data-bs-parent="#helpAccordion">
|
|
<div class="accordion-body">
|
|
<ul class="list-unstyled">
|
|
<li><i class="fas fa-check-circle text-success me-2"></i> Use uppercase with underscores</li>
|
|
<li><i class="fas fa-check-circle text-success me-2"></i> Group related settings with prefixes</li>
|
|
<li><i class="fas fa-check-circle text-success me-2"></i> Be descriptive but concise</li>
|
|
<li><i class="fas fa-times-circle text-danger me-2"></i> Avoid spaces or special characters</li>
|
|
</ul>
|
|
<div class="alert alert-info alert-sm">
|
|
<small><strong>Examples:</strong><br>
|
|
EMAIL_SMTP_HOST<br>
|
|
PATIENT_PORTAL_ENABLED<br>
|
|
MAX_APPOINTMENT_DAYS</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Data Types -->
|
|
<div class="accordion-item">
|
|
<h2 class="accordion-header" id="dataTypesHelp">
|
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#dataTypesContent">
|
|
Data Types Guide
|
|
</button>
|
|
</h2>
|
|
<div id="dataTypesContent" class="accordion-collapse collapse" data-bs-parent="#helpAccordion">
|
|
<div class="accordion-body">
|
|
<ul class="list-unstyled">
|
|
<li><strong>TEXT:</strong> For strings, names, descriptions</li>
|
|
<li><strong>INTEGER:</strong> For whole numbers</li>
|
|
<li><strong>FLOAT:</strong> For decimal numbers</li>
|
|
<li><strong>BOOLEAN:</strong> For true/false settings</li>
|
|
<li><strong>DATE:</strong> For date values</li>
|
|
<li><strong>DATETIME:</strong> For date and time values</li>
|
|
<li><strong>JSON:</strong> For structured data</li>
|
|
</ul>
|
|
<div class="alert alert-warning alert-sm">
|
|
<small><strong>Note:</strong> Changing data type after creation may cause data loss</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Categories -->
|
|
<div class="accordion-item">
|
|
<h2 class="accordion-header" id="categoriesHelp">
|
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#categoriesContent">
|
|
Common Categories
|
|
</button>
|
|
</h2>
|
|
<div id="categoriesContent" class="accordion-collapse collapse" data-bs-parent="#helpAccordion">
|
|
<div class="accordion-body">
|
|
<ul class="list-unstyled">
|
|
<li><strong>SYSTEM:</strong> Core system settings</li>
|
|
<li><strong>EMAIL:</strong> Email configuration</li>
|
|
<li><strong>SECURITY:</strong> Security settings</li>
|
|
<li><strong>UI:</strong> User interface settings</li>
|
|
<li><strong>BILLING:</strong> Billing and payment settings</li>
|
|
<li><strong>APPOINTMENTS:</strong> Appointment settings</li>
|
|
<li><strong>NOTIFICATIONS:</strong> Notification settings</li>
|
|
<li><strong>INTEGRATION:</strong> External integration settings</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- JSON Format -->
|
|
<div class="accordion-item">
|
|
<h2 class="accordion-header" id="jsonHelp">
|
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#jsonContent">
|
|
JSON Format Help
|
|
</button>
|
|
</h2>
|
|
<div id="jsonContent" class="accordion-collapse collapse" data-bs-parent="#helpAccordion">
|
|
<div class="accordion-body">
|
|
<p class="mb-2">Valid JSON requires:</p>
|
|
<ul class="list-unstyled">
|
|
<li><i class="fas fa-check-circle text-success me-2"></i> Double quotes for keys</li>
|
|
<li><i class="fas fa-check-circle text-success me-2"></i> Commas between items</li>
|
|
<li><i class="fas fa-check-circle text-success me-2"></i> Proper nesting of objects and arrays</li>
|
|
</ul>
|
|
<div class="alert alert-info alert-sm">
|
|
<small><strong>Example:</strong><br>
|
|
{<br>
|
|
"name": "value",<br>
|
|
"items": [1, 2, 3],<br>
|
|
"nested": {<br>
|
|
"key": "value"<br>
|
|
}<br>
|
|
}</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Test Results -->
|
|
<div class="card" id="testResultsCard" style="display: none;">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-flask me-2"></i>
|
|
Test Results
|
|
</h5>
|
|
</div>
|
|
<div class="card-body" id="testResults">
|
|
<!-- Test results will be displayed here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const dataTypeSelect = document.getElementById('{{ form.data_type.id_for_label }}');
|
|
|
|
// Show/hide value fields based on data type
|
|
function toggleValueFields() {
|
|
const dataType = dataTypeSelect.value;
|
|
const valueFields = document.querySelectorAll('.value-field');
|
|
|
|
valueFields.forEach(field => {
|
|
field.style.display = 'none';
|
|
});
|
|
|
|
if (dataType) {
|
|
const fieldMap = {
|
|
'TEXT': 'text-value-field',
|
|
'INTEGER': 'integer-value-field',
|
|
'FLOAT': 'float-value-field',
|
|
'BOOLEAN': 'boolean-value-field',
|
|
'DATE': 'date-value-field',
|
|
'DATETIME': 'datetime-value-field',
|
|
'JSON': 'json-value-field'
|
|
};
|
|
|
|
const fieldId = fieldMap[dataType];
|
|
if (fieldId) {
|
|
document.getElementById(fieldId).style.display = 'block';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize value fields display
|
|
toggleValueFields();
|
|
|
|
// Listen for data type changes
|
|
dataTypeSelect.addEventListener('change', toggleValueFields);
|
|
});
|
|
|
|
function validateJson() {
|
|
const jsonField = document.getElementById('{{ form.value_json.id_for_label }}');
|
|
const jsonValue = jsonField.value.trim();
|
|
|
|
if (!jsonValue) {
|
|
alert('Please enter JSON content to validate');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const parsedJson = JSON.parse(jsonValue);
|
|
alert('JSON is valid!');
|
|
} catch (error) {
|
|
alert('Invalid JSON: ' + error.message);
|
|
}
|
|
}
|
|
|
|
function formatJson() {
|
|
const jsonField = document.getElementById('{{ form.value_json.id_for_label }}');
|
|
const jsonValue = jsonField.value.trim();
|
|
|
|
if (!jsonValue) {
|
|
alert('Please enter JSON content to format');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const parsedJson = JSON.parse(jsonValue);
|
|
jsonField.value = JSON.stringify(parsedJson, null, 2);
|
|
} catch (error) {
|
|
alert('Cannot format invalid JSON: ' + error.message);
|
|
}
|
|
}
|
|
|
|
function testConfiguration() {
|
|
const form = document.getElementById('configForm');
|
|
const formData = new FormData(form);
|
|
|
|
// Show loading state
|
|
const testButton = event.target;
|
|
const originalText = testButton.innerHTML;
|
|
testButton.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Testing...';
|
|
testButton.disabled = true;
|
|
|
|
fetch('{% if object %}{% url "core:system_configuration_update" object.pk %}{% else %}{% url "core:system_configuration_create" %}{% endif %}test/', {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
|
|
},
|
|
body: formData
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
// Show test results
|
|
const testResultsCard = document.getElementById('testResultsCard');
|
|
const testResults = document.getElementById('testResults');
|
|
|
|
if (data.success) {
|
|
testResults.innerHTML = `
|
|
<div class="alert alert-success" role="alert">
|
|
<div class="d-flex">
|
|
<div class="flex-shrink-0">
|
|
<i class="fas fa-check-circle fa-lg"></i>
|
|
</div>
|
|
<div class="flex-grow-1 ms-3">
|
|
<h6 class="alert-heading">Configuration Valid!</h6>
|
|
<p class="mb-0">${data.message}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
} else {
|
|
testResults.innerHTML = `
|
|
<div class="alert alert-danger" role="alert">
|
|
<div class="d-flex">
|
|
<div class="flex-shrink-0">
|
|
<i class="fas fa-times-circle fa-lg"></i>
|
|
</div>
|
|
<div class="flex-grow-1 ms-3">
|
|
<h6 class="alert-heading">Configuration Invalid</h6>
|
|
<p class="mb-0">${data.error}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
testResultsCard.style.display = 'block';
|
|
testResultsCard.scrollIntoView({ behavior: 'smooth' });
|
|
})
|
|
.catch(error => {
|
|
console.error('Test error:', error);
|
|
const testResults = document.getElementById('testResults');
|
|
testResults.innerHTML = `
|
|
<div class="alert alert-danger" role="alert">
|
|
<div class="d-flex">
|
|
<div class="flex-shrink-0">
|
|
<i class="fas fa-times-circle fa-lg"></i>
|
|
</div>
|
|
<div class="flex-grow-1 ms-3">
|
|
<h6 class="alert-heading">Test Error</h6>
|
|
<p class="mb-0">An error occurred while testing the configuration.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
document.getElementById('testResultsCard').style.display = 'block';
|
|
})
|
|
.finally(() => {
|
|
// Restore button state
|
|
testButton.innerHTML = originalText;
|
|
testButton.disabled = false;
|
|
});
|
|
}
|
|
</script>
|
|
{% endblock %}
|
|
|