0) { ob_end_flush(); } ob_implicit_flush(true); // Send headers early header('Content-Type: text/html; charset=utf-8'); header('Cache-Control: no-cache'); header('X-Accel-Buffering: no'); // Hint for Nginx: disable buffering // Basic config $woPath = '/usr/local/bin/wo'; $bootstrapScript = '/usr/local/bin/wp-dev-bootstrap.sh'; // Defaults for bootstrap script $bootstrapAdminUser = 'vdidev'; $bootstrapAdminEmail = 'dev@vincentdevelopment.ca'; // Optional theme config for bootstrap script $themeStarterRepo = 'git@github.com:Vincent-Design-Inc/VDI-Starter-v5.git'; $themeRemoteOrigin = ''; // e.g. 'git@github.com:your-org/client-theme-repo.git' // SQLite config $dbPath = __DIR__ . '/panel.sqlite'; // ---------- Init DB ---------- $seedInfo = initDb(); // ---------- Routing / Auth Gate ---------- $action = isset($_GET['action']) ? $_GET['action'] : 'list'; // Logout handler if ($action === 'logout') { $_SESSION = []; session_destroy(); header('Location: ?action=login'); exit; } requireLogin($action); $user = getCurrentUser(); // ---------- Login Action ---------- if ($action === 'login') { $loginError = null; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $username = trim($_POST['username'] ?? ''); $password = $_POST['password'] ?? ''; if ($username !== '' && $password !== '') { $db = getDb(); $stmt = $db->prepare('SELECT id, username, password_hash, role FROM users WHERE username = ?'); $stmt->execute([$username]); $row = $stmt->fetch(PDO::FETCH_ASSOC); if ($row && password_verify($password, $row['password_hash'])) { $_SESSION['user_id'] = $row['id']; $_SESSION['username'] = $row['username']; $_SESSION['role'] = $row['role']; header('Location: ?action=list'); exit; } else { $loginError = 'Invalid username or password.'; } } else { $loginError = 'Username and password are required.'; } } ?>
Sign in to manage dev sites.
adminchange-me. Please log in and change it later.';
}
if ($loginError) {
echo '' . htmlspecialchars($loginError) . '
'; } ?>Create and manage dev sites.
WordOps log:
'; echo '';
@ob_flush();
@flush();
list($ok, $woLines) = runCommandStreaming($cmd, function ($line) {
echo htmlspecialchars($line) . "\n";
@ob_flush();
@flush();
});
echo '';
if ($ok) {
// Record site owner in SQLite
$db = getDb();
$stmt = $db->prepare('INSERT OR REPLACE INTO sites (domain, owner_id, created_at) VALUES (?, ?, datetime("now"))');
$stmt->execute([$domain, $user['id']]);
echo 'Site created successfully.
'; // Run bootstrap if requested if ($bootstrapProfile === 'standard') { $adminUser = $bootstrapAdminUser; $adminEmail = $bootstrapAdminEmail; $bootstrapArgs = [ '--domain', $domain, '--project-name', $projectName, '--admin-user', $adminUser, '--admin-email', $adminEmail, ]; if ($themeStarterRepo !== '') { $bootstrapArgs[] = '--theme-starter-repo'; $bootstrapArgs[] = $themeStarterRepo; } if ($themeRemoteOrigin !== '') { $bootstrapArgs[] = '--theme-remote-origin'; $bootstrapArgs[] = $themeRemoteOrigin; } $bootstrapCmd = escapeshellcmd($bootstrapScript); foreach ($bootstrapArgs as $arg) { $bootstrapCmd .= ' ' . escapeshellarg($arg); } echo 'Bootstrap log:
'; echo '';
@ob_flush();
@flush();
list($bootstrapOk, $bootstrapLines) = runCommandStreaming($bootstrapCmd, function ($line) {
echo htmlspecialchars($line) . "\n";
@ob_flush();
@flush();
});
echo '';
if ($bootstrapOk) {
echo 'Bootstrap completed: standard dev stack applied.
'; } else { echo 'Bootstrap failed: see log above.
'; } } echo ''; } else { echo 'Site creation failed. See WordOps log above.
'; echo ''; } echo '' . htmlspecialchars(implode("\n", $output)) . '';
echo '';
echo 'You're about to delete .
This will remove the vhost and files. Databases are handled by WordOps according to its defaults.
' . htmlspecialchars($userError) . '
'; } if ($generatedPasswordInfo) { echo 'New password for ' . htmlspecialchars($generatedPasswordInfo['username']) . ': ';
echo '' . htmlspecialchars($generatedPasswordInfo['password']) . '
No users found.
| Username | Role | Actions |
|---|---|---|
' . htmlspecialchars(implode("\n", $output)) . '';
} else {
$db = getDb();
// Map of domain => [owner_id, username, role]
$meta = [];
$stmt = $db->query('
SELECT s.domain, s.owner_id, u.username, u.role
FROM sites s
LEFT JOIN users u ON s.owner_id = u.id
');
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$meta[$row['domain']] = $row;
}
$sites = [];
foreach ($output as $line) {
$line = trim($line);
// Skip header / separator lines and the panel site itself
if (
$line === '' ||
strpos($line, 'Site') !== false ||
strpos($line, 'site') !== false ||
strpos($line, '---') !== false ||
$line === 'dev-panel.local'
) {
continue;
}
// Take the first column as the domain
$parts = preg_split('/\s+/', $line);
if (!$parts || !isset($parts[0])) {
continue;
}
$domain = sanitizeDomain($parts[0]);
if (!$domain) {
continue;
}
$ownerInfo = $meta[$domain] ?? null;
// Role-based filtering: dev sees only their own; admin sees all
if (!isAdmin()) {
if (!$ownerInfo || (int)$ownerInfo['owner_id'] !== (int)$user['id']) {
continue;
}
}
$sites[] = [
'domain' => $domain,
'owner' => $ownerInfo['username'] ?? null,
'ownerRole' => $ownerInfo['role'] ?? null,
'ownerId' => $ownerInfo['owner_id'] ?? null,
];
}
if (empty($sites) || (count($sites) === 1 && strPos($sites[0]['domain'], 'dev-panel') !== false)) {
echo 'No sites found for your account yet. Create your first site.
'; } else { echo '