cccb20bd31
- Replace app icon (assets/icon.png) with Avigilon Alta "A" logo - Add Windows .ico version of the icon - Remove Test Connection button from API Connection section - Rename Start/Stop Cookie Proxy buttons to Start/Stop Proxy - Add .claude/ and nul to .gitignore Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
477 lines
16 KiB
JavaScript
477 lines
16 KiB
JavaScript
// Global variables to store session data
|
|
let sessionData = {
|
|
deploymentUrl: '',
|
|
cookies: null,
|
|
isConnected: false
|
|
};
|
|
|
|
// DOM elements
|
|
const connectionStatus = document.getElementById('connectionStatus');
|
|
const deviceStatus = document.getElementById('deviceStatus');
|
|
const deviceList = document.getElementById('deviceList');
|
|
const statusIndicator = document.getElementById('statusIndicator');
|
|
const deviceSearch = document.getElementById('deviceSearch');
|
|
|
|
// Connection buttons
|
|
const disconnectBtn = document.getElementById('disconnectBtn');
|
|
|
|
// Cookie proxy elements
|
|
const cookieDeviceUUID = document.getElementById('cookieDeviceUUID');
|
|
const cookieKey = document.getElementById('cookieKey');
|
|
const startCookieProxyBtn = document.getElementById('startCookieProxyBtn');
|
|
const stopCookieProxyBtn = document.getElementById('stopCookieProxyBtn');
|
|
|
|
// Track selected device
|
|
let selectedDevice = null;
|
|
let activeCookieProxyConnections = new Map(); // Track cookie-based proxy connections
|
|
let allDevices = []; // Store all devices for search functionality
|
|
|
|
// Event listeners
|
|
disconnectBtn.addEventListener('click', handleDisconnect);
|
|
|
|
// Cookie proxy event listeners
|
|
startCookieProxyBtn.addEventListener('click', handleStartCookieProxy);
|
|
stopCookieProxyBtn.addEventListener('click', handleStopCookieProxy);
|
|
|
|
// Cookie key input listener to update button states
|
|
cookieKey.addEventListener('input', updateCookieProxyButtonStates);
|
|
|
|
// Device search event listener
|
|
deviceSearch.addEventListener('input', handleDeviceSearch);
|
|
|
|
// Handle disconnect
|
|
function handleDisconnect() {
|
|
sessionData.isConnected = false;
|
|
sessionData.cookies = null;
|
|
sessionData.deploymentUrl = '';
|
|
selectedDevice = null;
|
|
activeCookieProxyConnections.clear();
|
|
allDevices = []; // Clear stored devices
|
|
|
|
updateConnectionStatus(false);
|
|
updateButtonStates();
|
|
clearDeviceList();
|
|
cookieDeviceUUID.value = '';
|
|
cookieKey.value = '';
|
|
deviceSearch.value = ''; // Clear search input
|
|
|
|
// Clear device status message
|
|
deviceStatus.style.display = 'none';
|
|
deviceStatus.textContent = '';
|
|
|
|
showStatus(connectionStatus, 'Disconnected from API', 'info');
|
|
}
|
|
|
|
// Handle start cookie proxy
|
|
async function handleStartCookieProxy() {
|
|
if (!selectedDevice) {
|
|
showStatus(connectionStatus, 'Please select a device first', 'error');
|
|
return;
|
|
}
|
|
|
|
const cookieKeyValue = cookieKey.value.trim();
|
|
if (!cookieKeyValue) {
|
|
showStatus(connectionStatus, 'Please enter a cookie key', 'error');
|
|
return;
|
|
}
|
|
|
|
if (!sessionData.deploymentUrl) {
|
|
showStatus(connectionStatus, 'Please connect to API first to get deployment URL', 'error');
|
|
return;
|
|
}
|
|
|
|
// Check if this device already has an active cookie connection
|
|
const deviceId = selectedDevice.guid || selectedDevice.id;
|
|
if (activeCookieProxyConnections.has(deviceId)) {
|
|
showStatus(connectionStatus, `Cookie proxy already running for device ${selectedDevice.name}`, 'warning');
|
|
return;
|
|
}
|
|
|
|
// Disable button during launch
|
|
startCookieProxyBtn.disabled = true;
|
|
showStatus(connectionStatus, `Starting cookie proxy for device ${selectedDevice.name}...`, 'info');
|
|
|
|
try {
|
|
const result = await window.electronAPI.launchCookieCameraProxy({
|
|
deploymentUrl: sessionData.deploymentUrl,
|
|
cookieKey: cookieKeyValue,
|
|
deviceUuid: deviceId
|
|
});
|
|
|
|
if (result.success) {
|
|
// Track this connection
|
|
activeCookieProxyConnections.set(deviceId, {
|
|
processId: result.processId,
|
|
deviceName: selectedDevice.name,
|
|
deviceId: deviceId,
|
|
startTime: Date.now(),
|
|
type: 'cookie'
|
|
});
|
|
|
|
updateCookieProxyButtonStates();
|
|
showStatus(connectionStatus, `${result.message} (Cookie proxy active for ${selectedDevice.name})`, 'success');
|
|
} else {
|
|
showStatus(connectionStatus, `Failed to start cookie proxy: ${result.message}`, 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Cookie proxy launch error:', error);
|
|
showStatus(connectionStatus, `Error launching cookie proxy: ${error.message}`, 'error');
|
|
} finally {
|
|
startCookieProxyBtn.disabled = false;
|
|
}
|
|
}
|
|
|
|
// Handle stop cookie proxy
|
|
async function handleStopCookieProxy() {
|
|
if (activeCookieProxyConnections.size === 0) {
|
|
showStatus(connectionStatus, 'No active cookie proxy connections found', 'warning');
|
|
return;
|
|
}
|
|
|
|
showStatus(connectionStatus, 'Stopping cookie proxy connections...', 'info');
|
|
|
|
try {
|
|
const result = await window.electronAPI.stopCameraProxy(null); // Stop all processes
|
|
|
|
if (result.success) {
|
|
activeCookieProxyConnections.clear();
|
|
showStatus(connectionStatus, 'Cookie proxy connections stopped successfully', 'success');
|
|
|
|
// Update visual indicators for all devices
|
|
const deviceItems = deviceList.querySelectorAll('.device-item');
|
|
deviceItems.forEach(item => {
|
|
item.classList.remove('cookie-proxy-active');
|
|
});
|
|
} else {
|
|
showStatus(connectionStatus, `Failed to stop cookie proxy: ${result.message}`, 'warning');
|
|
}
|
|
} catch (error) {
|
|
console.error('Stop cookie proxy error:', error);
|
|
showStatus(connectionStatus, 'Error stopping cookie proxy', 'error');
|
|
}
|
|
|
|
updateCookieProxyButtonStates();
|
|
}
|
|
|
|
// Update cookie proxy button states
|
|
function updateCookieProxyButtonStates() {
|
|
const hasSelectedDevice = selectedDevice !== null;
|
|
const hasCookieKey = cookieKey.value.trim().length > 0;
|
|
const hasActiveConnection = activeCookieProxyConnections.size > 0;
|
|
|
|
if (sessionData.isConnected && hasSelectedDevice && hasCookieKey) {
|
|
const deviceId = selectedDevice.guid || selectedDevice.id;
|
|
const isThisDeviceActive = activeCookieProxyConnections.has(deviceId);
|
|
|
|
startCookieProxyBtn.disabled = isThisDeviceActive;
|
|
stopCookieProxyBtn.disabled = !hasActiveConnection;
|
|
} else {
|
|
startCookieProxyBtn.disabled = true;
|
|
stopCookieProxyBtn.disabled = !hasActiveConnection;
|
|
}
|
|
}
|
|
|
|
// Update connection status indicator
|
|
function updateConnectionStatus(connected) {
|
|
const statusDot = statusIndicator.querySelector('.status-dot');
|
|
const statusText = statusIndicator.querySelector('.status-text');
|
|
|
|
if (connected) {
|
|
statusDot.className = 'status-dot online';
|
|
statusText.textContent = 'Connected';
|
|
} else {
|
|
statusDot.className = 'status-dot offline';
|
|
statusText.textContent = 'Disconnected';
|
|
}
|
|
}
|
|
|
|
// Update button states based on connection status
|
|
function updateButtonStates() {
|
|
if (sessionData.isConnected) {
|
|
disconnectBtn.disabled = false;
|
|
} else {
|
|
disconnectBtn.disabled = true;
|
|
startCookieProxyBtn.disabled = true;
|
|
stopCookieProxyBtn.disabled = true;
|
|
}
|
|
|
|
// Always update cookie proxy button states
|
|
updateCookieProxyButtonStates();
|
|
}
|
|
|
|
// Handle get devices (now called automatically)
|
|
async function handleGetDevices() {
|
|
if (!sessionData.isConnected) {
|
|
showStatus(deviceStatus, 'Please connect to the API first', 'error');
|
|
return;
|
|
}
|
|
|
|
showStatus(deviceStatus, 'Fetching devices...', 'info');
|
|
clearDeviceList();
|
|
|
|
try {
|
|
const result = await window.electronAPI.getDevices({
|
|
deploymentUrl: sessionData.deploymentUrl,
|
|
cookies: sessionData.cookies
|
|
});
|
|
|
|
if (result.success) {
|
|
// Filter devices to only show non-cloud cameras (localStorage = false)
|
|
const filteredDevices = result.devices.filter(device => {
|
|
// Check if device has capabilities and localStorage property
|
|
if (device.capabilities && device.capabilities.localStorage !== undefined) {
|
|
// Only show devices where localStorage is false (non-cloud cameras)
|
|
return device.capabilities.localStorage === false;
|
|
}
|
|
// If no capabilities or localStorage property, include the device (fallback)
|
|
return true;
|
|
});
|
|
|
|
const totalDevices = result.devices.length;
|
|
const filteredCount = filteredDevices.length;
|
|
const cloudDevicesHidden = totalDevices - filteredCount;
|
|
|
|
let statusMessage = `Found ${filteredCount} local camera${filteredCount !== 1 ? 's' : ''}`;
|
|
if (cloudDevicesHidden > 0) {
|
|
statusMessage += ` (${cloudDevicesHidden} cloud camera${cloudDevicesHidden !== 1 ? 's' : ''} hidden)`;
|
|
}
|
|
|
|
showStatus(deviceStatus, statusMessage, 'success');
|
|
|
|
// Store all devices for search functionality
|
|
allDevices = filteredDevices;
|
|
displayDevices(filteredDevices);
|
|
} else {
|
|
showStatus(deviceStatus, `Failed to get devices: ${result.message}`, 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Get devices error:', error);
|
|
showStatus(deviceStatus, `Error getting devices: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
// Helper function to determine device status from API data
|
|
function getDeviceStatus(device) {
|
|
// Check for live.display_status first (Alta API standard)
|
|
if (device.live && device.live.display_status) {
|
|
const status = device.live.display_status.toLowerCase();
|
|
|
|
// Handle color-based status responses from Alta API
|
|
if (status === 'green') {
|
|
return {
|
|
isOnline: true,
|
|
statusText: 'Online'
|
|
};
|
|
} else if (status === 'red') {
|
|
return {
|
|
isOnline: false,
|
|
statusText: 'Offline'
|
|
};
|
|
} else if (status === 'yellow' || status === 'orange') {
|
|
return {
|
|
isOnline: false,
|
|
statusText: 'Warning'
|
|
};
|
|
}
|
|
|
|
// Handle text-based status responses
|
|
return {
|
|
isOnline: status === 'online' || status === 'live' || status === 'connected',
|
|
statusText: status === 'online' || status === 'live' || status === 'connected' ? 'Online' : 'Offline'
|
|
};
|
|
}
|
|
|
|
// Fallback to other possible status fields
|
|
if (device.online !== undefined) {
|
|
return {
|
|
isOnline: device.online,
|
|
statusText: device.online ? 'Online' : 'Offline'
|
|
};
|
|
}
|
|
|
|
if (device.status) {
|
|
const status = device.status.toLowerCase();
|
|
|
|
// Handle color-based status in other fields
|
|
if (status === 'green') {
|
|
return {
|
|
isOnline: true,
|
|
statusText: 'Online'
|
|
};
|
|
} else if (status === 'red') {
|
|
return {
|
|
isOnline: false,
|
|
statusText: 'Offline'
|
|
};
|
|
}
|
|
|
|
return {
|
|
isOnline: status === 'online' || status === 'live' || status === 'connected',
|
|
statusText: status === 'online' || status === 'live' || status === 'connected' ? 'Online' : 'Offline'
|
|
};
|
|
}
|
|
|
|
// Default to offline if no status information available
|
|
return {
|
|
isOnline: false,
|
|
statusText: 'Offline'
|
|
};
|
|
}
|
|
|
|
// Handle device search
|
|
function handleDeviceSearch() {
|
|
const searchTerm = deviceSearch.value.toLowerCase().trim();
|
|
|
|
if (!searchTerm) {
|
|
// Show all devices if search is empty
|
|
displayDevices(allDevices);
|
|
return;
|
|
}
|
|
|
|
// Filter devices based on search term
|
|
const filteredDevices = allDevices.filter(device => {
|
|
const deviceName = (device.name || '').toLowerCase();
|
|
const deviceId = (device.guid || device.id || '').toLowerCase();
|
|
const deviceType = (device.type || '').toLowerCase();
|
|
const deviceModel = (device.model || '').toLowerCase();
|
|
const deviceIp = (device.ipAddress || '').toLowerCase();
|
|
|
|
return deviceName.includes(searchTerm) ||
|
|
deviceId.includes(searchTerm) ||
|
|
deviceType.includes(searchTerm) ||
|
|
deviceModel.includes(searchTerm) ||
|
|
deviceIp.includes(searchTerm);
|
|
});
|
|
|
|
displayDevices(filteredDevices);
|
|
}
|
|
|
|
// Display devices in the UI
|
|
function displayDevices(devices) {
|
|
clearDeviceList();
|
|
|
|
if (!devices || devices.length === 0) {
|
|
const searchTerm = deviceSearch.value.toLowerCase().trim();
|
|
const message = searchTerm ? 'No devices match your search' : 'No devices found';
|
|
deviceList.innerHTML = `<p class="no-devices">${message}</p>`;
|
|
return;
|
|
}
|
|
|
|
devices.forEach((device, index) => {
|
|
const deviceItem = document.createElement('div');
|
|
deviceItem.className = 'device-item';
|
|
deviceItem.dataset.deviceIndex = index;
|
|
deviceItem.dataset.deviceId = device.guid || device.id;
|
|
|
|
const deviceStatus = getDeviceStatus(device);
|
|
const deviceId = device.guid || device.id;
|
|
const isCookieProxyActive = activeCookieProxyConnections.has(deviceId);
|
|
|
|
// Add cookie-proxy-active class if this device has an active cookie connection
|
|
if (isCookieProxyActive) {
|
|
deviceItem.classList.add('cookie-proxy-active');
|
|
}
|
|
|
|
deviceItem.innerHTML = `
|
|
<div class="device-name">${escapeHtml(device.name || 'Unnamed Device')}</div>
|
|
<div class="device-status-dot ${deviceStatus.isOnline ? 'online' : 'offline'}"></div>
|
|
`;
|
|
|
|
// Add click handler for device selection
|
|
deviceItem.addEventListener('click', () => selectDevice(device, deviceItem));
|
|
|
|
deviceList.appendChild(deviceItem);
|
|
});
|
|
|
|
// Update cookie proxy button states after displaying devices
|
|
updateCookieProxyButtonStates();
|
|
}
|
|
|
|
// Handle device selection
|
|
function selectDevice(device, deviceElement) {
|
|
// Remove previous selection
|
|
const previousSelected = deviceList.querySelector('.device-item.selected');
|
|
if (previousSelected) {
|
|
previousSelected.classList.remove('selected');
|
|
}
|
|
|
|
// Select current device
|
|
deviceElement.classList.add('selected');
|
|
selectedDevice = device;
|
|
|
|
// Auto-populate cookie device UUID field
|
|
const uuid = device.guid || device.id || '';
|
|
cookieDeviceUUID.value = uuid;
|
|
|
|
// Show device selection feedback with connection status
|
|
if (uuid) {
|
|
const isCookieActive = activeCookieProxyConnections.has(uuid);
|
|
const cookieStatusText = isCookieActive ? ' (COOKIE PROXY ACTIVE)' : '';
|
|
showStatus(connectionStatus, `Selected device: ${device.name || 'Unnamed Device'} (UUID: ${uuid})${cookieStatusText}`, 'info');
|
|
}
|
|
|
|
updateCookieProxyButtonStates();
|
|
}
|
|
|
|
// Utility functions
|
|
function showStatus(element, message, type) {
|
|
element.textContent = message;
|
|
element.className = `status-message ${type}`;
|
|
element.style.display = 'block';
|
|
}
|
|
|
|
function clearDeviceList() {
|
|
deviceList.innerHTML = '<p class="placeholder-text">Connect to API to load devices</p>';
|
|
selectedDevice = null;
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
if (typeof text !== 'string') return text;
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
// Handle cookie received from Chrome extension via local HTTP bridge
|
|
async function handleExtensionCookie(data) {
|
|
const { deploymentUrl, cookies, cookieValue } = data;
|
|
|
|
// If already connected, disconnect first
|
|
if (sessionData.isConnected) {
|
|
handleDisconnect();
|
|
}
|
|
|
|
// Set session state from extension cookie
|
|
sessionData.deploymentUrl = deploymentUrl;
|
|
sessionData.cookies = cookies;
|
|
sessionData.isConnected = true;
|
|
|
|
showStatus(connectionStatus, `Connected via Chrome extension to ${deploymentUrl}`, 'success');
|
|
updateConnectionStatus(true);
|
|
updateButtonStates();
|
|
|
|
// Auto-populate cookie key
|
|
cookieKey.value = cookieValue;
|
|
updateCookieProxyButtonStates();
|
|
|
|
// Fetch devices
|
|
try {
|
|
await handleGetDevices();
|
|
} catch (err) {
|
|
console.error('Failed to fetch devices after extension cookie:', err);
|
|
showStatus(deviceStatus, 'Connected, but failed to load devices.', 'warning');
|
|
}
|
|
}
|
|
|
|
// Initialize the app
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
console.log('Alta Video Camera Proxy loaded');
|
|
|
|
// Initialize connection status
|
|
updateConnectionStatus(false);
|
|
updateButtonStates();
|
|
|
|
// Listen for cookies pushed from Chrome extension
|
|
window.electronAPI.onExtensionCookie(handleExtensionCookie);
|
|
});
|