Merge pull request #4685 from Icinga/enhance-pdf-export
Enhance pdf export
This commit is contained in:
commit
dd7f418b48
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Web\StyleSheet;
|
||||
|
||||
|
||||
|
@ -10,6 +11,9 @@ if ($moduleName !== 'default') {
|
|||
$moduleClass = '';
|
||||
}
|
||||
|
||||
$logoPath = Icinga::app()->getBootstrapDirectory() . '/img/icinga-logo-big-dark.png';
|
||||
$logo = base64_encode(file_get_contents($logoPath));
|
||||
|
||||
|
||||
?><!DOCTYPE html>
|
||||
<html>
|
||||
|
@ -20,33 +24,21 @@ if ($moduleName !== 'default') {
|
|||
<base href="<?= $this->serverUrl() ?>">
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/php">
|
||||
// This attempt doesn't work :(
|
||||
if ( isset($pdf) )
|
||||
{
|
||||
$w = $pdf->get_width();
|
||||
$h = $pdf->get_height();
|
||||
$font = Font_Metrics::get_font("helvetica");
|
||||
$pdf->page_text($w -100, $h - 40, "Page {PAGE_NUM} of {PAGE_COUNT}", $font, 10, array(0,0,0));
|
||||
}
|
||||
|
||||
</script>
|
||||
<?= $this->img('img/icinga-logo-big-dark.png', null, array('align' => 'right', 'width' => '75')) ?>
|
||||
<!--<div id="page-header">
|
||||
<div id="header">
|
||||
<table>
|
||||
<tr>
|
||||
<td><?= $this->escape($this->title) ?></td>
|
||||
<td style="text-align: right;"></td>
|
||||
</tr>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th><?= $this->escape($this->title) ?></th>
|
||||
<td style="text-align: right;"><img width="75" src="data:image/png;base64,<?= $logo ?>"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>-->
|
||||
<h1><?= $this->escape($this->title) ?></h1>
|
||||
<div id="col1" class="container<?= $moduleClass ?>">
|
||||
<?= $this->render('inline.phtml') ?>
|
||||
</div>
|
||||
|
||||
<div id="page-footer">
|
||||
<div id="footer">
|
||||
<div class="page-number"></div>
|
||||
</div>
|
||||
<div class="<?= $moduleClass ?>">
|
||||
<?= $this->render('inline.phtml') ?>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -5,6 +5,12 @@ v2.6 to v2.8 requires to follow the instructions for v2.7 too.
|
|||
|
||||
## Upgrading to Icinga Web 2 2.10.x
|
||||
|
||||
**Deprecations**
|
||||
|
||||
* Builtin support for PDF exports using the `dompdf` library will be dropped with version 2.12.
|
||||
It is highly recommended to use [Icinga PDF Export](https://github.com/Icinga/icingaweb2-module-pdfexport)
|
||||
instead.
|
||||
|
||||
**Framework changes affecting third-party code**
|
||||
|
||||
* Asset support for modules (#3961) introduced with v2.8 has now been removed.
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
|
||||
|
||||
namespace Icinga\Common;
|
||||
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Date\DateFormatter;
|
||||
use Icinga\Exception\ConfigurationError;
|
||||
use Icinga\Module\Pdfexport\PrintableHtmlDocument;
|
||||
use Icinga\Util\Environment;
|
||||
use Icinga\Web\Controller;
|
||||
use ipl\Html\Html;
|
||||
use ipl\Html\HtmlString;
|
||||
use ipl\Html\ValidHtml;
|
||||
use ipl\Web\Compat\CompatController;
|
||||
use ipl\Web\Url;
|
||||
|
||||
trait PdfExport
|
||||
{
|
||||
/** @var string The image to show in a pdf exports page header */
|
||||
private $pdfHeaderImage = 'img/icinga-logo-big-dark.png';
|
||||
|
||||
/**
|
||||
* Export the requested action to PDF and send it
|
||||
*
|
||||
* @return never
|
||||
* @throws ConfigurationError If the pdfexport module is not available
|
||||
*/
|
||||
protected function sendAsPdf()
|
||||
{
|
||||
if (! Icinga::app()->getModuleManager()->has('pdfexport')) {
|
||||
throw new ConfigurationError('The pdfexport module is required for exports to PDF');
|
||||
}
|
||||
|
||||
putenv('ICINGAWEB_EXPORT_FORMAT=pdf');
|
||||
Environment::raiseMemoryLimit('512M');
|
||||
Environment::raiseExecutionTime(300);
|
||||
|
||||
$time = DateFormatter::formatDateTime(time());
|
||||
$iconPath = is_readable($this->pdfHeaderImage)
|
||||
? $this->pdfHeaderImage
|
||||
: Icinga::app()->getBootstrapDirectory() . '/' . $this->pdfHeaderImage;
|
||||
$encodedIcon = is_readable($iconPath) ? base64_encode(file_get_contents($iconPath)) : null;
|
||||
$html = $this instanceof CompatController && ! empty($this->content)
|
||||
? $this->content
|
||||
: $this->renderControllerAction();
|
||||
|
||||
$doc = (new PrintableHtmlDocument())
|
||||
->setTitle($this->view->title)
|
||||
->setHeader(Html::wantHtml([
|
||||
Html::tag('span', ['class' => 'title']),
|
||||
$encodedIcon
|
||||
? Html::tag('img', ['height' => 13, 'src' => 'data:image/png;base64,' . $encodedIcon])
|
||||
: null,
|
||||
Html::tag('time', null, $time)
|
||||
]))
|
||||
->setFooter(Html::wantHtml([
|
||||
Html::tag('span', null, [
|
||||
t('Page') . ' ',
|
||||
Html::tag('span', ['class' => 'pageNumber']),
|
||||
' / ',
|
||||
Html::tag('span', ['class' => 'totalPages'])
|
||||
]),
|
||||
Html::tag('p', null, Url::fromRequest()->setParams($this->params))
|
||||
]))
|
||||
->addHtml($html);
|
||||
|
||||
if (($moduleName = $this->getRequest()->getModuleName()) !== 'default') {
|
||||
$doc->getAttributes()->add('class', 'icinga-module module-' . $moduleName);
|
||||
}
|
||||
|
||||
\Icinga\Module\Pdfexport\ProvidedHook\Pdfexport::first()->streamPdfFromHtml($doc, sprintf(
|
||||
'%s-%s',
|
||||
$this->view->title ?: $this->getRequest()->getActionName(),
|
||||
$time
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the requested action
|
||||
*
|
||||
* @return ValidHtml
|
||||
*/
|
||||
protected function renderControllerAction()
|
||||
{
|
||||
/** @var Controller $this */
|
||||
$this->view->compact = true;
|
||||
|
||||
$viewRenderer = $this->getHelper('viewRenderer');
|
||||
$viewRenderer->postDispatch();
|
||||
|
||||
$layoutHelper = $this->getHelper('layout');
|
||||
$oldLayout = $layoutHelper->getLayout();
|
||||
$layout = $layoutHelper->setLayout('inline');
|
||||
|
||||
$layout->content = $this->getResponse();
|
||||
$html = $layout->render();
|
||||
|
||||
// Restore previous layout and reset content, to properly show errors
|
||||
$this->getResponse()->clearBody($viewRenderer->getResponseSegment());
|
||||
$layoutHelper->setLayout($oldLayout);
|
||||
|
||||
return HtmlString::create($html);
|
||||
}
|
||||
}
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
namespace Icinga\Web\Controller;
|
||||
|
||||
use Icinga\Application\Modules\Module;
|
||||
use Icinga\Common\PdfExport;
|
||||
use Icinga\File\Pdf;
|
||||
use ipl\I18n\Translation;
|
||||
use Zend_Controller_Action;
|
||||
use Zend_Controller_Action_HelperBroker;
|
||||
|
@ -14,7 +17,6 @@ use Icinga\Authentication\Auth;
|
|||
use Icinga\Exception\Http\HttpMethodNotAllowedException;
|
||||
use Icinga\Exception\IcingaException;
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
use Icinga\File\Pdf;
|
||||
use Icinga\Forms\AutoRefreshForm;
|
||||
use Icinga\Security\SecurityException;
|
||||
use Icinga\Web\Session;
|
||||
|
@ -41,6 +43,9 @@ use Icinga\Web\Window;
|
|||
class ActionController extends Zend_Controller_Action
|
||||
{
|
||||
use Translation;
|
||||
use PdfExport {
|
||||
sendAsPdf as private newSendAsPdf;
|
||||
}
|
||||
|
||||
/**
|
||||
* The login route to use when requiring authentication
|
||||
|
@ -522,8 +527,12 @@ class ActionController extends Zend_Controller_Action
|
|||
|
||||
protected function sendAsPdf()
|
||||
{
|
||||
$pdf = new Pdf();
|
||||
$pdf->renderControllerAction($this);
|
||||
if (Module::exists('pdfexport')) {
|
||||
$this->newSendAsPdf();
|
||||
} else {
|
||||
$pdf = new Pdf();
|
||||
$pdf->renderControllerAction($this);
|
||||
}
|
||||
}
|
||||
|
||||
protected function shutdownSession()
|
||||
|
|
|
@ -126,13 +126,13 @@ class LessCompiler
|
|||
/**
|
||||
* Set the path to the LESS theme
|
||||
*
|
||||
* @param string $theme Path to the LESS theme
|
||||
* @param ?string $theme Path to the LESS theme
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setTheme($theme)
|
||||
{
|
||||
if (is_file($theme) && is_readable($theme)) {
|
||||
if ($theme === null || (is_file($theme) && is_readable($theme))) {
|
||||
$this->theme = $theme;
|
||||
} else {
|
||||
Logger::error('Can\t load theme %s. Make sure that the theme exists and is readable', $theme);
|
||||
|
|
|
@ -187,6 +187,8 @@ class StyleSheet
|
|||
public static function forPdf()
|
||||
{
|
||||
$styleSheet = new self();
|
||||
$styleSheet->lessCompiler->setTheme(null);
|
||||
$styleSheet->lessCompiler->setThemeMode($styleSheet->pubPath . '/css/modes/none.less');
|
||||
$styleSheet->lessCompiler->addLessFile($styleSheet->pubPath . '/css/pdf/pdfprint.less');
|
||||
// TODO(el): Caching
|
||||
return $styleSheet;
|
||||
|
|
|
@ -20,6 +20,12 @@
|
|||
data-icinga-multiselect-url="<?= $this->href('monitoring/comments/show') ?>"
|
||||
data-icinga-multiselect-related="<?= $this->href("monitoring/comments") ?>"
|
||||
data-icinga-multiselect-data="comment_id">
|
||||
<thead class="print-only">
|
||||
<tr>
|
||||
<th><?= $this->translate('Type') ?></th>
|
||||
<th><?= $this->translate('Comment') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($comments->peekAhead($this->compact) as $comment): ?>
|
||||
<tr href="<?= $this->href('monitoring/comment/show', array('comment_id' => $comment->id)) ?>">
|
||||
|
@ -40,7 +46,7 @@
|
|||
</tbody>
|
||||
</table>
|
||||
<?php if ($comments->hasMore()): ?>
|
||||
<div class="action-links">
|
||||
<div class="dont-print action-links">
|
||||
<?= $this->qlink(
|
||||
$this->translate('Show More'),
|
||||
$this->url()->without(array('showCompact', 'limit')),
|
||||
|
|
|
@ -68,7 +68,7 @@
|
|||
</tbody>
|
||||
</table>
|
||||
<?php if ($contacts->hasMore()): ?>
|
||||
<div class="action-links">
|
||||
<div class="dont-print action-links">
|
||||
<?= $this->qlink(
|
||||
$this->translate('Show More'),
|
||||
$this->url()->without(array('showCompact', 'limit')),
|
||||
|
|
|
@ -24,6 +24,12 @@ if (! $this->compact): ?>
|
|||
data-icinga-multiselect-url="<?= $this->href('monitoring/downtimes/show') ?>"
|
||||
data-icinga-multiselect-controllers="<?= $this->href("monitoring/downtimes") ?>"
|
||||
data-icinga-multiselect-data="downtime_id">
|
||||
<thead class="print-only">
|
||||
<tr>
|
||||
<th><?= $this->translate('State') ?></th>
|
||||
<th><?= $this->translate('Downtime') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($downtimes->peekAhead($this->compact) as $downtime):
|
||||
if (isset($downtime->service_description)) {
|
||||
|
@ -43,7 +49,7 @@ if (! $this->compact): ?>
|
|||
</tbody>
|
||||
</table>
|
||||
<?php if ($downtimes->hasMore()): ?>
|
||||
<div class="action-links">
|
||||
<div class="dont-print action-links">
|
||||
<?= $this->qlink(
|
||||
$this->translate('Show More'),
|
||||
$this->url()->without(array('showCompact', 'limit')),
|
||||
|
|
|
@ -281,7 +281,7 @@ if (! $this->compact): ?>
|
|||
</tbody>
|
||||
</table>
|
||||
<?php if ($hostGroups->hasMore()): ?>
|
||||
<div class="action-links">
|
||||
<div class="dont-print action-links">
|
||||
<?= $this->qlink(
|
||||
$this->translate('Show More'),
|
||||
$this->url()->without(array('showCompact', 'limit')),
|
||||
|
|
|
@ -23,6 +23,15 @@ if (! $this->compact): ?>
|
|||
data-icinga-multiselect-url="<?= $this->href('monitoring/hosts/show') ?>"
|
||||
data-icinga-multiselect-controllers="<?= $this->href("monitoring/hosts") ?>"
|
||||
data-icinga-multiselect-data="host">
|
||||
<thead class="print-only">
|
||||
<tr>
|
||||
<th><?= $this->translate('State') ?></th>
|
||||
<th><?= $this->translate('Host') ?></th>
|
||||
<?php foreach($this->addColumns as $col): ?>
|
||||
<th><?= $this->escape($col) ?></th>
|
||||
<?php endforeach ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($hosts->peekAhead($this->compact) as $host):
|
||||
$hostStateName = Host::getStateText($host->host_state);
|
||||
|
@ -76,7 +85,7 @@ if (! $this->compact): ?>
|
|||
</tbody>
|
||||
</table>
|
||||
<?php if ($hosts->hasMore()): ?>
|
||||
<div class="action-links">
|
||||
<div class="dont-print action-links">
|
||||
<?= $this->qlink(
|
||||
$this->translate('Show More'),
|
||||
$this->url()->without(array('showCompact', 'limit')),
|
||||
|
|
|
@ -169,7 +169,7 @@ if (! $this->compact): ?>
|
|||
</tbody>
|
||||
</table>
|
||||
<?php if ($serviceGroups->hasMore()): ?>
|
||||
<div class="action-links">
|
||||
<div class="dont-print action-links">
|
||||
<?= $this->qlink(
|
||||
$this->translate('Show More'),
|
||||
$this->url()->without(array('showCompact', 'limit')),
|
||||
|
|
|
@ -24,6 +24,15 @@ if (! $this->compact): ?>
|
|||
data-icinga-multiselect-url="<?= $this->href('monitoring/services/show') ?>"
|
||||
data-icinga-multiselect-controllers="<?= $this->href('monitoring/services') ?>"
|
||||
data-icinga-multiselect-data="service,host">
|
||||
<thead class="print-only">
|
||||
<tr>
|
||||
<th><?= $this->translate('State') ?></th>
|
||||
<th><?= $this->translate('Service') ?></th>
|
||||
<?php foreach($this->addColumns as $col): ?>
|
||||
<th><?= $this->escape($col) ?></th>
|
||||
<?php endforeach ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($services->peekAhead($this->compact) as $service):
|
||||
$serviceLink = $this->href(
|
||||
|
@ -131,7 +140,7 @@ if (! $this->compact): ?>
|
|||
</tbody>
|
||||
</table>
|
||||
<?php if ($services->hasMore()): ?>
|
||||
<div class="action-links">
|
||||
<div class="dont-print action-links">
|
||||
<?= $this->qlink(
|
||||
$this->translate('Show More'),
|
||||
$this->url()->without(array('showCompact', 'limit')),
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
$textId = 'comment-' . $uniqId;
|
||||
$deleteButton = clone $delCommentForm;
|
||||
/** @var \Icinga\Module\Monitoring\Forms\Command\Object\DeleteCommentCommandForm $deleteButton */
|
||||
$deleteButton->setAttrib('class', $deleteButton->getAttrib('class') . ' remove-action');
|
||||
$deleteButton->setAttrib('class', $deleteButton->getAttrib('class') . ' remove-action dont-print');
|
||||
$deleteButton->populate(
|
||||
array(
|
||||
'comment_id' => $comment->id,
|
||||
|
|
|
@ -81,7 +81,7 @@
|
|||
$textId = 'downtime-' . $uniqId;
|
||||
$deleteButton = clone $delDowntimeForm;
|
||||
/** @var \Icinga\Module\Monitoring\Forms\Command\Object\DeleteDowntimeCommandForm $deleteButton */
|
||||
$deleteButton->setAttrib('class', $deleteButton->getAttrib('class') . ' remove-action');
|
||||
$deleteButton->setAttrib('class', $deleteButton->getAttrib('class') . ' remove-action dont-print');
|
||||
$deleteButton->populate(
|
||||
array(
|
||||
'downtime_id' => $downtime->id,
|
||||
|
|
|
@ -1,31 +1,15 @@
|
|||
/*! Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
// Print styles
|
||||
/*! Icinga Web 2 | (c) 2015 Icinga GmbH | GPLv2+ */
|
||||
|
||||
@media print {
|
||||
#fileupload-frame-target,
|
||||
#header,
|
||||
#responsive-debug,
|
||||
#sidebar,
|
||||
.controls > .tabs,
|
||||
.controls .filter,
|
||||
.controls .limiter-control,
|
||||
.controls .pagination-control,
|
||||
.controls .selection-info,
|
||||
.controls .sort-control,
|
||||
.controls,
|
||||
.dontprint, // Compat only, use dont-print instead
|
||||
.dont-print {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#layout,
|
||||
#main,
|
||||
.controls {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.container {
|
||||
float: none !important;
|
||||
width: 100% !important;
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media not print {
|
||||
.print-only {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,71 +1,75 @@
|
|||
/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
|
||||
/*! Icinga Web 2 | (c) 2014 Icinga GmbH | GPLv2+ */
|
||||
|
||||
.controls,
|
||||
.dontprint, // Compat only, use dont-print instead
|
||||
.dont-print {
|
||||
display: none !important;
|
||||
}
|
||||
// Ensure styling is light, exports use a white background
|
||||
|
||||
table.action img.inlinepie {
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
}
|
||||
@gray: #7F7F7F;
|
||||
@gray-semilight: #A9A9A9;
|
||||
@gray-light: #C9C9C9;
|
||||
@gray-lighter: #EEEEEE;
|
||||
@gray-lightest: #F7F7F7;
|
||||
@icinga-blue: #0095BF;
|
||||
@low-sat-blue: #dae3e6;
|
||||
@low-sat-blue-dark: #becbcf;
|
||||
@body-bg-color: #fff;
|
||||
@text-color: @black;
|
||||
@text-color-light: @gray;
|
||||
@tr-active-color: @body-bg-color;
|
||||
@tr-hover-color: @body-bg-color;
|
||||
|
||||
// Page layout
|
||||
|
||||
@page {
|
||||
margin: 2cm;
|
||||
margin: 1cm;
|
||||
}
|
||||
|
||||
.container * {
|
||||
font-size: 7pt;
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
margin: 0;
|
||||
padding-top: 37px; // ~ logo height in the header
|
||||
}
|
||||
|
||||
body > h1 {
|
||||
font-size: 1em;
|
||||
.content {
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
#page-header, #page-footer {
|
||||
font-family: fixed;
|
||||
font-weight: bold;
|
||||
#header,
|
||||
#footer {
|
||||
position: fixed;
|
||||
left: 1em;
|
||||
right: 1em;
|
||||
left: 0;
|
||||
right: 0;
|
||||
color: #aaa;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
#page-header table, #page-footer table {
|
||||
#header {
|
||||
top: 0;
|
||||
border-bottom: 0.1pt solid #aaa;
|
||||
|
||||
img {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
#footer {
|
||||
bottom: 0;
|
||||
border-top: 0.1pt solid #aaa;
|
||||
}
|
||||
|
||||
#header table,
|
||||
#footer table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#page-header table {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
#page-header td, #page-footer td {
|
||||
font-family: fixed;
|
||||
#header td,
|
||||
#header th,
|
||||
#footer td,
|
||||
#footer th {
|
||||
padding: 0;
|
||||
color: #aaa;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
#page-header {
|
||||
top: 0.5em;
|
||||
border-bottom: 0.2pt solid #aaa;
|
||||
}
|
||||
|
||||
#page-footer {
|
||||
bottom: 2.5em;
|
||||
border-top: 0.2pt solid #aaa;
|
||||
}
|
||||
|
||||
@page {
|
||||
@bottom-right {
|
||||
content: counter(page) " of " counter(pages);
|
||||
}
|
||||
}
|
||||
|
||||
.page-number {
|
||||
text-align: center;
|
||||
}
|
||||
|
@ -79,19 +83,10 @@ hr {
|
|||
border: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: 0;
|
||||
}
|
||||
// General style
|
||||
|
||||
.dashboard > div.container {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
h1 form {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 1cm 1cm 1.5cm 1cm;
|
||||
.controls,
|
||||
.dontprint, // Compat only, use dont-print instead
|
||||
.dont-print {
|
||||
display: none !important;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue