N°1370 portal : add charts capacity to the ManageBrick (restore 2018-04-10 revisions : r5646)

SVN:trunk[5635]
This commit is contained in:
Pierre Goiffon
2018-04-12 08:55:16 +00:00
parent e15bad7d3b
commit d6e7309c34
21 changed files with 2801 additions and 1601 deletions

View File

@@ -103,6 +103,17 @@ Dict::Add('EN US', 'English', 'English', array(
'Brick:Portal:Manage:Name' => 'Manage items',
'Brick:Portal:Manage:Table:NoData' => 'No item.',
'Brick:Portal:Manage:Table:ItemActions' => 'Actions',
'Brick:Portal:Manage:DisplayType:pie-chart' => 'Pie Chart',
'Brick:Portal:Manage:DisplayType:bar-chart' => 'Bar Chart',
'Brick:Portal:Manage:DisplayType:default' => 'List',
'Brick:Portal:Manage:Others' => 'Others',
'Brick:Portal:Manage:All' => 'All',
'Brick:Portal:Manage:Group' => 'Group',
'Brick:Portal:Manage:fct:count' => 'Total',
'Brick:Portal:Manage:fct:sum' => 'Sum',
'Brick:Portal:Manage:fct:avg' => 'Average',
'Brick:Portal:Manage:fct:min' => 'Min',
'Brick:Portal:Manage:fct:max' => 'Max',
));
// ObjectBrick brick

View File

@@ -103,6 +103,17 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Brick:Portal:Manage:Name' => 'Gestion d\'éléments',
'Brick:Portal:Manage:Table:NoData' => 'Aucun élément',
'Brick:Portal:Manage:Table:ItemActions' => 'Actions',
'Brick:Portal:Manage:DisplayType:pie-chart' => 'Secteur',
'Brick:Portal:Manage:DisplayType:bar-chart' => 'Histogramme',
'Brick:Portal:Manage:DisplayType:default' => 'Liste',
'Brick:Portal:Manage:Others' => 'Autres',
'Brick:Portal:Manage:All' => 'Total',
'Brick:Portal:Manage:Group' => 'Groupe',
'Brick:Portal:Manage:fct:count' => 'Total',
'Brick:Portal:Manage:fct:sum' => 'Somme',
'Brick:Portal:Manage:fct:avg' => 'Moyenne',
'Brick:Portal:Manage:fct:min' => 'Min',
'Brick:Portal:Manage:fct:max' => 'Max',
));
// ObjectBrick brick

View File

@@ -13,11 +13,12 @@ SetupWebPage::AddModule(
'visible' => true,
// Components
'datamodel' => array(
'portal/src/entities/abstractbrick.class.inc.php',
'portal/src/entities/portalbrick.class.inc.php',
'portal/src/apis/extensions/d3portaluiextension.class.inc.php',
'portal/src/controllers/abstractcontroller.class.inc.php',
'portal/src/controllers/brickcontroller.class.inc.php',
'portal/src/routers/abstractrouter.class.inc.php',
'portal/src/entities/abstractbrick.class.inc.php',
'portal/src/entities/portalbrick.class.inc.php',
'portal/src/routers/abstractrouter.class.inc.php',
),
'webservice' => array(
//'webservices.itop-portal-base.php',

View File

@@ -0,0 +1,44 @@
<?php
// Copyright (c) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
//
namespace Combodo\iTop\Portal\API\Extension;
use AbstractPortalUIExtension;
use Silex\Application;
use utils;
class D3PortalUIExtension extends AbstractPortalUIExtension
{
public function GetCSSFiles(Application $oApp)
{
$aCSSFiles = array(
utils::GetAbsoluteUrlAppRoot().'css/c3.min.css?v='.ITOP_VERSION,
);
return $aCSSFiles;
}
public function GetJSFiles(Application $oApp)
{
$aJSFiles = array(
utils::GetAbsoluteUrlAppRoot().'js/d3.min.js?v='.ITOP_VERSION,
utils::GetAbsoluteUrlAppRoot().'js/c3.min.js?v='.ITOP_VERSION,
utils::GetCurrentModuleUrl().'/portal/web/js/export.js?v='.ITOP_VERSION,
);
return $aJSFiles;
}
}

View File

@@ -4,7 +4,7 @@
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
@@ -16,53 +16,97 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
//
namespace Combodo\iTop\Portal\Brick;
use DOMFormatException;
use DBSearch;
use MetaModel;
use Combodo\iTop\DesignElement;
use Combodo\iTop\Portal\Brick\PortalBrick;
use DBSearch;
use DOMFormatException;
use MetaModel;
define('MANAGE_BRICK_LAYOUT_PATH', 'itop-portal-base/portal/src/views/bricks/manage/');
/**
* Description of ManageBrick
*
* @author Guillaume Lajarige
*/
class ManageBrick extends PortalBrick
{
const ENUM_ACTION_VIEW = 'view';
const ENUM_ACTION_EDIT = 'edit';
const ENUM_ACTION_VIEW = 'view';
const ENUM_ACTION_EDIT = 'edit';
const DEFAULT_DECORATION_CLASS_HOME = 'fa fa-pencil-square';
const DEFAULT_DECORATION_CLASS_NAVIGATION_MENU = 'fa fa-pencil-square fa-2x';
const DEFAULT_PAGE_TEMPLATE_PATH = 'itop-portal-base/portal/src/views/bricks/manage/layout.html.twig';
const DEFAULT_PAGE_TEMPLATE_PATH = 'itop-portal-base/portal/src/views/bricks/manage/layout-table.html.twig';
const CHART_PAGE_TEMPLATE_PATH = 'itop-portal-base/portal/src/views/bricks/manage/layout-chart.html.twig';
const DEFAULT_OQL = '';
const DEFAULT_OPENING_MODE = self::ENUM_ACTION_EDIT;
const DEFAULT_DATA_LOADING = self::ENUM_DATA_LOADING_LAZY;
const DEFAULT_LIST_LENGTH = 20;
const DEFAULT_ZLIST_FIELDS = 'list';
const DEFAULT_SHOW_TAB_COUNTS = false;
const DEFAULT_SHOW_TAB_COUNTS = false;
const DEFAULT_TILE_TEMPLATE_PATH = 'itop-portal-base/portal/src/views/bricks/manage/tile-default.html.twig';
const DEFAULT_TILE_CONTROLLER_ACTION = 'Combodo\\iTop\\Portal\\Controller\\ManageBrickController::TileAction';
static $sRouteName = 'p_manage_brick';
protected $sOql;
protected $sOpeningMode;
protected $aGrouping;
protected $aFields;
protected $aExportFields;
protected $bShowTabCounts;
protected $sDisplayType;
protected $iGroupLimit;
protected $bGroupShowOthers;
protected $aPresentationData = array(
'badge' => array(
'decorationCssClass' => 'fa fa-id-card-o fa-2x',
'tileTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/tile-badge.html.twig',
'layoutTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/layout-table.html.twig',
'layoutDisplayType' => 'default',
'need_details' => true,
),
'top-list' => array(
'decorationCssClass' => 'fa fa-signal fa-rotate-270 fa-2x',
'tileTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/tile-top-list.html.twig',
'layoutTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/layout-table.html.twig',
'layoutDisplayType' => 'default',
'need_details' => true,
),
'pie-chart' => array(
'decorationCssClass' => 'fa fa-pie-chart fa-2x',
'tileTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/tile-chart.html.twig',
'layoutTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/layout-chart.html.twig',
'layoutDisplayType' => 'pie-chart',
'need_details' => false,
),
'bar-chart' => array(
'decorationCssClass' => 'fa fa-bar-chart fa-2x',
'tileTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/tile-chart.html.twig',
'layoutTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/layout-chart.html.twig',
'layoutDisplayType' => 'bar-chart',
'need_details' => false,
),
'default' => array(
'decorationCssClass' => 'fa fa-pencil-square fa-2x',
'tileTemplate' => self::DEFAULT_TILE_TEMPLATE_PATH,
'layoutTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/layout-table.html.twig',
'layoutDisplayType' => 'default',
'need_details' => true,
),
);
public function __construct()
{
parent::__construct();
$this->sOql = static::DEFAULT_OQL;
$this->sOpeningMode = static::DEFAULT_OPENING_MODE;
$this->sOpeningMode = static::DEFAULT_OPENING_MODE;
$this->aGrouping = array();
$this->aFields = array();
$this->aExportFields = array();
$this->bShowTabCounts = static::DEFAULT_SHOW_TAB_COUNTS;
// This is hardcoded for now, we might allow area grouping on another attribute in the futur
// This is hardcoded for now, we might allow area grouping on another attribute in the future
$this->AddGrouping('areas', array('attribute' => 'finalclass'));
}
@@ -76,15 +120,15 @@ class ManageBrick extends PortalBrick
return $this->sOql;
}
/**
* Returns the brick's objects opening mode (edit or view)
*
* @return string
*/
public function GetOpeningMode()
{
return $this->sOpeningMode;
}
/**
* Returns the brick's objects opening mode (edit or view)
*
* @return string
*/
public function GetOpeningMode()
{
return $this->sOpeningMode;
}
/**
* Returns the brick grouping
@@ -106,39 +150,114 @@ class ManageBrick extends PortalBrick
return $this->aFields;
}
/**
* Returns if the brick should display objects count on tabs
*
* @return bool
*/
/**
* Returns the brick fields to export
*
* @return array
*/
public function GetExportFields()
{
return $this->aExportFields;
}
/**
* Returns if the brick should display objects count on tabs
*
* @return bool
*/
public function GetShowTabCounts()
{
return $this->bShowTabCounts;
}
{
return $this->bShowTabCounts;
}
/**
* @return string
*/
public function GetDisplayType()
{
return $this->sDisplayType;
}
/**
* @param string $sDisplayType
*/
public function SetDisplayType($sDisplayType)
{
$this->sDisplayType = $sDisplayType;
}
/**
* @param string $sDisplayType
*
* @return string[] parameters for specified type, default parameters if type is invalid
*/
public function GetPresentationDataForDisplayType($sDisplayType)
{
if (isset($this->aPresentationData[$sDisplayType]))
{
return $this->aPresentationData[$sDisplayType];
}
return $this->aPresentationData['default'];
}
/**
* @return mixed
*/
public function GetGroupLimit()
{
return $this->iGroupLimit;
}
/**
* @return mixed
*/
public function ShowGroupOthers()
{
return $this->bGroupShowOthers;
}
/**
* @return array
*/
public function ListLayoutDisplayTypes()
{
$aLayoutDisplayTypes = array();
foreach ($this->aPresentationData as $aPresentationDatum)
{
$aLayoutDisplayTypes[$aPresentationDatum['layoutDisplayType']] = true;
}
return array_keys($aLayoutDisplayTypes);
}
/**
* Sets the oql of the brick
*
* @param string $sOql
* @return \Combodo\iTop\Portal\Brick\ManageBrick
*
* @return ManageBrick
*/
public function SetOql($sOql)
{
$this->sOql = $sOql;
return $this;
}
/**
* Sets the brick's objects opening mode
*
* @param string $sOpeningMode
* @return \Combodo\iTop\Portal\Brick\ManageBrick
*/
public function SetOpeningMode($sOpeningMode)
{
$this->sOpeningMode = $sOpeningMode;
return $this;
}
/**
* Sets the brick's objects opening mode
*
* @param string $sOpeningMode
*
* @return ManageBrick
*/
public function SetOpeningMode($sOpeningMode)
{
$this->sOpeningMode = $sOpeningMode;
return $this;
}
/**
* Sets the grouping of the brick
@@ -148,6 +267,7 @@ class ManageBrick extends PortalBrick
public function SetGrouping($aGrouping)
{
$this->aGrouping = $aGrouping;
return $this;
}
@@ -159,20 +279,23 @@ class ManageBrick extends PortalBrick
public function SetFields($aFields)
{
$this->aFields = $aFields;
return $this;
}
/**
* Sets if the brick should display objects count on tab
*
* @param bool $bShowTabCounts
* @return \Combodo\iTop\Portal\Brick\ManageBrick
*/
public function SetShowTabCounts($bShowTabCounts)
{
$this->bShowTabCounts = $bShowTabCounts;
return $this;
}
/**
* Sets if the brick should display objects count on tab
*
* @param bool $bShowTabCounts
*
* @return ManageBrick
*/
public function SetShowTabCounts($bShowTabCounts)
{
$this->bShowTabCounts = $bShowTabCounts;
return $this;
}
/**
* Adds a grouping.
@@ -181,7 +304,8 @@ class ManageBrick extends PortalBrick
*
* @param string $sName (Must be "tabs" or -Not implemented yet, implicit grouping on y axis-)
* @param array $aGrouping
* @return \Combodo\iTop\Portal\Brick\ManageBrick
*
* @return ManageBrick
*/
public function AddGrouping($sName, $aGrouping)
{
@@ -190,8 +314,7 @@ class ManageBrick extends PortalBrick
// Sorting
if (!$this->IsGroupingByDistinctValues($sName))
{
usort($this->aGrouping[$sName]['groups'], function($a, $b)
{
usort($this->aGrouping[$sName]['groups'], function ($a, $b) {
return $a['rank'] > $b['rank'];
});
}
@@ -203,7 +326,8 @@ class ManageBrick extends PortalBrick
* Removes a grouping by its name
*
* @param string $sName
* @return \Combodo\iTop\Portal\Brick\ManageBrick
*
* @return ManageBrick
*/
public function RemoveGrouping($sName)
{
@@ -211,6 +335,7 @@ class ManageBrick extends PortalBrick
{
unset($this->aGrouping[$sName]);
}
return $this;
}
@@ -218,7 +343,8 @@ class ManageBrick extends PortalBrick
* Adds a field to display from its attribute_code.
*
* @param string $sAttCode
* @return \Combodo\iTop\Portal\Brick\ManageBrick
*
* @return ManageBrick
*/
public function AddField($sAttCode)
{
@@ -234,7 +360,8 @@ class ManageBrick extends PortalBrick
* Removes a field
*
* @param string $sAttCode
* @return \Combodo\iTop\Portal\Brick\ManageBrick
*
* @return ManageBrick
*/
public function RemoveField($sAttCode)
{
@@ -242,9 +369,31 @@ class ManageBrick extends PortalBrick
{
unset($this->aFields[$sAttCode]);
}
return $this;
}
public function AddExportField($sAttCode)
{
if (!in_array($sAttCode, $this->aExportFields))
{
$this->aExportFields[] = $sAttCode;
}
return $this;
}
public function RemoveExportField($sAttCode)
{
if (isset($this->aExportFields[$sAttCode]))
{
unset($this->aExportFields[$sAttCode]);
}
return $this;
}
/**
* Returns if the brick has grouping tabs or not.
*
@@ -290,6 +439,7 @@ class ManageBrick extends PortalBrick
* This is supposed to be called by the IsGroupingTabsByDistinctValues / IsGroupingAreasByDistinctValues function.
*
* @param string $sGroupingName
*
* @return boolean
*/
public function IsGroupingByDistinctValues($sGroupingName)
@@ -299,7 +449,8 @@ class ManageBrick extends PortalBrick
/**
* Returns true is the groupings tabs properties exists and is of the form attribute => attribute_code.
* This is mostly used to know if the tabs are grouped by attribute distinct values or by meta-groups (eg : status in ('accepted', 'opened')).
* This is mostly used to know if the tabs are grouped by attribute distinct values or by meta-groups (eg : status
* in ('accepted', 'opened')).
*
* @return boolean
*/
@@ -310,7 +461,8 @@ class ManageBrick extends PortalBrick
/**
* Returns true is the groupings areas properties exists and is of the form attribute => attribute_code.
* This is mostly used to know if the areas are grouped by attribute distinct values or by meta-groups (eg : finalclass in ('Server', 'Router')).
* This is mostly used to know if the areas are grouped by attribute distinct values or by meta-groups (eg :
* finalclass in ('Server', 'Router')).
*
* @return boolean
*/
@@ -324,11 +476,18 @@ class ManageBrick extends PortalBrick
* This is used to set all the brick attributes at once.
*
* @param \Combodo\iTop\DesignElement $oMDElement
*
* @return ManageBrick
* @throws DOMFormatException
* @throws \OQLException
*/
public function LoadFromXml(DesignElement $oMDElement)
{
parent::LoadFromXml($oMDElement);
$this->sDisplayType = 'default';
$this->iGroupLimit = 0;
$this->bGroupShowOthers = true;
$bUseListFieldsForExport = false;
// Checking specific elements
foreach ($oMDElement->GetNodes('./*') as $oBrickSubNode)
@@ -339,53 +498,90 @@ class ManageBrick extends PortalBrick
$sClass = $oBrickSubNode->GetText();
if ($sClass === '')
{
throw new DOMFormatException('ManageBrick : class tag is empty. Must contain Classname', null, null, $oBrickSubNode);
throw new DOMFormatException('ManageBrick : class tag is empty. Must contain Classname', null,
null, $oBrickSubNode);
}
$this->SetOql('SELECT ' . $sClass);
$this->SetOql('SELECT '.$sClass);
break;
case 'oql':
$sOql = $oBrickSubNode->GetText();
if ($sOql === '')
{
throw new DOMFormatException('ManageBrick : oql tag is empty. Must contain OQL statement', null, null, $oBrickSubNode);
throw new DOMFormatException('ManageBrick : oql tag is empty. Must contain OQL statement', null,
null, $oBrickSubNode);
}
$this->SetOql($sOql);
break;
case 'opening_mode':
$sOpeningMode = $oBrickSubNode->GetText(static::DEFAULT_OPENING_MODE);
if (!in_array($sOpeningMode, array(static::ENUM_ACTION_VIEW, static::ENUM_ACTION_EDIT)))
{
throw new DOMFormatException('ManageBrick : opening_mode tag value must be edit|view ("' . $sOpeningMode . '" given)', null, null, $oBrickSubNode);
}
case 'opening_mode':
$sOpeningMode = $oBrickSubNode->GetText(static::DEFAULT_OPENING_MODE);
if (!in_array($sOpeningMode, array(static::ENUM_ACTION_VIEW, static::ENUM_ACTION_EDIT)))
{
throw new DOMFormatException('ManageBrick : opening_mode tag value must be edit|view ("'.$sOpeningMode.'" given)',
null, null, $oBrickSubNode);
}
$this->SetOpeningMode($sOpeningMode);
break;
$this->SetOpeningMode($sOpeningMode);
break;
case 'display_type':
$this->sDisplayType = $oBrickSubNode->GetText();
$aDisplayParameterForType = $this->GetPresentationDataForDisplayType($this->sDisplayType);
$this->SetTileTemplatePath($aDisplayParameterForType['tileTemplate']);
$this->SetPageTemplatePath($aDisplayParameterForType['layoutTemplate']);
break;
case 'fields':
foreach ($oBrickSubNode->GetNodes('./field') as $oFieldNode)
{
if (!$oFieldNode->hasAttribute('id'))
{
throw new DOMFormatException('ManageBrick : Field must have a unique ID attribute', null, null, $oFieldNode);
throw new DOMFormatException('ManageBrick : Field must have a unique ID attribute', null,
null, $oFieldNode);
}
$this->AddField($oFieldNode->getAttribute('id'));
}
break;
case 'export':
foreach ($oBrickSubNode->GetNodes('./*') as $oExportNode)
{
switch ($oExportNode->nodeName)
{
case 'fields':
foreach ($oExportNode->GetNodes('./field') as $oFieldNode)
{
if (!$oFieldNode->hasAttribute('id'))
{
throw new DOMFormatException('ManageBrick : Field must have a unique ID attribute',
null,
null, $oFieldNode);
}
$this->AddExportField($oFieldNode->getAttribute('id'));
}
break;
case 'export_default_fields':
$bUseListFieldsForExport = (strtolower($oExportNode->GetText()) === 'true' ? true : false);
break;
}
}
break;
case 'grouping':
// Tabs grouping
foreach ($oBrickSubNode->GetNodes('./tabs/*') as $oGroupingNode)
{
switch ($oGroupingNode->nodeName)
{
case 'show_tab_counts';
$bShowTabCounts = ( $oGroupingNode->GetText(static::DEFAULT_SHOW_TAB_COUNTS) === 'true' ) ? true : false;
$this->SetShowTabCounts($bShowTabCounts);
break;
case 'show_tab_counts';
$bShowTabCounts = ($oGroupingNode->GetText(static::DEFAULT_SHOW_TAB_COUNTS) === 'true') ? true : false;
$this->SetShowTabCounts($bShowTabCounts);
break;
case 'attribute':
$sAttribute = $oGroupingNode->GetText();
if ($sAttribute !== '')
@@ -393,13 +589,24 @@ class ManageBrick extends PortalBrick
$this->AddGrouping('tabs', array('attribute' => $sAttribute));
}
break;
case 'limit':
$iLimit = $oGroupingNode->GetText();
if (is_numeric($iLimit))
{
$this->iGroupLimit = $iLimit;
}
break;
case 'show_others':
$this->bGroupShowOthers = ($oGroupingNode->GetText() === 'true') ? true : false;
break;
case 'groups':
$aGroups = array();
foreach ($oGroupingNode->GetNodes('./group') as $oGroupNode)
{
if (!$oGroupNode->hasAttribute('id'))
{
throw new DOMFormatException('ManageBrick : Group must have a unique ID attribute', null, null, $oGroupNode);
throw new DOMFormatException('ManageBrick : Group must have a unique ID attribute',
null, null, $oGroupNode);
}
$sGroupId = $oGroupNode->getAttribute('id');
@@ -410,7 +617,7 @@ class ManageBrick extends PortalBrick
switch ($oGroupProperty->nodeName)
{
case 'rank':
$aGroup[$oGroupProperty->nodeName] = (int) $oGroupProperty->GetText(0);
$aGroup[$oGroupProperty->nodeName] = (int)$oGroupProperty->GetText(0);
break;
case 'title':
case 'condition':
@@ -422,11 +629,13 @@ class ManageBrick extends PortalBrick
// Checking constitancy
if (!isset($aGroup['title']) || $aGroup['title'] === '')
{
throw new DOMFormatException('ManageBrick : Group must have a title tag and it must not be empty', null, null, $oGroupNode);
throw new DOMFormatException('ManageBrick : Group must have a title tag and it must not be empty',
null, null, $oGroupNode);
}
if (!isset($aGroup['condition']) || $aGroup['condition'] === '')
{
throw new DOMFormatException('ManageBrick : Group must have a condition tag and it must not be empty', null, null, $oGroupNode);
throw new DOMFormatException('ManageBrick : Group must have a condition tag and it must not be empty',
null, null, $oGroupNode);
}
$aGroups[] = $aGroup;
}
@@ -448,11 +657,42 @@ class ManageBrick extends PortalBrick
if (empty($this->aFields))
{
$sClass = DBSearch::FromOQL($this->GetOql());
$aFields = MetaModel::FlattenZList(MetaModel::GetZListItems($sClass->GetClass(), static::DEFAULT_ZLIST_FIELDS));
$aFields = MetaModel::FlattenZList(MetaModel::GetZListItems($sClass->GetClass(),
static::DEFAULT_ZLIST_FIELDS));
$this->SetFields($aFields);
}
// Default Export Fields
if ($bUseListFieldsForExport)
{
foreach ($this->GetFields() as $sAttCode)
{
$this->AddExportField($sAttCode);
}
}
// Checking the navigation icon
$sDecorationClassNavigationMenu = $this->GetDecorationClassNavigationMenu();
if (empty($sDecorationClassNavigationMenu) && isset($this->aPresentationData[$this->sDisplayType]))
{
$sDecorationClassNavigationMenu = $this->aPresentationData[$this->sDisplayType]['decorationCssClass'];
if (!empty($sDecorationClassNavigationMenu))
{
$this->SetDecorationClassNavigationMenu($sDecorationClassNavigationMenu);
}
}
$sTitle = $this->GetTitleHome();
if (empty($sTitle))
{
$sOql = $this->GetOql();
$oSeach = DBSearch::FromOQL($sOql);
$sClassName = MetaModel::GetName($oSeach->GetClass());
$this->SetTitleHome($sClassName);
$this->SetTitleNavigationMenu($sClassName);
$this->SetTitle($sClassName);
}
return $this;
}
}

View File

@@ -1,6 +1,6 @@
<?php
// Copyright (C) 2010-2017 Combodo SARL
// Copyright (C) 2010-2018 Combodo SARL
//
// This file is part of iTop.
//
@@ -19,17 +19,23 @@
namespace Combodo\iTop\Portal\Router;
use Silex\Application;
class ManageBrickRouter extends AbstractRouter
{
static $aRoutes = array(
array('pattern' => '/manage/{sBrickId}/{sGroupingTab}',
array(
'pattern' => '/manage/{sBrickId}/{sDisplayType}/{sGroupingTab}',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\ManageBrickController::DisplayAction',
'bind' => 'p_manage_brick',
'values' => array('sGroupingTab' => null)
'asserts' => array(
'sDisplayType' => 'badge|pie-chart|bar-chart|top-list|default'
),
'values' => array(
'sDisplayType' => null, // will be set using brick's XML config
'sGroupingTab' => null
)
),
array('pattern' => '/manage/{sBrickId}/{sGroupingTab}/{sGroupingArea}/page/{iPageNumber}/show/{iListLength}',
array(
'pattern' => '/manage/{sBrickId}/{sGroupingTab}/{sGroupingArea}/page/{iPageNumber}/show/{iListLength}',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\ManageBrickController::DisplayAction',
'bind' => 'p_manage_brick_lazy',
'asserts' => array(
@@ -41,7 +47,14 @@ class ManageBrickRouter extends AbstractRouter
'iPageNumber' => '1',
'iListLength' => '20'
)
)
),
array(
'pattern' => '/manage/export/excel/start/{sBrickId}/{sGroupingTab}/{sGroupingArea}',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\ManageBrickController::ExcelExportStartAction',
'bind' => 'p_manage_brick_excel_export_start',
'asserts' => array(),
'values' => array(),
),
);
}

View File

@@ -0,0 +1,15 @@
{# itop-portal-base/portal/src/views/bricks/manage/layout-chart.html.twig #}
{# Manage brick base layout #}
{% extends 'itop-portal-base/portal/src/views/bricks/manage/layout.html.twig' %}
{% block pPageBodyClass %}{{ parent() }} page_manage_brick{% endblock %}
{% block pMainContentHolder %}
<div class="panel panel-default">
<div class="panel-body">
{% include 'itop-portal-base/portal/src/views/bricks/manage/mode-' ~ sDisplayType ~ '.html.twig' with {'oBrick': oBrick, 'aColumns': aColumns, 'aNames': aNames, 'aUrls': aUrls, 'aDisplayValues': aDisplayValues} %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,339 @@
{# itop-portal-base/portal/src/views/bricks/manage/layout-table.html.twig #}
{# Manage brick base layout #}
{% extends 'itop-portal-base/portal/src/views/bricks/manage/layout.html.twig' %}
{% block pPageBodyClass %}{{ parent() }} page_manage_brick{% endblock %}
{% block pMainContentHolder %}
{% if aGroupingTabsValues|length > 1 %}
<ul class="nav nav-pills">
{% for aGroupingTab in aGroupingTabsValues %}
<li{% if sGroupingTab is defined and sGroupingTab == aGroupingTab.value %} class="active"{% endif %}>
<a href="{{ app.url_generator.generate('p_manage_brick', {'sBrickId': sBrickId, 'sDisplayType': 'default', 'sGroupingTab': aGroupingTab.value}) }}"
id="btn_tab_for_{{ aGroupingTab.value }}">
{{ aGroupingTab.label|raw }}
{% if oBrick.GetShowTabCounts() %}
<span class="badge">{{ aGroupingTab.count|raw }}</span>
{% endif %}
</a>
</li>
{% endfor %}
</ul>
{% endif %}
{% set iTableCount = 0 %}
{% if aGroupingAreasData|length > 0 %}
{% for aAreaData in aGroupingAreasData %}
{% if aAreaData.iItemsCount > 0 %}
{% set iTableCount = iTableCount + 1 %}
<div class="panel panel-default">
<div class="panel-heading clearfix">
<h3 class="panel-title" style="float: left;">{{ aAreaData.sTitle }}</h3>
{% if bCanExport %}
<a href="{{ app.url_generator.generate('p_manage_brick_excel_export_start', {'sBrickId': sBrickId, 'sGroupingTab': sGroupingTab, 'sGroupingArea': aAreaData.sId})|raw }}"
id="btn_export_excel_for_{{ aAreaData.sId }}"
data-toggle="modal" data-target="#modal-for-all">
<span class="fa fa-download fa-lg" style="float: right;"
data-toggle="tooltip" data-delay="300" data-placement="left"
title="{{ 'ExcelExporter:ExportMenu'|dict_s }}"></span>
</a>
{% endif %}
</div>
<div class="panel-body">
<table id="table-{{ aAreaData.sId }}" class="table table-striped table-bordered responsive"
width="100%"></table>
</div>
</div>
{% endif %}
{% endfor %}
{% endif %}
{% if iTableCount == 0 %}
<div class="panel panel-default">
<div class="panel-body">
<h3 class="text-center">{{ 'Brick:Portal:Manage:Table:NoData'|dict_s }}</h3>
</div>
</div>
{% endif %}
{% endblock %}
{% block pPageLiveScripts %}
{{ parent() }}
<script type="text/javascript">
var sDataLoading = '{{ sDataLoading }}';
// Used for ajax throttling
var iSearchThrottle = 300;
var oKeyTimeout;
var aKeyTimeoutFilteredKeys = [16, 17, 18, 19, 27, 33, 34, 35, 36, 37, 38, 39, 40]; // Shift, Ctrl, Alt, Pause, Esc, Page Up/Down, Home, End, Left/Up/Right/Down arrows
var columnsProperties = {
{% for aAreaData in aGroupingAreasData %}
'{{ aAreaData.sId }}': {{ aAreaData.aColumnsDefinition|json_encode()|raw }},
{% endfor %}
};
var rawData = {
{% for aAreaData in aGroupingAreasData %}
'{{ aAreaData.sId }}': {{ aAreaData.aItems|json_encode()|raw }},
{% endfor %}
};
// Show a loader inside the table
var showTableLoader = function (oElem) {
oElem.children('tbody').html('<tr><td class="datatables_overlay" colspan="100">' + $('#page_overlay').html() + '</td></tr>');
};
// Columns definition for the table from the columnsProperties
var getColumnsDefinition = function (tableName) {
var tableProperties = columnsProperties[tableName];
if (tableProperties === undefined && window.console) {
console.log('Could not retrieve columns properties for table "' + tableName + '"');
return false;
}
if (rawData[tableName] === undefined && window.console) {
console.log('Could not retrieve data for table "' + tableName + '"');
return false;
}
var columnsDefinition = [];
for (key in tableProperties) {
// Regular attribute columns
if (key !== '_ui_extensions') {
columnsDefinition.push({
"width": "auto",
"searchable": true,
"sortable": (sDataLoading === '{{ constant('Combodo\\iTop\\Portal\\Brick\\AbstractBrick::ENUM_DATA_LOADING_FULL') }}'),
"title": tableProperties[key].title,
"defaultContent": "",
"type": "html",
"data": "attributes." + key + ".att_code",
"render": function (att_code, type, row) {
var cellElem;
var itemActions;
var itemPrimarayAction;
// Preparing action on the cell
// Note : For now we will use only one action, the secondary actions are therefore not implemented. Only the data structure is done.
itemActions = row.attributes[att_code].actions;
// Preparing the cell data
cellElem = (itemActions.length > 0) ? $('<a></a>') : $('<span></span>');
cellElem.html(row.attributes[att_code].value);
// Building actions
if (itemActions.length > 0) {
// - Primary action
itemPrimaryAction = itemActions[0];
switch (itemPrimaryAction.type) {
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\ManageBrick::ENUM_ACTION_VIEW') }}':
url = '{{ app.url_generator.generate('p_object_view', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, itemPrimaryAction.class).replace(/-objectId-/, itemPrimaryAction.id);
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\ManageBrick::ENUM_ACTION_EDIT') }}':
url = '{{ app.url_generator.generate('p_object_edit', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, itemPrimaryAction.class).replace(/-objectId-/, itemPrimaryAction.id);
break;
default:
url = '#';
//console.log('Action "'+itemPrimaryAction+'" not implemented');
break;
}
SetActionUrl(cellElem, url);
SetActionOpeningTarget(cellElem, itemPrimaryAction.opening_target);
// - Secondary actions
// Not done for now, only the data structure is ready in case we need it later
}
return cellElem.prop('outerHTML');
},
});
}
// UI extensions buttons
else {
columnsDefinition.push({
"width": "auto",
"searchable": false,
"sortable": (sDataLoading === '{{ constant('Combodo\\iTop\\Portal\\Brick\\AbstractBrick::ENUM_DATA_LOADING_FULL') }}'),
"title": tableProperties[key].title,
"defaultContent": "",
"type": "html",
"data": "attributes." + key + ".att_code",
"render": function (att_code, type, row) {
var cellElem = $('<div class="group-actions-wrapper"></div>');
var actionsCount = row.actions.length;
// Adding menu wrapper in case there are several actions
var actionsElem = $('<div></div>');
actionsElem.appendTo(cellElem);
if (actionsCount > 1) {
actionsElem.addClass('group-actions pull-right');
// Adding hamburger icon toggler
actionsElem.append(
$('<a class="glyphicon glyphicon-menu-hamburger" data-toggle="collapse" data-target="#item-actions-menu-' + row.id + '"></a>')
);
// Adding sub menu
var actionsSSMenuElem = $('<div id="item-actions-menu-' + row.id + '" class="item-action-wrapper panel panel-default"></div>')
.appendTo(actionsElem);
var actionsSSMenuContainerElem = $('<div class="panel-body"></div>')
.appendTo(actionsSSMenuElem);
}
// Adding actions
for (var i in row.actions) {
var actionDef = row.actions[i];
var actionElem = $('<a></a>')
.attr('href', actionDef.url)
.append($('<span></span>').html(actionDef.label));
// Adding css classes to action
for (var j in actionDef.css_classes) {
actionElem.addClass(actionDef.css_classes[j]);
}
// Performing specific treatment regarding the action type
if (actionDef.type === 'button') {
// External files
// Note: Not supported yet
// On click callback
actionElem.attr('onclick', actionDef.onclick);
}
else if (actionDef.type === 'link') {
actionElem.attr('target', actionDef.target);
}
if (actionsCount > 1) {
actionsSSMenuContainerElem.append($('<p></p>').append(actionElem));
}
else {
actionsElem.append(actionElem);
}
}
return cellElem.prop('outerHTML');
}
});
}
}
return columnsDefinition;
};
$(document).ready(function () {
{% for aAreaData in aGroupingAreasData %}
{% set sAreaId = aAreaData.sId %}
$(function () {
$('[data-toggle="tooltip"]').tooltip()
})
showTableLoader($('#table-{{ sAreaId }}'));
var oTable{{ sAreaId }} = $('#table-{{ sAreaId }}').dataTable({
"language": {
"processing": "{{ 'Portal:Datatables:Language:Processing'|dict_s }}",
"search": "{{ 'Portal:Datatables:Language:Search'|dict_s }}",
"lengthMenu": "{{ 'Portal:Datatables:Language:LengthMenu'|dict_s }}",
"zeroRecords": "{{ 'Portal:Datatables:Language:ZeroRecords'|dict_s }}",
"info": "{{ 'Portal:Datatables:Language:Info'|dict_s }}",
"infoEmpty": "{{ 'Portal:Datatables:Language:InfoEmpty'|dict_s }}",
"infoFiltered": "({{ 'Portal:Datatables:Language:InfoFiltered'|dict_s }})",
"emptyTable": "{{ 'Portal:Datatables:Language:EmptyTable'|dict_s }}",
"paginate": {
"first": "{{ 'Portal:Datatables:Language:Paginate:First'|dict_s }}",
"previous": "{{ 'Portal:Datatables:Language:Paginate:Previous'|dict_s }}",
"next": "{{ 'Portal:Datatables:Language:Paginate:Next'|dict_s }}",
"last": "{{ 'Portal:Datatables:Language:Paginate:Last'|dict_s }}"
},
"aria": {
"sortAscending": ": {{ 'Portal:Datatables:Language:Sort:Ascending'|dict_s }}",
"sortDescending": ": {{ 'Portal:Datatables:Language:Sort:Descending'|dict_s }}"
}
},
"lengthMenu": [[10, 20, 50, -1], [10, 20, 50, "{{ 'Portal:Datatables:Language:DisplayLength:All'|dict_s }}"]],
"displayLength": {{ constant('Combodo\\iTop\\Portal\\Brick\\ManageBrick::DEFAULT_LIST_LENGTH') }},
"dom": '<"row"<"col-sm-6"l><"col-sm-6"<f><"visible-xs"p>>>t<"row"<"col-sm-6"i><"col-sm-6"p>>',
"columns": getColumnsDefinition('{{ sAreaId }}'),
"order": [[0, "desc"]],
"rowCallback": function (oRow, oData) {
if (oData.highlight_class !== undefined) {
var sHighlightClass = oData.highlight_class;
var sBSHiglightClass = '';
// Adding classic iTop class
$(oRow).addClass(sHighlightClass);
// Adding mapped BS class
if (sHighlightClass === '{{ constant('HILIGHT_CLASS_CRITICAL') }}') {
sBSHiglightClass = 'danger';
}
else if (sHighlightClass === '{{ constant('HILIGHT_CLASS_WARNING') }}') {
sBSHiglightClass = 'warning';
}
else if (sHighlightClass === '{{ constant('HILIGHT_CLASS_OK') }}') {
sBSHiglightClass = 'success';
}
$(oRow).addClass(sBSHiglightClass);
}
},
"drawCallback": function (settings) {
// Hiding pagination if only one page
if ($(this).closest('.dataTables_wrapper').find('.dataTables_paginate:last .paginate_button:not(.previous):not(.next)').length < 2) {
$(this).closest('.dataTables_wrapper').find('.dataTables_paginate, .dataTables_info').hide();
}
else {
$(this).closest('.dataTables_wrapper').find('.dataTables_paginate, .dataTables_info').show();
}
},
{% if sDataLoading == constant('Combodo\\iTop\\Portal\\Brick\\AbstractBrick::ENUM_DATA_LOADING_FULL') %}
"data": rawData['{{ sAreaId }}'],
{% else %}
"processing": true,
"serverSide": true,
{#"searchDelay": 1000, // can be used to increase time between server calls when typing search query#}
"ajax": {
"url": "{{ app.url_generator.generate('p_manage_brick_lazy', {'sBrickId': sBrickId, 'sGroupingTab': sGroupingTab, 'sGroupingArea': sAreaId})|raw }}",
"data": function (d) {
d.iPageNumber = Math.floor(d.start / d.length) + 1;
d.iListLength = d.length;
d.columns = null;
d.orders = null;
{% if sSearchValue is not null %}
// Sets default filter value
if (d.draw === 1) {
$('#table-{{ sAreaId }}_filter input').val('{{ sSearchValue }}');
d.search.value = $('#table-{{ sAreaId }}_filter input').val();
}
{% endif %}
if (d.search.value) {
d.sSearchValue = d.search.value;
}
}
}
{% endif %}
});
// Overrides filter input to apply throttle. Otherwise, an ajax request is send each time a key is pressed
// Also removes accents from search string
// Note : The '.off()' call is to unbind event from DataTables that where triggered before we could intercept anything
$('#table-{{ sAreaId }}_filter input').off().on('keyup', function () {
var me = this;
clearTimeout(oKeyTimeout);
oKeyTimeout = setTimeout(function () {
oTable{{ sAreaId }}.search(me.value.latinise()).draw();
}, iSearchThrottle);
});// Shows a loader in the table when processing
$('#table-{{ sAreaId }}').on('processing.dt', function (event, settings, processing) {
if (processing === true) {
showTableLoader($(this));
}
});
{% endfor %}
// Auto collapse item actions popup
$('body').click(function () {
$('table .item-action-wrapper.collapse.in').collapse('hide');
});
});
</script>
{% endblock %}

View File

@@ -1,361 +1,17 @@
{# itop-portal-base/portal/src/views/bricks/manage/layout.html.twig #}
{# Manage brick base layout #}
{% extends 'itop-portal-base/portal/src/views/bricks/layout.html.twig' %}
{% block pPageBodyClass %}{{ parent() }} page_manage_brick{% endblock %}
{% block pMainHeaderTitle %}
{{ oBrick.GetTitle()|dict_s }}
{% endblock %}
{% block pMainHeaderTitle %}{{ oBrick.GetTitle()|dict_s }} ({{ iCount }}) {% endblock %}
{% block pMainHeaderActions %}
{% if aGroupingTabsValues|length > 1 %}
<div class="btn-group {#btn-group-sm#} btn_group_explicit">
{% for aGroupingTab in aGroupingTabsValues %}
<a href="{{ app.url_generator.generate('p_manage_brick', {'sBrickId': sBrickId, 'sGroupingTab': aGroupingTab.value}) }}" id="btn_tab_for_{{ aGroupingTab.value }}" class="btn btn-default {% if sGroupingTab is defined and sGroupingTab == aGroupingTab.value %}active{% endif %}">
{{ aGroupingTab.label|raw }}
{% if oBrick.GetShowTabCounts() %}
<span class="btn_tab_count">{{ aGroupingTab.count|raw }}</span>
{% endif %}
</a>
{% endfor %}
</div>
{% endif %}
<div class="btn-group btn_group_explicit">
{% for sDisplay in oBrick.ListLayoutDisplayTypes %}
<a href="{{ app.url_generator.generate('p_manage_brick', {'sBrickId': sBrickId, 'sDisplayType': sDisplay}) }}{% if app['combodo.portal.instance.routes'][oBrick.GetRouteName]['hash'] is defined %}#{{ app['combodo.portal.instance.routes'][oBrick.GetRouteName]['hash'] }}{% endif %}"
id="btn_tab_for_{{ sDisplay }}"
class="btn btn-default {% if sDisplay == oBrick.GetPresentationDataForDisplayType(sDisplayType).layoutDisplayType %}active{% endif %}">
{{ ('Brick:Portal:Manage:DisplayType:' ~ sDisplay)|dict_s }}
</a>
{% endfor %}
</div>
{% endblock %}
{% block pMainContentHolder%}
{% set iTableCount = 0 %}
{% if aGroupingAreasData|length > 0 %}
{% for aAreaData in aGroupingAreasData %}
{% if aAreaData.iItemsCount > 0 %}
{% set iTableCount = iTableCount + 1 %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ aAreaData.sTitle }}</h3>
</div>
<div class="panel-body">
{# We decided not to show empty tables anymore #}
{#
{% if aAreaData.iItemsCount > 0 %}
#}
<table id="table-{{ aAreaData.sId }}" class="table table-striped table-bordered responsive" width="100%"></table>
{#
{% else %}
<div class="text-center">
{{ 'Brick:Portal:Manage:Table:NoData'|dict_s }}
</div>
{% endif %}
#}
</div>
</div>
{% endif %}
{% endfor %}
{% endif %}
{% if iTableCount == 0 %}
<div class="panel panel-default">
<div class="panel-body">
<h3 class="text-center">{{ 'Brick:Portal:Manage:Table:NoData'|dict_s }}</h3>
</div>
</div>
{% endif %}
{% endblock %}
{% block pPageLiveScripts %}
{{ parent() }}
<script type="text/javascript">
var sDataLoading = '{{ sDataLoading }}';
// Used for ajax throttling
var iSearchThrottle = 300;
var oKeyTimeout;
var aKeyTimeoutFilteredKeys = [9, 16, 17, 18, 19, 27, 33, 34, 35, 36, 37, 38, 39, 40]; // Tab, Shift, Ctrl, Alt, Pause, Esc, Page Up/Down, Home, End, Left/Up/Right/Down arrows
var columnsProperties = {
{% for aAreaData in aGroupingAreasData %}
'{{ aAreaData.sId }}': {{ aAreaData.aColumnsDefinition|json_encode()|raw }},
{% endfor %}
};
var rawData = {
{% for aAreaData in aGroupingAreasData %}
'{{ aAreaData.sId }}': {{ aAreaData.aItems|json_encode()|raw }},
{% endfor %}
};
// Show a loader inside the table
var showTableLoader = function(oElem)
{
oElem.children('tbody').html('<tr><td class="datatables_overlay" colspan="100">' + $('#page_overlay').html() + '</td></tr>');
};
// Columns definition for the table from the columnsProperties
var getColumnsDefinition = function(tableName)
{
var tableProperties = columnsProperties[tableName];
if(tableProperties === undefined && window.console)
{
console.log('Could not retrieve columns properties for table "'+tableName+'"');
return false;
}
if(rawData[tableName] === undefined && window.console)
{
console.log('Could not retrieve data for table "'+tableName+'"');
return false;
}
var columnsDefinition = [];
for(key in tableProperties)
{
// Regular attribute columns
if(key !== '_ui_extensions') {
columnsDefinition.push({
"width": "auto",
"searchable": true,
"sortable": (sDataLoading === '{{ constant('Combodo\\iTop\\Portal\\Brick\\AbstractBrick::ENUM_DATA_LOADING_FULL') }}'),
"title": tableProperties[key].title,
"defaultContent": "",
"type": "html",
"data": "attributes." + key + ".att_code",
"render": function (att_code, type, row) {
var cellElem;
var itemActions;
var itemPrimarayAction;
// Preparing action on the cell
// Note : For now we will use only one action, the secondary actions are therefore not implemented. Only the data structure is done.
itemActions = row.attributes[att_code].actions;
// Preparing the cell data
cellElem = (itemActions.length > 0) ? $('<a></a>') : $('<span></span>');
cellElem.html(row.attributes[att_code].value);
// Building actions
if (itemActions.length > 0) {
// - Primary action
itemPrimaryAction = itemActions[0];
switch (itemPrimaryAction.type) {
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\ManageBrick::ENUM_ACTION_VIEW') }}':
url = '{{ app.url_generator.generate('p_object_view', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, itemPrimaryAction.class).replace(/-objectId-/, itemPrimaryAction.id);
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\ManageBrick::ENUM_ACTION_EDIT') }}':
url = '{{ app.url_generator.generate('p_object_edit', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, itemPrimaryAction.class).replace(/-objectId-/, itemPrimaryAction.id);
break;
default:
url = '#';
//console.log('Action "'+itemPrimaryAction+'" not implemented');
break;
}
SetActionUrl(cellElem, url);
SetActionOpeningTarget(cellElem, itemPrimaryAction.opening_target);
// - Secondary actions
// Not done for now, only the data structure is ready in case we need it later
}
return cellElem.prop('outerHTML');
},
});
}
// UI extensions buttons
else
{
columnsDefinition.push({
"width": "auto",
"searchable": false,
"sortable": (sDataLoading === '{{ constant('Combodo\\iTop\\Portal\\Brick\\AbstractBrick::ENUM_DATA_LOADING_FULL') }}'),
"title": tableProperties[key].title,
"defaultContent": "",
"type": "html",
"data": "attributes." + key + ".att_code",
"render": function (att_code, type, row) {
var cellElem = $('<div class="group-actions-wrapper"></div>');
var actionsCount = row.actions.length;
// Adding menu wrapper in case there are several actions
var actionsElem = $('<div></div>');
actionsElem.appendTo(cellElem);
if(actionsCount > 1) {
actionsElem.addClass('group-actions pull-right');
// Adding hamburger icon toggler
actionsElem.append(
$('<a class="glyphicon glyphicon-menu-hamburger" data-toggle="collapse" data-target="#item-actions-menu-' + row.id + '"></a>')
);
// Adding sub menu
var actionsSSMenuElem = $('<div id="item-actions-menu-' + row.id + '" class="item-action-wrapper panel panel-default"></div>')
.appendTo(actionsElem);
var actionsSSMenuContainerElem = $('<div class="panel-body"></div>')
.appendTo(actionsSSMenuElem);
}
// Adding actions
for(var i in row.actions)
{
var actionDef = row.actions[i];
var actionElem = $('<a></a>')
.attr('href', actionDef.url)
.append( $('<span></span>').html(actionDef.label) );
// Adding css classes to action
for(var j in actionDef.css_classes)
{
actionElem.addClass(actionDef.css_classes[j]);
}
// Performing specific treatment regarding the action type
if(actionDef.type === 'button')
{
// External files
// Note: Not supported yet
// On click callback
actionElem.attr('onclick', actionDef.onclick);
}
else if(actionDef.type === 'link')
{
actionElem.attr('target', actionDef.target);
}
if(actionsCount > 1)
{
actionsSSMenuContainerElem.append( $('<p></p>').append(actionElem) );
}
else
{
actionsElem.append( actionElem );
}
}
return cellElem.prop('outerHTML');
}
});
}
}
return columnsDefinition;
};
$(document).ready(function(){
{% for aAreaData in aGroupingAreasData %}
{% set sAreaId = aAreaData.sId %}
showTableLoader( $('#table-{{ sAreaId }}') );
var oTable{{ sAreaId }} = $('#table-{{ sAreaId }}').DataTable( {
"language": {
"processing": "{{ 'Portal:Datatables:Language:Processing'|dict_s }}",
"search": "{{ 'Portal:Datatables:Language:Search'|dict_s }}",
"lengthMenu": "{{ 'Portal:Datatables:Language:LengthMenu'|dict_s }}",
"zeroRecords": "{{ 'Portal:Datatables:Language:ZeroRecords'|dict_s }}",
"info": "{{ 'Portal:Datatables:Language:Info'|dict_s }}",
"infoEmpty": "{{ 'Portal:Datatables:Language:InfoEmpty'|dict_s }}",
"infoFiltered": "({{ 'Portal:Datatables:Language:InfoFiltered'|dict_s }})",
"emptyTable": "{{ 'Portal:Datatables:Language:EmptyTable'|dict_s }}",
"paginate": {
"first": "{{ 'Portal:Datatables:Language:Paginate:First'|dict_s }}",
"previous": "{{ 'Portal:Datatables:Language:Paginate:Previous'|dict_s }}",
"next": "{{ 'Portal:Datatables:Language:Paginate:Next'|dict_s }}",
"last": "{{ 'Portal:Datatables:Language:Paginate:Last'|dict_s }}"
},
"aria": {
"sortAscending": ": {{ 'Portal:Datatables:Language:Sort:Ascending'|dict_s }}",
"sortDescending": ": {{ 'Portal:Datatables:Language:Sort:Descending'|dict_s }}"
}
},
"lengthMenu": [[10, 20, 50, -1], [10, 20, 50, "{{ 'Portal:Datatables:Language:DisplayLength:All'|dict_s }}"]],
"displayLength": {{ constant('Combodo\\iTop\\Portal\\Brick\\ManageBrick::DEFAULT_LIST_LENGTH') }},
"dom": '<"row"<"col-sm-6"l><"col-sm-6"<f><"visible-xs"p>>>t<"row"<"col-sm-6"i><"col-sm-6"p>>',
"columns": getColumnsDefinition('{{ sAreaId }}'),
"order": [[0, "desc"]],
"rowCallback": function(oRow, oData){
if(oData.highlight_class !== undefined)
{
var sHighlightClass = oData.highlight_class;
var sBSHiglightClass = '';
// Adding classic iTop class
$(oRow).addClass(sHighlightClass);
// Adding mapped BS class
if(sHighlightClass === '{{ constant('HILIGHT_CLASS_CRITICAL') }}')
{
sBSHiglightClass = 'danger';
}
else if(sHighlightClass === '{{ constant('HILIGHT_CLASS_WARNING') }}')
{
sBSHiglightClass = 'warning';
}
else if(sHighlightClass === '{{ constant('HILIGHT_CLASS_OK') }}')
{
sBSHiglightClass = 'success';
}
$(oRow).addClass(sBSHiglightClass);
}
},
"drawCallback": function(settings){
// Hiding pagination if only one page
if($(this).closest('.dataTables_wrapper').find('.dataTables_paginate:last .paginate_button:not(.previous):not(.next)').length < 2)
{
$(this).closest('.dataTables_wrapper').find('.dataTables_paginate, .dataTables_info').hide();
}
else
{
$(this).closest('.dataTables_wrapper').find('.dataTables_paginate, .dataTables_info').show();
}
},
{% if sDataLoading == constant('Combodo\\iTop\\Portal\\Brick\\AbstractBrick::ENUM_DATA_LOADING_FULL') %}
"data": rawData['{{ sAreaId }}'],
{% else %}
"processing": true,
"serverSide": true,
{#"searchDelay": 1000, // can be used to increase time between server calls when typing search query#}
"ajax": {
"url": "{{ app.url_generator.generate('p_manage_brick_lazy', {'sBrickId': sBrickId, 'sGroupingTab': sGroupingTab, 'sGroupingArea': sAreaId})|raw }}",
"data": function(d){
d.iPageNumber = Math.floor(d.start/d.length) + 1;
d.iListLength = d.length;
d.columns = null;
d.orders = null;
{% if sSearchValue is not null %}
// Sets default filter value
if(d.draw === 1)
{
$('#table-{{ sAreaId }}_filter input').val('{{ sSearchValue }}');
d.search.value = $('#table-{{ sAreaId }}_filter input').val();
}
{% endif %}
if(d.search.value)
{
d.sSearchValue = d.search.value;
}
}
}
{% endif %}
} );
// Overrides filter input to apply throttle. Otherwise, an ajax request is send each time a key is pressed
// Also removes accents from search string
// Note : The '.off()' call is to unbind event from DataTables that where triggered before we could intercept anything
$('#table-{{ sAreaId }}_filter input').off().on('keyup', function(){
var me = this;
clearTimeout(oKeyTimeout);
oKeyTimeout = setTimeout(function() {
oTable{{ sAreaId }}.search(me.value.latinise()).draw();
}, iSearchThrottle);
});// Shows a loader in the table when processing
$('#table-{{ sAreaId }}').on('processing.dt', function(event, settings, processing){
if(processing === true)
{
showTableLoader($(this));
}
});
{% endfor %}
// Auto collapse item actions popup
$('body').click(function(){
$('table .item-action-wrapper.collapse.in').collapse('hide');
});
});
</script>
{% endblock %}

View File

@@ -0,0 +1,62 @@
{% if aNames|length > 0 %}
<div id="bar-{{ oBrick.GetId }}"></div>
<script type="text/javascript">
$(document).ready(function(){
$('#bar-{{ oBrick.GetId }}').on('click', function(oEvent){
oEvent.preventDefault();
oEvent.stopPropagation();
});
window.setTimeout(function() {
var chart = c3.generate({
bindto: d3.select('#bar-{{ oBrick.GetId }}'),
data: {
json: {{ aDisplayValues | json_encode() | raw }},
keys: {
x: 'label',
value: ["value"]
},
onclick: function (d, element) {
var aURLs = {{ aUrls | json_encode() | raw }};
window.location.href = aURLs[d.index];
},
selection: {
enabled: true
},
type: 'bar'
},
axis: {
x: {
tick: {
culling: {max: 25}, // Maximum 24 labels on x axis (2 years).
centered: true,
rotate: 90,
multiline: false
},
type: 'category' // this needed to load string x value
}
},
grid: {
y: {
show: true
}
},
legend: {
show: false,
},
tooltip: {
grouped: false,
format: {
title: function() { return '' },
name: function (name, ratio, id, index) {
var aNames = {{ aNames | json_encode() | raw }};
return aNames['series_' + index];
}
}
}
});
}, 100);
});
</script>
{% else %}
<h3 class="text-center">{{ 'Brick:Portal:Manage:Table:NoData'|dict_s }}</h3>
{% endif %}

View File

@@ -0,0 +1,36 @@
{% if aNames|length > 0 %}
<div id="pie-{{ oBrick.GetId }}"></div>
<script type="text/javascript">
$(document).ready(function () {
$('#pie-{{ oBrick.GetId }}').on('click', function(oEvent){
oEvent.preventDefault();
oEvent.stopPropagation();
});
window.setTimeout(function () {
var chart = c3.generate({
bindto: d3.select('#pie-{{ oBrick.GetId }}'),
data: {
columns: {{ aColumns | json_encode | raw }},
type: 'pie',
names: {{ aNames | json_encode() | raw }},
onclick: function (d, element) {
var aURLs = {{ aUrls | json_encode() | raw }};
window.location.href= aURLs[d.index];
},
},
legend: {
show: true,
position: 'right',
},
tooltip: {
format: {
value: function (value, ratio, id) { return value; }
}
}
});
}, 100);
});
</script>
{% else %}
<h3 class="text-center">{{ 'Brick:Portal:Manage:Table:NoData'|dict_s }}</h3>
{% endif %}

View File

@@ -0,0 +1,57 @@
{# itop-portal-base/popup-export-excel.html.twig #}
{# Export Excel popup layout #}
<div class="modal-header clearfix">
<h4 class="modal-title" style="float: left;">{{ 'ExcelExporter:ExportDialogTitle'|dict_s }}</h4>
</div>
<div class="modal-body">
<div id="export-text-result" style="display:none;">
<p>{{ 'Core:BulkExport:ExportResult'|dict_s }}</p>
<p id="export-error" class="alert alert-danger" role="alert"></p>
</div>
<div id="export-feedback">
<p class="export-message" style="text-align:center;">{{ 'ExcelExport:PreparingExport'|dict_s }}</p>
<div class="progress">
<div class="progress-bar" role="progressbar" style="width: 0%"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
<span class="progress-message">0%</span>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button id="export-close" type="button" class="btn btn-primary" data-dismiss="modal" style="display:none;">{{ 'Portal:Button:Close'|dict_s }}</button>
<button id="export-cancel" type="button" class="btn btn-secondary export-cancel">{{ 'Portal:Button:Cancel'|dict_s }}</button>
</div>
<script type="text/javascript">
var sDataState = 'not-yet-started';
var sOQL = "{{ sOQL|raw }}";
var sFormat = 'xlsx';
var sFields = "{{ sFields }}";
$(document).ready(function () {
window.setTimeout(function () {
$('.progress').progressbar({
value: 0,
change: function () {
$('.progress-message').text($(this).progressbar("value") + "%");
$('.progress-bar').attr('aria-valuenow', $(this).progressbar("value"));
$('.progress-bar').width($(this).progressbar("value") + "%");
},
complete: function () {
$('.progress-message').text('100 %');
$('.progress-bar').attr('aria-valuenow', '100');
$('.progress-bar').width('100%');
}
});
$('.export-cancel').on('click', function () {
sDataState = 'cancelled';
});
ExportStartExport();
}, 100);
});
</script>

View File

@@ -0,0 +1,28 @@
{# itop-portal-dashlet-basic/tile.html.twig #}
{# Image brick tile layout #}
<div class="col-xs-12 col-sm-{{ oBrick.GetWidth }} tile-badge">
{% block pTileWrapper %}
<a href="{{ app.url_generator.generate('p_manage_brick', {'sBrickId': sBrickId, 'sDisplayType': oBrick.GetDisplayType}) }}{% if app['combodo.portal.instance.routes'][oBrick.GetRouteName]['hash'] is defined %}#{{ app['combodo.portal.instance.routes'][oBrick.GetRouteName]['hash'] }}{% endif %}"
{% if app['combodo.portal.instance.routes'][oBrick.GetRouteName]['navigation_menu_attr'] is defined %}{% for key, value in app['combodo.portal.instance.routes'][oBrick.GetRouteName]['navigation_menu_attr'] %} {{ key }}="{{ value }}"{% endfor %}{% endif %}
{% if oBrick.GetModal %}data-toggle="modal" data-target="#modal-for-all"{% endif %}
class="tile{# vertical-center#}"
id="brick-{{ oBrick.GetId }}"
data-brick-id="{{ oBrick.GetId }}">
<div>
<div class="tile_decoration">{% if oBrick.GetDecorationClassHome %}<span
class="icon {{ oBrick.GetDecorationClassHome }}"></span>{% else %}<img src="{{ sIconURL }}"
style="vertical-align:middle;"
alt="Class Icon">{% endif %}
</div>
<div class="tile_body">
<div>
<div class="tile_description">{{ oBrick.GetTitle()|dict_s }}</div>
<div class="tile_title">{{ iCount }}</div>
</div>
<div class="tile_description"><p>{{ oBrick.GetDescription|dict_s|raw }}</p></div>
</div>
</div>
</a>
{% endblock %}
</div>

View File

@@ -0,0 +1,20 @@
{# itop-portal-dashlet-basic/tile.html.twig #}
{# Image brick tile layout #}
<div class="col-xs-12 col-sm-{{ oBrick.GetWidth }}">
{% block pTileWrapper %}
<a href="{{ app.url_generator.generate('p_manage_brick', {'sBrickId': sBrickId, 'sDisplayType': oBrick.GetDisplayType}) }}{% if app['combodo.portal.instance.routes'][oBrick.GetRouteName]['hash'] is defined %}#{{ app['combodo.portal.instance.routes'][oBrick.GetRouteName]['hash'] }}{% endif %}"
{% if app['combodo.portal.instance.routes'][oBrick.GetRouteName]['navigation_menu_attr'] is defined %}{% for key, value in app['combodo.portal.instance.routes'][oBrick.GetRouteName]['navigation_menu_attr'] %} {{ key }}="{{ value }}"{% endfor %}{% endif %}
{% if oBrick.GetModal %}data-toggle="modal" data-target="#modal-for-all"{% endif %}
class="tile{# vertical-center#}"
id="brick-{{ oBrick.GetId }}"
data-brick-id="{{ oBrick.GetId }}">
<div style="background-color:#fff;padding:0.25em;">
<div class="tile_title"><span
class="icon fa fa-{{ oBrick.GetDisplayType }}"></span> {{ oBrick.GetTitle()|dict_s }}
({{ iCount }})</span> </div>
{% include 'itop-portal-base/portal/src/views/bricks/manage/mode-' ~ oBrick.GetDisplayType ~ '.html.twig' with {'oBrick': oBrick, 'aColumns': aColumns, 'aNames': aNames, 'aUrls': aUrls, 'aDisplayValues': aDisplayValues} %}
</div>
</a>
{% endblock %}
</div>

View File

@@ -0,0 +1,21 @@
{# itop-portal-base/portal/src/views/bricks/tile.html.twig #}
{# Base brick tile layout #}
<div class="col-xs-12 col-sm-{{ oBrick.GetWidth }}">
{% block pTileWrapper %}
<a href="{{ app.url_generator.generate(oBrick.GetRouteName, {sBrickId: oBrick.GetId}) }}{% if app['combodo.portal.instance.routes'][oBrick.GetRouteName]['hash'] is defined %}#{{ app['combodo.portal.instance.routes'][oBrick.GetRouteName]['hash'] }}{% endif %}"
{% if app['combodo.portal.instance.routes'][oBrick.GetRouteName]['navigation_menu_attr'] is defined %}{% for key, value in app['combodo.portal.instance.routes'][oBrick.GetRouteName]['navigation_menu_attr'] %} {{ key }}="{{ value }}"{% endfor %}{% endif %}
{% if oBrick.GetModal %}data-toggle="modal" data-target="#modal-for-all"{% endif %}
class="tile{# vertical-center#}" id="brick-{{ oBrick.GetId }}" data-brick-id="{{ oBrick.GetId }}">
<div class="tile_decoration">
<span class="icon {{ oBrick.GetDecorationClassHome }}"></span>
</div>
<div class="tile_body">
<div class="tile_title">{{ oBrick.GetTitleHome|dict_s }}</div>
{% if oBrick.HasDescription %}
<div class="tile_description">{{ oBrick.GetDescription|dict_s|raw }}</div>
{% endif %}
</div>
</a>
{% endblock %}
</div>

View File

@@ -0,0 +1,40 @@
{# itop-portal-dashlet-basic/tile.html.twig #}
{# Topx List tile layout #}
<div class="col-xs-12 col-sm-{{ oBrick.GetWidth }}">
{% block pTileWrapper %}
<a href="{{ app.url_generator.generate('p_manage_brick', {'sBrickId': sBrickId, 'sDisplayType': oBrick.GetDisplayType}) }}{% if app['combodo.portal.instance.routes'][oBrick.GetRouteName]['hash'] is defined %}#{{ app['combodo.portal.instance.routes'][oBrick.GetRouteName]['hash'] }}{% endif %}"
{% if app['combodo.portal.instance.routes'][oBrick.GetRouteName]['navigation_menu_attr'] is defined %}{% for key, value in app['combodo.portal.instance.routes'][oBrick.GetRouteName]['navigation_menu_attr'] %} {{ key }}="{{ value }}"{% endfor %}{% endif %}
{% if oBrick.GetModal %}data-toggle="modal" data-target="#modal-for-all"{% endif %}
class="tile{# vertical-center#}"
id="brick-{{ oBrick.GetId }}"
data-brick-id="{{ oBrick.GetId }}">
<div style="background-color:#fff;padding:0.25em;">
<div class="tile_title">
<span class="icon fa fa-signal fa-rotate-270"></span> {{ oBrick.GetTitle()|dict_s }}
({{ iCount }})</span> </div>
<table class="table table-sm">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">{{ 'Brick:Portal:Manage:Group'|dict_s }}</th>
<th scope="col">{{ ('Brick:Portal:Manage:fct:' ~ sFct)|dict_s }}</th>
</tr>
</thead>
<tbody>
{% set iLineCount = 0 %}
{% for aDisplayValue in aDisplayValues %}
{% set sURL = aUrls[iLineCount] %}
{% set iLineCount = iLineCount + 1 %}
<tr>
<th scope="row">{{ iLineCount }}</th>
<td><a href="{{ sURL }}">{{ aDisplayValue.label_html }}</a></td>
<td>{{ aDisplayValue.value }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</a>
{% endblock %}
</div>

File diff suppressed because it is too large Load Diff

View File

@@ -355,10 +355,65 @@ footer{
font-weight: bold;
color: #333;
}
.home .tile .tile_title > span.icon {
color: $combodo-orange;
}
.home .tile .tile_description{
display: none;
color: #555555;
}
.home div.tile-badge > a.tile {
display: table;
width: 100%;
}
.home div.tile-badge > a.tile > div {
display: table-row;
}
.home div.tile-badge > a.tile > div > div {
display: table-cell;
}
.home div.tile-badge > a.tile > div > div.tile_decoration {
text-align: center;
vertical-align: top;
position: inherit;
float: inherit;
}
.home div.tile-badge > a.tile > div > div.tile_body {
text-align: right;
padding-left: 0;
}
.home div.tile-badge > a.tile > div > div.tile_body > div:first-child {
text-align: center;
margin: 0 auto;
margin-left: 10%;
}
.home div.tile-badge > a.tile > div > div.tile_body > div:first-child > div {
text-align: center;
}
.home div.tile-badge > a.tile > div > div.tile_body > div:first-child > div.tile_description {
margin-bottom: 1.5em;
}
.home div.tile-badge > a.tile > div > div.tile_body > div:first-child > div.tile_title {
font-size: 1.5em;
font-weight: bold;
}
.home div.tile-badge > a.tile > div > div.tile_body > div.tile_description {
text-align: right;
}
@media (min-width: 768px) {
.home .tile{
display: block;

View File

@@ -0,0 +1,109 @@
// Copyright (c) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
//
function ExportStartExport() {
var oParams = {};
oParams.operation = 'export_build';
oParams.format = sFormat;
oParams.expression = sOQL;
oParams.fields = sFields;
$.post(GetAbsoluteUrlAppRoot() + 'pages/ajax.render.php', oParams, function (data) {
if (data == null) {
ExportError('Export failed (no data provided), please contact your administrator');
}
else {
ExportRun(data);
}
}, 'json')
.fail(function () {
ExportError('Export failed, please contact your administrator');
});
}
function ExportError(sMessage) {
sDataState = 'error';
$('#export-feedback').hide();
$('#export-text-result').show();
$('#export-error').html(sMessage);
}
function ExportRun(data) {
switch (data.code) {
case 'run':
// Continue
$('.progress').progressbar({value: data.percentage});
$('.export-message').html(data.message);
oParams = {};
oParams.token = data.token;
if (sDataState == 'cancelled') {
oParams.operation = 'export_cancel';
$('#export-cancel').hide();
$('#export-close').show();
}
else {
oParams.operation = 'export_build';
}
$.post(GetAbsoluteUrlAppRoot() + 'pages/ajax.render.php', oParams, function (data) {
ExportRun(data);
},
'json');
break;
case 'done':
sDataState = 'done';
$('#export-cancel').hide();
$('#export-close').show();
$('.progress').progressbar({value: data.percentage});
sMessage = '<a href="' + GetAbsoluteUrlAppRoot() + 'pages/ajax.render.php?operation=export_download&token=' + data.token + '" target="_blank">' + data.message + '</a>';
$('.export-message').html(sMessage);
if (data.text_result != undefined) {
if (data.mime_type == 'text/html') {
$('#export-content').parent().html(data.text_result);
$('#export-text-result').show();
$('#export-text-result .listResults').tableHover();
$('#export-text-result .listResults').tablesorter({widgets: ['myZebra']});
}
else {
if ($('#export-text-result').closest('ui-dialog').length == 0) {
// not inside a dialog box, adjust the height... approximately
var jPane = $('#export-text-result').closest('.ui-layout-content');
var iTotalHeight = jPane.height();
jPane.children(':visible').each(function () {
if ($(this).attr('id') != '') {
iTotalHeight -= $(this).height();
}
});
$('#export-content').height(iTotalHeight - 80);
}
$('#export-content').val(data.text_result);
$('#export-text-result').show();
}
}
break;
case 'error':
sDataState = 'error';
$('#export-feedback').hide();
$('#export-text-result').show();
$('#export-error').html(data.message);
$('#export-cancel').hide();
$('#export-close').show();
default:
}
}