feat: NoJs Admin View (#3059)

Adds a nojs blade template to be able to enable/disable extensions when one of them misbehaves.
This commit is contained in:
Sami Mazouz 2021-08-31 09:08:27 +01:00 committed by GitHub
parent 7d59bbad88
commit c3ab5b96bb
15 changed files with 403 additions and 70 deletions

View File

@ -10,4 +10,5 @@
@import "admin/ExtensionWidget";
@import "admin/AppearancePage";
@import "admin/MailPage";
@import "admin/NoJs";
@import "admin/UsersListPage.less";

View File

@ -0,0 +1,17 @@
// Minimal NoJs specific styles
.NoJs-ExtensionsTable {
td&-icon {
padding-top: 0;
padding-bottom: 0;
}
.ExtensionListItem-Dot {
position: relative;
right: 0;
margin: 0;
}
.ExtensionIcon {
--size: 25px;
}
}

View File

@ -0,0 +1,69 @@
.Table {
background: @control-bg;
border-radius: @border-radius;
border-collapse: collapse;
border-spacing: 0;
caption {
text-align: start;
}
td, th {
border-bottom: 1px solid @body-bg;
color: @control-color;
}
td, th, .Checkbox, &-controls-item {
padding: 10px 15px;
}
& &-checkbox, & &-controls {
padding: 0;
}
thead {
th {
text-align: center;
padding: 15px 25px;
}
.icon {
display: block;
font-size: 14px;
width: auto;
margin-bottom: 5px;
}
}
&-groupToggle {
cursor: pointer;
.icon {
font-size: 14px;
margin-right: 2px;
.fa-fw();
}
}
&-checkbox {
.Checkbox {
display: block;
}
.Checkbox-display {
text-align: center;
cursor: pointer;
}
&.highlighted .Checkbox, .Checkbox:hover {
&:not(.disabled) {
background: darken(@control-bg, 4%);
}
}
}
&-controls-item {
width: 100%;
border-radius: 0;
}
}

View File

@ -27,6 +27,7 @@
@import "Placeholder";
@import "Search";
@import "Select";
@import "Table";
@import "TextEditor";
@import "Tooltip";
@import "ValidationError";

View File

@ -1,52 +1,3 @@
.NotificationGrid {
background: @control-bg;
border-radius: @border-radius;
border-collapse: collapse;
border-spacing: 0;
td, th {
border-bottom: 1px solid @body-bg;
color: @control-color;
}
td, th, .Checkbox {
padding: 10px 15px;
}
.NotificationGrid-checkbox {
padding: 0;
}
thead {
th {
text-align: center;
padding: 15px 25px;
}
.icon {
display: block;
font-size: 14px;
width: auto;
margin-bottom: 5px;
}
}
}
.NotificationGrid-groupToggle {
cursor: pointer;
.icon {
font-size: 14px;
margin-right: 2px;
.fa-fw();
}
}
.NotificationGrid-checkbox {
.Checkbox {
display: block;
}
.Checkbox-display {
text-align: center;
cursor: pointer;
}
&.highlighted .Checkbox, .Checkbox:hover {
&:not(.disabled) {
background: darken(@control-bg, 4%);
}
}
.Table();
}

View File

@ -545,6 +545,20 @@ core:
# Translations in this namespace are used in views other than Flarum's normal JS client.
views:
# Translations in this namespace are displayed by the basic HTML admin index.
admin:
extensions:
caption: => core.ref.extensions
disable: Disable
empty: No installed extensions
enable: Enable
name: Extension Name
package_name: Package Name
version: Version
info:
caption: Application Info
title: Administration
# Translations in this namespace are displayed by the Confirm Email interface.
confirm_email:
submit_button: => core.ref.confirm_email
@ -673,6 +687,7 @@ core:
edit: Edit
edit_user: Edit User
email: Email
extensions: Extensions
icon: Icon
icon_text: "Enter the name of any <a>FontAwesome</a> icon class, <em>including</em> the <code>fas fa-</code> prefix."
load_more: Load More

View File

@ -0,0 +1,60 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Admin\Content;
use Flarum\Extension\ExtensionManager;
use Flarum\Foundation\Application;
use Flarum\Frontend\Document;
use Flarum\Settings\SettingsRepositoryInterface;
use Illuminate\Contracts\View\Factory;
use Psr\Http\Message\ServerRequestInterface as Request;
class Index
{
/**
* @var Factory
*/
protected $view;
/**
* @var ExtensionManager
*/
protected $extensions;
/**
* @var SettingsRepositoryInterface
*/
protected $settings;
public function __construct(Factory $view, ExtensionManager $extensions, SettingsRepositoryInterface $settings)
{
$this->view = $view;
$this->extensions = $extensions;
$this->settings = $settings;
}
public function __invoke(Document $document, Request $request): Document
{
$extensions = $this->extensions->getExtensions();
$extensionsEnabled = json_decode($this->settings->get('extensions_enabled', '{}'), true);
$csrfToken = $request->getAttribute('session')->token();
$mysqlVersion = $document->payload['mysqlVersion'];
$phpVersion = $document->payload['phpVersion'];
$flarumVersion = Application::VERSION;
$document->content = $this->view->make(
'flarum.admin::frontend.content.admin',
compact('extensions', 'extensionsEnabled', 'csrfToken', 'flarumVersion', 'phpVersion', 'mysqlVersion')
);
return $document;
}
}

View File

@ -0,0 +1,55 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Admin\Controller;
use Flarum\Bus\Dispatcher;
use Flarum\Extension\Command\ToggleExtension;
use Flarum\Http\RequestUtil;
use Flarum\Http\UrlGenerator;
use Illuminate\Support\Arr;
use Laminas\Diactoros\Response\RedirectResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface;
class UpdateExtensionController implements RequestHandlerInterface
{
/**
* @var UrlGenerator
*/
protected $url;
/**
* @var Dispatcher
*/
protected $bus;
public function __construct(UrlGenerator $url, Dispatcher $bus)
{
$this->url = $url;
$this->bus = $bus;
}
/**
* {@inheritdoc}
*/
public function handle(Request $request): ResponseInterface
{
$actor = RequestUtil::getActor($request);
$enabled = (bool) (int) Arr::get($request->getParsedBody(), 'enabled');
$name = Arr::get($request->getQueryParams(), 'name');
$this->bus->dispatch(
new ToggleExtension($actor, $name, $enabled)
);
return new RedirectResponse($this->url->to('admin')->base());
}
}

View File

@ -7,6 +7,8 @@
* LICENSE file that was distributed with this source code.
*/
use Flarum\Admin\Content\Index;
use Flarum\Admin\Controller\UpdateExtensionController;
use Flarum\Http\RouteCollection;
use Flarum\Http\RouteHandlerFactory;
@ -14,6 +16,12 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
$map->get(
'/',
'index',
$route->toAdmin()
$route->toAdmin(Index::class)
);
$map->post(
'/extensions/{name}',
'extensions.update',
$route->toController(UpdateExtensionController::class)
);
};

View File

@ -9,7 +9,8 @@
namespace Flarum\Api\Controller;
use Flarum\Extension\ExtensionManager;
use Flarum\Bus\Dispatcher;
use Flarum\Extension\Command\ToggleExtension;
use Flarum\Http\RequestUtil;
use Illuminate\Support\Arr;
use Laminas\Diactoros\Response\EmptyResponse;
@ -20,16 +21,13 @@ use Psr\Http\Server\RequestHandlerInterface;
class UpdateExtensionController implements RequestHandlerInterface
{
/**
* @var ExtensionManager
* @var Dispatcher
*/
protected $extensions;
protected $bus;
/**
* @param ExtensionManager $extensions
*/
public function __construct(ExtensionManager $extensions)
public function __construct(Dispatcher $bus)
{
$this->extensions = $extensions;
$this->bus = $bus;
}
/**
@ -37,16 +35,13 @@ class UpdateExtensionController implements RequestHandlerInterface
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
RequestUtil::getActor($request)->assertAdmin();
$enabled = Arr::get($request->getParsedBody(), 'enabled');
$actor = RequestUtil::getActor($request);
$enabled = (bool) (int) Arr::get($request->getParsedBody(), 'enabled');
$name = Arr::get($request->getQueryParams(), 'name');
if ($enabled === true) {
$this->extensions->enable($name);
} elseif ($enabled === false) {
$this->extensions->disable($name);
}
$this->bus->dispatch(
new ToggleExtension($actor, $name, $enabled)
);
return new EmptyResponse;
}

View File

@ -0,0 +1,37 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Extension\Command;
use Flarum\User\User;
class ToggleExtension
{
/**
* @var User
*/
public $actor;
/**
* @var string
*/
public $name;
/**
* @var bool
*/
public $enabled;
public function __construct(User $actor, string $name, bool $enabled)
{
$this->actor = $actor;
$this->name = $name;
$this->enabled = $enabled;
}
}

View File

@ -0,0 +1,41 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Extension\Command;
use Flarum\Extension\ExtensionManager;
class ToggleExtensionHandler
{
/**
* @var ExtensionManager
*/
protected $extensions;
public function __construct(ExtensionManager $extensions)
{
$this->extensions = $extensions;
}
/**
* @throws \Flarum\User\Exception\PermissionDeniedException
* @throws \Flarum\Extension\Exception\MissingDependenciesException
* @throws \Flarum\Extension\Exception\DependentExtensionsException
*/
public function handle(ToggleExtension $command)
{
$command->actor->assertAdmin();
if ($command->enabled) {
$this->extensions->enable($command->name);
} else {
$this->extensions->disable($command->name);
}
}
}

View File

@ -279,6 +279,19 @@ class Extension implements Arrayable
return $icon;
}
public function getIconStyles(): string
{
$properties = $this->getIcon();
unset($properties['name']);
return implode(';', array_map(function (string $property, string $value) {
$property = Str::kebab($property);
return "$property: $value";
}, array_keys($properties), $properties));
}
/**
* @internal
*/

View File

@ -28,9 +28,9 @@
<div id="admin-navigation" class="App-nav sideNav"></div>
</div>
<div id="content" class="sideNavOffset"></div>
{!! $content !!}
<div id="content" class="sideNavOffset">
{!! $content !!}
</div>
</main>
</div>

View File

@ -0,0 +1,70 @@
@inject('url', 'Flarum\Http\UrlGenerator')
<div class="container">
<h2>{{ $translator->trans('core.views.admin.title') }}</h2>
<table class="NoJs-InfoTable Table">
<caption><h3>{{ $translator->trans('core.views.admin.info.caption') }}</h3></caption>
<tbody>
<tr>
<td>Flarum</td>
<td>{{ $flarumVersion }}</td>
</tr>
<tr>
<td>PHP</td>
<td>{{ $phpVersion }}</td>
</tr>
<tr>
<td>MySQL</td>
<td>{{ $mysqlVersion }}</td>
</tr>
</tbody>
</table>
<table class="NoJs-ExtensionsTable Table">
<caption><h3>{{ $translator->trans('core.views.admin.extensions.caption') }}</h3></caption>
<thead>
<tr>
<th></th>
<th>{{ $translator->trans('core.views.admin.extensions.name') }}</th>
<th>{{ $translator->trans('core.views.admin.extensions.package_name') }}</th>
<th>{{ $translator->trans('core.views.admin.extensions.version') }}</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
@forelse($extensions as $extension)
@php $isEnabled = in_array($extension->getId(), $extensionsEnabled); @endphp
<tr>
<td class="NoJs-ExtensionsTable-icon">
<div class="ExtensionIcon" style="{{ $extension->getIconStyles() }}">
<span class="icon {{ $extension->getIcon()['name'] ?? '' }}"></span>
</div>
</td>
<td class="NoJs-ExtensionsTable-title">{{ $extension->getTitle() }}</td>
<td class="NoJs-ExtensionsTable-name">{{ $extension->name }}</td>
<td class="NoJs-ExtensionsTable-version">{{ $extension->getVersion() }}</td>
<td class="NoJs-ExtensionsTable-state">
<span class="ExtensionListItem-Dot {{ $isEnabled ? 'enabled' : 'disabled' }}" aria-hidden="true"></span>
</td>
<td class="NoJs-ExtensionsTable-toggle Table-controls">
<form action="{{ $url->to('admin')->route('extensions.update', ['name' => $extension->getId()]) }}" method="POST">
<input type="hidden" name="csrfToken" value="{{ $csrfToken }}">
<input type="hidden" name="enabled" value="{{ $isEnabled ? 0 : 1 }}">
@if($isEnabled)
<button type="submit" class="Button Table-controls-item">{{ $translator->trans('core.views.admin.extensions.disable') }}</button>
@else
<button type="submit" class="Button Table-controls-item">{{ $translator->trans('core.views.admin.extensions.enable') }}</button>
@endif
</form>
</td>
</tr>
@empty
<tr><td colspan="6" class="NoJs-ExtensionsTable-empty">{{ $translator->trans('core.views.admin.extensions.empty') }}</td></tr>
@endforelse
</tbody>
</table>
</div>