mirror of
https://github.com/Combodo/iTop.git
synced 2026-07-06 23:06:38 +02:00
Merge branch 'develop' into SessionManagement
This commit is contained in:
@@ -19,8 +19,12 @@ use Combodo\iTop\Application\UI\Base\Component\FieldSet\FieldSetUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Form\Form;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Form\FormUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Html\HtmlFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Input\Select\SelectOptionUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Input\SelectUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\MedallionIcon\MedallionIcon;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Panel\Panel;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Title\Title;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory;
|
||||
@@ -2948,6 +2952,93 @@ EOF
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the derived class to create
|
||||
* @param string $sClass
|
||||
* @param \WebPage $oP
|
||||
* @param \ApplicationContext $oAppContext
|
||||
* @param array $aPossibleClasses
|
||||
* @param array $aHiddenFields
|
||||
*
|
||||
* @return void
|
||||
* @throws \CoreException
|
||||
* @throws \DictExceptionMissingString
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public static function DisplaySelectClassToCreate(string $sClass, WebPage $oP, ApplicationContext $oAppContext, array $aPossibleClasses, array $aHiddenFields)
|
||||
{
|
||||
$sClassLabel = MetaModel::GetName($sClass);
|
||||
$sTitle = Dict::Format('UI:CreationTitle_Class', $sClassLabel);
|
||||
|
||||
$oP->set_title($sTitle);
|
||||
$sClassIconUrl = MetaModel::GetClassIcon($sClass, false);
|
||||
$oPanel = PanelUIBlockFactory::MakeForClass($sClass, $sTitle)
|
||||
->SetIcon($sClassIconUrl);
|
||||
|
||||
|
||||
$oClassForm = FormUIBlockFactory::MakeStandard();
|
||||
$oPanel->AddMainBlock($oClassForm);
|
||||
|
||||
$oClassForm->AddHtml($oAppContext->GetForForm())
|
||||
->AddSubBlock(InputUIBlockFactory::MakeForHidden('checkSubclass', '0'))
|
||||
->AddSubBlock(InputUIBlockFactory::MakeForHidden('operation', 'new'));
|
||||
|
||||
foreach ($aHiddenFields as $sKey => $sValue) {
|
||||
if (is_scalar($sValue)) {
|
||||
$oClassForm->AddSubBlock(InputUIBlockFactory::MakeForHidden($sKey, $sValue));
|
||||
}
|
||||
}
|
||||
|
||||
$aDefaults = utils::ReadParam('default', array(), false, 'raw_data');
|
||||
foreach ($aDefaults as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $key2 => $value2) {
|
||||
if (is_array($value2)) {
|
||||
foreach ($value2 as $key3 => $value3) {
|
||||
$sValue = utils::EscapeHtml($value3);
|
||||
$oClassForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("default[$key][$key2][$key3]", $sValue));
|
||||
}
|
||||
} else {
|
||||
$sValue = utils::EscapeHtml($value2);
|
||||
$oClassForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("default[$key][$key2]", $sValue));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$sValue = utils::EscapeHtml($value);
|
||||
$oClassForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("default[$key]", $sValue));
|
||||
}
|
||||
}
|
||||
|
||||
$oClassForm->AddSubBlock(self::DisplayBlockSelectClassToCreate($sClass, $sClassLabel, $aPossibleClasses));
|
||||
|
||||
$oP->AddSubBlock($oPanel);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClassLabel
|
||||
* @param array $aPossibleClasses
|
||||
* @param string $sClass
|
||||
*
|
||||
* @return UIContentBlock
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public static function DisplayBlockSelectClassToCreate( string $sClass, string $sClassLabel,array $aPossibleClasses): UIContentBlock
|
||||
{
|
||||
$oBlock= UIContentBlockUIBlockFactory::MakeStandard();
|
||||
$oBlock->AddSubBlock(HtmlFactory::MakeRaw(Dict::Format('UI:SelectTheTypeOf_Class_ToCreate', $sClassLabel)));
|
||||
$oSelect = SelectUIBlockFactory::MakeForSelect('class');
|
||||
$oBlock->AddSubBlock($oSelect);
|
||||
asort($aPossibleClasses);
|
||||
foreach ($aPossibleClasses as $sClassName => $sClassLabel) {
|
||||
$oSelect->AddOption(SelectOptionUIBlockFactory::MakeForSelectOption($sClassName, $sClassLabel, ($sClassName == $sClass)));
|
||||
}
|
||||
|
||||
$oToolbar = ToolbarUIBlockFactory::MakeForAction();
|
||||
$oBlock->AddSubBlock($oToolbar);
|
||||
$oToolbar->AddSubBlock(ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:Apply'), null, null, true));
|
||||
return $oBlock;
|
||||
}
|
||||
/**
|
||||
* @param \WebPage $oPage
|
||||
* @param string $sClass
|
||||
|
||||
@@ -543,7 +543,7 @@ EOF
|
||||
|
||||
if ($bFromDasboardPage) {
|
||||
$sTitleForHTML = utils::HtmlEntities(Dict::S($this->sTitle));
|
||||
$sHtml = "<div class=\"ibo-top-bar--toolbar-dashboard-title\">{$sTitleForHTML}</div>";
|
||||
$sHtml = "<div class=\"ibo-top-bar--toolbar-dashboard-title\" title=\"{$sTitleForHTML}\">{$sTitleForHTML}</div>";
|
||||
if ($oPage instanceof iTopWebPage) {
|
||||
$oTopBar = $oPage->GetTopBarLayout();
|
||||
$oToolbar = ToolbarUIBlockFactory::MakeStandard();
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\UI\Base\Component\Form\FormUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
|
||||
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
|
||||
|
||||
require_once(APPROOT.'/application/displayblock.class.inc.php');
|
||||
@@ -220,6 +222,7 @@ class UIExtKeyWidget
|
||||
$bAddingValue = true;
|
||||
}
|
||||
$sObjectImageAttCode = MetaModel::GetImageAttributeCode($sClassAllowed);
|
||||
$bInitValue = false;
|
||||
while ($oObj = $oAllowedValues->Fetch()) {
|
||||
$aOption = [];
|
||||
$aOption['value'] = $oObj->GetKey();
|
||||
@@ -229,6 +232,7 @@ class UIExtKeyWidget
|
||||
// When there is only once choice, select it by default
|
||||
if ($value != $oObj->GetKey()) {
|
||||
$value = $oObj->GetKey();
|
||||
$bInitValue = true;
|
||||
}
|
||||
}
|
||||
if ($oObj->IsObsolete()) {
|
||||
@@ -263,10 +267,12 @@ class UIExtKeyWidget
|
||||
oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
|
||||
oACWidget_{$this->iId}.AddSelectize('$sJsonOptions','$value');
|
||||
$('#$this->iId').on('update', function() { oACWidget_{$this->iId}.Update(); } );
|
||||
$('#$this->iId').on('change', function() { $(this).trigger('extkeychange') } );
|
||||
|
||||
$('#$this->iId').on('change', function() { $(this).trigger('extkeychange'); } );
|
||||
EOF
|
||||
);
|
||||
if ($bInitValue) {
|
||||
$oPage->add_ready_script("$('#$this->iId').one('validate', function() { $(this).trigger('change'); } );");
|
||||
}
|
||||
$sHTMLValue .= "<div class=\"ibo-input-select--action-buttons\">";
|
||||
}
|
||||
else
|
||||
@@ -899,26 +905,17 @@ JS
|
||||
}
|
||||
}
|
||||
|
||||
$sDialogTitle = '';
|
||||
$oPage->add('<div id="ac_create_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div id="dcr_'.$this->iId.'">');
|
||||
$oPage->add('<form>');
|
||||
|
||||
$sClassLabel = MetaModel::GetName($this->sTargetClass);
|
||||
$oPage->add('<p>'.Dict::Format('UI:SelectTheTypeOf_Class_ToCreate', $sClassLabel));
|
||||
$oPage->add('<nobr><select name="class">');
|
||||
asort($aPossibleClasses);
|
||||
foreach($aPossibleClasses as $sClassName => $sClassLabel)
|
||||
{
|
||||
$oPage->add("<option value=\"$sClassName\">$sClassLabel</option>");
|
||||
}
|
||||
$oPage->add('</select>');
|
||||
$oPage->add(' <button type="submit" class="action" style="margin-top:15px;"><span>' . Dict::S('UI:Button:Ok') . '</span></button></nobr></p>');
|
||||
$sDialogTitle = Dict::Format('UI:CreationTitle_Class', $sClassLabel);;
|
||||
$oBlock = UIContentBlockUIBlockFactory::MakeStandard('ac_create_'.$this->iId,['ibo-is-visible']);
|
||||
$oPage->AddSubBlock($oBlock);
|
||||
$oClassForm = FormUIBlockFactory::MakeStandard();
|
||||
$oBlock->AddSubBlock($oClassForm);
|
||||
$oClassForm->AddSubBlock(cmdbAbstractObject::DisplayBlockSelectClassToCreate( $sClassLabel, $this->sTargetClass, $aPossibleClasses));
|
||||
|
||||
$oPage->add('</form>');
|
||||
$oPage->add('</div></div></div>');
|
||||
$oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
|
||||
$oPage->add_ready_script("$('#dcr_{$this->iId} form').removeAttr('onsubmit');");
|
||||
$oPage->add_ready_script("$('#dcr_{$this->iId} form').on('submit.uilinksWizard', oACWidget_{$this->iId}.DoSelectObjectClass);");
|
||||
$oPage->add_ready_script("$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
|
||||
$oPage->add_ready_script("$('#ac_create_{$this->iId} form').removeAttr('onsubmit');");
|
||||
$oPage->add_ready_script("$('#ac_create_{$this->iId} form').on('submit.uilinksWizard', oACWidget_{$this->iId}.DoSelectObjectClass);");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -911,9 +911,21 @@ class DeprecatedCallsLog extends LogAPI
|
||||
|
||||
$iStackCallerMethodLevel = $iStackDeprecatedMethodLevel + 1; // level 3 = caller of the deprecated method
|
||||
if (array_key_exists($iStackCallerMethodLevel, $aStack)) {
|
||||
$sCallerObject = $aStack[3]['class'];
|
||||
$sCallerMethod = $aStack[3]['function'];
|
||||
$sMessage .= " ({$sCallerObject}::{$sCallerMethod})";
|
||||
$sCallerObject = $aStack[$iStackCallerMethodLevel]['class'] ?? null;
|
||||
$sCallerMethod = $aStack[$iStackCallerMethodLevel]['function'] ?? null;
|
||||
$sMessage .= ' (';
|
||||
if (!is_null($sCallerObject)) {
|
||||
$sMessage .= "{$sCallerObject}::{$sCallerMethod}";
|
||||
} else {
|
||||
$sCallerMethodFile = $aStack[$iStackCallerMethodLevel]['file'];
|
||||
$sCallerMethodLine = $aStack[$iStackCallerMethodLevel]['line'];
|
||||
if (!is_null($sCallerMethod)) {
|
||||
$sMessage .= "call to {$sCallerMethod}() in {$sCallerMethodFile}#L{$sCallerMethodLine}";
|
||||
} else {
|
||||
$sMessage .= "{$sCallerMethodFile}#L{$sCallerMethodLine}";
|
||||
}
|
||||
}
|
||||
$sMessage .= ')';
|
||||
}
|
||||
|
||||
if (!empty($errstr)) {
|
||||
|
||||
@@ -168,7 +168,6 @@ class ormCaseLog {
|
||||
return $aEntries;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a "plain text" version of the log (equivalent to $this->m_sLog) where all the HTML markup from the 'html' entries have been removed
|
||||
* @return string
|
||||
@@ -201,6 +200,15 @@ class ormCaseLog {
|
||||
{
|
||||
return ($this->m_sLog === null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int The number of entries in this log
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function GetEntryCount(): int
|
||||
{
|
||||
return count($this->m_aIndex);
|
||||
}
|
||||
|
||||
public function ClearModifiedFlag()
|
||||
{
|
||||
@@ -570,7 +578,6 @@ class ormCaseLog {
|
||||
$this->m_bModified = true;
|
||||
}
|
||||
|
||||
|
||||
public function AddLogEntryFromJSON($oJson, $bCheckUserId = true)
|
||||
{
|
||||
if (isset($oJson->user_id))
|
||||
@@ -647,7 +654,6 @@ class ormCaseLog {
|
||||
$this->m_bModified = true;
|
||||
}
|
||||
|
||||
|
||||
public function GetModifiedEntry($sFormat = 'text')
|
||||
{
|
||||
$sModifiedEntry = '';
|
||||
@@ -726,4 +732,3 @@ class ormCaseLog {
|
||||
return $sText;
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -16,6 +16,8 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
$ibo-breadcrumbs--margin-right: 16px !default;
|
||||
|
||||
$ibo-breadcrumbs--item--text-color: $ibo-color-grey-800 !default;
|
||||
|
||||
$ibo-breadcrumbs--item-icon--margin-x: 8px !default;
|
||||
@@ -27,9 +29,27 @@ $ibo-breadcrumbs--item-label--max-width: 100px !default;
|
||||
$ibo-breadcrumbs--item-separator--margin-x: 12px !default;
|
||||
$ibo-breadcrumbs--item-separator--text-color: $ibo-color-grey-500 !default;
|
||||
|
||||
$ibo-breadcrumbs--previous-items-list-toggler--text-color: $ibo-color-grey-700 !default;
|
||||
$ibo-breadcrumbs--previous-items-list-toggler--margin-right: 2 * $ibo-breadcrumbs--item-separator--margin-x !default;
|
||||
|
||||
$ibo-breadcrumbs--previous-items-list--top: 37px !default;
|
||||
$ibo-breadcrumbs--previous-items-list--padding-y: 8px !default;
|
||||
$ibo-breadcrumbs--previous-items-list--background-color: $ibo-color-white-100 !default;
|
||||
|
||||
$ibo-breadcrumbs--previous-item--text-color: $ibo-breadcrumbs--item--text-color !default;
|
||||
$ibo-breadcrumbs--previous-item--padding-x: 12px !default;
|
||||
$ibo-breadcrumbs--previous-item--padding-y: 12px !default;
|
||||
$ibo-breadcrumbs--previous-item-label--max-width: 200px !default;
|
||||
|
||||
.ibo-breadcrumbs{
|
||||
position: relative;
|
||||
margin-right: $ibo-breadcrumbs--margin-right;
|
||||
@extend %ibo-full-height-content;
|
||||
|
||||
&.ibo-is-overflowing {
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
* {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -40,13 +60,6 @@ $ibo-breadcrumbs--item-separator--text-color: $ibo-color-grey-500 !default;
|
||||
@extend %ibo-font-ral-med-100;
|
||||
|
||||
&:not(:last-child){
|
||||
&::after{
|
||||
content: '\f054';
|
||||
margin: 0 $ibo-breadcrumbs--item-separator--margin-x;
|
||||
color: $ibo-breadcrumbs--item-separator--text-color;
|
||||
@extend %fa-solid-base;
|
||||
}
|
||||
|
||||
&:hover{
|
||||
.ibo-breadcrumbs--item-icon{
|
||||
> *{
|
||||
@@ -78,3 +91,50 @@ $ibo-breadcrumbs--item-separator--text-color: $ibo-color-grey-500 !default;
|
||||
max-width: $ibo-breadcrumbs--item-label--max-width;
|
||||
@extend %ibo-text-truncated-with-ellipsis;
|
||||
}
|
||||
|
||||
.ibo-breadcrumbs--item,
|
||||
.ibo-breadcrumbs--previous-items-list-toggler {
|
||||
&:not(:last-child){
|
||||
&::after{
|
||||
content: '\f054';
|
||||
margin: 0 $ibo-breadcrumbs--item-separator--margin-x;
|
||||
color: $ibo-breadcrumbs--item-separator--text-color;
|
||||
@extend %fa-solid-base;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-breadcrumbs--previous-items-list-toggler {
|
||||
margin-right: $ibo-breadcrumbs--previous-items-list-toggler--margin-right;
|
||||
color: $ibo-breadcrumbs--previous-items-list-toggler--text-color !important;
|
||||
@extend %ibo-font-ral-med-100;
|
||||
|
||||
&:not(:last-child) {
|
||||
&::after {
|
||||
position: absolute; /* To be outside of the button */
|
||||
right: -1 * $ibo-breadcrumbs--previous-items-list-toggler--margin-right;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ibo-breadcrumbs--previous-items-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch; /* For the items to occupy the full width */
|
||||
|
||||
position: fixed;
|
||||
top: $ibo-breadcrumbs--previous-items-list--top;
|
||||
padding: $ibo-breadcrumbs--previous-items-list--padding-y 0;
|
||||
|
||||
background-color: $ibo-breadcrumbs--previous-items-list--background-color;
|
||||
@extend %ibo-elevation-300;
|
||||
}
|
||||
|
||||
.ibo-breadcrumbs--previous-item {
|
||||
color: $ibo-breadcrumbs--previous-item--text-color;
|
||||
@extend %ibo-font-ral-med-100;
|
||||
padding: $ibo-breadcrumbs--previous-item--padding-y $ibo-breadcrumbs--previous-item--padding-x;
|
||||
|
||||
.ibo-breadcrumbs--item-label {
|
||||
max-width: 200px;
|
||||
}
|
||||
}
|
||||
@@ -461,6 +461,7 @@ $ibo-button-colors: (
|
||||
}
|
||||
|
||||
.ibo-button {
|
||||
position: relative;
|
||||
display: inline-block; /* Used to allow truncated text on .ibo-button--label */
|
||||
padding: $ibo-button--padding-y $ibo-button--padding-x;
|
||||
border: $ibo-button--border;
|
||||
|
||||
@@ -36,11 +36,18 @@ $ibo-field--value-decoration--spacing-x: 0.5rem !default;
|
||||
/* Avoid value to overflow from its container with very long strings (typically URLs) */
|
||||
/* Note: Some types of attribute must be excluding as it can alter their rendering */
|
||||
&:not([data-attribute-type="AttributeBlob"]):not([data-attribute-type="AttributeFile"]):not([data-attribute-type="AttributeImage"]):not([data-attribute-type="AttributeCustomFields"]):not(.ibo-input-file-select--container) {
|
||||
/* We need the rule to apply for the class and all its descendants */
|
||||
/* We need the rule to apply for the class and all its descendants, hence the "&, & *" */
|
||||
.ibo-field--value {
|
||||
* {
|
||||
&, & * {
|
||||
word-break: break-word;
|
||||
white-space: pre-line;
|
||||
white-space: inherit; /* Here we don't put break-spaces as it would put ".ibo-field-small .ibo-field-value" on a new line due to the spaces/indentation of the HTML templates. For now we rather have this rule than diminish the templates readability/maintenability */
|
||||
}
|
||||
}
|
||||
&.ibo-field-large {
|
||||
.ibo-field--value {
|
||||
&, & * {
|
||||
white-space: break-spaces; /* For large fields we don't have the issue stated above */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,7 +64,6 @@ $ibo-field--value-decoration--spacing-x: 0.5rem !default;
|
||||
.ibo-field--label {
|
||||
position: relative; /* Necessary for fullscreen toggler */
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
max-width: initial;
|
||||
width: 100%;
|
||||
@@ -113,7 +119,7 @@ $ibo-field--value-decoration--spacing-x: 0.5rem !default;
|
||||
|
||||
@extend %ibo-hyperlink-inherited-colors;
|
||||
@extend %ibo-fully-centered-content;
|
||||
@extend %ibo-font-size-100;
|
||||
@extend %ibo-font-size-50;
|
||||
|
||||
&:hover {
|
||||
background-color: $ibo-field--fullscreen-toggler--background-color--on-hover;
|
||||
@@ -138,6 +144,9 @@ $ibo-field--value-decoration--spacing-x: 0.5rem !default;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ibo-field--label-small .ibo-field--label{
|
||||
width: 145px;
|
||||
}
|
||||
.ibo-field--value {
|
||||
display: table;
|
||||
width: 100%;
|
||||
|
||||
@@ -26,6 +26,8 @@ $ibo-top-bar--elements-spacing: 32px !default;
|
||||
|
||||
$ibo-top-bar--quick-actions--margin-right: $ibo-top-bar--elements-spacing !default;
|
||||
|
||||
$ibo-top-bar--toolbar-dashboard-title--max-width: 350px !default;
|
||||
|
||||
/* CSS variables (can be changed directly from the browser) */
|
||||
:root{
|
||||
--ibo-top-bar--height: #{$ibo-top-bar--height};
|
||||
@@ -65,6 +67,8 @@ $ibo-top-bar--quick-actions--margin-right: $ibo-top-bar--elements-spacing !defau
|
||||
|
||||
.ibo-top-bar--toolbar-dashboard-title {
|
||||
@extend %ibo-font-size-250;
|
||||
@extend %ibo-text-truncated-with-ellipsis;
|
||||
max-width: $ibo-top-bar--toolbar-dashboard-title--max-width;
|
||||
}
|
||||
|
||||
.ibo-top-bar--toolbar-dashboard-menu-toggler {
|
||||
|
||||
@@ -202,17 +202,6 @@ $ibo-activity-panel--load-all-entries--is-hover--margin-left: ($ibo-activity-pan
|
||||
flex-grow: 1; /* So it occupies all the remaining width, which is easier for the user to click */
|
||||
word-break: break-word; /* To avoid content to overflow its container (typically very long URLs) */
|
||||
|
||||
/* Avoid pre (code snippets) to overflow outside the entry */
|
||||
pre {
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
/* Avoid table to overflow outside the entry (see N°2127) */
|
||||
table {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Specific hyperlink color */
|
||||
a {
|
||||
color: $ibo-activity-entry--main-information-hyperlink--text-color;
|
||||
|
||||
@@ -96,9 +96,12 @@ $ibo-activity-panel--add-caselog-entry-button--right: 12px !default;
|
||||
$ibo-activity-panel--add-caselog-entry-button--top: 76px + $ibo-activity-panel--add-caselog-entry-button--right !default;
|
||||
$ibo-activity-panel--add-caselog-entry-button--diameter: 36px !default;
|
||||
$ibo-activity-panel--add-caselog-entry-button--background-color: $ibo-color-primary-600 !default;
|
||||
$ibo-activity-panel--add-caselog-entry-button--background-color--on-hover: $ibo-color-primary-500 !default;
|
||||
$ibo-activity-panel--add-caselog-entry-button--background-color--is-active: $ibo-color-primary-700 !default;
|
||||
$ibo-activity-panel--add-caselog-entry-button--color: $ibo-color-white-100 !default;
|
||||
$ibo-activity-panel--add-caselog-entry-button--border-radius: $ibo-border-radius-full !default;
|
||||
$ibo-activity-panel--add-caselog-entry-button--box-shadow: $ibo-elevation-100 !default;
|
||||
$ibo-activity-panel--add-caselog-entry-button--hover--box-shadow: $ibo-elevation-200 !default;
|
||||
|
||||
$ibo-activity-panel--add-caselog-entry-button--icon--height: 100% !default;
|
||||
$ibo-activity-panel--add-caselog-entry-button--icon--width: $ibo-activity-panel--add-caselog-entry-button--icon--height !default;
|
||||
@@ -379,6 +382,7 @@ $ibo-activity-panel--open-icon--margin-left: 0.75rem !default;
|
||||
|
||||
border-radius: $ibo-activity-panel--add-caselog-entry-button--border-radius;
|
||||
box-shadow: $ibo-activity-panel--add-caselog-entry-button--box-shadow;
|
||||
|
||||
> i{
|
||||
text-align: center;
|
||||
height: $ibo-activity-panel--add-caselog-entry-button--icon--height;
|
||||
@@ -388,7 +392,13 @@ $ibo-activity-panel--open-icon--margin-left: 0.75rem !default;
|
||||
}
|
||||
&:hover {
|
||||
color: $ibo-activity-panel--add-caselog-entry-button--color;
|
||||
}
|
||||
background-color: $ibo-activity-panel--add-caselog-entry-button--background-color--on-hover;
|
||||
box-shadow: $ibo-activity-panel--add-caselog-entry-button--hover--box-shadow;
|
||||
}
|
||||
&:active {
|
||||
color: $ibo-activity-panel--add-caselog-entry-button--color;
|
||||
background-color: $ibo-activity-panel--add-caselog-entry-button--background-color--is-active;
|
||||
}
|
||||
&.ibo-is-hidden{
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -137,6 +137,7 @@ $ibo-tab--temporary-remote-content--button--hover--color: $ibo-color-grey-200 !d
|
||||
|
||||
.ibo-tab-container--tab-container {
|
||||
padding: $ibo-tab-container--tab-container--padding-y $ibo-tab-container--tab-container--padding-x;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.ibo-is-scrollable .ibo-tab-container--tab-container--label {
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
$ibo-class-icon--small--size: 32px !default;
|
||||
$ibo-class-icon--medium--size: 48px !default;
|
||||
$ibo-class-icon--large--size: 64px !default;
|
||||
|
||||
.ibo-class-icon{
|
||||
&.ibo-is-small{
|
||||
width: 32px;
|
||||
min-width: 32px;
|
||||
max-height: 32px;
|
||||
width: $ibo-class-icon--small--size;
|
||||
min-width: $ibo-class-icon--small--size;
|
||||
max-height: $ibo-class-icon--small--size;
|
||||
}
|
||||
&.ibo-is-medium{
|
||||
width: 48px;
|
||||
min-width: 48px;
|
||||
max-height: 48px;
|
||||
width: $ibo-class-icon--medium--size;
|
||||
min-width: $ibo-class-icon--medium--size;
|
||||
max-height: $ibo-class-icon--medium--size;
|
||||
}
|
||||
&.ibo-is-large{
|
||||
width: 64px;
|
||||
min-width: 64px;
|
||||
max-height: 64px;
|
||||
width: $ibo-class-icon--large--size;
|
||||
min-width: $ibo-class-icon--large--size;
|
||||
max-height: $ibo-class-icon--large--size;
|
||||
}
|
||||
}
|
||||
@@ -97,7 +97,7 @@ body.ibo-has-fullscreen-descendant {
|
||||
.ibo-has-fullscreen-descendant {
|
||||
position: static !important;
|
||||
overflow: visible !important;
|
||||
z-index: 9000 !important;
|
||||
z-index: 1050 !important;
|
||||
}
|
||||
|
||||
/* Used on a fullscreen element (see .ibo-has-fullscreen-descendant) */
|
||||
@@ -110,7 +110,7 @@ body.ibo-has-fullscreen-descendant {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
z-index: 9000;
|
||||
z-index: 1050;
|
||||
}
|
||||
|
||||
/****************/
|
||||
@@ -159,8 +159,11 @@ body.ibo-has-fullscreen-descendant {
|
||||
width: unset !important;
|
||||
max-width: max-content;
|
||||
}
|
||||
code {
|
||||
color: initial;
|
||||
|
||||
/* Preserve original text color in code blocks, except for the Highlight.js blocks which have their own colors */
|
||||
& > code,
|
||||
:not(pre.hljs) code {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
3
css/backoffice/vendors/_all.scss
vendored
3
css/backoffice/vendors/_all.scss
vendored
@@ -24,4 +24,5 @@
|
||||
@import "jquery-multiselect";
|
||||
@import "datatables";
|
||||
@import "jquery-treeview";
|
||||
@import "jquery-blockui";
|
||||
@import "jquery-blockui";
|
||||
@import "magnific-popup";
|
||||
7
css/backoffice/vendors/_datatables.scss
vendored
7
css/backoffice/vendors/_datatables.scss
vendored
@@ -23,9 +23,12 @@ $ibo-vendors-datatables--page-length-selector--border-color: $ibo-color-grey-500
|
||||
$ibo-vendors-datatables--page-length-selector--border-radius: $ibo-border-radius-300 !default;
|
||||
$ibo-vendors-datatables--page-length-selector--border-color--on-focus: $ibo-color-primary-600 !default;
|
||||
|
||||
$ibo-vendors-datatables--cell--padding-x: 12px !default;
|
||||
$ibo-vendors-datatables--cell--padding-y: 10px !default;
|
||||
|
||||
$ibo-vendors-datatables--column-sorting-icon--opacity: 0.3 !default;
|
||||
$ibo-vendors-datatables--column-sorting-icon--opacity--is-sorted: 1 !default;
|
||||
$ibo-vendors-datatables--column-sorting-icon--right: 1em !default;
|
||||
$ibo-vendors-datatables--column-sorting-icon--right: calc((#{$ibo-vendors-datatables--cell--padding-x} - 8px) / 2) !default;
|
||||
$ibo-vendors-datatables--column-sorting-icon--content: "\f0dc" !default;
|
||||
$ibo-vendors-datatables--column-sorting-icon--content--is-sorted-asc: "\f0d8" !default;
|
||||
$ibo-vendors-datatables--column-sorting-icon--content--is-sorted-desc: "\f0d7" !default;
|
||||
@@ -137,7 +140,7 @@ $ibo-vendors-datatables--row-highlight--colors:(
|
||||
.dataTable {
|
||||
th, td {
|
||||
position: relative;
|
||||
padding: 10px 12px;
|
||||
padding: $ibo-vendors-datatables--cell--padding-y $ibo-vendors-datatables--cell--padding-x;
|
||||
@extend %ibo-font-ral-med-100; /* Necessary to set the font-size as its container can have a different one */
|
||||
}
|
||||
|
||||
|
||||
6
css/backoffice/vendors/_magnific-popup.scss
vendored
Normal file
6
css/backoffice/vendors/_magnific-popup.scss
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
.mfp-bg{
|
||||
z-index: 1100;
|
||||
}
|
||||
.mfp-wrap{
|
||||
z-index: 1101;
|
||||
}
|
||||
@@ -179,7 +179,9 @@ a:hover {
|
||||
.v-spacer {
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
#login-sso-buttons{
|
||||
padding: 0 10px;
|
||||
}
|
||||
.sso-button {
|
||||
margin: 5px 0px;
|
||||
position: relative;
|
||||
|
||||
@@ -170,10 +170,6 @@ JS
|
||||
require_once(dirname(__FILE__).'/dbrestore.class.inc.php');
|
||||
|
||||
$sEnvironment = utils::ReadParam('environment', 'production', false, 'raw_data');
|
||||
$oRestoreMutex = new iTopMutex('restore.'.$sEnvironment);
|
||||
IssueLog::Info("Backup Restore - Acquiring the LOCK 'restore.$sEnvironment'");
|
||||
$oRestoreMutex->Lock();
|
||||
IssueLog::Info('Backup Restore - LOCK acquired, executing...');
|
||||
try
|
||||
{
|
||||
set_time_limit(0);
|
||||
@@ -203,7 +199,6 @@ JS
|
||||
finally
|
||||
{
|
||||
unlink($tokenRealPath);
|
||||
$oRestoreMutex->Unlock();
|
||||
}
|
||||
|
||||
$oPage->output();
|
||||
|
||||
@@ -30,9 +30,6 @@ if (!defined('APPROOT'))
|
||||
}
|
||||
}
|
||||
require_once(APPROOT.'application/application.inc.php');
|
||||
require_once(APPROOT.'application/webpage.class.inc.php');
|
||||
require_once(APPROOT.'application/csvpage.class.inc.php');
|
||||
require_once(APPROOT.'application/clipage.class.inc.php');
|
||||
require_once(APPROOT.'application/ajaxwebpage.class.inc.php');
|
||||
|
||||
require_once(APPROOT.'core/log.class.inc.php');
|
||||
|
||||
@@ -28,8 +28,8 @@ class DBRestore extends DBBackup
|
||||
{
|
||||
parent::__construct($oConfig);
|
||||
|
||||
$this->sDBUser = $oConfig->Get('db_user');
|
||||
$this->sDBPwd = $oConfig->Get('db_pwd');
|
||||
$this->sDBUser = $this->oConfig->Get('db_user');
|
||||
$this->sDBPwd = $this->oConfig->Get('db_pwd');
|
||||
}
|
||||
|
||||
protected function LogInfo($sMsg)
|
||||
@@ -127,79 +127,88 @@ class DBRestore extends DBBackup
|
||||
*/
|
||||
public function RestoreFromCompressedBackup($sFile, $sEnvironment = 'production')
|
||||
{
|
||||
$this->LogInfo("Starting restore of ".basename($sFile));
|
||||
$oRestoreMutex = new iTopMutex('restore.'.$sEnvironment);
|
||||
IssueLog::Info("Backup Restore - Acquiring the LOCK 'restore.$sEnvironment'");
|
||||
$oRestoreMutex->Lock();
|
||||
|
||||
$sNormalizedFile = strtolower(basename($sFile));
|
||||
if (substr($sNormalizedFile, -4) == '.zip')
|
||||
{
|
||||
$this->LogInfo('zip file detected');
|
||||
$oArchive = new ZipArchiveEx();
|
||||
$oArchive->open($sFile);
|
||||
}
|
||||
elseif (substr($sNormalizedFile, -7) == '.tar.gz')
|
||||
{
|
||||
$this->LogInfo('tar.gz file detected');
|
||||
$oArchive = new TarGzArchive($sFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BackupException('Unsupported format for a backup file: '.$sFile);
|
||||
}
|
||||
try {
|
||||
IssueLog::Info('Backup Restore - LOCK acquired, executing...');
|
||||
$bReadonlyBefore = SetupUtils::EnterReadOnlyMode(MetaModel::GetConfig());
|
||||
|
||||
// Load the database
|
||||
//
|
||||
$sDataDir = APPROOT.'data/tmp-backup-'.rand(10000, getrandmax());
|
||||
try {
|
||||
//safe zone for db backup => cron is stopped/ itop in readonly
|
||||
$this->LogInfo("Starting restore of ".basename($sFile));
|
||||
|
||||
SetupUtils::builddir($sDataDir); // Here is the directory
|
||||
$oArchive->extractTo($sDataDir);
|
||||
|
||||
$sDataFile = $sDataDir.'/itop-dump.sql';
|
||||
$this->LoadDatabase($sDataFile);
|
||||
$sNormalizedFile = strtolower(basename($sFile));
|
||||
if (substr($sNormalizedFile, -4) == '.zip') {
|
||||
$this->LogInfo('zip file detected');
|
||||
$oArchive = new ZipArchiveEx();
|
||||
$oArchive->open($sFile);
|
||||
} elseif (substr($sNormalizedFile, -7) == '.tar.gz') {
|
||||
$this->LogInfo('tar.gz file detected');
|
||||
$oArchive = new TarGzArchive($sFile);
|
||||
} else {
|
||||
throw new BackupException('Unsupported format for a backup file: '.$sFile);
|
||||
}
|
||||
|
||||
// Update the code
|
||||
//
|
||||
$sDeltaFile = APPROOT.'data/'.$sEnvironment.'.delta.xml';
|
||||
// Load the database
|
||||
//
|
||||
$sDataDir = APPROOT.'data/tmp-backup-'.rand(10000, getrandmax());
|
||||
|
||||
if (is_file($sDataDir.'/delta.xml'))
|
||||
{
|
||||
// Extract and rename delta.xml => <env>.delta.xml;
|
||||
rename($sDataDir.'/delta.xml', $sDeltaFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
@unlink($sDeltaFile);
|
||||
}
|
||||
if (is_dir(APPROOT.'data/production-modules/'))
|
||||
{
|
||||
try
|
||||
{
|
||||
SetupUtils::rrmdir(APPROOT.'data/production-modules/');
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
throw new BackupException("Can't remove production-modules dir", 0, $e);
|
||||
SetupUtils::builddir($sDataDir); // Here is the directory
|
||||
$oArchive->extractTo($sDataDir);
|
||||
|
||||
$sDataFile = $sDataDir.'/itop-dump.sql';
|
||||
$this->LoadDatabase($sDataFile);
|
||||
|
||||
// Update the code
|
||||
//
|
||||
$sDeltaFile = APPROOT.'data/'.$sEnvironment.'.delta.xml';
|
||||
|
||||
if (is_file($sDataDir.'/delta.xml')) {
|
||||
// Extract and rename delta.xml => <env>.delta.xml;
|
||||
rename($sDataDir.'/delta.xml', $sDeltaFile);
|
||||
} else {
|
||||
@unlink($sDeltaFile);
|
||||
}
|
||||
if (is_dir(APPROOT.'data/production-modules/')) {
|
||||
try {
|
||||
SetupUtils::rrmdir(APPROOT.'data/production-modules/');
|
||||
} catch (Exception $e) {
|
||||
throw new BackupException("Can't remove production-modules dir", 0, $e);
|
||||
}
|
||||
}
|
||||
if (is_dir($sDataDir.'/production-modules')) {
|
||||
rename($sDataDir.'/production-modules', APPROOT.'data/production-modules/');
|
||||
}
|
||||
|
||||
$sConfigFile = APPROOT.'conf/'.$sEnvironment.'/config-itop.php';
|
||||
@chmod($sConfigFile, 0770); // Allow overwriting the file
|
||||
rename($sDataDir.'/config-itop.php', $sConfigFile);
|
||||
@chmod($sConfigFile, 0440); // Read-only
|
||||
|
||||
try {
|
||||
SetupUtils::rrmdir($sDataDir);
|
||||
} catch (Exception $e) {
|
||||
throw new BackupException("Can't remove data dir", 0, $e);
|
||||
}
|
||||
|
||||
$oEnvironment = new RunTimeEnvironment($sEnvironment);
|
||||
$oEnvironment->CompileFrom($sEnvironment);
|
||||
} finally {
|
||||
if (! $bReadonlyBefore) {
|
||||
SetupUtils::ExitReadOnlyMode();
|
||||
} else {
|
||||
//we are in the scope of main process that needs to handle/keep readonly mode.
|
||||
$this->LogInfo("Keep readonly mode after restore");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_dir($sDataDir.'/production-modules'))
|
||||
finally
|
||||
{
|
||||
rename($sDataDir.'/production-modules', APPROOT.'data/production-modules/');
|
||||
IssueLog::Info('Backup Restore - LOCK released.');
|
||||
$oRestoreMutex->Unlock();
|
||||
}
|
||||
|
||||
$sConfigFile = APPROOT.'conf/'.$sEnvironment.'/config-itop.php';
|
||||
@chmod($sConfigFile, 0770); // Allow overwriting the file
|
||||
rename($sDataDir.'/config-itop.php', $sConfigFile);
|
||||
@chmod($sConfigFile, 0440); // Read-only
|
||||
|
||||
try
|
||||
{
|
||||
SetupUtils::rrmdir($sDataDir);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
throw new BackupException("Can't remove data dir", 0, $e);
|
||||
}
|
||||
|
||||
$oEnvironment = new RunTimeEnvironment($sEnvironment);
|
||||
$oEnvironment->CompileFrom($sEnvironment);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Set>
|
||||
<DatabaseSchema alias="DatabaseSchema" id="20">
|
||||
<name>openerpprod</name>
|
||||
<name>odooprod</name>
|
||||
<description></description>
|
||||
<org_id>2</org_id>
|
||||
<business_criticity>low</business_criticity>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Set>
|
||||
<OSVersion alias="OSVersion" id="8">
|
||||
<name>Unbuntu 11.10</name>
|
||||
<name>Ubuntu 20.04</name>
|
||||
<osfamily_id>7</osfamily_id>
|
||||
</OSVersion>
|
||||
<OSVersion alias="OSVersion" id="4">
|
||||
<name>Windows 2008 Server</name>
|
||||
<name>Windows 2019 Server</name>
|
||||
<osfamily_id>3</osfamily_id>
|
||||
</OSVersion>
|
||||
</Set>
|
||||
|
||||
@@ -90,8 +90,8 @@
|
||||
<brand_id>1</brand_id>
|
||||
<model_id>4</model_id>
|
||||
<asset_number></asset_number>
|
||||
<purchase_date>2011-05-22</purchase_date>
|
||||
<end_of_warranty>2013-05-21</end_of_warranty>
|
||||
<purchase_date>2021-07-30</purchase_date>
|
||||
<end_of_warranty>2025-07-29</end_of_warranty>
|
||||
<rack_id>0</rack_id>
|
||||
<enclosure_id>0</enclosure_id>
|
||||
<nb_u></nb_u>
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
<Software alias="Software" id="2">
|
||||
<name>MySql</name>
|
||||
<vendor>Oracle</vendor>
|
||||
<version>5.3</version>
|
||||
<version>8</version>
|
||||
<type>DBServer</type>
|
||||
</Software>
|
||||
<Software alias="Software" id="1">
|
||||
<name>Oracle DB engine</name>
|
||||
<vendor>Oracle</vendor>
|
||||
<version>11i</version>
|
||||
<version>19c</version>
|
||||
<type>DBServer</type>
|
||||
</Software>
|
||||
</Set>
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Set>
|
||||
<WebApplication alias="WebApplication" id="25">
|
||||
<name>Open ERP</name>
|
||||
<name>Odoo</name>
|
||||
<description></description>
|
||||
<org_id>2</org_id>
|
||||
<business_criticity>low</business_criticity>
|
||||
|
||||
@@ -703,6 +703,7 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
|
||||
'UI:RunQuery:HeaderOQLExpression' => 'OQL dotaz',
|
||||
'UI:RunQuery:HeaderOQLExpression+' => 'Dotaz v OQL syntaxi',
|
||||
'UI:RunQuery:ExpressionToEvaluate' => 'Dotaz k vyhodnocení: ',
|
||||
'UI:RunQuery:QueryArguments' => 'Query Arguments~~',
|
||||
'UI:RunQuery:MoreInfo' => 'Více informací o dotazu: ',
|
||||
'UI:RunQuery:DevelopedQuery' => 'Rekonstruovaný dotaz: ',
|
||||
'UI:RunQuery:SerializedFilter' => 'Serializovaný filtr: ',
|
||||
|
||||
@@ -692,6 +692,7 @@ Dict::Add('DA DA', 'Danish', 'Dansk', array(
|
||||
'UI:RunQuery:HeaderOQLExpression' => 'OQL Udtryk',
|
||||
'UI:RunQuery:HeaderOQLExpression+' => 'Forespørgslen i OQL syntaks',
|
||||
'UI:RunQuery:ExpressionToEvaluate' => 'Udtryk der skal evalueres: ',
|
||||
'UI:RunQuery:QueryArguments' => 'Query Arguments~~',
|
||||
'UI:RunQuery:MoreInfo' => 'Mere information om forespørgslen: ',
|
||||
'UI:RunQuery:DevelopedQuery' => 'Videreudviklet forespørgselsudtryk: ',
|
||||
'UI:RunQuery:SerializedFilter' => 'Serielt filter: ',
|
||||
|
||||
@@ -692,6 +692,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'UI:RunQuery:HeaderOQLExpression' => 'OQL-Ausdruck',
|
||||
'UI:RunQuery:HeaderOQLExpression+' => 'Die Abfrage in OQL-Syntax',
|
||||
'UI:RunQuery:ExpressionToEvaluate' => 'Auszuwertender Ausdruck: ',
|
||||
'UI:RunQuery:QueryArguments' => 'Query Arguments~~',
|
||||
'UI:RunQuery:MoreInfo' => 'Mehr Informationen zur Abfrage: ',
|
||||
'UI:RunQuery:DevelopedQuery' => 'Überarbeiteter Abfrageausdruck: ',
|
||||
'UI:RunQuery:SerializedFilter' => 'Serieller Filter: ',
|
||||
|
||||
@@ -720,6 +720,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
||||
'UI:RunQuery:HeaderOQLExpression' => 'OQL Expression',
|
||||
'UI:RunQuery:HeaderOQLExpression+' => 'The query in OQL syntax',
|
||||
'UI:RunQuery:ExpressionToEvaluate' => 'Expression to evaluate: ',
|
||||
'UI:RunQuery:QueryArguments' => 'Query Arguments',
|
||||
'UI:RunQuery:MoreInfo' => 'More information about the query: ',
|
||||
'UI:RunQuery:DevelopedQuery' => 'Redevelopped query expression: ',
|
||||
'UI:RunQuery:SerializedFilter' => 'Serialized filter: ',
|
||||
|
||||
@@ -703,6 +703,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array(
|
||||
'UI:RunQuery:HeaderOQLExpression' => 'Expresión OQL',
|
||||
'UI:RunQuery:HeaderOQLExpression+' => 'La consulta en sintáxis OQL',
|
||||
'UI:RunQuery:ExpressionToEvaluate' => 'Expresión a evaluar: ',
|
||||
'UI:RunQuery:QueryArguments' => 'Query Arguments~~',
|
||||
'UI:RunQuery:MoreInfo' => 'Más información acerca de la consulta: ',
|
||||
'UI:RunQuery:DevelopedQuery' => 'Expresión de consulta rediseñada: ',
|
||||
'UI:RunQuery:SerializedFilter' => 'Filtro de serialización: ',
|
||||
|
||||
@@ -703,6 +703,7 @@ Nous espérons que vous aimerez cette version autant que nous avons eu du plaisi
|
||||
'UI:RunQuery:HeaderOQLExpression' => 'Requête OQL',
|
||||
'UI:RunQuery:HeaderOQLExpression+' => 'La requête en OQL',
|
||||
'UI:RunQuery:ExpressionToEvaluate' => 'Requête à exécuter : ',
|
||||
'UI:RunQuery:QueryArguments' => 'Arguments de la requête',
|
||||
'UI:RunQuery:MoreInfo' => 'Plus d\'information sur la requête : ',
|
||||
'UI:RunQuery:DevelopedQuery' => 'Requête OQL décompilée : ',
|
||||
'UI:RunQuery:SerializedFilter' => 'Version sérialisée : ',
|
||||
|
||||
@@ -692,6 +692,7 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
|
||||
'UI:RunQuery:HeaderOQLExpression' => 'OQL kifejezés',
|
||||
'UI:RunQuery:HeaderOQLExpression+' => '',
|
||||
'UI:RunQuery:ExpressionToEvaluate' => 'Értékelendő kifejezés: ',
|
||||
'UI:RunQuery:QueryArguments' => 'Query Arguments~~',
|
||||
'UI:RunQuery:MoreInfo' => 'Több információ a lekérdezésről: ',
|
||||
'UI:RunQuery:DevelopedQuery' => 'Újraírt lekérdező értékelés: ',
|
||||
'UI:RunQuery:SerializedFilter' => 'Szerializált szűrő: ',
|
||||
|
||||
@@ -703,6 +703,7 @@ Dict::Add('IT IT', 'Italian', 'Italiano', array(
|
||||
'UI:RunQuery:HeaderOQLExpression' => 'Espressioni OQL',
|
||||
'UI:RunQuery:HeaderOQLExpression+' => '',
|
||||
'UI:RunQuery:ExpressionToEvaluate' => 'Espressione da valutare: ',
|
||||
'UI:RunQuery:QueryArguments' => 'Query Arguments~~',
|
||||
'UI:RunQuery:MoreInfo' => 'Maggiori informazioni sulla query: ',
|
||||
'UI:RunQuery:DevelopedQuery' => 'Espressione della query riqualificata:',
|
||||
'UI:RunQuery:SerializedFilter' => 'Filtro pubblicato: ',
|
||||
|
||||
@@ -692,6 +692,7 @@ Dict::Add('JA JP', 'Japanese', '日本語', array(
|
||||
'UI:RunQuery:HeaderOQLExpression' => 'OQL式',
|
||||
'UI:RunQuery:HeaderOQLExpression+' => 'OQL文法によるクエリ',
|
||||
'UI:RunQuery:ExpressionToEvaluate' => '評価式',
|
||||
'UI:RunQuery:QueryArguments' => 'Query Arguments~~',
|
||||
'UI:RunQuery:MoreInfo' => 'クエリに関する追加情報',
|
||||
'UI:RunQuery:DevelopedQuery' => 'クエリ式の再開発',
|
||||
'UI:RunQuery:SerializedFilter' => 'シリアライズされたフィルタ:',
|
||||
|
||||
@@ -703,6 +703,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
|
||||
'UI:RunQuery:HeaderOQLExpression' => 'OQL-expressie',
|
||||
'UI:RunQuery:HeaderOQLExpression+' => 'De query in OQL syntax',
|
||||
'UI:RunQuery:ExpressionToEvaluate' => 'Expressie om te evalueren: ',
|
||||
'UI:RunQuery:QueryArguments' => 'Query Arguments~~',
|
||||
'UI:RunQuery:MoreInfo' => 'Meer informatie over de query: ',
|
||||
'UI:RunQuery:DevelopedQuery' => 'Herschreven query-expressie: ',
|
||||
'UI:RunQuery:SerializedFilter' => 'Geserialiseerde filter: ',
|
||||
|
||||
@@ -705,6 +705,7 @@ Dict::Add('PL PL', 'Polish', 'Polski', array(
|
||||
'UI:RunQuery:HeaderOQLExpression' => 'Wyrażenie OQL',
|
||||
'UI:RunQuery:HeaderOQLExpression+' => 'Zapytanie w składni OQL',
|
||||
'UI:RunQuery:ExpressionToEvaluate' => 'Wyrażenie do wykonania: ',
|
||||
'UI:RunQuery:QueryArguments' => 'Query Arguments~~',
|
||||
'UI:RunQuery:MoreInfo' => 'Więcej informacji o zapytaniu: ',
|
||||
'UI:RunQuery:DevelopedQuery' => 'Rozwinięte wyrażenie zapytania: ',
|
||||
'UI:RunQuery:SerializedFilter' => 'Filtr serializowany: ',
|
||||
|
||||
@@ -703,6 +703,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
|
||||
'UI:RunQuery:HeaderOQLExpression' => 'A consulta na sintaxe OQL',
|
||||
'UI:RunQuery:HeaderOQLExpression+' => 'A consulta na sintaxe OQL',
|
||||
'UI:RunQuery:ExpressionToEvaluate' => 'Expressão para avaliar: ',
|
||||
'UI:RunQuery:QueryArguments' => 'Query Arguments~~',
|
||||
'UI:RunQuery:MoreInfo' => 'Mais informações sobre a consulta: ',
|
||||
'UI:RunQuery:DevelopedQuery' => 'Redevelopped query expression: ',
|
||||
'UI:RunQuery:SerializedFilter' => 'Filtro serializado: ',
|
||||
|
||||
@@ -704,6 +704,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
|
||||
'UI:RunQuery:HeaderOQLExpression' => 'Выражение OQL',
|
||||
'UI:RunQuery:HeaderOQLExpression+' => 'Запрос в синтаксисе OQL',
|
||||
'UI:RunQuery:ExpressionToEvaluate' => 'Оценка выражения: ',
|
||||
'UI:RunQuery:QueryArguments' => 'Query Arguments~~',
|
||||
'UI:RunQuery:MoreInfo' => 'Подробная информация о запросе: ',
|
||||
'UI:RunQuery:DevelopedQuery' => 'Декомпилированный запрос OQL: ',
|
||||
'UI:RunQuery:SerializedFilter' => 'Сериализованная версия: ',
|
||||
|
||||
@@ -693,6 +693,7 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', array(
|
||||
'UI:RunQuery:HeaderOQLExpression' => 'OQL Výraz',
|
||||
'UI:RunQuery:HeaderOQLExpression+' => '',
|
||||
'UI:RunQuery:ExpressionToEvaluate' => 'Výraz k určeniu: ',
|
||||
'UI:RunQuery:QueryArguments' => 'Query Arguments~~',
|
||||
'UI:RunQuery:MoreInfo' => 'Viac informácií o dopyte: ',
|
||||
'UI:RunQuery:DevelopedQuery' => 'Dopyt rozvinutého výrazu: ',
|
||||
'UI:RunQuery:SerializedFilter' => 'Serializovaný filter: ',
|
||||
|
||||
@@ -703,6 +703,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
|
||||
'UI:RunQuery:HeaderOQLExpression' => 'OQL ifadesi',
|
||||
'UI:RunQuery:HeaderOQLExpression+' => 'OQL yapısında sorgu',
|
||||
'UI:RunQuery:ExpressionToEvaluate' => 'Değerlendirilecek ifade: ',
|
||||
'UI:RunQuery:QueryArguments' => 'Query Arguments~~',
|
||||
'UI:RunQuery:MoreInfo' => 'Sorgu hakkında detaylı bilgi: ',
|
||||
'UI:RunQuery:DevelopedQuery' => 'Yeniden düzenlenen sorgu: ',
|
||||
'UI:RunQuery:SerializedFilter' => 'Özel filtre: ',
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2013-2021 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
|
||||
*/
|
||||
|
||||
// Global search
|
||||
Dict::Add('EN US', 'English', 'English', array(
|
||||
'UI:Component:Breadcrumbs:PreviousItemsListToggler:Label' => 'Previous pages',
|
||||
));
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2013-2021 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
|
||||
*/
|
||||
|
||||
// Global search
|
||||
Dict::Add('FR FR', 'French', 'Français', array(
|
||||
'UI:Component:Breadcrumbs:PreviousItemsListToggler:Label' => 'Pages précédentes',
|
||||
));
|
||||
@@ -703,6 +703,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
'UI:RunQuery:HeaderOQLExpression' => 'OQL 表达式',
|
||||
'UI:RunQuery:HeaderOQLExpression+' => 'OQL 语法表示的查询',
|
||||
'UI:RunQuery:ExpressionToEvaluate' => '请输入表达式: ',
|
||||
'UI:RunQuery:QueryArguments' => 'Query Arguments~~',
|
||||
'UI:RunQuery:MoreInfo' => '该查询的更多信息: ',
|
||||
'UI:RunQuery:DevelopedQuery' => '重新开发的查询表达式: ',
|
||||
'UI:RunQuery:SerializedFilter' => '序列化的过滤器: ',
|
||||
|
||||
@@ -30,6 +30,26 @@ $(function()
|
||||
new_entry: null,
|
||||
max_count: 8
|
||||
},
|
||||
css_classes:
|
||||
{
|
||||
is_hidden: 'ibo-is-hidden',
|
||||
is_transparent: 'ibo-is-transparent',
|
||||
is_opaque: 'ibo-is-opaque',
|
||||
is_overflowing: 'ibo-is-overflowing',
|
||||
breadcrumbs_item: 'ibo-breadcrumbs--item',
|
||||
breadcrumbs_previous_item: 'ibo-breadcrumbs--previous-item',
|
||||
},
|
||||
js_selectors:
|
||||
{
|
||||
breadcrumbs: '[data-role="ibo-breadcrumbs"]',
|
||||
item: '[data-role="ibo-breadcrumbs--item"]',
|
||||
previous_items_container: '[data-role="ibo-breadcrumbs--previous-items-container"]',
|
||||
previous_items_list_toggler: '[data-role="ibo-breadcrumbs--previous-items-list-toggler"]',
|
||||
previous_items_list: '[data-role="ibo-breadcrumbs--previous-items-list"]',
|
||||
previous_item: '[data-role="ibo-breadcrumbs--previous-item"]',
|
||||
},
|
||||
|
||||
items_observer: null,
|
||||
|
||||
// the constructor
|
||||
_create: function()
|
||||
@@ -41,7 +61,7 @@ $(function()
|
||||
// Check that storage API is available
|
||||
if(typeof(Storage) !== 'undefined')
|
||||
{
|
||||
$(window).bind('hashchange', function(e)
|
||||
$(window).on('hashchange', function(e)
|
||||
{
|
||||
me.RefreshLatestEntry();
|
||||
});
|
||||
@@ -50,7 +70,7 @@ $(function()
|
||||
|
||||
if (this.options.new_entry !== null) {
|
||||
var sUrl = this.options.new_entry.url;
|
||||
if (sUrl.length == 0) {
|
||||
if (sUrl.length === 0) {
|
||||
sUrl = window.location.href;
|
||||
}
|
||||
// Eliminate items having the same id, before appending the new item
|
||||
@@ -71,6 +91,8 @@ $(function()
|
||||
}
|
||||
this._writeDataToStorage(aBreadCrumb);
|
||||
|
||||
// Build markup
|
||||
// - Add entries to the markup
|
||||
for (iEntry in aBreadCrumb)
|
||||
{
|
||||
var sBreadcrumbsItemHtml = '';
|
||||
@@ -90,30 +112,131 @@ $(function()
|
||||
|
||||
var sTitle = oEntry['description'],
|
||||
sLabel = oEntry['label'];
|
||||
if (sTitle.length == 0) {
|
||||
if (sTitle.length === 0) {
|
||||
sTitle = sLabel;
|
||||
}
|
||||
sTitle = EncodeHtml(sTitle, false);
|
||||
sLabel = EncodeHtml(sLabel, false);
|
||||
|
||||
if ((this.options.new_entry !== null) && (iEntry == aBreadCrumb.length - 1)) {
|
||||
if ((this.options.new_entry !== null) && (iEntry === aBreadCrumb.length - 1)) {
|
||||
// Last entry is the current page
|
||||
sBreadcrumbsItemHtml += '<span class="ibo-breadcrumbs--item--is-current" data-breadcrumb-entry-number="'+iEntry+'" title="'+sTitle+'">'+sIconSpec+'<span class="ibo-breadcrumbs--item-label">'+sLabel+'</span></span>';
|
||||
sBreadcrumbsItemHtml += '<span class="ibo-is-current" data-role="" data-breadcrumb-entry-number="'+iEntry+'" title="'+sTitle+'">'+sIconSpec+'<span class="ibo-breadcrumbs--item-label">'+sLabel+'</span></span>';
|
||||
} else {
|
||||
var sSanitizedUrl = StripArchiveArgument(oEntry['url']);
|
||||
sBreadcrumbsItemHtml += '<a class="ibo-breadcrumbs--item" data-breadcrumb-entry-number="'+iEntry+'" href="'+sSanitizedUrl+'" title="'+sTitle+'">'+sIconSpec+'<span class="ibo-breadcrumbs--item-label">'+sLabel+'</span></a>';
|
||||
sBreadcrumbsItemHtml += '<a class="" data-role="" data-breadcrumb-entry-number="'+iEntry+'" href="'+sSanitizedUrl+'" title="'+sTitle+'">'+sIconSpec+'<span class="ibo-breadcrumbs--item-label">'+sLabel+'</span></a>';
|
||||
}
|
||||
}
|
||||
this.element.append(sBreadcrumbsItemHtml);
|
||||
|
||||
const oNormalItemElem = $(sBreadcrumbsItemHtml)
|
||||
.addClass(this.css_classes.breadcrumbs_item)
|
||||
.attr('data-role', 'ibo-breadcrumbs--item');
|
||||
this.element.append(oNormalItemElem);
|
||||
|
||||
const oPreviousItemElem = $(sBreadcrumbsItemHtml)
|
||||
.addClass(this.css_classes.breadcrumbs_previous_item)
|
||||
.attr('data-role', 'ibo-breadcrumbs--previous-item')
|
||||
// Note: We prepend items as we want the oldest to be at the bottom of the list, like in a browser
|
||||
this.element.find(this.js_selectors.previous_items_list).prepend(oPreviousItemElem);
|
||||
}
|
||||
}
|
||||
|
||||
this._updateOverflowingState();
|
||||
this._bindEvents();
|
||||
},
|
||||
// events bound via _bind are removed automatically
|
||||
// revert other modifications here
|
||||
_destroy: function()
|
||||
{
|
||||
// Remove listeners
|
||||
this.element.find(this.js_selectors.previous_items_list_toggler).off('click');
|
||||
|
||||
// Remove observers
|
||||
this.items_observer.disconnect();
|
||||
|
||||
// Clear any existing entries in the markup
|
||||
this.element.find(this.js_selectors.item).remove();
|
||||
this.element.find(this.js_selectors.previous_item).remove();
|
||||
|
||||
this.element.removeClass('ibo-breadcrumbs');
|
||||
},
|
||||
_bindEvents: function ()
|
||||
{
|
||||
const me = this;
|
||||
|
||||
// Enable responsiveness if more than 1 item
|
||||
if(window.IntersectionObserver && (this.element.find(this.js_selectors.item).length > 1)) {
|
||||
// Set an observer on the items
|
||||
this.items_observer = new IntersectionObserver(function(aItems, oIntersectObs){
|
||||
aItems.forEach(oItem => {
|
||||
let oItemElem = $(oItem.target);
|
||||
let bIsVisible = oItem.isIntersecting;
|
||||
|
||||
// Important: We toggle "visibility" instead of "display" otherwise once they are hidden, they never trigger back the intersection.
|
||||
if(bIsVisible) {
|
||||
oItemElem.css('visibility', '');
|
||||
}
|
||||
else {
|
||||
// Here we also check if the item has an invisible left sibbling before hiding it.
|
||||
// There reason is that on initialization, the last item might be overflowing on the right BEFORE the breadcrumbs is flagged as overflowing, making it disappear
|
||||
let oLeftSiblingElem = oItemElem.prev(me.js_selectors.item);
|
||||
if (oLeftSiblingElem.length > 0 && oLeftSiblingElem.css('visibility') !== 'hidden') {
|
||||
bIsVisible = true;
|
||||
} else {
|
||||
oItemElem.css('visibility', 'hidden');
|
||||
}
|
||||
}
|
||||
me._updateItemDisplay(oItemElem, bIsVisible);
|
||||
});
|
||||
|
||||
let bShouldShowPreviousItemsList = false;
|
||||
me.element.find(me.js_selectors.item).each(function() {
|
||||
if ($(this).css('visibility') === 'hidden') {
|
||||
bShouldShowPreviousItemsList = true;
|
||||
|
||||
// Note: Can break a .each function loop, must return false
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Move previous items toggler before first visible item for a better UX
|
||||
if (bShouldShowPreviousItemsList) {
|
||||
let oFirstVisibleItem = me.element.find(me.js_selectors.item).first();
|
||||
me.element.find(me.js_selectors.item).each(function() {
|
||||
if ($(this).css('visibility') !== 'hidden') {
|
||||
oFirstVisibleItem = $(this);
|
||||
|
||||
// Note: Can break a .each function loop, must return false
|
||||
return false;
|
||||
}
|
||||
});
|
||||
me.element.find(me.js_selectors.previous_items_container).insertBefore(oFirstVisibleItem);
|
||||
}
|
||||
|
||||
me._updateOverflowingState();
|
||||
me._updatePreviousItemsList();
|
||||
}, {
|
||||
root: $(this.js_selectors.breadcrumbs)[0],
|
||||
threshold: [1] // Must be completely visible
|
||||
});
|
||||
this.element.find(this.js_selectors.item).each(function(){
|
||||
me.items_observer.observe(this);
|
||||
});
|
||||
|
||||
this.element.find(this.js_selectors.previous_items_list_toggler).on('click', function (oEvent) {
|
||||
oEvent.preventDefault();
|
||||
me.element.find(me.js_selectors.previous_items_list).toggleClass(me.css_classes.is_hidden);
|
||||
});
|
||||
$('body').on('click', function (oEvent) {
|
||||
if (true === me.element.find(me.js_selectors.previous_items_list).hasClass(me.css_classes.is_hidden)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($(oEvent.target.closest(me.js_selectors.previous_items_container)).length === 0) {
|
||||
me.element.find(me.js_selectors.previous_items_list).addClass(me.css_classes.is_hidden);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
_readDataFromStorage: function()
|
||||
{
|
||||
var sBreadCrumbStorageKey = this.options.itop_instance_id + 'breadcrumb-v1';
|
||||
@@ -131,6 +254,7 @@ $(function()
|
||||
sBreadCrumbData = JSON.stringify(aBreadCrumb);
|
||||
sessionStorage.setItem(sBreadCrumbStorageKey, sBreadCrumbData);
|
||||
},
|
||||
|
||||
// Refresh the latest entry (navigating to a tab)
|
||||
RefreshLatestEntry: function(sRefreshHrefTo)
|
||||
{
|
||||
@@ -149,5 +273,68 @@ $(function()
|
||||
}
|
||||
this._writeDataToStorage(aBreadCrumb);
|
||||
},
|
||||
|
||||
// Helpers
|
||||
/**
|
||||
* Update item display based on its visibility to the user
|
||||
*
|
||||
* @param oItemElem {Object} jQuery element
|
||||
* @param bIsVisible {boolean|null} If null, visibility will be computed automatically. Not that performance might not be great so it's preferable to pass the value when known
|
||||
* @return {void}
|
||||
* @private
|
||||
*/
|
||||
_updateItemDisplay(oItemElem, bIsVisible = null)
|
||||
{
|
||||
const iEntryNumber = parseInt(oItemElem.attr('data-breadcrumb-entry-number'));
|
||||
const oMatchingExtraItemElem = this.element.find(this.js_selectors.previous_items_list+' [data-breadcrumb-entry-number="'+iEntryNumber+'"]');
|
||||
|
||||
// Manually check if the item is visible if the info isn't passed
|
||||
if (bIsVisible === null) {
|
||||
bIsVisible = CombodoGlobalToolbox.IsElementVisibleToTheUser(oItemElem[0], true, 2);
|
||||
}
|
||||
|
||||
// Hide/show the corresponding extra item element
|
||||
if (bIsVisible) {
|
||||
oMatchingExtraItemElem.addClass(this.css_classes.is_hidden);
|
||||
} else {
|
||||
oMatchingExtraItemElem.removeClass(this.css_classes.is_hidden);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Update previous items list
|
||||
*
|
||||
* @return {void}
|
||||
* @private
|
||||
*/
|
||||
_updatePreviousItemsList: function () {
|
||||
const iVisiblePreviousItemsCount = this.element.find(this.js_selectors.previous_item+':not(.'+this.css_classes.is_hidden+')').length;
|
||||
const oPreviousItemsContainerElem = this.element.find(this.js_selectors.previous_items_container);
|
||||
|
||||
if (iVisiblePreviousItemsCount > 0) {
|
||||
oPreviousItemsContainerElem.removeClass(this.css_classes.is_hidden);
|
||||
} else {
|
||||
oPreviousItemsContainerElem.addClass(this.css_classes.is_hidden);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Update the overflowing state of the breadcrumbs by checking if the items cumulated width is greater than the breadcrumbs visible space
|
||||
*
|
||||
* @return {void}
|
||||
* @private
|
||||
*/
|
||||
_updateOverflowingState: function () {
|
||||
const fBreadcrumbsWidth = this.element.outerWidth();
|
||||
let fItemsTotalWidth = 0;
|
||||
|
||||
this.element.find(this.js_selectors.item).each(function () {
|
||||
fItemsTotalWidth += $(this).outerWidth();
|
||||
});
|
||||
|
||||
if (fItemsTotalWidth > fBreadcrumbsWidth) {
|
||||
this.element.addClass(this.css_classes.is_overflowing);
|
||||
} else {
|
||||
this.element.removeClass(this.css_classes.is_overflowing);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,7 +27,7 @@ $(function()
|
||||
this.moveup_btn = $('<button type="button" disabled class="ibo-button ibo-is-regular ibo-is-neutral ibo-button--vertical-align">'+this.options.labels.moveup+'</button>');
|
||||
this.movedown_btn = $('<button type="button" disabled class="ibo-button ibo-is-regular ibo-is-neutral ibo-button--vertical-align">'+this.options.labels.movedown+'</button>');
|
||||
columnWithButtons = $('<div class="ibo-mini-column"></div>');
|
||||
this.element.parent().parent().append(columnWithButtons.append(this.moveup_btn).append('<br>').append(this.movedown_btn));
|
||||
this.element.parent().parent().append(columnWithButtons.append(this.moveup_btn).append(this.movedown_btn));
|
||||
this.moveup_btn.on('click', function () {
|
||||
me._moveUp();
|
||||
});
|
||||
@@ -93,9 +93,10 @@ $(function()
|
||||
{
|
||||
this.element
|
||||
.removeClass('itop-fieldsorter');
|
||||
|
||||
let oButtonsColumn = this.moveup_btn.parent('.ibo-mini-column');
|
||||
this.moveup_btn.remove();
|
||||
this.movedown_btn.remove();
|
||||
oButtonsColumn.remove();
|
||||
this.element.sortable('destroy').html('');
|
||||
},
|
||||
// _setOptions is called with a hash of all options that are changing
|
||||
|
||||
@@ -787,6 +787,22 @@ $(function()
|
||||
|
||||
return oEntries;
|
||||
},
|
||||
/**
|
||||
* @returns {Object} The case logs having a new entry and their values, format is {<ATT_CODE_1>: <HTML_VALUE_1>, <ATT_CODE_2>: <HTML_VALUE_2>}
|
||||
* @private
|
||||
*/
|
||||
_GetExtraInputsFromAllForms: function () {
|
||||
const me = this;
|
||||
|
||||
let oExtraInputs = {};
|
||||
this.element.find(this.js_selectors.caselog_entry_form).each(function () {
|
||||
const oEntryFormElem = $(this);
|
||||
oExtraInputs = $.extend(oExtraInputs, oEntryFormElem.triggerHandler('get_extra_inputs.caselog_entry_form.itop'));
|
||||
});
|
||||
|
||||
return oExtraInputs;
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {boolean} True if at least 1 of the entry form is draft (has some text in it)
|
||||
* @private
|
||||
@@ -863,6 +879,7 @@ $(function()
|
||||
_SendEntriesToServer: function (sStimulusCode = null) {
|
||||
const me = this;
|
||||
const oEntries = this._GetEntriesFromAllForms();
|
||||
const oExtraInputs = this._GetExtraInputsFromAllForms();
|
||||
|
||||
// Proceed only if entries to send
|
||||
if (Object.keys(oEntries).length === 0) {
|
||||
@@ -870,13 +887,13 @@ $(function()
|
||||
}
|
||||
|
||||
// Prepare parameters
|
||||
let oParams = {
|
||||
let oParams = $.extend(oExtraInputs, {
|
||||
operation: 'activity_panel_add_caselog_entries',
|
||||
object_class: this._GetHostObjectClass(),
|
||||
object_id: this._GetHostObjectID(),
|
||||
transaction_id: this.options.transaction_id,
|
||||
entries: oEntries,
|
||||
};
|
||||
});
|
||||
|
||||
// Freeze case logs
|
||||
this._FreezeCaseLogsEntryForms();
|
||||
|
||||
@@ -157,6 +157,9 @@ $(function() {
|
||||
this.element.on('get_entry.caselog_entry_form.itop', function () {
|
||||
return me._GetInputData();
|
||||
});
|
||||
this.element.on('get_extra_inputs.caselog_entry_form.itop', function () {
|
||||
return me._GetExtraInputs();
|
||||
});
|
||||
// Clear the entry value
|
||||
this.element.on('clear_entry.caselog_entry_form.itop', function () {
|
||||
me._EmptyInput();
|
||||
@@ -281,6 +284,27 @@ $(function() {
|
||||
_GetInputData: function() {
|
||||
return (this._GetCKEditorInstance() === undefined) ? '' : this._GetCKEditorInstance().getData();
|
||||
},
|
||||
_GetExtraInputs: function() {
|
||||
let aExtraInputs = {};
|
||||
const aFormInputs = this.element.serializeArray();
|
||||
// Iterate across all values that would be sent if we submit current form
|
||||
for (const aExtraInput of aFormInputs) {
|
||||
// If we don't already have a value with the same name, add it
|
||||
// Otherwise we'll consider that we need to return this value as an array of values
|
||||
if(aExtraInputs[aExtraInput.name] === undefined) {
|
||||
aExtraInputs[aExtraInput.name] = aExtraInput.value;
|
||||
}
|
||||
else {
|
||||
if(Array.isArray(aExtraInputs[aExtraInput.name])){
|
||||
aExtraInputs[aExtraInput.name].push(aExtraInput.value);
|
||||
}
|
||||
else{
|
||||
aExtraInputs[aExtraInput.name] = [aExtraInputs[aExtraInput.name], aExtraInput.value];
|
||||
}
|
||||
}
|
||||
};
|
||||
return aExtraInputs;
|
||||
},
|
||||
// - Main actions
|
||||
_ShowMainActions: function() {
|
||||
this.element.find(this.js_selectors.main_actions).removeClass(this.css_classes.is_hidden);
|
||||
|
||||
@@ -224,7 +224,6 @@ $(function()
|
||||
|
||||
$('#ibo-breadcrumbs')
|
||||
.breadcrumbs('destroy')
|
||||
.html('')
|
||||
.breadcrumbs({
|
||||
itop_instance_id: oData['breadcrumb_instance_id'],
|
||||
max_count: oData['breadcrumb_max_count'],
|
||||
|
||||
53
pages/UI.php
53
pages/UI.php
@@ -8,12 +8,8 @@ use Combodo\iTop\Application\Helper\Session;
|
||||
use Combodo\iTop\Application\TwigBase\Twig\TwigHelper;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Form\Form;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Form\FormUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\GlobalSearch\GlobalSearchHelper;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Html\HtmlFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Input\Select\SelectOptionUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Input\SelectUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\QuickCreate\QuickCreateHelper;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Title\Title;
|
||||
@@ -753,54 +749,7 @@ EOF
|
||||
cmdbAbstractObject::DisplayCreationForm($oP, $sRealClass, $oObjToClone, array(), array('wizard_container' => 1, 'keep_source_object' => true)); // wizard_container: Display the title above the form
|
||||
} else {
|
||||
// Select the derived class to create
|
||||
$sClassLabel = MetaModel::GetName($sClass);
|
||||
$sClassIconUrl = MetaModel::GetClassIcon($sClass, false);
|
||||
$sTitle = Dict::Format('UI:CreationTitle_Class', $sClassLabel);
|
||||
|
||||
$oP->set_title($sTitle);
|
||||
$oPanel = PanelUIBlockFactory::MakeForClass($sClass, $sTitle)
|
||||
->SetIcon($sClassIconUrl);
|
||||
$oP->AddUiBlock($oPanel);
|
||||
|
||||
$oClassForm = FormUIBlockFactory::MakeStandard();
|
||||
$oPanel->AddMainBlock($oClassForm);
|
||||
|
||||
$oClassForm->AddSubBlock(HtmlFactory::MakeParagraph(Dict::Format('UI:SelectTheTypeOf_Class_ToCreate', $sClassLabel)))
|
||||
->AddHtml($oAppContext->GetForForm())
|
||||
->AddSubBlock(InputUIBlockFactory::MakeForHidden('checkSubclass', '0'))
|
||||
->AddSubBlock(InputUIBlockFactory::MakeForHidden('state', $sStateCode))
|
||||
->AddSubBlock(InputUIBlockFactory::MakeForHidden('operation', 'new'));
|
||||
|
||||
$aDefaults = utils::ReadParam('default', array(), false, 'raw_data');
|
||||
foreach ($aDefaults as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $key2 => $value2) {
|
||||
if (is_array($value2)) {
|
||||
foreach ($value2 as $key3 => $value3) {
|
||||
$sValue = utils::EscapeHtml($value3);
|
||||
$oClassForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("default[$key][$key2][$key3]", $sValue));
|
||||
}
|
||||
} else {
|
||||
$sValue = utils::EscapeHtml($value2);
|
||||
$oClassForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("default[$key][$key2]", $sValue));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$sValue = utils::EscapeHtml($value);
|
||||
$oClassForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("default[$key]", $sValue));
|
||||
}
|
||||
}
|
||||
|
||||
$oSelect = SelectUIBlockFactory::MakeForSelect('class');
|
||||
$oClassForm->AddSubBlock($oSelect);
|
||||
asort($aPossibleClasses);
|
||||
foreach ($aPossibleClasses as $sClassName => $sClassLabel) {
|
||||
$oSelect->AddOption(SelectOptionUIBlockFactory::MakeForSelectOption($sClassName, $sClassLabel, ($sClassName == $sClass)));
|
||||
}
|
||||
|
||||
$oToolbar = ToolbarUIBlockFactory::MakeForAction();
|
||||
$oClassForm->AddSubBlock($oToolbar);
|
||||
$oToolbar->AddSubBlock(ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:Apply'), null, null, true));
|
||||
cmdbAbstractObject::DisplaySelectClassToCreate($sClass, $oP, $oAppContext, $aPossibleClasses,['state' => $sStateCode]);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -1369,7 +1369,7 @@ EOF
|
||||
foreach($aLicenses as $oLicense)
|
||||
{
|
||||
$oPage->add('<li><b>'.$oLicense->product.'</b>, © '.$oLicense->author.' is licensed under the <b>'.$oLicense->license_type.' license</b>. (<a id="toggle_'.$index.'" class="CollapsibleLabel" style="cursor:pointer;">Details</a>)');
|
||||
$oPage->add('<div id="license_'.$index.'" class="license_text" style="display:none;overflow:auto;max-height:10em;font-size:small;border:1px #696969 solid;margin-bottom:1em; margin-top:0.5em;padding:0.5em;">'.$oLicense->text.'</div>');
|
||||
$oPage->add('<div id="license_'.$index.'" class="license_text ibo-is-html-content" style="display:none;overflow:auto;max-height:10em;font-size:small;border:1px #696969 solid;margin-bottom:1em; margin-top:0.5em;padding:0.5em;">'.$oLicense->text.'</div>');
|
||||
$oPage->add_ready_script(<<<JS
|
||||
$("#toggle_$index").on('click', function() {
|
||||
$(this).toggleClass('open');
|
||||
|
||||
@@ -1009,7 +1009,7 @@ EOF
|
||||
if ($(this).val() == sRefValue)
|
||||
{
|
||||
iOccurences++;
|
||||
}c
|
||||
}
|
||||
});
|
||||
if ((iOccurences > 1) && (sRefValue != ':none:'))
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\CollapsibleSection\CollapsibleSection;
|
||||
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Field\Field;
|
||||
use Combodo\iTop\Application\UI\Base\Component\FieldSet\FieldSet;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Form\Form;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Form\FormUIBlockFactory;
|
||||
@@ -163,14 +164,15 @@ try
|
||||
}
|
||||
}
|
||||
|
||||
$oPanelQuery = PanelUIBlockFactory::MakeWithBrandingPrimaryColor(Dict::S('UI:RunQuery:ExpressionToEvaluate'));
|
||||
$oP->AddSubBlock($oPanelQuery);
|
||||
$oQueryForm = new Form();
|
||||
$oP->AddUiBlock($oQueryForm);
|
||||
$oPanelQuery->AddSubBlock($oQueryForm);
|
||||
|
||||
$oHiddenParams = new Html($oAppContext->GetForForm());
|
||||
$oQueryForm->AddSubBlock($oHiddenParams);
|
||||
|
||||
//--- Query textarea
|
||||
$oQueryForm->AddSubBlock(TitleUIBlockFactory::MakeNeutral(Dict::S('UI:RunQuery:ExpressionToEvaluate'), 2));
|
||||
$oQueryTextArea = new TextArea('expression', utils::EscapeHtml($sExpression), 'expression', 120, 8);
|
||||
$oQueryTextArea->AddCSSClass('ibo-queryoql');
|
||||
$oQueryForm->AddSubBlock($oQueryTextArea);
|
||||
@@ -186,15 +188,14 @@ EOF
|
||||
|
||||
if (count($aArgs) > 0) {
|
||||
//--- Query arguments
|
||||
$oQueryArgsContainer = PanelUIBlockFactory::MakeForInformation('Query arguments')
|
||||
->SetCSSClasses(['wizContainer']);
|
||||
$oQueryForm->AddSubBlock(TitleUIBlockFactory::MakeNeutral(Dict::S('UI:RunQuery:QueryArguments'),2)->AddCSSClass("ibo-collapsible-section--title"));
|
||||
$oQueryArgsContainer = UIContentBlockUIBlockFactory::MakeStandard(null,['wizContainer']);
|
||||
$oQueryForm->AddSubBlock($oQueryArgsContainer);
|
||||
foreach ($aArgs as $sParam => $sValue) {
|
||||
$oArgInput = InputUIBlockFactory::MakeForInputWithLabel(
|
||||
$sParam,
|
||||
'arg_'.$sParam,
|
||||
$sValue
|
||||
);
|
||||
$oInput = InputUIBlockFactory::MakeStandard("text",'arg_'.$sParam, $sValue);
|
||||
$oArgInput = \Combodo\iTop\Application\UI\Base\Component\Field\FieldUIBlockFactory::MakeFromObject($sParam,$oInput,Field::ENUM_FIELD_LAYOUT_SMALL);
|
||||
$oArgInput->AddCSSClass("ibo-field--label-small");
|
||||
//$oArgInput = InputUIBlockFactory::MakeForInputWithLabel( $sParam, 'arg_'.$sParam, $sValue );
|
||||
$oQueryArgsContainer->AddSubBlock($oArgInput);
|
||||
}
|
||||
}
|
||||
@@ -214,10 +215,10 @@ EOF
|
||||
|
||||
if ($oFilter) {
|
||||
//--- Query filter
|
||||
$oP->AddSubBlock(TitleUIBlockFactory::MakeNeutral(Dict::S('UI:RunQuery:QueryResults'), 2));
|
||||
|
||||
$oPanelResult= PanelUIBlockFactory::MakeWithBrandingSecondaryColor(Dict::S('UI:RunQuery:QueryResults'));
|
||||
$oP->AddSubBlock($oPanelResult);
|
||||
$oResultBlock = new DisplayBlock($oFilter, 'list', false);
|
||||
$oP->AddSubBlock($oResultBlock->GetDisplay($oP, 'runquery'));
|
||||
$oPanelResult->AddSubBlock($oResultBlock->GetDisplay($oP, 'runquery'));
|
||||
|
||||
// Breadcrumb
|
||||
//$iCount = $oResultBlock->GetDisplayedCount();
|
||||
|
||||
@@ -172,33 +172,42 @@ class DBBackup
|
||||
*/
|
||||
public function CreateCompressedBackup($sTargetFile, $sSourceConfigFile = null)
|
||||
{
|
||||
$bIsCmdbSourceInitialized = CMDBSource::GetMysqli() instanceof mysqli;
|
||||
if (!$bIsCmdbSourceInitialized)
|
||||
{
|
||||
$sErrorMsg = 'Cannot backup : CMDBSource not initialized !';
|
||||
$this->LogError($sErrorMsg);
|
||||
throw new CoreException($sErrorMsg);
|
||||
$bReadonlyBefore = SetupUtils::EnterReadOnlyMode(MetaModel::GetConfig());
|
||||
try {
|
||||
//safe zone for db backup => cron is stopped/ itop in readonly
|
||||
$bIsCmdbSourceInitialized = CMDBSource::GetMysqli() instanceof mysqli;
|
||||
if (!$bIsCmdbSourceInitialized) {
|
||||
$sErrorMsg = 'Cannot backup : CMDBSource not initialized !';
|
||||
$this->LogError($sErrorMsg);
|
||||
throw new CoreException($sErrorMsg);
|
||||
}
|
||||
|
||||
$this->LogInfo("Creating backup: '$sTargetFile.tar.gz'");
|
||||
|
||||
$oArchive = new ITopArchiveTar($sTargetFile.'.tar.gz');
|
||||
|
||||
$sTmpFolder = APPROOT.'data/tmp-backup-'.rand(10000, getrandmax());
|
||||
$aFiles = $this->PrepareFilesToBackup($sSourceConfigFile, $sTmpFolder);
|
||||
|
||||
$sFilesList = var_export($aFiles, true);
|
||||
$this->LogInfo("backup: adding to archive files '$sFilesList'");
|
||||
$bArchiveCreationResult = $oArchive->createModify($aFiles, '', $sTmpFolder);
|
||||
if (!$bArchiveCreationResult) {
|
||||
$sErrorMsg = 'Cannot backup : unable to create archive';
|
||||
$this->LogError($sErrorMsg);
|
||||
throw new BackupException($sErrorMsg);
|
||||
}
|
||||
|
||||
$this->LogInfo("backup: removing tmp folder '$sTmpFolder'");
|
||||
SetupUtils::rrmdir($sTmpFolder);
|
||||
} finally {
|
||||
if (! $bReadonlyBefore) {
|
||||
SetupUtils::ExitReadOnlyMode();
|
||||
} else {
|
||||
//we are in the scope of main process that needs to handle/keep readonly mode (setup for example).
|
||||
$this->LogInfo("Keep readonly mode after backup");
|
||||
}
|
||||
}
|
||||
|
||||
$this->LogInfo("Creating backup: '$sTargetFile.tar.gz'");
|
||||
|
||||
$oArchive = new ITopArchiveTar($sTargetFile.'.tar.gz');
|
||||
|
||||
$sTmpFolder = APPROOT.'data/tmp-backup-'.rand(10000, getrandmax());
|
||||
$aFiles = $this->PrepareFilesToBackup($sSourceConfigFile, $sTmpFolder);
|
||||
|
||||
$sFilesList = var_export($aFiles, true);
|
||||
$this->LogInfo("backup: adding to archive files '$sFilesList'");
|
||||
$bArchiveCreationResult = $oArchive->createModify($aFiles, '', $sTmpFolder);
|
||||
if (!$bArchiveCreationResult)
|
||||
{
|
||||
$sErrorMsg = 'Cannot backup : unable to create archive';
|
||||
$this->LogError($sErrorMsg);
|
||||
throw new BackupException($sErrorMsg);
|
||||
}
|
||||
|
||||
$this->LogInfo("backup: removing tmp folder '$sTmpFolder'");
|
||||
SetupUtils::rrmdir($sTmpFolder);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2581,6 +2581,10 @@ class MFDocument extends \Combodo\iTop\DesignDocument
|
||||
public function GetNodes($sXPath, $oContextNode = null, $bSafe = true)
|
||||
{
|
||||
$oXPath = new DOMXPath($this);
|
||||
// For Designer audit
|
||||
$oXPath->registerNamespace("php", "http://php.net/xpath");
|
||||
$oXPath->registerPhpFunctions();
|
||||
|
||||
if ($bSafe)
|
||||
{
|
||||
$sXPath .= "[not(@_alteration) or @_alteration!='removed']";
|
||||
|
||||
@@ -1966,11 +1966,13 @@ JS
|
||||
return APPROOT.'log/setup-queries-'.strftime('%Y-%m-%d_%H_%M').'.sql';
|
||||
}
|
||||
|
||||
public static function EnterMaintenanceMode($oConfig)
|
||||
public static function EnterMaintenanceMode($oConfig): bool
|
||||
{
|
||||
$bPreviousMode = self::IsInMaintenanceMode();
|
||||
@touch(MAINTENANCE_MODE_FILE);
|
||||
self::Log("----> Entering maintenance mode");
|
||||
self::WaitCronTermination($oConfig, "maintenance");
|
||||
return $bPreviousMode;
|
||||
}
|
||||
|
||||
public static function ExitMaintenanceMode($bLog = true)
|
||||
@@ -1987,11 +1989,14 @@ JS
|
||||
return file_exists(MAINTENANCE_MODE_FILE);
|
||||
}
|
||||
|
||||
public static function EnterReadOnlyMode($oConfig)
|
||||
public static function EnterReadOnlyMode($oConfig): bool
|
||||
{
|
||||
$bPreviousMode = self::IsInReadOnlyMode();
|
||||
@touch(READONLY_MODE_FILE);
|
||||
self::Log("----> Entering read only mode");
|
||||
self::WaitCronTermination($oConfig, "read only");
|
||||
|
||||
return $bPreviousMode;
|
||||
}
|
||||
|
||||
public static function ExitReadOnlyMode($bLog = true)
|
||||
@@ -2017,8 +2022,7 @@ JS
|
||||
try
|
||||
{
|
||||
// Wait for cron to stop
|
||||
if (is_null($oConfig))
|
||||
{
|
||||
if (is_null($oConfig) || ContextTag::Check(ContextTag::TAG_CRON)) {
|
||||
return;
|
||||
}
|
||||
// Use mutex to check if cron is running
|
||||
|
||||
@@ -720,7 +720,7 @@ CSS
|
||||
foreach ($aLicenses as $oLicense)
|
||||
{
|
||||
$oPage->add('<li><b>'.$oLicense->product.'</b>, © '.$oLicense->author.' is licensed under the <b>'.$oLicense->license_type.' license</b>. (<span class="toggle" id="toggle_'.$index.'">Details</span>)');
|
||||
$oPage->add('<div id="license_'.$index.'" class="license_text" style="display:none;overflow:auto;max-height:10em;font-size:12px;border:1px #696969 solid;margin-bottom:1em; margin-top:0.5em;padding:0.5em;"><pre>'.$oLicense->text.'</pre></div>');
|
||||
$oPage->add('<div id="license_'.$index.'" class="license_text ibo-is-html-content" style="display:none;overflow:auto;max-height:10em;font-size:12px;border:1px #696969 solid;margin-bottom:1em; margin-top:0.5em;padding:0.5em;"><pre>'.$oLicense->text.'</pre></div>');
|
||||
$oPage->add_ready_script('$(".license_text a").attr("target", "_blank").addClass("no-arrow");');
|
||||
$oPage->add_ready_script('$("#toggle_'.$index.'").on("click", function() { $("#license_'.$index.'").toggle(); } );');
|
||||
$index++;
|
||||
|
||||
@@ -62,7 +62,7 @@ class AjaxRenderController
|
||||
$oSet->SetShowObsoleteData($bShowObsoleteData);
|
||||
$aResult["draw"] = $iDrawNumber;
|
||||
$aResult["recordsTotal"] = $oSet->Count();
|
||||
$aResult["recordsFiltered"] = $oSet->Count();
|
||||
$aResult["recordsFiltered"] = $aResult["recordsTotal"] ;
|
||||
$aResult["data"] = [];
|
||||
while ($aObject = $oSet->FetchAssoc()) {
|
||||
$aObj = [];
|
||||
@@ -79,7 +79,7 @@ class AjaxRenderController
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($aObj)) {
|
||||
if (!empty($aObj)) {
|
||||
if ($sIdName != "") {
|
||||
if (isset($aObj[$sIdName])) {
|
||||
$aObj["id"] = $aObj[$sIdName];
|
||||
|
||||
@@ -116,6 +116,14 @@ class ActivityPanelController
|
||||
'html_rendering' => $sEntryAsHtml,
|
||||
];
|
||||
}
|
||||
|
||||
// Invoke extensions after the update of the object from the activity form
|
||||
/** @var \iApplicationUIExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance)
|
||||
{
|
||||
$oExtensionInstance->OnFormSubmit($oObject);
|
||||
}
|
||||
|
||||
$oObject->DBWrite();
|
||||
|
||||
// Finalize inline images
|
||||
|
||||
@@ -379,10 +379,17 @@ abstract class Controller
|
||||
}
|
||||
$aParams = array_merge($this->GetDefaultParameters(), $aParams);
|
||||
$this->CreatePage($sPageType);
|
||||
$this->AddToPage($this->RenderTemplate($aParams, $sTemplateName, 'html'));
|
||||
$this->AddScriptToPage($this->RenderTemplate($aParams, $sTemplateName, 'js'));
|
||||
$this->AddReadyScriptToPage($this->RenderTemplate($aParams, $sTemplateName, 'ready.js'));
|
||||
$this->AddStyleToPage($this->RenderTemplate($aParams, $sTemplateName, 'css'));
|
||||
$sHTMLContent = $this->RenderTemplate($aParams, $sTemplateName, 'html');
|
||||
$this->AddToPage($sHTMLContent);
|
||||
$sJSScript = $this->RenderTemplate($aParams, $sTemplateName, 'js');
|
||||
$this->AddScriptToPage($sJSScript);
|
||||
$sReadyScript = $this->RenderTemplate($aParams, $sTemplateName, 'ready.js');
|
||||
$this->AddReadyScriptToPage($sReadyScript);
|
||||
$sStyle = $this->RenderTemplate($aParams, $sTemplateName, 'css');
|
||||
$this->AddStyleToPage($sStyle);
|
||||
if (strlen($sHTMLContent) == 0 && strlen($sJSScript) == 0 && strlen($sReadyScript) == 0 && strlen($sStyle) == 0) {
|
||||
IssueLog::Error("Missing TWIG template for $sTemplateName");
|
||||
}
|
||||
if (!empty($this->m_aAjaxTabs)) {
|
||||
$this->m_oPage->AddTabContainer('TwigBaseTabContainer');
|
||||
$this->m_oPage->SetCurrentTabContainer('TwigBaseTabContainer');
|
||||
|
||||
@@ -228,6 +228,10 @@ class Alert extends UIContentBlock
|
||||
*/
|
||||
public function IsCollapsible(): bool
|
||||
{
|
||||
if (empty($this->sTitle)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->bIsCollapsible;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,10 @@ class DataTable extends UIContentBlock
|
||||
protected $aAjaxData;
|
||||
protected $aDisplayColumns;
|
||||
protected $aResultColumns;
|
||||
protected $sJsonData;
|
||||
/*
|
||||
* array of data to display the first page
|
||||
*/
|
||||
protected $aInitDisplayData;
|
||||
|
||||
/**
|
||||
* Panel constructor.
|
||||
@@ -101,6 +104,7 @@ class DataTable extends UIContentBlock
|
||||
}
|
||||
|
||||
/**
|
||||
* Get $aAjaxData as a JSON
|
||||
* @return mixed
|
||||
*/
|
||||
public function GetJsonAjaxData(): string
|
||||
@@ -179,11 +183,21 @@ class DataTable extends UIContentBlock
|
||||
}
|
||||
|
||||
/**
|
||||
* Get $aInitDisplayData as a JSON This is data of first page
|
||||
* @return string
|
||||
*/
|
||||
public function GetJsonData(): string
|
||||
public function GetJsonInitDisplayData(): string
|
||||
{
|
||||
return $this->sJsonData;
|
||||
return json_encode($this->aInitDisplayData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get $aInitDisplayData
|
||||
* @return array
|
||||
*/
|
||||
public function GetInitDisplayData(): array
|
||||
{
|
||||
return $this->aInitDisplayData;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,9 +205,9 @@ class DataTable extends UIContentBlock
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function SetJsonData(string $sJSON)
|
||||
public function SetInitDisplayData(array $aData)
|
||||
{
|
||||
$this->sJsonData = $sJSON;
|
||||
$this->aInitDisplayData = $aData;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -131,7 +131,11 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
}
|
||||
|
||||
if (!isset($aExtraParams['surround_with_panel']) || $aExtraParams['surround_with_panel']) {
|
||||
$iCount = $oSet->Count();
|
||||
if(!empty($oDataTable->GetInitDisplayData()) && isset($oDataTable->GetInitDisplayData()['recordsTotal'])){
|
||||
$iCount = $oDataTable->GetInitDisplayData()['recordsTotal'];
|
||||
} else {
|
||||
$iCount = $oSet->Count();
|
||||
}
|
||||
$sTitle = (isset($aExtraParams['panel_title'])) ? $aExtraParams['panel_title'] : "";
|
||||
$oContainer = PanelUIBlockFactory::MakeForClass($oSet->GetClass(), $sTitle)->AddCSSClass('ibo-datatable-panel');
|
||||
$oContainer->SetSubTitle(Dict::Format("UI:Pagination:HeaderNoSelection", $iCount));
|
||||
@@ -438,7 +442,7 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
]);
|
||||
$oDataTable->SetDisplayColumns($aColumnDefinition);
|
||||
$oDataTable->SetResultColumns($oCustomSettings->aColumns);
|
||||
$oDataTable->SetJsonData(json_encode(AjaxRenderController::GetDataForTable($oSet, $aClassAliases, $aColumnsToLoad, $sIdName, $aExtraParams)));
|
||||
$oDataTable->SetInitDisplayData(AjaxRenderController::GetDataForTable($oSet, $aClassAliases, $aColumnsToLoad, $sIdName, $aExtraParams));
|
||||
|
||||
return $oDataTable;
|
||||
}
|
||||
@@ -670,7 +674,7 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
]);
|
||||
$oDataTable->SetDisplayColumns($aColumnDefinition);
|
||||
$oDataTable->SetResultColumns($oCustomSettings->aColumns);
|
||||
$oDataTable->SetJsonData(json_encode(AjaxRenderController::GetDataForTable($oSet, $aClassAliases, $aColumnsToLoad, $sIdName, $aExtraParams)));
|
||||
$oDataTable->SetInitDisplayData(AjaxRenderController::GetDataForTable($oSet, $aClassAliases, $aColumnsToLoad, $sIdName, $aExtraParams));
|
||||
|
||||
return $oDataTable;
|
||||
}
|
||||
@@ -713,10 +717,10 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
$aColumnDefinition["data"] = "";
|
||||
$aColumnDefinition["render"]["display"] = "";
|
||||
if ($sSelectMode != "single") {
|
||||
$aColumnDefinition["render"]["display"] = $aColumnDefinition["render"]["display"] . " var oCheckboxElem = $('<span class=\"row_input\"><input type=\"checkbox\" class=\"selectList".$sTableId."\" name=\"selectObject\" /></span>');";
|
||||
$aColumnDefinition["render"]["display"] = $aColumnDefinition["render"]["display"] . " var oCheckboxElem = $('<span class=\"row_input\"><input type=\"checkbox\" class=\"selectList".$sTableId."\" name=\"selectObject[]\" value='+row.id+' /></span>');";
|
||||
}
|
||||
else {
|
||||
$aColumnDefinition["render"]["display"] = $aColumnDefinition["render"]["display"] . " var oCheckboxElem = $('<span class=\"row_input\"><input type=\"radio\" class=\"selectList".$sTableId."\" name=\"selectObject\" /></span>');";
|
||||
$aColumnDefinition["render"]["display"] = $aColumnDefinition["render"]["display"] . " var oCheckboxElem = $('<span class=\"row_input\"><input type=\"radio\" class=\"selectList".$sTableId."\" name=\"selectObject[]\" value='+ row.id +' /></span>');";
|
||||
}
|
||||
$aColumnDefinition["render"]["display"] = $aColumnDefinition["render"]["display"] . " if (row.limited_access) { oCheckboxElem.html('-'); } else { oCheckboxElem.find(':input').attr('data-object-id', row.id).attr('data-target-object-id', row.target_id); }";
|
||||
$aColumnDefinition["render"]["display"] = $aColumnDefinition["render"]["display"]. " return oCheckboxElem.prop('outerHTML'); ";
|
||||
@@ -743,11 +747,6 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
'attribute_label' => $aData['alias'],
|
||||
];
|
||||
$aColumnDefinition["data"] = $sClassAlias."/".$sAttCode;
|
||||
if (MetaModel::IsValidAttCode($sClassName, 'obsolescence_flag')) {
|
||||
$sDisplay = "let displayField = '<span class=\"object-ref\" title=\"".$sClassAlias."::'+data+'\"><a class=\'object-ref-link\' href=\'".$oAppRoot."/pages/UI.php?operation=details&class=".$sClassName."&id='+data+'\'>'+row['".$sClassAlias."/friendlyname']+'</a></span>'; if (row['".$sClassAlias."/obsolescence_flag'].indexOf('no') == -1){displayField = '<span class=\"object-ref obsolete\" title=\"obsolete\"><a class=\'object-ref-link\' href=\'UI.php?operation=details&class=".$sClassName."&id='+data+'\'><span class=\"object-ref-icon text_decoration\"><span class=\"fas fa-eye-slash object-obsolete fa-1x fa-fw\"></span></span>'+row['".$sClassAlias."/friendlyname']+'</a></span>';} return displayField;";
|
||||
} else {
|
||||
$sDisplay = "let displayField = '<span class=\"object-ref\" title=\"".$sClassAlias."::'+data+'\"><a class=\'object-ref-link\' href=\'".$oAppRoot."/pages/UI.php?operation=details&class=".$sClassName."&id='+data+'\'>'+row['".$sClassAlias."/friendlyname']+'</a></span>'; return displayField;";
|
||||
}
|
||||
$aColumnDefinition["render"] = [
|
||||
"display" => "return row['".$sClassAlias."/hyperlink'];",
|
||||
"_" => $sClassAlias."/".$sAttCode,
|
||||
|
||||
@@ -47,7 +47,7 @@ class Panel extends UIContentBlock
|
||||
/** @inheritDoc */
|
||||
public const REQUIRES_ANCESTORS_DEFAULT_CSS_FILES = true;
|
||||
public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/components/panel/layout';
|
||||
public const DEFAULT_JS_TEMPLATE_REL_PATH = 'base/components/panel/layout';
|
||||
public const DEFAULT_JS_ON_READY_TEMPLATE_REL_PATH = 'base/components/panel/layout';
|
||||
public const DEFAULT_JS_FILES_REL_PATH = [
|
||||
'js/components/panel.js',
|
||||
];
|
||||
|
||||
@@ -78,12 +78,42 @@ class ActivityPanelFactory
|
||||
$oActivityPanel->SetCaseLogTabEntryForm($sCaseLogAttCode, CaseLogEntryFormFactory::MakeForCaselogTab($oObject, $sCaseLogAttCode, $sMode));
|
||||
}
|
||||
|
||||
// Retrieve case logs entries
|
||||
// Retrieve CMDBChange of the log entries to find their origins
|
||||
//
|
||||
// IMPORTANT: We don't have an easy way to find a log entry's origin, so we have to cross it with the corresponding CMDBChange
|
||||
// The following code is an attempt to do so, but it might not be bulletproof against missing CMDBChanges or future code evolutions
|
||||
$oCaseLogEntriesOriginsSearch = DBObjectSearch::FromOQL('SELECT C, CO FROM CMDBChange AS C JOIN CMDBChangeOpSetAttributeCaseLog AS CO ON CO.change = C.id WHERE CO.objclass = :objclass AND CO.objkey = :objkey AND CO.attcode = :attcode');
|
||||
$oCaseLogEntriesOriginsSearch->SetInternalParams([
|
||||
'objclass' => $sObjClass,
|
||||
'objkey' => $sObjId,
|
||||
'attcode' => $sCaseLogAttCode,
|
||||
]);
|
||||
|
||||
// - Results must be in the same order as the entries in the ormCaseLog (otherwise we would need to loop over the set to find the matching index, which would not be efficient)
|
||||
$oCaseLogEntriesOriginsSet = new DBObjectSet($oCaseLogEntriesOriginsSearch, ['date' => false]);
|
||||
$oCaseLogEntriesOriginsSet->OptimizeColumnLoad(['C' => ['origin'], 'CO' => ['lastentry']]);
|
||||
|
||||
// Retrieve log entries
|
||||
/** @var \ormCaseLog $oCaseLog */
|
||||
$oCaseLog = $oObject->Get($sCaseLogAttCode);
|
||||
|
||||
// Debug message to help understand why there could be anomalies on the log entries origins
|
||||
if ($oCaseLog->GetEntryCount() !== $oCaseLogEntriesOriginsSet->Count()) {
|
||||
IssueLog::Debug(static::class.": Number of log entries ({$oCaseLog->GetEntryCount()}) don't match number of corresponding CMDBChanges ({$oCaseLogEntriesOriginsSet->Count()}) for object {$sObjClass}::{$sObjId} / attribute {$sCaseLogAttCode}");
|
||||
}
|
||||
|
||||
$iCurrentEntryIndex = $oCaseLog->GetLatestEntryIndex();
|
||||
foreach ($oCaseLog->GetAsArray() as $aOrmEntry) {
|
||||
$oCaseLogEntry = ActivityEntryFactory::MakeFromCaseLogEntryArray($sCaseLogAttCode, $aOrmEntry);
|
||||
|
||||
// Try to set the origin, only if we have the corresponding data
|
||||
$aChangeRow = $oCaseLogEntriesOriginsSet->FetchAssoc();
|
||||
if (($aChangeRow !== null) && ($aChangeRow['CO']->Get('lastentry') === $iCurrentEntryIndex)) {
|
||||
$oCaseLogEntry->SetOrigin($aChangeRow['C']->Get('origin'));
|
||||
}
|
||||
|
||||
$oActivityPanel->AddEntry($oCaseLogEntry);
|
||||
$iCurrentEntryIndex--;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,8 +60,6 @@ class UnauthenticatedWebPage extends NiceWebPage
|
||||
|
||||
// TODO 3.0 Find a clever way to allow theme customization for unauthenticated webpages
|
||||
private $sCustomThemeUrl;
|
||||
/** @var array $aReadyScripts */
|
||||
protected $aReadyScripts;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
@@ -71,7 +69,6 @@ class UnauthenticatedWebPage extends NiceWebPage
|
||||
{
|
||||
parent::__construct($s_title, $bPrintable);
|
||||
|
||||
$this->aReadyScripts = array();
|
||||
$this->sContent = '';
|
||||
$this->sPanelTitle = '';
|
||||
$this->sPanelIcon = utils::GetAbsoluteUrlAppRoot().'images/itop-logo.png';
|
||||
@@ -109,14 +106,6 @@ class UnauthenticatedWebPage extends NiceWebPage
|
||||
$this->sContent .= $sHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function add_ready_script($sScript)
|
||||
{
|
||||
$this->aReadyScripts[] = $sScript;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
|
||||
@@ -1 +1,11 @@
|
||||
<div id="{{ oUIBlock.GetId() }}" class="ibo-breadcrumbs"></div>
|
||||
<div id="{{ oUIBlock.GetId() }}" class="ibo-breadcrumbs">
|
||||
<span class="ibo-breadcrumbs--previous-items-container ibo-is-hidden" data-role="ibo-breadcrumbs--previous-items-container">
|
||||
<button class="ibo-breadcrumbs--previous-items-list-toggler ibo-button ibo-is-alternative ibo-is-neutral" data-role="ibo-breadcrumbs--previous-items-list-toggler"
|
||||
aria-label="{{ 'UI:Component:Breadcrumbs:PreviousItemsListToggler:Label'|dict_s }}"
|
||||
data-tooltip-content="{{ 'UI:Component:Breadcrumbs:PreviousItemsListToggler:Label'|dict_s }}"
|
||||
>
|
||||
<span class="fas fa-ellipsis-h"></span>
|
||||
</button>
|
||||
<div class="ibo-breadcrumbs--previous-items-list ibo-is-hidden" data-role="ibo-breadcrumbs--previous-items-list"></div>
|
||||
</span>
|
||||
</div>
|
||||
@@ -342,7 +342,7 @@ var aOptions{{ sListIDForVarSuffix }} = {
|
||||
sSelectMode: "{{ oUIBlock.GetOption("select_mode") }}",
|
||||
sSelectedItemsName: "oSelectedItems{{ sListId }}",
|
||||
sViewLink: '{{ oUIBlock.GetOption("bViewLink") }}',
|
||||
iPageSize: '{{ oUIBlock.GetOption("iPageSize") }}',
|
||||
iPageSize: ' {{ iPageSize }}',
|
||||
oClassAliases: JSON.parse('{{ oUIBlock.GetOption("oClassAliases") |raw }}'),
|
||||
sTableId: '{{ oUIBlock.GetOption("sTableId") }}',
|
||||
sRenderUrl: "{{ oUIBlock.GetAjaxUrl() }}",
|
||||
@@ -351,7 +351,8 @@ var aOptions{{ sListIDForVarSuffix }} = {
|
||||
oLabels: {moveup: "{{ 'UI:Button:MoveUp'|dict_s }}", movedown: "{{ 'UI:Button:MoveDown'|dict_s }}"},
|
||||
};
|
||||
|
||||
if ($('#datatable_dlg_{{ oUIBlock.GetId() }}').hasClass('itop-datatable')) {
|
||||
if ($('#datatable_dlg_{{ oUIBlock.GetId() }}').hasClass('itop-datatable'))
|
||||
{
|
||||
$('#datatable_dlg_{{ oUIBlock.GetId() }}').DataTableSettings('destroy');
|
||||
}
|
||||
$('#datatable_dlg_{{ oUIBlock.GetId() }}').DataTableSettings(aOptions{{ sListIDForVarSuffix }});
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
<div class="ibo-caselog-entry-form--text-input" data-role="ibo-caselog-entry-form--text-input">
|
||||
{{ render_block(oUIBlock.GetTextInput(), {aPage: aPage}) }}
|
||||
</div>
|
||||
<div class="ibo-caselog-entry-form--extra-inputs" data-role="ibo-caselog-entry-form--extra-inputs">
|
||||
</div>
|
||||
<div class="ibo-caselog-entry-form--lock-indicator ibo-is-hidden" data-role="ibo-caselog-entry-form--lock-indicator">
|
||||
<span class="ibo-caselog-entry-form--lock-icon" data-role="ibo-caselog-entry-form--lock-icon">
|
||||
<span class="fas fa-lock"></span>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endfor %}
|
||||
<li class="ibo-tab-container--extra-tabs-container ibo-tab-container--tab-header" data-role="ibo-tab-container--extra-tabs-container">
|
||||
<li class="ibo-tab-container--extra-tabs-container ibo-tab-container--tab-header ibo-is-hidden" data-role="ibo-tab-container--extra-tabs-container">
|
||||
<a href="#" class="ibo-tab-container--extra-tabs-list-toggler" data-role="ibo-tab-container--extra-tabs-list-toggler"
|
||||
aria-label="{{ 'UI:Layout:TabContainer:ExtraTabsListToggler:Label'|dict_s }}"
|
||||
data-tooltip-content="{{ 'UI:Layout:TabContainer:ExtraTabsListToggler:Label'|dict_s }}"
|
||||
|
||||
@@ -15,8 +15,8 @@ class iTopConfigAstValidatorTest extends ItopTestCase
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
require_once APPROOT.'datamodels/2.x/itop-config/src/Validator/iTopConfigAstValidator.php';
|
||||
require_once APPROOT.'datamodels/2.x/itop-config/src/Validator/ConfigNodesVisitor.php';
|
||||
require_once APPROOT.'env-production/itop-config/src/Validator/iTopConfigAstValidator.php';
|
||||
require_once APPROOT.'env-production/itop-config/src/Validator/ConfigNodesVisitor.php';
|
||||
}
|
||||
|
||||
public function testValidateFileValid()
|
||||
|
||||
40
test/core/ormCaseLogTest.php
Normal file
40
test/core/ormCaseLogTest.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use ormCaseLog;
|
||||
|
||||
|
||||
/**
|
||||
* Tests of the ormCaseLog class
|
||||
*
|
||||
* @covers \ormCaseLog
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class ormCaseLogTest extends ItopDataTestCase
|
||||
{
|
||||
/**
|
||||
* @covers \ormCaseLog::GetEntryCount()
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testGetEntryCount()
|
||||
{
|
||||
// New log, with no entry
|
||||
$oLog = new ormCaseLog();
|
||||
$this->assertEquals($oLog->GetEntryCount(), 0, 'Should be no entry yet, returned '.$oLog->GetEntryCount());
|
||||
|
||||
// Add an entry
|
||||
$oLog->AddLogEntry('First entry');
|
||||
$this->assertEquals($oLog->GetEntryCount(), 1, 'Should be 1 entry, returned '.$oLog->GetEntryCount());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user