(function () {
window.AppItems = window.AppItems || [];
// --- Component: SFTP Connections ---
const section = {
title: "Connections",
html: `
<div class="conn-container">
<div class="conn-header">
<h2 style="margin:0;">🔐 SFTP Connections</h2>
<button class="conn-btn conn-btn-primary" onclick="openConnectionModal()">+ New Connection</button>
</div>
<div class="connections-grid" id="connectionsGrid"></div>
</div>
<!-- Add/Edit Connection Modal -->
<div class="conn-modal" id="connectionModal" aria-hidden="true">
<div class="conn-modal__backdrop" onclick="closeConnectionModal()"></div>
<div class="conn-modal__dialog">
<div class="conn-modal__header">
<h3 id="modalTitle">New Connection</h3>
<button class="conn-close-btn" onclick="closeConnectionModal()">×</button>
</div>
<form id="connectionForm">
<input type="hidden" id="connectionId">
<div class="conn-form-group">
<label class="conn-label">Connection Name</label>
<input type="text" class="conn-input" id="connName" placeholder="My Server" required>
</div>
<div class="conn-form-group">
<label class="conn-label">Host</label>
<input type="text" class="conn-input" id="connHost" placeholder="files.devbrewing.com" required>
</div>
<div class="conn-form-group">
<label class="conn-label">Port</label>
<input type="number" class="conn-input" id="connPort" value="22" required>
</div>
<div class="conn-form-group">
<label class="conn-label">Username</label>
<input type="text" class="conn-input" id="connUser" required>
</div>
<div class="conn-form-group">
<label class="conn-label">Password</label>
<div class="conn-pw-wrap">
<input type="password" class="conn-input" id="connPass" required>
<button type="button" class="conn-pw-toggle" onclick="toggleConnPassword()">👁️</button>
</div>
</div>
<div class="conn-form-actions">
<button type="submit" class="conn-btn conn-btn-primary">Save Connection</button>
<button type="button" class="conn-btn conn-btn-secondary" onclick="closeConnectionModal()">Cancel</button>
</div>
<div id="formMessage"></div>
</form>
</div>
</div>
<!-- Toast Container -->
<div class="conn-toast-container" id="connToastContainer"></div>
<style>
${document.querySelector('style')?.textContent || ''} /* preserves all your CSS */
</style>
`
};
window.AppItems.push(section);
// --- Local Storage Key ---
const STORAGE_KEY = 'sftp_connections';
// --- Helpers ---
function getConnections() {
const data = localStorage.getItem(STORAGE_KEY);
return data ? JSON.parse(data) : [];
}
function saveConnections(list) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(list));
}
function showToast(message, type = 'success', timeout = 2500) {
const container = document.getElementById('connToastContainer');
if (!container) return;
const toast = document.createElement('div');
toast.className = `conn-toast ${type}`;
toast.textContent = message;
container.appendChild(toast);
setTimeout(() => {
toast.style.animation = 'connToastOut 160ms ease forwards';
setTimeout(() => toast.remove(), 200);
}, timeout);
}
// --- Modal Handlers ---
window.openConnectionModal = function (id = null) {
const modal = document.getElementById('connectionModal');
const form = document.getElementById('connectionForm');
const title = document.getElementById('modalTitle');
form.reset();
document.getElementById('formMessage').innerHTML = '';
const connections = getConnections();
if (id) {
const conn = connections.find(c => c.id === id);
if (conn) {
title.textContent = 'Edit Connection';
document.getElementById('connectionId').value = conn.id;
document.getElementById('connName').value = conn.name;
document.getElementById('connHost').value = conn.host;
document.getElementById('connPort').value = conn.port;
document.getElementById('connUser').value = conn.username;
document.getElementById('connPass').value = conn.password;
}
} else {
title.textContent = 'New Connection';
}
modal.setAttribute('aria-hidden', 'false');
};
window.closeConnectionModal = () =>
document.getElementById('connectionModal').setAttribute('aria-hidden', 'true');
window.toggleConnPassword = () => {
const input = document.getElementById('connPass');
input.type = input.type === 'password' ? 'text' : 'password';
};
// --- Core Actions via AppStorage.driver ---
async function connectToServer(conn, card) {
try {
card.classList.add('connecting');
const res = await AppStorage.driver.connect(conn);
if (res.success) {
const all = getConnections();
all.forEach(c => c.active = false);
conn.active = true;
saveConnections(all);
renderConnections();
showToast(`✅ Connected to ${conn.name}`);
} else throw new Error(res.message);
} catch (err) {
showToast(`❌ Connection failed: ${err.message}`, 'error');
} finally {
card.classList.remove('connecting');
}
}
async function disconnectConnection(id) {
try {
await AppStorage.driver.disconnect();
const conns = getConnections();
conns.forEach(c => (c.active = false));
saveConnections(conns);
renderConnections();
showToast('Disconnected successfully');
} catch (err) {
showToast('Disconnect failed: ' + err.message, 'error');
}
}
// --- Render Connections ---
function renderConnections() {
const grid = document.getElementById('connectionsGrid');
if (!grid) return;
grid.innerHTML = '';
const conns = getConnections();
conns.forEach(conn => {
const card = document.createElement('div');
card.className = `conn-card ${conn.active ? 'active' : ''}`;
card.innerHTML = `
<div class="conn-status-bar"></div>
<div class="conn-card-header">
<div class="conn-card-title">${conn.name}</div>
</div>
<div class="conn-card-info">
${conn.host}:${conn.port}<br>${conn.username}
</div>
<span class="conn-status-badge ${conn.active ? 'active' : 'inactive'}">
${conn.active ? '🟢 Connected' : '⚫ Disconnected'}
</span>
`;
card.addEventListener('click', () =>
conn.active ? disconnectConnection(conn.id) : connectToServer(conn, card)
);
grid.appendChild(card);
});
// Add connection button
const addCard = document.createElement('div');
addCard.className = 'conn-card conn-add-card';
addCard.innerHTML = `<div class="conn-add-icon">+</div><div>Add Connection</div>`;
addCard.addEventListener('click', () => openConnectionModal());
grid.appendChild(addCard);
}
// --- Form Submission ---
document.addEventListener('submit', e => {
if (e.target.id === 'connectionForm') {
e.preventDefault();
const conns = getConnections();
const id = document.getElementById('connectionId').value || Date.now().toString();
const conn = {
id,
name: document.getElementById('connName').value,
host: document.getElementById('connHost').value,
port: parseInt(document.getElementById('connPort').value),
username: document.getElementById('connUser').value,
password: document.getElementById('connPass').value,
active: false
};
const existingIndex = conns.findIndex(c => c.id === id);
if (existingIndex >= 0) conns[existingIndex] = conn;
else conns.push(conn);
saveConnections(conns);
renderConnections();
closeConnectionModal();
showToast('Connection saved');
}
});
// --- Initialize Render when overlay opens ---
document.addEventListener('click', e => {
const btn = e.target.closest('.chip');
if (btn && btn.textContent.includes('Connections')) {
setTimeout(renderConnections, 100);
}
});
console.log('[connectionmanager.js] Cleaned up — uses SFTP driver via core');
})();