Multi-user mode & UI reworks#258
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Added 'is_shared' boolean cast to Plugin model's $casts array and implemented scopeVisibleTo(Builder $query, User $user) scope method. The scope allows users to see their own plugins and all shared plugins, while admins see all plugins. Also updated PluginFactory to include is_shared=false default. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Registers the `confirmed` middleware alias that logs out unconfirmed authenticated users, invalidates their session, and redirects to login with a status flash. Applied to all auth-guarded route groups in web.php and settings.php. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rmed Ensures newly registered users via Fortify registration form are created with confirmed_at = null. Also verifies unconfirmed users are blocked from accessing protected routes by EnsureUserIsConfirmed middleware. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…able auto-join - Auto-joined devices now have user_id=null instead of being assigned to the auto-join user - Device names are now generic 'Auto-Joined TRMNL' instead of user-specific - Auto-join toggle (Permit Auto-Join) is now visible to all confirmed users, not just id=1 - Updated tests to reflect new behavior: auto-joined devices remain unowned Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Creates DevicePolicy (view/update/delete/reassign) with admin bypass and unowned-device semantics, wires it into DisplayStatusController via the AuthorizesRequests trait, and fixes the id=1 auto-admin edge case in affected tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces PluginPolicy with view/update/delete/share/reassign/copy abilities, wires it into PluginSettingsController::destroy() and PluginArchiveController::export(), and fixes id=1 auto-admin hazard in UserModelTest and PluginArchiveTest. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The DeviceAutoJoin component no longer has the $isFirstUser property. The toggle is now visible to ALL confirmed users, not just id=1. Updated tests: - Removed all assertions checking $isFirstUser - Changed 'identifies first user correctly' test to verify the toggle is visible to all users (both id=1 and other users) - Updated property-setting test to use valid component properties only Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds /settings/admin/users Volt component with Livewire actions for confirming, revoking, promoting, and deleting users; admin nav link in settings sidebar; route registration; and 11 feature tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d reassignment Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nment Add is_shared toggle, Shared Plugins tab, admin Show All toggle, Install Copy action, and plugin ownership reassignment to the plugins index Livewire component; enforce authorization via PluginPolicy on all new actions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Device reassignment moved from devices/manage table row to the edit modal in devices/configure (admin section with Owner select) - Plugin reassignment moved from plugins/index card footer to a header row in plugins/recipe (visible to admins below the plugin title) - Admin can now access configure/recipe pages for any device/plugin; updateDevice and deleteDevice also allow admins - Admin show-all toggles on manage and index now use explicit <span> labels instead of relying solely on the flux:switch label prop - Tests updated to call reassign methods on their new host components Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds app.docker config key (auto-detected via /.dockerenv, overridable with APP_DOCKER env var). Updates page link and profile dropdown entry are hidden when docker=true, since self-update is not applicable in container deployments. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace segmented radio group with a compact dropdown matching the Owner selector style (label + flux:select) - Move Getting started buttons below the Save button; make them size="sm" - Add "These will replace the current template." note next to the buttons Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
mount() now reads User::where('assign_new_devices', true)->exists() so
any user sees the toggle as on when any other user has enabled it.
Turning off clears assign_new_devices on all users, not just the current
one, so any user can shut off auto-join regardless of who enabled it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DevicePolicy::update() was requiring user_id === auth()->id(), which returns false for unowned devices (user_id = null). The manage page already shows unowned devices to all users; the configure page should follow the same rule. - DevicePolicy::update(): allow null user_id, matching view() - configure.blade.php: replace all raw abort_unless(devices->contains) checks with $this->authorize() so the policy is the single source of truth for all device actions (delete, update, playlist ops, firmware) - DevicePolicyTest: flip 'cannot update unowned' to 'can update unowned' Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Plugin cards on the shared tab were wrapping the title in an <a> that led to the recipe edit page, resulting in a 403 for non-owners. Now the card header renders as a plain <div> when the plugin belongs to another user, leaving only the Install Copy button as the available action. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Hey there, are you and @vadimitri colleagues, or are you working on this independently? A few things to consider:
|
|
Hi, @vadimitri and I are both working on this on behalf of Spark for the Spark x TRMNL hackathon next week; the two PRs just happened because of an internal communication issue. Regarding your points:
Regarding your comment in the other PR: for the hackathon, we'll be using our larapaper fork, but I think there is no reason why we couldn't contribute back the features we build. The hackathon is on July 1st, you will probably be able to find pictures on the official trmnl social media channels :) |
|
Hey there, appreciate the contributions, looking forward to your hackathon.
The underlying “toggle” package supports feature flags via environment variables or database-backed UI toggles. Given the security implications, I’d prefer using an environment variable here.
Ideally, it would be possible to isolate Blade execution, so that PHP code cannot be executed. If that’s not feasible, then a feature flag would be the fallback.
That’s interesting, will check out. In this case, it would be great to implement it with an abstraction layer so that both an internal execution engine and an external serverless engine (like yours) can be supported interchangeably. |
This PR reworks multi-user mode, adding the concept of admins and shared plugins. It also redesignes the polling section of the recipe editor and hides the "updates" setting when running inside Docker.
The unprivileged user can now use auto-join, devices joined this way are "unassigned" and can be edited by everyone. Admins can view other people's devices.
Redesigned recipe editor for polling. The transform card shown in that screenshot is part of a serverless transform feature, which I will PR later. The settings card contains the polling-specific fields that used to be below (except bleed margin + dark mode)
Registration stays open, but users can't do anything until they are confirmed by an admin. Admins can manage users and approve/revoke them.
Plugins can be shared between users, then they can be copied to the user's account manually ("forked").
I agree to license this contribution as MIT. Supersedes #257