# Master Panel

Vanilla PHP + SQLite + AJAX admin panel. PHP 7.4 / 8.1 compatible.

## Quick start

```
cd panel
php -S 127.0.0.1:8080 -t .
```

Open `http://127.0.0.1:8080` and log in with the default credentials:

```
admin / admin
```

The SQLite database (`data/panel.db`) is created automatically on the first request — schema and a single empty config row are applied by `includes/db.php`. There are no `.sql` files and no installer page.

## Layout

```
panel/
├── index.php                 redirect to login or dashboard
├── login.php                 login form
├── logout.php
├── dashboard.php             stat cards, subs breakdown, recent activity, top endpoints
├── services_clients.php      device + subscription + parental control CRUD
├── playlists.php             per-device playlists CRUD
├── portals.php               Service spinner choices CRUD
├── vpn.php                   VPN profiles CRUD
├── configuration.php         identity (libapksec / bootstrap keys) + branding
├── logs.php                  panel / guard_key / bootstrap log tabs
├── api_help.php              endpoint reference for the admin AJAX surface
├── settings.php              account info + change password + admin users
│
├── api/                      JSON CRUD endpoints (session-authenticated, CSRF-checked)
│   ├── _common.php
│   ├── config.php
│   ├── devices.php
│   ├── subscriptions.php
│   ├── parent_control.php
│   ├── playlists.php
│   ├── portals.php
│   ├── vpns.php
│   ├── admin_users.php
│   ├── logs.php
│   └── stats.php
│
├── includes/                 shared PHP includes
│   ├── config.php            PANEL_* defines (default creds, debug flag)
│   ├── bootstrap.php         session + auto-DB-init + error mode
│   ├── db.php                PDO + schema + seed (self-populating)
│   ├── auth.php              admin login helpers
│   ├── csrf.php              token mint + verify
│   ├── helpers.php           h(), json_response(), random_hex(), …
│   ├── codec.php             body envelope encode/decode
│   ├── crypto.php            signing + AES (gs/bs/hm/bootstrap)
│   ├── page_header.php       <head> + topbar + sidebar shell
│   └── page_footer.php       closes the shell + script tags
│
├── assets/
│   ├── css/panel.css         panel theme
│   ├── css/extra.css         additions (modals, pills, table toolbar)
│   ├── css/phosphor.css      icon font CSS
│   ├── fonts/                Phosphor.woff2 / woff / ttf
│   ├── images/               favicon + logos
│   └── js/app.js             AJAX fetch wrapper, toasts, modal helpers
│
└── data/
    └── panel.db              created on first request
```

## Default credentials

The first request creates one admin user:

```
username: admin
password: admin
```

Change them in `panel/settings.php` after logging in. The defaults are also overridable via the `PANEL_DEFAULT_ADMIN_USER` / `PANEL_DEFAULT_ADMIN_PASS` constants in `includes/config.php` if you want a different first-boot password.

## How the admin AJAX endpoints work

Every endpoint under `api/`:

- Requires a valid admin session (`auth_require_login()`).
- Requires a CSRF token on anything other than `GET` — sent either via the `X-CSRF-Token` header (what `assets/js/app.js` does automatically) or as a `_csrf` form field.
- Returns JSON.
- For mutations, accepts a JSON body with an `action` field:

```json
{ "action": "create", "name": "...", "url": "..." }
{ "action": "update", "id": 42, "name": "..." }
{ "action": "delete", "id": 42 }
```

GET with `?id=N` returns a single row; bare GET returns the list.

## Schema

Eleven tables, no tenant scoping, single global `config` row:

- `admin_users` — panel login accounts
- `config` — singleton row (id = 1): identity fields (pkg_name, cert_sha256_hex, libapksec/bootstrap auth_keys + build_ids) + branding (theme, noti, urls, app_version, is_ibo, is_vu)
- `devices` — one row per app install
- `subscriptions` — 1:1 with `devices`
- `parent_control` — 1:1 with `devices`
- `playlists` — N per device (`AppInfoModel.result`)
- `portals` — global Service spinner choices (`AppInfoModel.portals`)
- `vpn_profiles` — global VPN list
- `guard_key_log`, `bootstrap_log`, `panel_request_log` — audit tables (optional)

All tables, indexes and the seed row live inside `includes/db.php` (`db_ensure_schema()` / `db_ensure_seed()`).

## Resetting the database

```
rm data/panel.db data/panel.db-shm data/panel.db-wal
```

The next page load recreates everything.

## Production notes

- Set `PANEL_DEBUG` to `0` in `includes/config.php` to suppress verbose JSON errors.
- Serve over HTTPS — the session cookie will then auto-flip its `secure` flag.
- Change the default admin password immediately on first login.
