Merge remote-tracking branch 'origin/master' into feature/query-interfaces-6018
This commit is contained in:
commit
c0c7a815f8
|
@ -73,6 +73,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||||
# information on available options.
|
# information on available options.
|
||||||
config.vm.provider "virtualbox" do |vb|
|
config.vm.provider "virtualbox" do |vb|
|
||||||
vb.customize ["setextradata", :id, "VBoxInternal2/SharedFoldersEnableSymlinksCreate//vagrant/config", "1"]
|
vb.customize ["setextradata", :id, "VBoxInternal2/SharedFoldersEnableSymlinksCreate//vagrant/config", "1"]
|
||||||
|
vb.customize ["modifyvm", :id, "--memory", "1024"]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Enable provisioning with Puppet stand alone. Puppet manifests
|
# Enable provisioning with Puppet stand alone. Puppet manifests
|
||||||
|
|
|
@ -32,30 +32,41 @@ namespace Icinga\Protocol;
|
||||||
/**
|
/**
|
||||||
* Discover dns records using regular or reverse lookup
|
* Discover dns records using regular or reverse lookup
|
||||||
*/
|
*/
|
||||||
class Dns {
|
class Dns
|
||||||
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all ldap records for the given domain
|
* Discover all service records on a given domain
|
||||||
*
|
*
|
||||||
* @param String $query The domain to query
|
* @param string $domain The domain to search
|
||||||
|
* @param string $service The type of the service, like for example 'ldaps' or 'ldap'
|
||||||
|
* @param string $protocol The transport protocol used by the service, defaults to 'tcp'
|
||||||
*
|
*
|
||||||
* @return array An array of entries
|
* @return array|bool An array of all service domains
|
||||||
*/
|
*/
|
||||||
public static function ldapRecords($query)
|
public static function getSrvRecords($domain, $service, $protocol = 'tcp')
|
||||||
{
|
{
|
||||||
$ldaps_records = dns_get_record('_ldaps._tcp.' . $query);
|
$records = dns_get_record('_' . $service . '._' . $protocol . '.' . $domain, DNS_SRV);
|
||||||
$ldap_records = dns_get_record('_ldap._tcp.' . $query);
|
if ($records === false) {
|
||||||
return array_merge($ldaps_records, $ldap_records);
|
return false;
|
||||||
|
}
|
||||||
|
$targets = array();
|
||||||
|
foreach ($records as $record) {
|
||||||
|
if (array_key_exists('target', $record)) {
|
||||||
|
$targets[] = $record['target'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $targets;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all ldap records for the given domain
|
* Get all ldap records for the given domain
|
||||||
*
|
*
|
||||||
* @param String $query The domain to query
|
* @param string $query The domain to query
|
||||||
* @param int $type The type of DNS-entry to fetch, see http://www.php.net/manual/de/function.dns-get-record.php
|
* @param int $type The type of DNS-entry to fetch, see
|
||||||
* for available types
|
* http://www.php.net/manual/de/function.dns-get-record.php for available types
|
||||||
*
|
*
|
||||||
* @return array|Boolean An array of entries
|
* @return array|bool An array of entries
|
||||||
*/
|
*/
|
||||||
public static function records($query, $type = DNS_ANY)
|
public static function records($query, $type = DNS_ANY)
|
||||||
{
|
{
|
||||||
|
@ -63,12 +74,12 @@ class Dns {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reverse lookup all hostname on the given ip address
|
* Reverse lookup all host names available on the given ip address
|
||||||
*
|
*
|
||||||
* @param $ipAddress
|
* @param string $ipAddress
|
||||||
* @param int $type
|
* @param int $type
|
||||||
*
|
*
|
||||||
* @return array|Boolean
|
* @return array|bool
|
||||||
*/
|
*/
|
||||||
public static function ptr($ipAddress, $type = DNS_ANY)
|
public static function ptr($ipAddress, $type = DNS_ANY)
|
||||||
{
|
{
|
||||||
|
@ -83,9 +94,9 @@ class Dns {
|
||||||
/**
|
/**
|
||||||
* Get the IPv4 address of the given hostname.
|
* Get the IPv4 address of the given hostname.
|
||||||
*
|
*
|
||||||
* @param $hostname The hostname to resolve
|
* @param $hostname The hostname to resolve
|
||||||
*
|
*
|
||||||
* @return String|Boolean The IPv4 address of the given hostname, or false when no entry exists.
|
* @return string|bool The IPv4 address of the given hostname, or false when no entry exists.
|
||||||
*/
|
*/
|
||||||
public static function ipv4($hostname)
|
public static function ipv4($hostname)
|
||||||
{
|
{
|
||||||
|
@ -99,9 +110,9 @@ class Dns {
|
||||||
/**
|
/**
|
||||||
* Get the IPv6 address of the given hostname.
|
* Get the IPv6 address of the given hostname.
|
||||||
*
|
*
|
||||||
* @param $hostname The hostname to resolve
|
* @param $hostname The hostname to resolve
|
||||||
*
|
*
|
||||||
* @return String|Boolean The IPv6 address of the given hostname, or false when no entry exists.
|
* @return string|bool The IPv6 address of the given hostname, or false when no entry exists.
|
||||||
*/
|
*/
|
||||||
public static function ipv6($hostname)
|
public static function ipv6($hostname)
|
||||||
{
|
{
|
||||||
|
@ -111,4 +122,4 @@ class Dns {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,12 +105,20 @@ class Connection
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the bind on this connection was already performed
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $bindDone = false;
|
||||||
|
|
||||||
protected $root;
|
protected $root;
|
||||||
|
|
||||||
protected $supports_v3 = false;
|
protected $supports_v3 = false;
|
||||||
protected $supports_tls = false;
|
protected $supports_tls = false;
|
||||||
|
|
||||||
protected $capabilities;
|
protected $capabilities;
|
||||||
|
protected $namingContexts;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
@ -155,6 +163,8 @@ class Connection
|
||||||
public function hasDN($dn)
|
public function hasDN($dn)
|
||||||
{
|
{
|
||||||
$this->connect();
|
$this->connect();
|
||||||
|
$this->bind();
|
||||||
|
|
||||||
$result = ldap_read($this->ds, $dn, '(objectClass=*)', array('objectClass'));
|
$result = ldap_read($this->ds, $dn, '(objectClass=*)', array('objectClass'));
|
||||||
return ldap_count_entries($this->ds, $result) > 0;
|
return ldap_count_entries($this->ds, $result) > 0;
|
||||||
}
|
}
|
||||||
|
@ -162,6 +172,8 @@ class Connection
|
||||||
public function deleteRecursively($dn)
|
public function deleteRecursively($dn)
|
||||||
{
|
{
|
||||||
$this->connect();
|
$this->connect();
|
||||||
|
$this->bind();
|
||||||
|
|
||||||
$result = @ldap_list($this->ds, $dn, '(objectClass=*)', array('objectClass'));
|
$result = @ldap_list($this->ds, $dn, '(objectClass=*)', array('objectClass'));
|
||||||
if ($result === false) {
|
if ($result === false) {
|
||||||
if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) {
|
if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) {
|
||||||
|
@ -176,7 +188,7 @@ class Connection
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$children = ldap_get_entries($this->ds, $result);
|
$children = ldap_get_entries($this->ds, $result);
|
||||||
for($i = 0; $i < $children['count']; $i++) {
|
for ($i = 0; $i < $children['count']; $i++) {
|
||||||
$result = $this->deleteRecursively($children[$i]['dn']);
|
$result = $this->deleteRecursively($children[$i]['dn']);
|
||||||
if (!$result) {
|
if (!$result) {
|
||||||
//return result code, if delete fails
|
//return result code, if delete fails
|
||||||
|
@ -189,6 +201,7 @@ class Connection
|
||||||
public function deleteDN($dn)
|
public function deleteDN($dn)
|
||||||
{
|
{
|
||||||
$this->connect();
|
$this->connect();
|
||||||
|
$this->bind();
|
||||||
|
|
||||||
$result = @ldap_delete($this->ds, $dn);
|
$result = @ldap_delete($this->ds, $dn);
|
||||||
if ($result === false) {
|
if ($result === false) {
|
||||||
|
@ -213,17 +226,23 @@ class Connection
|
||||||
* @param $query
|
* @param $query
|
||||||
* @param array $fields
|
* @param array $fields
|
||||||
*
|
*
|
||||||
* @return bool|String Returns the distinguished name, or false when the given query yields no results
|
* @return null|string Returns the distinguished name, or false when the given query yields no results
|
||||||
*/
|
*/
|
||||||
public function fetchDN($query, $fields = array())
|
public function fetchDN($query, $fields = array())
|
||||||
{
|
{
|
||||||
$rows = $this->fetchAll($query, $fields);
|
$rows = $this->fetchAll($query, $fields);
|
||||||
if (count($rows) !== 1) {
|
if (count($rows) !== 1) {
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
return key($rows);
|
return key($rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $query
|
||||||
|
* @param array $fields
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
public function fetchRow($query, $fields = array())
|
public function fetchRow($query, $fields = array())
|
||||||
{
|
{
|
||||||
// TODO: This is ugly, make it better!
|
// TODO: This is ugly, make it better!
|
||||||
|
@ -231,6 +250,11 @@ class Connection
|
||||||
return array_shift($results);
|
return array_shift($results);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Query $query
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
public function count(Query $query)
|
public function count(Query $query)
|
||||||
{
|
{
|
||||||
$results = $this->runQuery($query, '+');
|
$results = $this->runQuery($query, '+');
|
||||||
|
@ -289,6 +313,7 @@ class Connection
|
||||||
protected function runQuery($query, $fields)
|
protected function runQuery($query, $fields)
|
||||||
{
|
{
|
||||||
$this->connect();
|
$this->connect();
|
||||||
|
$this->bind();
|
||||||
if ($query instanceof Query) {
|
if ($query instanceof Query) {
|
||||||
$fields = $query->listFields();
|
$fields = $query->listFields();
|
||||||
}
|
}
|
||||||
|
@ -350,6 +375,11 @@ class Connection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param null $sub
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
protected function getConfigDir($sub = null)
|
protected function getConfigDir($sub = null)
|
||||||
{
|
{
|
||||||
$dir = Config::getInstance()->getConfigDir() . '/ldap';
|
$dir = Config::getInstance()->getConfigDir() . '/ldap';
|
||||||
|
@ -359,6 +389,12 @@ class Connection
|
||||||
return $dir;
|
return $dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to the given ldap server and apply settings depending on the discovered capabilities
|
||||||
|
*
|
||||||
|
* @return resource A positive LDAP link identifier
|
||||||
|
* @throws \Exception When the connection is not possible
|
||||||
|
*/
|
||||||
protected function prepareNewConnection()
|
protected function prepareNewConnection()
|
||||||
{
|
{
|
||||||
$use_tls = false;
|
$use_tls = false;
|
||||||
|
@ -370,11 +406,12 @@ class Connection
|
||||||
}
|
}
|
||||||
|
|
||||||
$ds = ldap_connect($this->hostname, $this->port);
|
$ds = ldap_connect($this->hostname, $this->port);
|
||||||
$cap = $this->discoverCapabilities($ds);
|
list($cap, $namingContexts) = $this->discoverCapabilities($ds);
|
||||||
$this->capabilities = $cap;
|
$this->capabilities = $cap;
|
||||||
|
$this->namingContexts = $namingContexts;
|
||||||
|
|
||||||
if ($use_tls) {
|
if ($use_tls) {
|
||||||
if ($cap->starttls) {
|
if ($cap->supports_starttls) {
|
||||||
if (@ldap_start_tls($ds)) {
|
if (@ldap_start_tls($ds)) {
|
||||||
Logger::debug('LDAP STARTTLS succeeded');
|
Logger::debug('LDAP STARTTLS succeeded');
|
||||||
} else {
|
} else {
|
||||||
|
@ -398,9 +435,9 @@ class Connection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ldap_rename requires LDAPv3:
|
// ldap_rename requires LDAPv3:
|
||||||
if ($cap->ldapv3) {
|
if ($cap->supports_ldapv3) {
|
||||||
if (! ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3)) {
|
if (! ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3)) {
|
||||||
throw new Exception('LDAPv3 is required');
|
throw new \Exception('LDAPv3 is required');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
@ -430,17 +467,31 @@ class Connection
|
||||||
}
|
}
|
||||||
putenv('LDAPRC=' . $ldap_conf);
|
putenv('LDAPRC=' . $ldap_conf);
|
||||||
if (getenv('LDAPRC') !== $ldap_conf) {
|
if (getenv('LDAPRC') !== $ldap_conf) {
|
||||||
throw new Exception('putenv failed');
|
throw new \Exception('putenv failed');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function hasCapabilityStarTSL($cap)
|
/**
|
||||||
|
* Return if the capability object contains support for StartTLS
|
||||||
|
*
|
||||||
|
* @param $cap The object containing the capabilities
|
||||||
|
*
|
||||||
|
* @return bool Whether StartTLS is supported
|
||||||
|
*/
|
||||||
|
protected function hasCapabilityStartTLS($cap)
|
||||||
{
|
{
|
||||||
$cap = $this->getExtensionCapabilities($cap);
|
$cap = $this->getExtensionCapabilities($cap);
|
||||||
return isset($cap['1.3.6.1.4.1.1466.20037']);
|
return isset($cap['1.3.6.1.4.1.1466.20037']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if the capability objects contains support for LdapV3
|
||||||
|
*
|
||||||
|
* @param $cap
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
protected function hasCapabilityLdapV3($cap)
|
protected function hasCapabilityLdapV3($cap)
|
||||||
{
|
{
|
||||||
if ((is_string($cap->supportedLDAPVersion)
|
if ((is_string($cap->supportedLDAPVersion)
|
||||||
|
@ -453,6 +504,13 @@ class Connection
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract an array of all extension capabilities from the given ldap response
|
||||||
|
*
|
||||||
|
* @param $cap object The response returned by a ldap_search discovery query
|
||||||
|
*
|
||||||
|
* @return object The extracted capabilities.
|
||||||
|
*/
|
||||||
protected function getExtensionCapabilities($cap)
|
protected function getExtensionCapabilities($cap)
|
||||||
{
|
{
|
||||||
$extensions = array();
|
$extensions = array();
|
||||||
|
@ -468,6 +526,13 @@ class Connection
|
||||||
return $extensions;
|
return $extensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract an array of all MSAD capabilities from the given ldap response
|
||||||
|
*
|
||||||
|
* @param $cap object The response returned by a ldap_search discovery query
|
||||||
|
*
|
||||||
|
* @return object The extracted capabilities.
|
||||||
|
*/
|
||||||
protected function getMsCapabilities($cap)
|
protected function getMsCapabilities($cap)
|
||||||
{
|
{
|
||||||
$ms = array();
|
$ms = array();
|
||||||
|
@ -485,6 +550,13 @@ class Connection
|
||||||
return (object)$ms;
|
return (object)$ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a single capability name entry into camel-case
|
||||||
|
*
|
||||||
|
* @param $name string The name to convert
|
||||||
|
*
|
||||||
|
* @return string The name in camel-case
|
||||||
|
*/
|
||||||
private function convName($name)
|
private function convName($name)
|
||||||
{
|
{
|
||||||
$parts = explode('_', $name);
|
$parts = explode('_', $name);
|
||||||
|
@ -508,7 +580,7 @@ class Connection
|
||||||
/**
|
/**
|
||||||
* Get the default naming context of this ldap connection
|
* Get the default naming context of this ldap connection
|
||||||
*
|
*
|
||||||
* @return String|null the default naming context, or null when no contexts are available
|
* @return string|null the default naming context, or null when no contexts are available
|
||||||
*/
|
*/
|
||||||
public function getDefaultNamingContext()
|
public function getDefaultNamingContext()
|
||||||
{
|
{
|
||||||
|
@ -527,23 +599,22 @@ class Connection
|
||||||
*/
|
*/
|
||||||
public function namingContexts()
|
public function namingContexts()
|
||||||
{
|
{
|
||||||
$cap = $this->capabilities;
|
if (!isset($this->namingContexts)) {
|
||||||
if (!isset($cap->namingContexts)) {
|
|
||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
if (!is_array($cap->namingContexts)) {
|
if (!is_array($this->namingContexts)) {
|
||||||
return array($cap->namingContexts);
|
return array($this->namingContexts);
|
||||||
}
|
}
|
||||||
return $cap->namingContexts;
|
return $this->namingContexts;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Discover the capabilities of the given ldap-server
|
* Discover the capabilities of the given ldap-server
|
||||||
*
|
*
|
||||||
* @param $ds The link identifier of the current ldap connection
|
* @param resource $ds The link identifier of the current ldap connection
|
||||||
*
|
*
|
||||||
* @return bool|object The capabilities or false if the server has none
|
* @return array The capabilities and naming-contexts
|
||||||
* @throws Exception When the capability query fails
|
* @throws \Exception When the capability query fails
|
||||||
*/
|
*/
|
||||||
protected function discoverCapabilities($ds)
|
protected function discoverCapabilities($ds)
|
||||||
{
|
{
|
||||||
|
@ -563,8 +634,6 @@ class Connection
|
||||||
'+'
|
'+'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
$fields = $query->listFields();
|
|
||||||
|
|
||||||
$result = @ldap_read(
|
$result = @ldap_read(
|
||||||
$ds,
|
$ds,
|
||||||
'',
|
'',
|
||||||
|
@ -573,7 +642,7 @@ class Connection
|
||||||
);
|
);
|
||||||
|
|
||||||
if (! $result) {
|
if (! $result) {
|
||||||
throw new Exception(
|
throw new \Exception(
|
||||||
sprintf(
|
sprintf(
|
||||||
'Capability query failed (%s:%d): %s',
|
'Capability query failed (%s:%d): %s',
|
||||||
$this->hostname,
|
$this->hostname,
|
||||||
|
@ -585,8 +654,8 @@ class Connection
|
||||||
$entry = ldap_first_entry($ds, $result);
|
$entry = ldap_first_entry($ds, $result);
|
||||||
|
|
||||||
$cap = (object) array(
|
$cap = (object) array(
|
||||||
'ldapv3' => false,
|
'supports_ldapv3' => false,
|
||||||
'starttls' => false,
|
'supports_starttls' => false,
|
||||||
'msCapabilities' => array()
|
'msCapabilities' => array()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -596,41 +665,51 @@ class Connection
|
||||||
}
|
}
|
||||||
$ldapAttributes = ldap_get_attributes($ds, $entry);
|
$ldapAttributes = ldap_get_attributes($ds, $entry);
|
||||||
$result = $this->cleanupAttributes($ldapAttributes);
|
$result = $this->cleanupAttributes($ldapAttributes);
|
||||||
$cap->ldapv3 = $this->hasCapabilityLdapV3($result);
|
$cap->supports_ldapv3 = $this->hasCapabilityLdapV3($result);
|
||||||
$cap->starttls = $this->hasCapabilityStarTSL($result);
|
$cap->supports_starttls = $this->hasCapabilityStartTLS($result);
|
||||||
$cap->msCapabilities = $this->getMsCapabilities($result);
|
$cap->msCapabilities = $this->getMsCapabilities($result);
|
||||||
$cap->namingContexts = $result->namingContexts;
|
|
||||||
/*
|
|
||||||
if (isset($result->dnsHostName)) {
|
|
||||||
ldap_set_option($ds, LDAP_OPT_HOST_NAME, $result->dnsHostName);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
return $cap;
|
return array($cap, $result->namingContexts);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function connect($anonymous = false)
|
/**
|
||||||
|
* Try to connect to the given ldap server
|
||||||
|
*
|
||||||
|
* @throws \Exception When connecting is not possible
|
||||||
|
*/
|
||||||
|
public function connect()
|
||||||
{
|
{
|
||||||
if ($this->ds !== null) {
|
if ($this->ds !== null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->ds = $this->prepareNewConnection();
|
$this->ds = $this->prepareNewConnection();
|
||||||
|
}
|
||||||
|
|
||||||
if (!$anonymous) {
|
/**
|
||||||
$r = @ldap_bind($this->ds, $this->bind_dn, $this->bind_pw);
|
* Try to bind to the current ldap domain using the provided bind_dn and bind_pw
|
||||||
if (! $r) {
|
*
|
||||||
throw new \Exception(
|
* @throws \Exception When binding is not possible
|
||||||
sprintf(
|
*/
|
||||||
'LDAP connection to %s:%s (%s / %s) failed: %s',
|
public function bind()
|
||||||
$this->hostname,
|
{
|
||||||
$this->port,
|
if ($this->bindDone) {
|
||||||
$this->bind_dn,
|
return;
|
||||||
'***' /* $this->bind_pw */,
|
|
||||||
ldap_error($this->ds)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$r = @ldap_bind($this->ds, $this->bind_dn, $this->bind_pw);
|
||||||
|
if (! $r) {
|
||||||
|
throw new \Exception(
|
||||||
|
sprintf(
|
||||||
|
'LDAP connection to %s:%s (%s / %s) failed: %s',
|
||||||
|
$this->hostname,
|
||||||
|
$this->port,
|
||||||
|
$this->bind_dn,
|
||||||
|
'***' /* $this->bind_pw */,
|
||||||
|
ldap_error($this->ds)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$this->bindDone = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -649,10 +728,10 @@ class Connection
|
||||||
/**
|
/**
|
||||||
* Modify a ldap entry
|
* Modify a ldap entry
|
||||||
*
|
*
|
||||||
* @param string $dn DN of the entry to change
|
* @param string $dn DN of the entry to change
|
||||||
* @param array $entry Change values
|
* @param array $entry Change values
|
||||||
*
|
*
|
||||||
* @return bool True on success
|
* @return bool True on success
|
||||||
*/
|
*/
|
||||||
public function modifyEntry($dn, array $entry)
|
public function modifyEntry($dn, array $entry)
|
||||||
{
|
{
|
||||||
|
|
|
@ -32,6 +32,8 @@ namespace Icinga\Web\Widget\Chart;
|
||||||
|
|
||||||
use Icinga\Web\Widget\AbstractWidget;
|
use Icinga\Web\Widget\AbstractWidget;
|
||||||
use Icinga\Web\Url;
|
use Icinga\Web\Url;
|
||||||
|
use Icinga\Util\Format;
|
||||||
|
use Icinga\Logger\Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A SVG-PieChart intended to be displayed as a small icon next to labels, to offer a better visualization of the
|
* A SVG-PieChart intended to be displayed as a small icon next to labels, to offer a better visualization of the
|
||||||
|
@ -44,14 +46,33 @@ use Icinga\Web\Url;
|
||||||
*/
|
*/
|
||||||
class InlinePie extends AbstractWidget
|
class InlinePie extends AbstractWidget
|
||||||
{
|
{
|
||||||
|
const NUMBER_FORMAT_TIME = 'time';
|
||||||
|
const NUMBER_FORMAT_BYTES = 'bytes';
|
||||||
|
const NUMBER_FORMAT_RATIO = 'ratio';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* The template string used for rendering this widget
|
||||||
* The template string used for rendering this widget
|
* The template string used for rendering this widget
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
private $template =<<<'EOD'
|
private $template =<<<'EOD'
|
||||||
<span class="sparkline" sparkTitle="{title}" sparkWidth="{width}" sparkHeight="{height}" style="{style}"
|
<span
|
||||||
sparkSliceColors="[{colors}]" values="{data}" sparkType="pie"></span>
|
class="sparkline"
|
||||||
|
sparkTitle="{title}"
|
||||||
|
sparkWidth="{width}"
|
||||||
|
sparkHeight="{height}"
|
||||||
|
sparkBorderWidth="{borderWidth}"
|
||||||
|
sparkBorderColor="{borderColor}"
|
||||||
|
sparkTooltipChartTitle="{title}"
|
||||||
|
style="{style}"
|
||||||
|
labels="{labels}"
|
||||||
|
formatted="{formatted}"
|
||||||
|
hideEmptyLabel={hideEmptyLabel}
|
||||||
|
values="{data}"
|
||||||
|
tooltipFormat="{tooltipFormat}"
|
||||||
|
sparkSliceColors="[{colors}]"
|
||||||
|
sparkType="pie"></span>
|
||||||
<noscript>
|
<noscript>
|
||||||
<img class="inlinepie"
|
<img class="inlinepie"
|
||||||
title="{title}" src="{url}" style="width: {width}px; height: {height}px; {style}"
|
title="{title}" src="{url}" style="width: {width}px; height: {height}px; {style}"
|
||||||
|
@ -86,6 +107,20 @@ EOD;
|
||||||
*/
|
*/
|
||||||
private $height = 28;
|
private $height = 28;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PieChart border width
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
*/
|
||||||
|
private $borderWidth = 1.25;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The color of the border
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $borderColor = '#888';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The title of the chart
|
* The title of the chart
|
||||||
*
|
*
|
||||||
|
@ -103,10 +138,48 @@ EOD;
|
||||||
/**
|
/**
|
||||||
* The data displayed by the pie-chart
|
* The data displayed by the pie-chart
|
||||||
*
|
*
|
||||||
* @var
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $data;
|
private $data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The labels to display for each data set
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $labels = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the tooltip for the "empty" area should be hidden
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $hideEmptyLabel = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The format string used to display tooltips
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $tooltipFormat = '<b>{{title}}</b></br> {{label}}: {{formatted}} ({{percent}}%)';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number format used to render numeric values in tooltips
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $format = self::NUMBER_FORMAT_BYTES;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set if the tooltip for the empty area should be hidden
|
||||||
|
*
|
||||||
|
* @param bool $hide Whether to hide the empty area
|
||||||
|
*/
|
||||||
|
public function setHideEmptyLabel($hide = true)
|
||||||
|
{
|
||||||
|
$this->hideEmptyLabel = $hide;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the data to be displayed.
|
* Set the data to be displayed.
|
||||||
*
|
*
|
||||||
|
@ -127,7 +200,12 @@ EOD;
|
||||||
*/
|
*/
|
||||||
public function setLabels($labels = null)
|
public function setLabels($labels = null)
|
||||||
{
|
{
|
||||||
$this->url->setParam('labels', implode(',', $labels));
|
if ($labels != null) {
|
||||||
|
$this->url->setParam('labels', implode(',', $labels));
|
||||||
|
} else {
|
||||||
|
$this->url->removeKey('labels');
|
||||||
|
}
|
||||||
|
$this->labels = $labels;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,6 +224,34 @@ EOD;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the used number format
|
||||||
|
*
|
||||||
|
* @param $format string 'bytes' or 'time'
|
||||||
|
*/
|
||||||
|
public function setNumberFormat($format)
|
||||||
|
{
|
||||||
|
$this->format = $format;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A format string used to render the content of the piechart tooltips
|
||||||
|
*
|
||||||
|
* Placeholders using curly braces '{FOO}' are replace with their specific values. The format
|
||||||
|
* String may contain HTML-Markup. The available replaceable values are:
|
||||||
|
* <ul>
|
||||||
|
* <li><b>label</b>: The description for the current value </li>
|
||||||
|
* <li><b>formatted</b>: A string representing the formatted value </li>
|
||||||
|
* <li><b>value</b>: The raw (non-formatted) value used to render the piechart </li>
|
||||||
|
* <li><b>percent</b>: The percentage of the current value </li>
|
||||||
|
* </ul>
|
||||||
|
* Note: Changes will only affect JavaScript sparklines and not the SVG charts used for fallback
|
||||||
|
*/
|
||||||
|
public function setTooltipFormat($format)
|
||||||
|
{
|
||||||
|
$this->tooltipFormat = $format;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $height
|
* @param $height
|
||||||
*
|
*
|
||||||
|
@ -157,6 +263,26 @@ EOD;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the border width of the pie chart
|
||||||
|
*
|
||||||
|
* @param float $width Width in px
|
||||||
|
*/
|
||||||
|
public function setBorderWidth($width)
|
||||||
|
{
|
||||||
|
$this->borderWidth = $width;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the color of the pie chart border
|
||||||
|
*
|
||||||
|
* @param string $col The color string
|
||||||
|
*/
|
||||||
|
public function setBorderColor($col)
|
||||||
|
{
|
||||||
|
$this->borderColor = $col;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $width
|
* @param $width
|
||||||
*
|
*
|
||||||
|
@ -171,7 +297,7 @@ EOD;
|
||||||
/**
|
/**
|
||||||
* Set the styling of the created HtmlElement
|
* Set the styling of the created HtmlElement
|
||||||
*
|
*
|
||||||
* @param $style
|
* @param string $style
|
||||||
*/
|
*/
|
||||||
public function setStyle($style)
|
public function setStyle($style)
|
||||||
{
|
{
|
||||||
|
@ -179,9 +305,9 @@ EOD;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the title of the created HtmlElement
|
* Set the title of the displayed Data
|
||||||
*
|
*
|
||||||
* @param $title
|
* @param string $title
|
||||||
*/
|
*/
|
||||||
public function setTitle($title)
|
public function setTitle($title)
|
||||||
{
|
{
|
||||||
|
@ -191,10 +317,10 @@ EOD;
|
||||||
/**
|
/**
|
||||||
* Create a new InlinePie
|
* Create a new InlinePie
|
||||||
*
|
*
|
||||||
* @param array $data The data displayed by the slices
|
* @param array $data The data displayed by the slices
|
||||||
* @param array $colors The colors displayed by the slices
|
* @param array $colors An array of RGB-Color values to use
|
||||||
*/
|
*/
|
||||||
public function __construct(array $data, array $colors = null)
|
public function __construct(array $data, $colors = null)
|
||||||
{
|
{
|
||||||
$this->url = Url::fromPath('svg/chart.php');
|
$this->url = Url::fromPath('svg/chart.php');
|
||||||
if (array_key_exists('data', $data)) {
|
if (array_key_exists('data', $data)) {
|
||||||
|
@ -215,6 +341,19 @@ EOD;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a serialization containing the current label array
|
||||||
|
*
|
||||||
|
* @return string A serialized array of labels
|
||||||
|
*/
|
||||||
|
private function createLabelString () {
|
||||||
|
$labels = $this->labels;
|
||||||
|
foreach ($labels as $key => $label) {
|
||||||
|
$labels[$key] = preg_replace('/|/', '', $label);
|
||||||
|
}
|
||||||
|
return isset($this->labels) && is_array($this->labels) ? implode('|', $this->labels) : '';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders this widget via the given view and returns the
|
* Renders this widget via the given view and returns the
|
||||||
* HTML as a string
|
* HTML as a string
|
||||||
|
@ -224,19 +363,49 @@ EOD;
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
$template = $this->template;
|
$template = $this->template;
|
||||||
// Locale-ignorant string cast:
|
|
||||||
$data = array();
|
|
||||||
foreach ($this->data as $dat) {
|
|
||||||
$data[] = sprintf('%F', $dat);
|
|
||||||
}
|
|
||||||
$template = preg_replace('{{url}}', $this->url, $template);
|
$template = preg_replace('{{url}}', $this->url, $template);
|
||||||
$template = preg_replace('{{width}}', $this->width, $template);
|
|
||||||
$template = preg_replace('{{height}}', $this->height, $template);
|
// style
|
||||||
$template = preg_replace('{{title}}', $this->title, $template);
|
$template = preg_replace('{{width}}', htmlspecialchars($this->width), $template);
|
||||||
|
$template = preg_replace('{{height}}', htmlspecialchars($this->height), $template);
|
||||||
|
$template = preg_replace('{{title}}', htmlspecialchars($this->title), $template);
|
||||||
$template = preg_replace('{{style}}', $this->style, $template);
|
$template = preg_replace('{{style}}', $this->style, $template);
|
||||||
$template = preg_replace('{{data}}', implode(',', $data), $template);
|
|
||||||
$template = preg_replace('{{colors}}', implode(',', $this->colors), $template);
|
$template = preg_replace('{{colors}}', implode(',', $this->colors), $template);
|
||||||
|
$template = preg_replace('{{borderWidth}}', htmlspecialchars($this->borderWidth), $template);
|
||||||
|
$template = preg_replace('{{borderColor}}', htmlspecialchars($this->borderColor), $template);
|
||||||
|
$template = preg_replace('{{hideEmptyLabel}}', $this->hideEmptyLabel ? 'true' : 'false', $template);
|
||||||
|
|
||||||
|
// values
|
||||||
|
$formatted = array();
|
||||||
|
foreach ($this->data as $key => $value) {
|
||||||
|
$formatted[$key] = $this->formatValue($value);
|
||||||
|
}
|
||||||
|
$template = preg_replace('{{data}}', htmlspecialchars(implode(',', $this->data)), $template);
|
||||||
|
$template = preg_replace('{{formatted}}', htmlspecialchars(implode('|', $formatted)), $template);
|
||||||
|
$template = preg_replace('{{labels}}', htmlspecialchars($this->createLabelString()), $template);
|
||||||
|
$template = preg_replace('{{tooltipFormat}}', $this->tooltipFormat, $template);
|
||||||
return $template;
|
return $template;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format the given value depending on the current value of numberFormat
|
||||||
|
*
|
||||||
|
* @param float $value The value to format
|
||||||
|
*
|
||||||
|
* @return string The formatted value
|
||||||
|
*/
|
||||||
|
private function formatValue($value)
|
||||||
|
{
|
||||||
|
if ($this->format === self::NUMBER_FORMAT_BYTES) {
|
||||||
|
return Format::bytes($value);
|
||||||
|
} else if ($this->format === self::NUMBER_FORMAT_TIME) {
|
||||||
|
return Format::duration($value);
|
||||||
|
} else if ($this->format === self::NUMBER_FORMAT_RATIO) {
|
||||||
|
return $value;
|
||||||
|
} else {
|
||||||
|
Logger::warning('Unknown format string "' . $this->format . '" for InlinePie, value not formatted.');
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// @codeCoverageIgnoreEnd
|
// @codeCoverageIgnoreEnd
|
||||||
|
|
|
@ -18,19 +18,21 @@ class Zend_View_Helper_Perfdata extends Zend_View_Helper_Abstract
|
||||||
if (!$perfdata->isPercentage() && $perfdata->getMaximumValue() === null) {
|
if (!$perfdata->isPercentage() && $perfdata->getMaximumValue() === null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
$pieChart = $this->createInlinePie($perfdata, $label);
|
||||||
$pieChart = new InlinePie($this->calculatePieChartData($perfdata));
|
|
||||||
if ($compact) {
|
if ($compact) {
|
||||||
$pieChart->setTitle(
|
$pieChart->setTitle(
|
||||||
htmlspecialchars($label) . ': ' . htmlspecialchars($this->formatPerfdataValue($perfdata))
|
htmlspecialchars($label) /* . ': ' . htmlspecialchars($this->formatPerfdataValue($perfdata) */
|
||||||
);
|
);
|
||||||
if (!$float) {
|
if (! $float) {
|
||||||
$result .= $pieChart->render();
|
$result .= $pieChart->render();
|
||||||
} else {
|
} else {
|
||||||
$result .= '<div style="float: right;">' . $pieChart->render() . '</div>';
|
$result .= '<div style="float: right;">' . $pieChart->render() . '</div>';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$pieChart->setTitle(htmlspecialchars($label));
|
$pieChart->setTitle(htmlspecialchars($label));
|
||||||
|
if (! $perfdata->isPercentage()) {
|
||||||
|
$pieChart->setTooltipFormat('{{label}}: {{formatted}} ({{percent}}%)');
|
||||||
|
}
|
||||||
$pieChart->setStyle('float: left; margin: 0.2em 0.5em 0.2em 0.5em;');
|
$pieChart->setStyle('float: left; margin: 0.2em 0.5em 0.2em 0.5em;');
|
||||||
$table[] = '<tr><th>' . $pieChart->render()
|
$table[] = '<tr><th>' . $pieChart->render()
|
||||||
. htmlspecialchars($label)
|
. htmlspecialchars($label)
|
||||||
|
@ -82,4 +84,25 @@ class Zend_View_Helper_Perfdata extends Zend_View_Helper_Abstract
|
||||||
|
|
||||||
return $perfdata->getValue();
|
return $perfdata->getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function createInlinePie(Perfdata $perfdata, $label = '')
|
||||||
|
{
|
||||||
|
$pieChart = new InlinePie($this->calculatePieChartData($perfdata));
|
||||||
|
$pieChart->setLabels(array($label, $label, $label, ''));
|
||||||
|
$pieChart->setHideEmptyLabel();
|
||||||
|
|
||||||
|
//$pieChart->setHeight(32)->setWidth(32);
|
||||||
|
if ($perfdata->isBytes()) {
|
||||||
|
$pieChart->setTooltipFormat('{{label}}: {{formatted}} ({{percent}}%)');
|
||||||
|
$pieChart->setNumberFormat(InlinePie::NUMBER_FORMAT_BYTES);
|
||||||
|
} else if ($perfdata->isSeconds()) {
|
||||||
|
$pieChart->setTooltipFormat('{{label}}: {{formatted}} ({{percent}}%)');
|
||||||
|
$pieChart->setNumberFormat(InlinePie::NUMBER_FORMAT_TIME);
|
||||||
|
} else {
|
||||||
|
$pieChart->setTooltipFormat('{{label}}: {{formatted}}%');
|
||||||
|
$pieChart->setNumberFormat(InlinePie::NUMBER_FORMAT_RATIO);
|
||||||
|
$pieChart->setHideEmptyLabel();
|
||||||
|
}
|
||||||
|
return $pieChart;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,8 +70,37 @@
|
||||||
$('input.autofocus', el).focus();
|
$('input.autofocus', el).focus();
|
||||||
|
|
||||||
// replace all sparklines
|
// replace all sparklines
|
||||||
$('span.sparkline', el).sparkline('html', { enableTagOptions: true });
|
$('span.sparkline', el).each(function(i, element) {
|
||||||
|
// read custom options
|
||||||
|
var $spark = $(element);
|
||||||
|
var labels = $spark.attr('labels').split('|');
|
||||||
|
var formatted = $spark.attr('formatted').split('|');
|
||||||
|
var tooltipChartTitle = $spark.attr('sparkTooltipChartTitle') || '';
|
||||||
|
var format = $spark.attr('tooltipformat');
|
||||||
|
var hideEmpty = $spark.attr('hideEmptyLabel') === 'true';
|
||||||
|
$spark.sparkline(
|
||||||
|
'html',
|
||||||
|
{
|
||||||
|
enableTagOptions: true,
|
||||||
|
tooltipFormatter: function (sparkline, options, fields) {
|
||||||
|
var out = format;
|
||||||
|
if (hideEmpty && fields.offset === 3) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
var replace = {
|
||||||
|
title: tooltipChartTitle,
|
||||||
|
label: labels[fields.offset] ? labels[fields.offset] : fields.offset,
|
||||||
|
formatted: formatted[fields.offset] ? formatted[fields.offset] : '',
|
||||||
|
value: fields.value,
|
||||||
|
percent: Math.round(fields.percent * 100) / 100
|
||||||
|
};
|
||||||
|
$.each(replace, function(key, value) {
|
||||||
|
out = out.replace('{{' + key + '}}', value);
|
||||||
|
});
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
var searchField = $('#menu input.search', el);
|
var searchField = $('#menu input.search', el);
|
||||||
// Remember initial search field value if any
|
// Remember initial search field value if any
|
||||||
if (searchField.length && searchField.val().length) {
|
if (searchField.length && searchField.val().length) {
|
||||||
|
|
|
@ -53,6 +53,19 @@
|
||||||
return hours + ':' + minutes + ':' + seconds;
|
return hours + ':' + minutes + ':' + seconds;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format the given byte-value into a human-readable string
|
||||||
|
*
|
||||||
|
* @param {number} The amount of bytes to format
|
||||||
|
* @returns {string} The formatted string
|
||||||
|
*/
|
||||||
|
formatBytes: function (bytes) {
|
||||||
|
var log2 = Math.log(bytes) / Math.LN2;
|
||||||
|
var pot = Math.floor(log2 / 10);
|
||||||
|
var unit = (['b', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB'])[pot];
|
||||||
|
return ((bytes / Math.pow(1024, pot)).toFixed(2)) + ' ' + unit;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return whether the given element is visible in the users view
|
* Return whether the given element is visible in the users view
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in New Issue