Basic implementation of dashboard widgets, statistics

Currently not user-customizable. Just needed to display statistics for a
client, so figured I'd make a start at this. Nothing too fancy for now,
but I'm sure some people will be happy to have this information at their
fingertips.
This commit is contained in:
Toby Zerner 2017-11-29 17:19:20 +10:30
parent 50047851d1
commit d592322a61
8 changed files with 646 additions and 75 deletions

View File

@ -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) {

View File

@ -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 (
<div className="DashboardPage">
<div className="container">
<h2>{app.translator.trans('core.admin.dashboard.welcome_text')}</h2>
<p>{app.translator.trans('core.admin.dashboard.version_text', {version: <strong>{app.forum.attribute('version')}</strong>})}</p>
<p>{app.translator.trans('core.admin.dashboard.beta_warning_text', {strong: <strong/>})}</p>
<ul>
<li>{app.translator.trans('core.admin.dashboard.contributing_text', {a: <a href="http://flarum.org/docs/contributing" target="_blank"/>})}</li>
<li>{app.translator.trans('core.admin.dashboard.troubleshooting_text', {a: <a href="http://flarum.org/docs/troubleshooting" target="_blank"/>})}</li>
<li>{app.translator.trans('core.admin.dashboard.support_text', {a: <a href="http://discuss.flarum.org/t/support" target="_blank"/>})}</li>
<li>{app.translator.trans('core.admin.dashboard.features_text', {a: <a href="http://discuss.flarum.org/t/features" target="_blank"/>})}</li>
<li>{app.translator.trans('core.admin.dashboard.extension_text', {a: <a href="http://flarum.org/docs/extend" target="_blank"/>})}</li>
</ul>
<StatusWidget/>
<StatisticsWidget/>
</div>
</div>
);

View File

@ -0,0 +1,38 @@
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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 (
<div className={"Widget "+this.className()}>
{this.content()}
</div>
);
}
/**
* Get the class name to apply to the widget.
*
* @return {String}
*/
className() {
return '';
}
/**
* Get the content of the widget.
*
* @return {VirtualElement}
*/
content() {
return [];
}
}

View File

@ -0,0 +1,60 @@
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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 (
<table>
<thead>
<tr>
<th></th>
<th>{app.translator.trans('core.admin.statistics.users_heading')}</th>
<th>{app.translator.trans('core.admin.statistics.discussions_heading')}</th>
<th>{app.translator.trans('core.admin.statistics.posts_heading')}</th>
</tr>
</thead>
<tbody>
<tr className="StatisticsWidget-total">
<th>{app.translator.trans('core.admin.statistics.total_label')}</th>
<td>{app.data.statistics.total.users}</td>
<td>{app.data.statistics.total.discussions}</td>
<td>{app.data.statistics.total.posts}</td>
</tr>
<tr>
<th>{app.translator.trans('core.admin.statistics.last_28_days_label')}</th>
<td>{app.data.statistics.month.users}</td>
<td>{app.data.statistics.month.discussions}</td>
<td>{app.data.statistics.month.posts}</td>
</tr>
<tr>
<th>{app.translator.trans('core.admin.statistics.last_7_days_label')}</th>
<td>{app.data.statistics.week.users}</td>
<td>{app.data.statistics.week.discussions}</td>
<td>{app.data.statistics.week.posts}</td>
</tr>
<tr>
<th>{app.translator.trans('core.admin.statistics.today_label')}</th>
<td>{app.data.statistics.today.users}</td>
<td>{app.data.statistics.today.discussions}</td>
<td>{app.data.statistics.today.posts}</td>
</tr>
</tbody>
</table>
);
}
}

View File

@ -0,0 +1,41 @@
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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 (
<ul>{listItems(this.items().toArray())}</ul>
);
}
items() {
const items = new ItemList();
items.add('help', (
<a href="http://flarum.org/docs/troubleshooting" target="_blank">
{icon('question-circle')} {app.translator.trans('core.admin.dashboard.help_link')}
</a>
));
items.add('version-flarum', [<strong>Flarum</strong>, <br/>, app.forum.attribute('version')]);
items.add('version-php', [<strong>PHP</strong>, <br/>, app.data.phpVersion]);
items.add('version-mysql', [<strong>MySQL</strong>, <br/>, app.data.mysqlVersion]);
return items;
}
}

View File

@ -0,0 +1,38 @@
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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 (
<div className={"DashboardWidget "+this.className()}>
{this.content()}
</div>
);
}
/**
* Get the class name to apply to the widget.
*
* @return {String}
*/
className() {
return '';
}
/**
* Get the content of the widget.
*
* @return {VirtualElement}
*/
content() {
return [];
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}