Architecture
One service provider, one controller, five focused Support classes, one Eloquent model, and one guard-aware Gate middleware. No facades, no repository layer, no DTO hierarchy, and no per-command action classes — keeping the package lightweight, readable, and trivial to audit.
src/
├── OpConsoleServiceProvider.php Config merge, isolated lock store registration, routes, views, translations, migrations, publishing, default Gates
├── Http/
│ ├── Controllers/OpConsoleController.php 5 actions: index, asset, downloadLog, deleteLog, run
│ └── Middleware/AuthorizeOpConsole.php Configured guard check + Gate::forUser() enforcement, fail-closed
├── Models/OpConsoleRun.php Audit & history row: started / succeeded / failed
├── Contracts/HealthCheck.php The single host extension contract
└── Support/
├── ConsoleStatus.php Read model: database/migrator snapshot, caches, checks, system info, advice, bounded audit history
├── ActionRunner.php Write model: renewable owner-bound lock, bounded synchronous Artisan execution, allow-list, sanitization, audit writes
├── HealthChecks.php Built-in 4-state diagnostics + container-resolved config('opconsole.checks')
├── HealthNotifier.php Opt-in transition evaluation with minimal cache state and duplicate suppression
├── LogViewer.php Request-scoped bounded tail reader (2 MB), text & NDJSON parser, filter/group, safe download/delete
└── PagePresenter.php View presenter: overview/readiness, risk-graded action cards, sorted checks, operation history, filter URLs, XSS-safe highlightingKey Architectural Decisions
ConsoleStatusvsActionRunnerseparation: Reads and writes have distinct risk profiles, rate limits, and authorization Gates (view_gatevsrun_gate). Separating them ensures diagnostic page loads never touch Artisan execution paths.- Isolated
opconsolelock store:OpConsoleServiceProviderregisterscache.stores.opconsole(storage/framework/cache/opconsole) automatically when not overridden by the host.ActionRunner::validateLockStore()verifies lock isolation fail-closed across both Laravel 12 and Laravel 13 sooptimize:clear(cache:clear) can never flush an active maintenance lock. - Request-scoped
LogViewer: Registered via$this->app->scoped(LogViewer::class)sofiles()and the 2 MB tail parse run at most once per HTTP request (reused acrossConsoleStatus::recentErrors()andOpConsoleController::index()) while never leaking stale state across Laravel Octane / long-lived worker requests. PagePresenterowns all presentation logic: Blade templates (layout.blade.php,index.blade.php, andpartials/*) only print pre-formatted arrays and inline SVG icons. Grouping, severity ordering (critical→warning→unknown→healthy), stale-run classification (Interrupted / Unknown), URL generation, and raw-before-escape<mark>highlighting all live inPagePresenter.- Overview communicates verification limits: The first section distinguishes verified critical/warning results from unknown checks, shows scheduler, worker, maintenance and last-check status, and links to details. Recent error totals are explicitly sampled from the bounded tail of the newest readable log; zero means no sampled errors, not proof that every log is clear.
- Progressive disclosure and navigation: Overview, Health, Operations, Logs, History, and System information share a keyboard-operable tab bar. Hash targets and query filters survive navigation and refresh; check details and per-run details remain collapsible.
- Self-contained, zero-CDN frontend:
public/opconsole.cssandpublic/opconsole.jshave zero runtime dependencies on Bootstrap, jQuery, or external icon fonts, use scopedoc-*classes to avoid colliding with host admin stylesheets, and emit zero inline<script>orstyle=attributes for strict Content-Security-Policy compatibility. If assets have not been published topublic/vendor/opconsole/yet,OpConsoleController::asset()serves them behindopconsole.authorize:view. HealthCheckis the sole callable extension point:config/opconsole.phpcontains zero closures (config:cachesafe). Host applications register class names inconfig('opconsole.checks');HealthChecksresolves each via the container, validates scalar types and shape, deduplicates repeated classes, and blocks collisions with built-in checks.- Bounded synchronous execution: The seven allow-listed Artisan operations run in the request without queue dispatch. A pcntl alarm enforces
max_runtime_secondsand renews the owner-bound lease where the driver supportsrefresh(). The lease always exceeds the budget, so renewal is a safety margin rather than a requirement. Runtimes without pcntl refuse every timer-bound action before locking; only the subprocess-boundedsecurity_scanruns. Fatal errors release the lock from a shutdown hook, and killed processes release it by lease expiry. Queue workers are deliberately not involved: queue workers stop processing while the application is down, so a queuedupcould never run. - One health report policy: Dashboard, CLI, and HTTP endpoint consume the same check list and unknown-status policy. Notifications run only when explicitly requested from
opconsole:health --notify, so health reads have no delivery side effects. - Bounded
OpConsoleRunqueries:ConsoleStatusfetches the latest completed run per action viaSELECT MAX(id) ... GROUP BY actionand the 15 most recent audit rows viaORDER BY id DESC LIMIT 15, keeping SQL query count and memory constant regardless of historical table size.user_idis stored as a nullable string without foreign keys so integer, UUID, and ULID user primary keys all work portably across SQLite, MySQL/MariaDB, PostgreSQL, and SQL Server.
Request Flow
GET {prefix}/ → web + opconsole.authorize:view
→ ConsoleStatus::status() + LogViewer::files()/read() → PagePresenter::page() → Blade view
GET {prefix}/assets/{asset} → web + opconsole.authorize:view
→ OpConsoleController::asset() (serves opconsole.css / opconsole.js with nosniff)
GET {prefix}/logs/{file} → web + opconsole.authorize:download + throttle:30,1,opconsole-download
→ LogViewer::path() → ActionRunner::record('log.download') → response()->download()
DELETE {prefix}/logs/{file} → web + opconsole.authorize:delete + throttle:6,1,opconsole-delete
→ LogViewer::deleteOutcome() → ActionRunner::record('log.delete') → redirect
POST {prefix}/{action} → web + opconsole.authorize:run + throttle:6,1,opconsole-run
→ ActionRunner::run() (validateLockStore → lock->get → recordStart → Artisan::call → recordFinish → lock->release) → redirect