feat: ✨ ip address notes/descriptions
This commit is contained in:
+148
-1
@@ -31,8 +31,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
rows.forEach(row => {
|
||||
const ipCell = row.querySelector('td:nth-child(1)').textContent.toLowerCase();
|
||||
const hostnameCell = row.querySelector('td:nth-child(2)').textContent.toLowerCase();
|
||||
const descCell = row.querySelector('td:nth-child(3)');
|
||||
const descText = descCell ? descCell.textContent.toLowerCase() : '';
|
||||
|
||||
if (ipCell.includes(searchTerm) || hostnameCell.includes(searchTerm)) {
|
||||
if (ipCell.includes(searchTerm) || hostnameCell.includes(searchTerm) || descText.includes(searchTerm)) {
|
||||
row.style.backgroundColor = 'rgba(59, 130, 246, 0.5)';
|
||||
row.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
|
||||
@@ -132,4 +134,149 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-resize all description textareas (both editable and readonly)
|
||||
const allDescTextareas = document.querySelectorAll('.desc-col textarea');
|
||||
allDescTextareas.forEach(textarea => {
|
||||
textarea.style.overflow = 'hidden';
|
||||
textarea.style.resize = 'none';
|
||||
function autoResize() {
|
||||
textarea.style.height = 'auto';
|
||||
textarea.style.height = textarea.scrollHeight + 'px';
|
||||
}
|
||||
autoResize();
|
||||
});
|
||||
|
||||
// IP Notes inline editing functionality
|
||||
const ipNotesTextareas = document.querySelectorAll('.ip-notes-textarea');
|
||||
const originalValues = new Map();
|
||||
|
||||
// Helper function to show toast notification
|
||||
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);
|
||||
}
|
||||
|
||||
ipNotesTextareas.forEach(textarea => {
|
||||
// Store original value
|
||||
originalValues.set(textarea, textarea.value);
|
||||
|
||||
// Ensure overflow is hidden and resize is disabled
|
||||
textarea.style.overflow = 'hidden';
|
||||
textarea.style.resize = 'none';
|
||||
|
||||
// Auto-resize textarea
|
||||
function autoResize() {
|
||||
textarea.style.height = 'auto';
|
||||
textarea.style.height = textarea.scrollHeight + 'px';
|
||||
}
|
||||
autoResize();
|
||||
|
||||
// Handle input to auto-resize
|
||||
textarea.addEventListener('input', autoResize);
|
||||
|
||||
// Handle blur event to save notes
|
||||
textarea.addEventListener('blur', async function() {
|
||||
const ipId = this.getAttribute('data-ip-id');
|
||||
const deviceDesc = this.getAttribute('data-device-desc') || '';
|
||||
const fullValue = this.value;
|
||||
const originalValue = originalValues.get(this);
|
||||
|
||||
// Extract IP notes: everything after the device description
|
||||
let ipNotes = '';
|
||||
if (deviceDesc) {
|
||||
// If device description exists, check if textarea starts with it
|
||||
const deviceDescTrimmed = deviceDesc.trim();
|
||||
const fullValueTrimmed = fullValue.trim();
|
||||
|
||||
if (fullValueTrimmed.startsWith(deviceDescTrimmed)) {
|
||||
// Remove device description from the beginning
|
||||
ipNotes = fullValueTrimmed.substring(deviceDescTrimmed.length).trim();
|
||||
// Also handle case where there's a newline separator
|
||||
if (ipNotes.startsWith('\n')) {
|
||||
ipNotes = ipNotes.substring(1).trim();
|
||||
}
|
||||
} else {
|
||||
// Device description was modified or removed - extract everything as IP notes
|
||||
// This shouldn't normally happen, but handle gracefully
|
||||
ipNotes = fullValueTrimmed;
|
||||
}
|
||||
} else {
|
||||
// No device description, so entire value is IP notes
|
||||
ipNotes = fullValue.trim();
|
||||
}
|
||||
|
||||
// Only save if value changed
|
||||
if (fullValue !== originalValue) {
|
||||
// Show loading indicator
|
||||
const originalBg = this.style.backgroundColor;
|
||||
this.style.backgroundColor = 'rgba(59, 130, 246, 0.2)';
|
||||
this.disabled = true;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/ip/${ipId}/update_notes`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ notes: ipNotes })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
// Update the displayed value to reflect what was saved
|
||||
let newDisplayValue = '';
|
||||
if (deviceDesc) {
|
||||
newDisplayValue = deviceDesc;
|
||||
if (ipNotes) {
|
||||
newDisplayValue += '\n' + ipNotes;
|
||||
}
|
||||
} else {
|
||||
newDisplayValue = ipNotes;
|
||||
}
|
||||
this.value = newDisplayValue;
|
||||
originalValues.set(this, newDisplayValue);
|
||||
autoResize();
|
||||
showToast('Notes saved successfully', 'success');
|
||||
} else {
|
||||
// Restore original value on error
|
||||
this.value = originalValue;
|
||||
autoResize();
|
||||
showToast(data.error || 'Failed to save notes', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
// Restore original value on error
|
||||
this.value = originalValue;
|
||||
autoResize();
|
||||
showToast('Error saving notes. Please try again.', 'error');
|
||||
console.error('Error saving IP notes:', error);
|
||||
} finally {
|
||||
this.style.backgroundColor = originalBg;
|
||||
this.disabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle Escape key to cancel editing
|
||||
textarea.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
this.value = originalValues.get(this);
|
||||
autoResize();
|
||||
this.blur();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user