refactor: 🎨 auto save custom fields
This commit is contained in:
@@ -2906,8 +2906,17 @@ def register_routes(app, limiter=None):
|
||||
new_values = {}
|
||||
errors = []
|
||||
|
||||
# Handle both form data and JSON requests
|
||||
if request.is_json:
|
||||
submitted_data = request.json
|
||||
else:
|
||||
submitted_data = request.form
|
||||
|
||||
for field_key, field_def in field_defs.items():
|
||||
submitted_value = request.form.get(f'custom_field_{field_key}', '')
|
||||
if request.is_json:
|
||||
submitted_value = submitted_data.get(f'custom_field_{field_key}', '')
|
||||
else:
|
||||
submitted_value = submitted_data.get(f'custom_field_{field_key}', '')
|
||||
|
||||
# Parse validation rules
|
||||
validation_rules = field_def.get('validation_rules')
|
||||
@@ -2944,7 +2953,7 @@ def register_routes(app, limiter=None):
|
||||
conn.commit()
|
||||
invalidate_cache_for_subnet(subnet_id)
|
||||
|
||||
if request.headers.get('Content-Type') == 'application/json':
|
||||
if request.is_json or request.headers.get('Content-Type') == 'application/json':
|
||||
return jsonify({'success': True, 'message': 'Custom fields updated successfully'})
|
||||
return redirect(url_for('subnet', subnet_id=subnet_id))
|
||||
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
// Auto-save custom fields on blur (subnet page)
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const customFieldsForm = document.getElementById('custom-fields-form');
|
||||
if (!customFieldsForm) {
|
||||
return; // No custom fields form on this page
|
||||
}
|
||||
|
||||
const subnetId = customFieldsForm.action.match(/\/subnet\/(\d+)\/update_custom_fields/)?.[1];
|
||||
if (!subnetId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all form fields
|
||||
const formFields = customFieldsForm.querySelectorAll('input, textarea, select');
|
||||
const originalValues = new Map();
|
||||
|
||||
// Store original values
|
||||
formFields.forEach(field => {
|
||||
if (field.type === 'checkbox') {
|
||||
originalValues.set(field, field.checked);
|
||||
} else {
|
||||
originalValues.set(field, field.value);
|
||||
}
|
||||
});
|
||||
|
||||
// Helper function to show toast notification (reuse from subnet.js if available)
|
||||
function showToast(message, type = 'success') {
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `fixed top-20 right-4 px-4 py-3 rounded-lg shadow-lg z-50 ${
|
||||
type === 'success'
|
||||
? 'bg-green-500 text-white'
|
||||
: 'bg-red-500 text-white'
|
||||
}`;
|
||||
toast.textContent = message;
|
||||
document.body.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
toast.style.transition = 'opacity 0.3s';
|
||||
toast.style.opacity = '0';
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Auto-resize textareas
|
||||
const textareas = customFieldsForm.querySelectorAll('textarea');
|
||||
textareas.forEach(textarea => {
|
||||
textarea.style.overflow = 'hidden';
|
||||
textarea.style.resize = 'none';
|
||||
|
||||
function autoResize() {
|
||||
textarea.style.height = 'auto';
|
||||
textarea.style.height = textarea.scrollHeight + 'px';
|
||||
}
|
||||
autoResize();
|
||||
textarea.addEventListener('input', autoResize);
|
||||
});
|
||||
|
||||
// Check if form has changes
|
||||
function hasChanges() {
|
||||
for (const field of formFields) {
|
||||
let currentValue;
|
||||
if (field.type === 'checkbox') {
|
||||
currentValue = field.checked;
|
||||
} else {
|
||||
currentValue = field.value;
|
||||
}
|
||||
|
||||
const originalValue = originalValues.get(field);
|
||||
if (currentValue !== originalValue) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Save all custom fields
|
||||
let saveInProgress = false;
|
||||
async function saveCustomFields() {
|
||||
if (saveInProgress) {
|
||||
return; // Prevent multiple simultaneous saves
|
||||
}
|
||||
|
||||
if (!hasChanges()) {
|
||||
return; // No changes to save
|
||||
}
|
||||
|
||||
saveInProgress = true;
|
||||
|
||||
// Show loading indicator on form
|
||||
const originalOpacity = customFieldsForm.style.opacity;
|
||||
customFieldsForm.style.opacity = '0.6';
|
||||
customFieldsForm.style.pointerEvents = 'none';
|
||||
|
||||
try {
|
||||
// Create FormData from form and convert to JSON
|
||||
const formData = new FormData(customFieldsForm);
|
||||
const data = {};
|
||||
|
||||
// Process all fields
|
||||
for (const [key, value] of formData.entries()) {
|
||||
data[key] = value;
|
||||
}
|
||||
|
||||
// Handle checkboxes that weren't checked (they don't appear in FormData)
|
||||
formFields.forEach(field => {
|
||||
if (field.type === 'checkbox' && !field.checked) {
|
||||
data[field.name] = '';
|
||||
}
|
||||
});
|
||||
|
||||
const response = await fetch(customFieldsForm.action, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Update original values
|
||||
formFields.forEach(field => {
|
||||
if (field.type === 'checkbox') {
|
||||
originalValues.set(field, field.checked);
|
||||
} else {
|
||||
originalValues.set(field, field.value);
|
||||
}
|
||||
});
|
||||
|
||||
showToast('Custom fields saved successfully', 'success');
|
||||
} else {
|
||||
const data = await response.json().catch(() => ({}));
|
||||
const errorMsg = data.errors ? data.errors.join(', ') : (data.error || 'Failed to save custom fields');
|
||||
showToast(errorMsg, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('Error saving custom fields. Please try again.', 'error');
|
||||
console.error('Error saving custom fields:', error);
|
||||
} finally {
|
||||
customFieldsForm.style.opacity = originalOpacity;
|
||||
customFieldsForm.style.pointerEvents = '';
|
||||
saveInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Add blur event listeners to all fields
|
||||
formFields.forEach(field => {
|
||||
// Skip if it's a checkbox (we'll handle change event instead)
|
||||
if (field.type === 'checkbox') {
|
||||
field.addEventListener('change', () => {
|
||||
// Small delay to ensure value is updated
|
||||
setTimeout(saveCustomFields, 100);
|
||||
});
|
||||
} else {
|
||||
field.addEventListener('blur', saveCustomFields);
|
||||
}
|
||||
});
|
||||
|
||||
// Prevent form submission (since we're using auto-save)
|
||||
customFieldsForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
saveCustomFields();
|
||||
});
|
||||
});
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
document.addEventListener("DOMContentLoaded",()=>{let e=document.getElementById("custom-fields-form");if(!e)return;let t=e.action.match(/\/subnet\/(\d+)\/update_custom_fields/)?.[1];if(!t)return;let r=e.querySelectorAll("input, textarea, select"),s=new Map;function o(e,t="success"){let r=document.createElement("div");r.className=`fixed top-20 right-4 px-4 py-3 rounded-lg shadow-lg z-50 ${"success"===t?"bg-green-500 text-white":"bg-red-500 text-white"}`,r.textContent=e,document.body.appendChild(r),setTimeout(()=>{r.style.transition="opacity 0.3s",r.style.opacity="0",setTimeout(()=>r.remove(),300)},3e3)}r.forEach(e=>{"checkbox"===e.type?s.set(e,e.checked):s.set(e,e.value)});let n=e.querySelectorAll("textarea");function c(){for(let e of r){let t;t="checkbox"===e.type?e.checked:e.value;let o=s.get(e);if(t!==o)return!0}return!1}n.forEach(e=>{function t(){e.style.height="auto",e.style.height=e.scrollHeight+"px"}e.style.overflow="hidden",e.style.resize="none",t(),e.addEventListener("input",t)});let l=!1;async function i(){if(l||!c())return;l=!0;let t=e.style.opacity;e.style.opacity="0.6",e.style.pointerEvents="none";try{let n=new FormData(e),i={};for(let[a,d]of n.entries())i[a]=d;r.forEach(e=>{"checkbox"!==e.type||e.checked||(i[e.name]="")});let u=await fetch(e.action,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});if(u.ok)r.forEach(e=>{"checkbox"===e.type?s.set(e,e.checked):s.set(e,e.value)}),o("Custom fields saved successfully","success");else{let y=await u.json().catch(()=>({})),f=y.errors?y.errors.join(", "):y.error||"Failed to save custom fields";o(f,"error")}}catch(h){o("Error saving custom fields. Please try again.","error"),console.error("Error saving custom fields:",h)}finally{e.style.opacity=t,e.style.pointerEvents="",l=!1}}r.forEach(e=>{"checkbox"===e.type?e.addEventListener("change",()=>{setTimeout(i,100)}):e.addEventListener("blur",i)}),e.addEventListener("submit",e=>{e.preventDefault(),i()})});
|
||||
@@ -149,12 +149,6 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="flex justify-center mt-4">
|
||||
<button type="submit" class="bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 hover:cursor-pointer px-4 py-2 rounded-lg flex items-center gap-2">
|
||||
<i class="fas fa-save"></i>
|
||||
<span>Save</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="space-y-4">
|
||||
@@ -243,5 +237,8 @@
|
||||
<script src="/static/js/export_csv.min.js"></script>
|
||||
<script src="/static/js/subnet.min.js"></script>
|
||||
<script src="/static/js/ip_history.min.js"></script>
|
||||
{% if can_edit_subnet %}
|
||||
<script src="/static/js/subnet_custom_fields.min.js"></script>
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user