Data Exchange implementation in progress...

SVN:trunk[1104]
This commit is contained in:
Denis Flaven
2011-03-02 09:12:15 +00:00
parent 76b5bfb19e
commit be1d8674f3
6 changed files with 333 additions and 78 deletions

View File

@@ -39,7 +39,40 @@ require_once(APPROOT.'/application/ui.passwordwidget.class.inc.php');
require_once(APPROOT.'/application/ui.extkeywidget.class.inc.php');
require_once(APPROOT.'/application/ui.htmleditorwidget.class.inc.php');
abstract class cmdbAbstractObject extends CMDBObject
/**
* All objects to be displayed in the application (either as a list or as details)
* must implement this interface.
*/
interface iDisplay
{
/**
* Maps the given context parameter name to the appropriate filter/search code for this class
* @param string $sContextParam Name of the context parameter, i.e. 'org_id'
* @return string Filter code, i.e. 'customer_id'
*/
public static function MapContextParam($sContextParam);
/**
* This function returns a 'hilight' CSS class, used to hilight a given row in a table
* There are currently (i.e defined in the CSS) 4 possible values HILIGHT_CLASS_CRITICAL,
* HILIGHT_CLASS_WARNING, HILIGHT_CLASS_OK, HILIGHT_CLASS_NONE
* To Be overridden by derived classes
* @param void
* @return String The desired higlight class for the object/row
*/
public function GetHilightClass();
/**
* Returns the relative path to the page that handles the display of the object
* @return string
*/
public static function GetUIPage();
/**
* Displays the details of the object
*/
public function DisplayDetails(WebPage $oPage, $bEditMode = false);
}
abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
{
protected $m_iFormId; // The ID of the form used to edit the object (when in edition mode !)
@@ -48,40 +81,6 @@ abstract class cmdbAbstractObject extends CMDBObject
return '../pages/UI.php';
}
public static function ComputeUIPage($sClass)
{
static $aUIPagesCache = array(); // Cache to store the php page used to display each class of object
if (!isset($aUIPagesCache[$sClass]))
{
$UIPage = false;
if (is_callable("$sClass::GetUIPage"))
{
$UIPage = eval("return $sClass::GetUIPage();"); // May return false in case of error
}
$aUIPagesCache[$sClass] = $UIPage === false ? './UI.php' : $UIPage;
}
$sPage = $aUIPagesCache[$sClass];
return $sPage;
}
protected static function MakeHyperLink($sObjClass, $sObjKey, $sLabel = '')
{
if ($sObjKey <= 0) return '<em>'.Dict::S('UI:UndefinedObject').'</em>'; // Objects built in memory have negative IDs
$oAppContext = new ApplicationContext();
$sPage = self::ComputeUIPage($sObjClass);
$sAbsoluteUrl = utils::GetAbsoluteUrlPath();
// Safety net
//
if (empty($sLabel))
{
$sLabel = MetaModel::GetName($sObjClass)." #$sObjKey";
}
$sHint = MetaModel::GetName($sObjClass)."::$sObjKey";
return "<a href=\"{$sAbsoluteUrl}{$sPage}?operation=details&class=$sObjClass&id=$sObjKey&".$oAppContext->GetForLink()."\" title=\"$sHint\">$sLabel</a>";
}
function DisplayBareHeader(WebPage $oPage, $bEditMode = false)
{
// Standard Header with name, actions menu and history block

View File

@@ -482,14 +482,56 @@ abstract class DBObject
protected static function MakeHyperLink($sObjClass, $sObjKey, $sLabel = '')
{
if ($sObjKey == 0) return '<em>undefined</em>';
if ($sObjKey <= 0) return '<em>'.Dict::S('UI:UndefinedObject').'</em>'; // Objects built in memory have negative IDs
return MetaModel::GetName($sObjClass)."::$sObjKey";
$oAppContext = new ApplicationContext();
$sPage = self::ComputeUIPage($sObjClass);
$sAbsoluteUrl = utils::GetAbsoluteUrlPath();
// Safety net
//
if (empty($sLabel))
{
$sLabel = MetaModel::GetName($sObjClass)." #$sObjKey";
}
$sHint = MetaModel::GetName($sObjClass)."::$sObjKey";
return "<a href=\"{$sAbsoluteUrl}{$sPage}?operation=details&class=$sObjClass&id=$sObjKey&".$oAppContext->GetForLink()."\" title=\"$sHint\">$sLabel</a>";
}
public function GetHyperlink()
{
return $this->MakeHyperLink(get_class($this), $this->GetKey(), $this->GetName());
if ($this->IsNew()) return '<em>'.Dict::S('UI:UndefinedObject').'</em>'; // Objects built in memory have negative IDs
$oAppContext = new ApplicationContext();
$sPage = $this->GetUIPage();
$sAbsoluteUrl = utils::GetAbsoluteUrlPath();
$sObjClass = get_class($this);
$sObjKey = $this->GetKey();
$sLabel = $this->GetName();
$sHint = MetaModel::GetName($sObjClass)."::$sObjKey";
return "<a href=\"{$sAbsoluteUrl}{$sPage}?operation=details&class=$sObjClass&id=$sObjKey&".$oAppContext->GetForLink()."\" title=\"$sHint\">$sLabel</a>";
}
public static function ComputeUIPage($sClass)
{
static $aUIPagesCache = array(); // Cache to store the php page used to display each class of object
if (!isset($aUIPagesCache[$sClass]))
{
$UIPage = false;
if (is_callable("$sClass::GetUIPage"))
{
$UIPage = eval("return $sClass::GetUIPage();"); // May return false in case of error
}
$aUIPagesCache[$sClass] = $UIPage === false ? './UI.php' : $UIPage;
}
$sPage = $aUIPagesCache[$sClass];
return $sPage;
}
public static function GetUIPage()
{
return 'UI.php';
}

View File

@@ -539,7 +539,12 @@ Dict::Add('EN US', 'English', 'English', array(
'Core:Synchro:Nb_Class:Objects' => '%1$s: %2$s',
'Class:SynchroDataSource/Error:AtLeastOneReconciliationKeyMustBeSpecified' => 'At Least one reconciliation key must be specified.',
'Class:SynchroDataSource/Error:DeleteRetentionDurationMustBeSpecified' => 'A delete retention period must be specified, since objects are to be deleted after being marked as obsolete',
'Class:SynchroDataSource/Error:DeletePolicyUpdateMustBeSpecified' => 'Obsolete objects are to be updated, but no update is specified.',
'Class:SynchroDataSource/Error:DeletePolicyUpdateMustBeSpecified' => 'Obsolete objects are to be updated, but no update is specified.',
'Core:SynchroReplica:PublicData' => 'Public Data',
'Core:SynchroReplica:PrivateDetails' => 'Private Details',
'Core:SynchroReplica:BackToDataSource' => 'Go Back to the Synchro Data Source: %1$s',
'Core:SynchroReplica:ListOfReplicas' => 'List of Replica',
));
//

83
synchro/replica.php Normal file
View File

@@ -0,0 +1,83 @@
<?php
// Copyright (C) 2011 Combodo SARL
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 3 of the License.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
/**
* Display and search synchro replicas
*/
require_once('../approot.inc.php');
require_once(APPROOT.'/application/application.inc.php');
require_once(APPROOT.'/application/itopwebpage.class.inc.php');
require_once(APPROOT.'/application/startup.inc.php');
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
LoginWebPage::DoLogin(true); // Check user rights and prompt if needed, admins only here !
$sOperation = utils::ReadParam('operation', 'menu');
$oAppContext = new ApplicationContext();
$oP = new iTopWebPage("iTop - Synchro Replicas");
// Main program
$sOperation = utils::ReadParam('operation', 'details');
try
{
switch($sOperation)
{
case 'details':
$iId = utils::ReadParam('id', null);
if ($iId == null)
{
throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'id'));
}
$oReplica = MetaModel::GetObject('SynchroReplica', $iId);
$oReplica->DisplayDetails($oP);
break;
case 'oql':
$sOQL = utils::ReadParam('oql', null);
if ($sOQL == null)
{
throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'oql'));
}
$oP->add('<p class="page-header">'.MetaModel::GetClassIcon('SynchroReplica').Dict::S('Core:SynchroReplica:ListOfReplicas').'</p>');
$iSourceId = utils::ReadParam('datasource', null);
if ($iSourceId != null)
{
$oSource = MetaModel::GetObject('SynchroDataSource', $iSourceId);
$oP->p(Dict::Format('Core:SynchroReplica:BackToDataSource', $oSource->GetHyperlink()).'</a>');
}
$oFilter = DBObjectSearch::FromOQL($sOQL);
$oBlock = new DisplayBlock($oFilter, 'list', false, array('menu'=>false));
$oBlock->Display($oP, 1);
break;
}
}
catch(CoreException $e)
{
$oP->p('<b>An error occured while running the query:</b>');
$oP->p($e->getHtmlDesc());
}
catch(Exception $e)
{
$oP->p('<b>An error occured while running the query:</b>');
$oP->p($e->getMessage());
}
$oP->output();
?>
?>

View File

@@ -102,7 +102,6 @@ else
$oP = new WebPage(Dict::S("TitleSynchroExecution"));
$sDataSourcesList = utils::ReadParam('data_sources', null, true);
$bSimulate = (utils::ReadParam('simulate', '0', true) == '1');
if ($sDataSourcesList == null)
{
@@ -110,6 +109,8 @@ else
}
}
$bSimulate = (utils::ReadParam('simulate', '0', true) == '1');
foreach(explode(',', $sDataSourcesList) as $iSDS)
{

View File

@@ -174,6 +174,8 @@ class SynchroDataSource extends cmdbAbstractObject
$oLastLog = $oSetSynchroLog->Fetch();
$sStartDate = $oLastLog->Get('start_date');
$oLastLog->Get('stats_nb_replica_seen');
$iLastLog = 0;
$iDSid = $this->GetKey();
if ($oLastLog->Get('status') == 'running')
{
// Still running !
@@ -182,6 +184,7 @@ class SynchroDataSource extends cmdbAbstractObject
else
{
$sEndDate = $oLastLog->Get('end_date');
$iLastLog = $oLastLog->GetKey();
$oPage->p('<h2>'.Dict::Format('Core:Synchro:SynchroEndedOn_Date', $sEndDate).'</h2>');
}
@@ -204,6 +207,7 @@ class SynchroDataSource extends cmdbAbstractObject
$sScript .= "end: 'Done'";
$sScript .= "};\n";
$sScript .= <<<EOF
var sLastLog = '$iLastLog';
function UpdateSynoptics(id)
{
var aValues = aSynchroLog[id];
@@ -214,6 +218,33 @@ class SynchroDataSource extends cmdbAbstractObject
var fOpacity = (aValues[sKey] == 0) ? 0.3 : 1;
$('#'+sKey).fadeTo("slow", fOpacity);
}
//alert('id = '+id+', lastLog='+sLastLog+', id==sLastLog: '+(id==sLastLog)+' obj_updated_errors: '+aValues['obj_updated_errors']);
if ( (id == sLastLog) && (aValues['obj_new_errors'] > 0) )
{
$('#new_errors_link').show();
}
else
{
$('#new_errors_link').hide();
}
if ( (id == sLastLog) && (aValues['obj_updated_errors'] > 0) )
{
$('#updated_errors_link').show();
}
else
{
$('#updated_errors_link').hide();
}
if ( (id == sLastLog) && (aValues['obj_disappeared_errors'] > 0) )
{
$('#disappeared_errors_link').show();
}
else
{
$('#disappeared_errors_link').hide();
}
}
EOF
;
@@ -236,6 +267,7 @@ EOF
<tr>
EOF
);
$sBaseOQL = "SELECT SynchroReplica WHERE sync_source_id=".$this->GetKey()." AND status_last_error!=''";
$oPage->add($this->HtmlBox('repl_ignored', $aData, '#999').'<td colspan="2">&nbsp;</td>');
$oPage->add("</tr>\n<tr>");
$oPage->add($this->HtmlBox('repl_disappeared', $aData, '#630', 'rowspan="4"').'<td rowspan="4" class="arrow">=&gt;</td>'.$this->HtmlBox('obj_disappeared_no_action', $aData, '#333'));
@@ -244,13 +276,15 @@ EOF
$oPage->add("</tr>\n<tr>");
$oPage->add($this->HtmlBox('obj_obsoleted', $aData, '#630'));
$oPage->add("</tr>\n<tr>");
$oPage->add($this->HtmlBox('obj_disappeared_errors', $aData, '#C00'));
$sOQL = urlencode($sBaseOQL." AND status='obsolete'");
$oPage->add($this->HtmlBox('obj_disappeared_errors', $aData, '#C00', '', " <a style=\"color:#fff\" href=\"../synchro/replica?operation=oql&datasource=$iDSid&oql=$sOQL\" id=\"disappeared_errors_link\">Show</a>"));
$oPage->add("</tr>\n<tr>");
$oPage->add($this->HtmlBox('repl_existing', $aData, '#093', 'rowspan="3"').'<td rowspan="3" class="arrow">=&gt;</td>'.$this->HtmlBox('obj_unchanged', $aData, '#393'));
$oPage->add("</tr>\n<tr>");
$oPage->add($this->HtmlBox('obj_updated', $aData, '#3C3'));
$oPage->add("</tr>\n<tr>");
$oPage->add($this->HtmlBox('obj_updated_errors', $aData, '#C00'));
$sOQL = urlencode($sBaseOQL." AND status='modified'");
$oPage->add($this->HtmlBox('obj_updated_errors', $aData, '#C00', '', " <a style=\"color:#fff\" href=\"../synchro/replica?operation=oql&datasource=$iDSid&oql=$sOQL\" id=\"updated_errors_link\">Show</a>"));
$oPage->add("</tr>\n<tr>");
$oPage->add($this->HtmlBox('repl_new', $aData, '#339', 'rowspan="4"').'<td rowspan="4" class="arrow">=&gt;</td>'.$this->HtmlBox('obj_new_unchanged', $aData, '#393'));
$oPage->add("</tr>\n<tr>");
@@ -258,9 +292,11 @@ EOF
$oPage->add("</tr>\n<tr>");
$oPage->add($this->HtmlBox('obj_created', $aData, '#339'));
$oPage->add("</tr>\n<tr>");
$oPage->add($this->HtmlBox('obj_new_errors', $aData, '#C00'));
$sOQL = urlencode($sBaseOQL." AND status='new'");
$oPage->add($this->HtmlBox('obj_new_errors', $aData, '#C00', '', " <a style=\"color:#fff\" href=\"../synchro/replica?operation=oql&datasource=$iDSid&oql=$sOQL\" id=\"new_errors_link\">Show</a>"));
$oPage->add("</tr>\n</table>\n");
$oPage->add('</td></tr></table>');
$oPage->add_ready_script("UpdateSynoptics('$iLastLog')");
}
else
{
@@ -268,13 +304,14 @@ EOF
}
}
protected function HtmlBox($sId, $aData, $sColor, $sHTMLAttribs = '')
protected function HtmlBox($sId, $aData, $sColor, $sHTMLAttribs = '', $sErrorLink = '')
{
$iCount = $aData[$sId];
$sCount = "<span id=\"c_{$sId}\">$iCount</span>";
$sLabel = Dict::Format('Core:Synchro:label_'.$sId, $sCount);
$sOpacity = ($iCount==0) ? "opacity:0.3;" : "";
return "<td id=\"$sId\" style=\"background-color:$sColor;$sOpacity;\" {$sHTMLAttribs}>$sLabel</td>";
return "<td id=\"$sId\" style=\"background-color:$sColor;$sOpacity;\" {$sHTMLAttribs}>{$sLabel}{$sErrorLink}</td>";
}
protected function ProcessLog($oLastLog)
@@ -378,6 +415,37 @@ EOF
{
parent::DoCheckToWrite();
if ($this->IsNew())
{
// When inserting a new datasource object, also create the SynchroAttribute objects
// for each field of the target class
// Create all the SynchroAttribute records
$oAttributeSet = $this->Get('attribute_list');
foreach(MetaModel::ListAttributeDefs($this->GetTargetClass()) as $sAttCode=>$oAttDef)
{
if ($oAttDef->IsScalar() && $oAttDef->IsWritable())
{
$oAttDef = MetaModel::GetAttributeDef($this->GetTargetClass(), $sAttCode);
if ($oAttDef->IsExternalKey())
{
$oAttribute = new SynchroAttExtKey();
$oAttribute->Set('reconciliation_attcode', ''); // Blank means by pkey
}
else
{
$oAttribute = new SynchroAttribute();
}
$oAttribute->Set('sync_source_id', $this->GetKey());
$oAttribute->Set('attcode', $sAttCode);
$oAttribute->Set('reconcile', MetaModel::IsReconcKey($this->GetTargetClass(), $sAttCode) ? 1 : 0);
$oAttribute->Set('update', 1);
$oAttribute->Set('update_policy', 'master_locked');
$oAttributeSet->AddObject($oAttribute);
}
}
$this->Set('attribute_list', $oAttributeSet);
}
// Check that there is at least one reconciliation key defined
if ($this->Get('reconciliation_policy') == 'use_attributes')
{
@@ -425,38 +493,6 @@ EOF
return $sTable;
}
/**
* When inserting a new datasource object, also create the SynchroAttribute objects
* for each field of the target class
*/
protected function OnInsert()
{
// Create all the SynchroAttribute records
$oAttributeSet = $this->Get('attribute_list');
foreach(MetaModel::ListAttributeDefs($this->GetTargetClass()) as $sAttCode=>$oAttDef)
{
if ($oAttDef->IsScalar() && $oAttDef->IsWritable())
{
$oAttDef = MetaModel::GetAttributeDef($this->GetTargetClass(), $sAttCode);
if ($oAttDef->IsExternalKey())
{
$oAttribute = new SynchroAttExtKey();
$oAttribute->Set('reconciliation_attcode', ''); // Blank means by pkey
}
else
{
$oAttribute = new SynchroAttribute();
}
$oAttribute->Set('sync_source_id', $this->GetKey());
$oAttribute->Set('attcode', $sAttCode);
$oAttribute->Set('reconcile', MetaModel::IsReconcKey($this->GetTargetClass(), $sAttCode) ? 1 : 0);
$oAttribute->Set('update', 1);
$oAttribute->Set('update_policy', 'master_locked');
$oAttributeSet->AddObject($oAttribute);
}
}
$this->Set('attribute_list', $oAttributeSet);
}
/**
* When the new datasource has been created, let's create the synchro_data table
* that will hold the data records and the correspoding triggers which will maintain
@@ -1084,7 +1120,7 @@ class SynchroLog extends DBObject
}
class SynchroReplica extends DBObject
class SynchroReplica extends DBObject implements iDisplay
{
static $aSearches = array(); // Cache of OQL queries used for reconciliation (per data source)
@@ -1460,6 +1496,94 @@ class SynchroReplica extends DBObject
return $aData[$sColumnName];
}
/**
* Maps the given context parameter name to the appropriate filter/search code for this class
* @param string $sContextParam Name of the context parameter, i.e. 'org_id'
* @return string Filter code, i.e. 'customer_id'
*/
public static function MapContextParam($sContextParam)
{
if ($sContextParam == 'menu')
{
return null;
}
else
{
return $sContextParam;
}
}
/**
* This function returns a 'hilight' CSS class, used to hilight a given row in a table
* There are currently (i.e defined in the CSS) 4 possible values HILIGHT_CLASS_CRITICAL,
* HILIGHT_CLASS_WARNING, HILIGHT_CLASS_OK, HILIGHT_CLASS_NONE
* To Be overridden by derived classes
* @param void
* @return String The desired higlight class for the object/row
*/
public function GetHilightClass()
{
// Possible return values are:
// HILIGHT_CLASS_CRITICAL, HILIGHT_CLASS_WARNING, HILIGHT_CLASS_OK, HILIGHT_CLASS_NONE
return HILIGHT_CLASS_NONE; // Not hilighted by default
}
public static function GetUIPage()
{
return '../synchro/replica.php';
}
function DisplayDetails(WebPage $oPage, $bEditMode = false)
{
// Object's details
//$this->DisplayBareHeader($oPage, $bEditMode);
$oPage->AddTabContainer(OBJECT_PROPERTIES_TAB);
$oPage->SetCurrentTabContainer(OBJECT_PROPERTIES_TAB);
$oPage->SetCurrentTab(Dict::S('UI:PropertiesTab'));
$this->DisplayBareProperties($oPage, $bEditMode);
}
function DisplayBareProperties(WebPage $oPage, $bEditMode = false)
{
if ($bEditMode) return; // Not editable
$oPage->add('<table style="vertical-align:top"><tr style="vertical-align:top"><td>');
$aDetails = array();
$sClass = get_class($this);
$oPage->add('<fieldset>');
$oPage->add('<legend>'.Dict::S('Core:SynchroReplica:PrivateDetails').'</legend>');
$aZList = MetaModel::GetZListItems($sClass, 'details');
foreach( $aZList as $sAttCode)
{
$sDisplayValue = $this->GetAsHTML($sAttCode);
$aDetails[] = array('label' => '<span title="'.MetaModel::GetDescription($sClass, $sAttCode).'">'.MetaModel::GetLabel($sClass, $sAttCode).'</span>', 'value' => $sDisplayValue);
}
$oPage->Details($aDetails);
$oPage->add('</fieldset>');
$oPage->add('</td><td>');
$oPage->add('<fieldset>');
$oPage->add('<legend>'.Dict::S('Core:SynchroReplica:PublicData').'</legend>');
$oSource = MetaModel::GetObject('SynchroDataSource', $this->Get('sync_source_id'));
$sSQLTable = $oSource->GetDataTable();
$sSQL = "SELECT * FROM $sSQLTable WHERE id=".$this->GetKey();
$rQuery = CMDBSource::Query($sSQL);
$aData = CMDBSource::FetchArray($rQuery);
$aHeaders = array('attcode' => array('label' => 'Attribute Code', 'description' => ''),
'data' => array('label' => 'Value', 'description' => ''));
$aRows = array();
foreach($aData as $sKey => $value)
{
$aRows[] = array('attcode' => $sKey, 'data' => $value);
}
$oPage->Table($aHeaders, $aRows);
$oPage->add('</fieldset>');
$oPage->add('</td></tr></table>');
}
}
// TO DO: finalize.... admins only ? which options ? troubleshoot WebPageMenuNode::__construct(.... sEnableClass...) ?
@@ -1467,6 +1591,7 @@ class SynchroReplica extends DBObject
{
$oAdminMenu = new MenuGroup('AdminTools', 80 /* fRank */);
new OQLMenuNode('DataSources', 'SELECT SynchroDataSource', $oAdminMenu->GetIndex(), 12 /* fRank */, true, 'SynchroDataSource', UR_ACTION_MODIFY, UR_ALLOWED_YES);
new OQLMenuNode('Replicas', 'SELECT SynchroReplica', $oAdminMenu->GetIndex(), 12 /* fRank */, true, 'SynchroReplica', UR_ACTION_MODIFY, UR_ALLOWED_YES);
new WebPageMenuNode('Test:RunSynchro', '../synchro/synchro_exec.php', $oAdminMenu->GetIndex(), 13 /* fRank */, 'SynchroDataSource');
}
?>