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();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Vendored
+2
-2
@@ -1,4 +1,4 @@
|
||||
document.addEventListener("DOMContentLoaded",()=>{let e=document.querySelectorAll("form"),t=null;for(let o of e)if("/search"!==o.action&&"POST"===o.method){t=o;break}if(t&&!document.querySelector('input[placeholder="Search by IP or Hostname"]')){t.addEventListener("submit",e=>{e.preventDefault()});let l=document.createElement("input");l.type="text",l.placeholder="Search by IP or Hostname",l.className="p-2 w-full rounded-lg bg-gray-200 dark:bg-zinc-800 border border-gray-600 focus:outline-none focus:border-blue-400 mb-4 text-center",t.insertAdjacentElement("beforebegin",l),l.addEventListener("keypress",e=>{if("Enter"===e.key){e.preventDefault();let t=l.value.toLowerCase(),o=document.querySelectorAll("tbody tr");o.forEach(e=>{let o=e.querySelector("td:nth-child(1)").textContent.toLowerCase(),l=e.querySelector("td:nth-child(2)").textContent.toLowerCase();o.includes(t)||l.includes(t)?(e.style.backgroundColor="rgba(59, 130, 246, 0.5)",e.scrollIntoView({behavior:"smooth",block:"center"}),setTimeout(()=>{e.style.backgroundColor=""},3e3)):e.style.backgroundColor=""})}})}let r=document.getElementById("toggle-desc"),n=document.querySelectorAll(".desc-col"),s=document.getElementById("desc-col-header"),a=!1;r&&r.addEventListener("click",function(){a=!a,n.forEach(e=>e.classList.toggle("hidden",!a)),s&&s.classList.toggle("hidden",!a),r.textContent=a?"Hide Descriptions":"Show Descriptions"});let d=document.createElement("button");d.innerHTML='<i class="fas fa-arrow-up"></i>',d.style.fontSize="26px",d.className="fixed bottom-5 right-5 bg-gray-200 dark:bg-zinc-800 text-black dark:text-white p-3 rounded-full shadow-lg hidden",d.style.width="60px",d.style.height="60px",d.style.borderRadius="50%",document.body.appendChild(d);let c=document.createElement("style");if(c.textContent=`
|
||||
document.addEventListener("DOMContentLoaded",()=>{let e=document.querySelectorAll("form"),t=null;for(let l of e)if("/search"!==l.action&&"POST"===l.method){t=l;break}if(t&&!document.querySelector('input[placeholder="Search by IP or Hostname"]')){t.addEventListener("submit",e=>{e.preventDefault()});let o=document.createElement("input");o.type="text",o.placeholder="Search by IP or Hostname",o.className="p-2 w-full rounded-lg bg-gray-200 dark:bg-zinc-800 border border-gray-600 focus:outline-none focus:border-blue-400 mb-4 text-center",t.insertAdjacentElement("beforebegin",o),o.addEventListener("keypress",e=>{if("Enter"===e.key){e.preventDefault();let t=o.value.toLowerCase(),l=document.querySelectorAll("tbody tr");l.forEach(e=>{let l=e.querySelector("td:nth-child(1)").textContent.toLowerCase(),o=e.querySelector("td:nth-child(2)").textContent.toLowerCase(),s=e.querySelector("td:nth-child(3)"),r=s?s.textContent.toLowerCase():"";l.includes(t)||o.includes(t)||r.includes(t)?(e.style.backgroundColor="rgba(59, 130, 246, 0.5)",e.scrollIntoView({behavior:"smooth",block:"center"}),setTimeout(()=>{e.style.backgroundColor=""},3e3)):e.style.backgroundColor=""})}})}let s=document.getElementById("toggle-desc"),r=document.querySelectorAll(".desc-col"),n=document.getElementById("desc-col-header"),i=!1;s&&s.addEventListener("click",function(){i=!i,r.forEach(e=>e.classList.toggle("hidden",!i)),n&&n.classList.toggle("hidden",!i),s.textContent=i?"Hide Descriptions":"Show Descriptions"});let a=document.createElement("button");a.innerHTML='<i class="fas fa-arrow-up"></i>',a.style.fontSize="26px",a.className="fixed bottom-5 right-5 bg-gray-200 dark:bg-zinc-800 text-black dark:text-white p-3 rounded-full shadow-lg hidden",a.style.width="60px",a.style.height="60px",a.style.borderRadius="50%",document.body.appendChild(a);let d=document.createElement("style");if(d.textContent=`
|
||||
@keyframes bob {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
@@ -11,4 +11,4 @@ document.addEventListener("DOMContentLoaded",()=>{let e=document.querySelectorAl
|
||||
.bobbing {
|
||||
animation: bob 1.5s infinite;
|
||||
}
|
||||
`,document.head.appendChild(c),d.classList.add("bobbing"),window.addEventListener("scroll",()=>{window.scrollY>200?d.classList.remove("hidden"):d.classList.add("hidden")}),d.addEventListener("click",()=>{window.scrollTo({top:0,behavior:"smooth"})}),requestAnimationFrame(()=>{let e=document.documentElement.scrollHeight>document.documentElement.clientHeight;e&&0===window.scrollY&&(window.scrollBy(0,1),requestAnimationFrame(()=>{window.scrollBy(0,-1)}))}),window.location.hash){let i=window.location.hash.substring(1),b=document.getElementById(i);b&&setTimeout(()=>{b.scrollIntoView({behavior:"smooth",block:"center"}),b.style.backgroundColor="rgba(59, 130, 246, 0.5)",setTimeout(()=>{b.style.backgroundColor=""},3e3)},100)}});
|
||||
`,document.head.appendChild(d),a.classList.add("bobbing"),window.addEventListener("scroll",()=>{window.scrollY>200?a.classList.remove("hidden"):a.classList.add("hidden")}),a.addEventListener("click",()=>{window.scrollTo({top:0,behavior:"smooth"})}),requestAnimationFrame(()=>{let e=document.documentElement.scrollHeight>document.documentElement.clientHeight;e&&0===window.scrollY&&(window.scrollBy(0,1),requestAnimationFrame(()=>{window.scrollBy(0,-1)}))}),window.location.hash){let c=window.location.hash.substring(1),h=document.getElementById(c);h&&setTimeout(()=>{h.scrollIntoView({behavior:"smooth",block:"center"}),h.style.backgroundColor="rgba(59, 130, 246, 0.5)",setTimeout(()=>{h.style.backgroundColor=""},3e3)},100)}let u=document.querySelectorAll(".desc-col textarea");u.forEach(e=>{function t(){e.style.height="auto",e.style.height=e.scrollHeight+"px"}e.style.overflow="hidden",e.style.resize="none",t()});let y=document.querySelectorAll(".ip-notes-textarea"),b=new Map;function g(e,t="success"){let l=document.createElement("div");l.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"}`,l.textContent=e,document.body.appendChild(l),setTimeout(()=>{l.style.transition="opacity 0.3s",l.style.opacity="0",setTimeout(()=>l.remove(),300)},3e3)}y.forEach(e=>{function t(){e.style.height="auto",e.style.height=e.scrollHeight+"px"}b.set(e,e.value),e.style.overflow="hidden",e.style.resize="none",t(),e.addEventListener("input",t),e.addEventListener("blur",async function(){let e=this.getAttribute("data-ip-id"),l=this.getAttribute("data-device-desc")||"",o=this.value,s=b.get(this),r="";if(l){let n=l.trim(),i=o.trim();i.startsWith(n)?(r=i.substring(n.length).trim()).startsWith("\n")&&(r=r.substring(1).trim()):r=i}else r=o.trim();if(o!==s){let a=this.style.backgroundColor;this.style.backgroundColor="rgba(59, 130, 246, 0.2)",this.disabled=!0;try{let d=await fetch(`/ip/${e}/update_notes`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({notes:r})}),c=await d.json();if(c.success){let h="";l?(h=l,r&&(h+="\n"+r)):h=r,this.value=h,b.set(this,h),t(),g("Notes saved successfully","success")}else this.value=s,t(),g(c.error||"Failed to save notes","error")}catch(u){this.value=s,t(),g("Error saving notes. Please try again.","error"),console.error("Error saving IP notes:",u)}finally{this.style.backgroundColor=a,this.disabled=!1}}}),e.addEventListener("keydown",function(e){"Escape"===e.key&&(this.value=b.get(this),t(),this.blur())})})});
|
||||
Reference in New Issue
Block a user