update - add internal SFTP Endpoint

This commit is contained in:
MCHost
2025-07-09 21:18:49 -04:00
parent f40dd2a3ba
commit b982e45300
2 changed files with 177 additions and 144 deletions

View File

@@ -4,7 +4,7 @@ import { checkConnectionStatus, checkGeyserStatus, checkSftpStatus } from './sta
import { apiRequest } from './api.js'; import { apiRequest } from './api.js';
const clients = new Map(); const clients = new Map();
const staticEndpoints = ['log', 'website', 'map', 'my-link-cache', 'my-geyser-cache', 'my-sftp-cache', 'my-link', 'my-geyser-link', 'my-sftp', 'holesail-hashes']; const staticEndpoints = ['log', 'website', 'map', 'my-link-cache', 'my-geyser-cache', 'my-sftp-cache', 'my-link', 'my-geyser-link', 'my-sftp', 'holesail-hashes', 'internal-sftp'];
const dynamicEndpoints = ['hello', 'time', 'mod-list']; const dynamicEndpoints = ['hello', 'time', 'mod-list'];
// Helper function to start Docker stats interval // Helper function to start Docker stats interval
@@ -80,10 +80,12 @@ async function fetchAndSendUpdate(ws, endpoint, client, docker) {
const container = docker.getContainer(client.user); const container = docker.getContainer(client.user);
const inspect = await container.inspect(); const inspect = await container.inspect();
if (inspect.State.Status !== 'running') { if (inspect.State.Status !== 'running') {
console.warn(`Container ${client.user} is not running for endpoint ${endpoint}`);
ws.send(JSON.stringify({ type: endpoint, error: `Container ${client.user} is not running` })); ws.send(JSON.stringify({ type: endpoint, error: `Container ${client.user} is not running` }));
return; return;
} }
} catch (error) { } catch (error) {
console.error(`Failed to check container status for ${endpoint} for ${client.user}:`, error.message);
ws.send(JSON.stringify({ type: endpoint, error: `Failed to check container status: ${error.message}` })); ws.send(JSON.stringify({ type: endpoint, error: `Failed to check container status: ${error.message}` }));
return; return;
} }
@@ -99,6 +101,12 @@ async function fetchAndSendUpdate(ws, endpoint, client, docker) {
return; return;
} }
if (endpoint === 'internal-sftp' && client.cache['internal-sftp']) {
console.log(`Using cached internal-sftp data for ${client.user}`);
ws.send(JSON.stringify({ type: 'internal-sftp', data: client.cache['internal-sftp'] }));
return;
}
if (endpoint === 'holesail-hashes') { if (endpoint === 'holesail-hashes') {
try { try {
const [hashResponse, geyserHashResponse, sftpHashResponse] = await Promise.all([ const [hashResponse, geyserHashResponse, sftpHashResponse] = await Promise.all([
@@ -132,6 +140,7 @@ async function fetchAndSendUpdate(ws, endpoint, client, docker) {
return; return;
} }
try {
const response = await apiRequest(`/${endpoint}`, client.apiKey); const response = await apiRequest(`/${endpoint}`, client.apiKey);
if (!response.error) { if (!response.error) {
if (endpoint === 'time') client.cache['time'] = response; if (endpoint === 'time') client.cache['time'] = response;
@@ -148,6 +157,7 @@ async function fetchAndSendUpdate(ws, endpoint, client, docker) {
ws.send(JSON.stringify({ type: 'connection-status', error: `Container ${client.user} is not running` })); ws.send(JSON.stringify({ type: 'connection-status', error: `Container ${client.user} is not running` }));
} }
} catch (error) { } catch (error) {
console.error(`Error checking connection status for ${client.user}:`, error.message);
ws.send(JSON.stringify({ type: 'connection-status', error: `Failed to check container status: ${error.message}` })); ws.send(JSON.stringify({ type: 'connection-status', error: `Failed to check container status: ${error.message}` }));
} }
} }
@@ -165,6 +175,7 @@ async function fetchAndSendUpdate(ws, endpoint, client, docker) {
ws.send(JSON.stringify({ type: 'geyser-status', error: `Container ${client.user} is not running` })); ws.send(JSON.stringify({ type: 'geyser-status', error: `Container ${client.user} is not running` }));
} }
} catch (error) { } catch (error) {
console.error(`Error checking geyser status for ${client.user}:`, error.message);
ws.send(JSON.stringify({ type: 'geyser-status', error: `Failed to check container status: ${error.message}` })); ws.send(JSON.stringify({ type: 'geyser-status', error: `Failed to check container status: ${error.message}` }));
} }
} }
@@ -192,9 +203,9 @@ async function fetchAndSendUpdate(ws, endpoint, client, docker) {
ipAddress ipAddress
})); }));
} }
// Add IP address to my-sftp-cache response
response.ipAddress = ipAddress; response.ipAddress = ipAddress;
} catch (error) { } catch (error) {
console.error(`Error checking sftp status for ${client.user}:`, error.message);
ws.send(JSON.stringify({ ws.send(JSON.stringify({
type: 'sftp-status', type: 'sftp-status',
error: `Failed to check container status: ${error.message}` error: `Failed to check container status: ${error.message}`
@@ -202,14 +213,24 @@ async function fetchAndSendUpdate(ws, endpoint, client, docker) {
} }
} }
} }
if (endpoint === 'internal-sftp') {
client.cache['internal-sftp'] = response;
}
if (ws.readyState === ws.OPEN) { if (ws.readyState === ws.OPEN) {
ws.send(JSON.stringify({ type: endpoint, data: response })); ws.send(JSON.stringify({ type: endpoint, data: response }));
} }
} else { } else {
console.error(`API error for /${endpoint}: ${response.error}`);
if (ws.readyState === ws.OPEN) { if (ws.readyState === ws.OPEN) {
ws.send(JSON.stringify({ type: endpoint, error: response.error })); ws.send(JSON.stringify({ type: endpoint, error: response.error }));
} }
} }
} catch (error) {
console.error(`Error fetching /${endpoint} for ${client.user}:`, error.message);
if (ws.readyState === ws.OPEN) {
ws.send(JSON.stringify({ type: endpoint, error: `Failed to fetch ${endpoint}: ${error.message}` }));
}
}
} }
async function manageStatusChecks(ws, client, user, docker) { async function manageStatusChecks(ws, client, user, docker) {
@@ -229,12 +250,14 @@ async function manageStatusChecks(ws, client, user, docker) {
}); });
if (!isRunning || user === 'Unknown') { if (!isRunning || user === 'Unknown') {
['my-link-cache', 'my-geyser-cache', 'my-sftp-cache'].forEach((sub) => { ['my-link-cache', 'my-geyser-cache', 'my-sftp-cache', 'internal-sftp'].forEach((sub) => {
if (client.subscriptions.has(sub)) { if (client.subscriptions.has(sub)) {
ws.send(JSON.stringify({ type: sub.replace('-cache', '-status'), error: `Container ${user} is not running or user unknown` })); const statusType = sub.replace('-cache', '-status');
console.warn(`Container ${user} is not running or user unknown for subscription ${sub}`);
ws.send(JSON.stringify({ type: statusType, error: `Container ${user} is not running or user unknown` }));
} }
}); });
if (!isRunning && (client.subscriptions.has('my-link-cache') || client.subscriptions.has('my-geyser-cache') || client.subscriptions.has('my-sftp-cache')) && user !== 'Unknown') { if (!isRunning && (client.subscriptions.has('my-link-cache') || client.subscriptions.has('my-geyser-cache') || client.subscriptions.has('my-sftp-cache') || client.subscriptions.has('internal-sftp')) && user !== 'Unknown') {
console.log(`Starting container status monitor for ${user}`); console.log(`Starting container status monitor for ${user}`);
client.statusCheckMonitorInterval = setInterval(async () => { client.statusCheckMonitorInterval = setInterval(async () => {
try { try {
@@ -368,6 +391,20 @@ export function handleWebSocket(ws, req, docker) {
console.log(`Received pong from client for user ${client.user || 'unknown'}`); console.log(`Received pong from client for user ${client.user || 'unknown'}`);
}); });
// Send internal-sftp data immediately on connection if subscribed
(async () => {
try {
if (client.subscriptions.has('internal-sftp')) {
await fetchAndSendUpdate(ws, 'internal-sftp', client, docker);
}
} catch (error) {
console.error('Error sending initial internal-sftp data:', error.message);
if (ws.readyState === ws.OPEN) {
ws.send(JSON.stringify({ type: 'internal-sftp', error: `Failed to fetch initial internal-sftp data: ${error.message}` }));
}
}
})();
ws.on('message', async (message) => { ws.on('message', async (message) => {
try { try {
const data = JSON.parse(message.toString()); const data = JSON.parse(message.toString());
@@ -380,6 +417,18 @@ export function handleWebSocket(ws, req, docker) {
}); });
console.log(`Client subscriptions: ${Array.from(client.subscriptions)}`); console.log(`Client subscriptions: ${Array.from(client.subscriptions)}`);
// Send internal-sftp data immediately after subscription
if (client.subscriptions.has('internal-sftp')) {
try {
await fetchAndSendUpdate(ws, 'internal-sftp', client, docker);
} catch (error) {
console.error(`Error fetching internal-sftp data after subscription for ${client.user}:`, error.message);
if (ws.readyState === ws.OPEN) {
ws.send(JSON.stringify({ type: 'internal-sftp', error: `Failed to fetch internal-sftp data: ${error.message}` }));
}
}
}
let hello = client.cache['hello'] || await apiRequest('/hello', client.apiKey); let hello = client.cache['hello'] || await apiRequest('/hello', client.apiKey);
if (!client.cache['hello'] && !hello.error) client.cache['hello'] = hello; if (!client.cache['hello'] && !hello.error) client.cache['hello'] = hello;
@@ -439,7 +488,7 @@ export function handleWebSocket(ws, req, docker) {
client.intervals.push(setInterval(async () => { client.intervals.push(setInterval(async () => {
try { try {
for (const endpoint of staticEndpoints) { for (const endpoint of staticEndpoints) {
if (client.subscriptions.has(endpoint) && endpoint !== 'holesail-hashes') { if (client.subscriptions.has(endpoint) && endpoint !== 'holesail-hashes' && endpoint !== 'internal-sftp') {
await fetchAndSendUpdate(ws, endpoint, client, docker); await fetchAndSendUpdate(ws, endpoint, client, docker);
} }
} }
@@ -448,6 +497,28 @@ export function handleWebSocket(ws, req, docker) {
} }
}, parseInt(process.env.STATIC_ENDPOINTS_INTERVAL_MS, 10))); }, parseInt(process.env.STATIC_ENDPOINTS_INTERVAL_MS, 10)));
// Add 10-minute interval for internal-sftp updates
if (client.subscriptions.has('internal-sftp')) {
const sftpIntervalId = setInterval(async () => {
try {
if (ws.readyState !== ws.OPEN) {
console.warn(`WebSocket not open (state: ${ws.readyState}) for ${user}, clearing internal-sftp interval ${sftpIntervalId}`);
clearInterval(sftpIntervalId);
client.intervals = client.intervals.filter(id => id !== sftpIntervalId);
return;
}
await fetchAndSendUpdate(ws, 'internal-sftp', client, docker);
} catch (error) {
console.error(`Error in internal-sftp interval for ${user}:`, error.message);
if (ws.readyState === ws.OPEN) {
ws.send(JSON.stringify({ type: 'internal-sftp', error: `Failed to fetch internal-sftp data: ${error.message}` }));
}
}
}, 10 * 60 * 1000); // 10 minutes
client.intervals.push(sftpIntervalId);
console.log(`Internal SFTP interval ID ${sftpIntervalId} added for ${user}`);
}
if (client.subscriptions.has('list-players') && user !== 'Unknown') { if (client.subscriptions.has('list-players') && user !== 'Unknown') {
try { try {
const container = docker.getContainer(user); const container = docker.getContainer(user);
@@ -520,6 +591,18 @@ export function handleWebSocket(ws, req, docker) {
console.log(`Starting docker logs stream for new user ${client.user}`); console.log(`Starting docker logs stream for new user ${client.user}`);
await streamContainerLogs(docker, ws, client.user, client); await streamContainerLogs(docker, ws, client.user, client);
} }
// Refresh internal-sftp data for new user
if (client.subscriptions.has('internal-sftp')) {
try {
await fetchAndSendUpdate(ws, 'internal-sftp', client, docker);
} catch (error) {
console.error(`Error refreshing internal-sftp data for new user ${client.user}:`, error.message);
if (ws.readyState === ws.OPEN) {
ws.send(JSON.stringify({ type: 'internal-sftp', error: `Failed to fetch internal-sftp data: ${error.message}` }));
}
}
}
} }
} else if (data.type === 'request') { } else if (data.type === 'request') {
const { requestId, endpoint, method, body } = data; const { requestId, endpoint, method, body } = data;
@@ -540,6 +623,7 @@ export function handleWebSocket(ws, req, docker) {
response = client.user === 'Unknown' ? { error: 'User not identified' } : await createBackup(docker, client.user); response = client.user === 'Unknown' ? { error: 'User not identified' } : await createBackup(docker, client.user);
} else { } else {
response = await apiRequest(endpoint, client.apiKey, method, body); response = await apiRequest(endpoint, client.apiKey, method, body);
console.log(`API request response for ${endpoint}:`, response);
} }
if (ws.readyState === ws.OPEN) { if (ws.readyState === ws.OPEN) {
ws.send(JSON.stringify({ requestId, ...response })); ws.send(JSON.stringify({ requestId, ...response }));
@@ -696,6 +780,7 @@ export function handleWebSocket(ws, req, docker) {
console.log('Processing refresh request'); console.log('Processing refresh request');
delete client.cache['hello']; delete client.cache['hello'];
delete client.cache['time']; delete client.cache['time'];
delete client.cache['internal-sftp'];
await Promise.all([ await Promise.all([
...staticEndpoints.filter(e => client.subscriptions.has(e)).map(e => fetchAndSendUpdate(ws, e, client, docker)), ...staticEndpoints.filter(e => client.subscriptions.has(e)).map(e => fetchAndSendUpdate(ws, e, client, docker)),
...dynamicEndpoints.filter(e => client.subscriptions.has(e)).map(e => fetchAndSendUpdate(ws, e, client, docker)), ...dynamicEndpoints.filter(e => client.subscriptions.has(e)).map(e => fetchAndSendUpdate(ws, e, client, docker)),
@@ -739,6 +824,9 @@ export function handleWebSocket(ws, req, docker) {
if (client.subscriptions.has('my-sftp-cache')) { if (client.subscriptions.has('my-sftp-cache')) {
ws.send(JSON.stringify({ type: 'sftp-status', error: `Container ${client.user} is not running` })); ws.send(JSON.stringify({ type: 'sftp-status', error: `Container ${client.user} is not running` }));
} }
if (client.subscriptions.has('internal-sftp')) {
ws.send(JSON.stringify({ type: 'internal-sftp', error: `Container ${client.user} is not running` }));
}
} }
} catch (error) { } catch (error) {
console.error(`Error during refresh for ${client.user}:`, error.message); console.error(`Error during refresh for ${client.user}:`, error.message);

View File

@@ -28,11 +28,8 @@ document.addEventListener('DOMContentLoaded', () => {
'enable-status' 'enable-status'
]; ];
const sections = document.querySelectorAll('.section-bg'); const sections = document.querySelectorAll('.section-bg');
const observer = new IntersectionObserver((entries) => { const observer = new IntersectionObserver((entries) => {
entries.forEach((entry, index) => { entries.forEach((entry, index) => {
if (entry.isIntersecting) { if (entry.isIntersecting) {
@@ -50,7 +47,6 @@ document.addEventListener('DOMContentLoaded', () => {
observer.observe(section); observer.observe(section);
}); });
function throttle(fn, wait) { function throttle(fn, wait) {
let lastTime = 0; let lastTime = 0;
return function (...args) { return function (...args) {
@@ -66,13 +62,6 @@ document.addEventListener('DOMContentLoaded', () => {
const hamburger = document.querySelector('.hamburger'); const hamburger = document.querySelector('.hamburger');
const mobileNav = document.querySelector('[data-mobile-nav]'); const mobileNav = document.querySelector('[data-mobile-nav]');
const navLinks = mobileNav.querySelectorAll('a'); const navLinks = mobileNav.querySelectorAll('a');
sections.forEach(section => {
section.style.transform = 'translateY(30px)';
section.style.opacity = '0';
section.style.transition = 'transform 0.7s ease-out, opacity 0.7s ease-out';
observer.observe(section);
});
// Debounce function to prevent rapid clicks // Debounce function to prevent rapid clicks
function debounce(fn, wait) { function debounce(fn, wait) {
@@ -101,6 +90,7 @@ document.addEventListener('DOMContentLoaded', () => {
hamburger.classList.remove('active'); hamburger.classList.remove('active');
} }
}); });
const elements = { const elements = {
loginPage: document.getElementById('loginPage'), loginPage: document.getElementById('loginPage'),
loginApiKey: document.getElementById('loginApiKey'), loginApiKey: document.getElementById('loginApiKey'),
@@ -459,6 +449,7 @@ document.addEventListener('DOMContentLoaded', () => {
fitAddon = null; fitAddon = null;
} }
} }
function connectWebSocket() { function connectWebSocket() {
if (ws && ws.readyState === WebSocket.OPEN) { if (ws && ws.readyState === WebSocket.OPEN) {
return; return;
@@ -485,7 +476,8 @@ document.addEventListener('DOMContentLoaded', () => {
'my-sftp-cache', 'my-sftp-cache',
'backup', 'backup',
'sftp-status', 'sftp-status',
'holesail-hashes' 'holesail-hashes',
'internal-sftp'
] ]
})); }));
responseTimeout = setTimeout(() => { responseTimeout = setTimeout(() => {
@@ -498,6 +490,19 @@ document.addEventListener('DOMContentLoaded', () => {
try { try {
clearTimeout(responseTimeout); clearTimeout(responseTimeout);
const message = JSON.parse(event.data); const message = JSON.parse(event.data);
// Handle internal-sftp message immediately to save credentials
if (message.type === 'internal-sftp' && message.data?.hostname && message.data?.port && message.data?.user && message.data?.password) {
sftpCredentials = {
hostname: message.data.hostname,
port: message.data.port,
user: message.data.user,
password: message.data.password
};
if (!hasAttemptedSftpConnect) {
hasAttemptedSftpConnect = true;
connectSftp();
}
}
if (message.requestId && pendingRequests.has(message.requestId)) { if (message.requestId && pendingRequests.has(message.requestId)) {
const { resolve, reject, notification, key } = pendingRequests.get(message.requestId); const { resolve, reject, notification, key } = pendingRequests.get(message.requestId);
pendingRequests.delete(message.requestId); pendingRequests.delete(message.requestId);
@@ -610,39 +615,6 @@ document.addEventListener('DOMContentLoaded', () => {
} }
} }
function updateDockerUI(message) {
if (message.error) {
console.log('Docker error detected, setting status to Not Running');
if (elements.serverStatus) elements.serverStatus.textContent = 'Not Running';
toggleSections('Not Running');
return;
}
const memoryPercent = parseFloat(message.data?.memory?.percent) || 0;
if (state.memoryPercent !== memoryPercent && elements.memoryPercent && memoryMeter) {
memoryMeter.data.datasets[0].data = [memoryPercent, 100 - memoryPercent];
memoryMeter.update();
elements.memoryPercent.textContent = `${memoryPercent.toFixed(1)}%`;
state.memoryPercent = memoryPercent;
}
const cpuPercent = parseFloat(message.data?.cpu) || 0;
if (state.cpuPercent !== cpuPercent && elements.cpuPercent && cpuMeter) {
const scaledCpuPercent = Math.min((cpuPercent / 600) * 100, 100);
cpuMeter.data.datasets[0].data = [scaledCpuPercent, 100 - scaledCpuPercent];
cpuMeter.update();
elements.cpuPercent.textContent = `${cpuPercent.toFixed(1)}%`;
state.cpuPercent = cpuPercent;
}
const status = message.data?.status || 'Unknown';
if (elements.serverStatus) {
elements.serverStatus.textContent = status;
state.serverStatus = status;
toggleSections(status);
}
}
function toggleSections(status) { function toggleSections(status) {
const sections = document.querySelectorAll('.section-bg'); const sections = document.querySelectorAll('.section-bg');
const serverStatusSection = document.querySelector('.section-bg[data-section="server-status"]'); const serverStatusSection = document.querySelector('.section-bg[data-section="server-status"]');
@@ -653,7 +625,6 @@ document.addEventListener('DOMContentLoaded', () => {
const stopBtn = elements.stopBtn; const stopBtn = elements.stopBtn;
const restartBtn = elements.restartBtn; const restartBtn = elements.restartBtn;
const sftpBrowserSection = elements.sftpBrowserSection; const sftpBrowserSection = elements.sftpBrowserSection;
// Menu items in desktop and mobile nav
const refreshBtn = elements.refresh || document.getElementById('refresh'); const refreshBtn = elements.refresh || document.getElementById('refresh');
const mobileRefreshBtn = elements.mobileRefresh || document.getElementById('mobileRefresh'); const mobileRefreshBtn = elements.mobileRefresh || document.getElementById('mobileRefresh');
const backupBtn = elements.backupBtn; const backupBtn = elements.backupBtn;
@@ -672,13 +643,11 @@ document.addEventListener('DOMContentLoaded', () => {
} }
if (status.toLowerCase() !== 'running') { if (status.toLowerCase() !== 'running') {
// Hide all sections except Server Status
sections.forEach(section => { sections.forEach(section => {
if (section !== serverStatusSection) { if (section !== serverStatusSection) {
section.classList.add('hidden'); section.classList.add('hidden');
} }
}); });
// Hide control buttons
if (editPropertiesBtn) { if (editPropertiesBtn) {
editPropertiesBtn.classList.add('hidden'); editPropertiesBtn.classList.add('hidden');
editPropertiesBtn.style.display = 'none'; editPropertiesBtn.style.display = 'none';
@@ -701,7 +670,6 @@ document.addEventListener('DOMContentLoaded', () => {
if (sftpBrowserSection) { if (sftpBrowserSection) {
sftpBrowserSection.style.display = 'none'; sftpBrowserSection.style.display = 'none';
} }
// Hide Refresh, Backup, and Logout buttons in desktop nav
if (refreshBtn) { if (refreshBtn) {
refreshBtn.classList.add('hidden'); refreshBtn.classList.add('hidden');
refreshBtn.style.display = 'none'; refreshBtn.style.display = 'none';
@@ -714,7 +682,6 @@ document.addEventListener('DOMContentLoaded', () => {
logoutBtn.classList.add('hidden'); logoutBtn.classList.add('hidden');
logoutBtn.style.display = 'none'; logoutBtn.style.display = 'none';
} }
// Hide Refresh, Backup, and Logout menu items in mobile nav
if (mobileRefreshBtn && mobileRefreshBtn.parentElement) { if (mobileRefreshBtn && mobileRefreshBtn.parentElement) {
mobileRefreshBtn.parentElement.classList.add('hidden'); mobileRefreshBtn.parentElement.classList.add('hidden');
} }
@@ -729,11 +696,9 @@ document.addEventListener('DOMContentLoaded', () => {
state.hasShownStartNotification = true; state.hasShownStartNotification = true;
} }
} else { } else {
// Show all sections
sections.forEach(section => { sections.forEach(section => {
section.classList.remove('hidden'); section.classList.remove('hidden');
}); });
// Show control buttons
if (editPropertiesBtn) { if (editPropertiesBtn) {
editPropertiesBtn.classList.remove('hidden'); editPropertiesBtn.classList.remove('hidden');
editPropertiesBtn.style.display = ''; editPropertiesBtn.style.display = '';
@@ -751,12 +716,11 @@ document.addEventListener('DOMContentLoaded', () => {
} }
if (restartBtn) { if (restartBtn) {
restartBtn.disabled = false; restartBtn.disabled = false;
stopBtn.classList.remove('disabled-btn'); restartBtn.classList.remove('disabled-btn');
} }
if (sftpBrowserSection) { if (sftpBrowserSection) {
sftpBrowserSection.style.display = 'block'; sftpBrowserSection.style.display = 'block';
} }
// Show Refresh, Backup, and Logout buttons in desktop nav
if (refreshBtn) { if (refreshBtn) {
refreshBtn.classList.remove('hidden'); refreshBtn.classList.remove('hidden');
refreshBtn.style.display = ''; refreshBtn.style.display = '';
@@ -769,7 +733,6 @@ document.addEventListener('DOMContentLoaded', () => {
logoutBtn.classList.remove('hidden'); logoutBtn.classList.remove('hidden');
logoutBtn.style.display = ''; logoutBtn.style.display = '';
} }
// Show Refresh, Backup, and Logout menu items in mobile nav
if (mobileRefreshBtn && mobileRefreshBtn.parentElement) { if (mobileRefreshBtn && mobileRefreshBtn.parentElement) {
mobileRefreshBtn.parentElement.classList.remove('hidden'); mobileRefreshBtn.parentElement.classList.remove('hidden');
} }
@@ -840,21 +803,11 @@ document.addEventListener('DOMContentLoaded', () => {
function updateSftpCacheUI(message) { function updateSftpCacheUI(message) {
if (message.data?.hostname && message.data?.port && message.data?.user && message.data?.password) { if (message.data?.hostname && message.data?.port && message.data?.user && message.data?.password) {
sftpCredentials = {
hostname: state.user,
port: 22,
user: message.data.user,
password: message.data.password
};
const sftpLinkText = `${message.data.hostname}:${message.data.port}`; const sftpLinkText = `${message.data.hostname}:${message.data.port}`;
if (state.sftpLink !== sftpLinkText && elements.sftpLink) { if (state.sftpLink !== sftpLinkText && elements.sftpLink) {
elements.sftpLink.textContent = sftpLinkText; elements.sftpLink.textContent = sftpLinkText;
state.sftpLink = sftpLinkText; state.sftpLink = sftpLinkText;
} }
if (!hasAttemptedSftpConnect) {
hasAttemptedSftpConnect = true;
connectSftp();
}
} }
} }
@@ -931,12 +884,10 @@ document.addEventListener('DOMContentLoaded', () => {
state.currentPlayers = players; state.currentPlayers = players;
const isSinglePlayer = players.length <= 1; const isSinglePlayer = players.length <= 1;
// Initialize state for tracking action button visibility if not already present
state.actionButtonStates = state.actionButtonStates || {}; state.actionButtonStates = state.actionButtonStates || {};
const playerListHtml = players.length > 0 const playerListHtml = players.length > 0
? players.map(player => { ? players.map(player => {
// Default to hidden action buttons unless state says otherwise
const isActionButtonsVisible = state.actionButtonStates[player] || false; const isActionButtonsVisible = state.actionButtonStates[player] || false;
return ` return `
<div class="flex items-center space-x-4 mb-2"> <div class="flex items-center space-x-4 mb-2">
@@ -964,7 +915,6 @@ document.addEventListener('DOMContentLoaded', () => {
elements.playerList.innerHTML = playerListHtml; elements.playerList.innerHTML = playerListHtml;
state.playerList = playerListHtml; state.playerList = playerListHtml;
// Remove existing event listeners to prevent duplication
const toggleButtons = document.querySelectorAll('.toggle-actions'); const toggleButtons = document.querySelectorAll('.toggle-actions');
const closeButtons = document.querySelectorAll('.close-actions'); const closeButtons = document.querySelectorAll('.close-actions');
toggleButtons.forEach(button => { toggleButtons.forEach(button => {
@@ -974,7 +924,6 @@ document.addEventListener('DOMContentLoaded', () => {
button.replaceWith(button.cloneNode(true)); button.replaceWith(button.cloneNode(true));
}); });
// Add event listeners for toggle actions
document.querySelectorAll('.toggle-actions').forEach(button => { document.querySelectorAll('.toggle-actions').forEach(button => {
button.addEventListener('click', () => { button.addEventListener('click', () => {
const player = button.getAttribute('data-player').trim(); const player = button.getAttribute('data-player').trim();
@@ -982,12 +931,11 @@ document.addEventListener('DOMContentLoaded', () => {
if (actionDiv) { if (actionDiv) {
actionDiv.classList.remove('hidden'); actionDiv.classList.remove('hidden');
button.classList.add('hidden'); button.classList.add('hidden');
state.actionButtonStates[player] = true; // Update state state.actionButtonStates[player] = true;
} }
}); });
}); });
// Add event listeners for close actions
document.querySelectorAll('.close-actions').forEach(button => { document.querySelectorAll('.close-actions').forEach(button => {
button.addEventListener('click', () => { button.addEventListener('click', () => {
const player = button.getAttribute('data-player').trim(); const player = button.getAttribute('data-player').trim();
@@ -996,7 +944,7 @@ document.addEventListener('DOMContentLoaded', () => {
if (actionDiv && toggleButton) { if (actionDiv && toggleButton) {
actionDiv.classList.add('hidden'); actionDiv.classList.add('hidden');
toggleButton.classList.remove('hidden'); toggleButton.classList.remove('hidden');
state.actionButtonStates[player] = false; // Update state state.actionButtonStates[player] = false;
} }
}); });
}); });
@@ -1290,7 +1238,6 @@ document.addEventListener('DOMContentLoaded', () => {
} }
function showMainContent() { function showMainContent() {
// Check if apiKey exists in localStorage
const apiKey = localStorage.getItem('apiKey'); const apiKey = localStorage.getItem('apiKey');
if (!apiKey) { if (!apiKey) {
console.error('API key not found in localStorage'); console.error('API key not found in localStorage');
@@ -1309,7 +1256,6 @@ document.addEventListener('DOMContentLoaded', () => {
elements.loginPage.classList.add('hidden'); elements.loginPage.classList.add('hidden');
elements.mainContent.classList.remove('hidden'); elements.mainContent.classList.remove('hidden');
// Verify buttons exist
const logoutBtn = document.getElementById('logoutBtn'); const logoutBtn = document.getElementById('logoutBtn');
const mobileLogoutBtn = document.getElementById('mobileLogoutBtn'); const mobileLogoutBtn = document.getElementById('mobileLogoutBtn');
@@ -1325,10 +1271,8 @@ document.addEventListener('DOMContentLoaded', () => {
console.error('Mobile logout button (#mobileLogoutBtn) not found'); console.error('Mobile logout button (#mobileLogoutBtn) not found');
} }
// Remove any existing logout listeners to prevent duplicates
document.removeEventListener('click', handleLogoutClick); document.removeEventListener('click', handleLogoutClick);
// Event delegation for logout buttons
function handleLogoutClick(event) { function handleLogoutClick(event) {
if (event.target.id === 'logoutBtn' || event.target.id === 'mobileLogoutBtn') { if (event.target.id === 'logoutBtn' || event.target.id === 'mobileLogoutBtn') {
console.log(`Logout button clicked: ${event.target.id}`); console.log(`Logout button clicked: ${event.target.id}`);
@@ -1910,6 +1854,7 @@ document.addEventListener('DOMContentLoaded', () => {
elements.editPropertiesModal.appendChild(modal); elements.editPropertiesModal.appendChild(modal);
} }
function renderPropertiesList(properties, filter = '') { function renderPropertiesList(properties, filter = '') {
const propertiesList = elements.propertiesFields.querySelector('#propertiesList'); const propertiesList = elements.propertiesFields.querySelector('#propertiesList');
propertiesList.innerHTML = ''; propertiesList.innerHTML = '';