Commit d7525b53 authored by Oliver Bartsch's avatar Oliver Bartsch Committed by Richard Haeser
Browse files

[FEATURE] Dashboard widget: number of failed logins

A new widget is added which displays the number of failed
logins during the last 24 hours.

Resolves: #90355
Releases: master
Change-Id: I24e9e40fd7ed567f97867910d6988d8662fd7a14
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/63364


Tested-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Tested-by: default avatarKoen Wouters <koen.wouters@maxserv.com>
Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: Richard Haeser's avatarRichard Haeser <richard@maxserv.com>
Reviewed-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Reviewed-by: default avatarKoen Wouters <koen.wouters@maxserv.com>
Reviewed-by: Richard Haeser's avatarRichard Haeser <richard@maxserv.com>
parent d4c019cb
......@@ -39,6 +39,7 @@
}
.widget-content-main {
display: flex;
flex-grow: 1;
overflow-y: auto;
padding: $widget-padding;
......
.dashboard-widget-number--icon {
.widget-number-icon {
display: flex;
justify-content: center;
align-items: center;
......@@ -7,20 +7,20 @@
color: $text-color;
}
.dashboard-widget-number--content {
.widget-number-content {
display: flex;
flex-direction: column;
justify-content: center;
}
.dashboard-widget-number--title {
.widget-number-title {
line-height: 1.3;
margin-bottom: 5px;
font-size: 16px;
color: $text-color;
}
.dashboard-widget-number--number {
.widget-number-number {
line-height: 1.3;
font-weight: 900;
font-size: 24px;
......
......@@ -36,7 +36,8 @@ The following widgets are shipped by core extensions now:
* TypoScript Template Reference: This widget will provide a link to the TypoScript Template Reference (EXT:dashboard)
* TSconfig Reference: This widget will provide a link to the TSconfig Reference (EXT:dashboard)
* Number of errors in system log: Shows the number of errors in the sys_log grouped by day for the last month (EXT:dashboard)
* Type of backend users: A widget to show the different types of backend users
* Type of backend users: A widget to show the different types of backend users (EXT:dashboard)
* Failed Logins: This widget will show you the number of failed logins during the last 24 hours (EXT:dashboard)
Creating your own widget
^^^^^^^^^^^^^^^^^^^^^^^^
......@@ -50,6 +51,7 @@ do so, you can extend one of the WidgetAbstracts available in EXT:dashboard.
* AbstractChartWidget: the base of all chart widgets
* AbstractBarChartWidget: when you want to show a widget with a bar-chart you can extend this class
* AbstractDoughnutChartWidget: this abstract gives you the possibility to create a doughnut-chart widget
* AbstractNumberWithIconWidget: this abstract will give you the possibility to show a title, number and an icon
By extending one of those abstracts, and provide it with the right data, you are able to
have a new widget quite fast. The only thing that is left is to register the widget.
......
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Dashboard\Widgets;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
/**
* The AbstractTextWidget class is the basic widget class for simple text content.
* It is possible to extend this class for own widgets.
* In your class you have to set $this->text with the text to display.
*/
abstract class AbstractNumberWithIconWidget extends AbstractWidget
{
/**
* @inheritDoc
*/
protected $iconIdentifier = 'content-widget-number';
/**
* @inheritDoc
*/
protected $templateName = 'NumberWithIconWidget';
/**
* When filled, a subtitle is shown below the title
*
* @var string
*/
protected $subtitle;
/**
* This number will be the main data in the widget
*
* @var int
*/
protected $number;
/**
* This property contains the identifier of the icon that should be shown in the widget
*
* @var string
*/
protected $icon;
protected function initializeView(): void
{
parent::initializeView();
$this->view->assign('icon', $this->icon);
$this->view->assign('subtitle', $this->getSubTitle());
$this->view->assign('number', $this->number);
}
public function getSubTitle(): string
{
return $this->getLanguageService()->sL($this->subtitle) ?: $this->subtitle;
}
}
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Dashboard\Widgets;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\SysLog\Action\Login as SystemLogLoginAction;
use TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
use TYPO3\CMS\Core\SysLog\Type as SystemLogType;
use TYPO3\CMS\Core\Utility\GeneralUtility;
class FailedLoginsWidget extends AbstractNumberWithIconWidget
{
protected $title = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.title';
protected $description = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.description';
protected $subtitle = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.subtitle';
protected $icon = 'content-elements-login';
protected function initializeView(): void
{
$this->number = $this->getNumberOfFailedLogins();
parent::initializeView();
}
/**
* Get number of failed logins during a period
*
* @param int $secondsBack
*
* @return int
*/
public function getNumberOfFailedLogins(int $secondsBack = 86400): int
{
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
$queryBuilder = $connectionPool->getQueryBuilderForTable('sys_log');
return (int)$queryBuilder->count('uid')
->from('sys_log')
->where(
$queryBuilder->expr()->eq(
'type',
$queryBuilder->createNamedParameter(SystemLogType::LOGIN, Connection::PARAM_INT)
),
$queryBuilder->expr()->eq(
'action',
$queryBuilder->createNamedParameter(SystemLogLoginAction::ATTEMPT, Connection::PARAM_INT)
),
$queryBuilder->expr()->neq(
'error',
$queryBuilder->createNamedParameter(SystemLogErrorClassification::MESSAGE, Connection::PARAM_INT)
),
$queryBuilder->expr()->gt(
'tstamp',
$queryBuilder->createNamedParameter($GLOBALS['EXEC_TIME'] - $secondsBack, Connection::PARAM_INT)
)
)
->execute()
->fetchColumn();
}
}
......@@ -58,6 +58,13 @@ services:
identifier: typeOfUsers
widgetGroups: 'systemInfo'
TYPO3\CMS\Dashboard\Widgets\FailedLoginsWidget:
arguments: ['failedLogins']
tags:
- name: dashboard.widget
identifier: failedLogins
widgetGroups: 'general'
TYPO3\CMS\Dashboard\Widgets\T3NewsWidget:
arguments: ['t3news']
tags:
......
......@@ -161,6 +161,16 @@
<source>Admin users</source>
</trans-unit>
<trans-unit id="widgets.failedLogins.title" xml:space="preserve">
<source>Failed backend logins</source>
</trans-unit>
<trans-unit id="widgets.failedLogins.subtitle" xml:space="preserve">
<source>In last 24 hours</source>
</trans-unit>
<trans-unit id="widgets.failedLogins.description" xml:space="preserve">
<source>Information about the number of failed logins during the last 24 hours.</source>
</trans-unit>
<trans-unit id="widget_group.general" xml:space="preserve">
<source>General</source>
</trans-unit>
......
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers" data-namespace-typo3-fluid="true">
<div class="widget-content-main">
<f:if condition="{icon}">
<div class="widget-number-icon">
<core:icon identifier="{icon}" size="large" alternativeMarkupIdentifier="inline" />
</div>
</f:if>
<div class="widget-number-content">
<div class="widget-number-title">{title}</div>
<f:if condition="{subtitle}">
<div class="widget-number-subtitle"><small>{subtitle}</small></div>
</f:if>
<div class="widget-number-number">{number}</div>
</div>
</div>
</html>
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers" data-namespace-typo3-fluid="true">
<f:layout name="Widget/Widget" />
<f:section name="main">
<f:comment>You can put your text in this template</f:comment>
</f:section>
</html>
......@@ -10,4 +10,4 @@
*
* The TYPO3 project - inspiring people to share!
*/
.module.module{background-color:#eaeaea}.module.module h1{line-height:calc(48 / 32);margin-bottom:20px;font-weight:900;font-size:32px}.dashboard-header{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;margin:-24px -24px 24px;padding:24px 24px 0;background:#dadada;border-bottom:1px solid #cdcdcd}.dashboard-tabs{display:flex;flex-wrap:wrap;align-items:center}.dashboard-tab{border-radius:5px 5px 0 0;display:inline-block;padding:12px;margin-right:2px;background:#bababa;color:#000}.dashboard-tab:focus,.dashboard-tab:hover{text-decoration:none;background:#adadad;color:#000}.dashboard-tab--active{background:#ff8700;color:#fff}.dashboard-tab--active:focus,.dashboard-tab--active:hover{text-decoration:none;background:#e67a00;color:#f2f2f2}.dashboard-button-tab-add{margin:5px}.dashboard-configuration{padding:10px 0}.dashboard-configuration-button{margin-left:10px;color:#737373;text-decoration:none}.dashboard-configuration-button:focus,.dashboard-configuration-button:hover{color:#ff8700;text-decoration:none}.dashboard-configuration-button:active{color:#000;text-decoration:none}.dashboard-empty{position:relative}.dashboard-empty-content{background-color:rgba(0,0,0,.05);border:2px dashed rgba(0,0,0,.15);padding:2.5em;text-align:center}.dashboard-empty-content h3{font-size:1.5em;margin-bottom:.5em}.dashboard-empty-content p{font-size:1.25em;margin-bottom:1em}.dashboard-empty-content>:first-child{margin-top:0}.dashboard-empty-content>:last-child{margin-bottom:0}.dashboard-grid{position:relative;margin-right:-10px;margin-left:-10px}.dashboard-item{position:absolute;z-index:1;padding:10px;width:100%;height:auto}@media screen and (min-width:750px){.dashboard-item{width:50%;height:200px}}@media screen and (min-width:1285px){.dashboard-item{width:25%}}.dashboard-item.muuri-item-positioning{z-index:2}.dashboard-item.muuri-item-positioning .widget-remove{display:none}.dashboard-item.muuri-item-placeholder{z-index:2;margin:0;opacity:.5}.dashboard-item.muuri-item-placeholder .widget{border:1px dashed #737373}.dashboard-item.muuri-item-placeholder .widget-remove{display:none}.dashboard-item.muuri-item-dragging,.dashboard-item.muuri-item-releasing{z-index:9999}.dashboard-item.muuri-item-releasing .widget-remove{display:none}.dashboard-item.muuri-item-dragging{cursor:move}.dashboard-item.muuri-item-hidden{z-index:0}.dashboard-item.widget-waiting{line-height:200px}.dashboard-item--enableSelect{-webkit-user-select:auto!important;-moz-user-select:auto!important;-ms-user-select:auto!important;user-select:auto!important}@media screen and (min-width:750px){.dashboard-item--h4{height:400px}}@media screen and (min-width:750px){.dashboard-item--h6{height:600px}}.dashboard-item--w4{width:100%}@media screen and (min-width:1285px){.dashboard-item--w4{width:50%}}.dashboard-item-content{position:relative;width:100%;height:100%}.dashboard-button{display:inline-flex;align-items:center;border-radius:3px;background:#313131;color:#fff;padding:8px;text-decoration:none}.dashboard-button:focus,.dashboard-button:hover{text-decoration:none;background:#ff8700;color:#fff}.dashboard-button .dashboard-button-icon .icon{display:block}.dashboard-button .dashboard-button-icon+.dashboard-button-text{margin-left:.25em;margin-right:.25em}.dashboard-button-add{position:fixed;padding:16px;right:24px;bottom:24px;z-index:2}.widget{height:100%;border-radius:2px;overflow:hidden;background-color:#fff;box-shadow:0 2px 2px 0 rgba(0,0,0,.15);color:#000}.widget:hover .widget-actions{opacity:1}.widget-content{display:flex;flex-direction:column;height:100%}.widget-content-title{padding:10px 20px;padding-right:76px;border-bottom:1px solid #d7d7d7;font-family:"Source Sans Pro",sans-serif;font-size:16px;font-weight:700;line-height:1.25}.widget-content-title span{overflow:hidden;display:block;white-space:nowrap;text-overflow:ellipsis}.widget-content-main{flex-grow:1;overflow-y:auto;padding:20px}.widget-content-footer{padding:20px;padding-top:0}.widget-actions{position:absolute;display:flex;top:calc(((16px * 1.25)/ 2) + (20px / 2));right:10px;transform:translate(0,-50%);opacity:0;transition:opacity .2s ease-in-out}.widget-action{width:28px;height:28px;position:relative;color:#737373;text-align:center}.widget-action:focus,.widget-action:hover{color:#ff8700}.widget-action .icon{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.widget-action-move{cursor:-webkit-grab;cursor:grab}.widget-waiting{position:absolute;top:50%;left:50%;line-height:300px;margin-right:-50%;transform:translate(-50%,-50%)}.widget-error{padding:20px;position:absolute;top:50%;text-align:center;transform:translateY(-50%);color:#c83c3c}.widget-chart{width:100%;height:100%}.widget-edit{width:45px;text-align:center}.widget-editIcon{color:#000}.widget-editIcon:focus,.widget-editIcon:hover{color:#ff8700}.widget-table{width:100%;color:#000}.widget-table thead tr{background-color:transparent}.widget-table tr:nth-child(odd){background-color:transparent}.widget-table tr:nth-child(even){background-color:#f2f2f2}.widget-table tbody td,.widget-table tbody th{border-top:1px solid #e0e0e0}.widget-table tbody:first-child tr:first-child td,.widget-table tbody:first-child tr:first-child th{border-top:none}.widget-table td,.widget-table th{padding:10px}.widget-table td>:first-child,.widget-table th>:first-child{margin-top:0}.widget-table td>:last-child,.widget-table th>:last-child{margin-bottom:0}.widget-table th{font-weight:700}.widget-content-main .widget-table-wrapper{margin-top:-10px;margin-left:-20px;margin-right:-20px}.widget-content-main .widget-table-wrapper td:first-child,.widget-content-main .widget-table-wrapper th:first-child{padding-left:20px}.widget-content-main .widget-table-wrapper td:last-child,.widget-content-main .widget-table-wrapper th:last-child{padding-right:20px}.widget-cta{display:flex;justify-content:center;align-items:center;background-color:#313131;color:#fff;border-radius:3px;padding:8px}.widget-cta:focus,.widget-cta:hover{text-decoration:none;background:#ff8700;color:#fff}.widget-cta-icon{display:flex;justify-content:center;align-items:center;width:18px;height:18px;margin-right:12px;color:#fff}.widget-doughnut--value{line-height:1.3;font-weight:900;font-size:36px;text-align:center}.widget-doughnut--meta{margin-top:10px;font-style:italic;color:#737373;text-align:center}.dashboard-widget-number--icon{display:flex;justify-content:center;align-items:center;width:42px;margin-right:20px;color:#000}.dashboard-widget-number--content{display:flex;flex-direction:column;justify-content:center}.dashboard-widget-number--title{line-height:1.3;margin-bottom:5px;font-size:16px;color:#000}.dashboard-widget-number--number{line-height:1.3;font-weight:900;font-size:24px}
\ No newline at end of file
.module.module{background-color:#eaeaea}.module.module h1{line-height:calc(48 / 32);margin-bottom:20px;font-weight:900;font-size:32px}.dashboard-header{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;margin:-24px -24px 24px;padding:24px 24px 0;background:#dadada;border-bottom:1px solid #cdcdcd}.dashboard-tabs{display:flex;flex-wrap:wrap;align-items:center}.dashboard-tab{border-radius:5px 5px 0 0;display:inline-block;padding:12px;margin-right:2px;background:#bababa;color:#000}.dashboard-tab:focus,.dashboard-tab:hover{text-decoration:none;background:#adadad;color:#000}.dashboard-tab--active{background:#ff8700;color:#fff}.dashboard-tab--active:focus,.dashboard-tab--active:hover{text-decoration:none;background:#e67a00;color:#f2f2f2}.dashboard-button-tab-add{margin:5px}.dashboard-configuration{padding:10px 0}.dashboard-configuration-button{margin-left:10px;color:#737373;text-decoration:none}.dashboard-configuration-button:focus,.dashboard-configuration-button:hover{color:#ff8700;text-decoration:none}.dashboard-configuration-button:active{color:#000;text-decoration:none}.dashboard-empty{position:relative}.dashboard-empty-content{background-color:rgba(0,0,0,.05);border:2px dashed rgba(0,0,0,.15);padding:2.5em;text-align:center}.dashboard-empty-content h3{font-size:1.5em;margin-bottom:.5em}.dashboard-empty-content p{font-size:1.25em;margin-bottom:1em}.dashboard-empty-content>:first-child{margin-top:0}.dashboard-empty-content>:last-child{margin-bottom:0}.dashboard-grid{position:relative;margin-right:-10px;margin-left:-10px}.dashboard-item{position:absolute;z-index:1;padding:10px;width:100%;height:auto}@media screen and (min-width:750px){.dashboard-item{width:50%;height:200px}}@media screen and (min-width:1285px){.dashboard-item{width:25%}}.dashboard-item.muuri-item-positioning{z-index:2}.dashboard-item.muuri-item-positioning .widget-remove{display:none}.dashboard-item.muuri-item-placeholder{z-index:2;margin:0;opacity:.5}.dashboard-item.muuri-item-placeholder .widget{border:1px dashed #737373}.dashboard-item.muuri-item-placeholder .widget-remove{display:none}.dashboard-item.muuri-item-dragging,.dashboard-item.muuri-item-releasing{z-index:9999}.dashboard-item.muuri-item-releasing .widget-remove{display:none}.dashboard-item.muuri-item-dragging{cursor:move}.dashboard-item.muuri-item-hidden{z-index:0}.dashboard-item.widget-waiting{line-height:200px}.dashboard-item--enableSelect{-webkit-user-select:auto!important;-moz-user-select:auto!important;-ms-user-select:auto!important;user-select:auto!important}@media screen and (min-width:750px){.dashboard-item--h4{height:400px}}@media screen and (min-width:750px){.dashboard-item--h6{height:600px}}.dashboard-item--w4{width:100%}@media screen and (min-width:1285px){.dashboard-item--w4{width:50%}}.dashboard-item-content{position:relative;width:100%;height:100%}.dashboard-button{display:inline-flex;align-items:center;border-radius:3px;background:#313131;color:#fff;padding:8px;text-decoration:none}.dashboard-button:focus,.dashboard-button:hover{text-decoration:none;background:#ff8700;color:#fff}.dashboard-button .dashboard-button-icon .icon{display:block}.dashboard-button .dashboard-button-icon+.dashboard-button-text{margin-left:.25em;margin-right:.25em}.dashboard-button-add{position:fixed;padding:16px;right:24px;bottom:24px;z-index:2}.widget{height:100%;border-radius:2px;overflow:hidden;background-color:#fff;box-shadow:0 2px 2px 0 rgba(0,0,0,.15);color:#000}.widget:hover .widget-actions{opacity:1}.widget-content{display:flex;flex-direction:column;height:100%}.widget-content-title{padding:10px 20px;padding-right:76px;border-bottom:1px solid #d7d7d7;font-family:"Source Sans Pro",sans-serif;font-size:16px;font-weight:700;line-height:1.25}.widget-content-title span{overflow:hidden;display:block;white-space:nowrap;text-overflow:ellipsis}.widget-content-main{display:flex;flex-grow:1;overflow-y:auto;padding:20px}.widget-content-footer{padding:20px;padding-top:0}.widget-actions{position:absolute;display:flex;top:calc(((16px * 1.25)/ 2) + (20px / 2));right:10px;transform:translate(0,-50%);opacity:0;transition:opacity .2s ease-in-out}.widget-action{width:28px;height:28px;position:relative;color:#737373;text-align:center}.widget-action:focus,.widget-action:hover{color:#ff8700}.widget-action .icon{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.widget-action-move{cursor:-webkit-grab;cursor:grab}.widget-waiting{position:absolute;top:50%;left:50%;line-height:300px;margin-right:-50%;transform:translate(-50%,-50%)}.widget-error{padding:20px;position:absolute;top:50%;text-align:center;transform:translateY(-50%);color:#c83c3c}.widget-chart{width:100%;height:100%}.widget-edit{width:45px;text-align:center}.widget-editIcon{color:#000}.widget-editIcon:focus,.widget-editIcon:hover{color:#ff8700}.widget-table{width:100%;color:#000}.widget-table thead tr{background-color:transparent}.widget-table tr:nth-child(odd){background-color:transparent}.widget-table tr:nth-child(even){background-color:#f2f2f2}.widget-table tbody td,.widget-table tbody th{border-top:1px solid #e0e0e0}.widget-table tbody:first-child tr:first-child td,.widget-table tbody:first-child tr:first-child th{border-top:none}.widget-table td,.widget-table th{padding:10px}.widget-table td>:first-child,.widget-table th>:first-child{margin-top:0}.widget-table td>:last-child,.widget-table th>:last-child{margin-bottom:0}.widget-table th{font-weight:700}.widget-content-main .widget-table-wrapper{margin-top:-10px;margin-left:-20px;margin-right:-20px}.widget-content-main .widget-table-wrapper td:first-child,.widget-content-main .widget-table-wrapper th:first-child{padding-left:20px}.widget-content-main .widget-table-wrapper td:last-child,.widget-content-main .widget-table-wrapper th:last-child{padding-right:20px}.widget-cta{display:flex;justify-content:center;align-items:center;background-color:#313131;color:#fff;border-radius:3px;padding:8px}.widget-cta:focus,.widget-cta:hover{text-decoration:none;background:#ff8700;color:#fff}.widget-cta-icon{display:flex;justify-content:center;align-items:center;width:18px;height:18px;margin-right:12px;color:#fff}.widget-doughnut--value{line-height:1.3;font-weight:900;font-size:36px;text-align:center}.widget-doughnut--meta{margin-top:10px;font-style:italic;color:#737373;text-align:center}.widget-number-icon{display:flex;justify-content:center;align-items:center;width:42px;margin-right:20px;color:#000}.widget-number-content{display:flex;flex-direction:column;justify-content:center}.widget-number-title{line-height:1.3;margin-bottom:5px;font-size:16px;color:#000}.widget-number-number{line-height:1.3;font-weight:900;font-size:24px}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment