diff --git a/framework/core/js/admin/dist/app.js b/framework/core/js/admin/dist/app.js index bacc3fc03..7d1a68eb7 100644 --- a/framework/core/js/admin/dist/app.js +++ b/framework/core/js/admin/dist/app.js @@ -18358,15 +18358,19 @@ System.register('flarum/components/Checkbox', ['flarum/Component', 'flarum/compo } }; });; -"use strict"; +'use strict'; -System.register("flarum/components/DashboardPage", ["flarum/components/Page"], function (_export, _context) { +System.register('flarum/components/DashboardPage', ['flarum/components/Page', 'flarum/components/StatusWidget', 'flarum/components/StatisticsWidget'], function (_export, _context) { "use strict"; - var Page, DashboardPage; + var Page, StatusWidget, StatisticsWidget, DashboardPage; return { setters: [function (_flarumComponentsPage) { Page = _flarumComponentsPage.default; + }, function (_flarumComponentsStatusWidget) { + StatusWidget = _flarumComponentsStatusWidget.default; + }, function (_flarumComponentsStatisticsWidget) { + StatisticsWidget = _flarumComponentsStatisticsWidget.default; }], execute: function () { DashboardPage = function (_Page) { @@ -18378,62 +18382,16 @@ System.register("flarum/components/DashboardPage", ["flarum/components/Page"], f } babelHelpers.createClass(DashboardPage, [{ - key: "view", + key: 'view', value: function view() { return m( - "div", - { className: "DashboardPage" }, + 'div', + { className: 'DashboardPage' }, m( - "div", - { className: "container" }, - m( - "h2", - null, - app.translator.trans('core.admin.dashboard.welcome_text') - ), - m( - "p", - null, - app.translator.trans('core.admin.dashboard.version_text', { version: m( - "strong", - null, - app.forum.attribute('version') - ) }) - ), - m( - "p", - null, - app.translator.trans('core.admin.dashboard.beta_warning_text', { strong: m("strong", null) }) - ), - m( - "ul", - null, - m( - "li", - null, - app.translator.trans('core.admin.dashboard.contributing_text', { a: m("a", { href: "http://flarum.org/docs/contributing", target: "_blank" }) }) - ), - m( - "li", - null, - app.translator.trans('core.admin.dashboard.troubleshooting_text', { a: m("a", { href: "http://flarum.org/docs/troubleshooting", target: "_blank" }) }) - ), - m( - "li", - null, - app.translator.trans('core.admin.dashboard.support_text', { a: m("a", { href: "http://discuss.flarum.org/t/support", target: "_blank" }) }) - ), - m( - "li", - null, - app.translator.trans('core.admin.dashboard.features_text', { a: m("a", { href: "http://discuss.flarum.org/t/features", target: "_blank" }) }) - ), - m( - "li", - null, - app.translator.trans('core.admin.dashboard.extension_text', { a: m("a", { href: "http://flarum.org/docs/extend", target: "_blank" }) }) - ) - ) + 'div', + { className: 'container' }, + m(StatusWidget, null), + m(StatisticsWidget, null) ) ); } @@ -18441,7 +18399,53 @@ System.register("flarum/components/DashboardPage", ["flarum/components/Page"], f return DashboardPage; }(Page); - _export("default", DashboardPage); + _export('default', DashboardPage); + } + }; +});; +'use strict'; + +System.register('flarum/components/DashboardWidget', ['flarum/Component'], function (_export, _context) { + "use strict"; + + var Component, Widget; + return { + setters: [function (_flarumComponent) { + Component = _flarumComponent.default; + }], + execute: function () { + Widget = function (_Component) { + babelHelpers.inherits(Widget, _Component); + + function Widget() { + babelHelpers.classCallCheck(this, Widget); + return babelHelpers.possibleConstructorReturn(this, (Widget.__proto__ || Object.getPrototypeOf(Widget)).apply(this, arguments)); + } + + babelHelpers.createClass(Widget, [{ + key: 'view', + value: function view() { + return m( + 'div', + { className: "Widget " + this.className() }, + this.content() + ); + } + }, { + key: 'className', + value: function className() { + return ''; + } + }, { + key: 'content', + value: function content() { + return []; + } + }]); + return Widget; + }(Component); + + _export('default', Widget); } }; });; @@ -21108,6 +21112,254 @@ System.register('flarum/components/SplitDropdown', ['flarum/components/Dropdown' });; 'use strict'; +System.register('flarum/components/StatisticsWidget', ['flarum/components/DashboardWidget', 'flarum/helpers/icon', 'flarum/helpers/listItems', 'flarum/utils/ItemList'], function (_export, _context) { + "use strict"; + + var DashboardWidget, icon, listItems, ItemList, StatisticsWidget; + return { + setters: [function (_flarumComponentsDashboardWidget) { + DashboardWidget = _flarumComponentsDashboardWidget.default; + }, function (_flarumHelpersIcon) { + icon = _flarumHelpersIcon.default; + }, function (_flarumHelpersListItems) { + listItems = _flarumHelpersListItems.default; + }, function (_flarumUtilsItemList) { + ItemList = _flarumUtilsItemList.default; + }], + execute: function () { + StatisticsWidget = function (_DashboardWidget) { + babelHelpers.inherits(StatisticsWidget, _DashboardWidget); + + function StatisticsWidget() { + babelHelpers.classCallCheck(this, StatisticsWidget); + return babelHelpers.possibleConstructorReturn(this, (StatisticsWidget.__proto__ || Object.getPrototypeOf(StatisticsWidget)).apply(this, arguments)); + } + + babelHelpers.createClass(StatisticsWidget, [{ + key: 'className', + value: function className() { + return 'StatisticsWidget'; + } + }, { + key: 'content', + value: function content() { + return m( + 'table', + null, + m( + 'thead', + null, + m( + 'tr', + null, + m('th', null), + m( + 'th', + null, + app.translator.trans('core.admin.statistics.users_heading') + ), + m( + 'th', + null, + app.translator.trans('core.admin.statistics.discussions_heading') + ), + m( + 'th', + null, + app.translator.trans('core.admin.statistics.posts_heading') + ) + ) + ), + m( + 'tbody', + null, + m( + 'tr', + { className: 'StatisticsWidget-total' }, + m( + 'th', + null, + app.translator.trans('core.admin.statistics.total_label') + ), + m( + 'td', + null, + app.data.statistics.total.users + ), + m( + 'td', + null, + app.data.statistics.total.discussions + ), + m( + 'td', + null, + app.data.statistics.total.posts + ) + ), + m( + 'tr', + null, + m( + 'th', + null, + app.translator.trans('core.admin.statistics.last_28_days_label') + ), + m( + 'td', + null, + app.data.statistics.month.users + ), + m( + 'td', + null, + app.data.statistics.month.discussions + ), + m( + 'td', + null, + app.data.statistics.month.posts + ) + ), + m( + 'tr', + null, + m( + 'th', + null, + app.translator.trans('core.admin.statistics.last_7_days_label') + ), + m( + 'td', + null, + app.data.statistics.week.users + ), + m( + 'td', + null, + app.data.statistics.week.discussions + ), + m( + 'td', + null, + app.data.statistics.week.posts + ) + ), + m( + 'tr', + null, + m( + 'th', + null, + app.translator.trans('core.admin.statistics.today_label') + ), + m( + 'td', + null, + app.data.statistics.today.users + ), + m( + 'td', + null, + app.data.statistics.today.discussions + ), + m( + 'td', + null, + app.data.statistics.today.posts + ) + ) + ) + ); + } + }]); + return StatisticsWidget; + }(DashboardWidget); + + _export('default', StatisticsWidget); + } + }; +});; +'use strict'; + +System.register('flarum/components/StatusWidget', ['flarum/components/DashboardWidget', 'flarum/helpers/icon', 'flarum/helpers/listItems', 'flarum/utils/ItemList'], function (_export, _context) { + "use strict"; + + var DashboardWidget, icon, listItems, ItemList, StatusWidget; + return { + setters: [function (_flarumComponentsDashboardWidget) { + DashboardWidget = _flarumComponentsDashboardWidget.default; + }, function (_flarumHelpersIcon) { + icon = _flarumHelpersIcon.default; + }, function (_flarumHelpersListItems) { + listItems = _flarumHelpersListItems.default; + }, function (_flarumUtilsItemList) { + ItemList = _flarumUtilsItemList.default; + }], + execute: function () { + StatusWidget = function (_DashboardWidget) { + babelHelpers.inherits(StatusWidget, _DashboardWidget); + + function StatusWidget() { + babelHelpers.classCallCheck(this, StatusWidget); + return babelHelpers.possibleConstructorReturn(this, (StatusWidget.__proto__ || Object.getPrototypeOf(StatusWidget)).apply(this, arguments)); + } + + babelHelpers.createClass(StatusWidget, [{ + key: 'className', + value: function className() { + return 'StatusWidget'; + } + }, { + key: 'content', + value: function content() { + return m( + 'ul', + null, + listItems(this.items().toArray()) + ); + } + }, { + key: 'items', + value: function items() { + var items = new ItemList(); + + items.add('help', m( + 'a', + { href: 'http://flarum.org/docs/troubleshooting', target: '_blank' }, + icon('question-circle'), + ' ', + app.translator.trans('core.admin.dashboard.help_link') + )); + + items.add('version-flarum', [m( + 'strong', + null, + 'Flarum' + ), m('br', null), app.forum.attribute('version')]); + items.add('version-php', [m( + 'strong', + null, + 'PHP' + ), m('br', null), app.data.phpVersion]); + items.add('version-mysql', [m( + 'strong', + null, + 'MySQL' + ), m('br', null), app.data.mysqlVersion]); + + return items; + } + }]); + return StatusWidget; + }(DashboardWidget); + + _export('default', StatusWidget); + } + }; +});; +'use strict'; + System.register('flarum/components/Switch', ['flarum/components/Checkbox'], function (_export, _context) { "use strict"; @@ -21261,6 +21513,52 @@ System.register('flarum/components/UploadImageButton', ['flarum/components/Butto } }; });; +'use strict'; + +System.register('flarum/components/Widget', ['flarum/Component'], function (_export, _context) { + "use strict"; + + var Component, DashboardWidget; + return { + setters: [function (_flarumComponent) { + Component = _flarumComponent.default; + }], + execute: function () { + DashboardWidget = function (_Component) { + babelHelpers.inherits(DashboardWidget, _Component); + + function DashboardWidget() { + babelHelpers.classCallCheck(this, DashboardWidget); + return babelHelpers.possibleConstructorReturn(this, (DashboardWidget.__proto__ || Object.getPrototypeOf(DashboardWidget)).apply(this, arguments)); + } + + babelHelpers.createClass(DashboardWidget, [{ + key: 'view', + value: function view() { + return m( + 'div', + { className: "DashboardWidget " + this.className() }, + this.content() + ); + } + }, { + key: 'className', + value: function className() { + return ''; + } + }, { + key: 'content', + value: function content() { + return []; + } + }]); + return DashboardWidget; + }(Component); + + _export('default', DashboardWidget); + } + }; +});; "use strict"; System.register("flarum/extend", [], function (_export, _context) { diff --git a/framework/core/js/admin/src/components/DashboardPage.js b/framework/core/js/admin/src/components/DashboardPage.js index 0c7207d26..c1a7bd888 100644 --- a/framework/core/js/admin/src/components/DashboardPage.js +++ b/framework/core/js/admin/src/components/DashboardPage.js @@ -1,20 +1,14 @@ import Page from 'flarum/components/Page'; +import StatusWidget from 'flarum/components/StatusWidget'; +import StatisticsWidget from 'flarum/components/StatisticsWidget'; export default class DashboardPage extends Page { view() { return (
-

{app.translator.trans('core.admin.dashboard.welcome_text')}

-

{app.translator.trans('core.admin.dashboard.version_text', {version: {app.forum.attribute('version')}})}

-

{app.translator.trans('core.admin.dashboard.beta_warning_text', {strong: })}

- + +
); diff --git a/framework/core/js/admin/src/components/DashboardWidget.js b/framework/core/js/admin/src/components/DashboardWidget.js new file mode 100644 index 000000000..4e0b0ea96 --- /dev/null +++ b/framework/core/js/admin/src/components/DashboardWidget.js @@ -0,0 +1,38 @@ +/* + * This file is part of Flarum. + * + * (c) Toby Zerner + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import Component from 'flarum/Component'; + +export default class Widget extends Component { + view() { + return ( +
+ {this.content()} +
+ ); + } + + /** + * Get the class name to apply to the widget. + * + * @return {String} + */ + className() { + return ''; + } + + /** + * Get the content of the widget. + * + * @return {VirtualElement} + */ + content() { + return []; + } +} diff --git a/framework/core/js/admin/src/components/StatisticsWidget.js b/framework/core/js/admin/src/components/StatisticsWidget.js new file mode 100644 index 000000000..a653a3c0c --- /dev/null +++ b/framework/core/js/admin/src/components/StatisticsWidget.js @@ -0,0 +1,60 @@ +/* + * This file is part of Flarum. + * + * (c) Toby Zerner + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import DashboardWidget from 'flarum/components/DashboardWidget'; +import icon from 'flarum/helpers/icon'; +import listItems from 'flarum/helpers/listItems'; +import ItemList from 'flarum/utils/ItemList'; + +export default class StatisticsWidget extends DashboardWidget { + className() { + return 'StatisticsWidget'; + } + + content() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{app.translator.trans('core.admin.statistics.users_heading')}{app.translator.trans('core.admin.statistics.discussions_heading')}{app.translator.trans('core.admin.statistics.posts_heading')}
{app.translator.trans('core.admin.statistics.total_label')}{app.data.statistics.total.users}{app.data.statistics.total.discussions}{app.data.statistics.total.posts}
{app.translator.trans('core.admin.statistics.last_28_days_label')}{app.data.statistics.month.users}{app.data.statistics.month.discussions}{app.data.statistics.month.posts}
{app.translator.trans('core.admin.statistics.last_7_days_label')}{app.data.statistics.week.users}{app.data.statistics.week.discussions}{app.data.statistics.week.posts}
{app.translator.trans('core.admin.statistics.today_label')}{app.data.statistics.today.users}{app.data.statistics.today.discussions}{app.data.statistics.today.posts}
+ ); + } +} diff --git a/framework/core/js/admin/src/components/StatusWidget.js b/framework/core/js/admin/src/components/StatusWidget.js new file mode 100644 index 000000000..429b7a504 --- /dev/null +++ b/framework/core/js/admin/src/components/StatusWidget.js @@ -0,0 +1,41 @@ +/* + * This file is part of Flarum. + * + * (c) Toby Zerner + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import DashboardWidget from 'flarum/components/DashboardWidget'; +import icon from 'flarum/helpers/icon'; +import listItems from 'flarum/helpers/listItems'; +import ItemList from 'flarum/utils/ItemList'; + +export default class StatusWidget extends DashboardWidget { + className() { + return 'StatusWidget'; + } + + content() { + return ( +
    {listItems(this.items().toArray())}
+ ); + } + + items() { + const items = new ItemList(); + + items.add('help', ( +
+ {icon('question-circle')} {app.translator.trans('core.admin.dashboard.help_link')} + + )); + + items.add('version-flarum', [Flarum,
, app.forum.attribute('version')]); + items.add('version-php', [PHP,
, app.data.phpVersion]); + items.add('version-mysql', [MySQL,
, app.data.mysqlVersion]); + + return items; + } +} diff --git a/framework/core/js/admin/src/components/Widget.js b/framework/core/js/admin/src/components/Widget.js new file mode 100644 index 000000000..df0b2f999 --- /dev/null +++ b/framework/core/js/admin/src/components/Widget.js @@ -0,0 +1,38 @@ +/* + * This file is part of Flarum. + * + * (c) Toby Zerner + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import Component from 'flarum/Component'; + +export default class DashboardWidget extends Component { + view() { + return ( +
+ {this.content()} +
+ ); + } + + /** + * Get the class name to apply to the widget. + * + * @return {String} + */ + className() { + return ''; + } + + /** + * Get the content of the widget. + * + * @return {VirtualElement} + */ + content() { + return []; + } +} diff --git a/framework/core/less/admin/DashboardPage.less b/framework/core/less/admin/DashboardPage.less index fceee2a30..3c97eedc3 100644 --- a/framework/core/less/admin/DashboardPage.less +++ b/framework/core/less/admin/DashboardPage.less @@ -2,20 +2,81 @@ background: @control-bg; color: @control-color; min-height: 100vh; - font-size: 14px; - line-height: 1.7; @media @desktop-up { .container { - max-width: 600px; padding: 30px; margin: 0; } } +} - h2 { - font-size: 26px; - font-weight: 300; - margin-top: 0; +.Widget { + background: @body-bg; + color: @text-color; + border-radius: @border-radius; + padding: 20px; + margin-bottom: 20px; +} + +.StatusWidget { + color: @muted-color; + + > ul { + margin: 0; + padding: 0; + list-style-type: none; + + > li { + display: inline-block; + margin-right: 30px; + vertical-align: middle; + + &[class^="item-version-"] { + max-width: 100px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + &.item-help { + float: right; + margin-right: 0; + } + } + } +} + +.StatisticsWidget { + td { + font-size: 14px; + } + td, th { + padding: 5px 20px 5px 0; + text-align: left; + font-weight: normal; + border-bottom: 1px solid @control-bg; + } + tbody tr:last-child { + td, th { + border-bottom: 0; + } + } + th { + color: @muted-color; + } + thead th { + font-weight: bold; + } +} +.StatisticsWidget-total { + td, th { + font-weight: bold; + } +} +@media @tablet-up { + .StatisticsWidget { + td, th { + min-width: 120px; + } } } diff --git a/framework/core/src/Admin/Controller/WebAppController.php b/framework/core/src/Admin/Controller/WebAppController.php index d0870900a..30c660ff3 100644 --- a/framework/core/src/Admin/Controller/WebAppController.php +++ b/framework/core/src/Admin/Controller/WebAppController.php @@ -11,13 +11,18 @@ namespace Flarum\Admin\Controller; +use DateTime; use Flarum\Admin\WebApp; +use Flarum\Core\Discussion; use Flarum\Core\Permission; +use Flarum\Core\Post; +use Flarum\Core\User; use Flarum\Event\PrepareUnserializedSettings; use Flarum\Extension\ExtensionManager; use Flarum\Http\Controller\AbstractWebAppController; use Flarum\Settings\SettingsRepositoryInterface; use Illuminate\Contracts\Events\Dispatcher; +use Illuminate\Database\ConnectionInterface; use Psr\Http\Message\ServerRequestInterface; class WebAppController extends AbstractWebAppController @@ -37,13 +42,15 @@ class WebAppController extends AbstractWebAppController * @param Dispatcher $events * @param SettingsRepositoryInterface $settings * @param ExtensionManager $extensions + * @param ConnectionInterface $db */ - public function __construct(WebApp $webApp, Dispatcher $events, SettingsRepositoryInterface $settings, ExtensionManager $extensions) + public function __construct(WebApp $webApp, Dispatcher $events, SettingsRepositoryInterface $settings, ExtensionManager $extensions, ConnectionInterface $db) { $this->webApp = $webApp; $this->events = $events; $this->settings = $settings; $this->extensions = $extensions; + $this->db = $db; } /** @@ -63,6 +70,40 @@ class WebAppController extends AbstractWebAppController $view->setVariable('permissions', Permission::map()); $view->setVariable('extensions', $this->extensions->getExtensions()->toArray()); + $view->setVariable('phpVersion', PHP_VERSION); + $view->setVariable('mysqlVersion', $this->db->selectOne('select version() as version')->version); + + $view->setVariable('statistics', $this->getStatistics()); + return $view; } + + private function getStatistics() + { + return [ + 'total' => $this->getEntityCounts(), + 'month' => $this->getEntityCounts(new DateTime('-28 days')), + 'week' => $this->getEntityCounts(new DateTime('-7 days')), + 'today' => $this->getEntityCounts(new DateTime('-1 day')) + ]; + } + + private function getEntityCounts($since = null) + { + $queries = [ + 'users' => User::query(), + 'discussions' => Discussion::query(), + 'posts' => Post::where('type', 'comment') + ]; + + if ($since) { + $queries['users']->where('join_time', '>', $since); + $queries['discussions']->where('start_time', '>', $since); + $queries['posts']->where('time', '>', $since); + } + + return array_map(function ($query) { + return $query->count(); + }, $queries); + } }