summaryrefslogtreecommitdiffstats
path: root/application
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:29:17 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:29:17 +0000
commitc22a2c3ebc334fd7a891370e43a841d914893d47 (patch)
tree8a2c06166a1025a97cad914e1ce9da2bc78d646c /application
parentReleasing progress-linux version 0.10.0-2~progress7.99u1. (diff)
downloadicingaweb2-module-reporting-c22a2c3ebc334fd7a891370e43a841d914893d47.tar.xz
icingaweb2-module-reporting-c22a2c3ebc334fd7a891370e43a841d914893d47.zip
Merging upstream version 1.0.1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'application')
-rw-r--r--application/clicommands/DownloadCommand.php98
-rw-r--r--application/clicommands/ListCommand.php133
-rw-r--r--application/clicommands/ScheduleCommand.php120
-rw-r--r--application/controllers/ConfigController.php1
-rw-r--r--application/controllers/ReportController.php216
-rw-r--r--application/controllers/ReportsController.php95
-rw-r--r--application/controllers/TemplateController.php107
-rw-r--r--application/controllers/TemplatesController.php75
-rw-r--r--application/controllers/TestController.php47
-rw-r--r--application/controllers/TimeframeController.php41
-rw-r--r--application/controllers/TimeframesController.php61
-rw-r--r--application/forms/ConfigureMailForm.php1
-rw-r--r--application/forms/SelectBackendForm.php11
13 files changed, 768 insertions, 238 deletions
diff --git a/application/clicommands/DownloadCommand.php b/application/clicommands/DownloadCommand.php
new file mode 100644
index 0000000..9b7c99d
--- /dev/null
+++ b/application/clicommands/DownloadCommand.php
@@ -0,0 +1,98 @@
+<?php
+
+/* Icinga Reporting | (c) 2023 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\Reporting\Clicommands;
+
+use Icinga\Exception\NotFoundError;
+use Icinga\Module\Pdfexport\ProvidedHook\Pdfexport;
+use Icinga\Module\Reporting\Cli\Command;
+use Icinga\Module\Reporting\Database;
+use Icinga\Module\Reporting\Model;
+use Icinga\Module\Reporting\Report;
+use InvalidArgumentException;
+use ipl\Stdlib\Filter;
+
+class DownloadCommand extends Command
+{
+ /**
+ * Download report with specified ID as PDF, CSV or JSON
+ *
+ * USAGE
+ *
+ * icingacli reporting download <id> [--format=<pdf|csv|json>]
+ *
+ * OPTIONS
+ *
+ * --format=<pdf|csv|json>
+ * Download report as PDF, CSV or JSON. Defaults to pdf.
+ *
+ * --output=<file>
+ * Save report to the specified <file>.
+ *
+ * EXAMPLES
+ *
+ * Download report with ID 1:
+ * icingacli reporting download 1
+ *
+ * Download report with ID 1 as CSV:
+ * icingacli reporting download 1 --format=csv
+ *
+ * Download report with ID 1 as JSON to the specified file:
+ * icingacli reporting download 1 --format=json --output=sla.json
+ */
+ public function defaultAction()
+ {
+ $id = $this->params->getStandalone();
+ if ($id === null) {
+ $this->fail($this->translate('Argument id is mandatory'));
+ }
+
+ /** @var Model\Report $report */
+ $report = Model\Report::on(Database::get())
+ ->with('timeframe')
+ ->filter(Filter::equal('id', $id))
+ ->first();
+
+ if ($report === null) {
+ throw new NotFoundError('Report not found');
+ }
+
+ $report = Report::fromModel($report);
+
+ /** @var string $format */
+ $format = $this->params->get('format', 'pdf');
+ $format = strtolower($format);
+ switch ($format) {
+ case 'pdf':
+ $content = Pdfexport::first()->htmlToPdf($report->toPdf());
+ break;
+ case 'csv':
+ $content = $report->toCsv();
+ break;
+ case 'json':
+ $content = $report->toJson();
+ break;
+ default:
+ throw new InvalidArgumentException(sprintf('Format %s is not supported', $format));
+ }
+
+ /** @var string $output */
+ $output = $this->params->get('output');
+ if ($output === null) {
+ $name = sprintf(
+ '%s (%s) %s',
+ $report->getName(),
+ $report->getTimeframe()->getName(),
+ date('Y-m-d H:i')
+ );
+
+ $output = "$name.$format";
+ } elseif (is_dir($output)) {
+ $this->fail($this->translate(sprintf('%s is a directory', $output)));
+ }
+
+ file_put_contents($output, $content);
+ echo "$output\n";
+ }
+}
diff --git a/application/clicommands/ListCommand.php b/application/clicommands/ListCommand.php
new file mode 100644
index 0000000..2486ae0
--- /dev/null
+++ b/application/clicommands/ListCommand.php
@@ -0,0 +1,133 @@
+<?php
+
+/* Icinga Reporting | (c) 2023 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\Reporting\Clicommands;
+
+use Icinga\Module\Reporting\Cli\Command;
+use Icinga\Module\Reporting\Database;
+use Icinga\Module\Reporting\Model;
+use InvalidArgumentException;
+use ipl\Stdlib\Filter;
+
+class ListCommand extends Command
+{
+ /**
+ * List reports
+ *
+ * USAGE
+ *
+ * icingacli reporting list [OPTIONS]
+ *
+ * OPTIONS
+ *
+ * --sort=<id|name|author>
+ * Sort the reports by the given column. Defaults to id.
+ *
+ * --direction=<asc|desc>
+ * Sort the reports by the specified sort column in ascending or descending order. Defaults to asc.
+ *
+ * --filter=<name>
+ * Filter the reports by the specified report name. Performs a wildcard search by default.
+ *
+ * EXAMPLES
+ *
+ * Sort the reports by name:
+ * icingacli reporting list --sort=name
+ *
+ * Sort the reports by author in descending order:
+ * icingacli reporting list --sort=author --direction=DESC
+ *
+ * Filter the reports that contain "Host" in the report name:
+ * icingacli reporting list --filter=Host
+ *
+ * Filter the reports that begin with "Service":
+ * icingacli reporting list --filter=Service*
+ *
+ * Filter the reports that end with "SLA":
+ * icingacli reporting list --filter=*SLA
+ */
+ public function indexAction()
+ {
+ /** @var string $sort */
+ $sort = $this->params->get('sort', 'id');
+ $sort = strtolower($sort);
+
+ if ($sort !== 'id' && $sort !== 'name' && $sort !== 'author') {
+ throw new InvalidArgumentException(sprintf('Sorting by %s is not supported', $sort));
+ }
+
+ $direction = $this->params->get('direction', 'ASC');
+
+ $reports = Model\Report::on(Database::get());
+ $reports
+ ->with(['reportlets'])
+ ->orderBy($sort, $direction);
+
+ $filter = $this->params->get('filter');
+ if ($filter !== null) {
+ if (strpos($filter, '*') === false) {
+ $filter = '*' . $filter . '*';
+ }
+ $reports->filter(Filter::like('name', $filter));
+ }
+
+ if ($reports->count() === 0) {
+ print $this->translate("No reports found\n");
+ exit;
+ }
+
+ $dataCallbacks = [
+ 'ID' => function ($report) {
+ return $report->id;
+ },
+ 'Name' => function ($report) {
+ return $report->name;
+ },
+ 'Author' => function ($report) {
+ return $report->author;
+ },
+ 'Type' => function ($report) {
+ return (new $report->reportlets->class())->getName();
+ }
+ ];
+
+ $this->outputTable($reports, $dataCallbacks);
+ }
+
+ protected function outputTable($reports, array $dataCallbacks)
+ {
+ $columnsAndLengths = [];
+ foreach ($dataCallbacks as $key => $_) {
+ $columnsAndLengths[$key] = strlen($key);
+ }
+
+ $rows = [];
+ foreach ($reports as $report) {
+ $row = [];
+ foreach ($dataCallbacks as $key => $callback) {
+ $row[] = $callback($report);
+ $columnsAndLengths[$key] = max($columnsAndLengths[$key], mb_strlen($callback($report)));
+ }
+
+ $rows[] = $row;
+ }
+
+ $format = '|';
+ $beautifier = '|';
+ foreach ($columnsAndLengths as $length) {
+ $headerFormat = " %-" . sprintf('%ss |', $length);
+ $format .= $headerFormat;
+ $beautifier .= sprintf($headerFormat, str_repeat('-', $length));
+ }
+ $format .= "\n";
+ $beautifier .= "\n";
+
+ printf($format, ...array_keys($columnsAndLengths));
+ print $beautifier;
+
+ foreach ($rows as $row) {
+ printf($format, ...$row);
+ }
+ }
+}
diff --git a/application/clicommands/ScheduleCommand.php b/application/clicommands/ScheduleCommand.php
index e554138..f50d046 100644
--- a/application/clicommands/ScheduleCommand.php
+++ b/application/clicommands/ScheduleCommand.php
@@ -1,10 +1,25 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Clicommands;
+use DateTime;
+use Exception;
+use Icinga\Application\Config;
+use Icinga\Application\Logger;
+use Icinga\Data\ResourceFactory;
use Icinga\Module\Reporting\Cli\Command;
-use Icinga\Module\Reporting\Scheduler;
+use Icinga\Module\Reporting\Database;
+use Icinga\Module\Reporting\Model;
+use Icinga\Module\Reporting\Report;
+use Icinga\Module\Reporting\Schedule;
+use ipl\Scheduler\Contract\Frequency;
+use ipl\Scheduler\Contract\Task;
+use ipl\Scheduler\Scheduler;
+use React\EventLoop\Loop;
+use React\Promise\ExtendedPromiseInterface;
+use Throwable;
class ScheduleCommand extends Command
{
@@ -17,8 +32,107 @@ class ScheduleCommand extends Command
*/
public function runAction()
{
- $scheduler = new Scheduler($this->getDb());
+ $scheduler = new Scheduler();
+ $this->attachJobsLogging($scheduler);
+
+ /** @var Schedule[] $runningSchedules */
+ $runningSchedules = [];
+ // Check for configuration changes every 5 minutes to make sure new jobs are scheduled, updated and deleted
+ // jobs are cancelled.
+ $watchdog = function () use (&$watchdog, $scheduler, &$runningSchedules) {
+ $schedules = [];
+ try {
+ // Since this is a long-running daemon, the resources or module config may change meanwhile.
+ // Therefore, reload the resources and module config from disk each time (at 5m intervals)
+ // before reconnecting to the database.
+ ResourceFactory::setConfig(Config::app('resources', true));
+ Config::module('reporting', 'config', true);
+
+ $schedules = $this->fetchSchedules();
+ } catch (Throwable $err) {
+ Logger::error('Failed to fetch report schedules from the database: %s', $err);
+ Logger::debug($err->getTraceAsString());
+ }
+
+ $outdated = array_diff_key($runningSchedules, $schedules);
+ foreach ($outdated as $schedule) {
+ Logger::info(
+ 'Removing %s as it either no longer exists in the database or its config has been changed',
+ $schedule->getName()
+ );
+
+ $scheduler->remove($schedule);
+
+ unset($runningSchedules[$schedule->getUuid()->toString()]);
+ }
+
+ $newSchedules = array_diff_key($schedules, $runningSchedules);
+ foreach ($newSchedules as $key => $schedule) {
+ $config = $schedule->getConfig();
+ $frequency = $config['frequency'];
+
+ try {
+ /** @var Frequency $type */
+ $type = $config['frequencyType'];
+ $frequency = $type::fromJson($frequency);
+ } catch (Exception $err) {
+ Logger::error(
+ '%s has invalid schedule expression %s: %s',
+ $schedule->getName(),
+ $frequency,
+ $err->getMessage()
+ );
+
+ continue;
+ }
+
+ $scheduler->schedule($schedule, $frequency);
+
+ $runningSchedules[$key] = $schedule;
+ }
+
+ Loop::addTimer(5 * 60, $watchdog);
+ };
+ Loop::futureTick($watchdog);
+ }
+
+ /**
+ * Fetch schedules from the database
+ *
+ * @return Schedule[]
+ */
+ protected function fetchSchedules(): array
+ {
+ $schedules = [];
+ $query = Model\Schedule::on(Database::get())->with(['report.timeframe', 'report']);
+
+ foreach ($query as $schedule) {
+ $schedule = Schedule::fromModel($schedule, Report::fromModel($schedule->report));
+ $schedules[$schedule->getUuid()->toString()] = $schedule;
+ }
+
+ return $schedules;
+ }
+
+ protected function attachJobsLogging(Scheduler $scheduler)
+ {
+ $scheduler->on(Scheduler::ON_TASK_FAILED, function (Task $job, Throwable $e) {
+ Logger::error('Failed to run job %s: %s', $job->getName(), $e->getMessage());
+ Logger::debug($e->getTraceAsString());
+ });
+
+ $scheduler->on(Scheduler::ON_TASK_RUN, function (Task $job, ExtendedPromiseInterface $_) {
+ Logger::info('Running job %s', $job->getName());
+ });
+
+ $scheduler->on(Scheduler::ON_TASK_SCHEDULED, function (Task $job, DateTime $dateTime) {
+ Logger::info('Scheduling job %s to run at %s', $job->getName(), $dateTime->format('Y-m-d H:i:s'));
+ });
- $scheduler->run();
+ $scheduler->on(Scheduler::ON_TASK_EXPIRED, function (Task $task, DateTime $dateTime) {
+ Logger::info(
+ sprintf('Detaching expired job %s at %s', $task->getName(), $dateTime->format('Y-m-d H:i:s'))
+ );
+ });
}
}
diff --git a/application/controllers/ConfigController.php b/application/controllers/ConfigController.php
index 30fcc67..7e73c3c 100644
--- a/application/controllers/ConfigController.php
+++ b/application/controllers/ConfigController.php
@@ -1,4 +1,5 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Controllers;
diff --git a/application/controllers/ReportController.php b/application/controllers/ReportController.php
index 090c759..0a694f2 100644
--- a/application/controllers/ReportController.php
+++ b/application/controllers/ReportController.php
@@ -1,55 +1,137 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Controllers;
-use GuzzleHttp\Psr7\ServerRequest;
+use Exception;
use Icinga\Application\Hook;
use Icinga\Module\Pdfexport\ProvidedHook\Pdfexport;
use Icinga\Module\Reporting\Database;
+use Icinga\Module\Reporting\Model;
use Icinga\Module\Reporting\Report;
use Icinga\Module\Reporting\Web\Controller;
use Icinga\Module\Reporting\Web\Forms\ReportForm;
use Icinga\Module\Reporting\Web\Forms\ScheduleForm;
use Icinga\Module\Reporting\Web\Forms\SendForm;
use Icinga\Module\Reporting\Web\Widget\CompatDropdown;
+use Icinga\Web\Notification;
use ipl\Html\Error;
+use ipl\Html\HtmlElement;
+use ipl\Stdlib\Filter;
use ipl\Web\Url;
use ipl\Web\Widget\ActionBar;
use Icinga\Util\Environment;
+use ipl\Web\Widget\ActionLink;
class ReportController extends Controller
{
- use Database;
-
/** @var Report */
protected $report;
public function init()
{
- $this->report = Report::fromDb($this->params->getRequired('id'));
+ $reportId = $this->params->getRequired('id');
+
+ /** @var Model\Report $report */
+ $report = Model\Report::on(Database::get())
+ ->with(['timeframe'])
+ ->filter(Filter::equal('id', $reportId))
+ ->first();
+
+ if ($report === null) {
+ $this->httpNotFound($this->translate('Report not found'));
+ }
+
+ $this->report = Report::fromModel($report);
}
public function indexAction()
{
$this->addTitleTab($this->report->getName());
+ $this->controls->getAttributes()->add('class', 'default-layout');
$this->addControl($this->assembleActions());
+ if ($this->isXhr()) {
+ /** @var string $contentId */
+ $contentId = $this->content->getAttributes()->get('id')->getValue();
+ $this->sendExtraUpdates([
+ $contentId => Url::fromPath('reporting/report/content', ['id' => $this->report->getId()])
+ ]);
+
+ // Will be replaced once the report content is rendered
+ $this->addContent(new HtmlElement('div'));
+ } else {
+ Environment::raiseExecutionTime();
+ Environment::raiseMemoryLimit();
+
+ try {
+ $this->addContent($this->report->toHtml());
+ } catch (Exception $e) {
+ $this->addContent(Error::show($e));
+ }
+ }
+ }
+
+ public function contentAction(): void
+ {
Environment::raiseExecutionTime();
Environment::raiseMemoryLimit();
+ $this->view->compact = true;
+ $this->_helper->layout()->disableLayout();
+
try {
- $this->addContent($this->report->toHtml());
- } catch (\Exception $e) {
- $this->addContent(Error::show($e));
+ $this->getDocument()->addHtml($this->report->toHtml());
+ } catch (Exception $e) {
+ $this->getDocument()->addHtml(Error::show($e));
+ }
+ }
+
+ public function cloneAction()
+ {
+ $this->assertPermission('reporting/reports');
+ $this->addTitleTab($this->translate('Clone Report'));
+
+ $values = ['timeframe' => (string) $this->report->getTimeframe()->getId()];
+
+ $reportlet = $this->report->getReportlets()[0];
+
+ $values['reportlet'] = $reportlet->getClass();
+
+ foreach ($reportlet->getConfig() as $name => $value) {
+ if ($name === 'name') {
+ if (preg_match('/(?:Clone )(\d+)$/', $value, $m)) {
+ $value = preg_replace('/\d+$/', (string) ((int) $m[1] + 1), $value);
+ } else {
+ $value .= ' Clone 1';
+ }
+ }
+
+ $values[$name] = $value;
}
+
+ $form = (new ReportForm())
+ ->setSubmitButtonLabel($this->translate('Clone Report'))
+ ->setAction((string) Url::fromRequest())
+ ->populate($values)
+ ->on(ReportForm::ON_SUCCESS, function (ReportForm $form) {
+ Notification::success($this->translate('Cloned report successfully'));
+
+ $this->sendExtraUpdates(['#col1']);
+
+ $this->redirectNow(Url::fromPath('reporting/report', ['id' => $form->getId()]));
+ })
+ ->handleRequest($this->getServerRequest());
+
+ $this->addContent($form);
}
public function editAction()
{
$this->assertPermission('reporting/reports');
- $this->addTitleTab('Edit Report');
+ $this->addTitleTab($this->translate('Edit Report'));
$values = [
'name' => $this->report->getName(),
@@ -66,29 +148,44 @@ class ReportController extends Controller
$values[$name] = $value;
}
- $form = new ReportForm();
- $form->setId($this->report->getId());
- $form->populate($values);
- $form->handleRequest(ServerRequest::fromGlobals());
-
- $this->redirectForm($form, 'reporting/reports');
+ $form = ReportForm::fromId($this->report->getId())
+ ->setAction((string) Url::fromRequest())
+ ->populate($values)
+ ->on(ReportForm::ON_SUCCESS, function (ReportForm $form) {
+ $pressedButton = $form->getPressedSubmitElement();
+ if ($pressedButton && $pressedButton->getName() === 'remove') {
+ Notification::success($this->translate('Removed report successfully'));
+
+ $this->switchToSingleColumnLayout();
+ } else {
+ Notification::success($this->translate('Updated report successfully'));
+
+ $this->closeModalAndRefreshRemainingViews(
+ Url::fromPath('reporting/report', ['id' => $this->report->getId()])
+ );
+ }
+ })
+ ->handleRequest($this->getServerRequest());
$this->addContent($form);
}
public function sendAction()
{
- $this->addTitleTab('Send Report');
+ $this->addTitleTab($this->translate('Send Report'));
Environment::raiseExecutionTime();
Environment::raiseMemoryLimit();
- $form = new SendForm();
- $form
+ $form = (new SendForm())
->setReport($this->report)
- ->handleRequest(ServerRequest::fromGlobals());
-
- $this->redirectForm($form, "reporting/report?id={$this->report->getId()}");
+ ->setAction((string) Url::fromRequest())
+ ->on(SendForm::ON_SUCCESS, function () {
+ $this->closeModalAndRefreshRelatedView(
+ Url::fromPath('reporting/report', ['id' => $this->report->getId()])
+ );
+ })
+ ->handleRequest($this->getServerRequest());
$this->addContent($form);
}
@@ -96,16 +193,38 @@ class ReportController extends Controller
public function scheduleAction()
{
$this->assertPermission('reporting/schedules');
- $this->addTitleTab('Schedule');
-
- $form = new ScheduleForm();
- $form
- ->setReport($this->report)
- ->handleRequest(ServerRequest::fromGlobals());
-
- $this->redirectForm($form, "reporting/report?id={$this->report->getId()}");
+ $this->addTitleTab($this->translate('Schedule'));
+
+ $form = ScheduleForm::fromReport($this->report);
+ $form->setAction((string) Url::fromRequest())
+ ->on(ScheduleForm::ON_SUCCESS, function () use ($form) {
+ $pressedButton = $form->getPressedSubmitElement();
+ if ($pressedButton) {
+ $pressedButton = $pressedButton->getName();
+ }
+
+ if ($pressedButton === 'remove') {
+ Notification::success($this->translate('Removed schedule successfully'));
+ } elseif ($pressedButton === 'send') {
+ Notification::success($this->translate('Report sent successfully'));
+ } elseif ($this->report->getSchedule() !== null) {
+ Notification::success($this->translate('Updated schedule successfully'));
+ } else {
+ Notification::success($this->translate('Created schedule successfully'));
+ }
+
+ $this->closeModalAndRefreshRelatedView(
+ Url::fromPath('reporting/report', ['id' => $this->report->getId()])
+ );
+ })
+ ->handleRequest($this->getServerRequest());
$this->addContent($form);
+
+ $parts = $form->getPartUpdates();
+ if (! empty($parts)) {
+ $this->sendMultipartUpdate(...$parts);
+ }
}
public function downloadAction()
@@ -124,8 +243,9 @@ class ReportController extends Controller
switch ($type) {
case 'pdf':
- /** @var Hook\PdfexportHook */
- Pdfexport::first()->streamPdfFromHtml($this->report->toPdf(), $name);
+ /** @var Hook\PdfexportHook $exports */
+ $exports = Pdfexport::first();
+ $exports->streamPdfFromHtml($this->report->toPdf(), $name);
exit;
case 'csv':
$response = $this->getResponse();
@@ -184,24 +304,42 @@ class ReportController extends Controller
$actions = new ActionBar();
if ($this->hasPermission('reporting/reports')) {
- $actions->addLink(
- 'Modify',
- Url::fromPath('reporting/report/edit', ['id' => $reportId]),
- 'edit'
+ $actions->addHtml(
+ (new ActionLink(
+ $this->translate('Modify'),
+ Url::fromPath('reporting/report/edit', ['id' => $reportId]),
+ 'edit'
+ ))->openInModal()
+ );
+
+ $actions->addHtml(
+ (new ActionLink(
+ $this->translate('Clone'),
+ Url::fromPath('reporting/report/clone', ['id' => $reportId]),
+ 'clone'
+ ))->openInModal()
);
}
if ($this->hasPermission('reporting/schedules')) {
- $actions->addLink(
- 'Schedule',
- Url::fromPath('reporting/report/schedule', ['id' => $reportId]),
- 'calendar-empty'
+ $actions->addHtml(
+ (new ActionLink(
+ $this->translate('Schedule'),
+ Url::fromPath('reporting/report/schedule', ['id' => $reportId]),
+ 'calendar-empty'
+ ))->openInModal()
);
}
$actions
->add($download)
- ->addLink('Send', Url::fromPath('reporting/report/send', ['id' => $reportId]), 'forward');
+ ->addHtml(
+ (new ActionLink(
+ $this->translate('Send'),
+ Url::fromPath('reporting/report/send', ['id' => $reportId]),
+ 'forward'
+ ))->openInModal()
+ );
return $actions;
}
diff --git a/application/controllers/ReportsController.php b/application/controllers/ReportsController.php
index 7971897..f6aeedd 100644
--- a/application/controllers/ReportsController.php
+++ b/application/controllers/ReportsController.php
@@ -1,23 +1,23 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Controllers;
-use GuzzleHttp\Psr7\ServerRequest;
+use Icinga\Module\Icingadb\ProvidedHook\Reporting\HostSlaReport;
+use Icinga\Module\Icingadb\ProvidedHook\Reporting\ServiceSlaReport;
use Icinga\Module\Reporting\Database;
+use Icinga\Module\Reporting\Model\Report;
use Icinga\Module\Reporting\Web\Controller;
use Icinga\Module\Reporting\Web\Forms\ReportForm;
use Icinga\Module\Reporting\Web\ReportsTimeframesAndTemplatesTabs;
+use Icinga\Web\Notification;
use ipl\Html\Html;
-use ipl\Sql\Select;
use ipl\Web\Url;
use ipl\Web\Widget\ButtonLink;
-use ipl\Web\Widget\Icon;
-use ipl\Web\Widget\Link;
class ReportsController extends Controller
{
- use Database;
use ReportsTimeframesAndTemplatesTabs;
public function indexAction()
@@ -25,36 +25,42 @@ class ReportsController extends Controller
$this->createTabs()->activate('reports');
if ($this->hasPermission('reporting/reports')) {
- $this->addControl(new ButtonLink(
- $this->translate('New Report'),
- Url::fromPath('reporting/reports/new'),
- 'plus'
- ));
+ $this->addControl(
+ (new ButtonLink(
+ $this->translate('New Report'),
+ Url::fromPath('reporting/reports/new'),
+ 'plus'
+ ))->openInModal()
+ );
}
$tableRows = [];
- $select = (new Select())
- ->from('report r')
- ->columns(['r.*', 'timeframe' => 't.name'])
- ->join('timeframe t', 'r.timeframe_id = t.id')
- ->orderBy('r.mtime', SORT_DESC);
+ $reports = Report::on(Database::get())
+ ->withColumns(['report.timeframe.name']);
+
+ $sortControl = $this->createSortControl(
+ $reports,
+ [
+ 'name' => $this->translate('Name'),
+ 'author' => $this->translate('Author'),
+ 'ctime' => $this->translate('Created At'),
+ 'mtime' => $this->translate('Modified At')
+ ]
+ );
+
+ $this->addControl($sortControl);
- foreach ($this->getDb()->select($select) as $report) {
+ /** @var Report $report */
+ foreach ($reports as $report) {
$url = Url::fromPath('reporting/report', ['id' => $report->id])->getAbsoluteUrl('&');
$tableRows[] = Html::tag('tr', ['href' => $url], [
Html::tag('td', null, $report->name),
Html::tag('td', null, $report->author),
- Html::tag('td', null, $report->timeframe),
- Html::tag('td', null, date('Y-m-d H:i', $report->ctime / 1000)),
- Html::tag('td', null, date('Y-m-d H:i', $report->mtime / 1000)),
- Html::tag('td', ['class' => 'icon-col'], [
- new Link(
- new Icon('edit'),
- Url::fromPath('reporting/report/edit', ['id' => $report->id])
- )
- ])
+ Html::tag('td', null, $report->timeframe->name),
+ Html::tag('td', null, $report->ctime->format('Y-m-d H:i')),
+ Html::tag('td', null, $report->mtime->format('Y-m-d H:i')),
]);
}
@@ -94,10 +100,43 @@ class ReportsController extends Controller
$this->assertPermission('reporting/reports');
$this->addTitleTab($this->translate('New Report'));
- $form = new ReportForm();
- $form->handleRequest(ServerRequest::fromGlobals());
+ switch ($this->params->shift('report')) {
+ case 'host':
+ $class = HostSlaReport::class;
+ break;
+ case 'service':
+ $class = ServiceSlaReport::class;
+ break;
+ default:
+ $class = null;
+ break;
+ }
+
+ $form = (new ReportForm())
+ ->setAction((string) Url::fromRequest())
+ ->setRenderCreateAndShowButton($class !== null)
+ ->populate([
+ 'filter' => $this->params->shift('filter'),
+ 'reportlet' => $class
+ ])
+ ->on(ReportForm::ON_SUCCESS, function (ReportForm $form) {
+ Notification::success($this->translate('Created report successfully'));
- $this->redirectForm($form, 'reporting/reports');
+ $pressedButton = $form->getPressedSubmitElement();
+ if ($pressedButton && $pressedButton->getName() !== 'create_show') {
+ $this->closeModalAndRefreshRelatedView(Url::fromPath('reporting/reports'));
+ } else {
+ $this->redirectNow(
+ Url::fromPath(
+ sprintf(
+ 'reporting/reports#!%s',
+ Url::fromPath('reporting/report', ['id' => $form->getId()])->getAbsoluteUrl()
+ )
+ )
+ );
+ }
+ })
+ ->handleRequest($this->getServerRequest());
$this->addContent($form);
}
diff --git a/application/controllers/TemplateController.php b/application/controllers/TemplateController.php
index bb37b3c..70cf9f0 100644
--- a/application/controllers/TemplateController.php
+++ b/application/controllers/TemplateController.php
@@ -1,31 +1,54 @@
<?php
+
// Icinga Reporting | (c) 2019 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Controllers;
use DateTime;
+use Exception;
use GuzzleHttp\Psr7\ServerRequest;
use Icinga\Module\Reporting\Database;
+use Icinga\Module\Reporting\Model;
use Icinga\Module\Reporting\Web\Controller;
use Icinga\Module\Reporting\Web\Forms\TemplateForm;
use Icinga\Module\Reporting\Web\Widget\Template;
-use ipl\Sql\Select;
+use Icinga\Web\Notification;
+use ipl\Html\Form;
+use ipl\Html\ValidHtml;
+use ipl\Stdlib\Filter;
+use ipl\Web\Url;
+use ipl\Web\Widget\ActionBar;
+use ipl\Web\Widget\ActionLink;
class TemplateController extends Controller
{
- use Database;
+ /** @var Model\Template */
+ protected $template;
- public function indexAction()
+ public function init()
{
- $this->createTabs()->activate('preview');
+ parent::init();
- $template = Template::fromDb($this->params->getRequired('id'));
+ /** @var Model\Template $template */
+ $template = Model\Template::on(Database::get())
+ ->filter(Filter::equal('id', $this->params->getRequired('id')))
+ ->first();
if ($template === null) {
- throw new \Exception('Template not found');
+ throw new Exception('Template not found');
}
- $template
+ $this->template = $template;
+ }
+
+ public function indexAction()
+ {
+ $this->addTitleTab($this->translate('Preview'));
+
+ $this->controls->getAttributes()->add('class', 'default-layout');
+ $this->addControl($this->createActionBars());
+
+ $template = Template::fromModel($this->template)
->setMacros([
'date' => (new DateTime())->format('jS M, Y'),
'time_frame' => 'Time Frame',
@@ -40,50 +63,40 @@ class TemplateController extends Controller
public function editAction()
{
$this->assertPermission('reporting/templates');
-
- $this->createTabs()->activate('edit');
-
- $select = (new Select())
- ->from('template')
- ->columns(['id', 'settings'])
- ->where(['id = ?' => $this->params->getRequired('id')]);
-
- $template = $this->getDb()->select($select)->fetch();
-
- if ($template === false) {
- throw new \Exception('Template not found');
- }
-
- $template->settings = json_decode($template->settings, true);
-
- $form = (new TemplateForm())
- ->setTemplate($template);
-
- $form->handleRequest(ServerRequest::fromGlobals());
-
- $this->redirectForm($form, 'reporting/templates');
+ $this->addTitleTab($this->translate('Edit Template'));
+
+ $form = TemplateForm::fromTemplate($this->template)
+ ->setAction((string) Url::fromRequest())
+ ->on(TemplateForm::ON_SUCCESS, function (Form $form) {
+ $pressedButton = $form->getPressedSubmitElement();
+ if ($pressedButton && $pressedButton->getName() === 'remove') {
+ Notification::success($this->translate('Removed template successfully'));
+
+ $this->switchToSingleColumnLayout();
+ } else {
+ Notification::success($this->translate('Updated template successfully'));
+
+ $this->closeModalAndRefreshRemainingViews(
+ Url::fromPath('reporting/template', ['id' => $this->template->id])
+ );
+ }
+ })
+ ->handleRequest(ServerRequest::fromGlobals());
$this->addContent($form);
}
- protected function createTabs()
+ protected function createActionBars(): ValidHtml
{
- $tabs = $this->getTabs();
-
- if ($this->hasPermission('reporting/templates')) {
- $tabs->add('edit', [
- 'title' => $this->translate('Edit template'),
- 'label' => $this->translate('Edit Template'),
- 'url' => 'reporting/template/edit?id=' . $this->params->getRequired('id')
- ]);
- }
-
- $tabs->add('preview', [
- 'title' => $this->translate('Preview template'),
- 'label' => $this->translate('Preview'),
- 'url' => 'reporting/template?id=' . $this->params->getRequired('id')
- ]);
-
- return $tabs;
+ $actions = new ActionBar();
+ $actions->addHtml(
+ (new ActionLink(
+ $this->translate('Modify'),
+ Url::fromPath('reporting/template/edit', ['id' => $this->template->id]),
+ 'edit'
+ ))->openInModal()
+ );
+
+ return $actions;
}
}
diff --git a/application/controllers/TemplatesController.php b/application/controllers/TemplatesController.php
index 91a82b1..99a5dcb 100644
--- a/application/controllers/TemplatesController.php
+++ b/application/controllers/TemplatesController.php
@@ -1,22 +1,22 @@
<?php
+
// Icinga Reporting | (c) 2019 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Controllers;
-use GuzzleHttp\Psr7\ServerRequest;
use Icinga\Module\Reporting\Database;
+use Icinga\Module\Reporting\Model;
use Icinga\Module\Reporting\Web\Controller;
use Icinga\Module\Reporting\Web\Forms\TemplateForm;
use Icinga\Module\Reporting\Web\ReportsTimeframesAndTemplatesTabs;
+use Icinga\Web\Notification;
use ipl\Html\Html;
-use ipl\Sql\Select;
use ipl\Web\Url;
use ipl\Web\Widget\ButtonLink;
use ipl\Web\Widget\Link;
class TemplatesController extends Controller
{
- use Database;
use ReportsTimeframesAndTemplatesTabs;
public function indexAction()
@@ -26,38 +26,40 @@ class TemplatesController extends Controller
$canManage = $this->hasPermission('reporting/templates');
if ($canManage) {
- $this->addControl(new ButtonLink(
- $this->translate('New Template'),
- Url::fromPath('reporting/templates/new'),
- 'plus'
- ));
+ $this->addControl(
+ (new ButtonLink(
+ $this->translate('New Template'),
+ Url::fromPath('reporting/templates/new'),
+ 'plus'
+ ))->openInModal()
+ );
}
- $select = (new Select())
- ->from('template')
- ->columns(['id', 'name', 'author', 'ctime', 'mtime'])
- ->orderBy('mtime', SORT_DESC);
-
- foreach ($this->getDb()->select($select) as $template) {
- if ($canManage) {
- // Edit URL
- $subjectUrl = Url::fromPath(
- 'reporting/template/edit',
- ['id' => $template->id]
- );
- } else {
- // Preview URL
- $subjectUrl = Url::fromPath(
- 'reporting/template',
- ['id' => $template->id]
- );
- }
+ $templates = Model\Template::on(Database::get());
+
+ $sortControl = $this->createSortControl(
+ $templates,
+ [
+ 'name' => $this->translate('Name'),
+ 'author' => $this->translate('Author'),
+ 'ctime' => $this->translate('Created At'),
+ 'mtime' => $this->translate('Modified At')
+ ]
+ );
+ $this->addControl($sortControl);
+
+ $tableRows = [];
+
+ /** @var Model\Template $template */
+ foreach ($templates as $template) {
+ // Preview URL
+ $subjectLink = new Link($template->name, Url::fromPath('reporting/template', ['id' => $template->id]));
$tableRows[] = Html::tag('tr', null, [
- Html::tag('td', null, new Link($template->name, $subjectUrl)),
+ Html::tag('td', null, $subjectLink),
Html::tag('td', null, $template->author),
- Html::tag('td', null, date('Y-m-d H:i', $template->ctime / 1000)),
- Html::tag('td', null, date('Y-m-d H:i', $template->mtime / 1000))
+ Html::tag('td', null, $template->ctime->format('Y-m-d H:i')),
+ Html::tag('td', null, $template->mtime->format('Y-m-d H:i'))
]);
}
@@ -93,13 +95,16 @@ class TemplatesController extends Controller
public function newAction()
{
$this->assertPermission('reporting/templates');
- $this->addTitleTab('New Template');
-
- $form = new TemplateForm();
+ $this->addTitleTab($this->translate('New Template'));
- $form->handleRequest(ServerRequest::fromGlobals());
+ $form = (new TemplateForm())
+ ->setAction((string) Url::fromRequest())
+ ->on(TemplateForm::ON_SUCCESS, function () {
+ Notification::success($this->translate('Created template successfully'));
- $this->redirectForm($form, 'reporting/templates');
+ $this->closeModalAndRefreshRelatedView(Url::fromPath('reporting/templates'));
+ })
+ ->handleRequest($this->getServerRequest());
$this->addContent($form);
}
diff --git a/application/controllers/TestController.php b/application/controllers/TestController.php
deleted file mode 100644
index f666085..0000000
--- a/application/controllers/TestController.php
+++ /dev/null
@@ -1,47 +0,0 @@
-<?php
-// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
-
-namespace Icinga\Module\Reporting\Controllers;
-
-use Icinga\Module\Reporting\Database;
-use Icinga\Module\Reporting\Timeframe;
-use Icinga\Module\Reporting\Web\Controller;
-use ipl\Html\Table;
-use ipl\Sql\Select;
-
-class TestController extends Controller
-{
- use Database;
-
- public function timeframesAction()
- {
- $select = (new Select())
- ->from('timeframe')
- ->columns('*');
-
- $table = new Table();
-
- $table->getAttributes()->add('class', 'common-table');
-
- $table->getHeader()->add(Table::row(['Name', 'Title', 'Start', 'End'], null, 'th'));
-
- foreach ($this->getDb()->select($select) as $row) {
- $timeframe = (new Timeframe())
- ->setName($row->name)
- ->setTitle($row->title)
- ->setStart($row->start)
- ->setEnd($row->end);
-
- $table->getBody()->add(Table::row([
- $timeframe->getName(),
- $timeframe->getTitle(),
- $timeframe->getTimerange()->getStart()->format('Y-m-d H:i:s'),
- $timeframe->getTimerange()->getEnd()->format('Y-m-d H:i:s')
- ]));
- }
-
- $this->addTitleTab('Timeframes');
-
- $this->addContent($table);
- }
-}
diff --git a/application/controllers/TimeframeController.php b/application/controllers/TimeframeController.php
index ca67b0b..01395c2 100644
--- a/application/controllers/TimeframeController.php
+++ b/application/controllers/TimeframeController.php
@@ -1,24 +1,37 @@
<?php
+
// Icinga Reporting | (c) 2019 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Controllers;
-use GuzzleHttp\Psr7\ServerRequest;
+use Exception;
use Icinga\Module\Reporting\Database;
+use Icinga\Module\Reporting\Model;
use Icinga\Module\Reporting\Timeframe;
use Icinga\Module\Reporting\Web\Controller;
use Icinga\Module\Reporting\Web\Forms\TimeframeForm;
+use Icinga\Web\Notification;
+use ipl\Html\Form;
+use ipl\Web\Url;
+use ipl\Stdlib\Filter;
class TimeframeController extends Controller
{
- use Database;
-
/** @var Timeframe */
protected $timeframe;
public function init()
{
- $this->timeframe = Timeframe::fromDb($this->params->getRequired('id'));
+ /** @var Model\Timeframe $timeframe */
+ $timeframe = Model\Timeframe::on(Database::get())
+ ->filter(Filter::equal('id', $this->params->getRequired('id')))
+ ->first();
+
+ if ($timeframe === null) {
+ throw new Exception('Timeframe not found');
+ }
+
+ $this->timeframe = Timeframe::fromModel($timeframe);
}
public function editAction()
@@ -32,15 +45,19 @@ class TimeframeController extends Controller
'end' => $this->timeframe->getEnd()
];
+ $form = TimeframeForm::fromId($this->timeframe->getId())
+ ->setAction((string) Url::fromRequest())
+ ->populate($values)
+ ->on(TimeframeForm::ON_SUCCESS, function (Form $form) {
+ $pressedButton = $form->getPressedSubmitElement();
+ if ($pressedButton && $pressedButton->getName() === 'remove') {
+ Notification::success($this->translate('Removed timeframe successfully'));
+ } else {
+ Notification::success($this->translate('Update timeframe successfully'));
+ }
- $form = (new TimeframeForm())
- ->setId($this->timeframe->getId());
-
- $form->populate($values);
-
- $form->handleRequest(ServerRequest::fromGlobals());
-
- $this->redirectForm($form, 'reporting/timeframes');
+ $this->switchToSingleColumnLayout();
+ })->handleRequest($this->getServerRequest());
$this->addContent($form);
}
diff --git a/application/controllers/TimeframesController.php b/application/controllers/TimeframesController.php
index 505d8d9..f38c661 100644
--- a/application/controllers/TimeframesController.php
+++ b/application/controllers/TimeframesController.php
@@ -1,22 +1,22 @@
<?php
+
// Icinga Reporting | (c) 2019 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Controllers;
-use GuzzleHttp\Psr7\ServerRequest;
use Icinga\Module\Reporting\Database;
+use Icinga\Module\Reporting\Model;
use Icinga\Module\Reporting\Web\Controller;
use Icinga\Module\Reporting\Web\Forms\TimeframeForm;
use Icinga\Module\Reporting\Web\ReportsTimeframesAndTemplatesTabs;
+use Icinga\Web\Notification;
use ipl\Html\Html;
-use ipl\Sql\Select;
use ipl\Web\Url;
use ipl\Web\Widget\ButtonLink;
use ipl\Web\Widget\Link;
class TimeframesController extends Controller
{
- use Database;
use ReportsTimeframesAndTemplatesTabs;
public function indexAction()
@@ -26,42 +26,56 @@ class TimeframesController extends Controller
$canManage = $this->hasPermission('reporting/timeframes');
if ($canManage) {
- $this->addControl(new ButtonLink(
- $this->translate('New Timeframe'),
- Url::fromPath('reporting/timeframes/new'),
- 'plus'
- ));
+ $this->addControl(
+ (new ButtonLink(
+ $this->translate('New Timeframe'),
+ Url::fromPath('reporting/timeframes/new'),
+ 'plus'
+ ))->openInModal()
+ );
}
$tableRows = [];
- $select = (new Select())
- ->from('timeframe t')
- ->columns('*');
+ $timeframes = Model\Timeframe::on(Database::get());
+
+ $sortControl = $this->createSortControl(
+ $timeframes,
+ [
+ 'name' => $this->translate('Name'),
+ 'ctime' => $this->translate('Created At'),
+ 'mtime' => $this->translate('Modified At')
+ ]
+ );
- foreach ($this->getDb()->select($select) as $timeframe) {
+ $this->addControl($sortControl);
+
+ foreach ($timeframes as $timeframe) {
$subject = $timeframe->name;
if ($canManage) {
- $subject = new Link($timeframe->name, Url::fromPath(
- 'reporting/timeframe/edit',
- ['id' => $timeframe->id]
- ));
+ $subject = new Link(
+ $timeframe->name,
+ Url::fromPath('reporting/timeframe/edit', ['id' => $timeframe->id])
+ );
}
$tableRows[] = Html::tag('tr', null, [
Html::tag('td', null, $subject),
Html::tag('td', null, $timeframe->start),
Html::tag('td', null, $timeframe->end),
- Html::tag('td', null, date('Y-m-d H:i', $timeframe->ctime / 1000)),
- Html::tag('td', null, date('Y-m-d H:i', $timeframe->mtime / 1000))
+ Html::tag('td', null, $timeframe->ctime->format('Y-m-d H:i')),
+ Html::tag('td', null, $timeframe->mtime->format('Y-m-d H:i'))
]);
}
if (! empty($tableRows)) {
$table = Html::tag(
'table',
- ['class' => 'common-table table-row-selectable', 'data-base-target' => '_next'],
+ [
+ 'class' => 'common-table table-row-selectable',
+ 'data-base-target' => '_next'
+ ],
[
Html::tag(
'thead',
@@ -93,10 +107,13 @@ class TimeframesController extends Controller
$this->assertPermission('reporting/timeframes');
$this->addTitleTab($this->translate('New Timeframe'));
- $form = new TimeframeForm();
- $form->handleRequest(ServerRequest::fromGlobals());
+ $form = (new TimeframeForm())
+ ->setAction((string) Url::fromRequest())
+ ->on(TimeframeForm::ON_SUCCESS, function () {
+ Notification::success($this->translate('Created timeframe successfully'));
- $this->redirectForm($form, 'reporting/timeframes');
+ $this->closeModalAndRefreshRelatedView(Url::fromPath('reporting/timeframes'));
+ })->handleRequest($this->getServerRequest());
$this->addContent($form);
}
diff --git a/application/forms/ConfigureMailForm.php b/application/forms/ConfigureMailForm.php
index c27c934..b0a52ae 100644
--- a/application/forms/ConfigureMailForm.php
+++ b/application/forms/ConfigureMailForm.php
@@ -1,4 +1,5 @@
<?php
+
// Icinga Reporting | (c) 2019 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Forms;
diff --git a/application/forms/SelectBackendForm.php b/application/forms/SelectBackendForm.php
index 4ba9610..18013a3 100644
--- a/application/forms/SelectBackendForm.php
+++ b/application/forms/SelectBackendForm.php
@@ -1,4 +1,5 @@
<?php
+
// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2
namespace Icinga\Module\Reporting\Forms;
@@ -25,11 +26,11 @@ class SelectBackendForm extends ConfigForm
}
$this->addElement('select', 'backend_resource', [
- 'label' => $this->translate('Database'),
- 'description' => $this->translate('Database resource'),
- 'multiOptions' => $options,
- 'value' => $default,
- 'required' => true
+ 'label' => $this->translate('Database'),
+ 'description' => $this->translate('Database resource'),
+ 'multiOptions' => $options,
+ 'value' => $default,
+ 'required' => true
]);
}
}