Create app.js
This commit is contained in:
@@ -0,0 +1,575 @@
|
|||||||
|
// ===================================
|
||||||
|
// Order Dashboard - Main JavaScript
|
||||||
|
// ===================================
|
||||||
|
|
||||||
|
// Global state
|
||||||
|
let allData = [];
|
||||||
|
let filteredData = [];
|
||||||
|
let currentSort = { column: null, direction: null };
|
||||||
|
let filters = {};
|
||||||
|
let globalSearch = '';
|
||||||
|
let apiCredentials = null;
|
||||||
|
|
||||||
|
// API Configuration
|
||||||
|
const API_URL = 'https://n8n.finorbrands.com/webhook/61f89d5c-8474-4045-b52c-50ee608435c0';
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
updateAuthUI();
|
||||||
|
loadData();
|
||||||
|
setupClickOutside();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ===================================
|
||||||
|
// Authentication
|
||||||
|
// ===================================
|
||||||
|
|
||||||
|
function updateAuthUI() {
|
||||||
|
const btnText = document.getElementById('authBtnText');
|
||||||
|
if (!btnText) return;
|
||||||
|
|
||||||
|
const storedUsername = localStorage.getItem('api_username');
|
||||||
|
|
||||||
|
if (storedUsername) {
|
||||||
|
btnText.textContent = `Logged in as ${storedUsername}`;
|
||||||
|
} else {
|
||||||
|
btnText.textContent = 'Enter Credentials';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleAuth() {
|
||||||
|
const storedUsername = localStorage.getItem('api_username');
|
||||||
|
|
||||||
|
if (storedUsername) {
|
||||||
|
localStorage.removeItem('api_username');
|
||||||
|
localStorage.removeItem('api_password');
|
||||||
|
apiCredentials = null;
|
||||||
|
updateAuthUI();
|
||||||
|
loadData();
|
||||||
|
} else {
|
||||||
|
showCredentialPrompt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================
|
||||||
|
// Data Loading
|
||||||
|
// ===================================
|
||||||
|
|
||||||
|
async function loadData() {
|
||||||
|
const tableBody = document.getElementById('tableBody');
|
||||||
|
if (!tableBody) return;
|
||||||
|
|
||||||
|
tableBody.innerHTML = `
|
||||||
|
<tr>
|
||||||
|
<td colspan="7" class="loading">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<span>Loading data...</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.group('%c Webhook Call', 'background: #6366f1; color: white; padding: 4px 8px; border-radius: 4px; font-weight: bold;');
|
||||||
|
console.log('%c API URL:', 'font-weight: bold; color: #6366f1;', API_URL);
|
||||||
|
|
||||||
|
const storedUsername = localStorage.getItem('api_username');
|
||||||
|
const storedPassword = localStorage.getItem('api_password');
|
||||||
|
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
const fetchOptions = {};
|
||||||
|
if (storedUsername && storedPassword) {
|
||||||
|
apiCredentials = { username: storedUsername, password: storedPassword };
|
||||||
|
const credentials = btoa(`${storedUsername}:${storedPassword}`);
|
||||||
|
fetchOptions.headers = {
|
||||||
|
'Authorization': `Basic ${credentials}`,
|
||||||
|
'Accept': 'application/json'
|
||||||
|
};
|
||||||
|
console.log('%c Using Basic Auth', 'color: #10b981;', `User: ${storedUsername}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(API_URL, fetchOptions);
|
||||||
|
const duration = performance.now() - startTime;
|
||||||
|
|
||||||
|
console.log('%c Response Status:', response.ok ? 'color: #10b981' : 'color: #ef4444', response.status, response.statusText);
|
||||||
|
console.log('%c Response Time:', 'font-weight: bold;', `${duration.toFixed(0)}ms`);
|
||||||
|
|
||||||
|
if (response.status === 401) {
|
||||||
|
throw new Error('Authorization required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result = await response.json();
|
||||||
|
console.log('%c Response Data:', 'font-weight: bold;', result);
|
||||||
|
|
||||||
|
if (result.data) {
|
||||||
|
allData = result.data;
|
||||||
|
console.log('%c Data loaded:', 'color: #10b981; font-weight: bold;', allData.length, 'records');
|
||||||
|
console.log('%c First record:', 'color: #6366f1;', allData[0]);
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid response format');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('API error');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.groupEnd();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('%c Fetch Error:', 'background: #ef4444; color: white; padding: 4px 8px; border-radius: 4px;', error.message);
|
||||||
|
|
||||||
|
if (error.message === 'Authorization required') {
|
||||||
|
showCredentialPrompt();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Using sample data:', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredData = [...allData];
|
||||||
|
|
||||||
|
console.group('%c Filter State', 'background: #8b5cf6; color: white; padding: 4px 8px; border-radius: 4px; font-weight: bold;');
|
||||||
|
console.log('%c Current filters:', 'font-weight: bold;', filters);
|
||||||
|
console.log('%c Total records after filter:', 'font-weight: bold;', filteredData.length);
|
||||||
|
console.groupEnd();
|
||||||
|
|
||||||
|
applyFilters();
|
||||||
|
updateStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================
|
||||||
|
// Filtering & Sorting
|
||||||
|
// ===================================
|
||||||
|
|
||||||
|
function applyFilters() {
|
||||||
|
let result = filteredData.filter(item => {
|
||||||
|
if (!globalSearch) return true;
|
||||||
|
const searchLower = globalSearch.toLowerCase();
|
||||||
|
return Object.values(item).some(value =>
|
||||||
|
value !== null && value.toString().toLowerCase().includes(searchLower)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.entries(filters).forEach(([column, selectedValues]) => {
|
||||||
|
if (selectedValues.length > 0) {
|
||||||
|
result = result.filter(item =>
|
||||||
|
selectedValues.includes(item[column] === null ? 'null' : item[column])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
renderTable(result);
|
||||||
|
updateStats(result.length);
|
||||||
|
|
||||||
|
console.group('%c Table View', 'background: #06b6d4; color: white; padding: 4px 8px; border-radius: 4px; font-weight: bold;');
|
||||||
|
console.log('%c Rendering', 'font-weight: bold;', result.length, 'records');
|
||||||
|
console.log('%c View range:', 'color: #64748b;', '1-', result.length);
|
||||||
|
console.groupEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortTable(column) {
|
||||||
|
const direction = currentSort.column === column && currentSort.direction === 'asc' ? 'desc' : 'asc';
|
||||||
|
|
||||||
|
filteredData.sort((a, b) => {
|
||||||
|
let valA = a[column];
|
||||||
|
let valB = b[column];
|
||||||
|
|
||||||
|
if (valA === null) valA = '';
|
||||||
|
if (valB === null) valB = '';
|
||||||
|
|
||||||
|
if (typeof valA === 'string' && typeof valB === 'string') {
|
||||||
|
valA = valA.toLowerCase();
|
||||||
|
valB = valB.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valA < valB) return direction === 'asc' ? -1 : 1;
|
||||||
|
if (valA > valB) return direction === 'asc' ? 1 : -1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
currentSort = { column, direction };
|
||||||
|
renderTable(filteredData);
|
||||||
|
updateSortIndicators(column, direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSortIndicators(column, direction) {
|
||||||
|
document.querySelectorAll('.sort-indicator').forEach(indicator => {
|
||||||
|
indicator.classList.remove('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
const th = Array.from(document.querySelectorAll('th')).find(
|
||||||
|
th => th.textContent.includes(column)
|
||||||
|
);
|
||||||
|
if (th) {
|
||||||
|
const indicator = th.querySelector('.sort-indicator');
|
||||||
|
if (indicator) {
|
||||||
|
indicator.classList.add('active');
|
||||||
|
indicator.innerHTML = direction === 'asc'
|
||||||
|
? '<i class="fas fa-arrow-up"></i><i class="fas fa-arrow-down"></i>'
|
||||||
|
: '<i class="fas fa-arrow-down"></i><i class="fas fa-arrow-up"></i>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================
|
||||||
|
// Table Rendering
|
||||||
|
// ===================================
|
||||||
|
|
||||||
|
function renderTable(data) {
|
||||||
|
const tableBody = document.getElementById('tableBody');
|
||||||
|
if (!tableBody) return;
|
||||||
|
|
||||||
|
if (data.length === 0) {
|
||||||
|
tableBody.innerHTML = `
|
||||||
|
<tr>
|
||||||
|
<td colspan="7" class="empty-state">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
<p>No orders found matching your filters</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tableBody.innerHTML = data.map(item => `
|
||||||
|
<tr>
|
||||||
|
<td>${item.Month || ''}</td>
|
||||||
|
<td>${item.Odoo_AR_Nr || ''}</td>
|
||||||
|
<td>${item.Order_ID || ''}</td>
|
||||||
|
<td>
|
||||||
|
<div class="cell-country">
|
||||||
|
<span class="country-badge ${getCountryClass(item.Country_Name)}"></span>
|
||||||
|
${item.Country_Name || 'N/A'}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="cell-vat ${!item.VAT ? 'empty' : ''}">
|
||||||
|
${item.VAT || 'N/A'}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="cell-remark ${getRemarkClass(item.Remark)}">
|
||||||
|
<i class="fas ${getRemarkIcon(item.Remark)}"></i>
|
||||||
|
${item.Remark || 'N/A'}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox"
|
||||||
|
class="recheck-checkbox"
|
||||||
|
${item.recheck ? 'checked' : ''}
|
||||||
|
onchange="handleRecheck('${item.Order_ID}', this.checked)">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCountryClass(country) {
|
||||||
|
if (country === null || country === undefined) return 'unknown';
|
||||||
|
const str = String(country);
|
||||||
|
if (str.includes('Finnland')) return 'finland';
|
||||||
|
if (str.includes('Spanien')) return 'spanien';
|
||||||
|
if (str.includes('Deutschland')) return 'deutschland';
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRemarkClass(remark) {
|
||||||
|
if (remark === null || remark === undefined) return '';
|
||||||
|
const str = String(remark);
|
||||||
|
if (str.includes('NOK')) return 'remark-nok';
|
||||||
|
if (str.includes('found')) return 'remark-danger';
|
||||||
|
if (str.includes('valid') || str.includes('OK')) return 'remark-success';
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRemarkIcon(remark) {
|
||||||
|
if (remark === null || remark === undefined) return 'fa-file-alt';
|
||||||
|
const str = String(remark);
|
||||||
|
if (str.includes('NOK')) return 'fa-times-circle';
|
||||||
|
if (str.includes('found')) return 'fa-exclamation-triangle';
|
||||||
|
if (str.includes('valid') || str.includes('OK')) return 'fa-check-circle';
|
||||||
|
return 'fa-file-alt';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================
|
||||||
|
// Recheck Action
|
||||||
|
// ===================================
|
||||||
|
|
||||||
|
async function handleRecheck(orderId, isChecked) {
|
||||||
|
console.group('%c Recheck', 'background: #6366f1; color: white; padding: 4px 8px; border-radius: 4px; font-weight: bold;');
|
||||||
|
console.log('%c Order ID:', 'font-weight: bold;', orderId);
|
||||||
|
console.log('%c Status:', isChecked ? 'color: #10b981' : 'color: #94a3b8', isChecked ? 'Checked' : 'Unchecked');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(API_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(apiCredentials ? {
|
||||||
|
'Authorization': `Basic ${btoa(`${apiCredentials.username}:${apiCredentials.password}`)}`
|
||||||
|
} : {})
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
Order_ID: orderId,
|
||||||
|
recheck: isChecked
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
console.log('%c Success:', 'color: #10b981; font-weight: bold;', 'Recheck status updated');
|
||||||
|
} else {
|
||||||
|
console.error('%c Error:', 'background: #ef4444; color: white; padding: 4px 8px; border-radius: 4px;', response.status, response.statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.groupEnd();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('%c Request Error:', 'background: #ef4444; color: white; padding: 4px 8px; border-radius: 4px;', error.message);
|
||||||
|
console.groupEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================
|
||||||
|
// Column Filters
|
||||||
|
// ===================================
|
||||||
|
|
||||||
|
function toggleFilter(column) {
|
||||||
|
const container = document.getElementById('filterContainer');
|
||||||
|
|
||||||
|
document.querySelectorAll('.filter-dropdown').forEach(dropdown => {
|
||||||
|
if (dropdown.dataset.column !== column) {
|
||||||
|
dropdown.classList.remove('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let dropdown = document.querySelector(`.filter-dropdown[data-column="${column}"]`);
|
||||||
|
|
||||||
|
if (!dropdown) {
|
||||||
|
dropdown = createFilterDropdown(column);
|
||||||
|
container.appendChild(dropdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dropdown.classList.contains('show')) {
|
||||||
|
dropdown.classList.remove('show');
|
||||||
|
} else {
|
||||||
|
dropdown.classList.add('show');
|
||||||
|
positionDropdown(dropdown, column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFilterDropdown(column) {
|
||||||
|
const dropdown = document.createElement('div');
|
||||||
|
dropdown.className = 'filter-dropdown';
|
||||||
|
dropdown.dataset.column = column;
|
||||||
|
|
||||||
|
const uniqueValues = [...new Set(
|
||||||
|
allData.map(item => item[column] === null ? 'null' : item[column])
|
||||||
|
)];
|
||||||
|
|
||||||
|
dropdown.innerHTML = `
|
||||||
|
<div class="filter-header">
|
||||||
|
<h4>Filter ${column}</h4>
|
||||||
|
<button class="close-btn" onclick="this.closest('.filter-dropdown').classList.remove('show')">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="filter-list">
|
||||||
|
${uniqueValues.map(value => `
|
||||||
|
<label class="filter-item">
|
||||||
|
<input type="checkbox"
|
||||||
|
data-column="${column}"
|
||||||
|
value="${value}"
|
||||||
|
${filters[column] && filters[column].includes(value) ? 'checked' : ''}>
|
||||||
|
<span>${value === 'null' ? 'No Value' : value}</span>
|
||||||
|
</label>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
|
<button class="filter-clear" onclick="clearFilter('${column}')">
|
||||||
|
Clear Filter
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
dropdown.addEventListener('change', (e) => {
|
||||||
|
if (e.target.type === 'checkbox') {
|
||||||
|
updateFilter(column, e.target.value, e.target.checked);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return dropdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
function positionDropdown(dropdown, column) {
|
||||||
|
const th = Array.from(document.querySelectorAll('th')).find(
|
||||||
|
th => th.textContent.includes(column)
|
||||||
|
);
|
||||||
|
if (th) {
|
||||||
|
const rect = th.getBoundingClientRect();
|
||||||
|
const tableRect = document.querySelector('table').getBoundingClientRect();
|
||||||
|
dropdown.style.left = `${rect.left - tableRect.left}px`;
|
||||||
|
dropdown.style.width = `${rect.width}px`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFilter(column, value, isChecked) {
|
||||||
|
if (!filters[column]) {
|
||||||
|
filters[column] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const valueStr = value === 'null' ? 'null' : value;
|
||||||
|
|
||||||
|
if (isChecked) {
|
||||||
|
if (!filters[column].includes(valueStr)) {
|
||||||
|
filters[column].push(valueStr);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filters[column] = filters[column].filter(v => v !== valueStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFilterIndicators(column);
|
||||||
|
applyFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFilterIndicators(column) {
|
||||||
|
const th = Array.from(document.querySelectorAll('th')).find(
|
||||||
|
th => th.textContent.includes(column)
|
||||||
|
);
|
||||||
|
if (th) {
|
||||||
|
const indicator = th.querySelector('.filter-indicator');
|
||||||
|
if (indicator) {
|
||||||
|
if (filters[column] && filters[column].length > 0) {
|
||||||
|
indicator.classList.add('active');
|
||||||
|
} else {
|
||||||
|
indicator.classList.remove('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearFilter(column) {
|
||||||
|
if (filters[column]) {
|
||||||
|
filters[column] = [];
|
||||||
|
}
|
||||||
|
document.querySelectorAll(`.filter-dropdown[data-column="${column}"] input[type="checkbox"]`)
|
||||||
|
.forEach(checkbox => checkbox.checked = false);
|
||||||
|
|
||||||
|
updateFilterIndicators(column);
|
||||||
|
applyFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupClickOutside() {
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
if (!e.target.closest('.filter-indicator') &&
|
||||||
|
!e.target.closest('.filter-dropdown')) {
|
||||||
|
document.querySelectorAll('.filter-dropdown').forEach(dropdown => {
|
||||||
|
dropdown.classList.remove('show');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleGlobalSearch(search) {
|
||||||
|
globalSearch = search;
|
||||||
|
applyFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================
|
||||||
|
// Statistics
|
||||||
|
// ===================================
|
||||||
|
|
||||||
|
function updateStats(count = filteredData.length) {
|
||||||
|
const totalOrders = document.getElementById('totalOrders');
|
||||||
|
const totalCountries = document.getElementById('totalCountries');
|
||||||
|
const totalVAT = document.getElementById('totalVAT');
|
||||||
|
const tableCount = document.getElementById('tableCount');
|
||||||
|
|
||||||
|
if (totalOrders) totalOrders.textContent = allData.length;
|
||||||
|
if (totalCountries) totalCountries.textContent =
|
||||||
|
[...new Set(allData.map(item => item.Country_Name))].length;
|
||||||
|
if (totalVAT) totalVAT.textContent =
|
||||||
|
allData.filter(item => item.VAT !== null).length;
|
||||||
|
if (tableCount) tableCount.textContent = `${count} order${count !== 1 ? 's' : ''}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================
|
||||||
|
// Credential Prompt
|
||||||
|
// ===================================
|
||||||
|
|
||||||
|
function showCredentialPrompt() {
|
||||||
|
const existing = document.getElementById('credentialPrompt');
|
||||||
|
if (existing) existing.remove();
|
||||||
|
|
||||||
|
const prompt = document.createElement('div');
|
||||||
|
prompt.id = 'credentialPrompt';
|
||||||
|
prompt.innerHTML = `
|
||||||
|
<div class="credential-overlay">
|
||||||
|
<div class="credential-modal">
|
||||||
|
<div class="credential-content">
|
||||||
|
<div class="credential-header">
|
||||||
|
<i class="fas fa-key"></i>
|
||||||
|
<h2>API Credentials Required</h2>
|
||||||
|
</div>
|
||||||
|
<p>The webhook API requires authentication. Please enter your credentials below:</p>
|
||||||
|
<form id="credentialForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<i class="fas fa-user"></i>
|
||||||
|
<input type="text" id="username" placeholder="Enter username" autocomplete="username">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Password/API Key</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<i class="fas fa-lock"></i>
|
||||||
|
<input type="password" id="password" placeholder="Enter password or API key" autocomplete="current-password">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-actions">
|
||||||
|
<button type="button" class="btn-secondary" onclick="closeCredentialPrompt()">Cancel</button>
|
||||||
|
<button type="submit" class="btn-primary">Connect</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div class="credential-help">
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
<span>Your credentials will be used for this session only. They are not stored permanently.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.body.appendChild(prompt);
|
||||||
|
|
||||||
|
document.getElementById('credentialForm').addEventListener('submit', handleCredentials);
|
||||||
|
document.querySelector('.credential-overlay').addEventListener('click', function(e) {
|
||||||
|
if (e.target === this) {
|
||||||
|
closeCredentialPrompt();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeCredentialPrompt() {
|
||||||
|
const prompt = document.getElementById('credentialPrompt');
|
||||||
|
if (prompt) {
|
||||||
|
document.body.removeChild(prompt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCredentials(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const username = document.getElementById('username').value;
|
||||||
|
const password = document.getElementById('password').value;
|
||||||
|
|
||||||
|
console.group('%c Credentials', 'background: #6366f1; color: white; padding: 4px 8px; border-radius: 4px; font-weight: bold;');
|
||||||
|
console.log('%c Username:', 'font-weight: bold;', username);
|
||||||
|
console.log('%c Password length:', 'font-weight: bold;', password.length, 'characters');
|
||||||
|
console.groupEnd();
|
||||||
|
|
||||||
|
localStorage.setItem('api_username', username);
|
||||||
|
localStorage.setItem('api_password', password);
|
||||||
|
apiCredentials = { username, password };
|
||||||
|
|
||||||
|
console.log('%c Credentials saved:', 'color: #10b981; font-weight: bold;', {
|
||||||
|
username,
|
||||||
|
passwordLength: password.length,
|
||||||
|
authHeader: `Basic ${btoa(`${username}:${password}`)}`
|
||||||
|
});
|
||||||
|
|
||||||
|
closeCredentialPrompt();
|
||||||
|
console.log('%c Retrying connection with credentials...', 'color: #10b981; font-weight: bold;');
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user