- Implemented PayPal webhook for handling payment notifications, including signature verification and transaction updates. - Created invoice generation and license management for software upgrades upon successful payment. - Added comprehensive logging for debugging purposes. - Introduced new CSS styles for the marketing file management system, including layout, toolbar, breadcrumb navigation, search filters, and file management UI components.
1269 lines
52 KiB
JavaScript
1269 lines
52 KiB
JavaScript
const serialResultsDiv = document.getElementById("serialResults");
|
|
const readBar = document.getElementById("readBar");
|
|
|
|
// Buffer for accumulating received data before logging
|
|
let receivedDataBuffer = '';
|
|
|
|
// Software tool specific variables
|
|
let deviceSerialNumber = "";
|
|
let deviceVersion = "";
|
|
let deviceHwVersion = "";
|
|
let selectedSoftwareUrl = "";
|
|
|
|
// Serial port variables (port, writer, textEncoder, writableStreamClosed declared in PHP)
|
|
let reader;
|
|
let readableStreamClosed;
|
|
let keepReading = true;
|
|
|
|
// Function to log communication to API (reused from scripts.js)
|
|
async function logCommunication(data, direction) {
|
|
// Only log if debug mode is enabled
|
|
if (typeof DEBUG === 'undefined' || !DEBUG) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const serviceToken = document.getElementById("servicetoken")?.innerHTML || '';
|
|
|
|
let serialNumber = '';
|
|
if (deviceSerialNumber) {
|
|
serialNumber = deviceSerialNumber;
|
|
}
|
|
|
|
const logData = {
|
|
data: data,
|
|
direction: direction,
|
|
timestamp: new Date().toISOString(),
|
|
serial_number: serialNumber,
|
|
maintenance_run: 0
|
|
};
|
|
|
|
const url = link + '/v2/com_log/log';
|
|
const bearer = 'Bearer ' + serviceToken;
|
|
|
|
const response = await fetch(url, {
|
|
method: 'POST',
|
|
withCredentials: true,
|
|
credentials: 'include',
|
|
headers: {
|
|
'Authorization': bearer,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(logData)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
console.warn('Failed to log communication:', response.status);
|
|
}
|
|
} catch (error) {
|
|
console.warn('Error logging communication:', error);
|
|
}
|
|
}
|
|
|
|
// Progress bar function (reused from scripts.js)
|
|
function progressBar(percentage, message, color){
|
|
readBar.style.background = color;
|
|
readBar.style.width = percentage +"%";
|
|
readBar.innerHTML = message;
|
|
}
|
|
|
|
// Connect device for software tool
|
|
async function connectDeviceForSoftware() {
|
|
//clear input
|
|
readBar.innerHTML = '';
|
|
serialResultsDiv.innerHTML = '';
|
|
document.getElementById("softwareCheckStatus").style.display = "none";
|
|
document.getElementById("softwareOptions").style.display = "none";
|
|
document.getElementById("noUpdatesMessage").style.display = "none";
|
|
document.getElementById("uploadSection").style.display = "none";
|
|
|
|
// Reset data
|
|
receivedDataBuffer = '';
|
|
deviceSerialNumber = "";
|
|
deviceVersion = "";
|
|
deviceHwVersion = "";
|
|
|
|
//set progress bar
|
|
progressBar("1", "", "");
|
|
|
|
// Check if DEBUG mode is enabled - use mock device data
|
|
if (typeof DEBUG !== 'undefined' && DEBUG) {
|
|
// TEST MODE: Use mock device data
|
|
deviceSerialNumber = "22110095";
|
|
deviceVersion = "03e615af";
|
|
deviceHwVersion = "0000080";
|
|
|
|
document.getElementById("Device_output").style.display = "block";
|
|
serialResultsDiv.innerHTML = `<strong style="color: #ff9800;">DEBUG MODE - Simulated Device Data:</strong><br>SN=${deviceSerialNumber}<br>FW=${deviceVersion}<br>HW=${deviceHwVersion}`;
|
|
progressBar("60", "DEBUG MODE - Device data read - SN: " + deviceSerialNumber, "#ff9800");
|
|
|
|
await logCommunication(`DEBUG MODE: Simulated device - SN=${deviceSerialNumber}, Version=${deviceVersion}, HW=${deviceHwVersion}`, 'handshake');
|
|
|
|
// Proceed to check software availability
|
|
checkSoftwareAvailability();
|
|
return;
|
|
}
|
|
|
|
// Check if port is already open - if so, refresh the page to start clean
|
|
if (port) {
|
|
await logCommunication('Port already in use - refreshing page for clean reconnect', 'info');
|
|
location.reload();
|
|
return;
|
|
}
|
|
|
|
// Reset flags for new connection
|
|
keepReading = true;
|
|
|
|
try {
|
|
// Prompt user to select any serial port.
|
|
const filters = [{ usbVendorId: 1027, usbProductId: 24597 }];
|
|
port = await navigator.serial.requestPort({ filters });
|
|
|
|
// Log selected port details
|
|
const portInfo = port.getInfo();
|
|
const portDetails = {
|
|
processStep: 'Software',
|
|
usbVendorId: portInfo.usbVendorId,
|
|
usbProductId: portInfo.usbProductId,
|
|
readable: !!port.readable,
|
|
writable: !!port.writable,
|
|
opened: port.readable !== null && port.writable !== null
|
|
};
|
|
await logCommunication(`Selected USB device - ${JSON.stringify(portDetails)}`, 'connected');
|
|
|
|
await port.open({
|
|
baudRate: 56700,
|
|
dataBits: 8,
|
|
stopBits: 1,
|
|
parity: 'none',
|
|
flowControl: 'none'
|
|
});
|
|
|
|
progressBar("10", "Connecting", "#04AA6D");
|
|
|
|
// Log successful connection
|
|
await logCommunication('Port opened successfully', 'connected');
|
|
|
|
textEncoder = new TextEncoderStream();
|
|
writableStreamClosed = textEncoder.readable.pipeTo(port.writable);
|
|
writer = textEncoder.writable.getWriter();
|
|
|
|
progressBar("20", "Connected, reading device...", "#04AA6D");
|
|
|
|
// Read device output
|
|
await readDeviceOutput();
|
|
|
|
// Close the port immediately after reading
|
|
await closePortAfterRead();
|
|
|
|
// Check for software updates (port is now closed)
|
|
checkSoftwareAvailability();
|
|
|
|
} catch (error) {
|
|
await logCommunication(`Connection error: ${error.message}`, 'error');
|
|
|
|
// Check for specific "No port selected" error and show user-friendly message
|
|
if (error.message && error.message.includes('No port selected by the user')) {
|
|
progressBar("100", "No device selected, please try again", "#ff6666");
|
|
} else {
|
|
progressBar("100", "Error: " + error.message, "#ff6666");
|
|
}
|
|
}
|
|
}
|
|
|
|
async function readDeviceOutput() {
|
|
const textDecoder = new TextDecoderStream();
|
|
readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
|
|
reader = textDecoder.readable.getReader();
|
|
|
|
document.getElementById("Device_output").style.display = "block";
|
|
|
|
let dataCompleteMarkerFound = false;
|
|
const startTime = Date.now();
|
|
const timeout = 10000; // 10 second timeout
|
|
|
|
try {
|
|
progressBar("40", "Reading device...", "#04AA6D");
|
|
|
|
while (keepReading) {
|
|
const { value, done } = await reader.read();
|
|
if (done || !keepReading) {
|
|
break;
|
|
}
|
|
|
|
receivedDataBuffer += value;
|
|
serialResultsDiv.innerHTML = receivedDataBuffer;
|
|
|
|
// Check if we have received PLUGTY which comes after all device data
|
|
// Or wait for at least FWDATE which comes near the end
|
|
if (receivedDataBuffer.indexOf("PLUGTY") > 0 || receivedDataBuffer.indexOf("FWDATE=") > 0) {
|
|
dataCompleteMarkerFound = true;
|
|
}
|
|
|
|
// Only parse and exit if we found the completion marker
|
|
if (dataCompleteMarkerFound) {
|
|
// Parse device info from complete output
|
|
parseDeviceInfo(receivedDataBuffer);
|
|
|
|
// Check if we have all required data
|
|
if (deviceSerialNumber && deviceVersion && deviceHwVersion) {
|
|
progressBar("60", "Device data read - SN: " + deviceSerialNumber, "#04AA6D");
|
|
await logCommunication(`Device identification data received: SN=${deviceSerialNumber}, Version=${deviceVersion}, HW=${deviceHwVersion}`, 'handshake');
|
|
|
|
// Exit cleanly
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Timeout check
|
|
if (Date.now() - startTime > timeout) {
|
|
await logCommunication('Device read timeout - proceeding with partial data', 'error');
|
|
parseDeviceInfo(receivedDataBuffer);
|
|
break;
|
|
}
|
|
}
|
|
|
|
progressBar("65", "Device reading completed", "#04AA6D");
|
|
|
|
} catch (error) {
|
|
if (keepReading) {
|
|
await logCommunication(`Read error: ${error.message}`, 'error');
|
|
progressBar("0", "Error reading device: " + error.message, "#ff6666");
|
|
}
|
|
} finally {
|
|
// Clean up reader and cancel the stream
|
|
try {
|
|
if (reader) {
|
|
await reader.cancel();
|
|
reader.releaseLock();
|
|
}
|
|
} catch (e) {
|
|
console.log('Reader cleanup error:', e);
|
|
}
|
|
}
|
|
}
|
|
|
|
function parseDeviceInfo(data) {
|
|
// Extract SN, FW (firmware/software version), and HW from device output
|
|
// Device format: SN=12345678;FW=12345678;HW=12345678;STATE=...
|
|
// Match exact logic from scripts.js and readdevice.js
|
|
|
|
// Use exact same approach as scripts.js line 153 and readdevice.js line 649
|
|
const x = Array.from(new Set(data.split(";"))).toString();
|
|
|
|
// Parse SN= (8 characters, exact logic from scripts.js line 185-189)
|
|
if (x.indexOf("SN=") > 0 && !deviceSerialNumber) {
|
|
const a = x.indexOf("SN=");
|
|
const b = a + 3;
|
|
const c = b + 8;
|
|
deviceSerialNumber = x.substring(b, c);
|
|
console.log("Found SN:", deviceSerialNumber);
|
|
}
|
|
|
|
// Parse FW= (8 characters, exact logic from readdevice.js line 672-676)
|
|
if (x.indexOf("FW=") > 0 && !deviceVersion) {
|
|
const a = x.indexOf("FW=");
|
|
const b = a + 3;
|
|
const c = b + 8;
|
|
deviceVersion = x.substring(b, c);
|
|
console.log("Found FW/Version:", deviceVersion);
|
|
}
|
|
|
|
// Parse HW= (8 characters, exact logic from readdevice.js line 665-670)
|
|
if (x.indexOf("HW=") > 0 && !deviceHwVersion) {
|
|
const a = x.indexOf("HW=");
|
|
const b = a + 3;
|
|
const c = b + 8;
|
|
deviceHwVersion = x.substring(b, c);
|
|
console.log("Found HW Version:", deviceHwVersion);
|
|
}
|
|
}
|
|
|
|
async function closePortAfterRead() {
|
|
if (port) {
|
|
try {
|
|
await logCommunication('Closing port after reading device data', 'info');
|
|
|
|
// Reader is already cancelled and released by readDeviceOutput finally block
|
|
// Now abort the writer
|
|
if (writer) {
|
|
try {
|
|
await writer.abort();
|
|
} catch (e) {
|
|
console.log('Writer abort error:', e);
|
|
}
|
|
}
|
|
|
|
// Give time for streams to cleanup
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
// Close the port
|
|
await port.close();
|
|
await logCommunication('Port closed successfully', 'info');
|
|
|
|
// Reset for next connection
|
|
reader = null;
|
|
writer = null;
|
|
readableStreamClosed = null;
|
|
writableStreamClosed = null;
|
|
port = null;
|
|
} catch (error) {
|
|
console.error('Error closing port after read:', error);
|
|
await logCommunication(`Error closing port: ${error.message}`, 'error');
|
|
|
|
// Force reset even on error
|
|
reader = null;
|
|
writer = null;
|
|
readableStreamClosed = null;
|
|
writableStreamClosed = null;
|
|
port = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
async function closePort() {
|
|
if (port) {
|
|
try {
|
|
keepReading = false;
|
|
|
|
// Wait for read operations to complete
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
// Release reader if still locked
|
|
if (reader) {
|
|
try {
|
|
reader.releaseLock();
|
|
} catch (e) {
|
|
console.log('Reader release error:', e);
|
|
}
|
|
}
|
|
|
|
// Wait a bit more
|
|
await new Promise(resolve => setTimeout(resolve, 200));
|
|
|
|
// Now close the port
|
|
try {
|
|
await port.close();
|
|
await logCommunication('Port closed', 'info');
|
|
} catch (e) {
|
|
console.log('Port close error (may already be closed):', e);
|
|
}
|
|
|
|
// Reset for next connection
|
|
reader = null;
|
|
port = null;
|
|
} catch (error) {
|
|
console.error('Error closing port:', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function checkSoftwareAvailability() {
|
|
if (!deviceSerialNumber) {
|
|
progressBar("0", "Error: Serial number not found", "#ff6666");
|
|
await logCommunication('Serial number not found in device output', 'error');
|
|
return;
|
|
}
|
|
|
|
document.getElementById("softwareCheckStatus").style.display = "block";
|
|
progressBar("70", "Checking for software updates...", "#04AA6D");
|
|
|
|
try {
|
|
// Call software_available API
|
|
const availableUrl = link + "/v2/software_available/sn=" + deviceSerialNumber +
|
|
(deviceVersion ? "&version=" + deviceVersion : "") +
|
|
(deviceHwVersion ? "&hw_version=" + deviceHwVersion : "");
|
|
|
|
console.log("Calling API:", availableUrl);
|
|
await logCommunication(`Checking software availability for SN: ${deviceSerialNumber}`, 'sent');
|
|
|
|
const availableResponse = await fetch(availableUrl, {
|
|
method: "GET",
|
|
headers: {
|
|
"Authorization": "Bearer " + document.getElementById("servicetoken").textContent,
|
|
"Content-Type": "application/json"
|
|
}
|
|
});
|
|
|
|
const availableData = await availableResponse.json();
|
|
console.log("Available response:", availableData);
|
|
|
|
await logCommunication(`Software availability response: ${JSON.stringify(availableData)}`, 'received');
|
|
|
|
if (availableData.software_available === "error" || availableData.error) {
|
|
// Error checking for updates (e.g., invalid hardware version)
|
|
document.getElementById("softwareCheckStatus").style.display = "none";
|
|
progressBar("0", "Error: " + (availableData.error || "Unable to check for updates"), "#ff6666");
|
|
alert("Error checking for updates: " + (availableData.error || "Unknown error"));
|
|
} else if (availableData.software_available === "yes") {
|
|
// Software updates available, fetch options
|
|
progressBar("80", "Software updates found, loading options...", "#04AA6D");
|
|
await fetchSoftwareOptions();
|
|
} else {
|
|
// No updates available
|
|
document.getElementById("softwareCheckStatus").style.display = "none";
|
|
document.getElementById("noUpdatesMessage").style.display = "block";
|
|
progressBar("100", "No software updates available", "#04AA6D");
|
|
}
|
|
} catch (error) {
|
|
await logCommunication(`Software check error: ${error.message}`, 'error');
|
|
progressBar("0", "Error checking software: " + error.message, "#ff6666");
|
|
await closePort();
|
|
}
|
|
}
|
|
|
|
async function fetchSoftwareOptions() {
|
|
try {
|
|
// Call software_update API to get options
|
|
const updateUrl = link + "/v2/software_update/sn=" + deviceSerialNumber +
|
|
(deviceVersion ? "&version=" + deviceVersion : "") +
|
|
(deviceHwVersion ? "&hw_version=" + deviceHwVersion : "");
|
|
|
|
console.log("Fetching options from:", updateUrl);
|
|
await logCommunication(`Fetching software options for SN: ${deviceSerialNumber}`, 'sent');
|
|
|
|
const updateResponse = await fetch(updateUrl, {
|
|
method: "GET",
|
|
headers: {
|
|
"Authorization": "Bearer " + document.getElementById("servicetoken").textContent,
|
|
"Content-Type": "application/json"
|
|
}
|
|
});
|
|
|
|
const options = await updateResponse.json();
|
|
console.log("Software options:", options);
|
|
|
|
await logCommunication(`Software options response: ${JSON.stringify(options)}`, 'received');
|
|
|
|
if (options.error) {
|
|
document.getElementById("softwareCheckStatus").style.display = "none";
|
|
document.getElementById("noUpdatesMessage").style.display = "block";
|
|
progressBar("100", "No software updates available", "#04AA6D");
|
|
await closePort();
|
|
return;
|
|
}
|
|
|
|
// Display options in table
|
|
displaySoftwareOptions(options);
|
|
document.getElementById("softwareCheckStatus").style.display = "none";
|
|
document.getElementById("softwareOptions").style.display = "block";
|
|
progressBar("100", "Software options loaded", "#04AA6D");
|
|
|
|
} catch (error) {
|
|
await logCommunication(`Software options error: ${error.message}`, 'error');
|
|
progressBar("0", "Error loading options: " + error.message, "#ff6666");
|
|
await closePort();
|
|
}
|
|
}
|
|
|
|
function displaySoftwareOptions(options) {
|
|
const grid = document.getElementById("softwareOptionsGrid");
|
|
grid.innerHTML = "";
|
|
|
|
options.forEach((option, index) => {
|
|
const price = parseFloat(option.price);
|
|
const isFree = price === 0;
|
|
const isCurrent = option.is_current === true || option.is_current === 1;
|
|
|
|
// Create card with gradient background
|
|
const card = document.createElement("div");
|
|
card.style.cssText = `
|
|
background: ${isCurrent ? 'linear-gradient(135deg, #f5f5f5 0%, #e8e8e8 100%)' : 'linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%)'};
|
|
border-radius: 4px;
|
|
padding: 25px 20px;
|
|
transition: all 0.3s ease;
|
|
display: flex;
|
|
flex-direction: column;
|
|
position: relative;
|
|
overflow: visible;
|
|
transform: translateY(0px);
|
|
box-shadow: ${isCurrent ? '0 4px 12px rgba(0,0,0,0.08)' : '0 8px 20px rgba(0,0,0,0.12)'};
|
|
opacity: ${isCurrent ? '0.7' : '1'};
|
|
pointer-events: ${isCurrent ? 'none' : 'auto'};
|
|
min-height: 320px;
|
|
`;
|
|
|
|
if (!isCurrent) {
|
|
card.onmouseenter = () => {
|
|
card.style.transform = 'translateY(-8px) scale(1.02)';
|
|
card.style.boxShadow = '0 12px 28px rgba(0,0,0,0.2)';
|
|
card.style.borderColor = isFree ? '#038f5a' : '#FF4500';
|
|
};
|
|
card.onmouseleave = () => {
|
|
card.style.transform = 'translateY(0) scale(1)';
|
|
card.style.boxShadow = '0 8px 20px rgba(0,0,0,0.12)';
|
|
card.style.borderColor = isFree ? '#04AA6D' : '#FF6B35';
|
|
};
|
|
}
|
|
|
|
// Badge for current/free/paid - VISIBLE
|
|
const badge = document.createElement("div");
|
|
badge.style.cssText = `
|
|
position: absolute;
|
|
top: -10px;
|
|
right: 20px;
|
|
background: ${isCurrent ? '#6c757d' : (isFree ? 'linear-gradient(135deg, #04AA6D 0%, #038f5a 100%)' : 'linear-gradient(135deg, #FF6B35 0%, #FF4500 100%)')};
|
|
color: white;
|
|
padding: 8px 16px;
|
|
border-radius: 20px;
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
letter-spacing: 0.5px;
|
|
text-transform: uppercase;
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
`;
|
|
|
|
if (isCurrent) {
|
|
badge.textContent = "INSTALLED";
|
|
} else if (isFree) {
|
|
badge.textContent = "FREE";
|
|
} else {
|
|
badge.textContent = "PREMIUM";
|
|
}
|
|
|
|
card.appendChild(badge);
|
|
|
|
// Name with icon
|
|
const name = document.createElement("h4");
|
|
name.style.cssText = `
|
|
margin: 0 0 12px 0;
|
|
color: #333;
|
|
font-size: 22px;
|
|
font-weight: 700;
|
|
`;
|
|
name.innerHTML = `<i class="fa-solid fa-microchip" style="color: ${isFree ? '#04AA6D' : '#FF6B35'}; margin-right: 8px;"></i>${option.name || "Software Update"}`;
|
|
card.appendChild(name);
|
|
|
|
// Version with enhanced styling
|
|
const version = document.createElement("div");
|
|
version.style.cssText = `
|
|
color: #666;
|
|
font-size: 14px;
|
|
margin-bottom: 15px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
`;
|
|
version.innerHTML = `<i class="fa-solid fa-code-branch" style="color: #999;"></i> <span style="font-weight: 500;">Version:</span> <strong>${option.version || "N/A"}</strong>`;
|
|
card.appendChild(version);
|
|
|
|
// Description with preserved newlines
|
|
const descContainer = document.createElement("div");
|
|
descContainer.style.cssText = `
|
|
color: #555;
|
|
font-size: 13px;
|
|
line-height: 1.7;
|
|
margin: 0 0 20px 0;
|
|
flex-grow: 1;
|
|
white-space: pre-line;
|
|
`;
|
|
descContainer.textContent = option.description || "No description available";
|
|
card.appendChild(descContainer);
|
|
|
|
// Price section
|
|
const priceSection = document.createElement("div");
|
|
priceSection.style.cssText = `
|
|
border-top: 2px solid ${isFree ? '#04AA6D20' : '#FF6B3520'};
|
|
padding-top: 20px;
|
|
margin-top: auto;
|
|
`;
|
|
|
|
const priceText = document.createElement("div");
|
|
priceText.style.cssText = `
|
|
font-size: ${isCurrent ? '18px' : '28px'};
|
|
font-weight: ${isCurrent ? '600' : '800'};
|
|
color: ${isCurrent ? '#6c757d' : (isFree ? '#04AA6D' : '#FF6B35')};
|
|
margin-bottom: 15px;
|
|
text-align: center;
|
|
letter-spacing: 0.5px;
|
|
`;
|
|
|
|
if (isCurrent) {
|
|
priceText.innerHTML = '<i class="fa-solid fa-check-circle"></i> INSTALLED';
|
|
} else {
|
|
priceText.innerHTML = isFree
|
|
? 'Free'
|
|
: `${option.currency || "€"} ${price.toFixed(2)}`;
|
|
}
|
|
|
|
priceSection.appendChild(priceText);
|
|
|
|
// Action button with gradient for paid
|
|
const actionBtn = document.createElement("button");
|
|
actionBtn.className = "btn";
|
|
actionBtn.style.cssText = `
|
|
width: 100%;
|
|
background: ${isCurrent ? '#6c757d' : (isFree ? 'linear-gradient(135deg, #04AA6D 0%, #038f5a 100%)' : 'linear-gradient(135deg, #FF6B35 0%, #FF4500 100%)')};
|
|
color: white;
|
|
border: none;
|
|
cursor: ${isCurrent ? 'not-allowed' : 'pointer'};
|
|
transition: all 0.3s ease;
|
|
opacity: ${isCurrent ? '0.5' : '1'};
|
|
box-shadow: ${isCurrent ? 'none' : '0 4px 12px rgba(0,0,0,0.15)'};
|
|
letter-spacing: 0.5px;
|
|
text-transform: uppercase;
|
|
`;
|
|
|
|
if (isCurrent) {
|
|
actionBtn.innerHTML = '<i class="fa-solid fa-check"></i> Currently Installed';
|
|
actionBtn.disabled = true;
|
|
} else if (isFree) {
|
|
actionBtn.innerHTML = '<i class="fa-solid fa-download"></i>';
|
|
actionBtn.onclick = () => selectUpgrade(option);
|
|
actionBtn.onmouseenter = () => {
|
|
actionBtn.style.background = 'linear-gradient(135deg, #038f5a 0%, #026b43 100%)';
|
|
actionBtn.style.transform = 'translateY(-2px)';
|
|
actionBtn.style.boxShadow = '0 6px 16px rgba(0,0,0,0.2)';
|
|
};
|
|
actionBtn.onmouseleave = () => {
|
|
actionBtn.style.background = 'linear-gradient(135deg, #04AA6D 0%, #038f5a 100%)';
|
|
actionBtn.style.transform = 'translateY(0)';
|
|
actionBtn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
|
|
};
|
|
} else {
|
|
actionBtn.innerHTML = '<i class="fa-solid fa-shopping-cart"></i>';
|
|
actionBtn.onclick = () => selectUpgrade(option);
|
|
actionBtn.onmouseenter = () => {
|
|
actionBtn.style.background = 'linear-gradient(135deg, #FF4500 0%, #CC3700 100%)';
|
|
actionBtn.style.transform = 'translateY(-2px)';
|
|
actionBtn.style.boxShadow = '0 6px 16px rgba(255,107,53,0.4)';
|
|
};
|
|
actionBtn.onmouseleave = () => {
|
|
actionBtn.style.background = 'linear-gradient(135deg, #FF6B35 0%, #FF4500 100%)';
|
|
actionBtn.style.transform = 'translateY(0)';
|
|
actionBtn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
|
|
};
|
|
}
|
|
|
|
priceSection.appendChild(actionBtn);
|
|
|
|
card.appendChild(priceSection);
|
|
grid.appendChild(card);
|
|
});
|
|
}
|
|
|
|
async function selectUpgrade(option) {
|
|
const price = parseFloat(option.price || 0);
|
|
const isFree = price === 0;
|
|
|
|
// If paid upgrade, show payment modal first
|
|
if (!isFree) {
|
|
showPaymentModal(option);
|
|
return;
|
|
}
|
|
|
|
// Free upgrade - show confirmation modal first
|
|
showFreeInstallModal(option);
|
|
}
|
|
|
|
function showFreeInstallModal(option) {
|
|
// Create modal overlay
|
|
const modal = document.createElement("div");
|
|
modal.id = "freeInstallModal";
|
|
modal.style.cssText = `
|
|
display: flex;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0,0,0,0.5);
|
|
z-index: 1000;
|
|
align-items: center;
|
|
justify-content: center;
|
|
`;
|
|
|
|
// Create modal content
|
|
const modalContent = document.createElement("div");
|
|
modalContent.style.cssText = `
|
|
background: white;
|
|
border-radius: 8px;
|
|
max-width: 500px;
|
|
width: 90%;
|
|
max-height: 90vh;
|
|
overflow-y: auto;
|
|
margin: 20px;
|
|
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
|
|
`;
|
|
|
|
modalContent.innerHTML = `
|
|
<div style="padding: 25px; border-bottom: 1px solid #e0e0e0;">
|
|
<h3 style="margin: 0; color: #333;"><i class="fa-solid fa-download"></i> Confirm Software Installation</h3>
|
|
</div>
|
|
<div style="padding: 25px;">
|
|
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
|
|
<h4 style="margin: 0 0 10px 0; color: #333;">${option.name || "Software Update"}</h4>
|
|
<p style="margin: 0 0 5px 0; color: #666;">Version: <strong>${option.version || "N/A"}</strong></p>
|
|
<p style="margin: 0 0 15px 0; color: #666;">${option.description || ""}</p>
|
|
</div>
|
|
|
|
<form id="freeInstallForm" style="display: flex; flex-direction: column; gap: 15px;">
|
|
<div>
|
|
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 500;">Name *</label>
|
|
<input type="text" name="name" id="freeInstallName" required style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
|
|
</div>
|
|
|
|
<div>
|
|
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 500;">Email *</label>
|
|
<input type="email" name="email" id="freeInstallEmail" required style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
|
|
</div>
|
|
|
|
<div>
|
|
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 500;">Address *</label>
|
|
<input type="text" name="address" id="freeInstallAddress" required placeholder="Street and number" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; margin-bottom: 10px;">
|
|
<input type="text" name="city" id="freeInstallCity" required placeholder="City" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; margin-bottom: 10px;">
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
|
<input type="text" name="postal" id="freeInstallPostal" required placeholder="Postal code" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
|
|
<input type="text" name="country" id="freeInstallCountry" required placeholder="Country" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
|
|
</div>
|
|
</div>
|
|
|
|
<div style="display: flex; gap: 10px; margin-top: 10px;">
|
|
<button type="button" id="cancelFreeInstall" style="flex: 1; padding: 12px; background: #6c757d; color: white; border: none; border-radius: 6px; font-size: 20px; font-weight: 600; cursor: pointer;">
|
|
<i class="fa-solid fa-times"></i>
|
|
</button>
|
|
<button type="submit" style="flex: 1; padding: 12px; background: #04AA6D; color: white; border: none; border-radius: 6px; font-size: 20px; font-weight: 600; cursor: pointer;">
|
|
<i class="fa-solid fa-check"></i>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
`;
|
|
|
|
modal.appendChild(modalContent);
|
|
document.body.appendChild(modal);
|
|
|
|
// Prefill form with customer data from sessionStorage if available
|
|
const savedCustomerData = sessionStorage.getItem('customerData');
|
|
if (savedCustomerData) {
|
|
try {
|
|
const customerData = JSON.parse(savedCustomerData);
|
|
if (customerData.name) document.getElementById("freeInstallName").value = customerData.name;
|
|
if (customerData.email) document.getElementById("freeInstallEmail").value = customerData.email;
|
|
if (customerData.address) document.getElementById("freeInstallAddress").value = customerData.address;
|
|
if (customerData.city) document.getElementById("freeInstallCity").value = customerData.city;
|
|
if (customerData.postal) document.getElementById("freeInstallPostal").value = customerData.postal;
|
|
if (customerData.country) document.getElementById("freeInstallCountry").value = customerData.country;
|
|
} catch (e) {
|
|
console.warn('Error parsing saved customer data:', e);
|
|
}
|
|
}
|
|
|
|
// Close modal on cancel
|
|
document.getElementById("cancelFreeInstall").onclick = () => {
|
|
document.body.removeChild(modal);
|
|
};
|
|
|
|
// Handle form submission
|
|
document.getElementById("freeInstallForm").onsubmit = async (e) => {
|
|
e.preventDefault();
|
|
const formData = new FormData(e.target);
|
|
const customerData = {
|
|
name: formData.get("name"),
|
|
email: formData.get("email"),
|
|
address: formData.get("address"),
|
|
city: formData.get("city"),
|
|
postal: formData.get("postal"),
|
|
country: formData.get("country"),
|
|
version_id: option.version_id,
|
|
price: 0,
|
|
currency: option.currency || "€"
|
|
};
|
|
|
|
// Save customer data to sessionStorage for future use
|
|
sessionStorage.setItem('customerData', JSON.stringify({
|
|
name: customerData.name,
|
|
email: customerData.email,
|
|
address: customerData.address,
|
|
city: customerData.city,
|
|
postal: customerData.postal,
|
|
country: customerData.country
|
|
}));
|
|
|
|
// Close modal
|
|
document.body.removeChild(modal);
|
|
|
|
// Proceed to download and install with customer data
|
|
await downloadAndInstallSoftware(option, customerData);
|
|
};
|
|
}
|
|
|
|
function showPaymentModal(option) {
|
|
const price = parseFloat(option.price || 0);
|
|
const currency = option.currency || "€";
|
|
|
|
// Create modal overlay
|
|
const modal = document.createElement("div");
|
|
modal.id = "paymentModal";
|
|
modal.style.cssText = `
|
|
display: flex;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0,0,0,0.5);
|
|
z-index: 1000;
|
|
align-items: center;
|
|
justify-content: center;
|
|
`;
|
|
|
|
// Create modal content
|
|
const modalContent = document.createElement("div");
|
|
modalContent.style.cssText = `
|
|
background: white;
|
|
border-radius: 8px;
|
|
max-width: 500px;
|
|
width: 90%;
|
|
max-height: 90vh;
|
|
overflow-y: auto;
|
|
margin: 20px;
|
|
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
|
|
`;
|
|
|
|
modalContent.innerHTML = `
|
|
<div style="padding: 25px; border-bottom: 1px solid #e0e0e0;">
|
|
<h3 style="margin: 0; color: #333;"><i class="fa-solid fa-shopping-cart"></i> Purchase Software Upgrade</h3>
|
|
</div>
|
|
<div style="padding: 25px;">
|
|
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
|
|
<h4 style="margin: 0 0 10px 0; color: #333;">${option.name || "Software Update"}</h4>
|
|
<p style="margin: 0 0 5px 0; color: #666;">Version: <strong>${option.version || "N/A"}</strong></p>
|
|
<p style="margin: 0 0 15px 0; color: #666;">${option.description || ""}</p>
|
|
<div style="font-size: 24px; font-weight: bold; color: #04AA6D;">
|
|
${currency} ${price.toFixed(2)}
|
|
</div>
|
|
</div>
|
|
|
|
<form id="paymentForm" style="display: flex; flex-direction: column; gap: 15px;">
|
|
<div>
|
|
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 500;">Name *</label>
|
|
<input type="text" name="name" id="paymentName" required style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
|
|
</div>
|
|
|
|
<div>
|
|
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 500;">Email *</label>
|
|
<input type="email" name="email" id="paymentEmail" required style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
|
|
</div>
|
|
|
|
<div>
|
|
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 500;">Address *</label>
|
|
<input type="text" name="address" id="paymentAddress" required placeholder="Street and number" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; margin-bottom: 10px;">
|
|
<input type="text" name="city" id="paymentCity" required placeholder="City" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; margin-bottom: 10px;">
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
|
<input type="text" name="postal" id="paymentPostal" required placeholder="Postal code" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
|
|
<input type="text" name="country" id="paymentCountry" required placeholder="Country" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label style="display: block; margin-bottom: 5px; color: #333; font-weight: 500;">Payment Method *</label>
|
|
<select name="payment_method" required style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
|
|
<option value="">Select payment method</option>
|
|
${typeof MOLLIE_ENABLED !== 'undefined' && MOLLIE_ENABLED ? '<option value="credit_card">Credit Card</option>' : ''}
|
|
${typeof PAYPAL_ENABLED !== 'undefined' && PAYPAL_ENABLED ? '<option value="paypal">PayPal</option>' : ''}
|
|
${typeof PAY_ON_DELIVERY_ENABLED !== 'undefined' && PAY_ON_DELIVERY_ENABLED ? '<option value="bank_transfer">Bank Transfer</option>' : ''}
|
|
</select>
|
|
</div>
|
|
|
|
<div style="display: flex; gap: 10px; margin-top: 10px;">
|
|
<button type="button" id="cancelPayment" style="flex: 1; padding: 12px; background: #6c757d; color: white; border: none; border-radius: 6px; font-size: 20px; font-weight: 600; cursor: pointer;">
|
|
<i class="fa-solid fa-times"></i>
|
|
</button>
|
|
<button type="submit" style="flex: 1; padding: 12px; background: #04AA6D; color: white; border: none; border-radius: 6px; font-size: 20px; font-weight: 600; cursor: pointer;">
|
|
<i class="fa-solid fa-credit-card"></i>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
`;
|
|
|
|
modal.appendChild(modalContent);
|
|
document.body.appendChild(modal);
|
|
|
|
// Prefill form with customer data from sessionStorage if available
|
|
const savedCustomerData = sessionStorage.getItem('customerData');
|
|
if (savedCustomerData) {
|
|
try {
|
|
const customerData = JSON.parse(savedCustomerData);
|
|
if (customerData.name) document.getElementById("paymentName").value = customerData.name;
|
|
if (customerData.email) document.getElementById("paymentEmail").value = customerData.email;
|
|
if (customerData.address) document.getElementById("paymentAddress").value = customerData.address;
|
|
if (customerData.city) document.getElementById("paymentCity").value = customerData.city;
|
|
if (customerData.postal) document.getElementById("paymentPostal").value = customerData.postal;
|
|
if (customerData.country) document.getElementById("paymentCountry").value = customerData.country;
|
|
} catch (e) {
|
|
console.warn('Error parsing saved customer data:', e);
|
|
}
|
|
}
|
|
|
|
// Close modal on cancel
|
|
document.getElementById("cancelPayment").onclick = () => {
|
|
document.body.removeChild(modal);
|
|
};
|
|
|
|
// Handle form submission
|
|
document.getElementById("paymentForm").onsubmit = async (e) => {
|
|
e.preventDefault();
|
|
const formData = new FormData(e.target);
|
|
const paymentMethod = formData.get("payment_method");
|
|
|
|
// Auto-determine payment provider based on payment method
|
|
let paymentProvider = 'mollie'; // default
|
|
if (paymentMethod === 'paypal') {
|
|
paymentProvider = 'paypal';
|
|
} else if (paymentMethod === 'credit_card' || paymentMethod === 'bank_transfer') {
|
|
paymentProvider = 'mollie';
|
|
}
|
|
|
|
const paymentData = {
|
|
name: formData.get("name"),
|
|
email: formData.get("email"),
|
|
address: formData.get("address"),
|
|
city: formData.get("city"),
|
|
postal: formData.get("postal"),
|
|
country: formData.get("country"),
|
|
payment_method: paymentMethod,
|
|
payment_provider: paymentProvider,
|
|
version_id: option.version_id,
|
|
price: price,
|
|
currency: currency
|
|
};
|
|
|
|
// Save customer data to sessionStorage for future use
|
|
sessionStorage.setItem('customerData', JSON.stringify({
|
|
name: paymentData.name,
|
|
email: paymentData.email,
|
|
address: paymentData.address,
|
|
city: paymentData.city,
|
|
postal: paymentData.postal,
|
|
country: paymentData.country
|
|
}));
|
|
|
|
await processPayment(paymentData, option, modal);
|
|
};
|
|
}
|
|
|
|
async function logSoftwareInstallationToHistory(option, customerData = null) {
|
|
try {
|
|
const serviceToken = document.getElementById("servicetoken")?.innerHTML || '';
|
|
|
|
// Create payload matching the structure expected by v2/history
|
|
const payload = {
|
|
version: option.version,
|
|
version_id: option.version_id,
|
|
name: option.name,
|
|
description: option.description,
|
|
HW: deviceHwVersion,
|
|
HEX_FW: option.version,
|
|
price: option.price || 0,
|
|
currency: option.currency || "€",
|
|
installation_source: 'softwaretool'
|
|
};
|
|
|
|
// Add customer data if provided
|
|
if (customerData) {
|
|
payload.customer = {
|
|
name: customerData.name,
|
|
email: customerData.email,
|
|
address: customerData.address,
|
|
city: customerData.city,
|
|
postal: customerData.postal,
|
|
country: customerData.country
|
|
};
|
|
}
|
|
|
|
const historyData = {
|
|
sn: deviceSerialNumber,
|
|
type: 'firmware',
|
|
sn_service: 'Portal',
|
|
payload: payload
|
|
};
|
|
|
|
const url = link + '/v2/history';
|
|
const bearer = 'Bearer ' + serviceToken;
|
|
|
|
console.log("Logging installation to history:", historyData);
|
|
await logCommunication(`Logging software installation to history: ${JSON.stringify(historyData)}`, 'sent');
|
|
|
|
const response = await fetch(url, {
|
|
method: 'POST',
|
|
withCredentials: true,
|
|
credentials: 'include',
|
|
headers: {
|
|
'Authorization': bearer,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(historyData)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
console.warn('Failed to log to history:', response.status);
|
|
await logCommunication(`Failed to log to history: ${response.status}`, 'error');
|
|
} else {
|
|
const result = await response.json();
|
|
console.log("History logged successfully:", result);
|
|
await logCommunication(`History logged successfully: ${JSON.stringify(result)}`, 'received');
|
|
}
|
|
} catch (error) {
|
|
console.warn('Error logging to history:', error);
|
|
await logCommunication(`Error logging to history: ${error.message}`, 'error');
|
|
// Don't block the installation if history logging fails
|
|
}
|
|
}
|
|
|
|
async function processPayment(paymentData, option, modal) {
|
|
try {
|
|
progressBar("10", "Processing payment...", "#04AA6D");
|
|
|
|
// SECURITY: Only send serial_number and version_id
|
|
// Server will calculate price to prevent tampering
|
|
const paymentRequest = {
|
|
serial_number: deviceSerialNumber,
|
|
version_id: option.version_id,
|
|
payment_method: paymentData.payment_method,
|
|
payment_provider: paymentData.payment_provider,
|
|
user_data: paymentData // name, email, address, etc.
|
|
};
|
|
|
|
// Debug logging
|
|
if (typeof DEBUG !== 'undefined' && DEBUG) {
|
|
console.log("=== DEBUG: Payment Request ===");
|
|
console.log("Serial Number:", deviceSerialNumber);
|
|
console.log("Version ID:", option.version_id);
|
|
console.log("Payment Method:", paymentData.payment_method);
|
|
console.log("Payment Provider:", paymentData.payment_provider);
|
|
console.log("User Data:", paymentData);
|
|
console.log("Request payload:", paymentRequest);
|
|
}
|
|
|
|
await logCommunication(`Payment initiated for version ${option.version_id} via ${paymentData.payment_provider}`, 'sent');
|
|
|
|
// Call payment API (handles both Mollie and PayPal)
|
|
const response = await fetch(link + "/v2/payment", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": "Bearer " + document.getElementById("servicetoken").textContent
|
|
},
|
|
body: JSON.stringify(paymentRequest)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
if (typeof DEBUG !== 'undefined' && DEBUG) {
|
|
console.error("DEBUG: Payment API error:", errorData);
|
|
}
|
|
throw new Error(errorData.error || "Failed to create payment");
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (typeof DEBUG !== 'undefined' && DEBUG) {
|
|
console.log("=== DEBUG: Payment Response ===");
|
|
console.log("Result:", result);
|
|
console.log("Checkout URL:", result.checkout_url);
|
|
console.log("Payment ID:", result.payment_id);
|
|
}
|
|
|
|
if (result.checkout_url) {
|
|
await logCommunication(`Redirecting to ${paymentData.payment_provider} payment: ${result.payment_id}`, 'sent');
|
|
|
|
if (typeof DEBUG !== 'undefined' && DEBUG) {
|
|
console.log(`DEBUG: Redirecting to ${paymentData.payment_provider} checkout...`);
|
|
}
|
|
|
|
// Close modal before redirect
|
|
document.body.removeChild(modal);
|
|
|
|
// Redirect to payment checkout page
|
|
window.location.href = result.checkout_url;
|
|
} else {
|
|
throw new Error(result.error || "No checkout URL received");
|
|
}
|
|
|
|
} catch (error) {
|
|
if (typeof DEBUG !== 'undefined' && DEBUG) {
|
|
console.error("DEBUG: Payment processing error:", error);
|
|
}
|
|
await logCommunication(`Payment error: ${error.message}`, 'error');
|
|
progressBar("0", "Payment failed: " + error.message, "#ff6666");
|
|
alert("Payment failed: " + error.message);
|
|
}
|
|
}
|
|
|
|
async function downloadAndInstallSoftware(option, customerData = null) {
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
// SECURITY: Check if returning from payment - verify serial number
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const paymentId = urlParams.get('payment_id');
|
|
|
|
if (paymentId) {
|
|
try {
|
|
// Verify serial number matches payment
|
|
const response = await fetch(link + `/v2/payment?payment_id=${paymentId}`, {
|
|
method: "GET",
|
|
headers: {
|
|
"Authorization": "Bearer " + document.getElementById("servicetoken").textContent
|
|
}
|
|
});
|
|
|
|
if (response.ok) {
|
|
const paymentData = await response.json();
|
|
|
|
// Check if device serial number matches the one from payment
|
|
if (paymentData.serial_number && paymentData.serial_number !== deviceSerialNumber) {
|
|
const confirmed = confirm(
|
|
`WARNING: Different device detected!\n\n` +
|
|
`License was created for device: ${paymentData.serial_number}\n` +
|
|
`Currently connected device: ${deviceSerialNumber}\n\n` +
|
|
`The license is already applied to the original device. ` +
|
|
`Do you want to continue with this device anyway?`
|
|
);
|
|
|
|
if (!confirmed) {
|
|
progressBar("0", "Upload canceled by user", "#ff6666");
|
|
await logCommunication('Upload canceled - serial number mismatch', 'error');
|
|
return;
|
|
}
|
|
|
|
await logCommunication(`WARNING: Serial number mismatch detected (payment: ${paymentData.serial_number}, device: ${deviceSerialNumber})`, 'warning');
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Error verifying payment:", error);
|
|
// Don't block if verification fails - log and continue
|
|
await logCommunication(`Payment verification failed: ${error.message}`, 'warning');
|
|
}
|
|
}
|
|
|
|
selectedSoftwareUrl = option.source;
|
|
|
|
if (!selectedSoftwareUrl) {
|
|
alert("Error: No download URL available");
|
|
await logCommunication('No download URL available for selected option', 'error');
|
|
return;
|
|
}
|
|
|
|
console.log("Download URL:", selectedSoftwareUrl);
|
|
await logCommunication(`Downloading software from: ${selectedSoftwareUrl}`, 'sent');
|
|
|
|
// Log to v2/history before starting download (with customer data if provided)
|
|
await logSoftwareInstallationToHistory(option, customerData);
|
|
|
|
try {
|
|
// Download the software file
|
|
progressBar("10", "Downloading software...", "#04AA6D");
|
|
|
|
const response = await fetch(selectedSoftwareUrl, {
|
|
method: "GET",
|
|
headers: {
|
|
"Authorization": "Bearer " + document.getElementById("servicetoken").textContent
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
// Try to parse error message
|
|
const errorText = await response.text();
|
|
let errorMessage = `Download failed: ${response.status}`;
|
|
|
|
try {
|
|
const errorJson = JSON.parse(errorText);
|
|
errorMessage = errorJson.message || errorJson.error || errorMessage;
|
|
} catch (e) {
|
|
// Not JSON, use status text
|
|
}
|
|
|
|
console.error("Download failed:", response.status, errorText);
|
|
await logCommunication(`Download failed: ${response.status} - ${errorText}`, 'error');
|
|
progressBar("0", "Error: " + errorMessage, "#ff6666");
|
|
alert("Download failed: " + errorMessage);
|
|
return;
|
|
}
|
|
|
|
// Get the blob
|
|
const blob = await response.blob();
|
|
|
|
progressBar("50", "Download complete, preparing installation...", "#04AA6D");
|
|
await logCommunication(`Software downloaded successfully, size: ${blob.size} bytes`, 'received');
|
|
|
|
// Create a blob URL for the downloaded file
|
|
const blobUrl = URL.createObjectURL(blob);
|
|
|
|
// Set global variables expected by upload.js
|
|
window.firmwarelocation = blobUrl;
|
|
window.upgraded_version = option.version || "";
|
|
|
|
// DEBUG MODE: Don't auto-trigger upload, let user manually test
|
|
if (typeof DEBUG !== 'undefined' && DEBUG) {
|
|
// Show upload section and button for manual testing
|
|
document.getElementById("uploadSection").style.display = "block";
|
|
const uploadBtn = document.getElementById("uploadSoftware");
|
|
uploadBtn.style.display = "block";
|
|
uploadBtn.disabled = false;
|
|
|
|
progressBar("100", "DEBUG MODE: Download complete. Click 'Install Software' button to test upload.js manually", "#ff9800");
|
|
console.log("=== DEBUG MODE: Manual Upload Test ===");
|
|
console.log("Firmware downloaded successfully");
|
|
console.log("Blob size:", blob.size, "bytes");
|
|
console.log("Blob URL:", blobUrl);
|
|
console.log("window.firmwarelocation =", window.firmwarelocation);
|
|
console.log("window.upgraded_version =", window.upgraded_version);
|
|
console.log("Upload button is now visible and enabled");
|
|
console.log("Click the 'Install Software' button to test if upload.js can handle the file");
|
|
alert("DEBUG MODE: Download complete!\n\nBlob size: " + blob.size + " bytes\n\nClick the 'Install Software' button to test upload.js");
|
|
} else {
|
|
// PRODUCTION MODE: Hide button and show installation in progress
|
|
document.getElementById("uploadSection").style.display = "block";
|
|
const uploadBtn = document.getElementById("uploadSoftware");
|
|
uploadBtn.style.display = "none";
|
|
|
|
// Hide device version information during installation
|
|
const softwareOptions = document.getElementById("softwareOptions");
|
|
if (softwareOptions) {
|
|
softwareOptions.style.display = "none";
|
|
}
|
|
|
|
// Create installation status indicator
|
|
const installStatus = document.createElement("div");
|
|
installStatus.id = "installationStatus";
|
|
installStatus.style.cssText = `
|
|
text-align: center;
|
|
padding: 20px;
|
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
|
border-radius: 8px;
|
|
margin: 10px 0;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
`;
|
|
installStatus.innerHTML = `
|
|
<i class="fa-solid fa-spinner fa-spin" style="font-size: 32px; color: #04AA6D; margin-bottom: 10px;"></i>
|
|
<p style="margin: 0; font-size: 18px; font-weight: 600; color: #333;">Installing Software...</p>
|
|
<p style="margin: 5px 0 0 0; color: #666; font-size: 14px;">Please keep your device connected and do not close this page</p>
|
|
`;
|
|
|
|
// Insert status before the hidden upload section
|
|
document.getElementById("uploadSection").parentNode.insertBefore(installStatus, document.getElementById("uploadSection"));
|
|
|
|
progressBar("60", "Starting automatic installation...", "#04AA6D");
|
|
|
|
// Enable the upload button and automatically click it
|
|
setTimeout(() => {
|
|
uploadBtn.disabled = false;
|
|
|
|
// Start monitoring for completion
|
|
if (typeof startUploadMonitoring === 'function') {
|
|
startUploadMonitoring();
|
|
}
|
|
|
|
uploadBtn.click();
|
|
}, 1000);
|
|
}
|
|
|
|
} catch (error) {
|
|
await logCommunication(`Download error: ${error.message}`, 'error');
|
|
progressBar("0", "Error downloading software: " + error.message, "#ff6666");
|
|
alert("Error: " + error.message);
|
|
}
|
|
}
|