summaryrefslogtreecommitdiffstats
path: root/library/Reporting
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:29:16 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:29:16 +0000
commit982c30171dbfe32bf692d188408dfbb9b5821f7e (patch)
tree8a4056adab2a0ed9795678eb05848bcfcaefa886 /library/Reporting
parentAdding debian version 0.10.0-2. (diff)
downloadicingaweb2-module-reporting-982c30171dbfe32bf692d188408dfbb9b5821f7e.tar.xz
icingaweb2-module-reporting-982c30171dbfe32bf692d188408dfbb9b5821f7e.zip
Merging upstream version 1.0.1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'library/Reporting')
-rw-r--r--library/Reporting/Actions/SendMail.php40
-rw-r--r--library/Reporting/Cli/Command.php4
-rw-r--r--library/Reporting/Database.php103
-rw-r--r--library/Reporting/Dimensions.php1
-rw-r--r--library/Reporting/Hook/ActionHook.php8
-rw-r--r--library/Reporting/Hook/ReportHook.php13
-rw-r--r--library/Reporting/Mail.php19
-rw-r--r--library/Reporting/Model/Config.php47
-rw-r--r--library/Reporting/Model/Report.php71
-rw-r--r--library/Reporting/Model/Reportlet.php48
-rw-r--r--library/Reporting/Model/Schedule.php48
-rw-r--r--library/Reporting/Model/Schema.php49
-rw-r--r--library/Reporting/Model/Template.php53
-rw-r--r--library/Reporting/Model/Timeframe.php53
-rw-r--r--library/Reporting/ProvidedActions.php1
-rw-r--r--library/Reporting/ProvidedHook/DbMigration.php79
-rw-r--r--library/Reporting/ProvidedReports.php1
-rw-r--r--library/Reporting/Report.php249
-rw-r--r--library/Reporting/ReportData.php1
-rw-r--r--library/Reporting/ReportRow.php1
-rw-r--r--library/Reporting/Reportlet.php62
-rw-r--r--library/Reporting/Reports/SystemReport.php29
-rw-r--r--library/Reporting/RetryConnection.php1
-rw-r--r--library/Reporting/Schedule.php177
-rw-r--r--library/Reporting/Scheduler.php176
-rw-r--r--library/Reporting/Str.php3
-rw-r--r--library/Reporting/Timeframe.php97
-rw-r--r--library/Reporting/Timerange.php1
-rw-r--r--library/Reporting/Values.php1
-rw-r--r--library/Reporting/Web/Controller.php11
-rw-r--r--library/Reporting/Web/Flatpickr.php77
-rw-r--r--library/Reporting/Web/Forms/DecoratedElement.php17
-rw-r--r--library/Reporting/Web/Forms/Decorator/CompatDecorator.php63
-rw-r--r--library/Reporting/Web/Forms/ReportForm.php179
-rw-r--r--library/Reporting/Web/Forms/ScheduleForm.php195
-rw-r--r--library/Reporting/Web/Forms/SendForm.php8
-rw-r--r--library/Reporting/Web/Forms/TemplateForm.php190
-rw-r--r--library/Reporting/Web/Forms/TimeframeForm.php195
-rw-r--r--library/Reporting/Web/ReportsTimeframesAndTemplatesTabs.php22
-rw-r--r--library/Reporting/Web/Widget/CompatDropdown.php1
-rw-r--r--library/Reporting/Web/Widget/CoverPage.php18
-rw-r--r--library/Reporting/Web/Widget/HeaderOrFooter.php10
-rw-r--r--library/Reporting/Web/Widget/Template.php44
43 files changed, 1373 insertions, 1093 deletions
diff --git a/library/Reporting/Actions/SendMail.php b/library/Reporting/Actions/SendMail.php
index 7c70bf5..a0dc42f 100644
--- a/library/Reporting/Actions/SendMail.php
+++ b/library/Reporting/Actions/SendMail.php
@@ -1,4 +1,5 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Actions;
@@ -9,6 +10,9 @@ use Icinga\Module\Reporting\Hook\ActionHook;
use Icinga\Module\Reporting\Mail;
use Icinga\Module\Reporting\Report;
use ipl\Html\Form;
+use ipl\Stdlib\Str;
+use ipl\Validator\CallbackValidator;
+use ipl\Validator\EmailAddressValidator;
class SendMail extends ActionHook
{
@@ -28,7 +32,9 @@ class SendMail extends ActionHook
$mail = new Mail();
- $mail->setFrom(Config::module('reporting')->get('mail', 'from', 'reporting@icinga'));
+ $mail->setFrom(
+ Config::module('reporting', 'config', true)->get('mail', 'from', 'reporting@icinga')
+ );
if (isset($config['subject'])) {
$mail->setSubject($config['subject']);
@@ -51,9 +57,10 @@ class SendMail extends ActionHook
throw new \InvalidArgumentException();
}
- $recipients = array_filter(preg_split('/[\s,]+/', $config['recipients']));
+ /** @var array<int, string> $recipients */
+ $recipients = preg_split('/[\s,]+/', $config['recipients']);
- $mail->send(null, $recipients);
+ $mail->send(null, array_filter($recipients));
}
public function initConfigForm(Form $form, Report $report)
@@ -66,19 +73,34 @@ class SendMail extends ActionHook
}
$form->addElement('select', 'type', [
- 'required' => true,
- 'label' => t('Type'),
- 'options' => $types
+ 'required' => true,
+ 'label' => t('Type'),
+ 'options' => $types
]);
$form->addElement('text', 'subject', [
- 'label' => t('Subject'),
- 'placeholder' => Mail::DEFAULT_SUBJECT
+ 'label' => t('Subject'),
+ 'placeholder' => Mail::DEFAULT_SUBJECT
]);
$form->addElement('textarea', 'recipients', [
'required' => true,
- 'label' => t('Recipients')
+ 'label' => t('Recipients'),
+ 'validators' => [
+ new CallbackValidator(function ($value, CallbackValidator $validator): bool {
+ $mailValidator = new EmailAddressValidator();
+ $mails = Str::trimSplit($value);
+ foreach ($mails as $mail) {
+ if (! $mailValidator->isValid($mail)) {
+ $validator->addMessage(...$mailValidator->getMessages());
+
+ return false;
+ }
+ }
+
+ return true;
+ })
+ ]
]);
}
}
diff --git a/library/Reporting/Cli/Command.php b/library/Reporting/Cli/Command.php
index a89f77b..16ce8fa 100644
--- a/library/Reporting/Cli/Command.php
+++ b/library/Reporting/Cli/Command.php
@@ -1,16 +1,14 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Cli;
use Icinga\Application\Icinga;
use Icinga\Application\Version;
-use Icinga\Module\Reporting\Database;
class Command extends \Icinga\Cli\Command
{
- use Database;
-
// Fix Web 2 issue where $configs is not properly initialized
protected $configs = [];
diff --git a/library/Reporting/Database.php b/library/Reporting/Database.php
index 3dabe17..7ea32a7 100644
--- a/library/Reporting/Database.php
+++ b/library/Reporting/Database.php
@@ -1,4 +1,5 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting;
@@ -6,53 +7,97 @@ namespace Icinga\Module\Reporting;
use Icinga\Application\Config;
use Icinga\Data\ResourceFactory;
use ipl\Sql;
+use PDO;
+use stdClass;
-trait Database
+final class Database
{
- protected function getDb($resource = null)
+ /** @var RetryConnection Database connection */
+ private static $instance;
+
+ private function __construct()
{
- $config = new Sql\Config(ResourceFactory::getResourceConfig(
- $resource ?: Config::module('reporting')->get('backend', 'resource', 'reporting')
- ));
+ }
- $config->options = [\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_OBJ];
- if ($config->db === 'mysql') {
- $config->options[\PDO::MYSQL_ATTR_INIT_COMMAND] = "SET SESSION SQL_MODE='STRICT_TRANS_TABLES"
- . ",NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'";
+ /**
+ * Get the database connection
+ *
+ * @return RetryConnection
+ */
+ public static function get(): RetryConnection
+ {
+ if (self::$instance === null) {
+ self::$instance = self::getDb();
}
- $conn = new RetryConnection($config);
-
- return $conn;
+ return self::$instance;
}
- protected function listTimeframes()
+ private static function getDb(): RetryConnection
{
- $select = (new Sql\Select())
- ->from('timeframe')
- ->columns(['id', 'name']);
-
- $timeframes = [];
+ $config = new Sql\Config(
+ ResourceFactory::getResourceConfig(
+ Config::module('reporting')->get('backend', 'resource', 'reporting')
+ )
+ );
- foreach ($this->getDb()->select($select) as $row) {
- $timeframes[$row->id] = $row->name;
+ $config->options = [PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ];
+ if ($config->db === 'mysql') {
+ $config->options[PDO::MYSQL_ATTR_INIT_COMMAND] = "SET SESSION SQL_MODE='STRICT_TRANS_TABLES"
+ . ",NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'";
}
- return $timeframes;
+ return new RetryConnection($config);
}
- protected function listTemplates()
+ /**
+ * List all reporting timeframes
+ *
+ * @return array<int, string>
+ */
+ public static function listTimeframes(): array
{
- $select = (new Sql\Select())
- ->from('template')
- ->columns(['id', 'name']);
+ return self::list(
+ (new Sql\Select())
+ ->from('timeframe')
+ ->columns(['id', 'name'])
+ );
+ }
- $templates = [];
+ /**
+ * List all reporting templates
+ *
+ * @return array<int, string>
+ */
+ public static function listTemplates(): array
+ {
+ return self::list(
+ (new Sql\Select())
+ ->from('template')
+ ->columns(['id', 'name'])
+ );
+ }
+
+ /**
+ * Helper method for list templates and timeframes
+ *
+ * @param Sql\Select $select
+ *
+ * @return array<int, string>
+ */
+ private static function list(Sql\Select $select): array
+ {
+ $result = [];
+ /** @var stdClass $row */
+ foreach (self::get()->select($select) as $row) {
+ /** @var int $id */
+ $id = $row->id;
+ /** @var string $name */
+ $name = $row->name;
- foreach ($this->getDb()->select($select) as $row) {
- $templates[$row->id] = $row->name;
+ $result[$id] = $name;
}
- return $templates;
+ return $result;
}
}
diff --git a/library/Reporting/Dimensions.php b/library/Reporting/Dimensions.php
index dfedbc8..09b23c9 100644
--- a/library/Reporting/Dimensions.php
+++ b/library/Reporting/Dimensions.php
@@ -1,4 +1,5 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting;
diff --git a/library/Reporting/Hook/ActionHook.php b/library/Reporting/Hook/ActionHook.php
index ef550ee..2dd20fb 100644
--- a/library/Reporting/Hook/ActionHook.php
+++ b/library/Reporting/Hook/ActionHook.php
@@ -1,4 +1,5 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Hook;
@@ -15,13 +16,14 @@ abstract class ActionHook
abstract public function getName();
/**
- * @param Report $report
- * @param array $config
+ * @param Report $report
+ * @param array $config
*/
abstract public function execute(Report $report, array $config);
/**
- * @param Form $form
+ * @param Form $form
+ * @param Report $report
*/
public function initConfigForm(Form $form, Report $report)
{
diff --git a/library/Reporting/Hook/ReportHook.php b/library/Reporting/Hook/ReportHook.php
index 13cc01e..b9467c0 100644
--- a/library/Reporting/Hook/ReportHook.php
+++ b/library/Reporting/Hook/ReportHook.php
@@ -1,4 +1,5 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Hook;
@@ -20,8 +21,8 @@ abstract class ReportHook
abstract public function getName();
/**
- * @param Timerange $timerange
- * @param array $config
+ * @param Timerange $timerange
+ * @param array|null $config
*
* @return ReportData|null
*/
@@ -33,8 +34,8 @@ abstract class ReportHook
/**
* Get the HTML of the report
*
- * @param Timerange $timerange
- * @param array $config
+ * @param Timerange $timerange
+ * @param array|null $config
*
* @return ValidHtml|null
*/
@@ -46,7 +47,7 @@ abstract class ReportHook
/**
* Initialize the report's configuration form
*
- * @param Form $form
+ * @param Form $form
*/
public function initConfigForm(Form $form)
{
@@ -55,7 +56,7 @@ abstract class ReportHook
/**
* Get the description of the report
*
- * @return string
+ * @return ?string
*/
public function getDescription()
{
diff --git a/library/Reporting/Mail.php b/library/Reporting/Mail.php
index 7581f45..810b166 100644
--- a/library/Reporting/Mail.php
+++ b/library/Reporting/Mail.php
@@ -1,4 +1,5 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting;
@@ -11,15 +12,15 @@ use Zend_Mime_Part;
class Mail
{
/** @var string */
- const DEFAULT_SUBJECT = 'Icinga Reporting';
+ public const DEFAULT_SUBJECT = 'Icinga Reporting';
- /** @var string */
+ /** @var ?string */
protected $from;
/** @var string */
protected $subject = self::DEFAULT_SUBJECT;
- /** @var Zend_Mail_Transport_Sendmail */
+ /** @var ?Zend_Mail_Transport_Sendmail */
protected $transport;
/** @var array */
@@ -43,7 +44,7 @@ class Mail
}
foreach (['HTTP_HOST', 'SERVER_NAME', 'HOSTNAME'] as $key) {
- if (isset($_SEVER[$key])) {
+ if (isset($_SERVER[$key])) {
$this->from = 'icinga-reporting@' . $_SERVER[$key];
return $this->from;
@@ -58,7 +59,7 @@ class Mail
/**
* Set the from part
*
- * @param string $from
+ * @param string $from
*
* @return $this
*/
@@ -82,7 +83,7 @@ class Mail
/**
* Set the subject
*
- * @param string $subject
+ * @param string $subject
*
* @return $this
*/
@@ -161,14 +162,14 @@ class Mail
{
$mail = new Zend_Mail('UTF-8');
- $mail->setFrom($this->getFrom());
+ $mail->setFrom($this->getFrom(), '');
$mail->addTo($recipient);
$mail->setSubject($this->getSubject());
- if (strlen($body) !== strlen(strip_tags($body))) {
+ if ($body && (strlen($body) !== strlen(strip_tags($body)))) {
$mail->setBodyHtml($body);
} else {
- $mail->setBodyText($body);
+ $mail->setBodyText($body ?? '');
}
foreach ($this->attachments as $attachment) {
diff --git a/library/Reporting/Model/Config.php b/library/Reporting/Model/Config.php
new file mode 100644
index 0000000..791a76d
--- /dev/null
+++ b/library/Reporting/Model/Config.php
@@ -0,0 +1,47 @@
+<?php
+
+/* Icinga Reporting | (c) 2023 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\Reporting\Model;
+
+use ipl\Orm\Behavior\MillisecondTimestamp;
+use ipl\Orm\Behaviors;
+use ipl\Orm\Model;
+use ipl\Orm\Relations;
+
+class Config extends Model
+{
+ public function getTableName()
+ {
+ return 'config';
+ }
+
+ public function getKeyName()
+ {
+ return 'id';
+ }
+
+ public function getColumns()
+ {
+ return [
+ 'reportlet_id',
+ 'name',
+ 'value',
+ 'ctime',
+ 'mtime'
+ ];
+ }
+
+ public function createBehaviors(Behaviors $behaviors)
+ {
+ $behaviors->add(new MillisecondTimestamp([
+ 'ctime',
+ 'mtime'
+ ]));
+ }
+
+ public function createRelations(Relations $relations)
+ {
+ $relations->belongsTo('reportlet', Reportlet::class);
+ }
+}
diff --git a/library/Reporting/Model/Report.php b/library/Reporting/Model/Report.php
new file mode 100644
index 0000000..b466a60
--- /dev/null
+++ b/library/Reporting/Model/Report.php
@@ -0,0 +1,71 @@
+<?php
+
+/* Icinga Reporting | (c) 2023 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\Reporting\Model;
+
+use DateTime;
+use ipl\Orm\Behavior\MillisecondTimestamp;
+use ipl\Orm\Behaviors;
+use ipl\Orm\Model;
+use ipl\Orm\Relations;
+
+/**
+ * A Report database model
+ *
+ * @property int $id Unique identifier of this model
+ * @property int $timeframe_id The timeframe id used by this report
+ * @property int $template_id The template id used by this report
+ * @property string $author The author of this report
+ * @property string $name The name of this report
+ * @property DateTime $ctime The creation time of this report
+ * @property DateTime $mtime Modify time of this report
+ */
+class Report extends Model
+{
+ public function getTableName()
+ {
+ return 'report';
+ }
+
+ public function getKeyName()
+ {
+ return 'id';
+ }
+
+ public function getColumns()
+ {
+ return [
+ 'timeframe_id',
+ 'template_id',
+ 'author',
+ 'name',
+ 'ctime',
+ 'mtime'
+ ];
+ }
+
+ public function getDefaultSort()
+ {
+ return ['name'];
+ }
+
+ public function createBehaviors(Behaviors $behaviors)
+ {
+ $behaviors->add(new MillisecondTimestamp([
+ 'ctime',
+ 'mtime'
+ ]));
+ }
+
+ public function createRelations(Relations $relations)
+ {
+ $relations->belongsTo('timeframe', Timeframe::class);
+ $relations->belongsTo('template', Template::class)
+ ->setJoinType('LEFT');
+
+ $relations->hasOne('schedule', Schedule::class)
+ ->setJoinType('LEFT');
+ $relations->hasMany('reportlets', Reportlet::class);
+ }
+}
diff --git a/library/Reporting/Model/Reportlet.php b/library/Reporting/Model/Reportlet.php
new file mode 100644
index 0000000..3552cf5
--- /dev/null
+++ b/library/Reporting/Model/Reportlet.php
@@ -0,0 +1,48 @@
+<?php
+
+/* Icinga Reporting | (c) 2023 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\Reporting\Model;
+
+use ipl\Orm\Behavior\MillisecondTimestamp;
+use ipl\Orm\Behaviors;
+use ipl\Orm\Model;
+use ipl\Orm\Relations;
+
+class Reportlet extends Model
+{
+ public function getTableName()
+ {
+ return 'reportlet';
+ }
+
+ public function getKeyName()
+ {
+ return 'id';
+ }
+
+ public function getColumns()
+ {
+ return [
+ 'report_id',
+ 'class',
+ 'ctime',
+ 'mtime'
+ ];
+ }
+
+ public function createBehaviors(Behaviors $behaviors)
+ {
+ $behaviors->add(new MillisecondTimestamp([
+ 'ctime',
+ 'mtime'
+ ]));
+ }
+
+ public function createRelations(Relations $relations)
+ {
+ $relations->belongsTo('report', Report::class);
+
+ $relations->hasMany('config', Config::class);
+ }
+}
diff --git a/library/Reporting/Model/Schedule.php b/library/Reporting/Model/Schedule.php
new file mode 100644
index 0000000..6100d2a
--- /dev/null
+++ b/library/Reporting/Model/Schedule.php
@@ -0,0 +1,48 @@
+<?php
+
+/* Icinga Reporting | (c) 2023 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\Reporting\Model;
+
+use ipl\Orm\Behavior\MillisecondTimestamp;
+use ipl\Orm\Behaviors;
+use ipl\Orm\Model;
+use ipl\Orm\Relations;
+
+class Schedule extends Model
+{
+ public function getTableName()
+ {
+ return 'schedule';
+ }
+
+ public function getKeyName()
+ {
+ return 'id';
+ }
+
+ public function getColumns()
+ {
+ return [
+ 'report_id',
+ 'author',
+ 'action',
+ 'config',
+ 'ctime',
+ 'mtime'
+ ];
+ }
+
+ public function createBehaviors(Behaviors $behaviors)
+ {
+ $behaviors->add(new MillisecondTimestamp([
+ 'ctime',
+ 'mtime'
+ ]));
+ }
+
+ public function createRelations(Relations $relations)
+ {
+ $relations->belongsTo('report', Report::class);
+ }
+}
diff --git a/library/Reporting/Model/Schema.php b/library/Reporting/Model/Schema.php
new file mode 100644
index 0000000..102a6eb
--- /dev/null
+++ b/library/Reporting/Model/Schema.php
@@ -0,0 +1,49 @@
+<?php
+
+/* Icinga Reporting | (c) 2023 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\Reporting\Model;
+
+use DateTime;
+use ipl\Orm\Behavior\BoolCast;
+use ipl\Orm\Behavior\MillisecondTimestamp;
+use ipl\Orm\Behaviors;
+use ipl\Orm\Model;
+
+/**
+ * A database model for reporting schema version table
+ *
+ * @property int $id Unique identifier of the database schema entries
+ * @property string $version The current schema version of Icinga Web
+ * @property DateTime $timestamp The insert/modify time of the schema entry
+ * @property bool $success Whether the database migration of the current version was successful
+ * @property ?string $reason The reason why the database migration has failed
+ */
+class Schema extends Model
+{
+ public function getTableName(): string
+ {
+ return 'reporting_schema';
+ }
+
+ public function getKeyName()
+ {
+ return 'id';
+ }
+
+ public function getColumns(): array
+ {
+ return [
+ 'version',
+ 'timestamp',
+ 'success',
+ 'reason'
+ ];
+ }
+
+ public function createBehaviors(Behaviors $behaviors): void
+ {
+ $behaviors->add(new BoolCast(['success']));
+ $behaviors->add(new MillisecondTimestamp(['timestamp']));
+ }
+}
diff --git a/library/Reporting/Model/Template.php b/library/Reporting/Model/Template.php
new file mode 100644
index 0000000..6695f37
--- /dev/null
+++ b/library/Reporting/Model/Template.php
@@ -0,0 +1,53 @@
+<?php
+
+/* Icinga Reporting | (c) 2023 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\Reporting\Model;
+
+use ipl\Orm\Behavior\MillisecondTimestamp;
+use ipl\Orm\Behaviors;
+use ipl\Orm\Model;
+use ipl\Orm\Relations;
+
+class Template extends Model
+{
+ public function getTableName()
+ {
+ return 'template';
+ }
+
+ public function getKeyName()
+ {
+ return 'id';
+ }
+
+ public function getColumns()
+ {
+ return [
+ 'author',
+ 'name',
+ 'settings',
+ 'ctime',
+ 'mtime'
+ ];
+ }
+
+ public function getDefaultSort()
+ {
+ return ['name'];
+ }
+
+ public function createBehaviors(Behaviors $behaviors)
+ {
+ $behaviors->add(new MillisecondTimestamp([
+ 'ctime',
+ 'mtime'
+ ]));
+ }
+
+ public function createRelations(Relations $relations)
+ {
+ $relations->hasMany('report', Report::class)
+ ->setJoinType('LEFT');
+ }
+}
diff --git a/library/Reporting/Model/Timeframe.php b/library/Reporting/Model/Timeframe.php
new file mode 100644
index 0000000..9936a58
--- /dev/null
+++ b/library/Reporting/Model/Timeframe.php
@@ -0,0 +1,53 @@
+<?php
+
+/* Icinga Reporting | (c) 2023 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\Reporting\Model;
+
+use ipl\Orm\Behavior\MillisecondTimestamp;
+use ipl\Orm\Behaviors;
+use ipl\Orm\Model;
+use ipl\Orm\Relations;
+
+class Timeframe extends Model
+{
+ public function getTableName()
+ {
+ return 'timeframe';
+ }
+
+ public function getKeyName()
+ {
+ return 'id';
+ }
+
+ public function getColumns()
+ {
+ return [
+ 'name',
+ 'title',
+ 'start',
+ 'end',
+ 'ctime',
+ 'mtime'
+ ];
+ }
+
+ public function getDefaultSort(): string
+ {
+ return 'name';
+ }
+
+ public function createBehaviors(Behaviors $behaviors)
+ {
+ $behaviors->add(new MillisecondTimestamp([
+ 'ctime',
+ 'mtime'
+ ]));
+ }
+
+ public function createRelations(Relations $relations)
+ {
+ $relations->hasMany('report', Report::class);
+ }
+}
diff --git a/library/Reporting/ProvidedActions.php b/library/Reporting/ProvidedActions.php
index 2590d1f..b3187c7 100644
--- a/library/Reporting/ProvidedActions.php
+++ b/library/Reporting/ProvidedActions.php
@@ -1,4 +1,5 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting;
diff --git a/library/Reporting/ProvidedHook/DbMigration.php b/library/Reporting/ProvidedHook/DbMigration.php
new file mode 100644
index 0000000..2fa5cda
--- /dev/null
+++ b/library/Reporting/ProvidedHook/DbMigration.php
@@ -0,0 +1,79 @@
+<?php
+
+/* Icinga Reporting | (c) 2023 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\Reporting\ProvidedHook;
+
+use Icinga\Application\Hook\DbMigrationHook;
+use Icinga\Module\Reporting\Database;
+use Icinga\Module\Reporting\Model\Schema;
+use ipl\Orm\Query;
+use ipl\Sql\Connection;
+
+class DbMigration extends DbMigrationHook
+{
+ public function getName(): string
+ {
+ return $this->translate('Icinga Reporting');
+ }
+
+ public function providedDescriptions(): array
+ {
+ return [
+ '0.9.1' => $this->translate(
+ 'Modifies all columns that uses current_timestamp to unix_timestamp and alters the database'
+ . ' engine of some tables.'
+ ),
+ '0.10.0' => $this->translate('Creates the template table and adjusts some column types'),
+ '1.0.0' => $this->translate('Migrates all your configured report schedules to the new config.')
+ ];
+ }
+
+ protected function getSchemaQuery(): Query
+ {
+ return Schema::on($this->getDb());
+ }
+
+ public function getDb(): Connection
+ {
+ return Database::get();
+ }
+
+ public function getVersion(): string
+ {
+ if ($this->version === null) {
+ $conn = $this->getDb();
+ $schema = $this->getSchemaQuery()
+ ->columns(['version', 'success'])
+ ->orderBy('id', SORT_DESC)
+ ->limit(2);
+
+ if (static::tableExists($conn, $schema->getModel()->getTableName())) {
+ /** @var Schema $version */
+ foreach ($schema as $version) {
+ if ($version->success) {
+ $this->version = $version->version;
+ }
+ }
+
+ if (! $this->version) {
+ // Schema version table exist, but the user has probably deleted the entry!
+ $this->version = '1.0.0';
+ }
+ } elseif (static::tableExists($conn, 'template')) {
+ // We have added Postgres support and the template table with 0.10.0.
+ // So, use this as the last (migrated) version.
+ $this->version = '0.10.0';
+ } elseif (static::getColumnType($conn, 'timeframe', 'name') === 'varchar(128)') {
+ // Upgrade script 0.9.1 alters the timeframe.name column from `varchar(255)` -> `varchar(128)`.
+ // Therefore, we can safely use this as the last migrated version.
+ $this->version = '0.9.1';
+ } else {
+ // Use the initial version as the last migrated schema version!
+ $this->version = '0.9.0';
+ }
+ }
+
+ return $this->version;
+ }
+}
diff --git a/library/Reporting/ProvidedReports.php b/library/Reporting/ProvidedReports.php
index edfc2ce..b672478 100644
--- a/library/Reporting/ProvidedReports.php
+++ b/library/Reporting/ProvidedReports.php
@@ -1,4 +1,5 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting;
diff --git a/library/Reporting/Report.php b/library/Reporting/Report.php
index 7f2eee3..ac5c9b3 100644
--- a/library/Reporting/Report.php
+++ b/library/Reporting/Report.php
@@ -1,19 +1,22 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting;
use DateTime;
use Exception;
+use Icinga\Module\Icingadb\ProvidedHook\Reporting\ServiceSlaReport;
+use Icinga\Module\Icingadb\ProvidedHook\Reporting\SlaReport;
use Icinga\Module\Pdfexport\PrintableHtmlDocument;
+use Icinga\Module\Reporting\Model;
use Icinga\Module\Reporting\Web\Widget\Template;
use ipl\Html\HtmlDocument;
-use ipl\Sql;
+
+use function ipl\I18n\t;
class Report
{
- use Database;
-
/** @var int */
protected $id;
@@ -36,88 +39,43 @@ class Report
protected $template;
/**
- * @param int $id
+ * Create report from the given model
*
- * @return static
+ * @param Model\Report $reportModel
*
- * @throws Exception
+ * @return static
+ * @throws Exception If no reportlets are configured
*/
- public static function fromDb($id)
+ public static function fromModel(Model\Report $reportModel): self
{
$report = new static();
- $db = $report->getDb();
+ $report->id = $reportModel->id;
+ $report->name = $reportModel->name;
+ $report->author = $reportModel->author;
+ $report->timeframe = Timeframe::fromModel($reportModel->timeframe);
- $select = (new Sql\Select())
- ->from('report')
- ->columns('*')
- ->where(['id = ?' => $id]);
-
- $row = $db->select($select)->fetch();
-
- if ($row === false) {
- throw new Exception('Report not found');
+ $template = $reportModel->template->first();
+ if ($template !== null) {
+ $report->template = Template::fromModel($template);
}
- $report
- ->setId($row->id)
- ->setName($row->name)
- ->setAuthor($row->author)
- ->setTimeframe(Timeframe::fromDb($row->timeframe_id))
- ->setTemplate(Template::fromDb($row->template_id));
-
- $select = (new Sql\Select())
- ->from('reportlet')
- ->columns('*')
- ->where(['report_id = ?' => $id]);
-
- $row = $db->select($select)->fetch();
-
- if ($row === false) {
- throw new Exception('No reportlets configured.');
+ $reportlets = [];
+ foreach ($reportModel->reportlets as $reportlet) {
+ $reportlet->report_name = $reportModel->name;
+ $reportlet->report_id = $reportModel->id;
+ $reportlets[] = Reportlet::fromModel($reportlet);
}
- $reportlet = new Reportlet();
-
- $reportlet
- ->setId($row->id)
- ->setClass($row->class);
-
- $select = (new Sql\Select())
- ->from('config')
- ->columns('*')
- ->where(['reportlet_id = ?' => $row->id]);
-
- $rows = $db->select($select)->fetchAll();
-
- $config = [];
-
- foreach ($rows as $row) {
- $config[$row->name] = $row->value;
+ if (empty($reportlets)) {
+ throw new Exception('No reportlets configured');
}
- $reportlet->setConfig($config);
-
- $report->setReportlets([$reportlet]);
-
- $select = (new Sql\Select())
- ->from('schedule')
- ->columns('*')
- ->where(['report_id = ?' => $id]);
-
- $row = $db->select($select)->fetch();
+ $report->reportlets = $reportlets;
- if ($row !== false) {
- $schedule = new Schedule();
-
- $schedule
- ->setId($row->id)
- ->setStart((new \DateTime())->setTimestamp((int) $row->start / 1000))
- ->setFrequency($row->frequency)
- ->setAction($row->action)
- ->setConfig(json_decode($row->config, true));
-
- $report->setSchedule($schedule);
+ $schedule = $reportModel->schedule->first();
+ if ($schedule !== null) {
+ $report->schedule = Schedule::fromModel($schedule, $report);
}
return $report;
@@ -132,18 +90,6 @@ class Report
}
/**
- * @param int $id
- *
- * @return $this
- */
- public function setId($id)
- {
- $this->id = $id;
-
- return $this;
- }
-
- /**
* @return string
*/
public function getName()
@@ -152,18 +98,6 @@ class Report
}
/**
- * @param string $name
- *
- * @return $this
- */
- public function setName($name)
- {
- $this->name = $name;
-
- return $this;
- }
-
- /**
* @return string
*/
public function getAuthor()
@@ -172,18 +106,6 @@ class Report
}
/**
- * @param string $author
- *
- * @return $this
- */
- public function setAuthor($author)
- {
- $this->author = $author;
-
- return $this;
- }
-
- /**
* @return Timeframe
*/
public function getTimeframe()
@@ -192,18 +114,6 @@ class Report
}
/**
- * @param Timeframe $timeframe
- *
- * @return $this
- */
- public function setTimeframe(Timeframe $timeframe)
- {
- $this->timeframe = $timeframe;
-
- return $this;
- }
-
- /**
* @return Reportlet[]
*/
public function getReportlets()
@@ -212,18 +122,6 @@ class Report
}
/**
- * @param Reportlet[] $reportlets
- *
- * @return $this
- */
- public function setReportlets(array $reportlets)
- {
- $this->reportlets = $reportlets;
-
- return $this;
- }
-
- /**
* @return Schedule
*/
public function getSchedule()
@@ -232,18 +130,6 @@ class Report
}
/**
- * @param Schedule $schedule
- *
- * @return $this
- */
- public function setSchedule(Schedule $schedule)
- {
- $this->schedule = $schedule;
-
- return $this;
- }
-
- /**
* @return Template
*/
public function getTemplate()
@@ -251,18 +137,6 @@ class Report
return $this->template;
}
- /**
- * @param Template $template
- *
- * @return $this
- */
- public function setTemplate($template)
- {
- $this->template = $template;
-
- return $this;
- }
-
public function providesData()
{
foreach ($this->getReportlets() as $reportlet) {
@@ -300,6 +174,7 @@ class Report
public function toCsv()
{
$timerange = $this->getTimeframe()->getTimerange();
+ $convertFloats = version_compare(PHP_VERSION, '8.0.0', '<');
$csv = [];
@@ -309,8 +184,41 @@ class Report
if ($implementation->providesData()) {
$data = $implementation->getData($timerange, $reportlet->getConfig());
$csv[] = array_merge($data->getDimensions(), $data->getValues());
+
+ $hosts = [];
+ $isServiceExport = false;
+ $config = $reportlet->getConfig();
+ $exportTotalEnabled = isset($config['export_total']) && $config['export_total'];
+ if ($exportTotalEnabled) {
+ $isServiceExport = $reportlet->getClass() === ServiceSlaReport::class;
+ }
+
foreach ($data->getRows() as $row) {
- $csv[] = array_merge($row->getDimensions(), $row->getValues());
+ $values = $row->getValues();
+ if ($convertFloats) {
+ foreach ($values as &$value) {
+ if (is_float($value)) {
+ $value = sprintf('%.4F', $value);
+ }
+ }
+ }
+
+ if ($isServiceExport) {
+ $hosts[$row->getDimensions()[0]] = true;
+ }
+
+ $csv[] = array_merge($row->getDimensions(), $values);
+ }
+
+ if ($exportTotalEnabled) {
+ $precision = $config['sla_precision'] ?? SlaReport::DEFAULT_REPORT_PRECISION;
+ $total = [$isServiceExport ? count($hosts) : $data->count()];
+ if ($isServiceExport) {
+ $total[] = $data->count();
+ }
+ $total[] = round($data->getAverages()[0], $precision);
+
+ $csv[] = $total;
}
break;
@@ -336,9 +244,34 @@ class Report
$data = $implementation->getData($timerange, $reportlet->getConfig());
$dimensions = $data->getDimensions();
$values = $data->getValues();
+
+ $hosts = [];
+ $isServiceExport = false;
+ $config = $reportlet->getConfig();
+ $exportTotalEnabled = isset($config['export_total']) && $config['export_total'];
+ if ($exportTotalEnabled) {
+ $isServiceExport = $reportlet->getClass() === ServiceSlaReport::class;
+ }
+
foreach ($data->getRows() as $row) {
- $json[] = \array_combine($dimensions, $row->getDimensions())
- + \array_combine($values, $row->getValues());
+ $json[] = array_combine($dimensions, $row->getDimensions())
+ + array_combine($values, $row->getValues());
+
+ if ($isServiceExport) {
+ $hosts[$row->getDimensions()[0]] = true;
+ }
+ }
+
+ if ($exportTotalEnabled) {
+ $total = [t('Total Hosts') => $isServiceExport ? count($hosts) : $data->count()];
+ if ($isServiceExport) {
+ $total[t('Total Services')] = $data->count();
+ }
+
+ $precision = $config['sla_precision'] ?? SlaReport::DEFAULT_REPORT_PRECISION;
+ $total[t('Total SLA Averages')] = round($data->getAverages()[0], $precision);
+
+ $json[] = $total;
}
break;
diff --git a/library/Reporting/ReportData.php b/library/Reporting/ReportData.php
index 787f4db..addd6db 100644
--- a/library/Reporting/ReportData.php
+++ b/library/Reporting/ReportData.php
@@ -1,4 +1,5 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting;
diff --git a/library/Reporting/ReportRow.php b/library/Reporting/ReportRow.php
index 1536488..e7cb53d 100644
--- a/library/Reporting/ReportRow.php
+++ b/library/Reporting/ReportRow.php
@@ -1,4 +1,5 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting;
diff --git a/library/Reporting/Reportlet.php b/library/Reporting/Reportlet.php
index 2876a00..1c74f38 100644
--- a/library/Reporting/Reportlet.php
+++ b/library/Reporting/Reportlet.php
@@ -1,13 +1,11 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting;
class Reportlet
{
- /** @var int */
- protected $id;
-
/** @var string */
protected $class;
@@ -15,23 +13,29 @@ class Reportlet
protected $config;
/**
- * @return int
- */
- public function getId()
- {
- return $this->id;
- }
-
- /**
- * @param int $id
+ * Create reportlet from the given model
*
- * @return $this
+ * @param Model\Reportlet $reportletModel
+ *
+ * @return static
*/
- public function setId($id)
+ public static function fromModel(Model\Reportlet $reportletModel): self
{
- $this->id = $id;
+ $reportlet = new static();
+ $reportlet->class = $reportletModel->class;
+
+ $reportletConfig = [
+ 'name' => $reportletModel->report_name,
+ 'id' => $reportletModel->report_id
+ ];
+
+ foreach ($reportletModel->config as $config) {
+ $reportletConfig[$config->name] = $config->value;
+ }
- return $this;
+ $reportlet->config = $reportletConfig;
+
+ return $reportlet;
}
/**
@@ -43,18 +47,6 @@ class Reportlet
}
/**
- * @param string $class
- *
- * @return $this
- */
- public function setClass($class)
- {
- $this->class = $class;
-
- return $this;
- }
-
- /**
* @return array
*/
public function getConfig()
@@ -63,24 +55,12 @@ class Reportlet
}
/**
- * @param array $config
- *
- * @return $this
- */
- public function setConfig($config)
- {
- $this->config = $config;
-
- return $this;
- }
-
- /**
* @return \Icinga\Module\Reporting\Hook\ReportHook
*/
public function getImplementation()
{
$class = $this->getClass();
- return new $class;
+ return new $class();
}
}
diff --git a/library/Reporting/Reports/SystemReport.php b/library/Reporting/Reports/SystemReport.php
index 8a3d8dd..5c9a544 100644
--- a/library/Reporting/Reports/SystemReport.php
+++ b/library/Reporting/Reports/SystemReport.php
@@ -1,8 +1,10 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Reports;
+use Icinga\Application\Icinga;
use Icinga\Module\Reporting\Hook\ReportHook;
use Icinga\Module\Reporting\Timerange;
use ipl\Html\HtmlString;
@@ -18,22 +20,29 @@ class SystemReport extends ReportHook
{
ob_start();
phpinfo();
+ /** @var string $html */
$html = ob_get_clean();
- $doc = new \DOMDocument();
- @$doc->loadHTML($html);
+ if (! Icinga::app()->isCli()) {
+ $doc = new \DOMDocument();
+ @$doc->loadHTML($html);
+
+ $style = $doc->getElementsByTagName('style')->item(0);
+ $style->parentNode->removeChild($style);
- $style = $doc->getElementsByTagName('style')->item(0);
- $style->parentNode->removeChild($style);
+ $title = $doc->getElementsByTagName('title')->item(0);
+ $title->parentNode->removeChild($title);
- $title = $doc->getElementsByTagName('title')->item(0);
- $title->parentNode->removeChild($title);
+ $meta = $doc->getElementsByTagName('meta')->item(0);
+ $meta->parentNode->removeChild($meta);
- $meta = $doc->getElementsByTagName('meta')->item(0);
- $meta->parentNode->removeChild($meta);
+ $doc->getElementsByTagName('div')->item(0)->setAttribute('class', 'system-report');
- $doc->getElementsByTagName('div')->item(0)->setAttribute('class', 'system-report');
+ $html = $doc->saveHTML();
+ } else {
+ $html = nl2br($html);
+ }
- return new HtmlString($doc->saveHTML());
+ return new HtmlString($html);
}
}
diff --git a/library/Reporting/RetryConnection.php b/library/Reporting/RetryConnection.php
index ebadfd2..5f7e125 100644
--- a/library/Reporting/RetryConnection.php
+++ b/library/Reporting/RetryConnection.php
@@ -1,4 +1,5 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting;
diff --git a/library/Reporting/Schedule.php b/library/Reporting/Schedule.php
index e0ffa9f..ddd8bd3 100644
--- a/library/Reporting/Schedule.php
+++ b/library/Reporting/Schedule.php
@@ -1,160 +1,155 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting;
-class Schedule
-{
- /** @var int */
- protected $id;
+use Exception;
+use Icinga\Module\Reporting\Hook\ActionHook;
+use Icinga\Util\Json;
+use ipl\Scheduler\Common\TaskProperties;
+use ipl\Scheduler\Contract\Task;
+use Ramsey\Uuid\Uuid;
+use React\EventLoop\Loop;
+use React\Promise;
+use React\Promise\ExtendedPromiseInterface;
- /** @var int */
- protected $reportId;
+use function md5;
- /** @var \DateTime */
- protected $start;
+class Schedule implements Task
+{
+ use TaskProperties;
- /** @var string */
- protected $frequency;
+ /** @var int */
+ protected $id;
/** @var string */
protected $action;
/** @var array */
- protected $config;
+ protected $config = [];
- /**
- * @return int
- */
- public function getId()
+ /** @var Report */
+ protected $report;
+
+ public function __construct(string $name, string $action, array $config, Report $report)
{
- return $this->id;
+ $this->action = $action;
+ $this->config = $config;
+ ksort($this->config);
+
+ $this
+ ->setName($name)
+ ->setReport($report)
+ ->setUuid(Uuid::fromBytes($this->getChecksum()));
}
/**
- * @param int $id
+ * Create schedule from the given model
+ *
+ * @param Model\Schedule $scheduleModel
*
- * @return $this
+ * @return static
*/
- public function setId($id)
+
+ public static function fromModel(Model\Schedule $scheduleModel, Report $report): self
{
- $this->id = $id;
+ $config = Json::decode($scheduleModel->config ?? [], true);
+ $schedule = new static("Schedule{$scheduleModel->id}", $scheduleModel->action, $config, $report);
+ $schedule->id = $scheduleModel->id;
- return $this;
+ return $schedule;
}
/**
+ * Get the id of this schedule
+ *
* @return int
*/
- public function getReportId()
+ public function getId(): int
{
- return $this->reportId;
+ return $this->id;
}
/**
- * @param int $id
+ * Get the action hook class of this schedule
*
- * @return $this
- */
- public function setReportId($id)
- {
- $this->reportId = $id;
-
- return $this;
- }
-
- /**
- * @return \DateTime
+ * @return string
*/
- public function getStart()
+ public function getAction(): string
{
- return $this->start;
+ return $this->action;
}
/**
- * @param \DateTime $start
+ * Get the config of this schedule
*
- * @return $this
+ * @return array
*/
- public function setStart(\DateTime $start)
+ public function getConfig(): array
{
- $this->start = $start;
-
- return $this;
+ return $this->config;
}
/**
- * @return string
+ * Get the report this schedule belongs to
+ *
+ * @return Report
*/
- public function getFrequency()
+ public function getReport(): Report
{
- return $this->frequency;
+ return $this->report;
}
/**
- * @param string $frequency
+ * Set the report this schedule belongs to
*
- * @return $this
+ * @param Report $report
+ *
+ * @return $this
*/
- public function setFrequency($frequency)
+ public function setReport(Report $report): self
{
- $this->frequency = $frequency;
+ $this->report = $report;
return $this;
}
/**
+ * Get the checksum of this schedule
+ *
* @return string
*/
- public function getAction()
+ public function getChecksum(): string
{
- return $this->action;
+ return md5(
+ $this->getName() . $this->getReport()->getName() . $this->getAction() . Json::encode($this->getConfig()),
+ true
+ );
}
- /**
- * @param string $action
- *
- * @return $this
- */
- public function setAction($action)
+ public function run(): ExtendedPromiseInterface
{
- $this->action = $action;
+ $deferred = new Promise\Deferred();
+ Loop::futureTick(function () use ($deferred) {
+ $action = $this->getAction();
+ /** @var ActionHook $actionHook */
+ $actionHook = new $action();
- return $this;
- }
+ try {
+ $actionHook->execute($this->getReport(), $this->getConfig());
+ } catch (Exception $err) {
+ $deferred->reject($err);
- /**
- * @return array
- */
- public function getConfig()
- {
- return $this->config;
- }
+ return;
+ }
- /**
- * @param array $config
- *
- * @return $this
- */
- public function setConfig(array $config)
- {
- $this->config = $config;
+ $deferred->resolve();
+ });
- return $this;
- }
+ /** @var ExtendedPromiseInterface $promise */
+ $promise = $deferred->promise();
- /**
- * @return string
- */
- public function getChecksum()
- {
- return \md5(
- $this->getId()
- . $this->getReportId()
- . $this->getStart()->format('Y-m-d H:i:s')
- . $this->getAction()
- . $this->getFrequency()
- . \json_encode($this->getConfig())
- );
+ return $promise;
}
}
diff --git a/library/Reporting/Scheduler.php b/library/Reporting/Scheduler.php
deleted file mode 100644
index 1b8d9f6..0000000
--- a/library/Reporting/Scheduler.php
+++ /dev/null
@@ -1,176 +0,0 @@
-<?php
-// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
-
-namespace Icinga\Module\Reporting;
-
-use Cron\CronExpression;
-use ipl\Sql\Connection;
-use ipl\Sql\Select;
-use React\EventLoop\Factory as Loop;
-
-function datetime_get_time_of_day(\DateTime $dateTime)
-{
- $midnight = clone $dateTime;
- $midnight->modify('midnight');
-
- $diff = $midnight->diff($dateTime);
-
- return $diff->h * 60 * 60 + $diff->i * 60 + $diff->s;
-}
-
-class Scheduler
-{
- protected $db;
-
- protected $loop;
-
- /** @var array */
- protected $schedules = [];
-
- /** @var array */
- protected $timers = [];
-
- public function __construct(Connection $db)
- {
- $this->db = $db;
- $this->loop = Loop::create();
- }
-
- public function run()
- {
- $updateTimers = function () use (&$updateTimers) {
- $this->updateTimers();
-
- $this->loop->addTimer(60, $updateTimers);
- };
-
- $this->loop->futureTick($updateTimers);
-
- $this->loop->run();
- }
-
- protected function fetchSchedules()
- {
- $schedules = [];
-
- $select = (new Select())
- ->from('schedule')
- ->columns('*');
-
- foreach ($this->db->select($select) as $row) {
- $schedule = (new Schedule())
- ->setId((int) $row->id)
- ->setReportId((int) $row->report_id)
- ->setAction($row->action)
- ->setConfig(\json_decode($row->config, true))
- ->setStart((new \DateTime())->setTimestamp((int) $row->start / 1000))
- ->setFrequency($row->frequency);
-
- $schedules[$schedule->getChecksum()] = $schedule;
- }
-
- return $schedules;
- }
-
- protected function updateTimers()
- {
- $schedules = $this->fetchSchedules();
-
- $remove = \array_diff_key($this->schedules, $schedules);
-
- foreach ($remove as $schedule) {
- printf("Removing job %s.\n", "Schedule {$schedule->getId()}");
-
- $checksum = $schedule->getChecksum();
-
- if (isset($this->timers[$checksum])) {
- $this->loop->cancelTimer($this->timers[$checksum]);
- unset($this->timers[$checksum]);
- } else {
- printf("Can't find timer for job %s.\n", $checksum);
- }
- }
-
- $add = \array_diff_key($schedules, $this->schedules);
-
- foreach ($add as $schedule) {
- $this->add($schedule);
- }
-
- $this->schedules = $schedules;
- }
-
-
- protected function add(Schedule $schedule)
- {
- $name = "Schedule {$schedule->getId()}";
- $frequency = $schedule->getFrequency();
- $start = clone $schedule->getStart();
- $callback = function () use ($schedule) {
- $actionClass = $schedule->getAction();
- /** @var ActionHook $action */
- $action = new $actionClass;
-
- $action->execute(
- Report::fromDb($schedule->getReportId()),
- $schedule->getConfig()
- );
- };
-
- switch ($frequency) {
- case 'minutely':
- $modify = '+1 minute';
- break;
- case 'hourly':
- $modify = '+1 hour';
- break;
- case 'daily':
- $modify = '+1 day';
- break;
- case 'weekly':
- $modify = '+1 week';
- break;
- case 'monthly':
- $modify = '+1 month';
- break;
- default:
- throw new \InvalidArgumentException('Invalid frequency.');
- }
-
- $now = new \DateTime();
-
- if ($start < $now) {
-// printf("Scheduling job %s to run immediately.\n", $name);
-// $this->loop->futureTick($callback);
-
- while ($start < $now) {
- $start->modify($modify);
- }
- }
-
- $next = clone $start;
- $next->modify($modify);
- $interval = $next->getTimestamp() - $start->getTimestamp();
-
- $current = $start->getTimestamp() - $now->getTimestamp();
-
- printf("Scheduling job %s to run at %s.\n", $name, $start->format('Y-m-d H:i:s'));
-
- $loop = function () use (&$loop, $name, $callback, $interval, $schedule) {
- $callback();
-
- $nextRun = (new \DateTime())
- ->add(new \DateInterval("PT{$interval}S"));
-
- printf("Scheduling job %s to run at %s.\n", $name, $nextRun->format('Y-m-d H:i:s'));
-
- $timer = $this->loop->addTimer($interval, $loop);
-
- $this->timers[$schedule->getChecksum()] = $timer;
- };
-
- $timer = $this->loop->addTimer($current, $loop);
-
- $this->timers[$schedule->getChecksum()] = $timer;
- }
-}
diff --git a/library/Reporting/Str.php b/library/Reporting/Str.php
index d4c7355..77eb65e 100644
--- a/library/Reporting/Str.php
+++ b/library/Reporting/Str.php
@@ -1,4 +1,5 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting;
@@ -7,6 +8,7 @@ class Str
{
public static function putcsv(array $data, $delimiter = ',', $enclosure = '"', $escape = '\\')
{
+ /** @var resource $fp */
$fp = fopen('php://temp', 'r+b');
foreach ($data as $row) {
@@ -15,6 +17,7 @@ class Str
rewind($fp);
+ /** @var string $csv */
$csv = stream_get_contents($fp);
fclose($fp);
diff --git a/library/Reporting/Timeframe.php b/library/Reporting/Timeframe.php
index f295779..4e8cb9e 100644
--- a/library/Reporting/Timeframe.php
+++ b/library/Reporting/Timeframe.php
@@ -1,14 +1,13 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting;
-use ipl\Sql\Select;
+use Icinga\Module\Reporting\Model;
class Timeframe
{
- use Database;
-
/** @var int */
protected $id;
@@ -25,35 +24,21 @@ class Timeframe
protected $end;
/**
- * @param int $id
+ * Create timeframe from the given model
*
- * @return static
+ * @param Model\Timeframe $timeframeModel
*
- * @throws \Exception
+ * @return static
*/
- public static function fromDb($id)
+ public static function fromModel(Model\Timeframe $timeframeModel): self
{
$timeframe = new static();
- $db = $timeframe->getDb();
-
- $select = (new Select())
- ->from('timeframe')
- ->columns('*')
- ->where(['id = ?' => $id]);
-
- $row = $db->select($select)->fetch();
-
- if ($row === false) {
- throw new \Exception('Timeframe not found');
- }
-
- $timeframe
- ->setId($row->id)
- ->setName($row->name)
- ->setTitle($row->title)
- ->setStart($row->start)
- ->setEnd($row->end);
+ $timeframe->id = $timeframeModel->id;
+ $timeframe->name = $timeframeModel->name;
+ $timeframe->title = $timeframeModel->title;
+ $timeframe->start = $timeframeModel->start;
+ $timeframe->end = $timeframeModel->end;
return $timeframe;
}
@@ -67,18 +52,6 @@ class Timeframe
}
/**
- * @param int $id
- *
- * @return $this
- */
- public function setId($id)
- {
- $this->id = $id;
-
- return $this;
- }
-
- /**
* @return string
*/
public function getName()
@@ -87,18 +60,6 @@ class Timeframe
}
/**
- * @param string $name
- *
- * @return $this
- */
- public function setName($name)
- {
- $this->name = $name;
-
- return $this;
- }
-
- /**
* @return string
*/
public function getTitle()
@@ -107,18 +68,6 @@ class Timeframe
}
/**
- * @param string $title
- *
- * @return $this
- */
- public function setTitle($title)
- {
- $this->title = $title;
-
- return $this;
- }
-
- /**
* @return string
*/
public function getStart()
@@ -127,18 +76,6 @@ class Timeframe
}
/**
- * @param string $start
- *
- * @return $this
- */
- public function setStart($start)
- {
- $this->start = $start;
-
- return $this;
- }
-
- /**
* @return string
*/
public function getEnd()
@@ -146,18 +83,6 @@ class Timeframe
return $this->end;
}
- /**
- * @param string $end
- *
- * @return $this
- */
- public function setEnd($end)
- {
- $this->end = $end;
-
- return $this;
- }
-
public function getTimerange()
{
$start = new \DateTime($this->getStart());
diff --git a/library/Reporting/Timerange.php b/library/Reporting/Timerange.php
index 086bfb8..0ca0a2d 100644
--- a/library/Reporting/Timerange.php
+++ b/library/Reporting/Timerange.php
@@ -1,4 +1,5 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting;
diff --git a/library/Reporting/Values.php b/library/Reporting/Values.php
index 3aa9b24..e7a3cb1 100644
--- a/library/Reporting/Values.php
+++ b/library/Reporting/Values.php
@@ -1,4 +1,5 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting;
diff --git a/library/Reporting/Web/Controller.php b/library/Reporting/Web/Controller.php
index 5040183..1123332 100644
--- a/library/Reporting/Web/Controller.php
+++ b/library/Reporting/Web/Controller.php
@@ -1,20 +1,11 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Web;
-use ipl\Html\Form;
use ipl\Web\Compat\CompatController;
class Controller extends CompatController
{
- protected function redirectForm(Form $form, $url)
- {
- if ($form->hasBeenSubmitted()
- && ((isset($form->valid) && $form->valid === true)
- || $form->isValid())
- ) {
- $this->redirectNow($url);
- }
- }
}
diff --git a/library/Reporting/Web/Flatpickr.php b/library/Reporting/Web/Flatpickr.php
deleted file mode 100644
index 5f6605d..0000000
--- a/library/Reporting/Web/Flatpickr.php
+++ /dev/null
@@ -1,77 +0,0 @@
-<?php
-// Icinga Reporting | (c) 2019 Icinga GmbH | GPLv2
-
-namespace Icinga\Module\Reporting\Web;
-
-use Icinga\Application\Version;
-use ipl\Html\Html;
-use ipl\Web\Compat\CompatDecorator;
-
-class Flatpickr extends CompatDecorator
-{
- protected $allowInput = true;
-
- /**
- * Set whether to allow manual input
- *
- * @param bool $state
- *
- * @return $this
- */
- public function setAllowInput(bool $state): self
- {
- $this->allowInput = $state;
-
- return $this;
- }
-
- protected function assembleElement()
- {
- if (version_compare(Version::VERSION, '2.9.0', '>=')) {
- $element = parent::assembleElement();
- } else {
- $element = $this->formElement;
- }
-
- if (version_compare(Version::VERSION, '2.10.0', '<')) {
- $element->getAttributes()->set('data-use-flatpickr-fallback', true);
- } else {
- $element->getAttributes()->set('data-use-datetime-picker', true);
- }
-
- if (! $this->allowInput) {
- return $element;
- }
-
- $element->getAttributes()
- ->set('data-input', true)
- ->set('data-flatpickr-wrap', true)
- ->set('data-flatpickr-allow-input', true)
- ->set('data-flatpickr-click-opens', 'false');
-
- return [
- $element,
- Html::tag('button', ['type' => 'button', 'class' => 'icon-calendar', 'data-toggle' => true]),
- Html::tag('button', ['type' => 'button', 'class' => 'icon-cancel', 'data-clear' => true])
- ];
- }
-
- protected function assemble()
- {
- if (version_compare(Version::VERSION, '2.9.0', '>=')) {
- parent::assemble();
- return;
- }
-
- if ($this->formElement->hasBeenValidated() && ! $this->formElement->isValid()) {
- $this->getAttributes()->add('class', 'has-error');
- }
-
- $this->add(array_filter([
- $this->assembleLabel(),
- $this->assembleElement(),
- $this->assembleDescription(),
- $this->assembleErrors()
- ]));
- }
-}
diff --git a/library/Reporting/Web/Forms/DecoratedElement.php b/library/Reporting/Web/Forms/DecoratedElement.php
deleted file mode 100644
index 2578681..0000000
--- a/library/Reporting/Web/Forms/DecoratedElement.php
+++ /dev/null
@@ -1,17 +0,0 @@
-<?php
-// Icinga Reporting | (c) 2019 Icinga GmbH | GPLv2
-
-namespace Icinga\Module\Reporting\Web\Forms;
-
-use ipl\Html\Contract\FormElementDecorator;
-
-trait DecoratedElement
-{
- protected function addDecoratedElement(FormElementDecorator $decorator, $type, $name, array $attributes)
- {
- $element = $this->createElement($type, $name, $attributes);
- $decorator->decorate($element);
- $this->registerElement($element);
- $this->add($element);
- }
-}
diff --git a/library/Reporting/Web/Forms/Decorator/CompatDecorator.php b/library/Reporting/Web/Forms/Decorator/CompatDecorator.php
deleted file mode 100644
index b2eb536..0000000
--- a/library/Reporting/Web/Forms/Decorator/CompatDecorator.php
+++ /dev/null
@@ -1,63 +0,0 @@
-<?php
-// Icinga Reporting | (c) 2021 Icinga GmbH | GPLv2
-
-namespace Icinga\Module\Reporting\Web\Forms\Decorator;
-
-use Icinga\Application\Version;
-use ipl\Html\Attributes;
-use ipl\Html\FormElement\CheckboxElement;
-use ipl\Html\HtmlElement;
-
-class CompatDecorator extends \ipl\Web\Compat\CompatDecorator
-{
- protected function createCheckboxCompat(CheckboxElement $checkbox)
- {
- if (! $checkbox->getAttributes()->has('id')) {
- $checkbox->setAttribute('id', base64_encode(random_bytes(8)));
- }
-
- $checkbox->getAttributes()->add('class', 'sr-only');
-
- $classes = ['toggle-switch'];
- if ($checkbox->getAttributes()->get('disabled')->getValue()) {
- $classes[] = 'disabled';
- }
-
- return [
- $checkbox,
- new HtmlElement('label', Attributes::create([
- 'class' => $classes,
- 'aria-hidden' => 'true',
- 'for' => $checkbox->getAttributes()->get('id')->getValue()
- ]), new HtmlElement('span', Attributes::create(['class' => 'toggle-slider'])))
- ];
- }
-
- protected function assembleElementCompat()
- {
- if ($this->formElement instanceof CheckboxElement) {
- return $this->createCheckboxCompat($this->formElement);
- }
-
- return $this->formElement;
- }
-
- protected function assemble()
- {
- if (version_compare(Version::VERSION, '2.9.0', '>=')) {
- parent::assemble();
- return;
- }
-
- if ($this->formElement->hasBeenValidated() && ! $this->formElement->isValid()) {
- $this->getAttributes()->add('class', 'has-error');
- }
-
- $this->add(array_filter([
- $this->assembleLabel(),
- $this->assembleElementCompat(),
- $this->assembleDescription(),
- $this->assembleErrors()
- ]));
- }
-}
diff --git a/library/Reporting/Web/Forms/ReportForm.php b/library/Reporting/Web/Forms/ReportForm.php
index 6b1e692..40e376e 100644
--- a/library/Reporting/Web/Forms/ReportForm.php
+++ b/library/Reporting/Web/Forms/ReportForm.php
@@ -1,4 +1,5 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Web\Forms;
@@ -6,54 +7,143 @@ namespace Icinga\Module\Reporting\Web\Forms;
use Icinga\Authentication\Auth;
use Icinga\Module\Reporting\Database;
use Icinga\Module\Reporting\ProvidedReports;
-use Icinga\Module\Reporting\Web\Forms\Decorator\CompatDecorator;
use ipl\Html\Contract\FormSubmitElement;
use ipl\Html\Form;
+use ipl\Html\HtmlDocument;
+use ipl\Validator\CallbackValidator;
use ipl\Web\Compat\CompatForm;
class ReportForm extends CompatForm
{
- use Database;
use ProvidedReports;
- /** @var bool Hack to disable the {@link onSuccess()} code upon deletion of the report */
- protected $callOnSuccess;
-
protected $id;
- public function setId($id)
+ /** @var string Label to use for the submit button */
+ protected $submitButtonLabel;
+
+ /** @var bool Whether to render the create and show submit button (is only used from DB Web's object detail) */
+ protected $renderCreateAndShowButton = false;
+
+ /**
+ * Create a new form instance with the given report id
+ *
+ * @param $id
+ *
+ * @return static
+ */
+ public static function fromId($id): self
{
- $this->id = $id;
+ $form = new static();
+ $form->id = $id;
+
+ return $form;
+ }
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ /**
+ * Set the label of the submit button
+ *
+ * @param string $label
+ *
+ * @return $this
+ */
+ public function setSubmitButtonLabel(string $label): self
+ {
+ $this->submitButtonLabel = $label;
return $this;
}
- protected function assemble()
+ /**
+ * Get the label of the submit button
+ *
+ * @return string
+ */
+ public function getSubmitButtonLabel(): string
+ {
+ if ($this->submitButtonLabel !== null) {
+ return $this->submitButtonLabel;
+ }
+
+ return $this->id === null ? $this->translate('Create Report') : $this->translate('Update Report');
+ }
+
+ /**
+ * Set whether the create and show submit button should be rendered
+ *
+ * @param bool $renderCreateAndShowButton
+ *
+ * @return $this
+ */
+ public function setRenderCreateAndShowButton(bool $renderCreateAndShowButton): self
{
- $this->setDefaultElementDecorator(new CompatDecorator());
+ $this->renderCreateAndShowButton = $renderCreateAndShowButton;
+ return $this;
+ }
+
+ public function hasBeenSubmitted(): bool
+ {
+ return $this->hasBeenSent() && (
+ $this->getPopulatedValue('submit')
+ || $this->getPopulatedValue('create_show')
+ || $this->getPopulatedValue('remove')
+ );
+ }
+
+ protected function assemble()
+ {
$this->addElement('text', 'name', [
- 'required' => true,
- 'label' => 'Name'
+ 'required' => true,
+ 'label' => $this->translate('Name'),
+ 'description' => $this->translate(
+ 'A unique name of this report. It is used when exporting to pdf, json or csv format'
+ . ' and also when listing the reports in the cli'
+ ),
+ 'validators' => [
+ 'Callback' => function ($value, CallbackValidator $validator) {
+ if ($value !== null && strpos($value, '..') !== false) {
+ $validator->addMessage(
+ $this->translate('Double dots are not allowed in the report name')
+ );
+
+ return false;
+ }
+
+ return true;
+ }
+ ]
]);
$this->addElement('select', 'timeframe', [
- 'required' => true,
- 'label' => 'Timeframe',
- 'options' => [null => 'Please choose'] + $this->listTimeframes(),
- 'class' => 'autosubmit'
+ 'required' => true,
+ 'class' => 'autosubmit',
+ 'label' => $this->translate('Timeframe'),
+ 'options' => [null => $this->translate('Please choose')] + Database::listTimeframes(),
+ 'description' => $this->translate(
+ 'Specifies the time frame in which this report is to be generated'
+ )
]);
$this->addElement('select', 'template', [
- 'label' => 'Template',
- 'options' => [null => 'Please choose'] + $this->listTemplates()
+ 'label' => $this->translate('Template'),
+ 'options' => [null => $this->translate('Please choose')] + Database::listTemplates(),
+ 'description' => $this->translate(
+ 'Specifies the template to use when exporting this report to pdf. (Default Icinga template)'
+ )
]);
$this->addElement('select', 'reportlet', [
- 'required' => true,
- 'label' => 'Report',
- 'options' => [null => 'Please choose'] + $this->listReports(),
- 'class' => 'autosubmit'
+ 'required' => true,
+ 'class' => 'autosubmit',
+ 'label' => $this->translate('Report'),
+ 'options' => [null => $this->translate('Please choose')] + $this->listReports(),
+ 'description' => $this->translate('Specifies the type of the reportlet to be generated')
]);
$values = $this->getValues();
@@ -63,7 +153,7 @@ class ReportForm extends CompatForm
// $config->populate($this->getValues());
/** @var \Icinga\Module\Reporting\Hook\ReportHook $reportlet */
- $reportlet = new $values['reportlet'];
+ $reportlet = new $values['reportlet']();
$reportlet->initConfigForm($config);
@@ -73,40 +163,43 @@ class ReportForm extends CompatForm
}
$this->addElement('submit', 'submit', [
- 'label' => $this->id === null ? 'Create Report' : 'Update Report'
+ 'label' => $this->getSubmitButtonLabel()
]);
if ($this->id !== null) {
/** @var FormSubmitElement $removeButton */
$removeButton = $this->createElement('submit', 'remove', [
- 'label' => 'Remove Report',
+ 'label' => $this->translate('Remove Report'),
'class' => 'btn-remove',
'formnovalidate' => true
]);
$this->registerElement($removeButton);
- $this->getElement('submit')->getWrapper()->prepend($removeButton);
-
- if ($removeButton->hasBeenPressed()) {
- $this->getDb()->delete('report', ['id = ?' => $this->id]);
- // Stupid cheat because ipl/html is not capable of multiple submit buttons
- $this->getSubmitButton()->setValue($this->getSubmitButton()->getButtonLabel());
- $this->callOnSuccess = false;
- $this->valid = true;
+ /** @var HtmlDocument $wrapper */
+ $wrapper = $this->getElement('submit')->getWrapper();
+ $wrapper->prepend($removeButton);
+ } elseif ($this->renderCreateAndShowButton) {
+ $createAndShow = $this->createElement('submit', 'create_show', [
+ 'label' => $this->translate('Create and Show'),
+ ]);
+ $this->registerElement($createAndShow);
- return;
- }
+ /** @var HtmlDocument $wrapper */
+ $wrapper = $this->getElement('submit')->getWrapper();
+ $wrapper->prepend($createAndShow);
}
}
public function onSuccess()
{
- if ($this->callOnSuccess === false) {
+ $db = Database::get();
+
+ if ($this->getPopulatedValue('remove')) {
+ $db->delete('report', ['id = ?' => $this->id]);
+
return;
}
- $db = $this->getDb();
-
$values = $this->getValues();
$now = time() * 1000;
@@ -155,14 +248,16 @@ class ReportForm extends CompatForm
foreach ($values as $name => $value) {
$db->insert('config', [
- 'reportlet_id' => $reportletId,
- 'name' => $name,
- 'value' => $value,
- 'ctime' => $now,
- 'mtime' => $now
+ 'reportlet_id' => $reportletId,
+ 'name' => $name,
+ 'value' => $value,
+ 'ctime' => $now,
+ 'mtime' => $now
]);
}
$db->commitTransaction();
+
+ $this->id = $reportId;
}
}
diff --git a/library/Reporting/Web/Forms/ScheduleForm.php b/library/Reporting/Web/Forms/ScheduleForm.php
index 47f3ee3..72c4767 100644
--- a/library/Reporting/Web/Forms/ScheduleForm.php
+++ b/library/Reporting/Web/Forms/ScheduleForm.php
@@ -1,96 +1,98 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Web\Forms;
use DateTime;
-use Icinga\Application\Version;
+use Icinga\Application\Icinga;
+use Icinga\Application\Web;
use Icinga\Authentication\Auth;
use Icinga\Module\Reporting\Database;
+use Icinga\Module\Reporting\Hook\ActionHook;
use Icinga\Module\Reporting\ProvidedActions;
use Icinga\Module\Reporting\Report;
-use Icinga\Module\Reporting\Web\Flatpickr;
-use Icinga\Module\Reporting\Web\Forms\Decorator\CompatDecorator;
+use Icinga\Util\Json;
use ipl\Html\Contract\FormSubmitElement;
use ipl\Html\Form;
+use ipl\Html\HtmlDocument;
+use ipl\Html\HtmlElement;
+use ipl\Scheduler\Contract\Frequency;
use ipl\Web\Compat\CompatForm;
+use ipl\Web\FormElement\ScheduleElement;
+
+use function ipl\Stdlib\get_php_type;
class ScheduleForm extends CompatForm
{
- use Database;
- use DecoratedElement;
use ProvidedActions;
/** @var Report */
protected $report;
- protected $id;
+ /** @var ScheduleElement */
+ protected $scheduleElement;
- public function setReport(Report $report)
+ public function __construct()
{
- $this->report = $report;
+ $this->scheduleElement = new ScheduleElement('schedule_element');
+ /** @var Web $app */
+ $app = Icinga::app();
+ $this->scheduleElement->setIdProtector([$app->getRequest(), 'protectId']);
+ }
- $schedule = $report->getSchedule();
+ public function getPartUpdates(): array
+ {
+ return $this->scheduleElement->prepareMultipartUpdate($this->getRequest());
+ }
+
+ /**
+ * Create a new form instance with the given report
+ *
+ * @param Report $report
+ *
+ * @return static
+ */
+ public static function fromReport(Report $report): self
+ {
+ $form = new static();
+ $form->report = $report;
+ $schedule = $report->getSchedule();
if ($schedule !== null) {
- $this->setId($schedule->getId());
+ $config = $schedule->getConfig();
+ $config['action'] = $schedule->getAction();
+
+ /** @var Frequency $type */
+ $type = $config['frequencyType'];
+ $config['schedule_element'] = $type::fromJson($config['frequency']);
- $values = [
- 'start' => $schedule->getStart()->format('Y-m-d\\TH:i:s'),
- 'frequency' => $schedule->getFrequency(),
- 'action' => $schedule->getAction()
- ] + $schedule->getConfig();
+ unset($config['frequency']);
+ unset($config['frequencyType']);
- $this->populate($values);
+ $form->populate($config);
}
- return $this;
+ return $form;
}
- public function setId($id)
+ public function hasBeenSubmitted(): bool
{
- $this->id = $id;
-
- return $this;
+ return $this->hasBeenSent() && (
+ $this->getPopulatedValue('submit')
+ || $this->getPopulatedValue('remove')
+ || $this->getPopulatedValue('send')
+ );
}
protected function assemble()
{
- $this->setDefaultElementDecorator(new CompatDecorator());
-
- $frequency = [
- 'minutely' => 'Minutely',
- 'hourly' => 'Hourly',
- 'daily' => 'Daily',
- 'weekly' => 'Weekly',
- 'monthly' => 'Monthly'
- ];
-
- if (version_compare(Version::VERSION, '2.9.0', '>=')) {
- $this->addElement('localDateTime', 'start', [
- 'required' => true,
- 'label' => t('Start'),
- 'placeholder' => t('Choose date and time')
- ]);
- } else {
- $this->addDecoratedElement((new Flatpickr())->setAllowInput(false), 'text', 'start', [
- 'required' => true,
- 'label' => t('Start'),
- 'placeholder' => t('Choose date and time')
- ]);
- }
-
- $this->addElement('select', 'frequency', [
- 'required' => true,
- 'label' => 'Frequency',
- 'options' => [null => 'Please choose'] + $frequency,
- ]);
-
$this->addElement('select', 'action', [
- 'required' => true,
- 'label' => 'Action',
- 'options' => [null => 'Please choose'] + $this->listActions(),
- 'class' => 'autosubmit'
+ 'required' => true,
+ 'class' => 'autosubmit',
+ 'options' => array_merge([null => $this->translate('Please choose')], $this->listActions()),
+ 'label' => $this->translate('Action'),
+ 'description' => $this->translate('Specifies an action to be triggered by the scheduler')
]);
$values = $this->getValues();
@@ -99,8 +101,8 @@ class ScheduleForm extends CompatForm
$config = new Form();
// $config->populate($this->getValues());
- /** @var \Icinga\Module\Reporting\Hook\ActionHook $action */
- $action = new $values['action'];
+ /** @var ActionHook $action */
+ $action = new $values['action']();
$action->initConfigForm($config, $this->report);
@@ -109,67 +111,80 @@ class ScheduleForm extends CompatForm
}
}
+ $this->addHtml(HtmlElement::create('div', ['class' => 'schedule-element-separator']));
+ $this->addElement($this->scheduleElement);
+
+ $schedule = $this->report->getSchedule();
$this->addElement('submit', 'submit', [
- 'label' => $this->id === null ? 'Create Schedule' : 'Update Schedule'
+ 'label' => $schedule === null ? $this->translate('Create Schedule') : $this->translate('Update Schedule')
]);
- if ($this->id !== null) {
+ if ($schedule !== null) {
+ $sendButton = $this->createElement('submit', 'send', [
+ 'label' => $this->translate('Send Report Now'),
+ 'formnovalidate' => true
+ ]);
+ $this->registerElement($sendButton);
+
+ /** @var HtmlDocument $wrapper */
+ $wrapper = $this->getElement('submit')->getWrapper();
+ $wrapper->prepend($sendButton);
+
/** @var FormSubmitElement $removeButton */
$removeButton = $this->createElement('submit', 'remove', [
- 'label' => 'Remove Schedule',
+ 'label' => $this->translate('Remove Schedule'),
'class' => 'btn-remove',
'formnovalidate' => true
]);
$this->registerElement($removeButton);
- $this->getElement('submit')->getWrapper()->prepend($removeButton);
-
- if ($removeButton->hasBeenPressed()) {
- $this->getDb()->delete('schedule', ['id = ?' => $this->id]);
-
- // Stupid cheat because ipl/html is not capable of multiple submit buttons
- $this->getSubmitButton()->setValue($this->getSubmitButton()->getButtonLabel());
- $this->valid = true;
-
- return;
- }
+ $wrapper->prepend($removeButton);
}
}
public function onSuccess()
{
- $db = $this->getDb();
+ $db = Database::get();
+ $schedule = $this->report->getSchedule();
+ if ($this->getPopulatedValue('remove')) {
+ $db->delete('schedule', ['id = ?' => $schedule->getId()]);
- $values = $this->getValues();
+ return;
+ }
- $now = time() * 1000;
+ $values = $this->getValues();
+ if ($this->getPopulatedValue('send')) {
+ $action = new $values['action']();
+ $action->execute($this->report, $values);
- if (! $values['start'] instanceof DateTime) {
- $values['start'] = DateTime::createFromFormat('Y-m-d H:i:s', $values['start']);
+ return;
}
- $data = [
- 'start' => $values['start']->getTimestamp() * 1000,
- 'frequency' => $values['frequency'],
- 'action' => $values['action'],
- 'mtime' => $now
- ];
-
- unset($values['start']);
- unset($values['frequency']);
+ $action = $values['action'];
unset($values['action']);
+ unset($values['schedule_element']);
- $data['config'] = json_encode($values);
+ $frequency = $this->scheduleElement->getValue();
+ $values['frequency'] = Json::encode($frequency);
+ $values['frequencyType'] = get_php_type($frequency);
+ $config = Json::encode($values);
$db->beginTransaction();
- if ($this->id === null) {
- $db->insert('schedule', $data + [
+ if ($schedule === null) {
+ $now = (new DateTime())->getTimestamp() * 1000;
+ $db->insert('schedule', [
'author' => Auth::getInstance()->getUser()->getUsername(),
'report_id' => $this->report->getId(),
- 'ctime' => $now
+ 'ctime' => $now,
+ 'mtime' => $now,
+ 'action' => $action,
+ 'config' => $config
]);
} else {
- $db->update('schedule', $data, ['id = ?' => $this->id]);
+ $db->update('schedule', [
+ 'action' => $action,
+ 'config' => $config
+ ], ['id = ?' => $schedule->getId()]);
}
$db->commitTransaction();
diff --git a/library/Reporting/Web/Forms/SendForm.php b/library/Reporting/Web/Forms/SendForm.php
index 03b691c..e3cf3ec 100644
--- a/library/Reporting/Web/Forms/SendForm.php
+++ b/library/Reporting/Web/Forms/SendForm.php
@@ -1,18 +1,16 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Web\Forms;
use Icinga\Module\Reporting\Actions\SendMail;
-use Icinga\Module\Reporting\Database;
use Icinga\Module\Reporting\ProvidedReports;
use Icinga\Module\Reporting\Report;
-use Icinga\Module\Reporting\Web\Forms\Decorator\CompatDecorator;
use ipl\Web\Compat\CompatForm;
class SendForm extends CompatForm
{
- use Database;
use ProvidedReports;
/** @var Report */
@@ -27,12 +25,10 @@ class SendForm extends CompatForm
protected function assemble()
{
- $this->setDefaultElementDecorator(new CompatDecorator());
-
(new SendMail())->initConfigForm($this, $this->report);
$this->addElement('submit', 'submit', [
- 'label' => 'Send Report'
+ 'label' => $this->translate('Send Report')
]);
}
diff --git a/library/Reporting/Web/Forms/TemplateForm.php b/library/Reporting/Web/Forms/TemplateForm.php
index bb062bb..4cd44a9 100644
--- a/library/Reporting/Web/Forms/TemplateForm.php
+++ b/library/Reporting/Web/Forms/TemplateForm.php
@@ -1,23 +1,21 @@
<?php
+
// Icinga Reporting | (c) 2019 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Web\Forms;
+use Exception;
+use GuzzleHttp\Psr7\UploadedFile;
use Icinga\Authentication\Auth;
use Icinga\Module\Reporting\Database;
-use Icinga\Module\Reporting\Web\Forms\Decorator\CompatDecorator;
+use Icinga\Util\Json;
use ipl\Html\Contract\FormSubmitElement;
use ipl\Html\Html;
+use ipl\Html\HtmlDocument;
use ipl\Web\Compat\CompatForm;
-use reportingipl\Html\FormElement\FileElement;
class TemplateForm extends CompatForm
{
- use Database;
-
- /** @var bool Hack to disable the {@link onSuccess()} code upon deletion of the template */
- protected $callOnSuccess;
-
protected $template;
public function getTemplate()
@@ -25,126 +23,141 @@ class TemplateForm extends CompatForm
return $this->template;
}
- public function setTemplate($template)
+ /**
+ * Create a new form instance with the given report
+ *
+ * @param $template
+ *
+ * @return static
+ */
+ public static function fromTemplate($template): self
{
- $this->template = $template;
+ $form = new static();
+
+ $template->settings = Json::decode($template->settings, true);
+ $form->template = $template;
if ($template->settings) {
- $this->populate(array_filter($template->settings, function ($value) {
+ /** @var array<string, mixed> $settings */
+ $settings = $template->settings;
+ $form->populate(array_filter($settings, function ($value) {
// Don't populate files
return ! is_array($value);
}));
}
- return $this;
+ return $form;
}
- protected function assemble()
+ public function hasBeenSubmitted(): bool
{
- $this->setDefaultElementDecorator(new CompatDecorator());
+ return $this->hasBeenSent() && ($this->getPopulatedValue('submit') || $this->getPopulatedValue('remove'));
+ }
+ protected function assemble()
+ {
$this->setAttribute('enctype', 'multipart/form-data');
$this->add(Html::tag('h2', 'Template Settings'));
$this->addElement('text', 'name', [
- 'label' => 'Name',
- 'placeholder' => 'Template name',
+ 'label' => $this->translate('Name'),
+ 'placeholder' => $this->translate('Template name'),
'required' => true
]);
- $this->add(Html::tag('h2', 'Cover Page Settings'));
+ $this->add(Html::tag('h2', $this->translate('Cover Page Settings')));
- $this->addElement(new FileElement('cover_page_background_image', [
- 'label' => 'Background Image',
- 'accept' => 'image/png, image/jpeg'
- ]));
+ $this->addElement('file', 'cover_page_background_image', [
+ 'label' => $this->translate('Background Image'),
+ 'accept' => ['image/png', 'image/jpeg', 'image/jpg'],
+ 'destination' => sys_get_temp_dir()
+ ]);
- if ($this->template !== null
- && isset($this->template->settings['cover_page_background_image'])
+ if (
+ $this->template !== null
+ && isset($this->template->settings['cover_page_background_image'])
) {
$this->add(Html::tag(
'p',
- ['style' => ['margin-left: 14em;']],
- 'Upload a new background image to override the existing one'
+ ['class' => 'override-uploaded-file-hint'],
+ $this->translate('Upload a new background image to override the existing one')
));
$this->addElement('checkbox', 'remove_cover_page_background_image', [
- 'label' => 'Remove background image'
+ 'label' => $this->translate('Remove background image')
]);
}
- $this->addElement(new FileElement('cover_page_logo', [
- 'label' => 'Logo',
- 'accept' => 'image/png, image/jpeg'
- ]));
+ $this->addElement('file', 'cover_page_logo', [
+ 'label' => $this->translate('Logo'),
+ 'accept' => ['image/png', 'image/jpeg', 'image/jpg'],
+ 'destination' => sys_get_temp_dir()
+ ]);
- if ($this->template !== null
+ if (
+ $this->template !== null
&& isset($this->template->settings['cover_page_logo'])
) {
$this->add(Html::tag(
'p',
- ['style' => ['margin-left: 14em;']],
- 'Upload a new logo to override the existing one'
+ ['class' => 'override-uploaded-file-hint'],
+ $this->translate('Upload a new logo to override the existing one')
));
$this->addElement('checkbox', 'remove_cover_page_logo', [
- 'label' => 'Remove Logo'
+ 'label' => $this->translate('Remove Logo')
]);
}
$this->addElement('textarea', 'title', [
- 'label' => 'Title',
- 'placeholder' => 'Report title'
+ 'label' => $this->translate('Title'),
+ 'placeholder' => $this->translate('Report title')
]);
$this->addElement('text', 'color', [
- 'label' => 'Color',
- 'placeholder' => 'CSS color code'
+ 'label' => $this->translate('Color'),
+ 'placeholder' => $this->translate('CSS color code')
]);
- $this->add(Html::tag('h2', 'Header Settings'));
+ $this->add(Html::tag('h2', $this->translate('Header Settings')));
- $this->addColumnSettings('header_column1', 'Column 1');
- $this->addColumnSettings('header_column2', 'Column 2');
- $this->addColumnSettings('header_column3', 'Column 3');
+ $this->addColumnSettings('header_column1', $this->translate('Column 1'));
+ $this->addColumnSettings('header_column2', $this->translate('Column 2'));
+ $this->addColumnSettings('header_column3', $this->translate('Column 3'));
- $this->add(Html::tag('h2', 'Footer Settings'));
+ $this->add(Html::tag('h2', $this->translate('Footer Settings')));
- $this->addColumnSettings('footer_column1', 'Column 1');
- $this->addColumnSettings('footer_column2', 'Column 2');
- $this->addColumnSettings('footer_column3', 'Column 3');
+ $this->addColumnSettings('footer_column1', $this->translate('Column 1'));
+ $this->addColumnSettings('footer_column2', $this->translate('Column 2'));
+ $this->addColumnSettings('footer_column3', $this->translate('Column 3'));
$this->addElement('submit', 'submit', [
- 'label' => $this->template === null ? 'Create Template' : 'Update Template'
+ 'label' => $this->template === null
+ ? $this->translate('Create Template')
+ : $this->translate('Update Template')
]);
if ($this->template !== null) {
/** @var FormSubmitElement $removeButton */
$removeButton = $this->createElement('submit', 'remove', [
- 'label' => 'Remove Template',
+ 'label' => $this->translate('Remove Template'),
'class' => 'btn-remove',
'formnovalidate' => true
]);
$this->registerElement($removeButton);
- $this->getElement('submit')->getWrapper()->prepend($removeButton);
- if ($removeButton->hasBeenPressed()) {
- $this->getDb()->delete('template', ['id = ?' => $this->template->id]);
-
- // Stupid cheat because ipl/html is not capable of multiple submit buttons
- $this->getSubmitButton()->setValue($this->getSubmitButton()->getButtonLabel());
- $this->callOnSuccess = false;
- $this->valid = true;
-
- return;
- }
+ /** @var HtmlDocument $wrapper */
+ $wrapper = $this->getElement('submit')->getWrapper();
+ $wrapper->prepend($removeButton);
}
}
public function onSuccess()
{
- if ($this->callOnSuccess === false) {
+ if ($this->getPopulatedValue('remove')) {
+ Database::get()->delete('template', ['id = ?' => $this->template->id]);
+
return;
}
@@ -153,20 +166,17 @@ class TemplateForm extends CompatForm
$settings = $this->getValues();
try {
- /** @var $uploadedFile \GuzzleHttp\Psr7\UploadedFile */
- foreach ($this->getRequest()->getUploadedFiles() as $name => $uploadedFile) {
- if ($uploadedFile->getError() === UPLOAD_ERR_NO_FILE) {
- continue;
+ foreach ($settings as $name => $setting) {
+ if ($setting instanceof UploadedFile) {
+ $settings[$name] = [
+ 'mime_type' => $setting->getClientMediaType(),
+ 'size' => $setting->getSize(),
+ 'content' => base64_encode((string) $setting->getStream())
+ ];
}
-
- $settings[$name] = [
- 'mime_type' => $uploadedFile->getClientMediaType(),
- 'size' => $uploadedFile->getSize(),
- 'content' => base64_encode((string) $uploadedFile->getStream())
- ];
}
- $db = $this->getDb();
+ $db = Database::get();
$now = time() * 1000;
@@ -179,19 +189,21 @@ class TemplateForm extends CompatForm
'mtime' => $now
]);
} else {
- if (isset($settings['remove_cover_page_background_image'])) {
+ if ($this->getValue('remove_cover_page_background_image', 'n') === 'y') {
unset($settings['cover_page_background_image']);
unset($settings['remove_cover_page_background_image']);
- } elseif (! isset($settings['cover_page_background_image'])
+ } elseif (
+ ! isset($settings['cover_page_background_image'])
&& isset($this->template->settings['cover_page_background_image'])
) {
$settings['cover_page_background_image'] = $this->template->settings['cover_page_background_image'];
}
- if (isset($settings['remove_cover_page_logo'])) {
+ if ($this->getValue('remove_cover_page_logo', 'n') === 'y') {
unset($settings['cover_page_logo']);
unset($settings['remove_cover_page_logo']);
- } elseif (! isset($settings['cover_page_logo'])
+ } elseif (
+ ! isset($settings['cover_page_logo'])
&& isset($this->template->settings['cover_page_logo'])
) {
$settings['cover_page_logo'] = $this->template->settings['cover_page_logo'];
@@ -204,7 +216,8 @@ class TemplateForm extends CompatForm
if ($settings[$type] === 'image') {
$value = "{$headerOrFooter}_column{$i}_value";
- if (! isset($settings[$value])
+ if (
+ ! isset($settings[$value])
&& isset($this->template->settings[$value])
) {
$settings[$value] = $this->template->settings[$value];
@@ -219,7 +232,7 @@ class TemplateForm extends CompatForm
'mtime' => $now
], ['id = ?' => $this->template->id]);
}
- } catch (\Exception $e) {
+ } catch (Exception $e) {
die($e->getMessage());
}
}
@@ -240,20 +253,31 @@ class TemplateForm extends CompatForm
]
]);
+ $valueType = $this->getValue($type, 'none');
+ $populated = $this->getPopulatedValue($value);
+ if (
+ ($valueType === 'image' && ! $populated instanceof UploadedFile)
+ || ($valueType !== 'image' && $populated instanceof UploadedFile)
+ ) {
+ $this->clearPopulatedValue($value);
+ }
+
switch ($this->getValue($type, 'none')) {
case 'image':
- $this->addElement(new FileElement($value, [
- 'label' => 'Image',
- 'accept' => 'image/png, image/jpeg'
- ]));
+ $this->addElement('file', $value, [
+ 'label' => 'Image',
+ 'accept' => ['image/png', 'image/jpeg', 'image/jpg'],
+ 'destination' => sys_get_temp_dir()
+ ]);
- if ($this->template !== null
+ if (
+ $this->template !== null
&& $this->template->settings[$type] === 'image'
&& isset($this->template->settings[$value])
) {
$this->add(Html::tag(
'p',
- ['style' => ['margin-left: 14em;']],
+ ['class' => 'override-uploaded-file-hint'],
'Upload a new image to override the existing one'
));
}
@@ -270,7 +294,7 @@ class TemplateForm extends CompatForm
'page_of' => 'Page Number + Total Number of Pages',
'date' => 'Date'
],
- 'value' => 'report_title'
+ 'value' => 'report_title'
]);
break;
case 'text':
diff --git a/library/Reporting/Web/Forms/TimeframeForm.php b/library/Reporting/Web/Forms/TimeframeForm.php
index 3d78709..37ea34f 100644
--- a/library/Reporting/Web/Forms/TimeframeForm.php
+++ b/library/Reporting/Web/Forms/TimeframeForm.php
@@ -1,86 +1,203 @@
<?php
+
// Icinga Reporting | (c) 2019 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Web\Forms;
+use DateTime;
+use Exception;
use Icinga\Module\Reporting\Database;
-use Icinga\Module\Reporting\Web\Flatpickr;
-use Icinga\Module\Reporting\Web\Forms\Decorator\CompatDecorator;
use ipl\Html\Contract\FormSubmitElement;
+use ipl\Html\FormElement\LocalDateTimeElement;
+use ipl\Html\HtmlDocument;
+use ipl\Validator\CallbackValidator;
use ipl\Web\Compat\CompatForm;
class TimeframeForm extends CompatForm
{
- use Database;
- use DecoratedElement;
-
+ /** @var int */
protected $id;
- public function setId($id)
+ /**
+ * Create a new form instance with the given report
+ *
+ * @param int $id
+ *
+ * @return static
+ */
+ public static function fromId(int $id): self
{
- $this->id = $id;
+ $form = new static();
+
+ $form->id = $id;
- return $this;
+ return $form;
}
- protected function assemble()
+ public function hasBeenSubmitted(): bool
{
- $this->setDefaultElementDecorator(new CompatDecorator());
+ return $this->hasBeenSent() && ($this->getPopulatedValue('submit') || $this->getPopulatedValue('remove'));
+ }
+ protected function assemble()
+ {
$this->addElement('text', 'name', [
- 'required' => true,
- 'label' => 'Name'
+ 'required' => true,
+ 'label' => $this->translate('Name'),
+ 'description' => $this->translate('A unique name of this timeframe')
]);
- $flatpickr = new Flatpickr();
+ $default = new DateTime('00:00:00');
+ $start = $this->getPopulatedValue('start', $default);
+ if (! $start instanceof DateTime) {
+ $datetime = DateTime::createFromFormat(LocalDateTimeElement::FORMAT, $start);
+ if ($datetime) {
+ $start = $datetime;
+ }
+ }
- $this->addDecoratedElement($flatpickr, 'text', 'start', [
- 'required' => true,
- 'label' => 'Start',
- 'placeholder' => 'Select a start date or provide a textual datetime description',
- 'data-flatpickr-default-hour' => '00'
+ $relativeStart = $this->getPopulatedValue('relative-start', $start instanceof DateTime ? 'n' : 'y');
+ $this->addElement('checkbox', 'relative-start', [
+ 'required' => false,
+ 'class' => 'autosubmit',
+ 'value' => $relativeStart,
+ 'label' => $this->translate('Relative Start')
]);
- $this->addDecoratedElement($flatpickr, 'text', 'end', [
- 'required' => true,
- 'label' => 'End',
- 'placeholder' => 'Select a end date or provide a textual datetime description',
- 'data-flatpickrDefaultHour' => '23',
- 'data-flatpickrDefaultMinute' => '59',
- 'data-flatpickrDefaultSeconds' => '59'
- ]);
+ if ($relativeStart === 'n') {
+ if (! $start instanceof DateTime) {
+ $start = $default;
+ $this->clearPopulatedValue('start');
+ }
+
+ $this->addElement(
+ new LocalDateTimeElement('start', [
+ 'required' => true,
+ 'value' => $start,
+ 'label' => $this->translate('Start'),
+ 'description' => $this->translate('Specifies the start time of this timeframe')
+ ])
+ );
+ } else {
+ $this->addElement('text', 'start', [
+ 'required' => true,
+ 'label' => $this->translate('Start'),
+ 'placeholder' => $this->translate('First day of this month'),
+ 'description' => $this->translate('Specifies the start time of this timeframe'),
+ 'validators' => [
+ new CallbackValidator(function ($value, CallbackValidator $validator) {
+ if ($value !== null) {
+ try {
+ new DateTime($value);
+ } catch (Exception $_) {
+ $validator->addMessage($this->translate('Invalid textual date time'));
+
+ return false;
+ }
+ }
+
+ return true;
+ })
+ ]
+ ]);
+ }
+
+ $default = new DateTime('23:59:59');
+ $end = $this->getPopulatedValue('end', $default);
+ if (! $end instanceof DateTime) {
+ $datetime = DateTime::createFromFormat(LocalDateTimeElement::FORMAT, $end);
+ if ($datetime) {
+ $end = $datetime;
+ }
+ }
+
+ $relativeEnd = $this->getPopulatedValue('relative-end', $end instanceof DateTime ? 'n' : 'y');
+ if ($relativeStart === 'y') {
+ $this->addElement('checkbox', 'relative-end', [
+ 'required' => false,
+ 'class' => 'autosubmit',
+ 'value' => $relativeEnd,
+ 'label' => $this->translate('Relative End')
+ ]);
+ }
+
+ if ($relativeEnd === 'n' || $relativeStart === 'n') {
+ if (! $end instanceof DateTime) {
+ $end = $default;
+ $this->clearPopulatedValue('end');
+ }
+
+ $this->addElement(
+ new LocalDateTimeElement('end', [
+ 'required' => true,
+ 'value' => $end,
+ 'label' => $this->translate('End'),
+ 'description' => $this->translate('Specifies the end time of this timeframe')
+ ])
+ );
+ } else {
+ $this->addElement('text', 'end', [
+ 'required' => true,
+ 'label' => $this->translate('End'),
+ 'placeholder' => $this->translate('Last day of this month'),
+ 'description' => $this->translate('Specifies the end time of this timeframe'),
+ 'validators' => [
+ new CallbackValidator(function ($value, CallbackValidator $validator) {
+ if ($value !== null) {
+ try {
+ new DateTime($value);
+ } catch (Exception $_) {
+ $validator->addMessage($this->translate('Invalid textual date time'));
+
+ return false;
+ }
+ }
+
+ return true;
+ })
+ ]
+ ]);
+ }
$this->addElement('submit', 'submit', [
- 'label' => $this->id === null ? 'Create Time Frame' : 'Update Time Frame'
+ 'label' => $this->id === null
+ ? $this->translate('Create Time Frame')
+ : $this->translate('Update Time Frame')
]);
if ($this->id !== null) {
/** @var FormSubmitElement $removeButton */
$removeButton = $this->createElement('submit', 'remove', [
- 'label' => 'Remove Time Frame',
+ 'label' => $this->translate('Remove Time Frame'),
'class' => 'btn-remove',
'formnovalidate' => true
]);
$this->registerElement($removeButton);
- $this->getElement('submit')->getWrapper()->prepend($removeButton);
- if ($removeButton->hasBeenPressed()) {
- $this->getDb()->delete('timeframe', ['id = ?' => $this->id]);
-
- // Stupid cheat because ipl/html is not capable of multiple submit buttons
- $this->getSubmitButton()->setValue($this->getSubmitButton()->getButtonLabel());
- $this->valid = true;
-
- return;
- }
+ /** @var HtmlDocument $wrapper */
+ $wrapper = $this->getElement('submit')->getWrapper();
+ $wrapper->prepend($removeButton);
}
}
public function onSuccess()
{
- $db = $this->getDb();
+ $db = Database::get();
+
+ if ($this->getPopulatedValue('remove')) {
+ $db->delete('timeframe', ['id = ?' => $this->id]);
+
+ return;
+ }
$values = $this->getValues();
+ if ($values['start'] instanceof DateTime) {
+ $values['start'] = $values['start']->format(LocalDateTimeElement::FORMAT);
+ }
+
+ if ($values['end'] instanceof DateTime) {
+ $values['end'] = $values['end']->format(LocalDateTimeElement::FORMAT);
+ }
$now = time() * 1000;
diff --git a/library/Reporting/Web/ReportsTimeframesAndTemplatesTabs.php b/library/Reporting/Web/ReportsTimeframesAndTemplatesTabs.php
index afb8b14..fee6770 100644
--- a/library/Reporting/Web/ReportsTimeframesAndTemplatesTabs.php
+++ b/library/Reporting/Web/ReportsTimeframesAndTemplatesTabs.php
@@ -1,4 +1,5 @@
<?php
+
// Icinga Reporting | (c) 2019 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Web;
@@ -8,28 +9,29 @@ trait ReportsTimeframesAndTemplatesTabs
/**
* Create tabs
*
- * @return \Icinga\Web\Widget\Tabs
+ * @return \ipl\Web\Widget\Tabs
*/
protected function createTabs()
{
$tabs = $this->getTabs();
+ $tabs->getAttributes()->set('data-base-target', '_main');
$tabs->add('reports', [
- 'title' => $this->translate('Show reports'),
- 'label' => $this->translate('Reports'),
- 'url' => 'reporting/reports'
+ 'title' => $this->translate('Show reports'),
+ 'label' => $this->translate('Reports'),
+ 'url' => 'reporting/reports'
]);
$tabs->add('timeframes', [
- 'title' => $this->translate('Show time frames'),
- 'label' => $this->translate('Time Frames'),
- 'url' => 'reporting/timeframes'
+ 'title' => $this->translate('Show time frames'),
+ 'label' => $this->translate('Time Frames'),
+ 'url' => 'reporting/timeframes'
]);
$tabs->add('templates', [
- 'title' => $this->translate('Show templates'),
- 'label' => $this->translate('Templates'),
- 'url' => 'reporting/templates'
+ 'title' => $this->translate('Show templates'),
+ 'label' => $this->translate('Templates'),
+ 'url' => 'reporting/templates'
]);
return $tabs;
diff --git a/library/Reporting/Web/Widget/CompatDropdown.php b/library/Reporting/Web/Widget/CompatDropdown.php
index cdd7b40..f5d4b03 100644
--- a/library/Reporting/Web/Widget/CompatDropdown.php
+++ b/library/Reporting/Web/Widget/CompatDropdown.php
@@ -1,4 +1,5 @@
<?php
+
// Icinga Reporting | (c) 2021 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Web\Widget;
diff --git a/library/Reporting/Web/Widget/CoverPage.php b/library/Reporting/Web/Widget/CoverPage.php
index 545ef6a..5b95a45 100644
--- a/library/Reporting/Web/Widget/CoverPage.php
+++ b/library/Reporting/Web/Widget/CoverPage.php
@@ -5,6 +5,7 @@ namespace Icinga\Module\Reporting\Web\Widget;
use Icinga\Module\Reporting\Common\Macros;
use ipl\Html\BaseHtmlElement;
use ipl\Html\Html;
+use ipl\Web\Compat\StyleWithNonce;
class CoverPage extends BaseHtmlElement
{
@@ -138,15 +139,22 @@ class CoverPage extends BaseHtmlElement
protected function assemble()
{
if ($this->hasBackgroundImage()) {
- $this
- ->getAttributes()
- ->add('style', "background-image: url('" . Template::getDataUrl($this->getBackgroundImage()) . "');");
+ $coverPageBackground = (new StyleWithNonce())
+ ->setModule('reporting')
+ ->addFor($this, [
+ 'background-image' => sprintf("url('%s')", Template::getDataUrl($this->getBackgroundImage()))
+ ]);
+
+ $this->addHtml($coverPageBackground);
}
$content = Html::tag('div', ['class' => 'cover-page-content']);
-
if ($this->hasColor()) {
- $content->getAttributes()->add('style', "color: {$this->getColor()};");
+ $coverPageLogo = (new StyleWithNonce())
+ ->setModule('reporting')
+ ->addFor($content, ['color' => $this->getColor()]);
+
+ $content->addHtml($coverPageLogo);
}
if ($this->hasLogo()) {
diff --git a/library/Reporting/Web/Widget/HeaderOrFooter.php b/library/Reporting/Web/Widget/HeaderOrFooter.php
index dcb37e7..3ec9a7f 100644
--- a/library/Reporting/Web/Widget/HeaderOrFooter.php
+++ b/library/Reporting/Web/Widget/HeaderOrFooter.php
@@ -10,9 +10,9 @@ class HeaderOrFooter extends HtmlDocument
{
use Macros;
- const HEADER = 'header';
+ public const HEADER = 'header';
- const FOOTER = 'footer';
+ public const FOOTER = 'footer';
protected $type;
@@ -64,9 +64,9 @@ class HeaderOrFooter extends HtmlDocument
protected function createColumn(array $data, $key)
{
- $typeKey = "${key}_type";
- $valueKey = "${key}_value";
- $type = isset($data[$typeKey]) ? $data[$typeKey] : null;
+ $typeKey = "{$key}_type";
+ $valueKey = "{$key}_value";
+ $type = $data[$typeKey] ?? null;
switch ($type) {
case 'text':
diff --git a/library/Reporting/Web/Widget/Template.php b/library/Reporting/Web/Widget/Template.php
index e780a3d..0f07703 100644
--- a/library/Reporting/Web/Widget/Template.php
+++ b/library/Reporting/Web/Widget/Template.php
@@ -1,17 +1,15 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Web\Widget;
use Icinga\Module\Reporting\Common\Macros;
-use Icinga\Module\Reporting\Database;
+use Icinga\Module\Reporting\Model;
use ipl\Html\BaseHtmlElement;
-use ipl\Html\Html;
-use ipl\Sql\Select;
class Template extends BaseHtmlElement
{
- use Database;
use Macros;
protected $tag = 'div';
@@ -38,39 +36,35 @@ class Template extends BaseHtmlElement
return sprintf('data:%s;base64,%s', $image['mime_type'], $image['content']);
}
- public static function fromDb($id)
+ /**
+ * Create template from the given model
+ *
+ * @param Model\Template $templateModel
+ *
+ * @return static
+ */
+ public static function fromModel(Model\Template $templateModel): self
{
$template = new static();
- $select = (new Select())
- ->from('template')
- ->columns('*')
- ->where(['id = ?' => $id]);
-
- $row = $template->getDb()->select($select)->fetch();
-
- if ($row === false) {
- return null;
- }
-
- $row->settings = json_decode($row->settings, true);
+ $templateModel->settings = json_decode($templateModel->settings, true);
$coverPage = (new CoverPage())
- ->setColor($row->settings['color'])
- ->setTitle($row->settings['title']);
+ ->setColor($templateModel->settings['color'])
+ ->setTitle($templateModel->settings['title']);
- if (isset($row->settings['cover_page_background_image'])) {
- $coverPage->setBackgroundImage($row->settings['cover_page_background_image']);
+ if (isset($templateModel->settings['cover_page_background_image'])) {
+ $coverPage->setBackgroundImage($templateModel->settings['cover_page_background_image']);
}
- if (isset($row->settings['cover_page_logo'])) {
- $coverPage->setLogo($row->settings['cover_page_logo']);
+ if (isset($templateModel->settings['cover_page_logo'])) {
+ $coverPage->setLogo($templateModel->settings['cover_page_logo']);
}
$template
->setCoverPage($coverPage)
- ->setHeader(new HeaderOrFooter(HeaderOrFooter::HEADER, $row->settings))
- ->setFooter(new HeaderOrFooter(HeaderOrFooter::FOOTER, $row->settings));
+ ->setHeader(new HeaderOrFooter(HeaderOrFooter::HEADER, $templateModel->settings))
+ ->setFooter(new HeaderOrFooter(HeaderOrFooter::FOOTER, $templateModel->settings));
return $template;
}