diff options
Diffstat (limited to '')
-rw-r--r-- | application/clicommands/DownloadCommand.php | 98 | ||||
-rw-r--r-- | application/clicommands/ListCommand.php | 133 | ||||
-rw-r--r-- | application/clicommands/ScheduleCommand.php | 120 | ||||
-rw-r--r-- | application/controllers/ConfigController.php | 1 | ||||
-rw-r--r-- | application/controllers/ReportController.php | 216 | ||||
-rw-r--r-- | application/controllers/ReportsController.php | 95 | ||||
-rw-r--r-- | application/controllers/TemplateController.php | 107 | ||||
-rw-r--r-- | application/controllers/TemplatesController.php | 75 | ||||
-rw-r--r-- | application/controllers/TestController.php | 47 | ||||
-rw-r--r-- | application/controllers/TimeframeController.php | 41 | ||||
-rw-r--r-- | application/controllers/TimeframesController.php | 61 | ||||
-rw-r--r-- | application/forms/ConfigureMailForm.php | 1 | ||||
-rw-r--r-- | application/forms/SelectBackendForm.php | 11 |
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 ]); } } |