feat: ✨ convert to api and rewrite ui
This commit is contained in:
@@ -1,94 +0,0 @@
|
||||
/* Icon search suggestions styling */
|
||||
.icon-suggestions {
|
||||
max-height: 240px;
|
||||
overflow-y: auto;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.icon-suggestion-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.75rem 1rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease-in-out;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.icon-suggestion-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.icon-suggestion-item:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.dark .icon-suggestion-item {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.dark .icon-suggestion-item:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.icon-suggestion-item i {
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
font-size: 1.125rem;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.dark .icon-suggestion-item i {
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
||||
.icon-suggestion-item span {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.875rem;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.dark .icon-suggestion-item span {
|
||||
color: #e5e7eb;
|
||||
}
|
||||
|
||||
/* Icon preview styling */
|
||||
.icon-preview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 2rem;
|
||||
}
|
||||
|
||||
/* Scrollbar styling for suggestions */
|
||||
.icon-suggestions::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.icon-suggestions::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.dark .icon-suggestions::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.icon-suggestions::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.dark .icon-suggestions::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.icon-suggestions::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.dark .icon-suggestions::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
.icon-suggestions{max-height:240px;overflow-y:auto;border-radius:.5rem;box-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -2px rgba(0,0,0,.05)}.icon-suggestion-item{display:flex;align-items:center;gap:.75rem;padding:.75rem 1rem;cursor:pointer;transition:background-color .15s ease-in-out;border-bottom:1px solid rgba(0,0,0,.1)}.icon-suggestion-item:last-child{border-bottom:none}.icon-suggestion-item:hover{background-color:rgba(0,0,0,.05)}.dark .icon-suggestion-item{border-bottom-color:rgba(255,255,255,.1)}.dark .icon-suggestion-item:hover{background-color:rgba(255,255,255,.1)}.icon-suggestion-item i{width:20px;text-align:center;font-size:1.125rem;color:#4b5563}.dark .icon-suggestion-item i{color:#d1d5db}.icon-suggestion-item span{font-family:'Courier New',monospace;font-size:.875rem;color:#374151}.dark .icon-suggestion-item span{color:#e5e7eb}.icon-preview{display:flex;align-items:center;justify-content:center;min-width:2rem}.icon-suggestions::-webkit-scrollbar{width:8px}.icon-suggestions::-webkit-scrollbar-track{background:rgba(0,0,0,.05);border-radius:4px}.dark .icon-suggestions::-webkit-scrollbar-track{background:rgba(255,255,255,.05)}.icon-suggestions::-webkit-scrollbar-thumb{background:rgba(0,0,0,.2);border-radius:4px}.dark .icon-suggestions::-webkit-scrollbar-thumb{background:rgba(255,255,255,.2)}.icon-suggestions::-webkit-scrollbar-thumb:hover{background:rgba(0,0,0,.3)}.dark .icon-suggestions::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,.3)}
|
||||
@@ -1,16 +0,0 @@
|
||||
h2 {
|
||||
cursor: pointer;
|
||||
}
|
||||
.container form:not(.mb-6), .mt-4 {
|
||||
display: none;
|
||||
}
|
||||
.allocated-ips {
|
||||
display: block;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.button-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 1rem;
|
||||
justify-items: center;
|
||||
}
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
h2{cursor:pointer}.container form:not(.mb-6),.mt-4{display:none}.allocated-ips{display:block;margin-top:1rem}.button-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:1rem;justify-items:center}
|
||||
@@ -1 +0,0 @@
|
||||
@import "tailwindcss"
|
||||
@@ -1,15 +0,0 @@
|
||||
function validateSubnetForm() {
|
||||
const cidrInput = document.getElementById('cidr-input');
|
||||
const errorSpan = document.getElementById('cidr-error');
|
||||
const cidrPattern = /^(?:\d{1,3}\.){3}\d{1,3}\/([0-9]|[1-2][0-9]|3[0-2])$/;
|
||||
if (!cidrPattern.test(cidrInput.value.trim())) {
|
||||
errorSpan.textContent = 'Please enter a valid CIDR (e.g., 192.168.1.0/24)';
|
||||
errorSpan.classList.remove('hidden');
|
||||
cidrInput.classList.add('border-red-500');
|
||||
return false;
|
||||
}
|
||||
errorSpan.textContent = '';
|
||||
errorSpan.classList.add('hidden');
|
||||
cidrInput.classList.remove('border-red-500');
|
||||
return true;
|
||||
}
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
function validateSubnetForm(){let e=document.getElementById("cidr-input"),t=document.getElementById("cidr-error");return/^(?:\d{1,3}\.){3}\d{1,3}\/([0-9]|[1-2][0-9]|3[0-2])$/.test(e.value.trim())?(t.textContent="",t.classList.add("hidden"),e.classList.remove("border-red-500"),!0):(t.textContent="Please enter a valid CIDR (e.g., 192.168.1.0/24)",t.classList.remove("hidden"),e.classList.add("border-red-500"),!1)}
|
||||
@@ -1,147 +0,0 @@
|
||||
function showAddSubnetModal() {
|
||||
document.getElementById('add-subnet-modal').classList.remove('hidden');
|
||||
document.getElementById('add-subnet-name').value = '';
|
||||
document.getElementById('add-subnet-cidr').value = '';
|
||||
document.getElementById('add-subnet-site').value = '';
|
||||
document.getElementById('add-subnet-vlan-id').value = '';
|
||||
document.getElementById('add-subnet-vlan-description').value = '';
|
||||
document.getElementById('add-subnet-vlan-notes').value = '';
|
||||
document.getElementById('vlan-id-error').classList.add('hidden');
|
||||
}
|
||||
|
||||
function closeAddSubnetModal() {
|
||||
document.getElementById('add-subnet-modal').classList.add('hidden');
|
||||
document.getElementById('cidr-error').classList.add('hidden');
|
||||
document.getElementById('vlan-id-error').classList.add('hidden');
|
||||
}
|
||||
|
||||
function editSubnet(subnetId, name, cidr, site, vlanId, vlanDescription, vlanNotes) {
|
||||
document.getElementById('edit-subnet-id').value = subnetId;
|
||||
document.getElementById('edit-subnet-name').value = name;
|
||||
document.getElementById('edit-subnet-cidr').value = cidr;
|
||||
document.getElementById('edit-subnet-site').value = site;
|
||||
document.getElementById('edit-subnet-vlan-id').value = vlanId || '';
|
||||
document.getElementById('edit-subnet-vlan-description').value = vlanDescription || '';
|
||||
document.getElementById('edit-subnet-vlan-notes').value = vlanNotes || '';
|
||||
document.getElementById('edit-subnet-modal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function closeEditSubnetModal() {
|
||||
document.getElementById('edit-subnet-modal').classList.add('hidden');
|
||||
document.getElementById('edit-cidr-error').classList.add('hidden');
|
||||
document.getElementById('edit-vlan-id-error').classList.add('hidden');
|
||||
}
|
||||
|
||||
function validateVlanId(vlanIdValue, errorElementId) {
|
||||
if (!vlanIdValue || vlanIdValue.trim() === '') {
|
||||
return true; // VLAN ID is optional
|
||||
}
|
||||
|
||||
const vlanId = parseInt(vlanIdValue.trim());
|
||||
if (isNaN(vlanId)) {
|
||||
const errorElement = document.getElementById(errorElementId);
|
||||
if (errorElement) {
|
||||
errorElement.textContent = 'VLAN ID must be a valid integer';
|
||||
errorElement.classList.remove('hidden');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vlanId < 1 || vlanId > 4094) {
|
||||
const errorElement = document.getElementById(errorElementId);
|
||||
if (errorElement) {
|
||||
errorElement.textContent = 'VLAN ID must be between 1 and 4094';
|
||||
errorElement.classList.remove('hidden');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const errorElement = document.getElementById(errorElementId);
|
||||
if (errorElement) {
|
||||
errorElement.classList.add('hidden');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function validateSubnetForm() {
|
||||
const cidrInput = document.getElementById('add-subnet-cidr');
|
||||
const cidrError = document.getElementById('cidr-error');
|
||||
const cidr = cidrInput.value.trim();
|
||||
|
||||
// Basic CIDR validation
|
||||
const cidrPattern = /^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/;
|
||||
if (!cidrPattern.test(cidr)) {
|
||||
cidrError.textContent = 'Invalid CIDR format. Use format like 192.168.1.0/24';
|
||||
cidrError.classList.remove('hidden');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check prefix length
|
||||
const parts = cidr.split('/');
|
||||
if (parts.length === 2) {
|
||||
const prefixLen = parseInt(parts[1]);
|
||||
if (prefixLen < 24 || prefixLen > 32) {
|
||||
cidrError.textContent = 'Subnet must be /24 or smaller (e.g., /24, /25, ... /32)';
|
||||
cidrError.classList.remove('hidden');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
cidrError.classList.add('hidden');
|
||||
|
||||
// Validate VLAN ID
|
||||
const vlanIdInput = document.getElementById('add-subnet-vlan-id');
|
||||
if (!validateVlanId(vlanIdInput.value, 'vlan-id-error')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function validateEditSubnetForm() {
|
||||
const cidrInput = document.getElementById('edit-subnet-cidr');
|
||||
const cidrError = document.getElementById('edit-cidr-error');
|
||||
const cidr = cidrInput.value.trim();
|
||||
|
||||
// Basic CIDR validation
|
||||
const cidrPattern = /^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/;
|
||||
if (!cidrPattern.test(cidr)) {
|
||||
cidrError.textContent = 'Invalid CIDR format. Use format like 192.168.1.0/24';
|
||||
cidrError.classList.remove('hidden');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check prefix length
|
||||
const parts = cidr.split('/');
|
||||
if (parts.length === 2) {
|
||||
const prefixLen = parseInt(parts[1]);
|
||||
if (prefixLen < 24 || prefixLen > 32) {
|
||||
cidrError.textContent = 'Subnet must be /24 or smaller (e.g., /24, /25, ... /32)';
|
||||
cidrError.classList.remove('hidden');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
cidrError.classList.add('hidden');
|
||||
|
||||
// Validate VLAN ID
|
||||
const vlanIdInput = document.getElementById('edit-subnet-vlan-id');
|
||||
if (!validateVlanId(vlanIdInput.value, 'edit-vlan-id-error')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Close modals when clicking outside
|
||||
window.onclick = function(event) {
|
||||
const addModal = document.getElementById('add-subnet-modal');
|
||||
const editModal = document.getElementById('edit-subnet-modal');
|
||||
if (event.target === addModal) {
|
||||
closeAddSubnetModal();
|
||||
}
|
||||
if (event.target === editModal) {
|
||||
closeEditSubnetModal();
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
function showAddSubnetModal(){document.getElementById("add-subnet-modal").classList.remove("hidden"),document.getElementById("add-subnet-name").value="",document.getElementById("add-subnet-cidr").value="",document.getElementById("add-subnet-site").value="",document.getElementById("add-subnet-vlan-id").value="",document.getElementById("add-subnet-vlan-description").value="",document.getElementById("add-subnet-vlan-notes").value="",document.getElementById("vlan-id-error").classList.add("hidden")}function closeAddSubnetModal(){document.getElementById("add-subnet-modal").classList.add("hidden"),document.getElementById("cidr-error").classList.add("hidden"),document.getElementById("vlan-id-error").classList.add("hidden")}function editSubnet(e,t,d,n,l,i,a){document.getElementById("edit-subnet-id").value=e,document.getElementById("edit-subnet-name").value=t,document.getElementById("edit-subnet-cidr").value=d,document.getElementById("edit-subnet-site").value=n,document.getElementById("edit-subnet-vlan-id").value=l||"",document.getElementById("edit-subnet-vlan-description").value=i||"",document.getElementById("edit-subnet-vlan-notes").value=a||"",document.getElementById("edit-subnet-modal").classList.remove("hidden")}function closeEditSubnetModal(){document.getElementById("edit-subnet-modal").classList.add("hidden"),document.getElementById("edit-cidr-error").classList.add("hidden"),document.getElementById("edit-vlan-id-error").classList.add("hidden")}function validateVlanId(e,t){if(!e||""===e.trim())return!0;let d=parseInt(e.trim());if(isNaN(d)){let n=document.getElementById(t);return n&&(n.textContent="VLAN ID must be a valid integer",n.classList.remove("hidden")),!1}if(d<1||d>4094){let l=document.getElementById(t);return l&&(l.textContent="VLAN ID must be between 1 and 4094",l.classList.remove("hidden")),!1}let i=document.getElementById(t);return i&&i.classList.add("hidden"),!0}function validateSubnetForm(){let e=document.getElementById("add-subnet-cidr"),t=document.getElementById("cidr-error"),d=e.value.trim();if(!/^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/.test(d))return t.textContent="Invalid CIDR format. Use format like 192.168.1.0/24",t.classList.remove("hidden"),!1;let n=d.split("/");if(2===n.length){let l=parseInt(n[1]);if(l<24||l>32)return t.textContent="Subnet must be /24 or smaller (e.g., /24, /25, ... /32)",t.classList.remove("hidden"),!1}t.classList.add("hidden");let i=document.getElementById("add-subnet-vlan-id");return!!validateVlanId(i.value,"vlan-id-error")}function validateEditSubnetForm(){let e=document.getElementById("edit-subnet-cidr"),t=document.getElementById("edit-cidr-error"),d=e.value.trim();if(!/^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/.test(d))return t.textContent="Invalid CIDR format. Use format like 192.168.1.0/24",t.classList.remove("hidden"),!1;let n=d.split("/");if(2===n.length){let l=parseInt(n[1]);if(l<24||l>32)return t.textContent="Subnet must be /24 or smaller (e.g., /24, /25, ... /32)",t.classList.remove("hidden"),!1}t.classList.add("hidden");let i=document.getElementById("edit-subnet-vlan-id");return!!validateVlanId(i.value,"edit-vlan-id-error")}window.onclick=function(e){let t=document.getElementById("add-subnet-modal"),d=document.getElementById("edit-subnet-modal");e.target===t&&closeAddSubnetModal(),e.target===d&&closeEditSubnetModal()};
|
||||
@@ -1,122 +0,0 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Filter toggle functionality
|
||||
const filterToggle = document.getElementById('filter-toggle');
|
||||
const filterForm = document.getElementById('audit-filter-form');
|
||||
const filterArrow = document.getElementById('filter-arrow');
|
||||
|
||||
if (filterToggle && filterForm && filterArrow) {
|
||||
filterToggle.addEventListener('click', function() {
|
||||
filterForm.classList.toggle('hidden');
|
||||
// Toggle rotation using inline style for better compatibility
|
||||
if (filterForm.classList.contains('hidden')) {
|
||||
filterArrow.style.transform = 'rotate(0deg)';
|
||||
} else {
|
||||
filterArrow.style.transform = 'rotate(180deg)';
|
||||
}
|
||||
});
|
||||
|
||||
// Set initial arrow rotation if form is visible (has active filters or expand_filters param)
|
||||
if (!filterForm.classList.contains('hidden')) {
|
||||
filterArrow.style.transform = 'rotate(180deg)';
|
||||
}
|
||||
}
|
||||
|
||||
// Format timestamps
|
||||
document.querySelectorAll('td[data-utc]').forEach(function(td) {
|
||||
const utc = td.getAttribute('data-utc');
|
||||
if (utc) {
|
||||
const date = new Date(utc + 'Z');
|
||||
td.textContent = date.toLocaleString();
|
||||
}
|
||||
});
|
||||
|
||||
// Parse and display visual diffs
|
||||
document.querySelectorAll('.diff-container').forEach(function(container) {
|
||||
const details = container.getAttribute('data-details');
|
||||
if (!details) return;
|
||||
|
||||
// Try to parse common change patterns
|
||||
let html = details;
|
||||
|
||||
// Pattern 1: "Changed X from 'old' to 'new'"
|
||||
html = html.replace(/Changed (.+?) from ['"](.+?)['"] to ['"](.+?)['"]/gi, function(match, field, oldVal, newVal) {
|
||||
return `Changed ${field} from <span class="diff-removed">${oldVal}</span> to <span class="diff-added">${newVal}</span>`;
|
||||
});
|
||||
|
||||
// Pattern 2: "Renamed X to Y"
|
||||
html = html.replace(/Renamed (.+?) to ['"](.+?)['"]/gi, function(match, oldVal, newVal) {
|
||||
return `Renamed <span class="diff-removed">${oldVal}</span> to <span class="diff-added">${newVal}</span>`;
|
||||
});
|
||||
|
||||
// Pattern 3: "Updated X: old -> new"
|
||||
html = html.replace(/Updated (.+?):\s*(.+?)\s*->\s*(.+?)(?:\s|$)/gi, function(match, field, oldVal, newVal) {
|
||||
return `Updated ${field}: <span class="diff-removed">${oldVal}</span> → <span class="diff-added">${newVal}</span>`;
|
||||
});
|
||||
|
||||
// Pattern 4: "Set X to Y" (when it was previously something else, look for context)
|
||||
html = html.replace(/Set (.+?) to ['"](.+?)['"]/gi, function(match, field, newVal) {
|
||||
return `Set ${field} to <span class="diff-added">${newVal}</span>`;
|
||||
});
|
||||
|
||||
// Pattern 5: "Removed X" or "Deleted X"
|
||||
html = html.replace(/(Removed|Deleted) ['"](.+?)['"]/gi, function(match, action, val) {
|
||||
return `${action} <span class="diff-removed">${val}</span>`;
|
||||
});
|
||||
|
||||
// Pattern 6: "Added X"
|
||||
html = html.replace(/Added ['"](.+?)['"]/gi, function(match, val) {
|
||||
return `Added <span class="diff-added">${val}</span>`;
|
||||
});
|
||||
|
||||
// Pattern 7: "Assigned X to Y" or "Unassigned X from Y"
|
||||
// Capture everything after "to " or "from " to preserve all spaces in target
|
||||
html = html.replace(/(Assigned|Unassigned) (.+?) (to|from) (.+)$/gi, function(match, action, item, prep, target) {
|
||||
const actionClass = action === 'Assigned' ? 'diff-added' : 'diff-removed';
|
||||
// Preserve the space between prep and target
|
||||
return `${action} <span class="${actionClass}">${item}</span> ${prep} ${target}`;
|
||||
});
|
||||
|
||||
// Pattern 8: Generic "from X to Y" pattern
|
||||
html = html.replace(/from ['"](.+?)['"] to ['"](.+?)['"]/gi, function(match, oldVal, newVal) {
|
||||
return `from <span class="diff-removed">${oldVal}</span> to <span class="diff-added">${newVal}</span>`;
|
||||
});
|
||||
|
||||
container.innerHTML = html || details;
|
||||
});
|
||||
|
||||
// Export button handler
|
||||
const exportBtn = document.getElementById('export-btn');
|
||||
if (exportBtn) {
|
||||
exportBtn.addEventListener('click', function() {
|
||||
const form = document.getElementById('audit-filter-form');
|
||||
const formData = new FormData(form);
|
||||
const params = new URLSearchParams();
|
||||
|
||||
// Add all form fields to params
|
||||
for (const [key, value] of formData.entries()) {
|
||||
if (value) {
|
||||
if (key === 'user_ids') {
|
||||
// Handle multiple user_ids
|
||||
params.append('user_ids', value);
|
||||
} else {
|
||||
params.append(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle multiple user_ids separately
|
||||
const userSelect = form.querySelector('select[name="user_ids"]');
|
||||
if (userSelect) {
|
||||
const selectedUsers = Array.from(userSelect.selectedOptions).map(opt => opt.value);
|
||||
params.delete('user_ids');
|
||||
selectedUsers.forEach(userId => {
|
||||
params.append('user_ids', userId);
|
||||
});
|
||||
}
|
||||
|
||||
// Redirect to export endpoint
|
||||
window.location.href = '/audit/export_csv?' + params.toString();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded",function(){let e=document.getElementById("filter-toggle"),t=document.getElementById("audit-filter-form"),n=document.getElementById("filter-arrow");e&&t&&n&&(e.addEventListener("click",function(){t.classList.toggle("hidden"),t.classList.contains("hidden")?n.style.transform="rotate(0deg)":n.style.transform="rotate(180deg)"}),t.classList.contains("hidden")||(n.style.transform="rotate(180deg)")),document.querySelectorAll("td[data-utc]").forEach(function(e){let t=e.getAttribute("data-utc");if(t){let n=new Date(t+"Z");e.textContent=n.toLocaleString()}}),document.querySelectorAll(".diff-container").forEach(function(e){let t=e.getAttribute("data-details");if(!t)return;let n=t;n=(n=(n=(n=(n=(n=(n=(n=n.replace(/Changed (.+?) from ['"](.+?)['"] to ['"](.+?)['"]/gi,function(e,t,n,d){return`Changed ${t} from <span class="diff-removed">${n}</span> to <span class="diff-added">${d}</span>`})).replace(/Renamed (.+?) to ['"](.+?)['"]/gi,function(e,t,n){return`Renamed <span class="diff-removed">${t}</span> to <span class="diff-added">${n}</span>`})).replace(/Updated (.+?):\s*(.+?)\s*->\s*(.+?)(?:\s|$)/gi,function(e,t,n,d){return`Updated ${t}: <span class="diff-removed">${n}</span> → <span class="diff-added">${d}</span>`})).replace(/Set (.+?) to ['"](.+?)['"]/gi,function(e,t,n){return`Set ${t} to <span class="diff-added">${n}</span>`})).replace(/(Removed|Deleted) ['"](.+?)['"]/gi,function(e,t,n){return`${t} <span class="diff-removed">${n}</span>`})).replace(/Added ['"](.+?)['"]/gi,function(e,t){return`Added <span class="diff-added">${t}</span>`})).replace(/(Assigned|Unassigned) (.+?) (to|from) (.+)$/gi,function(e,t,n,d,a){return`${t} <span class="${"Assigned"===t?"diff-added":"diff-removed"}">${n}</span> ${d} ${a}`})).replace(/from ['"](.+?)['"] to ['"](.+?)['"]/gi,function(e,t,n){return`from <span class="diff-removed">${t}</span> to <span class="diff-added">${n}</span>`}),e.innerHTML=n||t});let d=document.getElementById("export-btn");d&&d.addEventListener("click",function(){let e=document.getElementById("audit-filter-form"),t=new FormData(e),n=new URLSearchParams;for(let[d,a]of t.entries())a&&("user_ids"===d?n.append("user_ids",a):n.append(d,a));let s=e.querySelector('select[name="user_ids"]');if(s){let r=Array.from(s.selectedOptions).map(e=>e.value);n.delete("user_ids"),r.forEach(e=>{n.append("user_ids",e)})}window.location.href="/audit/export_csv?"+n.toString()})});
|
||||
@@ -1,183 +0,0 @@
|
||||
function showTab(tabName) {
|
||||
// Hide all panels
|
||||
document.querySelectorAll('.tab-panel').forEach(panel => panel.classList.add('hidden'));
|
||||
|
||||
// Update all tab buttons to inactive state
|
||||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||
btn.classList.remove('border-gray-600', 'text-gray-900', 'dark:text-gray-100');
|
||||
btn.classList.add('border-transparent', 'text-gray-500');
|
||||
});
|
||||
|
||||
// Show selected panel
|
||||
document.getElementById('panel-' + tabName).classList.remove('hidden');
|
||||
|
||||
// Update selected tab to active state
|
||||
const activeTab = document.getElementById('tab-' + tabName);
|
||||
activeTab.classList.remove('border-transparent', 'text-gray-500');
|
||||
activeTab.classList.add('border-gray-600', 'text-gray-900', 'dark:text-gray-100');
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Update selected IP count
|
||||
document.getElementById('bulk-ip-select')?.addEventListener('change', function() {
|
||||
document.getElementById('selected-ip-count').textContent = this.selectedOptions.length;
|
||||
});
|
||||
|
||||
document.getElementById('bulk-tag-device-select')?.addEventListener('change', function() {
|
||||
document.getElementById('selected-tag-device-count').textContent = this.selectedOptions.length;
|
||||
});
|
||||
|
||||
// Load available IPs when subnet changes
|
||||
document.getElementById('bulk-subnet-select')?.addEventListener('change', function() {
|
||||
const subnetId = this.value;
|
||||
const ipSelect = document.getElementById('bulk-ip-select');
|
||||
if (!subnetId) {
|
||||
ipSelect.innerHTML = '<option value="" disabled>Select a subnet first...</option>';
|
||||
document.getElementById('selected-ip-count').textContent = '0';
|
||||
return;
|
||||
}
|
||||
ipSelect.innerHTML = '<option value="" disabled>Loading...</option>';
|
||||
fetch(`/get_available_ips?subnet_id=${subnetId}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
ipSelect.innerHTML = '';
|
||||
if (data.available_ips.length === 0) {
|
||||
ipSelect.innerHTML = '<option value="" disabled>No available IPs in this subnet</option>';
|
||||
} else {
|
||||
data.available_ips.forEach(ip => {
|
||||
const option = document.createElement('option');
|
||||
option.value = ip.id;
|
||||
option.textContent = ip.ip;
|
||||
ipSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
document.getElementById('selected-ip-count').textContent = '0';
|
||||
})
|
||||
.catch(() => {
|
||||
ipSelect.innerHTML = '<option value="" disabled>Error loading IPs</option>';
|
||||
});
|
||||
});
|
||||
|
||||
// Bulk IP Assignment
|
||||
document.getElementById('bulk-assign-ips-form')?.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(this);
|
||||
const resultDiv = document.getElementById('assign-ips-result');
|
||||
resultDiv.classList.remove('hidden');
|
||||
resultDiv.innerHTML = '<p class="text-blue-500">Processing...</p>';
|
||||
|
||||
fetch('/bulk/assign_ips', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
let html = '<div class="space-y-2">';
|
||||
if (data.success.length > 0) {
|
||||
html += `<div class="text-green-600 dark:text-green-400"><strong>Successfully assigned ${data.success.length} IP(s):</strong><ul class="list-disc list-inside mt-2">`;
|
||||
data.success.forEach(item => {
|
||||
html += `<li>${item.ip}</li>`;
|
||||
});
|
||||
html += '</ul></div>';
|
||||
}
|
||||
if (data.failed.length > 0) {
|
||||
html += `<div class="text-red-600 dark:text-red-400"><strong>Failed ${data.failed.length} assignment(s):</strong><ul class="list-disc list-inside mt-2">`;
|
||||
data.failed.forEach(item => {
|
||||
const ipDisplay = item.ip ? ` (${item.ip})` : '';
|
||||
html += `<li>IP ID ${item.ip_id}${ipDisplay}: ${item.reason}</li>`;
|
||||
});
|
||||
html += '</ul></div>';
|
||||
}
|
||||
html += '</div>';
|
||||
resultDiv.innerHTML = html;
|
||||
// Reload IP list if successful
|
||||
if (data.success.length > 0) {
|
||||
const subnetSelect = document.getElementById('bulk-subnet-select');
|
||||
if (subnetSelect.value) {
|
||||
subnetSelect.dispatchEvent(new Event('change'));
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
resultDiv.innerHTML = `<p class="text-red-600">Error: ${error.message}</p>`;
|
||||
});
|
||||
});
|
||||
|
||||
// Bulk Device Creation
|
||||
document.getElementById('bulk-create-devices-form')?.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(this);
|
||||
const resultDiv = document.getElementById('create-devices-result');
|
||||
resultDiv.classList.remove('hidden');
|
||||
resultDiv.innerHTML = '<p class="text-blue-500">Processing...</p>';
|
||||
|
||||
fetch('/bulk/create_devices', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
let html = '<div class="space-y-2">';
|
||||
if (data.success.length > 0) {
|
||||
html += `<div class="text-green-600 dark:text-green-400"><strong>Successfully created ${data.success.length} device(s):</strong><ul class="list-disc list-inside mt-2">`;
|
||||
data.success.forEach(item => {
|
||||
html += `<li>${item.name}</li>`;
|
||||
});
|
||||
html += '</ul></div>';
|
||||
}
|
||||
if (data.failed.length > 0) {
|
||||
html += `<div class="text-red-600 dark:text-red-400"><strong>Failed ${data.failed.length} creation(s):</strong><ul class="list-disc list-inside mt-2">`;
|
||||
data.failed.forEach(item => {
|
||||
html += `<li>${item.name}: ${item.reason}</li>`;
|
||||
});
|
||||
html += '</ul></div>';
|
||||
}
|
||||
html += '</div>';
|
||||
resultDiv.innerHTML = html;
|
||||
if (data.success.length > 0) {
|
||||
setTimeout(() => window.location.reload(), 2000);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
resultDiv.innerHTML = `<p class="text-red-600">Error: ${error.message}</p>`;
|
||||
});
|
||||
});
|
||||
|
||||
// Bulk Tag Assignment
|
||||
document.getElementById('bulk-assign-tags-form')?.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(this);
|
||||
const resultDiv = document.getElementById('assign-tags-result');
|
||||
resultDiv.classList.remove('hidden');
|
||||
resultDiv.innerHTML = '<p class="text-blue-500">Processing...</p>';
|
||||
|
||||
fetch('/bulk/assign_tags', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
let html = '<div class="space-y-2">';
|
||||
if (data.success.length > 0) {
|
||||
html += `<div class="text-green-600 dark:text-green-400"><strong>Successfully assigned ${data.success.length} tag(s):</strong><ul class="list-disc list-inside mt-2">`;
|
||||
data.success.forEach(item => {
|
||||
html += `<li>${item.device_name}: ${item.tag_name}</li>`;
|
||||
});
|
||||
html += '</ul></div>';
|
||||
}
|
||||
if (data.failed.length > 0) {
|
||||
html += `<div class="text-red-600 dark:text-red-400"><strong>Failed ${data.failed.length} assignment(s):</strong><ul class="list-disc list-inside mt-2">`;
|
||||
data.failed.forEach(item => {
|
||||
html += `<li>Device ID ${item.device_id}, Tag ID ${item.tag_id}: ${item.reason}</li>`;
|
||||
});
|
||||
html += '</ul></div>';
|
||||
}
|
||||
html += '</div>';
|
||||
resultDiv.innerHTML = html;
|
||||
})
|
||||
.catch(error => {
|
||||
resultDiv.innerHTML = `<p class="text-red-600">Error: ${error.message}</p>`;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
function showTab(e){document.querySelectorAll(".tab-panel").forEach(e=>e.classList.add("hidden")),document.querySelectorAll(".tab-btn").forEach(e=>{e.classList.remove("border-gray-600","text-gray-900","dark:text-gray-100"),e.classList.add("border-transparent","text-gray-500")}),document.getElementById("panel-"+e).classList.remove("hidden");let t=document.getElementById("tab-"+e);t.classList.remove("border-transparent","text-gray-500"),t.classList.add("border-gray-600","text-gray-900","dark:text-gray-100")}document.addEventListener("DOMContentLoaded",function(){document.getElementById("bulk-ip-select")?.addEventListener("change",function(){document.getElementById("selected-ip-count").textContent=this.selectedOptions.length}),document.getElementById("bulk-tag-device-select")?.addEventListener("change",function(){document.getElementById("selected-tag-device-count").textContent=this.selectedOptions.length}),document.getElementById("bulk-subnet-select")?.addEventListener("change",function(){let e=this.value,t=document.getElementById("bulk-ip-select");if(!e){t.innerHTML='<option value="" disabled>Select a subnet first...</option>',document.getElementById("selected-ip-count").textContent="0";return}t.innerHTML='<option value="" disabled>Loading...</option>',fetch(`/get_available_ips?subnet_id=${e}`).then(e=>e.json()).then(e=>{t.innerHTML="",0===e.available_ips.length?t.innerHTML='<option value="" disabled>No available IPs in this subnet</option>':e.available_ips.forEach(e=>{let s=document.createElement("option");s.value=e.id,s.textContent=e.ip,t.appendChild(s)}),document.getElementById("selected-ip-count").textContent="0"}).catch(()=>{t.innerHTML='<option value="" disabled>Error loading IPs</option>'})}),document.getElementById("bulk-assign-ips-form")?.addEventListener("submit",function(e){e.preventDefault();let t=new FormData(this),s=document.getElementById("assign-ips-result");s.classList.remove("hidden"),s.innerHTML='<p class="text-blue-500">Processing...</p>',fetch("/bulk/assign_ips",{method:"POST",body:t}).then(e=>e.json()).then(e=>{let t='<div class="space-y-2">';if(e.success.length>0&&(t+=`<div class="text-green-600 dark:text-green-400"><strong>Successfully assigned ${e.success.length} IP(s):</strong><ul class="list-disc list-inside mt-2">`,e.success.forEach(e=>{t+=`<li>${e.ip}</li>`}),t+="</ul></div>"),e.failed.length>0&&(t+=`<div class="text-red-600 dark:text-red-400"><strong>Failed ${e.failed.length} assignment(s):</strong><ul class="list-disc list-inside mt-2">`,e.failed.forEach(e=>{let s=e.ip?` (${e.ip})`:"";t+=`<li>IP ID ${e.ip_id}${s}: ${e.reason}</li>`}),t+="</ul></div>"),t+="</div>",s.innerHTML=t,e.success.length>0){let n=document.getElementById("bulk-subnet-select");n.value&&n.dispatchEvent(new Event("change"))}}).catch(e=>{s.innerHTML=`<p class="text-red-600">Error: ${e.message}</p>`})}),document.getElementById("bulk-create-devices-form")?.addEventListener("submit",function(e){e.preventDefault();let t=new FormData(this),s=document.getElementById("create-devices-result");s.classList.remove("hidden"),s.innerHTML='<p class="text-blue-500">Processing...</p>',fetch("/bulk/create_devices",{method:"POST",body:t}).then(e=>e.json()).then(e=>{let t='<div class="space-y-2">';e.success.length>0&&(t+=`<div class="text-green-600 dark:text-green-400"><strong>Successfully created ${e.success.length} device(s):</strong><ul class="list-disc list-inside mt-2">`,e.success.forEach(e=>{t+=`<li>${e.name}</li>`}),t+="</ul></div>"),e.failed.length>0&&(t+=`<div class="text-red-600 dark:text-red-400"><strong>Failed ${e.failed.length} creation(s):</strong><ul class="list-disc list-inside mt-2">`,e.failed.forEach(e=>{t+=`<li>${e.name}: ${e.reason}</li>`}),t+="</ul></div>"),t+="</div>",s.innerHTML=t,e.success.length>0&&setTimeout(()=>window.location.reload(),2e3)}).catch(e=>{s.innerHTML=`<p class="text-red-600">Error: ${e.message}</p>`})}),document.getElementById("bulk-assign-tags-form")?.addEventListener("submit",function(e){e.preventDefault();let t=new FormData(this),s=document.getElementById("assign-tags-result");s.classList.remove("hidden"),s.innerHTML='<p class="text-blue-500">Processing...</p>',fetch("/bulk/assign_tags",{method:"POST",body:t}).then(e=>e.json()).then(e=>{let t='<div class="space-y-2">';e.success.length>0&&(t+=`<div class="text-green-600 dark:text-green-400"><strong>Successfully assigned ${e.success.length} tag(s):</strong><ul class="list-disc list-inside mt-2">`,e.success.forEach(e=>{t+=`<li>${e.device_name}: ${e.tag_name}</li>`}),t+="</ul></div>"),e.failed.length>0&&(t+=`<div class="text-red-600 dark:text-red-400"><strong>Failed ${e.failed.length} assignment(s):</strong><ul class="list-disc list-inside mt-2">`,e.failed.forEach(e=>{t+=`<li>Device ID ${e.device_id}, Tag ID ${e.tag_id}: ${e.reason}</li>`}),t+="</ul></div>"),t+="</div>",s.innerHTML=t}).catch(e=>{s.innerHTML=`<p class="text-red-600">Error: ${e.message}</p>`})})});
|
||||
@@ -1,326 +0,0 @@
|
||||
// Custom Fields Management JavaScript
|
||||
|
||||
// Get initial tab from URL parameter or default to 'device'
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
let currentTab = urlParams.get('tab') || 'device';
|
||||
if (currentTab !== 'device' && currentTab !== 'subnet') {
|
||||
currentTab = 'device';
|
||||
}
|
||||
|
||||
// Switch to the correct tab on page load
|
||||
if (currentTab === 'subnet') {
|
||||
switchTab('subnet');
|
||||
} else {
|
||||
// Ensure device tab is active on load
|
||||
switchTab('device');
|
||||
}
|
||||
|
||||
// Function to get current active tab
|
||||
function getCurrentTab() {
|
||||
return currentTab;
|
||||
}
|
||||
|
||||
let fieldData = {};
|
||||
|
||||
// Tab switching
|
||||
function switchTab(entityType) {
|
||||
currentTab = entityType;
|
||||
|
||||
// Update tab buttons
|
||||
document.getElementById('tab-device').classList.remove('border-gray-600', 'text-gray-900', 'dark:text-gray-100');
|
||||
document.getElementById('tab-device').classList.add('border-transparent', 'text-gray-500');
|
||||
document.getElementById('tab-subnet').classList.remove('border-gray-600', 'text-gray-900', 'dark:text-gray-100');
|
||||
document.getElementById('tab-subnet').classList.add('border-transparent', 'text-gray-500');
|
||||
|
||||
if (entityType === 'device') {
|
||||
document.getElementById('tab-device').classList.remove('border-transparent', 'text-gray-500');
|
||||
document.getElementById('tab-device').classList.add('border-gray-600', 'text-gray-900', 'dark:text-gray-100');
|
||||
document.getElementById('device-fields-tab').classList.remove('hidden');
|
||||
document.getElementById('subnet-fields-tab').classList.add('hidden');
|
||||
} else {
|
||||
document.getElementById('tab-subnet').classList.remove('border-transparent', 'text-gray-500');
|
||||
document.getElementById('tab-subnet').classList.add('border-gray-600', 'text-gray-900', 'dark:text-gray-100');
|
||||
document.getElementById('device-fields-tab').classList.add('hidden');
|
||||
document.getElementById('subnet-fields-tab').classList.remove('hidden');
|
||||
}
|
||||
|
||||
// Update URL without reloading page
|
||||
const newUrl = new URL(window.location);
|
||||
newUrl.searchParams.set('tab', entityType);
|
||||
window.history.pushState({}, '', newUrl);
|
||||
}
|
||||
|
||||
// Show add field modal
|
||||
function showAddFieldModal(entityType) {
|
||||
// Determine the target entity type - prioritize explicit parameter, then read from DOM
|
||||
let targetEntityType = entityType;
|
||||
|
||||
if (!targetEntityType) {
|
||||
// Read from active tab button - check which tab has the active styling
|
||||
const deviceTab = document.getElementById('tab-device');
|
||||
const subnetTab = document.getElementById('tab-subnet');
|
||||
|
||||
if (deviceTab && deviceTab.classList.contains('border-gray-600')) {
|
||||
targetEntityType = 'device';
|
||||
} else if (subnetTab && subnetTab.classList.contains('border-gray-600')) {
|
||||
targetEntityType = 'subnet';
|
||||
} else {
|
||||
// Fallback to currentTab variable
|
||||
targetEntityType = currentTab || 'device';
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure targetEntityType is valid
|
||||
if (targetEntityType !== 'device' && targetEntityType !== 'subnet') {
|
||||
targetEntityType = 'device';
|
||||
}
|
||||
|
||||
// Ensure we're on the correct tab
|
||||
if (targetEntityType !== currentTab) {
|
||||
switchTab(targetEntityType);
|
||||
}
|
||||
|
||||
document.getElementById('modal-title').textContent = 'Add Custom Field';
|
||||
document.getElementById('form-action').value = 'add_field';
|
||||
document.getElementById('form-field-id').value = '';
|
||||
|
||||
// Always set entity_type explicitly - double check it's set
|
||||
const entityTypeInput = document.getElementById('form-entity-type');
|
||||
entityTypeInput.value = targetEntityType;
|
||||
|
||||
// Debug: log to verify
|
||||
console.log('Opening modal for entity type:', targetEntityType, 'currentTab:', currentTab, 'input value:', entityTypeInput.value);
|
||||
|
||||
// Reset form
|
||||
document.getElementById('field-name').value = '';
|
||||
document.getElementById('field-key').value = '';
|
||||
document.getElementById('field-type').value = 'text';
|
||||
document.getElementById('field-required').checked = false;
|
||||
document.getElementById('field-default-value').value = '';
|
||||
document.getElementById('field-help-text').value = '';
|
||||
document.getElementById('field-display-order').value = '0';
|
||||
|
||||
// Reset validation fields
|
||||
document.getElementById('field-min-length').value = '';
|
||||
document.getElementById('field-max-length').value = '';
|
||||
document.getElementById('field-regex-pattern').value = '';
|
||||
document.getElementById('field-min-value').value = '';
|
||||
document.getElementById('field-max-value').value = '';
|
||||
document.getElementById('field-select-options').value = '';
|
||||
|
||||
updateFieldTypeOptions();
|
||||
document.getElementById('field-modal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
// Close field modal
|
||||
function closeFieldModal() {
|
||||
document.getElementById('field-modal').classList.add('hidden');
|
||||
}
|
||||
|
||||
// Update field type options visibility
|
||||
function updateFieldTypeOptions() {
|
||||
const fieldType = document.getElementById('field-type').value;
|
||||
|
||||
// Hide all validation sections
|
||||
document.getElementById('text-validation').classList.add('hidden');
|
||||
document.getElementById('number-validation').classList.add('hidden');
|
||||
document.getElementById('select-validation').classList.add('hidden');
|
||||
|
||||
// Show relevant validation section
|
||||
if (fieldType === 'text' || fieldType === 'textarea') {
|
||||
document.getElementById('text-validation').classList.remove('hidden');
|
||||
} else if (fieldType === 'number' || fieldType === 'decimal') {
|
||||
document.getElementById('number-validation').classList.remove('hidden');
|
||||
} else if (fieldType === 'select') {
|
||||
document.getElementById('select-validation').classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-generate field key from name
|
||||
function generateFieldKey(name) {
|
||||
return name.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '_')
|
||||
.replace(/^_+|_+$/g, '');
|
||||
}
|
||||
|
||||
// Edit field
|
||||
function editField(fieldId, entityType) {
|
||||
// Get field data from embedded JSON
|
||||
const fieldsDataElement = document.getElementById('fields-data');
|
||||
if (!fieldsDataElement) {
|
||||
console.error('Fields data not found');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const fieldsData = JSON.parse(fieldsDataElement.textContent);
|
||||
const fields = fieldsData[entityType] || [];
|
||||
const field = fields.find(f => f.id === fieldId);
|
||||
|
||||
if (field) {
|
||||
populateEditForm(field, entityType);
|
||||
} else {
|
||||
console.error('Field not found:', fieldId, entityType);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing fields data:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function populateEditForm(field, entityType) {
|
||||
document.getElementById('modal-title').textContent = 'Edit Custom Field';
|
||||
document.getElementById('form-action').value = 'edit_field';
|
||||
document.getElementById('form-field-id').value = field.id;
|
||||
document.getElementById('form-entity-type').value = entityType;
|
||||
|
||||
document.getElementById('field-name').value = field.name || '';
|
||||
document.getElementById('field-key').value = field.field_key || '';
|
||||
document.getElementById('field-type').value = field.field_type || 'text';
|
||||
document.getElementById('field-required').checked = field.required || false;
|
||||
document.getElementById('field-default-value').value = field.default_value || '';
|
||||
document.getElementById('field-help-text').value = field.help_text || '';
|
||||
document.getElementById('field-display-order').value = field.display_order || 0;
|
||||
|
||||
// Parse validation rules
|
||||
let validationRules = {};
|
||||
if (field.validation_rules) {
|
||||
if (typeof field.validation_rules === 'string') {
|
||||
try {
|
||||
validationRules = JSON.parse(field.validation_rules);
|
||||
} catch (e) {
|
||||
validationRules = {};
|
||||
}
|
||||
} else {
|
||||
validationRules = field.validation_rules;
|
||||
}
|
||||
}
|
||||
|
||||
// Populate validation fields
|
||||
document.getElementById('field-min-length').value = validationRules.min_length || '';
|
||||
document.getElementById('field-max-length').value = validationRules.max_length || '';
|
||||
document.getElementById('field-regex-pattern').value = validationRules.regex_pattern || '';
|
||||
document.getElementById('field-min-value').value = validationRules.min_value || '';
|
||||
document.getElementById('field-max-value').value = validationRules.max_value || '';
|
||||
|
||||
if (validationRules.select_options) {
|
||||
document.getElementById('field-select-options').value = validationRules.select_options.join(', ');
|
||||
} else {
|
||||
document.getElementById('field-select-options').value = '';
|
||||
}
|
||||
|
||||
updateFieldTypeOptions();
|
||||
document.getElementById('field-modal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
// Move field up/down
|
||||
function moveField(entityType, fieldId, direction) {
|
||||
// Get all fields for this entity type
|
||||
const tbody = document.getElementById(`${entityType}-fields-tbody`);
|
||||
const rows = Array.from(tbody.querySelectorAll('tr'));
|
||||
const currentIndex = rows.findIndex(row => row.dataset.fieldId == fieldId);
|
||||
|
||||
if (currentIndex === -1) return;
|
||||
|
||||
let targetIndex;
|
||||
if (direction === 'up' && currentIndex > 0) {
|
||||
targetIndex = currentIndex - 1;
|
||||
} else if (direction === 'down' && currentIndex < rows.length - 1) {
|
||||
targetIndex = currentIndex + 1;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Swap rows
|
||||
const currentRow = rows[currentIndex];
|
||||
const targetRow = rows[targetIndex];
|
||||
tbody.insertBefore(currentRow, direction === 'up' ? targetRow : targetRow.nextSibling);
|
||||
|
||||
// Update display orders and submit
|
||||
const fieldOrders = {};
|
||||
Array.from(tbody.querySelectorAll('tr')).forEach((row, index) => {
|
||||
fieldOrders[row.dataset.fieldId] = index;
|
||||
});
|
||||
|
||||
// Submit reorder
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '/custom_fields';
|
||||
|
||||
const actionInput = document.createElement('input');
|
||||
actionInput.type = 'hidden';
|
||||
actionInput.name = 'action';
|
||||
actionInput.value = 'reorder';
|
||||
form.appendChild(actionInput);
|
||||
|
||||
const entityTypeInput = document.createElement('input');
|
||||
entityTypeInput.type = 'hidden';
|
||||
entityTypeInput.name = 'entity_type';
|
||||
entityTypeInput.value = entityType;
|
||||
form.appendChild(entityTypeInput);
|
||||
|
||||
const ordersInput = document.createElement('input');
|
||||
ordersInput.type = 'hidden';
|
||||
ordersInput.name = 'field_orders';
|
||||
ordersInput.value = JSON.stringify(fieldOrders);
|
||||
form.appendChild(ordersInput);
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Auto-generate field key from name
|
||||
const nameInput = document.getElementById('field-name');
|
||||
const keyInput = document.getElementById('field-key');
|
||||
|
||||
if (nameInput && keyInput) {
|
||||
nameInput.addEventListener('input', function() {
|
||||
// Only auto-generate if key is empty or matches previous generated value
|
||||
if (!keyInput.value || keyInput.dataset.autoGenerated === 'true') {
|
||||
keyInput.value = generateFieldKey(this.value);
|
||||
keyInput.dataset.autoGenerated = 'true';
|
||||
}
|
||||
});
|
||||
|
||||
keyInput.addEventListener('input', function() {
|
||||
// Mark as manually edited
|
||||
this.dataset.autoGenerated = 'false';
|
||||
});
|
||||
}
|
||||
|
||||
// Update field type options when type changes
|
||||
const fieldTypeSelect = document.getElementById('field-type');
|
||||
if (fieldTypeSelect) {
|
||||
fieldTypeSelect.addEventListener('change', updateFieldTypeOptions);
|
||||
}
|
||||
|
||||
// Ensure entity_type is set correctly before form submission
|
||||
const fieldForm = document.getElementById('field-form');
|
||||
if (fieldForm) {
|
||||
fieldForm.addEventListener('submit', function(e) {
|
||||
const entityTypeInput = document.getElementById('form-entity-type');
|
||||
// Always ensure entity_type is set to currentTab
|
||||
// This handles cases where the modal was opened without explicitly setting it
|
||||
if (!entityTypeInput.value || entityTypeInput.value.trim() === '') {
|
||||
entityTypeInput.value = currentTab;
|
||||
console.log('Entity type was empty, setting to:', currentTab);
|
||||
}
|
||||
// Double-check it's a valid value
|
||||
if (entityTypeInput.value !== 'device' && entityTypeInput.value !== 'subnet') {
|
||||
entityTypeInput.value = currentTab;
|
||||
console.log('Entity type was invalid, setting to currentTab:', currentTab);
|
||||
}
|
||||
console.log('Submitting form with entity_type:', entityTypeInput.value, 'currentTab:', currentTab);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Close modal when clicking outside
|
||||
window.onclick = function(event) {
|
||||
const modal = document.getElementById('field-modal');
|
||||
if (event.target === modal) {
|
||||
closeFieldModal();
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
-1
File diff suppressed because one or more lines are too long
@@ -1,61 +0,0 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const siteSelect = document.getElementById('site-select');
|
||||
const subnetSelect = document.getElementById('subnet-select');
|
||||
const ipSelect = document.getElementById('ip-select');
|
||||
const renameBtn = document.querySelector('.rename-btn');
|
||||
const saveBtn = document.querySelector('.save-btn');
|
||||
const cancelBtn = document.querySelector('.cancel-btn');
|
||||
const nameInput = document.querySelector('input[name="new_name"]');
|
||||
const h1 = document.querySelector('h1');
|
||||
siteSelect.addEventListener('change', function() {
|
||||
const selectedSite = this.value;
|
||||
let firstSubnet = null;
|
||||
Array.from(subnetSelect.options).forEach(option => {
|
||||
if (!option.value) return;
|
||||
if (option.getAttribute('data-site') === selectedSite) {
|
||||
option.style.display = '';
|
||||
if (!firstSubnet) firstSubnet = option.value;
|
||||
} else {
|
||||
option.style.display = 'none';
|
||||
}
|
||||
});
|
||||
subnetSelect.value = firstSubnet || '';
|
||||
const event = new Event('change', { bubbles: true });
|
||||
subnetSelect.dispatchEvent(event);
|
||||
});
|
||||
subnetSelect.addEventListener('change', function() {
|
||||
const subnetId = this.value;
|
||||
if (!subnetId) {
|
||||
ipSelect.innerHTML = '<option value="" disabled selected>Select IP</option>';
|
||||
return;
|
||||
}
|
||||
fetch(`/get_available_ips?subnet_id=${subnetId}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
ipSelect.innerHTML = '<option value="" disabled selected>Select IP</option>';
|
||||
data.available_ips.forEach(ip => {
|
||||
const option = document.createElement('option');
|
||||
option.value = ip.id;
|
||||
option.textContent = ip.ip;
|
||||
ipSelect.appendChild(option);
|
||||
});
|
||||
});
|
||||
});
|
||||
if (renameBtn && saveBtn && cancelBtn && nameInput && h1) {
|
||||
renameBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
nameInput.classList.remove('hidden');
|
||||
saveBtn.classList.remove('hidden');
|
||||
cancelBtn.classList.remove('hidden');
|
||||
h1.classList.add('hidden');
|
||||
nameInput.focus();
|
||||
});
|
||||
cancelBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
nameInput.classList.add('hidden');
|
||||
saveBtn.classList.add('hidden');
|
||||
cancelBtn.classList.add('hidden');
|
||||
h1.classList.remove('hidden');
|
||||
});
|
||||
}
|
||||
});
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded",function(){let e=document.getElementById("site-select"),t=document.getElementById("subnet-select"),n=document.getElementById("ip-select"),i=document.querySelector(".rename-btn"),l=document.querySelector(".save-btn"),s=document.querySelector(".cancel-btn"),a=document.querySelector('input[name="new_name"]'),d=document.querySelector("h1");e.addEventListener("change",function(){let e=this.value,n=null;Array.from(t.options).forEach(t=>{t.value&&(t.getAttribute("data-site")===e?(t.style.display="",n||(n=t.value)):t.style.display="none")}),t.value=n||"";let i=new Event("change",{bubbles:!0});t.dispatchEvent(i)}),t.addEventListener("change",function(){let e=this.value;if(!e){n.innerHTML='<option value="" disabled selected>Select IP</option>';return}fetch(`/get_available_ips?subnet_id=${e}`).then(e=>e.json()).then(e=>{n.innerHTML='<option value="" disabled selected>Select IP</option>',e.available_ips.forEach(e=>{let t=document.createElement("option");t.value=e.id,t.textContent=e.ip,n.appendChild(t)})})}),i&&l&&s&&a&&d&&(i.addEventListener("click",function(e){e.preventDefault(),a.classList.remove("hidden"),l.classList.remove("hidden"),s.classList.remove("hidden"),d.classList.add("hidden"),a.focus()}),s.addEventListener("click",function(e){e.preventDefault(),a.classList.add("hidden"),l.classList.add("hidden"),s.classList.add("hidden"),d.classList.remove("hidden")}))});
|
||||
@@ -1,157 +0,0 @@
|
||||
// Font Awesome icon search functionality
|
||||
// Common Font Awesome icons for device types
|
||||
const fontAwesomeIcons = [
|
||||
// Network & Server
|
||||
'fa-server', 'fa-router', 'fa-network-wired', 'fa-switch', 'fa-hub', 'fa-ethernet',
|
||||
'fa-satellite-dish', 'fa-broadcast-tower', 'fa-tower-cell', 'fa-wifi', 'fa-network',
|
||||
'fa-project-diagram', 'fa-sitemap', 'fa-diagram-project', 'fa-cloud',
|
||||
|
||||
// Security
|
||||
'fa-shield-halved', 'fa-shield', 'fa-shield-alt', 'fa-firewall', 'fa-lock', 'fa-unlock',
|
||||
'fa-key', 'fa-fingerprint', 'fa-user-shield', 'fa-user-lock',
|
||||
|
||||
// Hardware
|
||||
'fa-print', 'fa-boxes-stacked', 'fa-database', 'fa-hard-drive', 'fa-memory', 'fa-microchip',
|
||||
'fa-cpu', 'fa-usb', 'fa-fan', 'fa-battery-full', 'fa-power-off', 'fa-plug', 'fa-bolt',
|
||||
'fa-lightbulb', 'fa-monitor', 'fa-display', 'fa-tv', 'fa-camera', 'fa-video',
|
||||
|
||||
// Computing
|
||||
'fa-laptop', 'fa-desktop', 'fa-tablet', 'fa-mobile-alt', 'fa-phone', 'fa-keyboard',
|
||||
'fa-mouse', 'fa-microphone', 'fa-headphones', 'fa-speaker',
|
||||
|
||||
// Storage & Files
|
||||
'fa-box', 'fa-package', 'fa-archive', 'fa-folder', 'fa-file', 'fa-hdd', 'fa-ssd',
|
||||
'fa-floppy-disk', 'fa-disk', 'fa-save', 'fa-folder-open', 'fa-folder-plus',
|
||||
|
||||
// Data & Analytics
|
||||
'fa-chart-line', 'fa-chart-bar', 'fa-chart-pie', 'fa-graph', 'fa-analytics',
|
||||
'fa-database', 'fa-file-database', 'fa-file-chart-line', 'fa-file-chart-pie',
|
||||
|
||||
// Location & Infrastructure
|
||||
'fa-globe', 'fa-earth', 'fa-map', 'fa-location', 'fa-map-marker', 'fa-building',
|
||||
'fa-warehouse', 'fa-home', 'fa-office', 'fa-industry',
|
||||
|
||||
// Tools & Utilities
|
||||
'fa-robot', 'fa-cog', 'fa-gear', 'fa-wrench', 'fa-tools', 'fa-question',
|
||||
'fa-code', 'fa-terminal', 'fa-console', 'fa-bug', 'fa-bug-slash',
|
||||
|
||||
// Identification
|
||||
'fa-id-card', 'fa-credit-card', 'fa-qrcode', 'fa-barcode', 'fa-rfid',
|
||||
|
||||
// Transport & Logistics
|
||||
'fa-truck', 'fa-shipping-fast', 'fa-conveyor-belt', 'fa-pallet', 'fa-dolly',
|
||||
'fa-cube', 'fa-cubes', 'fa-layer-group', 'fa-stack',
|
||||
|
||||
// UI & Display
|
||||
'fa-th', 'fa-th-large', 'fa-th-list', 'fa-list', 'fa-list-ul', 'fa-list-ol',
|
||||
'fa-table', 'fa-columns', 'fa-grid', 'fa-window-maximize', 'fa-window-restore',
|
||||
'fa-window-minimize', 'fa-window-close', 'fa-expand', 'fa-compress',
|
||||
|
||||
// Actions
|
||||
'fa-sync', 'fa-sync-alt', 'fa-redo', 'fa-undo', 'fa-refresh', 'fa-download',
|
||||
'fa-upload', 'fa-exchange-alt', 'fa-share', 'fa-link', 'fa-unlink', 'fa-chain',
|
||||
'fa-chain-broken', 'fa-arrows-alt', 'fa-arrows', 'fa-move',
|
||||
|
||||
// Time & Calendar
|
||||
'fa-clock', 'fa-hourglass', 'fa-stopwatch', 'fa-timer', 'fa-calendar',
|
||||
'fa-calendar-alt', 'fa-calendar-check', 'fa-calendar-times', 'fa-history',
|
||||
|
||||
// Media
|
||||
'fa-play', 'fa-pause', 'fa-stop', 'fa-step-backward', 'fa-step-forward',
|
||||
'fa-fast-backward', 'fa-fast-forward', 'fa-eject', 'fa-record-vinyl',
|
||||
'fa-compact-disc', 'fa-cd', 'fa-dvd',
|
||||
|
||||
// Users
|
||||
'fa-user-shield', 'fa-user-lock', 'fa-user-secret', 'fa-user-cog', 'fa-user-gear',
|
||||
'fa-user-tie', 'fa-user-ninja', 'fa-users', 'fa-users-cog', 'fa-user-group',
|
||||
'fa-user-friends', 'fa-user-plus', 'fa-user-minus', 'fa-user-times', 'fa-user-check',
|
||||
'fa-user-xmark', 'fa-user-slash'
|
||||
];
|
||||
|
||||
function initIconSearch() {
|
||||
const iconInputs = document.querySelectorAll('.icon-search-input');
|
||||
|
||||
iconInputs.forEach(input => {
|
||||
const container = input.closest('.icon-search-container');
|
||||
const preview = container.querySelector('.icon-preview');
|
||||
const suggestions = container.querySelector('.icon-suggestions');
|
||||
|
||||
if (!preview || !suggestions) return;
|
||||
|
||||
// Initialize preview if input already has a value
|
||||
if (input.value && input.value.trim()) {
|
||||
const iconClass = input.value.trim().startsWith('fa-') ? input.value.trim() : `fa-${input.value.trim()}`;
|
||||
preview.innerHTML = `<i class="fas ${iconClass}"></i>`;
|
||||
preview.classList.remove('hidden');
|
||||
}
|
||||
|
||||
input.addEventListener('input', (e) => {
|
||||
const query = e.target.value.toLowerCase().trim();
|
||||
|
||||
// Update preview
|
||||
if (query) {
|
||||
const iconClass = query.startsWith('fa-') ? query : `fa-${query}`;
|
||||
preview.innerHTML = `<i class="fas ${iconClass}"></i>`;
|
||||
preview.classList.remove('hidden');
|
||||
} else {
|
||||
preview.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Filter and display suggestions
|
||||
if (query.length > 0) {
|
||||
const filtered = fontAwesomeIcons.filter(icon =>
|
||||
icon.includes(query) || icon.replace('fa-', '').includes(query)
|
||||
).slice(0, 10); // Show top 10 matches
|
||||
|
||||
if (filtered.length > 0) {
|
||||
suggestions.innerHTML = filtered.map(icon => `
|
||||
<div class="icon-suggestion-item" data-icon="${icon}">
|
||||
<i class="fas ${icon}"></i>
|
||||
<span>${icon}</span>
|
||||
</div>
|
||||
`).join('');
|
||||
suggestions.classList.remove('hidden');
|
||||
|
||||
// Add click handlers
|
||||
suggestions.querySelectorAll('.icon-suggestion-item').forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
input.value = item.dataset.icon;
|
||||
preview.innerHTML = `<i class="fas ${item.dataset.icon}"></i>`;
|
||||
preview.classList.remove('hidden');
|
||||
suggestions.classList.add('hidden');
|
||||
});
|
||||
});
|
||||
} else {
|
||||
suggestions.classList.add('hidden');
|
||||
}
|
||||
} else {
|
||||
suggestions.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// Hide suggestions when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!container.contains(e.target)) {
|
||||
suggestions.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// Update preview on blur if value exists
|
||||
input.addEventListener('blur', () => {
|
||||
const value = input.value.trim();
|
||||
if (value && preview) {
|
||||
const iconClass = value.startsWith('fa-') ? value : `fa-${value}`;
|
||||
preview.innerHTML = `<i class="fas ${iconClass}"></i>`;
|
||||
preview.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initIconSearch);
|
||||
} else {
|
||||
initIconSearch();
|
||||
}
|
||||
|
||||
Vendored
-6
@@ -1,6 +0,0 @@
|
||||
const fontAwesomeIcons=["fa-server","fa-router","fa-network-wired","fa-switch","fa-hub","fa-ethernet","fa-satellite-dish","fa-broadcast-tower","fa-tower-cell","fa-wifi","fa-network","fa-project-diagram","fa-sitemap","fa-diagram-project","fa-cloud","fa-shield-halved","fa-shield","fa-shield-alt","fa-firewall","fa-lock","fa-unlock","fa-key","fa-fingerprint","fa-user-shield","fa-user-lock","fa-print","fa-boxes-stacked","fa-database","fa-hard-drive","fa-memory","fa-microchip","fa-cpu","fa-usb","fa-fan","fa-battery-full","fa-power-off","fa-plug","fa-bolt","fa-lightbulb","fa-monitor","fa-display","fa-tv","fa-camera","fa-video","fa-laptop","fa-desktop","fa-tablet","fa-mobile-alt","fa-phone","fa-keyboard","fa-mouse","fa-microphone","fa-headphones","fa-speaker","fa-box","fa-package","fa-archive","fa-folder","fa-file","fa-hdd","fa-ssd","fa-floppy-disk","fa-disk","fa-save","fa-folder-open","fa-folder-plus","fa-chart-line","fa-chart-bar","fa-chart-pie","fa-graph","fa-analytics","fa-database","fa-file-database","fa-file-chart-line","fa-file-chart-pie","fa-globe","fa-earth","fa-map","fa-location","fa-map-marker","fa-building","fa-warehouse","fa-home","fa-office","fa-industry","fa-robot","fa-cog","fa-gear","fa-wrench","fa-tools","fa-question","fa-code","fa-terminal","fa-console","fa-bug","fa-bug-slash","fa-id-card","fa-credit-card","fa-qrcode","fa-barcode","fa-rfid","fa-truck","fa-shipping-fast","fa-conveyor-belt","fa-pallet","fa-dolly","fa-cube","fa-cubes","fa-layer-group","fa-stack","fa-th","fa-th-large","fa-th-list","fa-list","fa-list-ul","fa-list-ol","fa-table","fa-columns","fa-grid","fa-window-maximize","fa-window-restore","fa-window-minimize","fa-window-close","fa-expand","fa-compress","fa-sync","fa-sync-alt","fa-redo","fa-undo","fa-refresh","fa-download","fa-upload","fa-exchange-alt","fa-share","fa-link","fa-unlink","fa-chain","fa-chain-broken","fa-arrows-alt","fa-arrows","fa-move","fa-clock","fa-hourglass","fa-stopwatch","fa-timer","fa-calendar","fa-calendar-alt","fa-calendar-check","fa-calendar-times","fa-history","fa-play","fa-pause","fa-stop","fa-step-backward","fa-step-forward","fa-fast-backward","fa-fast-forward","fa-eject","fa-record-vinyl","fa-compact-disc","fa-cd","fa-dvd","fa-user-shield","fa-user-lock","fa-user-secret","fa-user-cog","fa-user-gear","fa-user-tie","fa-user-ninja","fa-users","fa-users-cog","fa-user-group","fa-user-friends","fa-user-plus","fa-user-minus","fa-user-times","fa-user-check","fa-user-xmark","fa-user-slash"];function initIconSearch(){let a=document.querySelectorAll(".icon-search-input");a.forEach(a=>{let e=a.closest(".icon-search-container"),f=e.querySelector(".icon-preview"),s=e.querySelector(".icon-suggestions");if(f&&s){if(a.value&&a.value.trim()){let i=a.value.trim().startsWith("fa-")?a.value.trim():`fa-${a.value.trim()}`;f.innerHTML=`<i class="fas ${i}"></i>`,f.classList.remove("hidden")}a.addEventListener("input",e=>{let i=e.target.value.toLowerCase().trim();if(i){let r=i.startsWith("fa-")?i:`fa-${i}`;f.innerHTML=`<i class="fas ${r}"></i>`,f.classList.remove("hidden")}else f.classList.add("hidden");if(i.length>0){let t=fontAwesomeIcons.filter(a=>a.includes(i)||a.replace("fa-","").includes(i)).slice(0,10);t.length>0?(s.innerHTML=t.map(a=>`
|
||||
<div class="icon-suggestion-item" data-icon="${a}">
|
||||
<i class="fas ${a}"></i>
|
||||
<span>${a}</span>
|
||||
</div>
|
||||
`).join(""),s.classList.remove("hidden"),s.querySelectorAll(".icon-suggestion-item").forEach(e=>{e.addEventListener("click",()=>{a.value=e.dataset.icon,f.innerHTML=`<i class="fas ${e.dataset.icon}"></i>`,f.classList.remove("hidden"),s.classList.add("hidden")})})):s.classList.add("hidden")}else s.classList.add("hidden")}),document.addEventListener("click",a=>{e.contains(a.target)||s.classList.add("hidden")}),a.addEventListener("blur",()=>{let e=a.value.trim();if(e&&f){let s=e.startsWith("fa-")?e:`fa-${e}`;f.innerHTML=`<i class="fas ${s}"></i>`,f.classList.remove("hidden")}})}})}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",initIconSearch):initIconSearch();
|
||||
@@ -1,73 +0,0 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// Tag filter functionality
|
||||
const tagFilter = document.getElementById('tag-filter');
|
||||
if (tagFilter) {
|
||||
tagFilter.addEventListener('change', function() {
|
||||
const selectedTag = this.value;
|
||||
if (selectedTag) {
|
||||
window.location.href = '/devices?tag=' + encodeURIComponent(selectedTag);
|
||||
} else {
|
||||
window.location.href = '/devices';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Expand/collapse site groups
|
||||
document.querySelectorAll('.site-header').forEach(header => {
|
||||
header.addEventListener('click', function(e) {
|
||||
const deviceList = this.closest('.site-group').querySelector('.device-list');
|
||||
const icon = this.querySelector('.expand-btn i');
|
||||
if (deviceList.classList.contains('hidden')) {
|
||||
deviceList.classList.remove('hidden');
|
||||
icon.classList.remove('fa-chevron-down');
|
||||
icon.classList.add('fa-chevron-up');
|
||||
} else {
|
||||
deviceList.classList.add('hidden');
|
||||
icon.classList.remove('fa-chevron-up');
|
||||
icon.classList.add('fa-chevron-down');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Scroll to Top Button
|
||||
const scrollToTopButton = document.createElement('button');
|
||||
scrollToTopButton.innerHTML = '<i class="fas fa-arrow-up"></i>';
|
||||
scrollToTopButton.style.fontSize = '26px';
|
||||
scrollToTopButton.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';
|
||||
scrollToTopButton.style.width = '60px';
|
||||
scrollToTopButton.style.height = '60px';
|
||||
scrollToTopButton.style.borderRadius = '50%';
|
||||
document.body.appendChild(scrollToTopButton);
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes bob {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
}
|
||||
|
||||
.bobbing {
|
||||
animation: bob 1.5s infinite;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
scrollToTopButton.classList.add('bobbing');
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
if (window.scrollY > 200) {
|
||||
scrollToTopButton.classList.remove('hidden');
|
||||
} else {
|
||||
scrollToTopButton.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
scrollToTopButton.addEventListener('click', () => {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
});
|
||||
});
|
||||
Vendored
-14
@@ -1,14 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded",function(){let e=document.getElementById("tag-filter");e&&e.addEventListener("change",function(){let e=this.value;e?window.location.href="/devices?tag="+encodeURIComponent(e):window.location.href="/devices"}),document.querySelectorAll(".site-header").forEach(e=>{e.addEventListener("click",function(e){let t=this.closest(".site-group").querySelector(".device-list"),s=this.querySelector(".expand-btn i");t.classList.contains("hidden")?(t.classList.remove("hidden"),s.classList.remove("fa-chevron-down"),s.classList.add("fa-chevron-up")):(t.classList.add("hidden"),s.classList.remove("fa-chevron-up"),s.classList.add("fa-chevron-down"))})});let t=document.createElement("button");t.innerHTML='<i class="fas fa-arrow-up"></i>',t.style.fontSize="26px",t.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",t.style.width="60px",t.style.height="60px",t.style.borderRadius="50%",document.body.appendChild(t);let s=document.createElement("style");s.textContent=`
|
||||
@keyframes bob {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
}
|
||||
|
||||
.bobbing {
|
||||
animation: bob 1.5s infinite;
|
||||
}
|
||||
`,document.head.appendChild(s),t.classList.add("bobbing"),window.addEventListener("scroll",()=>{window.scrollY>200?t.classList.remove("hidden"):t.classList.add("hidden")}),t.addEventListener("click",()=>{window.scrollTo({top:0,behavior:"smooth"})})});
|
||||
@@ -1,7 +0,0 @@
|
||||
document.querySelectorAll('.export-csv-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
const subnetId = this.getAttribute('data-subnet-id');
|
||||
window.location.href = `/subnet/${subnetId}/export_csv`;
|
||||
});
|
||||
});
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
document.querySelectorAll(".export-csv-btn").forEach(t=>{t.addEventListener("click",function(t){t.stopPropagation();let e=this.getAttribute("data-subnet-id");window.location.href=`/subnet/${e}/export_csv`})});
|
||||
@@ -1,96 +0,0 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const navToggle = document.getElementById('nav-toggle');
|
||||
const mobileNav = document.getElementById('mobile-nav');
|
||||
const searchModal = document.getElementById('search-modal');
|
||||
const searchModalOpen = document.getElementById('search-modal-open');
|
||||
const searchModalOpenMobile = document.getElementById('search-modal-open-mobile');
|
||||
const searchModalClose = document.getElementById('search-modal-close');
|
||||
const searchModalBackdrop = document.getElementById('search-modal-backdrop');
|
||||
const searchModalInput = document.getElementById('search-modal-input');
|
||||
|
||||
if (navToggle && mobileNav) {
|
||||
navToggle.addEventListener('click', function() {
|
||||
mobileNav.classList.toggle('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
function openSearchModal() {
|
||||
if (!searchModal) {
|
||||
return;
|
||||
}
|
||||
|
||||
searchModal.classList.remove('hidden');
|
||||
searchModal.classList.add('flex');
|
||||
document.body.classList.add('overflow-hidden');
|
||||
|
||||
if (mobileNav) {
|
||||
mobileNav.classList.add('hidden');
|
||||
}
|
||||
|
||||
setTimeout(function() {
|
||||
if (searchModalInput) {
|
||||
searchModalInput.focus();
|
||||
searchModalInput.select();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function closeSearchModal() {
|
||||
if (!searchModal) {
|
||||
return;
|
||||
}
|
||||
|
||||
searchModal.classList.add('hidden');
|
||||
searchModal.classList.remove('flex');
|
||||
document.body.classList.remove('overflow-hidden');
|
||||
}
|
||||
|
||||
if (searchModalOpen) {
|
||||
searchModalOpen.addEventListener('click', openSearchModal);
|
||||
}
|
||||
|
||||
if (searchModalOpenMobile) {
|
||||
searchModalOpenMobile.addEventListener('click', openSearchModal);
|
||||
}
|
||||
|
||||
if (searchModalClose) {
|
||||
searchModalClose.addEventListener('click', closeSearchModal);
|
||||
}
|
||||
|
||||
if (searchModalBackdrop) {
|
||||
searchModalBackdrop.addEventListener('click', closeSearchModal);
|
||||
}
|
||||
|
||||
document.addEventListener('click', function(e) {
|
||||
if (mobileNav && navToggle && !mobileNav.contains(e.target) && !navToggle.contains(e.target)) {
|
||||
mobileNav.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function(e) {
|
||||
const target = e.target;
|
||||
const isEditableTarget = target && (
|
||||
target.tagName === 'INPUT' ||
|
||||
target.tagName === 'TEXTAREA' ||
|
||||
target.tagName === 'SELECT' ||
|
||||
target.isContentEditable
|
||||
);
|
||||
|
||||
if (
|
||||
e.key === '/' &&
|
||||
!e.ctrlKey &&
|
||||
!e.metaKey &&
|
||||
!e.altKey &&
|
||||
searchModal &&
|
||||
!isEditableTarget
|
||||
) {
|
||||
e.preventDefault();
|
||||
openSearchModal();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'Escape') {
|
||||
closeSearchModal();
|
||||
}
|
||||
});
|
||||
});
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded",function(){let e=document.getElementById("nav-toggle"),t=document.getElementById("mobile-nav"),o=document.getElementById("search-modal"),n=document.getElementById("search-modal-open"),d=document.getElementById("search-modal-open-mobile"),c=document.getElementById("search-modal-close"),l=document.getElementById("search-modal-backdrop"),a=document.getElementById("search-modal-input");function s(){o&&(o.classList.remove("hidden"),o.classList.add("flex"),document.body.classList.add("overflow-hidden"),t&&t.classList.add("hidden"),setTimeout(function(){a&&(a.focus(),a.select())},0))}function i(){o&&(o.classList.add("hidden"),o.classList.remove("flex"),document.body.classList.remove("overflow-hidden"))}e&&t&&e.addEventListener("click",function(){t.classList.toggle("hidden")}),n&&n.addEventListener("click",s),d&&d.addEventListener("click",s),c&&c.addEventListener("click",i),l&&l.addEventListener("click",i),document.addEventListener("click",function(o){t&&e&&!t.contains(o.target)&&!e.contains(o.target)&&t.classList.add("hidden")}),document.addEventListener("keydown",function(e){let t=e.target,n=t&&("INPUT"===t.tagName||"TEXTAREA"===t.tagName||"SELECT"===t.tagName||t.isContentEditable);if("/"===e.key&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&o&&!n)return e.preventDefault(),void s();"Escape"===e.key&&i()})});
|
||||
@@ -1,104 +0,0 @@
|
||||
// IP History Modal functionality
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const modal = document.getElementById('ip-history-modal');
|
||||
const closeBtn = document.getElementById('close-ip-history-modal');
|
||||
const content = document.getElementById('ip-history-content');
|
||||
const ipAddressSpan = document.getElementById('modal-ip-address');
|
||||
|
||||
// Open modal when IP is clicked
|
||||
document.querySelectorAll('.ip-history-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const ip = this.getAttribute('data-ip');
|
||||
ipAddressSpan.textContent = ip;
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('flex');
|
||||
loadIPHistory(ip);
|
||||
});
|
||||
});
|
||||
|
||||
// Close modal
|
||||
closeBtn.addEventListener('click', function() {
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex');
|
||||
});
|
||||
|
||||
// Close modal when clicking outside
|
||||
modal.addEventListener('click', function(e) {
|
||||
if (e.target === modal) {
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex');
|
||||
}
|
||||
});
|
||||
|
||||
// Close modal with Escape key
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && !modal.classList.contains('hidden')) {
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex');
|
||||
}
|
||||
});
|
||||
|
||||
function loadIPHistory(ip) {
|
||||
content.innerHTML = '<div class="text-center text-gray-600 dark:text-gray-400">Loading...</div>';
|
||||
|
||||
fetch(`/ip/${encodeURIComponent(ip)}/history`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.history && data.history.length > 0) {
|
||||
displayHistory(data.history);
|
||||
} else {
|
||||
content.innerHTML = '<div class="text-center text-gray-600 dark:text-gray-400">No history found for this IP address.</div>';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading IP history:', error);
|
||||
content.innerHTML = '<div class="text-center text-red-500">Error loading IP history. Please try again.</div>';
|
||||
});
|
||||
}
|
||||
|
||||
function displayHistory(history) {
|
||||
let html = '<div class="space-y-3">';
|
||||
|
||||
history.forEach((entry, index) => {
|
||||
const isAssigned = entry.action === 'assigned';
|
||||
const icon = isAssigned ? 'fa-plus-circle text-green-500' : 'fa-minus-circle text-red-500';
|
||||
const actionText = isAssigned ? 'Assigned' : 'Removed';
|
||||
|
||||
// Format timestamp
|
||||
let timestamp = 'Unknown';
|
||||
if (entry.timestamp) {
|
||||
try {
|
||||
const date = new Date(entry.timestamp);
|
||||
timestamp = date.toLocaleString();
|
||||
} catch (e) {
|
||||
timestamp = entry.timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
html += `
|
||||
<div class="flex items-start gap-3 pb-3 ${index < history.length - 1 ? 'border-b border-gray-400 dark:border-zinc-600' : ''}">
|
||||
<div class="flex-shrink-0 mt-1">
|
||||
<i class="fas ${icon}"></i>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<span class="font-semibold">${actionText}</span>
|
||||
<span class="text-gray-600 dark:text-gray-400">to</span>
|
||||
<span class="font-semibold">${entry.device_name || 'Unknown'}</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||
${entry.subnet_name || 'Unknown'} (${entry.subnet_cidr || 'N/A'})
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-500 mt-1">
|
||||
by ${entry.user_name || 'Unknown'} • ${timestamp}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
content.innerHTML = html;
|
||||
}
|
||||
});
|
||||
|
||||
Vendored
-20
@@ -1,20 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded",function(){let e=document.getElementById("ip-history-modal"),t=document.getElementById("close-ip-history-modal"),s=document.getElementById("ip-history-content"),i=document.getElementById("modal-ip-address");document.querySelectorAll(".ip-history-btn").forEach(t=>{t.addEventListener("click",function(){var t;let a=this.getAttribute("data-ip");i.textContent=a,e.classList.remove("hidden"),e.classList.add("flex"),t=a,s.innerHTML='<div class="text-center text-gray-600 dark:text-gray-400">Loading...</div>',fetch(`/ip/${encodeURIComponent(t)}/history`).then(e=>e.json()).then(e=>{var t;let i;e.history&&e.history.length>0?(t=e.history,i='<div class="space-y-3">',t.forEach((e,s)=>{let a="assigned"===e.action,n="Unknown";if(e.timestamp)try{let d=new Date(e.timestamp);n=d.toLocaleString()}catch(r){n=e.timestamp}i+=`
|
||||
<div class="flex items-start gap-3 pb-3 ${s<t.length-1?"border-b border-gray-400 dark:border-zinc-600":""}">
|
||||
<div class="flex-shrink-0 mt-1">
|
||||
<i class="fas ${a?"fa-plus-circle text-green-500":"fa-minus-circle text-red-500"}"></i>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<span class="font-semibold">${a?"Assigned":"Removed"}</span>
|
||||
<span class="text-gray-600 dark:text-gray-400">to</span>
|
||||
<span class="font-semibold">${e.device_name||"Unknown"}</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||
${e.subnet_name||"Unknown"} (${e.subnet_cidr||"N/A"})
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-500 mt-1">
|
||||
by ${e.user_name||"Unknown"} • ${n}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`}),i+="</div>",s.innerHTML=i):s.innerHTML='<div class="text-center text-gray-600 dark:text-gray-400">No history found for this IP address.</div>'}).catch(e=>{console.error("Error loading IP history:",e),s.innerHTML='<div class="text-center text-red-500">Error loading IP history. Please try again.</div>'})})}),t.addEventListener("click",function(){e.classList.add("hidden"),e.classList.remove("flex")}),e.addEventListener("click",function(t){t.target===e&&(e.classList.add("hidden"),e.classList.remove("flex"))}),document.addEventListener("keydown",function(t){"Escape"!==t.key||e.classList.contains("hidden")||(e.classList.add("hidden"),e.classList.remove("flex"))})});
|
||||
@@ -1,41 +0,0 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Export CSV button
|
||||
const exportBtn = document.getElementById('export-csv');
|
||||
if (exportBtn) {
|
||||
exportBtn.addEventListener('click', function() {
|
||||
const rackId = exportBtn.getAttribute('data-rack-id');
|
||||
if (rackId) {
|
||||
window.location = '/rack/' + rackId + '/export_csv';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Form toggle functionality
|
||||
function showBothAddButtons() {
|
||||
document.getElementById('show-add-device-form').classList.remove('hidden');
|
||||
document.getElementById('show-nonnet-form').classList.remove('hidden');
|
||||
}
|
||||
|
||||
showBothAddButtons();
|
||||
|
||||
document.getElementById('show-nonnet-form').onclick = function() {
|
||||
document.getElementById('nonnet-form').classList.remove('hidden');
|
||||
this.classList.add('hidden');
|
||||
};
|
||||
|
||||
document.getElementById('hide-nonnet-form').onclick = function() {
|
||||
document.getElementById('nonnet-form').classList.add('hidden');
|
||||
showBothAddButtons();
|
||||
};
|
||||
|
||||
document.getElementById('show-add-device-form').onclick = function() {
|
||||
document.getElementById('add-device-form').classList.remove('hidden');
|
||||
this.classList.add('hidden');
|
||||
};
|
||||
|
||||
document.getElementById('hide-add-device-form').onclick = function() {
|
||||
document.getElementById('add-device-form').classList.add('hidden');
|
||||
showBothAddButtons();
|
||||
};
|
||||
});
|
||||
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded",function(){let e=document.getElementById("export-csv");function d(){document.getElementById("show-add-device-form").classList.remove("hidden"),document.getElementById("show-nonnet-form").classList.remove("hidden")}e&&e.addEventListener("click",function(){let d=e.getAttribute("data-rack-id");d&&(window.location="/rack/"+d+"/export_csv")}),d(),document.getElementById("show-nonnet-form").onclick=function(){document.getElementById("nonnet-form").classList.remove("hidden"),this.classList.add("hidden")},document.getElementById("hide-nonnet-form").onclick=function(){document.getElementById("nonnet-form").classList.add("hidden"),d()},document.getElementById("show-add-device-form").onclick=function(){document.getElementById("add-device-form").classList.remove("hidden"),this.classList.add("hidden")},document.getElementById("hide-add-device-form").onclick=function(){document.getElementById("add-device-form").classList.add("hidden"),d()}});
|
||||
@@ -1,34 +0,0 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('.site-header').forEach(header => {
|
||||
header.addEventListener('click', function(e) {
|
||||
if (e.target.closest('button')) return;
|
||||
const subnetList = this.closest('.site-group').querySelector('.subnet-list');
|
||||
const icon = this.querySelector('.expand-btn i');
|
||||
if (subnetList.classList.contains('hidden')) {
|
||||
subnetList.classList.remove('hidden');
|
||||
icon.classList.remove('fa-chevron-down');
|
||||
icon.classList.add('fa-chevron-up');
|
||||
} else {
|
||||
subnetList.classList.add('hidden');
|
||||
icon.classList.remove('fa-chevron-up');
|
||||
icon.classList.add('fa-chevron-down');
|
||||
}
|
||||
});
|
||||
});
|
||||
document.querySelectorAll('.expand-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
const subnetList = this.closest('.site-group').querySelector('.subnet-list');
|
||||
const icon = this.querySelector('i');
|
||||
if (subnetList.classList.contains('hidden')) {
|
||||
subnetList.classList.remove('hidden');
|
||||
icon.classList.remove('fa-chevron-down');
|
||||
icon.classList.add('fa-chevron-up');
|
||||
} else {
|
||||
subnetList.classList.add('hidden');
|
||||
icon.classList.remove('fa-chevron-up');
|
||||
icon.classList.add('fa-chevron-down');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll(".site-header").forEach(e=>{e.addEventListener("click",function(e){if(e.target.closest("button"))return;let s=this.closest(".site-group").querySelector(".subnet-list"),t=this.querySelector(".expand-btn i");s.classList.contains("hidden")?(s.classList.remove("hidden"),t.classList.remove("fa-chevron-down"),t.classList.add("fa-chevron-up")):(s.classList.add("hidden"),t.classList.remove("fa-chevron-up"),t.classList.add("fa-chevron-down"))})}),document.querySelectorAll(".expand-btn").forEach(e=>{e.addEventListener("click",function(e){e.stopPropagation();let s=this.closest(".site-group").querySelector(".subnet-list"),t=this.querySelector("i");s.classList.contains("hidden")?(s.classList.remove("hidden"),t.classList.remove("fa-chevron-down"),t.classList.add("fa-chevron-up")):(s.classList.add("hidden"),t.classList.remove("fa-chevron-up"),t.classList.add("fa-chevron-down"))})})});
|
||||
@@ -1,282 +0,0 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Only target the form on the subnet page, not the header search form
|
||||
// Look for a form that's not in the header (header forms have action="/search")
|
||||
const allForms = document.querySelectorAll('form');
|
||||
let form = null;
|
||||
for (let f of allForms) {
|
||||
if (f.action !== '/search' && f.method === 'POST') {
|
||||
form = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (form) {
|
||||
// Check if search input already exists to prevent duplicates
|
||||
if (!document.querySelector('input[placeholder="Search by IP or Hostname"]')) {
|
||||
form.addEventListener('submit', (event) => {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
const searchInput = document.createElement('input');
|
||||
searchInput.type = 'text';
|
||||
searchInput.placeholder = 'Search by IP or Hostname';
|
||||
searchInput.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';
|
||||
form.insertAdjacentElement('beforebegin', searchInput);
|
||||
|
||||
searchInput.addEventListener('keypress', (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
const searchTerm = searchInput.value.toLowerCase();
|
||||
const rows = document.querySelectorAll('tbody tr');
|
||||
|
||||
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) || descText.includes(searchTerm)) {
|
||||
row.style.backgroundColor = 'rgba(59, 130, 246, 0.5)';
|
||||
row.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
|
||||
setTimeout(() => {
|
||||
row.style.backgroundColor = '';
|
||||
}, 3000);
|
||||
} else {
|
||||
row.style.backgroundColor = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Description toggle functionality
|
||||
const toggleBtn = document.getElementById('toggle-desc');
|
||||
const descCols = document.querySelectorAll('.desc-col');
|
||||
const descHeader = document.getElementById('desc-col-header');
|
||||
let shown = false;
|
||||
if (toggleBtn) {
|
||||
toggleBtn.addEventListener('click', function() {
|
||||
shown = !shown;
|
||||
descCols.forEach(col => col.classList.toggle('hidden', !shown));
|
||||
if (descHeader) descHeader.classList.toggle('hidden', !shown);
|
||||
toggleBtn.textContent = shown ? 'Hide Descriptions' : 'Show Descriptions';
|
||||
});
|
||||
}
|
||||
|
||||
// Scroll to Top Button
|
||||
const scrollToTopButton = document.createElement('button');
|
||||
scrollToTopButton.innerHTML = '<i class="fas fa-arrow-up"></i>';
|
||||
scrollToTopButton.style.fontSize = '26px';
|
||||
scrollToTopButton.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';
|
||||
scrollToTopButton.style.width = '60px';
|
||||
scrollToTopButton.style.height = '60px';
|
||||
scrollToTopButton.style.borderRadius = '50%';
|
||||
document.body.appendChild(scrollToTopButton);
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes bob {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
}
|
||||
|
||||
.bobbing {
|
||||
animation: bob 1.5s infinite;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
scrollToTopButton.classList.add('bobbing');
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
if (window.scrollY > 200) {
|
||||
scrollToTopButton.classList.remove('hidden');
|
||||
} else {
|
||||
scrollToTopButton.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
scrollToTopButton.addEventListener('click', () => {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
});
|
||||
|
||||
// Force scrollbar thumb to render on page load
|
||||
// This fixes the issue where scrollbar thumb is missing on initial page load
|
||||
// The scrollbar only renders its thumb after a scroll event has occurred
|
||||
requestAnimationFrame(() => {
|
||||
const isScrollable = document.documentElement.scrollHeight > document.documentElement.clientHeight;
|
||||
if (isScrollable && window.scrollY === 0) {
|
||||
// Trigger a minimal scroll to force scrollbar rendering, then scroll back
|
||||
window.scrollBy(0, 1);
|
||||
requestAnimationFrame(() => {
|
||||
window.scrollBy(0, -1);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Scroll to IP anchor if present in URL hash
|
||||
if (window.location.hash) {
|
||||
const hash = window.location.hash.substring(1);
|
||||
const element = document.getElementById(hash);
|
||||
if (element) {
|
||||
setTimeout(() => {
|
||||
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
// Highlight the row briefly
|
||||
element.style.backgroundColor = 'rgba(59, 130, 246, 0.5)';
|
||||
setTimeout(() => {
|
||||
element.style.backgroundColor = '';
|
||||
}, 3000);
|
||||
}, 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
-14
@@ -1,14 +0,0 @@
|
||||
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);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
}
|
||||
|
||||
.bobbing {
|
||||
animation: bob 1.5s infinite;
|
||||
}
|
||||
`,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())})})});
|
||||
@@ -1,164 +0,0 @@
|
||||
// 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
@@ -1 +0,0 @@
|
||||
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()})});
|
||||
@@ -1,69 +0,0 @@
|
||||
// Tag Management JavaScript
|
||||
|
||||
function showAddTagModal() {
|
||||
document.getElementById('add-tag-modal').classList.remove('hidden');
|
||||
document.getElementById('add-tag-name').value = '';
|
||||
document.getElementById('add-tag-color').value = '#6B7280';
|
||||
document.getElementById('add-tag-description').value = '';
|
||||
updateColorPreview('add');
|
||||
}
|
||||
|
||||
function closeAddTagModal() {
|
||||
document.getElementById('add-tag-modal').classList.add('hidden');
|
||||
}
|
||||
|
||||
function editTag(tagId, name, color, description) {
|
||||
document.getElementById('edit-tag-id').value = tagId;
|
||||
document.getElementById('edit-tag-name').value = name;
|
||||
document.getElementById('edit-tag-color').value = color;
|
||||
document.getElementById('edit-tag-description').value = description || '';
|
||||
updateColorPreview('edit');
|
||||
document.getElementById('edit-tag-modal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function closeEditTagModal() {
|
||||
document.getElementById('edit-tag-modal').classList.add('hidden');
|
||||
}
|
||||
|
||||
function updateColorPreview(mode) {
|
||||
const colorInput = document.getElementById(`${mode}-tag-color`);
|
||||
const preview = document.getElementById(`${mode}-color-preview`);
|
||||
preview.textContent = colorInput.value.toUpperCase();
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const addColorInput = document.getElementById('add-tag-color');
|
||||
const editColorInput = document.getElementById('edit-tag-color');
|
||||
|
||||
if (addColorInput) {
|
||||
addColorInput.addEventListener('input', () => updateColorPreview('add'));
|
||||
}
|
||||
|
||||
if (editColorInput) {
|
||||
editColorInput.addEventListener('input', () => updateColorPreview('edit'));
|
||||
}
|
||||
|
||||
// Handle edit tag button clicks
|
||||
document.querySelectorAll('.edit-tag-btn').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const tagId = this.dataset.tagId;
|
||||
const tagName = this.dataset.tagName;
|
||||
const tagColor = this.dataset.tagColor;
|
||||
const tagDescription = this.dataset.tagDescription;
|
||||
editTag(tagId, tagName, tagColor, tagDescription);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Close modals when clicking outside
|
||||
window.onclick = function(event) {
|
||||
const addModal = document.getElementById('add-tag-modal');
|
||||
const editModal = document.getElementById('edit-tag-modal');
|
||||
if (event.target === addModal) {
|
||||
closeAddTagModal();
|
||||
}
|
||||
if (event.target === editModal) {
|
||||
closeEditTagModal();
|
||||
}
|
||||
}
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
function showAddTagModal(){document.getElementById("add-tag-modal").classList.remove("hidden"),document.getElementById("add-tag-name").value="",document.getElementById("add-tag-color").value="#6B7280",document.getElementById("add-tag-description").value="",updateColorPreview("add")}function closeAddTagModal(){document.getElementById("add-tag-modal").classList.add("hidden")}function editTag(e,t,d,a){document.getElementById("edit-tag-id").value=e,document.getElementById("edit-tag-name").value=t,document.getElementById("edit-tag-color").value=d,document.getElementById("edit-tag-description").value=a||"",updateColorPreview("edit"),document.getElementById("edit-tag-modal").classList.remove("hidden")}function closeEditTagModal(){document.getElementById("edit-tag-modal").classList.add("hidden")}function updateColorPreview(e){let t=document.getElementById(`${e}-tag-color`),d=document.getElementById(`${e}-color-preview`);d.textContent=t.value.toUpperCase()}document.addEventListener("DOMContentLoaded",function(){let e=document.getElementById("add-tag-color"),t=document.getElementById("edit-tag-color");e&&e.addEventListener("input",()=>updateColorPreview("add")),t&&t.addEventListener("input",()=>updateColorPreview("edit")),document.querySelectorAll(".edit-tag-btn").forEach(e=>{e.addEventListener("click",function(){let e=this.dataset.tagId,t=this.dataset.tagName,d=this.dataset.tagColor,a=this.dataset.tagDescription;editTag(e,t,d,a)})})}),window.onclick=function(e){let t=document.getElementById("add-tag-modal"),d=document.getElementById("edit-tag-modal");e.target===t&&closeAddTagModal(),e.target===d&&closeEditTagModal()};
|
||||
@@ -1,200 +0,0 @@
|
||||
// These variables are set inline in the template from server data
|
||||
// permissions and rolePermissions are passed from the template
|
||||
|
||||
function showTab(tab) {
|
||||
document.getElementById('users-tab').classList.add('hidden');
|
||||
document.getElementById('roles-tab').classList.add('hidden');
|
||||
document.getElementById('tab-users').classList.remove('border-blue-500', 'text-blue-600', 'dark:text-blue-400');
|
||||
document.getElementById('tab-users').classList.add('border-transparent', 'text-gray-600', 'dark:text-gray-400');
|
||||
document.getElementById('tab-roles').classList.remove('border-blue-500', 'text-blue-600', 'dark:text-blue-400');
|
||||
document.getElementById('tab-roles').classList.add('border-transparent', 'text-gray-600', 'dark:text-gray-400');
|
||||
|
||||
if (tab === 'users') {
|
||||
document.getElementById('users-tab').classList.remove('hidden');
|
||||
document.getElementById('tab-users').classList.remove('border-transparent', 'text-gray-600', 'dark:text-gray-400');
|
||||
document.getElementById('tab-users').classList.add('border-blue-500', 'text-blue-600', 'dark:text-blue-400');
|
||||
} else {
|
||||
document.getElementById('roles-tab').classList.remove('hidden');
|
||||
document.getElementById('tab-roles').classList.remove('border-transparent', 'text-gray-600', 'dark:text-gray-400');
|
||||
document.getElementById('tab-roles').classList.add('border-blue-500', 'text-blue-600', 'dark:text-blue-400');
|
||||
}
|
||||
}
|
||||
|
||||
function editUser(userId, name, email, roleId, apiKey) {
|
||||
document.getElementById('edit-user-id').value = userId;
|
||||
document.getElementById('edit-user-name').value = name;
|
||||
document.getElementById('edit-user-email').value = email;
|
||||
document.getElementById('edit-user-password').value = '';
|
||||
document.getElementById('edit-user-role').value = (roleId === null || roleId === 'null') ? '' : roleId;
|
||||
document.getElementById('edit-user-api-key').textContent = apiKey || 'No API Key';
|
||||
document.getElementById('edit-user-modal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function closeEditUserModal() {
|
||||
document.getElementById('edit-user-modal').classList.add('hidden');
|
||||
}
|
||||
|
||||
function showAddRoleModal() {
|
||||
// Make sure edit modal is closed first
|
||||
document.getElementById('edit-role-modal').classList.add('hidden');
|
||||
// Clear any form data
|
||||
const addForm = document.querySelector('#add-role-modal form');
|
||||
if (addForm) {
|
||||
addForm.reset();
|
||||
}
|
||||
// Show add modal
|
||||
document.getElementById('add-role-modal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function closeAddRoleModal() {
|
||||
document.getElementById('add-role-modal').classList.add('hidden');
|
||||
}
|
||||
|
||||
function editRole(roleId, roleName, roleDescription, require2fa) {
|
||||
// Make sure add modal is closed first
|
||||
document.getElementById('add-role-modal').classList.add('hidden');
|
||||
document.getElementById('edit-role-id').value = roleId;
|
||||
document.getElementById('edit-role-name').value = roleName;
|
||||
document.getElementById('edit-role-description').value = roleDescription || '';
|
||||
document.getElementById('edit-role-require-2fa').checked = require2fa === true || require2fa === 'True' || require2fa === 1;
|
||||
|
||||
const permissionsDiv = document.getElementById('edit-role-permissions');
|
||||
permissionsDiv.innerHTML = '';
|
||||
|
||||
const rolePerms = rolePermissions[roleId] || [];
|
||||
|
||||
// Group permissions by merged categories
|
||||
const viewPerms = permissions.filter(p => p[3] === 'View');
|
||||
const devicePerms = permissions.filter(p => p[3] === 'Device');
|
||||
const deviceTypePerms = permissions.filter(p => p[3] === 'Device Type');
|
||||
const subnetPerms = permissions.filter(p => p[3] === 'Subnet');
|
||||
const dhcpPerms = permissions.filter(p => p[3] === 'DHCP');
|
||||
const rackPerms = permissions.filter(p => p[3] === 'Rack');
|
||||
const adminPerms = permissions.filter(p => p[3] === 'Admin');
|
||||
|
||||
let html = '';
|
||||
|
||||
// View Permissions
|
||||
html += ' <!-- View Permissions -->\n';
|
||||
html += ' <div class="col-span-full">\n';
|
||||
html += ' <h4 class="font-semibold text-base mb-2 border-b border-gray-500 pb-1">View Permissions</h4>\n';
|
||||
html += ' <div class="grid grid-cols-1 md:grid-cols-2 gap-2">\n';
|
||||
viewPerms.forEach(perm => {
|
||||
const checked = rolePerms.includes(perm[0]) ? 'checked' : '';
|
||||
html += ` <label class="flex items-center mb-2 cursor-pointer hover:bg-gray-200 dark:hover:bg-zinc-800 p-2 rounded">
|
||||
<input type="checkbox" name="permissions" value="${perm[0]}" ${checked} class="mr-2">
|
||||
<span class="text-sm">${perm[2]}</span>
|
||||
</label>\n`;
|
||||
});
|
||||
html += ' </div>\n';
|
||||
html += ' </div>\n';
|
||||
html += ' \n';
|
||||
|
||||
// Device Management
|
||||
html += ' <!-- Device Management -->\n';
|
||||
html += ' <div>\n';
|
||||
html += ' <h4 class="font-semibold text-base mb-2 border-b border-gray-500 pb-1">Device Management</h4>\n';
|
||||
devicePerms.forEach(perm => {
|
||||
const checked = rolePerms.includes(perm[0]) ? 'checked' : '';
|
||||
html += ` <label class="flex items-center mb-2 cursor-pointer hover:bg-gray-200 dark:hover:bg-zinc-800 p-2 rounded">
|
||||
<input type="checkbox" name="permissions" value="${perm[0]}" ${checked} class="mr-2">
|
||||
<span class="text-sm">${perm[2]}</span>
|
||||
</label>\n`;
|
||||
});
|
||||
deviceTypePerms.forEach(perm => {
|
||||
const checked = rolePerms.includes(perm[0]) ? 'checked' : '';
|
||||
html += ` <label class="flex items-center mb-2 cursor-pointer hover:bg-gray-200 dark:hover:bg-zinc-800 p-2 rounded">
|
||||
<input type="checkbox" name="permissions" value="${perm[0]}" ${checked} class="mr-2">
|
||||
<span class="text-sm">${perm[2]}</span>
|
||||
</label>\n`;
|
||||
});
|
||||
html += ' </div>\n';
|
||||
html += ' \n';
|
||||
|
||||
// Network Management
|
||||
html += ' <!-- Network Management -->\n';
|
||||
html += ' <div>\n';
|
||||
html += ' <h4 class="font-semibold text-base mb-2 border-b border-gray-500 pb-1">Network Management</h4>\n';
|
||||
subnetPerms.forEach(perm => {
|
||||
const checked = rolePerms.includes(perm[0]) ? 'checked' : '';
|
||||
html += ` <label class="flex items-center mb-2 cursor-pointer hover:bg-gray-200 dark:hover:bg-zinc-800 p-2 rounded">
|
||||
<input type="checkbox" name="permissions" value="${perm[0]}" ${checked} class="mr-2">
|
||||
<span class="text-sm">${perm[2]}</span>
|
||||
</label>\n`;
|
||||
});
|
||||
dhcpPerms.forEach(perm => {
|
||||
const checked = rolePerms.includes(perm[0]) ? 'checked' : '';
|
||||
html += ` <label class="flex items-center mb-2 cursor-pointer hover:bg-gray-200 dark:hover:bg-zinc-800 p-2 rounded">
|
||||
<input type="checkbox" name="permissions" value="${perm[0]}" ${checked} class="mr-2">
|
||||
<span class="text-sm">${perm[2]}</span>
|
||||
</label>\n`;
|
||||
});
|
||||
html += ' </div>\n';
|
||||
html += ' \n';
|
||||
|
||||
// Rack Management
|
||||
html += ' <!-- Rack Management -->\n';
|
||||
html += ' <div>\n';
|
||||
html += ' <h4 class="font-semibold text-base mb-2 border-b border-gray-500 pb-1">Rack Management</h4>\n';
|
||||
rackPerms.forEach(perm => {
|
||||
const checked = rolePerms.includes(perm[0]) ? 'checked' : '';
|
||||
html += ` <label class="flex items-center mb-2 cursor-pointer hover:bg-gray-200 dark:hover:bg-zinc-800 p-2 rounded">
|
||||
<input type="checkbox" name="permissions" value="${perm[0]}" ${checked} class="mr-2">
|
||||
<span class="text-sm">${perm[2]}</span>
|
||||
</label>\n`;
|
||||
});
|
||||
html += ' </div>\n';
|
||||
html += ' \n';
|
||||
|
||||
// Admin
|
||||
html += ' <!-- Admin -->\n';
|
||||
html += ' <div>\n';
|
||||
html += ' <h4 class="font-semibold text-base mb-2 border-b border-gray-500 pb-1">Administration</h4>\n';
|
||||
adminPerms.forEach(perm => {
|
||||
const checked = rolePerms.includes(perm[0]) ? 'checked' : '';
|
||||
html += ` <label class="flex items-center mb-2 cursor-pointer hover:bg-gray-200 dark:hover:bg-zinc-800 p-2 rounded">
|
||||
<input type="checkbox" name="permissions" value="${perm[0]}" ${checked} class="mr-2">
|
||||
<span class="text-sm">${perm[2]}</span>
|
||||
</label>\n`;
|
||||
});
|
||||
html += ' </div>\n';
|
||||
|
||||
permissionsDiv.innerHTML = html;
|
||||
|
||||
document.getElementById('edit-role-modal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function closeEditRoleModal() {
|
||||
document.getElementById('edit-role-modal').classList.add('hidden');
|
||||
}
|
||||
|
||||
function deleteRole(roleId, roleName) {
|
||||
if (confirm(`Are you sure you want to delete the role "${roleName}"?`)) {
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '/users';
|
||||
form.innerHTML = `
|
||||
<input type="hidden" name="action" value="delete_role">
|
||||
<input type="hidden" name="role_id" value="${roleId}">
|
||||
`;
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
|
||||
// Close modals when clicking outside
|
||||
window.onclick = function(event) {
|
||||
const editUserModal = document.getElementById('edit-user-modal');
|
||||
const editRoleModal = document.getElementById('edit-role-modal');
|
||||
const addRoleModal = document.getElementById('add-role-modal');
|
||||
if (event.target === editUserModal) {
|
||||
closeEditUserModal();
|
||||
}
|
||||
if (event.target === editRoleModal) {
|
||||
closeEditRoleModal();
|
||||
}
|
||||
if (event.target === addRoleModal) {
|
||||
closeAddRoleModal();
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
-32
@@ -1,32 +0,0 @@
|
||||
function showTab(e){document.getElementById("users-tab").classList.add("hidden"),document.getElementById("roles-tab").classList.add("hidden"),document.getElementById("tab-users").classList.remove("border-blue-500","text-blue-600","dark:text-blue-400"),document.getElementById("tab-users").classList.add("border-transparent","text-gray-600","dark:text-gray-400"),document.getElementById("tab-roles").classList.remove("border-blue-500","text-blue-600","dark:text-blue-400"),document.getElementById("tab-roles").classList.add("border-transparent","text-gray-600","dark:text-gray-400"),"users"===e?(document.getElementById("users-tab").classList.remove("hidden"),document.getElementById("tab-users").classList.remove("border-transparent","text-gray-600","dark:text-gray-400"),document.getElementById("tab-users").classList.add("border-blue-500","text-blue-600","dark:text-blue-400")):(document.getElementById("roles-tab").classList.remove("hidden"),document.getElementById("tab-roles").classList.remove("border-transparent","text-gray-600","dark:text-gray-400"),document.getElementById("tab-roles").classList.add("border-blue-500","text-blue-600","dark:text-blue-400"))}function editUser(e,t,s,l,d){document.getElementById("edit-user-id").value=e,document.getElementById("edit-user-name").value=t,document.getElementById("edit-user-email").value=s,document.getElementById("edit-user-password").value="",document.getElementById("edit-user-role").value=null===l||"null"===l?"":l,document.getElementById("edit-user-api-key").textContent=d||"No API Key",document.getElementById("edit-user-modal").classList.remove("hidden")}function closeEditUserModal(){document.getElementById("edit-user-modal").classList.add("hidden")}function showAddRoleModal(){document.getElementById("edit-role-modal").classList.add("hidden");let e=document.querySelector("#add-role-modal form");e&&e.reset(),document.getElementById("add-role-modal").classList.remove("hidden")}function closeAddRoleModal(){document.getElementById("add-role-modal").classList.add("hidden")}function editRole(e,t,s,l){document.getElementById("add-role-modal").classList.add("hidden"),document.getElementById("edit-role-id").value=e,document.getElementById("edit-role-name").value=t,document.getElementById("edit-role-description").value=s||"",document.getElementById("edit-role-require-2fa").checked=!0===l||"True"===l||1===l;let d=document.getElementById("edit-role-permissions");d.innerHTML="";let a=rolePermissions[e]||[],r=permissions.filter(e=>"View"===e[3]),n=permissions.filter(e=>"Device"===e[3]),o=permissions.filter(e=>"Device Type"===e[3]),i=permissions.filter(e=>"Subnet"===e[3]),c=permissions.filter(e=>"DHCP"===e[3]),m=permissions.filter(e=>"Rack"===e[3]),b=permissions.filter(e=>"Admin"===e[3]),u="";u+=" <!-- View Permissions -->\n",u+=' <div class="col-span-full">\n',u+=' <h4 class="font-semibold text-base mb-2 border-b border-gray-500 pb-1">View Permissions</h4>\n',u+=' <div class="grid grid-cols-1 md:grid-cols-2 gap-2">\n',r.forEach(e=>{let t=a.includes(e[0])?"checked":"";u+=` <label class="flex items-center mb-2 cursor-pointer hover:bg-gray-200 dark:hover:bg-zinc-800 p-2 rounded">
|
||||
<input type="checkbox" name="permissions" value="${e[0]}" ${t} class="mr-2">
|
||||
<span class="text-sm">${e[2]}</span>
|
||||
</label>
|
||||
`}),u+=" </div>\n",u+=" </div>\n",u+=" \n",u+=" <!-- Device Management -->\n",u+=" <div>\n",u+=' <h4 class="font-semibold text-base mb-2 border-b border-gray-500 pb-1">Device Management</h4>\n',n.forEach(e=>{let t=a.includes(e[0])?"checked":"";u+=` <label class="flex items-center mb-2 cursor-pointer hover:bg-gray-200 dark:hover:bg-zinc-800 p-2 rounded">
|
||||
<input type="checkbox" name="permissions" value="${e[0]}" ${t} class="mr-2">
|
||||
<span class="text-sm">${e[2]}</span>
|
||||
</label>
|
||||
`}),o.forEach(e=>{let t=a.includes(e[0])?"checked":"";u+=` <label class="flex items-center mb-2 cursor-pointer hover:bg-gray-200 dark:hover:bg-zinc-800 p-2 rounded">
|
||||
<input type="checkbox" name="permissions" value="${e[0]}" ${t} class="mr-2">
|
||||
<span class="text-sm">${e[2]}</span>
|
||||
</label>
|
||||
`}),u+=" </div>\n",u+=" \n",u+=" <!-- Network Management -->\n",u+=" <div>\n",u+=' <h4 class="font-semibold text-base mb-2 border-b border-gray-500 pb-1">Network Management</h4>\n',i.forEach(e=>{let t=a.includes(e[0])?"checked":"";u+=` <label class="flex items-center mb-2 cursor-pointer hover:bg-gray-200 dark:hover:bg-zinc-800 p-2 rounded">
|
||||
<input type="checkbox" name="permissions" value="${e[0]}" ${t} class="mr-2">
|
||||
<span class="text-sm">${e[2]}</span>
|
||||
</label>
|
||||
`}),c.forEach(e=>{let t=a.includes(e[0])?"checked":"";u+=` <label class="flex items-center mb-2 cursor-pointer hover:bg-gray-200 dark:hover:bg-zinc-800 p-2 rounded">
|
||||
<input type="checkbox" name="permissions" value="${e[0]}" ${t} class="mr-2">
|
||||
<span class="text-sm">${e[2]}</span>
|
||||
</label>
|
||||
`}),u+=" </div>\n",u+=" \n",u+=" <!-- Rack Management -->\n",u+=" <div>\n",u+=' <h4 class="font-semibold text-base mb-2 border-b border-gray-500 pb-1">Rack Management</h4>\n',m.forEach(e=>{let t=a.includes(e[0])?"checked":"";u+=` <label class="flex items-center mb-2 cursor-pointer hover:bg-gray-200 dark:hover:bg-zinc-800 p-2 rounded">
|
||||
<input type="checkbox" name="permissions" value="${e[0]}" ${t} class="mr-2">
|
||||
<span class="text-sm">${e[2]}</span>
|
||||
</label>
|
||||
`}),u+=" </div>\n",u+=" \n",u+=" <!-- Admin -->\n",u+=" <div>\n",u+=' <h4 class="font-semibold text-base mb-2 border-b border-gray-500 pb-1">Administration</h4>\n',b.forEach(e=>{let t=a.includes(e[0])?"checked":"";u+=` <label class="flex items-center mb-2 cursor-pointer hover:bg-gray-200 dark:hover:bg-zinc-800 p-2 rounded">
|
||||
<input type="checkbox" name="permissions" value="${e[0]}" ${t} class="mr-2">
|
||||
<span class="text-sm">${e[2]}</span>
|
||||
</label>
|
||||
`}),u+=" </div>\n",d.innerHTML=u,document.getElementById("edit-role-modal").classList.remove("hidden")}function closeEditRoleModal(){document.getElementById("edit-role-modal").classList.add("hidden")}function deleteRole(e,t){if(confirm(`Are you sure you want to delete the role "${t}"?`)){let s=document.createElement("form");s.method="POST",s.action="/users",s.innerHTML=`
|
||||
<input type="hidden" name="action" value="delete_role">
|
||||
<input type="hidden" name="role_id" value="${e}">
|
||||
`,document.body.appendChild(s),s.submit()}}window.onclick=function(e){let t=document.getElementById("edit-user-modal"),s=document.getElementById("edit-role-modal"),l=document.getElementById("add-role-modal");e.target===t&&closeEditUserModal(),e.target===s&&closeEditRoleModal(),e.target===l&&closeAddRoleModal()};
|
||||
Reference in New Issue
Block a user