<?php
// Force login + disable caching
session_start();
require_once __DIR__ . '/../core/db_config.php';
// Anti-cache headers
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Expires: 0');
// Redirect if not logged in
if (empty($_SESSION['username'])) {
$redirect = urlencode($_SERVER['REQUEST_URI'] ?? '/');
header("Location: /core/auth/login.php?redirect={$redirect}");
exit;
}
// Cache-busting helper
function asset($path) {
$isAbsolute = strlen($path) && $path[0] === '/';
$abs = $isAbsolute ? rtrim($_SERVER['DOCUMENT_ROOT'], '/') . $path : __DIR__ . '/' . $path;
$v = is_file($abs) ? filemtime($abs) : time();
return $path . '?v=' . $v;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>App Index</title>
<!-- Shared overlay CSS -->
<link rel="stylesheet" href="<?= asset('/core/css/overlay.css') ?>">
<style>
html, body { height: 100%; margin: 0; overscroll-behavior-y: contain; }
body {
background: #0b0f14;
color: #e6edf3;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Inter, "Helvetica Neue", Arial;
}
.body-lock { overflow: hidden !important; touch-action: none; }
.topbar {
position: sticky; top: 0; z-index: 5;
background: linear-gradient(180deg, rgba(11,15,20,.95), rgba(11,15,20,.85));
border-bottom: 1px solid #1e2633;
backdrop-filter: blur(6px);
}
.topbar-inner {
max-width: 1000px;
margin: 0 auto;
padding: .75rem;
display: flex;
align-items: center;
gap: .75rem;
}
#buttonRow {
flex: 1 1 auto;
min-width: 0;
display: flex;
gap: .75rem;
align-items: center;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: thin;
-webkit-overflow-scrolling: touch;
}
#menuContainer { flex: 0 0 auto; margin-left: .25rem; position: relative; }
.chip {
flex: 0 0 auto;
border: 1px solid #2a3648;
background: #1a2332;
color: #e6edf3;
padding: .55rem .9rem;
border-radius: 999px;
font-weight: 600;
cursor: pointer;
transition: background .15s ease;
}
.chip:hover { background: #263244; }
.container { max-width: 1000px; margin: 1.25rem auto; padding: 0 .75rem; }
.lead { color: #9aa4b2; }
.menu-trigger { width: 38px; text-align: center; }
.menu-list {
display: none;
position: absolute;
right: 0; top: calc(100% + 6px);
background: #1a2332;
border: 1px solid #2a3648;
border-radius: 10px;
min-width: 180px;
padding: .25rem 0;
z-index: 9999;
box-shadow: 0 10px 30px rgba(0,0,0,.3);
}
.menu-list.open { display: block; }
.menu-item {
display: block;
width: 100%;
text-align: left;
background: none;
border: none;
color: #e6edf3;
padding: .6rem 1rem;
cursor: pointer;
font: inherit;
}
.menu-item:hover { background: #263244; }
/* SFTP Form */
form.sftp-form {
margin-top: 2rem;
background: #1a2332;
border: 1px solid #2a3648;
border-radius: 12px;
padding: 1.5rem;
max-width: 460px;
}
form.sftp-form label { display: block; margin-top: 0.75rem; font-weight: 600; }
form.sftp-form input[type="text"],
form.sftp-form input[type="password"],
form.sftp-form input[type="number"] {
width: 100%;
padding: 0.6rem;
margin-top: 0.3rem;
border-radius: 6px;
border: 1px solid #2a3648;
background: #0f1725;
color: #e6edf3;
}
.pw-wrap { position: relative; display: flex; width: 100%; }
.pw-toggle {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
background: transparent;
border: none;
color: #9aa4b2;
cursor: pointer;
padding: 6px;
border-radius: 6px;
}
form.sftp-form button[type="submit"] {
margin-top: 1rem;
padding: 0.6rem 1.2rem;
border: none;
border-radius: 8px;
background: linear-gradient(135deg,#3b82f6,#9333ea);
color: white;
font-weight: 600;
cursor: pointer;
}
.msg { margin-top: 1rem; font-weight: 600; }
</style>
</head>
<body>
<?php include __DIR__ . '/../core/auth/header.php'; ?>
<header class="topbar" aria-label="Top navigation">
<div class="topbar-inner">
<div id="buttonRow" role="tablist" aria-label="App sections"></div>
<div id="menuContainer" aria-label="More actions"></div>
</div>
</header>
<main class="container">
<h1>DevBrewing SFTP Connection</h1>
<p class="lead">Connect securely to the DevBrewing file server and manage your remote files.</p>
<!-- 🔹 SFTP Connect Form -->
<form id="sftpForm" class="sftp-form" autocomplete="off">
<label for="sftp_ip">Server</label>
<input type="text" id="sftp_ip" value="files.devbrewing.com" required>
<label for="sftp_user">Username</label>
<input type="text" id="sftp_user" value="<?= htmlspecialchars($_SESSION['username'] ?? '') ?>" required>
<label for="sftp_pass">Password</label>
<div class="pw-wrap">
<input type="password" id="sftp_pass" required>
<button type="button" class="pw-toggle" id="togglePw">👁️</button>
</div>
<label for="sftp_port">Port</label>
<input type="number" id="sftp_port" value="22" required>
<button type="submit">Connect</button>
</form>
<div id="sftpMsg" class="msg"></div>
</main>
<script>
window.AppItems = [
];
window.AppMenu = [];
</script>
<script src="<?= asset('/core/js/overlay.js') ?>" defer></script>
<script src="<?= asset('menu.js') ?>" defer></script>
<script src="<?= asset('filemanager.js') ?>" defer></script>
<script defer>
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('sftpForm');
const msg = document.getElementById('sftpMsg');
const toggle = document.getElementById('togglePw');
const pass = document.getElementById('sftp_pass');
// 👁️ toggle password visibility
toggle.addEventListener('click', () => {
pass.type = pass.type === 'password' ? 'text' : 'password';
toggle.textContent = pass.type === 'password' ? '👁️' : '🙈';
});
// 🔄 handle form submission via fetch
form.addEventListener('submit', async (e) => {
e.preventDefault();
msg.textContent = 'Connecting...';
msg.style.color = '#9aa4b2';
const payload = {
action: 'connect',
host: document.getElementById('sftp_ip').value.trim(),
username: document.getElementById('sftp_user').value.trim(),
password: document.getElementById('sftp_pass').value.trim(),
port: parseInt(document.getElementById('sftp_port').value, 10)
};
try {
const res = await fetch('SFTPconnector.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const data = await res.json();
if (data.success) {
msg.innerHTML = data.message;
msg.style.color = '#10b981';
// Optional: open overlay
if (window.AppOverlay) {
AppOverlay.open([
{ title: 'SFTP Connection', html: `<p>${data.message}</p>` }
]);
}
} else {
msg.innerHTML = data.message || 'Connection failed';
msg.style.color = '#ef4444';
}
} catch (err) {
msg.textContent = 'Network error: ' + err.message;
msg.style.color = '#ef4444';
}
});
});
</script>
<!-- 🔹 Render chips + menu (same logic as before) -->
<script defer>
document.addEventListener('DOMContentLoaded', () => {
const row = document.getElementById('buttonRow');
row.innerHTML = '';
(window.AppItems || []).forEach((item, i) => {
const btn = document.createElement('button');
btn.className = 'chip';
btn.textContent = item.title || `Item ${i+1}`;
btn.onclick = () => window.AppOverlay && AppOverlay.open(window.AppItems, i, btn);
row.appendChild(btn);
});
const menuContainer = document.getElementById('menuContainer');
menuContainer.innerHTML = '';
const menuItems = window.AppMenu || [];
if (menuItems.length > 0) {
const trigger = document.createElement('button');
trigger.className = 'chip menu-trigger';
trigger.textContent = '⋮';
menuContainer.appendChild(trigger);
const dropdown = document.createElement('div');
dropdown.className = 'menu-list';
menuContainer.appendChild(dropdown);
menuItems.forEach((m, idx) => {
const item = document.createElement('button');
item.className = 'menu-item';
item.textContent = m.label || `Action ${idx+1}`;
item.onclick = () => { dropdown.classList.remove('open'); m.action && m.action(); };
dropdown.appendChild(item);
});
trigger.addEventListener('click', (e)=>{
e.stopPropagation();
dropdown.classList.toggle('open');
});
document.addEventListener('click', (e)=>{
if (!menuContainer.contains(e.target)) dropdown.classList.remove('open');
});
}
});
</script>
</body>
</html>