<?php
// Force login + disable caching
session_start();
require_once __DIR__ . '/../core/db_config.php';
// Anti-cache headers (dev)
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 (absolute & relative paths)
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;
}
// Handle SFTP connect form
$msg = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$ip = trim($_POST['sftp_ip'] ?? '');
$user = trim($_POST['sftp_user'] ?? '');
$pass = trim($_POST['sftp_pass'] ?? '');
$port = (int)($_POST['sftp_port'] ?? 22);
if ($ip && $user && $pass) {
$conn = @ssh2_connect($ip, $port);
if ($conn && @ssh2_auth_password($conn, $user, $pass)) {
// Optionally keep credentials in session (short-lived) — uncomment if desired:
// $_SESSION['sftp'] = ['ip'=>$ip,'user'=>$user,'pass'=>$pass,'port'=>$port];
$msg = "✅ Connected successfully to <b>" . htmlspecialchars($ip) . "</b> on port <b>" . intval($port) . "</b>.";
} else {
$msg = "❌ Connection failed. Please check your credentials.";
}
} else {
$msg = "⚠️ All fields are required.";
}
}
?>
<!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 .row {
display:flex;
gap:0.5rem;
align-items:center;
}
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>Modular Overlay Index</h1>
<p class="lead">Each module adds its own button and overlay content. If modules register extra actions, a 3-dots menu appears on the right.</p>
<!-- 🔹 Simple SFTP Connect Form -->
<form method="POST" class="sftp-form" id="sftpForm" autocomplete="off">
<label for="sftp_ip">Server IP / Host</label>
<input type="text" id="sftp_ip" name="sftp_ip" value="files.devbrewing" required>
<label for="sftp_user">Username</label>
<input type="text" id="sftp_user" name="sftp_user" value="<?= htmlspecialchars($_SESSION['username'] ?? '') ?>" required>
<label for="sftp_pass">Password</label>
<div class="pw-wrap">
<input type="password" id="sftp_pass" name="sftp_pass" required aria-describedby="pwHelp">
<button type="button" class="pw-toggle" id="togglePw" aria-label="Show password">👁️</button>
</div>
<label for="sftp_port">Port</label>
<input type="number" id="sftp_port" name="sftp_port" value="22" required>
<button type="submit">Connect</button>
</form>
<?php if ($msg): ?>
<div class="msg"><?= $msg ?></div>
<?php endif; ?>
</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>
// toggle password visibility
document.addEventListener('DOMContentLoaded', () => {
const toggle = document.getElementById('togglePw');
const pass = document.getElementById('sftp_pass');
if (!toggle || !pass) return;
toggle.addEventListener('click', () => {
if (pass.type === 'password') {
pass.type = 'text';
toggle.textContent = '🙈'; // eye closed
toggle.setAttribute('aria-label','Hide password');
} else {
pass.type = 'password';
toggle.textContent = '👁️'; // eye open
toggle.setAttribute('aria-label','Show password');
}
});
});
</script>
<!-- render chips & menu (unchanged from your layout) -->
<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>