The Single-File Admin Panel
The admin panel for this server is a collection of PHP files behind HTTP Basic Auth. Each page — Docker management, site rebuilds, license administration, log viewing, deployment — is a single .php file that does double duty:
- GET request → render the HTML page with embedded JavaScript
- POST request → process an action and return JSON
No framework. No build step. No npm. No webpack. No client-side routing. Just PHP files and fetch().
The pattern
Every admin page follows the same structure:
<?php
// POST handler at the top
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
header('Content-Type: application/json');
$action = $_POST['action'] ?? '';
// Dispatch to backend helper
require_once '/path/to/actions.php';
echo json_encode(doAction($action, $_POST));
exit;
}
// GET handler: render the page
require_once 'menu.php';
?>
<!DOCTYPE html>
<html>
<!-- ... HTML with embedded JS ... -->
<script>
async function apiPost(params) {
const resp = await fetch(window.location.href, {
method: 'POST',
body: new URLSearchParams(params)
});
return resp.json();
}
</script>
</html>
The JavaScript apiPost() function sends requests to the same URL it’s loaded from. The PHP file distinguishes GET from POST at the top and routes accordingly. The response is always JSON with a success boolean.
The menu system
A shared menu.php provides a consistent navigation bar across all admin pages. It defines a $menuItems array and a renderMenu() function. Adding a new page means adding one entry to the array.
The menu CSS is also in menu.php — a single function call in the <head> of each page. This keeps the entire navigation system in one file, with zero external dependencies.
Backend separation
The POST handlers don’t contain business logic directly. They delegate to “action” files in a shared PHP directory:
docker_actions.php— container managementhugo_actions.php— site rebuildslicense_actions.php— user/license CRUDdeploy_sewers_actions.php— git operations, publish, archive
This separation means the admin UI can change independently of the backend logic, and the backend functions can be reused by other tools (cron jobs, scripts).
Why not a real framework?
Because this admin panel serves one user. It’s protected by HTTP Basic Auth and accessible only from a specific URL. The requirements are:
- Show some information
- Let the user trigger some actions
- Don’t break
A framework would add dependency management, a build step, a routing layer, middleware, and a templating engine — all to serve what amounts to a handful of AJAX forms. The single-file pattern is faster to build, faster to modify, and has zero maintenance overhead.
The trade-off
The pattern doesn’t scale. If this admin panel needed 50 pages, role-based access control, and a team of developers, we’d need a framework. But it has 12 pages, one user, and one developer (plus an AI that helps). At this scale, simplicity wins.
Every page loads in milliseconds. Every action completes in seconds. The entire admin panel is about 2,000 lines of PHP and JavaScript across all files. That’s a codebase small enough to keep in your head — or in a memory file.