refactor plugin architecture
authorAaron Dummer <aaron@dummer.info>
Sat, 29 Jul 2017 21:34:07 +0000 (14:34 -0700)
committerAaron Dummer <aaron@dummer.info>
Sat, 29 Jul 2017 21:34:07 +0000 (14:34 -0700)
move cacti and munin code into plugins

21 files changed:
.gitignore
ChangeLog
README.md
plugins/README
plugins/cacti/plugin.php [new file with mode: 0644]
plugins/munin/plugin.php [new file with mode: 0644]
wwwroot/inc/database.php
wwwroot/inc/dictionary.php
wwwroot/inc/functions.php
wwwroot/inc/init.php
wwwroot/inc/install.php
wwwroot/inc/interface-config.php
wwwroot/inc/interface-lib.php
wwwroot/inc/interface-reports.php
wwwroot/inc/interface.php
wwwroot/inc/navigation.php
wwwroot/inc/ophandlers.php
wwwroot/inc/solutions.php
wwwroot/inc/triggers.php
wwwroot/inc/upgrade.php
wwwroot/pix/tango-go-up.png [new file with mode: 0644]

index 5824de3..f4b06b6 100644 (file)
@@ -3,7 +3,6 @@ wwwroot/inc/local.php
 /.idea
 /.project
 tags
-plugins/*
 
 # vim
 [._]*.s[a-w][a-z]
index 4c6fdcb..83ddf03 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -3,6 +3,7 @@
        update: better display objects that have no common name
        update: highlight RackCode syntax in the Permissions viewer too
        update: refine delivery of rack thumb images
+       update: new plugin architecture
        bugfix: put dictionary-based attributes formatting right
        bugfix: date attributes now can be set for racks, rows and locations
        new feature: tag colors (GH#158 by Maik Ehinger)
index b1e1d76..b1eb7fe 100644 (file)
--- a/README.md
+++ b/README.md
@@ -160,6 +160,13 @@ and initialize the application.
 
 ## Release notes
 
+### Upgrading to 0.21.0
+
+This release introduces a new plugin architecture.  If you experience issues
+after the upgrade, try disabling plugins.
+Refer to http://wiki.racktables.org/index.php/RackTablesAdminGuide#Plugins
+for more information.
+
 ### Upgrading to 0.20.11
 
 New `IPV4_TREE_SHOW_UNALLOCATED` configuration option introduced to disable
index 868b7e3..a48b4cd 100644 (file)
@@ -1,3 +1,13 @@
+*** New Plugin Architecture ***
+
+Each plugin should reside in its own directory and contain a file named
+plugin.php housing certain mandatory functions.
+
+Refer to http://wiki.racktables.org/index.php/RackTablesAdminGuide#Plugins
+for more information.
+
+*** Old Plugin Architecture ***
+
 Put your custom RackTables add-ons into this folder.
 
 Old local.php file is the sample of such add-ons and should be put here
diff --git a/plugins/cacti/plugin.php b/plugins/cacti/plugin.php
new file mode 100644 (file)
index 0000000..93e405d
--- /dev/null
@@ -0,0 +1,393 @@
+<?php
+
+function plugin_cacti_info ()
+{
+       return array
+       (
+               'name' => 'cacti',
+               'longname' => 'Cacti',
+               'version' => '1.0',
+               'home_url' => 'http://www.racktables.org/'
+       );
+}
+
+function plugin_cacti_init ()
+{
+       global $interface_requires, $opspec_list, $page, $tab, $trigger;
+       $tab['object']['cacti'] = 'Cacti Graphs';
+       registerTabHandler ('object', 'cacti', 'renderObjectCactiGraphs');
+       $trigger['object']['cacti'] = 'triggerCactiGraphs';
+       registerOpHandler ('object', 'cacti', 'add', 'tableHandler');
+       registerOpHandler ('object', 'cacti', 'del', 'tableHandler');
+
+       $page['cacti']['title'] = 'Cacti';
+       $page['cacti']['parent'] = 'config';
+       $tab['cacti']['default'] = 'View';
+       $tab['cacti']['servers'] = 'Manage servers';
+       registerTabHandler ('cacti', 'default', 'renderCactiConfig');
+       registerTabHandler ('cacti', 'servers', 'renderCactiServersEditor');
+       registerOpHandler ('cacti', 'servers', 'add', 'tableHandler');
+       registerOpHandler ('cacti', 'servers', 'del', 'tableHandler');
+       registerOpHandler ('cacti', 'servers', 'upd', 'tableHandler');
+       $interface_requires['cacti-*'] = 'interface-config.php';
+
+       registerHook ('dispatchImageRequest_hook', 'plugin_cacti_dispatchImageRequest');
+       registerHook ('resetObject_hook', 'plugin_cacti_resetObject');
+       registerHook ('resetUIConfig_hook', 'plugin_cacti_resetUIConfig');
+
+       $opspec_list['object-cacti-add'] = array
+       (
+               'table' => 'CactiGraph',
+               'action' => 'INSERT',
+               'arglist' => array
+               (
+                       array ('url_argname' => 'object_id', 'assertion' => 'uint'),
+                       array ('url_argname' => 'server_id', 'assertion' => 'uint'),
+                       array ('url_argname' => 'graph_id', 'assertion' => 'uint'),
+                       array ('url_argname' => 'caption', 'assertion' => 'string0'),
+               ),
+       );
+       $opspec_list['object-cacti-del'] = array
+       (
+               'table' => 'CactiGraph',
+               'action' => 'DELETE',
+               'arglist' => array
+               (
+                       array ('url_argname' => 'object_id', 'assertion' => 'uint'),
+                       array ('url_argname' => 'server_id', 'assertion' => 'uint'),
+                       array ('url_argname' => 'graph_id', 'assertion' => 'uint'),
+               ),
+       );
+       $opspec_list['cacti-servers-add'] = array
+       (
+               'table' => 'CactiServer',
+               'action' => 'INSERT',
+               'arglist' => array
+               (
+                       array ('url_argname' => 'base_url', 'assertion' => 'string'),
+                       array ('url_argname' => 'username', 'assertion' => 'string0'),
+                       array ('url_argname' => 'password', 'assertion' => 'string0'),
+               ),
+       );
+       $opspec_list['cacti-servers-del'] = array
+       (
+               'table' => 'CactiServer',
+               'action' => 'DELETE',
+               'arglist' => array
+               (
+                       array ('url_argname' => 'id', 'assertion' => 'uint'),
+               ),
+       );
+       $opspec_list['cacti-servers-upd'] = array
+       (
+               'table' => 'CactiServer',
+               'action' => 'UPDATE',
+               'set_arglist' => array
+               (
+                       array ('url_argname' => 'base_url', 'assertion' => 'string'),
+                       array ('url_argname' => 'username', 'assertion' => 'string0'),
+                       array ('url_argname' => 'password', 'assertion' => 'string0'),
+               ),
+               'where_arglist' => array
+               (
+                       array ('url_argname' => 'id', 'assertion' => 'uint'),
+               ),
+       );
+
+       global $plugin_cacti_fkeys;
+       $plugin_cacti_fkeys = array (
+               'CactiGraph-FK-object_id' => 'CactiGraph',
+               'CactiGraph-FK-server_id' => 'CactiGraph'
+       );
+}
+
+function plugin_cacti_install ()
+{
+       if (extension_loaded ('curl') === FALSE)
+               throw new RackTablesError ('cURL PHP module is not installed', RackTablesError::MISCONFIGURED);
+
+       global $dbxlink;
+
+       $dbxlink->query (
+"CREATE TABLE `CactiServer` (
+  `id` int(10) unsigned NOT NULL auto_increment,
+  `base_url` char(255) DEFAULT NULL,
+  `username` char(64) DEFAULT NULL,
+  `password` char(64) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB");
+
+       $dbxlink->query (
+"CREATE TABLE `CactiGraph` (
+  `object_id` int(10) unsigned NOT NULL,
+  `server_id` int(10) unsigned NOT NULL,
+  `graph_id` int(10) unsigned NOT NULL,
+  `caption`  char(255) DEFAULT NULL,
+  PRIMARY KEY (`object_id`,`server_id`,`graph_id`),
+  KEY `graph_id` (`graph_id`),
+  KEY `server_id` (`server_id`),
+  CONSTRAINT `CactiGraph-FK-server_id` FOREIGN KEY (`server_id`) REFERENCES `CactiServer` (`id`),
+  CONSTRAINT `CactiGraph-FK-object_id` FOREIGN KEY (`object_id`) REFERENCES `Object` (`id`) ON DELETE CASCADE
+) ENGINE=InnoDB");
+
+       addConfigVar ('CACTI_LISTSRC', 'false', 'string', 'yes', 'no', 'no', 'List of object with Cacti graphs');
+       addConfigVar ('CACTI_RRA_ID', '1', 'uint', 'no', 'no', 'yes', 'RRA ID for Cacti graphs displayed in RackTables');
+
+       return TRUE;
+}
+
+function plugin_cacti_uninstall ()
+{
+       deleteConfigVar ('CACTI_LISTSRC');
+       deleteConfigVar ('CACTI_RRA_ID');
+
+       global $dbxlink;
+       $dbxlink->query ("DROP TABLE `CactiGraph`");
+       $dbxlink->query ("DROP TABLE `CactiServer`");
+
+       return TRUE;
+}
+
+function plugin_cacti_upgrade ()
+{
+       return TRUE;
+}
+
+function plugin_cacti_dispatchImageRequest ()
+{
+       global $pageno, $tabno;
+
+       if ($_REQUEST['img'] == 'cactigraph')
+       {
+               $pageno = 'object';
+               $tabno = 'cacti';
+               fixContext ();
+               assertPermission ();
+               $graph_id = genericAssertion ('graph_id', 'uint');
+               if (! array_key_exists ($graph_id, getCactiGraphsForObject (getBypassValue())))
+                       throw new InvalidRequestArgException ('graph_id', $graph_id);
+               proxyCactiRequest (genericAssertion ('server_id', 'uint'), $graph_id);
+       }
+       return TRUE;
+}
+
+function plugin_cacti_resetObject ($object_id)
+{
+       usePreparedDeleteBlade ('CactiGraph', array ('object_id' => $object_id));
+}
+
+function plugin_cacti_resetUIConfig ()
+{
+       setConfigVar ('CACTI_LISTSRC', 'false');
+       setConfigVar ('CACTI_RRA_ID', '1');
+}
+
+function getCactiGraphsForObject ($object_id)
+{
+       $result = usePreparedSelectBlade
+       (
+               'SELECT server_id, graph_id, caption FROM CactiGraph WHERE object_id = ? ORDER BY server_id, graph_id',
+               array ($object_id)
+       );
+       return reindexById ($result->fetchAll (PDO::FETCH_ASSOC), 'graph_id');
+}
+
+function getCactiServers ()
+{
+       $result = usePreparedSelectBlade
+       (
+               'SELECT id, base_url, username, password, COUNT(graph_id) AS num_graphs ' .
+               'FROM CactiServer AS CS LEFT JOIN CactiGraph AS CG ON CS.id = CG.server_id GROUP BY id'
+       );
+       return reindexById ($result->fetchAll (PDO::FETCH_ASSOC));
+}
+
+function renderCactiConfig ()
+{
+       $columns = array
+       (
+               array ('th_text' => 'base URL', 'row_key' => 'base_url'),
+               array ('th_text' => 'username', 'row_key' => 'username'),
+               array ('th_text' => 'graph(s)', 'row_key' => 'num_graphs', 'td_class' => 'tdright'),
+       );
+       $servers = getCactiServers ();
+       startPortlet ('Cacti servers (' . count ($servers) . ')');
+       renderTableViewer ($columns, $servers);
+       finishPortlet ();
+}
+
+function renderCactiServersEditor ()
+{
+       function printNewItemTR ()
+       {
+               printOpFormIntro ('add');
+               echo '<tr>' .
+                       '<td>' . getImageHREF ('create', 'add a new server', TRUE) . '</td>' .
+                       '<td><input type=text size=48 name=base_url></td>' .
+                       '<td><input type=text size=24 name=username></td>' .
+                       '<td><input type=password size=24 name=password></td>' .
+                       '<td>&nbsp;</td>' .
+                       '<td>' . getImageHREF ('create', 'add a new server', TRUE) . '</td>' .
+                       '</tr></form>';
+       }
+       echo '<table cellspacing=0 cellpadding=5 align=center class=widetable>';
+       echo '<tr>' .
+               '<th>&nbsp;</th>' .
+               '<th>base URL</th>' .
+               '<th>username</th>' .
+               '<th>password</th>' .
+               '<th>graph(s)</th>' .
+               '<th>&nbsp;</th>' .
+               '</tr>';
+       if (getConfigVar ('ADDNEW_AT_TOP') == 'yes')
+               printNewItemTR ();
+       foreach (getCactiServers () as $server)
+       {
+               printOpFormIntro ('upd', array ('id' => $server['id']));
+               echo '<tr><td>';
+               if ($server['num_graphs'])
+                       printImageHREF ('nodestroy', 'cannot delete, graphs exist');
+               else
+                       echo getOpLink (array ('op' => 'del', 'id' => $server['id']), '', 'destroy', 'delete this server');
+               echo '</td>';
+               echo '<td><input type=text size=48 name=base_url value="' . htmlspecialchars ($server['base_url'], ENT_QUOTES, 'UTF-8') . '"></td>';
+               echo '<td><input type=text size=24 name=username value="' . htmlspecialchars ($server['username'], ENT_QUOTES, 'UTF-8') . '"></td>';
+               echo '<td><input type=password size=24 name=password value="' . htmlspecialchars ($server['password'], ENT_QUOTES, 'UTF-8') . '"></td>';
+               echo "<td class=tdright>${server['num_graphs']}</td>";
+               echo '<td>' . getImageHREF ('save', 'update this server', TRUE) . '</td>';
+               echo '</tr></form>';
+       }
+       if (getConfigVar ('ADDNEW_AT_TOP') != 'yes')
+               printNewItemTR ();
+       echo '</table>';
+}
+
+function renderObjectCactiGraphs ($object_id)
+{
+       function printNewItemTR ($options)
+       {
+               echo "<table cellspacing=\"0\" align=\"center\" width=\"50%\">";
+               echo "<tr><td>&nbsp;</td><th>Server</th><th>Graph ID</th><th>Caption</th><td>&nbsp;</td></tr>\n";
+               printOpFormIntro ('add');
+               echo "<tr><td>";
+               printImageHREF ('Attach', 'Link new graph', TRUE);
+               echo '</td><td>' . getSelect ($options, array ('name' => 'server_id'));
+               echo "</td><td><input type=text name=graph_id></td><td><input type=text name=caption></td><td>";
+               printImageHREF ('Attach', 'Link new graph', TRUE);
+               echo "</td></tr></form>";
+               echo "</table>";
+               echo "<br/><br/>\n";
+       }
+       if (! extension_loaded ('curl'))
+               throw new RackTablesError ('The PHP cURL extension is not loaded.', RackTablesError::MISCONFIGURED);
+
+       $servers = getCactiServers ();
+       $options = array ();
+       foreach ($servers as $server)
+               $options[$server['id']] = "${server['id']}: ${server['base_url']}";
+       startPortlet ('Cacti Graphs');
+       if (getConfigVar ('ADDNEW_AT_TOP') == 'yes' && permitted ('object', 'cacti', 'add'))
+               printNewItemTR ($options);
+       echo "<table cellspacing=\"0\" cellpadding=\"10\" align=\"center\" width=\"50%\">\n";
+       foreach (getCactiGraphsForObject ($object_id) as $graph_id => $graph)
+       {
+               $cacti_url = $servers[$graph['server_id']]['base_url'];
+               $text = "(graph ${graph_id} on server ${graph['server_id']})";
+               echo "<tr><td>";
+               echo "<a href='${cacti_url}/graph.php?action=view&local_graph_id=${graph_id}&rra_id=all' target='_blank'>";
+               echo "<img src='index.php?module=image&img=cactigraph&object_id=${object_id}&server_id=${graph['server_id']}&graph_id=${graph_id}' alt='${text}' title='${text}'></a></td><td>";
+               if(permitted ('object', 'cacti', 'del'))
+                       echo getOpLink (array ('op' => 'del', 'server_id' => $graph['server_id'], 'graph_id' => $graph_id), '', 'Cut', 'Unlink graph', 'need-confirmation');
+               echo "&nbsp; &nbsp;${graph['caption']}";
+               echo "</td></tr>\n";
+       }
+       echo "</table>\n";
+       if (getConfigVar ('ADDNEW_AT_TOP') != 'yes' && permitted ('object', 'cacti', 'add'))
+               printNewItemTR ($options);
+       finishPortlet ();
+}
+
+function proxyCactiRequest ($server_id, $graph_id)
+{
+       $ret = array ();
+       $servers = getCactiServers ();
+       if (! array_key_exists ($server_id, $servers))
+               throw new InvalidRequestArgException ('server_id', $server_id);
+       $cacti_url = $servers[$server_id]['base_url'];
+       $url = "${cacti_url}/graph_image.php?action=view&local_graph_id=${graph_id}&rra_id=" . getConfigVar ('CACTI_RRA_ID');
+       $postvars = 'action=login&login_username=' . $servers[$server_id]['username'];
+       $postvars .= '&login_password=' . $servers[$server_id]['password'];
+
+       $session = curl_init ();
+
+       // Initial options up here so a specific type can override them
+       curl_setopt ($session, CURLOPT_FOLLOWLOCATION, FALSE);
+       curl_setopt ($session, CURLOPT_TIMEOUT, 10);
+       curl_setopt ($session, CURLOPT_RETURNTRANSFER, TRUE);
+       curl_setopt ($session, CURLOPT_URL, $url);
+
+       if (isset ($_SESSION['CACTICOOKIE'][$cacti_url]))
+               curl_setopt ($session, CURLOPT_COOKIE, $_SESSION['CACTICOOKIE'][$cacti_url]);
+
+       // Request the image
+       $ret['contents'] = curl_exec ($session);
+       $ret['type'] = curl_getinfo ($session, CURLINFO_CONTENT_TYPE);
+       $ret['size'] = curl_getinfo ($session, CURLINFO_SIZE_DOWNLOAD);
+
+       // Not an image, probably the login page
+       if (preg_match ('/^text\/html.*/i', $ret['type']))
+       {
+               // Request to set the cookies
+               curl_setopt ($session, CURLOPT_HEADER, TRUE);
+               curl_setopt ($session, CURLOPT_COOKIE, "");     // clear the old cookie
+               $headers = curl_exec ($session);
+
+               // Get the cookies from the headers
+               preg_match ('/Set-Cookie: ([^;]*)/i', $headers, $cookies);
+               array_shift ($cookies); // Remove 'Set-Cookie: ...' value
+               $cookie_header = implode (";", $cookies);
+               $_SESSION['CACTICOOKIE'][$cacti_url] = $cookie_header; // store for later use by this user
+
+               // CSRF security in 0.8.8h, regexp version
+               if (preg_match ("/sid:([a-z0-9,]+)\"/", $ret['contents'], $csf_output))
+                       if (array_key_exists (1, $csf_output))
+                               $postvars .= "&__csrf_magic=$csf_output[1]";
+
+               // POST Login
+               curl_setopt ($session, CURLOPT_COOKIE, $cookie_header);
+               curl_setopt ($session, CURLOPT_HEADER, FALSE);
+               curl_setopt ($session, CURLOPT_POST, TRUE);
+               curl_setopt ($session, CURLOPT_POSTFIELDS, $postvars);
+               curl_exec ($session);
+
+               // Request the image
+               curl_setopt ($session, CURLOPT_HTTPGET, TRUE);
+               $ret['contents'] = curl_exec ($session);
+               $ret['type'] = curl_getinfo ($session, CURLINFO_CONTENT_TYPE);
+               $ret['size'] = curl_getinfo ($session, CURLINFO_SIZE_DOWNLOAD);
+       }
+
+       curl_close ($session);
+
+       if ($ret['type'] != NULL)
+               header ("Content-Type: {$ret['type']}");
+       if ($ret['size'] > 0)
+               header ("Content-Length: {$ret['size']}");
+
+       echo $ret['contents'];
+}
+
+function triggerCactiGraphs ()
+{
+       if (! count (getCactiServers ()))
+               return '';
+       if
+       (
+               count (getCactiGraphsForObject (getBypassValue ())) or
+               considerConfiguredConstraint (spotEntity ('object', getBypassValue ()), 'CACTI_LISTSRC')
+       )
+               return 'std';
+       return '';
+}
+
+?>
diff --git a/plugins/munin/plugin.php b/plugins/munin/plugin.php
new file mode 100644 (file)
index 0000000..2a281c4
--- /dev/null
@@ -0,0 +1,375 @@
+<?php
+
+function plugin_munin_info ()
+{
+       return array
+       (
+               'name' => 'munin',
+               'longname' => 'Munin',
+               'version' => '1.0',
+               'home_url' => 'http://www.racktables.org/'
+       );
+}
+
+function plugin_munin_init ()
+{
+       global $interface_requires, $opspec_list, $page, $tab, $trigger;
+       $tab['object']['munin'] = 'Munin Graphs';
+       registerTabHandler ('object', 'munin', 'renderObjectMuninGraphs');
+       $trigger['object']['munin'] = 'triggerMuninGraphs';
+       $ophandler['object']['munin']['add'] = 'tableHandler';
+       $ophandler['object']['munin']['del'] = 'tableHandler';
+
+       $page['munin']['title'] = 'Munin';
+       $page['munin']['parent'] = 'config';
+       $tab['munin']['default'] = 'View';
+       $tab['munin']['servers'] = 'Manage servers';
+       registerTabHandler ('munin', 'default', 'renderMuninConfig');
+       registerTabHandler ('munin', 'servers', 'renderMuninServersEditor');
+       registerOpHandler ('munin', 'servers', 'add', 'tableHandler');
+       registerOpHandler ('munin', 'servers', 'del', 'tableHandler');
+       registerOpHandler ('munin', 'servers', 'upd', 'tableHandler');
+       $interface_requires['munin-*'] = 'interface-config.php';
+
+       registerHook ('dispatchImageRequest_hook', 'plugin_munin_dispatchImageRequest');
+       registerHook ('resetObject_hook', 'plugin_munin_resetObject');
+       registerHook ('resetUIConfig_hook', 'plugin_munin_resetUIConfig');
+
+       $opspec_list['object-munin-add'] = array
+       (
+               'table' => 'MuninGraph',
+               'action' => 'INSERT',
+               'arglist' => array
+               (
+                       array ('url_argname' => 'object_id', 'assertion' => 'uint'),
+                       array ('url_argname' => 'server_id', 'assertion' => 'uint'),
+                       array ('url_argname' => 'graph', 'assertion' => 'string'),
+                       array ('url_argname' => 'caption', 'assertion' => 'string0'),
+               ),
+       );
+       $opspec_list['object-munin-del'] = array
+       (
+               'table' => 'MuninGraph',
+               'action' => 'DELETE',
+               'arglist' => array
+               (
+                       array ('url_argname' => 'object_id', 'assertion' => 'uint'),
+                       array ('url_argname' => 'server_id', 'assertion' => 'uint'),
+                       array ('url_argname' => 'graph', 'assertion' => 'string'),
+               ),
+       );
+       $opspec_list['munin-servers-add'] = array
+       (
+               'table' => 'MuninServer',
+               'action' => 'INSERT',
+               'arglist' => array
+               (
+                       array ('url_argname' => 'base_url', 'assertion' => 'string')
+               ),
+       );
+       $opspec_list['munin-servers-del'] = array
+       (
+               'table' => 'MuninServer',
+               'action' => 'DELETE',
+               'arglist' => array
+               (
+                       array ('url_argname' => 'id', 'assertion' => 'uint'),
+               ),
+       );
+       $opspec_list['munin-servers-upd'] = array
+       (
+               'table' => 'MuninServer',
+               'action' => 'UPDATE',
+               'set_arglist' => array
+               (
+                       array ('url_argname' => 'base_url', 'assertion' => 'string'),
+               ),
+               'where_arglist' => array
+               (
+                       array ('url_argname' => 'id', 'assertion' => 'uint'),
+               ),
+       );
+
+       global $plugin_munin_fkeys;
+       $plugin_munin_fkeys = array (
+               'MuninGraph-FK-object_id' => 'MuninGraph',
+               'MuninGraph-FK-server_id' => 'MuninGraph'
+       );
+}
+
+function plugin_munin_install ()
+{
+       if (extension_loaded ('curl') === FALSE)
+               throw new RackTablesError ('cURL PHP module is not installed', RackTablesError::MISCONFIGURED);
+
+       global $dbxlink;
+
+       $dbxlink->query(
+"CREATE TABLE `MuninServer` (
+  `id` int(10) unsigned NOT NULL auto_increment,
+  `base_url` char(255) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB");
+
+       $dbxlink->query (
+"CREATE TABLE `MuninGraph` (
+  `object_id` int(10) unsigned NOT NULL,
+  `server_id` int(10) unsigned NOT NULL,
+  `graph` char(255) NOT NULL,
+  `caption`  char(255) DEFAULT NULL,
+  PRIMARY KEY (`object_id`,`server_id`,`graph`),
+  KEY `server_id` (`server_id`),
+  KEY `graph` (`graph`),
+  CONSTRAINT `MuninGraph-FK-server_id` FOREIGN KEY (`server_id`) REFERENCES `MuninServer` (`id`),
+  CONSTRAINT `MuninGraph-FK-object_id` FOREIGN KEY (`object_id`) REFERENCES `Object` (`id`) ON DELETE CASCADE
+) ENGINE=InnoDB");
+
+       addConfigVar ('MUNIN_LISTSRC', 'false', 'string', 'yes', 'no', 'no', 'List of object with Munin graphs');
+
+       return TRUE;
+}
+
+function plugin_munin_uninstall ()
+{
+       deleteConfigVar ('MUNIN_LISTSRC');
+
+       global $dbxlink;
+       $dbxlink->query ("DROP TABLE `MuninGraph`");
+       $dbxlink->query ("DROP TABLE `MuninServer`");
+
+       return TRUE;
+}
+
+function plugin_munin_upgrade ()
+{
+       return TRUE;
+}
+
+function plugin_munin_dispatchImageRequest ()
+{
+       global $pageno, $tabno;
+
+       if ($_REQUEST['img'] == 'muningraph')
+       {
+               $pageno = 'object';
+               $tabno = 'munin';
+               fixContext ();
+               assertPermission ();
+               $graph = genericAssertion ('graph', 'string');
+               if (! array_key_exists ($graph, getMuninGraphsForObject (getBypassValue())))
+                       throw new InvalidRequestArgException ('graph', $graph);
+               proxyMuninRequest (genericAssertion ('server_id', 'uint'), $graph);
+       }
+       return TRUE;
+}
+
+function plugin_munin_resetObject ($object_id)
+{
+       usePreparedDeleteBlade ('MuninGraph', array ('object_id' => $object_id));
+}
+
+function plugin_munin_resetUIConfig ()
+{
+       setConfigVar ('MUNIN_LISTSRC', 'false');
+}
+
+function getMuninGraphsForObject ($object_id)
+{
+       $result = usePreparedSelectBlade
+       (
+               'SELECT server_id, graph, caption FROM MuninGraph WHERE object_id = ? ORDER BY server_id, graph',
+               array ($object_id)
+       );
+       return reindexById ($result->fetchAll (PDO::FETCH_ASSOC), 'graph');
+}
+
+// Split object's FQDN (or the common name if FQDN is not set) into the
+// hostname and domain name in Munin convention (using the first period as the
+// separator), and return the pair. Throw an exception on error.
+function getMuninNameAndDomain ($object_id)
+{
+       $o = spotEntity ('object', $object_id);
+       $hd = $o['name'];
+       // FQDN overrides the common name for Munin purposes.
+       $attrs = getAttrValues ($object_id);
+       if (array_key_exists (3, $attrs) && $attrs[3]['value'] != '')
+               $hd = $attrs[3]['value'];
+       if (2 != count ($ret = preg_split ('/\./', $hd, 2)))
+               throw new InvalidArgException ('$object_id', $object_id, 'the name is not in the host.do.ma.in format');
+       return $ret;
+}
+
+function getMuninServers ()
+{
+       $result = usePreparedSelectBlade
+       (
+               'SELECT id, base_url, COUNT(MG.object_id) AS num_graphs ' .
+               'FROM MuninServer AS MS LEFT JOIN MuninGraph AS MG ON MS.id = MG.server_id GROUP BY id'
+       );
+       return reindexById ($result->fetchAll (PDO::FETCH_ASSOC));
+}
+
+function renderMuninConfig ()
+{
+       $columns = array
+       (
+               array ('th_text' => 'base URL', 'row_key' => 'base_url', 'td_maxlen' => 150),
+               array ('th_text' => 'graph(s)', 'row_key' => 'num_graphs', 'td_class' => 'tdright'),
+       );
+       $servers = getMuninServers ();
+       startPortlet ('Munin servers (' . count ($servers) . ')');
+       renderTableViewer ($columns, $servers);
+       finishPortlet ();
+}
+
+function renderMuninServersEditor ()
+{
+       function printNewItemTR ()
+       {
+               printOpFormIntro ('add');
+               echo '<tr>';
+               echo '<td>' . getImageHREF ('create', 'add a new server', TRUE) . '</td>';
+               echo '<td><input type=text size=48 name=base_url></td>';
+               echo '<td>&nbsp;</td>';
+               echo '<td>' . getImageHREF ('create', 'add a new server', TRUE) . '</td>';
+               echo '</tr></form>';
+       }
+       echo '<table cellspacing=0 cellpadding=5 align=center class=widetable>';
+       echo '<tr><th>&nbsp;</th><th>base URL</th><th>graph(s)</th><th>&nbsp;</th></tr>';
+       if (getConfigVar ('ADDNEW_AT_TOP') == 'yes')
+               printNewItemTR ();
+       foreach (getMuninServers () as $server)
+       {
+               printOpFormIntro ('upd', array ('id' => $server['id']));
+               echo '<tr><td>';
+               if ($server['num_graphs'])
+                       printImageHREF ('nodestroy', 'cannot delete, graphs exist');
+               else
+                       echo getOpLink (array ('op' => 'del', 'id' => $server['id']), '', 'destroy', 'delete this server');
+               echo '</td>';
+               echo '<td><input type=text size=48 name=base_url value="' . htmlspecialchars ($server['base_url'], ENT_QUOTES, 'UTF-8') . '"></td>';
+               echo "<td class=tdright>${server['num_graphs']}</td>";
+               echo '<td>' . getImageHREF ('save', 'update this server', TRUE) . '</td>';
+               echo '</tr></form>';
+       }
+       if (getConfigVar ('ADDNEW_AT_TOP') != 'yes')
+               printNewItemTR ();
+       echo '</table>';
+}
+
+function renderObjectMuninGraphs ($object_id)
+{
+       function printNewItem ($options)
+       {
+               echo "<table cellspacing=\"0\" align=\"center\" width=\"50%\">";
+               echo "<tr><td>&nbsp;</td><th>Server</th><th>Graph</th><th>Caption</th><td>&nbsp;</td></tr>\n";
+               printOpFormIntro ('add');
+               echo "<tr><td>";
+               printImageHREF ('Attach', 'Link new graph', TRUE);
+               echo '</td><td>' . getSelect ($options, array ('name' => 'server_id'));
+               echo "</td><td><input type=text name=graph></td><td><input type=text name=caption></td><td>";
+               printImageHREF ('Attach', 'Link new graph', TRUE);
+               echo "</td></tr></form>";
+               echo "</table>";
+               echo "<br/><br/>\n";
+       }
+       if (! extension_loaded ('curl'))
+       {
+               showError ('The PHP cURL extension is not loaded.');
+               return;
+       }
+       try
+       {
+               list ($host, $domain) = getMuninNameAndDomain ($object_id);
+       }
+       catch (InvalidArgException $e)
+       {
+               showError ('This object does not have the FQDN or the common name in the host.do.ma.in format.');
+               return;
+       }
+
+       $servers = getMuninServers ();
+       $options = array ();
+       foreach ($servers as $server)
+               $options[$server['id']] = "${server['id']}: ${server['base_url']}";
+       startPortlet ('Munin Graphs');
+       if (getConfigVar ('ADDNEW_AT_TOP') == 'yes')
+               printNewItem ($options);
+       echo "<table cellspacing=\"0\" cellpadding=\"10\" align=\"center\" width=\"50%\">\n";
+
+       foreach (getMuninGraphsForObject ($object_id) as $graph_name => $graph)
+       {
+               $munin_url = $servers[$graph['server_id']]['base_url'];
+               $text = "(graph ${graph_name} on server ${graph['server_id']})";
+               echo "<tr><td>";
+               echo "<a href='${munin_url}/${domain}/${host}.${domain}/${graph_name}.html' target='_blank'>";
+               echo "<img src='index.php?module=image&img=muningraph&object_id=${object_id}&server_id=${graph['server_id']}&graph=${graph_name}' alt='${text}' title='${text}'></a></td>";
+               echo "<td>";
+               echo getOpLink (array ('op' => 'del', 'server_id' => $graph['server_id'], 'graph' => $graph_name), '', 'Cut', 'Unlink graph', 'need-confirmation');
+               echo "&nbsp; &nbsp;${graph['caption']}";
+               echo "</td></tr>\n";
+       }
+       echo "</table>\n";
+       if (getConfigVar ('ADDNEW_AT_TOP') != 'yes')
+               printNewItem ($options);
+       finishPortlet ();
+}
+
+function proxyMuninRequest ($server_id, $graph)
+{
+       try
+       {
+               list ($host, $domain) = getMuninNameAndDomain (getBypassValue ());
+       }
+       catch (InvalidArgException $e)
+       {
+               throw new RTImageError ('munin_graph');
+       }
+
+       $ret = array ();
+       $servers = getMuninServers ();
+       if (! array_key_exists ($server_id, $servers))
+               throw new InvalidRequestArgException ('server_id', $server_id);
+       $munin_url = $servers[$server_id]['base_url'];
+       $url = "${munin_url}/${domain}/${host}.${domain}/${graph}-day.png";
+
+       $session = curl_init ();
+
+       // Initial options up here so a specific type can override them
+       curl_setopt ($session, CURLOPT_FOLLOWLOCATION, FALSE);
+       curl_setopt ($session, CURLOPT_TIMEOUT, 10);
+       curl_setopt ($session, CURLOPT_RETURNTRANSFER, TRUE);
+       curl_setopt ($session, CURLOPT_URL, $url);
+
+       if (isset($_SESSION['MUNINCOOKIE'][$munin_url]))
+               curl_setopt ($session, CURLOPT_COOKIE, $_SESSION['MUNINCOOKIE'][$munin_url]);
+
+       // Request the image
+       $ret['contents'] = curl_exec ($session);
+       $ret['type'] = curl_getinfo ($session, CURLINFO_CONTENT_TYPE);
+       $ret['size'] = curl_getinfo ($session, CURLINFO_SIZE_DOWNLOAD);
+
+       curl_close ($session);
+
+       if ($ret['type'] != NULL)
+               header ("Content-Type: {$ret['type']}");
+       if ($ret['size'] > 0)
+               header ("Content-Length: {$ret['size']}");
+
+       echo $ret['contents'];
+}
+
+function triggerMuninGraphs()
+{
+       if (! count (getMuninServers ()))
+               return '';
+       if
+       (
+               count (getMuninGraphsForObject (getBypassValue ())) or
+               considerConfiguredConstraint (spotEntity ('object', getBypassValue ()), 'MUNIN_LISTSRC')
+       )
+               return 'std';
+       return '';
+}
+
+?>
index 8aca3a6..10bff2a 100644 (file)
@@ -1433,10 +1433,6 @@ function commitResetObject ($object_id)
        commitUpdateAttrValue ($object_id, 3, "");
        // log history
        recordObjectHistory ($object_id);
-       # Cacti graphs
-       usePreparedDeleteBlade ('CactiGraph', array ('object_id' => $object_id));
-       # Munin graphs
-       usePreparedDeleteBlade ('MuninGraph', array ('object_id' => $object_id));
        # Do an additional reset if needed
        callHook ('resetObject_hook', $object_id);
 }
@@ -5719,6 +5715,37 @@ function getLogRecords()
        return $result->fetchAll (PDO::FETCH_ASSOC);
 }
 
+// plugins may install their own settings
+function addConfigVar ($varname, $varvalue, $vartype, $emptyok, $is_hidden, $is_userdefined, $description)
+{
+       usePreparedInsertBlade
+       (
+               'Config',
+               array
+               (
+                       'varname' => $varname,
+                       'varvalue' => $varvalue,
+                       'vartype' => $vartype,
+                       'emptyok' => $emptyok,
+                       'is_hidden' => $is_hidden,
+                       'is_userdefined' => $is_userdefined,
+                       'description' => $description
+               )
+       );
+       global $configCache;
+       $configCache = loadConfigDefaults ();
+}
+
+// used when uninstalling a plugin
+function deleteConfigVar ($varname)
+{
+       global $configCache;
+       if (! isset ($configCache))
+               throw new RackTablesError ('configuration cache is unavailable', RackTablesError::INTERNAL);
+       unset ($configCache[$varname]);
+       usePreparedDeleteBlade ('Config', array ('varname' => $varname));
+}
+
 function setConfigVar ($varname, $varvalue)
 {
        global $configCache;
@@ -5794,26 +5821,6 @@ function getConfiguredQuickLinks()
        return $ret;
 }
 
-function getCactiGraphsForObject ($object_id)
-{
-       $result = usePreparedSelectBlade
-       (
-               'SELECT server_id, graph_id, caption FROM CactiGraph WHERE object_id = ? ORDER BY server_id, graph_id',
-               array ($object_id)
-       );
-       return reindexById ($result->fetchAll (PDO::FETCH_ASSOC), 'graph_id');
-}
-
-function getMuninGraphsForObject ($object_id)
-{
-       $result = usePreparedSelectBlade
-       (
-               'SELECT server_id, graph, caption FROM MuninGraph WHERE object_id = ? ORDER BY server_id, graph',
-               array ($object_id)
-       );
-       return reindexById ($result->fetchAll (PDO::FETCH_ASSOC), 'graph');
-}
-
 function touchVLANSwitch ($switch_id)
 {
        return usePreparedExecuteBlade
@@ -5866,26 +5873,6 @@ function scanAttrRelativeDays ($attr_id, $not_before_days, $not_after_days)
        return $result->fetchAll (PDO::FETCH_ASSOC);
 }
 
-function getCactiServers()
-{
-       $result = usePreparedSelectBlade
-       (
-               'SELECT id, base_url, username, password, COUNT(graph_id) AS num_graphs ' .
-               'FROM CactiServer AS CS LEFT JOIN CactiGraph AS CG ON CS.id = CG.server_id GROUP BY id'
-       );
-       return reindexById ($result->fetchAll (PDO::FETCH_ASSOC));
-}
-
-function getMuninServers()
-{
-       $result = usePreparedSelectBlade
-       (
-               'SELECT id, base_url, COUNT(MG.object_id) AS num_graphs ' .
-               'FROM MuninServer AS MS LEFT JOIN MuninGraph AS MG ON MS.id = MG.server_id GROUP BY id'
-       );
-       return reindexById ($result->fetchAll (PDO::FETCH_ASSOC));
-}
-
 function isTransactionActive()
 {
        global $dbxlink;
@@ -6108,3 +6095,111 @@ function releaseDBMutex ($name)
        $row = $result->fetchColumn();
        return $row === '1';
 }
+
+// valid states: disabled, enabled, not_installed
+// if no state is specified, return all
+function getPlugins ($state = NULL)
+{
+       // installed
+       $result = usePreparedSelectBlade ('SELECT * FROM Plugin ORDER BY name');
+       $in_db = array ();
+       foreach ($result as $row)
+               if (! $state or $state == $row['state'])
+                       $in_db[$row['name']] = array
+                       (
+                               'longname' => $row['longname'],
+                               'code_version' => 'N/A',
+                               'db_version' => $row['version'],
+                               'home_url' => $row['home_url'],
+                               'state' => $row['state']
+                       );
+
+       // available
+       global $racktables_plugins_dir;
+       $plugin_dirs = glob ("${racktables_plugins_dir}/*", GLOB_ONLYDIR);
+       $in_code = array ();
+       foreach ($plugin_dirs as $plugin_dir)
+       {
+               $plugin = basename ($plugin_dir);
+               if (! file_exists ("${plugin_dir}/plugin.php"))
+                       continue;
+
+               require_once "${plugin_dir}/plugin.php";
+               if (! function_exists ("plugin_${plugin}_info"))
+                       continue;
+
+               $info = call_user_func ("plugin_${plugin}_info");
+               $in_code[$info['name']] = array
+               (
+                       'longname' => $info['longname'],
+                       'code_version' => $info['version'],
+                       'db_version' => (array_key_exists ($plugin, $in_db) ? $in_db[$info['name']]['db_version'] : 'N/A'),
+                       'home_url' => $info['home_url'],
+                       'state' => (array_key_exists ($plugin, $in_db) ? $in_db[$info['name']]['state'] : 'not_installed')
+               );
+       }
+
+       // merge plugin lists and filter them, if needed
+       $ret = array_replace_recursive ($in_db, $in_code);
+       if ($state)
+               foreach ($ret as $name => $info)
+                       if ($info['state'] != $state)
+                               unset ($ret[$name]);
+       ksort ($ret);
+       return $ret;
+}
+
+function getPlugin ($name)
+{
+       $result = usePreparedSelectBlade ('SELECT * FROM Plugin WHERE name = ?', array ($name));
+       $db_info = $result->fetch (PDO::FETCH_ASSOC);
+       $in_db = array ();
+       if (is_array ($db_info))
+       {
+               $in_db['name'] = $db_info['name'];
+               $in_db['longname'] = $db_info['longname'];
+               $in_db['code_version'] = 'N/A';
+               $in_db['db_version'] = $db_info['version'];
+               $in_db['home_url'] = $db_info['home_url'];
+               $in_db['state'] = $db_info['state'];
+       }
+
+       global $racktables_plugins_dir;
+       $in_code = array ();
+       if (file_exists ("$racktables_plugins_dir/${name}/plugin.php"))
+       {
+               require_once "${racktables_plugins_dir}/${name}/plugin.php";
+               if (! function_exists ("plugin_${name}_info"))
+                       return FALSE;
+
+               $code_info = call_user_func ("plugin_${name}_info");
+               $in_code['name'] = $code_info['name'];
+               $in_code['longname'] = $code_info['longname'];
+               $in_code['code_version'] = $code_info['version'];
+               $in_code['db_version'] = (isset ($in_db['db_version']) ? $in_db['db_version'] : 'N/A');
+               $in_code['home_url'] = $code_info['home_url'];
+               $in_code['state'] = (isset ($in_db['state']) ? $in_db['state'] : 'not_installed');
+       }
+       return array_replace ($in_db, $in_code);
+}
+
+function commitInstallPlugin ($name, $longname, $version, $home_url)
+{
+       usePreparedInsertBlade
+       (
+               'Plugin',
+               array
+               (
+                       'name' => $name,
+                       'longname' => $longname,
+                       'version' => $version,
+                       'home_url' => $home_url,
+                       'state' => 'enabled'
+               )
+       );
+}
+
+function commitUninstallPlugin ($name)
+{
+       usePreparedDeleteBlade ('Plugin', array ('name' => $name));
+}
index 8a63d09..71a1314 100644 (file)
@@ -84,7 +84,6 @@ function platform_is_ok ()
        platform_function_test ('ldap_connect', 'LDAP extension', 'Not found, LDAP authentication will not work.', 'trwarning');
        platform_function_test ('pcntl_waitpid', 'PCNTL extension', '802.1Q parallel sync is unavailable.', 'trwarning');
        $nerrs += platform_function_test ('json_encode', 'JSON extension', 'JavaScript interface bits may fail.');
-       platform_generic_test (in_array  ('curl', get_loaded_extensions()), 'cURL extension', 'Not found, Cacti Graph integration is unavailable.', 'trwarning');
        $nerrs += platform_function_test ('bcmul', 'BC Math extension');
        platform_generic_test
        (
index be9f365..0954b9c 100644 (file)
@@ -1118,22 +1118,6 @@ function findAllEndpoints ($object_id, $fallback = '')
        return $regular;
 }
 
-// Split object's FQDN (or the common name if FQDN is not set) into the
-// hostname and domain name in Munin convention (using the first period as the
-// separator), and return the pair. Throw an exception on error.
-function getMuninNameAndDomain ($object_id)
-{
-       $o = spotEntity ('object', $object_id);
-       $hd = $o['name'];
-       // FQDN overrides the common name for Munin purposes.
-       $attrs = getAttrValues ($object_id);
-       if (array_key_exists (3, $attrs) && $attrs[3]['value'] != '')
-               $hd = $attrs[3]['value'];
-       if (2 != count ($ret = preg_split ('/\./', $hd, 2)))
-               throw new InvalidArgException ('object_id', $object_id, 'the name is not in the host.do.ma.in format');
-       return $ret;
-}
-
 // Some records in the dictionary may be written as plain text or as Wiki
 // link in the following syntax:
 // 1. word
@@ -6708,3 +6692,18 @@ function replaceObjectPorts ($object_id, $desiredPorts)
        $dbxlink->exec ('UNLOCK TABLES');
        showSuccess (sprintf ('Added ports: %u, changed: %u, deleted: %u', count ($to_add), count ($to_update), count ($to_delete)));
 }
+
+function formatPluginState ($state)
+{
+       switch ($state)
+       {
+       case 'disabled':
+               return 'Disabled';
+       case 'enabled':
+               return 'Enabled';
+       case 'not_installed':
+               return 'Not installed';
+       default:
+               return 'unknown';
+       }
+}
index 779bf11..603bcca 100644 (file)
@@ -144,7 +144,7 @@ $virtual_obj_types = explode (',', getConfigVar ('VIRTUAL_OBJ_LISTSRC'));
 alterConfigWithUserPreferences();
 $op = '';
 
-// load additional plugins
+// load v1 plugins
 ob_start();
 if (FALSE !== $plugin_files = glob ("${racktables_plugins_dir}/*.php"))
        foreach ($plugin_files as $plugin_file)
@@ -155,6 +155,12 @@ if ($tmp != '' && ! preg_match ("/^\n+$/D", $tmp))
        echo $tmp;
 unset ($tmp);
 
+// load v2 plugins that are enabled
+$plugins = getPlugins ('enabled');
+foreach (array_keys ($plugins) as $plugin)
+       if (function_exists ("plugin_${plugin}_init"))
+               call_user_func ("plugin_${plugin}_init");
+
 // These will be filled in by fixContext()
 $expl_tags = array();
 $impl_tags = array();
index 4a1a767..7671ee4 100644 (file)
@@ -249,6 +249,9 @@ function init_config ()
        fwrite ($conf, "\$db_username = '" . $_REQUEST['mysql_username'] . "';\n");
        fwrite ($conf, "\$db_password = '" . $_REQUEST['mysql_password'] . "';\n\n");
        fwrite ($conf, <<<ENDOFTEXT
+# Set this if you need to override the default plugins directory.
+#\$racktables_plugins_dir = '/path/to/plugins';
+
 # Setting MySQL client buffer size may be required to make downloading work for
 # larger files, but it does not work with mysqlnd.
 # \$pdo_bufsize = 50 * 1024 * 1024;
@@ -559,26 +562,6 @@ function get_pseudo_file ($name)
   CONSTRAINT `CachedPVM-FK-object_id` FOREIGN KEY (`object_id`) REFERENCES `Object` (`id`) ON DELETE CASCADE
 ) ENGINE=InnoDB";
 
-               $query[] = "CREATE TABLE `CactiGraph` (
-  `object_id` int(10) unsigned NOT NULL,
-  `server_id` int(10) unsigned NOT NULL,
-  `graph_id` int(10) unsigned NOT NULL,
-  `caption`  char(255) DEFAULT NULL,
-  PRIMARY KEY (`object_id`,`server_id`,`graph_id`),
-  KEY `graph_id` (`graph_id`),
-  KEY `server_id` (`server_id`),
-  CONSTRAINT `CactiGraph-FK-server_id` FOREIGN KEY (`server_id`) REFERENCES `CactiServer` (`id`),
-  CONSTRAINT `CactiGraph-FK-object_id` FOREIGN KEY (`object_id`) REFERENCES `Object` (`id`) ON DELETE CASCADE
-) ENGINE=InnoDB";
-
-               $query[] = "CREATE TABLE `CactiServer` (
-  `id` int(10) unsigned NOT NULL auto_increment,
-  `base_url` char(255) DEFAULT NULL,
-  `username` char(64) DEFAULT NULL,
-  `password` char(64) DEFAULT NULL,
-  PRIMARY KEY (`id`)
-) ENGINE=InnoDB";
-
                $query[] = "CREATE TABLE `Chapter` (
   `id` int(10) unsigned NOT NULL auto_increment,
   `sticky` enum('yes','no') default 'no',
@@ -828,24 +811,6 @@ function get_pseudo_file ($name)
   CONSTRAINT `MountOperation-FK-new_molecule_id` FOREIGN KEY (`new_molecule_id`) REFERENCES `Molecule` (`id`) ON DELETE CASCADE
 ) ENGINE=InnoDB";
 
-               $query[] = "CREATE TABLE `MuninGraph` (
-  `object_id` int(10) unsigned NOT NULL,
-  `server_id` int(10) unsigned NOT NULL,
-  `graph` char(255) NOT NULL,
-  `caption`  char(255) DEFAULT NULL,
-  PRIMARY KEY (`object_id`,`server_id`,`graph`),
-  KEY `server_id` (`server_id`),
-  KEY `graph` (`graph`),
-  CONSTRAINT `MuninGraph-FK-server_id` FOREIGN KEY (`server_id`) REFERENCES `MuninServer` (`id`),
-  CONSTRAINT `MuninGraph-FK-object_id` FOREIGN KEY (`object_id`) REFERENCES `Object` (`id`) ON DELETE CASCADE
-) ENGINE=InnoDB";
-
-               $query[] = "CREATE TABLE `MuninServer` (
-  `id` int(10) unsigned NOT NULL auto_increment,
-  `base_url` char(255) DEFAULT NULL,
-  PRIMARY KEY (`id`)
-) ENGINE=InnoDB";
-
                $query[] = "CREATE TABLE `ObjectLog` (
   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
   `object_id` int(10) unsigned NOT NULL,
@@ -924,6 +889,15 @@ function get_pseudo_file ($name)
   UNIQUE KEY `pctype_per_origin` (`pctype`,`origin`)
 ) ENGINE=InnoDB";
 
+               $query[] = "CREATE TABLE `Plugin` (
+  `name` char(255) NOT NULL,
+  `longname` char(255) NOT NULL,
+  `version` char(64) NOT NULL,
+  `home_url` char(255) NOT NULL,
+  `state` enum('disabled','enabled') NOT NULL default 'disabled',
+  PRIMARY KEY (`name`)
+) ENGINE=InnoDB";
+
                $query[] = "CREATE TABLE `Port` (
   `id` int(10) unsigned NOT NULL auto_increment,
   `object_id` int(10) unsigned NOT NULL default '0',
@@ -2292,9 +2266,6 @@ WHERE O.objtype_id = 1562";
 ('MGMT_PROTOS','ssh: {\$typeid_4}; telnet: {\$typeid_8}','string','yes','no','yes','Mapping of management protocol to devices'),
 ('SYNC_8021Q_LISTSRC','','string','yes','no','no','List of VLAN switches sync is enabled on'),
 ('QUICK_LINK_PAGES','depot,ipv4space,rackspace','string','yes','no','yes','List of pages to display in quick links'),
-('CACTI_LISTSRC','false','string','yes','no','no','List of object with Cacti graphs'),
-('CACTI_RRA_ID','1','uint','no','no','yes','RRA ID for Cacti graphs displayed in RackTables'),
-('MUNIN_LISTSRC','false','string','yes','no','no','List of object with Munin graphs'),
 ('VIRTUAL_OBJ_LISTSRC','1504,1505,1506,1507','string','no','no','no','List source: virtual objects'),
 ('DATETIME_ZONE','UTC','string','yes','no','yes','Timezone to use for displaying/calculating dates'),
 ('DATETIME_FORMAT','%Y-%m-%d','string','no','no','yes','PHP strftime() format to use for date output'),
index 5d91490..57aa555 100644 (file)
@@ -1097,6 +1097,77 @@ function renderMyPasswordEditor ()
        echo '</table></form>';
 }
 
+function renderPluginConfig ()
+{
+       $plugins = getPlugins ();
+       if (empty ($plugins))
+       {
+               echo '<b>No plugins exist</b>';
+               return;
+       }
+
+       echo "<br><table cellspacing=0 cellpadding=5 align=center class=cooltable>\n";
+       echo "<tr><th>Plugin</th><th>Code Version</th><th>DB Version</th><th>Home page</th><th>State</th></tr>\n";
+       global $nextorder;
+       $order = 'odd';
+       foreach ($plugins as $name => $plugin)
+       {
+               echo "<tr class=row_${order}>";
+               echo "<td class=tdleft>${plugin['longname']}</td>";
+               echo "<td class=tdleft>${plugin['code_version']}</td>";
+               echo "<td class=tdleft>${plugin['db_version']}</td>";
+               echo "<td class=tdleft>${plugin['home_url']}</td>";
+               echo '<td class=tdleft>' . formatPluginState ($plugin['state']) . '</td>';
+               echo "</tr>\n";
+               $order = $nextorder[$order];
+       }
+       echo "</table>\n";
+}
+
+function renderPluginEditor()
+{
+       $plugins = getPlugins ();
+       if (empty ($plugins))
+       {
+               echo '<b>No plugins exist</b>';
+               return;
+       }
+
+       echo "<br><div class=msg_error>Warning: Uninstalling a plugin permanently deletes all related data.</div>\n";
+       echo "<br><table cellspacing=0 cellpadding=5 align=center class=cooltable>\n";
+       echo "<tr><th>Plugin</th><th>Code Version</th><th>DB Version</th><th>Home page</th><th>State</th><th></th></tr>\n";
+       global $nextorder;
+       $order = 'odd';
+       foreach ($plugins as $name => $plugin)
+       {
+               echo "<tr class=row_${order}>";
+               echo "<td class=tdleft>${plugin['longname']}</td>";
+               echo "<td class=tdleft>${plugin['code_version']}</td>";
+               echo "<td class=tdleft>${plugin['db_version']}</td>";
+               echo "<td class=tdleft>${plugin['home_url']}</td>";
+               echo '<td class=tdleft>' . formatPluginState ($plugin['state']) . '</td>';
+               echo "<td>";
+               if ($plugin['state'] == 'disabled')
+                       echo getOpLink (array ('op' => 'enable', 'name' => $name), '', 'enable', 'Enable');
+               if ($plugin['state'] == 'enabled')
+                       echo getOpLink (array ('op' => 'disable', 'name' => $name), '', 'disable', 'Disable');
+               if ($plugin['state'] == 'not_installed')
+                       echo getOpLink (array ('op' => 'install', 'name' => $name), '', 'add', 'Install');
+               if ($plugin['state'] == 'disabled' or $plugin['state'] == 'enabled')
+                       echo getOpLink (array ('op' => 'uninstall', 'name' => $name), '', 'delete', 'Uninstall', 'need-confirmation');
+               if
+               (
+                       $plugin['code_version'] != 'N/A' and
+                       $plugin['db_version'] != 'N/A' and
+                       $plugin['code_version'] != $plugin['db_version']
+               )
+                       echo getOpLink (array ('op' => 'upgrade', 'name' => $name), '', 'upgrade', 'Upgrade');
+               echo "</td></tr>\n";
+               $order = $nextorder[$order];
+       }
+       echo "</table>\n";
+}
+
 function renderMyQuickLinks ()
 {
        global $indexlayout, $page;
@@ -1125,116 +1196,3 @@ function renderMyQuickLinks ()
        echo '</form></div>';
        finishPortlet();
 }
-
-function renderCactiConfig()
-{
-       $columns = array
-       (
-               array ('th_text' => 'base URL', 'row_key' => 'base_url'),
-               array ('th_text' => 'username', 'row_key' => 'username'),
-               array ('th_text' => 'graph(s)', 'row_key' => 'num_graphs', 'td_class' => 'tdright'),
-       );
-       $servers = getCactiServers();
-       startPortlet ('Cacti servers (' . count ($servers) . ')');
-       renderTableViewer ($columns, $servers);
-       finishPortlet();
-}
-
-function renderCactiServersEditor()
-{
-       function printNewItemTR ()
-       {
-               printOpFormIntro ('add');
-               echo '<tr>' .
-                       '<td>' . getImageHREF ('create', 'add a new server', TRUE) . '</td>' .
-                       '<td><input type=text size=48 name=base_url></td>' .
-                       '<td><input type=text size=24 name=username></td>' .
-                       '<td><input type=password size=24 name=password></td>' .
-                       '<td>&nbsp;</td>' .
-                       '<td>' . getImageHREF ('create', 'add a new server', TRUE) . '</td>' .
-                       '</tr></form>';
-       }
-       echo '<table cellspacing=0 cellpadding=5 align=center class=widetable>';
-       echo '<tr>' .
-               '<th>&nbsp;</th>' .
-               '<th>base URL</th>' .
-               '<th>username</th>' .
-               '<th>password</th>' .
-               '<th>graph(s)</th>' .
-               '<th>&nbsp;</th>' .
-               '</tr>';
-       if (getConfigVar ('ADDNEW_AT_TOP') == 'yes')
-               printNewItemTR();
-       foreach (getCactiServers() as $server)
-       {
-               printOpFormIntro ('upd', array ('id' => $server['id']));
-               echo '<tr><td>';
-               if ($server['num_graphs'])
-                       printImageHREF ('nodestroy', 'cannot delete, graphs exist');
-               else
-                       echo getOpLink (array ('op' => 'del', 'id' => $server['id']), '', 'destroy', 'delete this server');
-               echo '</td>';
-               echo '<td><input type=text size=48 name=base_url value="' . htmlspecialchars ($server['base_url'], ENT_QUOTES, 'UTF-8') . '"></td>';
-               echo '<td><input type=text size=24 name=username value="' . htmlspecialchars ($server['username'], ENT_QUOTES, 'UTF-8') . '"></td>';
-               echo '<td><input type=password size=24 name=password value="' . htmlspecialchars ($server['password'], ENT_QUOTES, 'UTF-8') . '"></td>';
-               echo "<td class=tdright>${server['num_graphs']}</td>";
-               echo '<td>' . getImageHREF ('save', 'update this server', TRUE) . '</td>';
-               echo '</tr></form>';
-       }
-       if (getConfigVar ('ADDNEW_AT_TOP') != 'yes')
-               printNewItemTR();
-       echo '</table>';
-}
-
-function renderMuninConfig()
-{
-       $columns = array
-       (
-               array ('th_text' => 'base URL', 'row_key' => 'base_url', 'td_maxlen' => 150),
-               array ('th_text' => 'graph(s)', 'row_key' => 'num_graphs', 'td_class' => 'tdright'),
-       );
-       $servers = getMuninServers();
-       startPortlet ('Munin servers (' . count ($servers) . ')');
-       renderTableViewer ($columns, $servers);
-       finishPortlet();
-}
-
-function renderMuninServersEditor()
-{
-       function printNewItemTR()
-       {
-               printOpFormIntro ('add');
-               echo '<tr>' .
-                       '<td>' . getImageHREF ('create', 'add a new server', TRUE) . '</td>' .
-                       '<td><input type=text size=48 name=base_url></td>' .
-                       '<td>&nbsp;</td>' .
-                       '<td>' . getImageHREF ('create', 'add a new server', TRUE) . '</td>' .
-                       '</tr></form>';
-       }
-       echo '<table cellspacing=0 cellpadding=5 align=center class=widetable>';
-       echo '<tr>' .
-               '<th>&nbsp;</th>' .
-               '<th>base URL</th>' .
-               '<th>graph(s)</th>' .
-               '<th>&nbsp;</th>' .
-               '</tr>';
-       if (getConfigVar ('ADDNEW_AT_TOP') == 'yes')
-               printNewItemTR();
-       foreach (getMuninServers() as $server)
-       {
-               printOpFormIntro ('upd', array ('id' => $server['id']));
-               echo '<tr><td>';
-               if ($server['num_graphs'])
-                       printImageHREF ('nodestroy', 'cannot delete, graphs exist');
-               else
-                       echo getOpLink (array ('op' => 'del', 'id' => $server['id']), '', 'destroy', 'delete this server');
-               echo '</td>';
-               echo '<td><input type=text size=48 name=base_url value="' . htmlspecialchars ($server['base_url'], ENT_QUOTES, 'UTF-8') . '"></td>';
-               echo "<td class=tdright>${server['num_graphs']}</td>";
-               echo '<td>' . getImageHREF ('save', 'update this server', TRUE) . '</td>';
-               echo '</tr></form>';
-       }
-       if (getConfigVar ('ADDNEW_AT_TOP') != 'yes')
-               printNewItemTR();
-       echo '</table>';
-}
index 4ecbc19..faf2495 100644 (file)
@@ -291,6 +291,15 @@ $image['link disabled']['height'] = 16;
 $image['16x16t']['path'] = 'pix/1x1t.gif';
 $image['16x16t']['width'] = 16;
 $image['16x16t']['height'] = 16;
+$image['disable']['path'] = 'pix/link-disabled.png';
+$image['disable']['width'] = 16;
+$image['disable']['height'] = 16;
+$image['enable']['path'] = 'pix/link-up.png';
+$image['enable']['width'] = 16;
+$image['enable']['height'] = 16;
+$image['upgrade']['path'] = 'pix/tango-go-up.png';
+$image['upgrade']['width'] = 16;
+$image['upgrade']['height'] = 16;
 
 $page_by_realm = array();
 $page_by_realm['object'] = 'depot';
index e3d65b3..92c1115 100644 (file)
@@ -714,8 +714,6 @@ function renderDataIntegrityReport ()
                'CachedPAV-FK-vlan_id' => 'CachedPAV',
                'CachedPNV-FK-compound' => 'CachedPNV',
                'CachedPVM-FK-object_id' => 'CachedPVM',
-               'CactiGraph-FK-server_id' => 'CactiGraph',
-               'CactiGraph-FK-server_id' => 'CactiGraph',
                'Dictionary-FK-chapter_id' => 'Dictionary',
                'FileLink-File_fkey' => 'FileLink',
                'IPv4Allocation-FK-object_id' => 'IPv4Allocation',
@@ -730,8 +728,6 @@ function renderDataIntegrityReport ()
                'MountOperation-FK-object_id' => 'MountOperation',
                'MountOperation-FK-old_molecule_id' => 'MountOperation',
                'MountOperation-FK-new_molecule_id' => 'MountOperation',
-               'MuninGraph-FK-server_id' => 'MuninGraph',
-               'MuninGraph-FK-server_id' => 'MuninGraph',
                'ObjectHistory-FK-object_id' => 'ObjectHistory',
                'ObjectLog-FK-object_id' => 'ObjectLog',
                'PatchCableConnectorCompat-FK-connector_id' => 'PatchCableConnectorCompat',
@@ -776,6 +772,16 @@ function renderDataIntegrityReport ()
                'VSIPs-vs_id' => 'VSIPs',
                'VS-vs_id' => 'VSPorts'
        );
+
+       $plugins = getPlugins ('enabled');
+       foreach (array_keys ($plugins) as $plugin)
+       {
+               global ${"plugin_${plugin}_fkeys"};
+               if (isset (${"plugin_${plugin}_fkeys"}))
+                       $fkeys = array_merge ($fkeys, ${"plugin_${plugin}_fkeys"});
+       }
+       ksort ($fkeys);
+
        $result = usePreparedSelectBlade
        (
                'SELECT CONSTRAINT_NAME, TABLE_NAME ' .
index ed4f4e1..134950e 100644 (file)
@@ -6245,109 +6245,6 @@ function renderIPAddressLog ($ip_bin)
        finishPortlet();
 }
 
-function renderObjectCactiGraphs ($object_id)
-{
-       function printNewItemTR ($options)
-       {
-               echo "<table cellspacing=\"0\" align=\"center\" width=\"50%\">";
-               echo "<tr><td>&nbsp;</td><th>Server</th><th>Graph ID</th><th>Caption</th><td>&nbsp;</td></tr>\n";
-               printOpFormIntro ('add');
-               echo "<tr><td>";
-               printImageHREF ('Attach', 'Link new graph', TRUE);
-               echo '</td><td>' . getSelect ($options, array ('name' => 'server_id'));
-               echo "</td><td><input type=text name=graph_id></td><td><input type=text name=caption></td><td>";
-               printImageHREF ('Attach', 'Link new graph', TRUE);
-               echo "</td></tr></form>";
-               echo "</table>";
-               echo "<br/><br/>";
-       }
-       if (!extension_loaded ('curl'))
-               throw new RackTablesError ("The PHP cURL extension is not loaded.", RackTablesError::MISCONFIGURED);
-
-       $servers = getCactiServers();
-       $options = array();
-       foreach ($servers as $server)
-               $options[$server['id']] = "${server['id']}: ${server['base_url']}";
-       startPortlet ('Cacti Graphs');
-       if (getConfigVar ('ADDNEW_AT_TOP') == 'yes' && permitted('object','cacti','add'))
-               printNewItemTR ($options);
-       echo "<table cellspacing=\"0\" cellpadding=\"10\" align=\"center\" width=\"50%\">";
-       foreach (getCactiGraphsForObject ($object_id) as $graph_id => $graph)
-       {
-               $cacti_url = $servers[$graph['server_id']]['base_url'];
-               $text = "(graph ${graph_id} on server ${graph['server_id']})";
-               echo "<tr><td>";
-               echo "<a href='${cacti_url}/graph.php?action=view&local_graph_id=${graph_id}&rra_id=all' target='_blank'>";
-               echo "<img src='index.php?module=image&img=cactigraph&object_id=${object_id}&server_id=${graph['server_id']}&graph_id=${graph_id}' alt='${text}' title='${text}'></a></td><td>";
-               if(permitted('object','cacti','del'))
-                       echo getOpLink (array ('op' => 'del', 'server_id' => $graph['server_id'], 'graph_id' => $graph_id), '', 'Cut', 'Unlink graph', 'need-confirmation');
-               echo "&nbsp; &nbsp;${graph['caption']}";
-               echo "</td></tr>";
-       }
-       echo '</table>';
-       if (getConfigVar ('ADDNEW_AT_TOP') != 'yes' && permitted('object','cacti','add'))
-               printNewItemTR ($options);
-       finishPortlet ();
-}
-
-function renderObjectMuninGraphs ($object_id)
-{
-       function printNewItem ($options)
-       {
-               echo "<table cellspacing=\"0\" align=\"center\" width=\"50%\">";
-               echo "<tr><td>&nbsp;</td><th>Server</th><th>Graph</th><th>Caption</th><td>&nbsp;</td></tr>\n";
-               printOpFormIntro ('add');
-               echo "<tr><td>";
-               printImageHREF ('Attach', 'Link new graph', TRUE);
-               echo '</td><td>' . getSelect ($options, array ('name' => 'server_id'));
-               echo "</td><td><input type=text name=graph></td><td><input type=text name=caption></td><td>";
-               printImageHREF ('Attach', 'Link new graph', TRUE);
-               echo "</td></tr></form>";
-               echo "</table>";
-               echo "<br/><br/>";
-       }
-       if (!extension_loaded ('curl'))
-       {
-               showError ('The PHP cURL extension is not loaded.');
-               return;
-       }
-       try
-       {
-               list ($host, $domain) = getMuninNameAndDomain ($object_id);
-       }
-       catch (InvalidArgException $e)
-       {
-               showError ('This object does not have the FQDN or the common name in the host.do.ma.in format.');
-               return;
-       }
-
-       $servers = getMuninServers();
-       $options = array();
-       foreach ($servers as $server)
-               $options[$server['id']] = "${server['id']}: ${server['base_url']}";
-       startPortlet ('Munin Graphs');
-       if (getConfigVar ('ADDNEW_AT_TOP') == 'yes')
-               printNewItem ($options);
-       echo "<table cellspacing=\"0\" cellpadding=\"10\" align=\"center\" width=\"50%\">";
-
-       foreach (getMuninGraphsForObject ($object_id) as $graph_name => $graph)
-       {
-               $munin_url = $servers[$graph['server_id']]['base_url'];
-               $text = "(graph ${graph_name} on server ${graph['server_id']})";
-               echo "<tr><td>";
-               echo "<a href='${munin_url}/${domain}/${host}.${domain}/${graph_name}.html' target='_blank'>";
-               echo "<img src='index.php?module=image&img=muningraph&object_id=${object_id}&server_id=${graph['server_id']}&graph=${graph_name}' alt='${text}' title='${text}'></a></td>";
-               echo "<td>";
-               echo getOpLink (array ('op' => 'del', 'server_id' => $graph['server_id'], 'graph' => $graph_name), '', 'Cut', 'Unlink graph', 'need-confirmation');
-               echo "&nbsp; &nbsp;${graph['caption']}";
-               echo "</td></tr>";
-       }
-       echo '</table>';
-       if (getConfigVar ('ADDNEW_AT_TOP') != 'yes')
-               printNewItem ($options);
-       finishPortlet ();
-}
-
 // returns an array with two items - each is HTML-formatted <TD> tag
 function formatPortReservation ($port)
 {
index 5a20c7a..0194898 100644 (file)
@@ -193,8 +193,6 @@ $tab['object']['ucs'] = 'UCS';
 $tab['object']['8021qorder'] = '802.1Q order';
 $tab['object']['8021qports'] = '802.1Q ports';
 $tab['object']['8021qsync'] = '802.1Q sync';
-$tab['object']['cacti'] = 'Cacti Graphs';
-$tab['object']['munin'] = 'Munin Graphs';
 $tabhandler['object']['default'] = 'renderObject';
 $tabhandler['object']['edit'] = 'renderEditObjectForm';
 $tabhandler['object']['log'] = 'renderObjectLogEditor';
@@ -215,8 +213,6 @@ $tabhandler['object']['editvslinks'] = 'renderTripletForm';
 $tabhandler['object']['8021qorder'] = 'render8021QOrderForm';
 $tabhandler['object']['8021qports'] = 'renderObject8021QPorts';
 $tabhandler['object']['8021qsync'] = 'renderObject8021QSync';
-$tabhandler['object']['cacti'] = 'renderObjectCactiGraphs';
-$tabhandler['object']['munin'] = 'renderObjectMuninGraphs';
 $tabhandler['object']['ucs'] = 'renderEditUCSForm';
 $trigger['object']['rackspace'] = 'trigger_rackspace';
 $trigger['object']['ports'] = 'trigger_ports';
@@ -234,8 +230,6 @@ $trigger['object']['tags'] = 'trigger_tags';
 $trigger['object']['8021qorder'] = 'trigger_object_8021qorder';
 $trigger['object']['8021qports'] = 'trigger_object_8021qports';
 $trigger['object']['8021qsync'] = 'trigger_object_8021qsync';
-$trigger['object']['cacti'] = 'triggerCactiGraphs';
-$trigger['object']['munin'] = 'triggerMuninGraphs';
 $trigger['object']['ucs'] = 'trigger_ucs';
 $ophandler['object']['edit']['linkObjects'] = 'linkObjects';
 $ophandler['object']['edit']['unlinkObjects'] = 'tableHandler';
@@ -284,10 +278,6 @@ $ophandler['object']['8021qsync']['exec8021QPull'] = 'process8021QSyncRequest';
 $ophandler['object']['8021qsync']['exec8021QPush'] = 'process8021QSyncRequest';
 $ophandler['object']['8021qsync']['resolve8021QConflicts'] = 'resolve8021QConflicts';
 $ophandler['object']['8021qsync']['updPortList'] = 'update8021QPortList';
-$ophandler['object']['cacti']['add'] = 'tableHandler';
-$ophandler['object']['cacti']['del'] = 'tableHandler';
-$ophandler['object']['munin']['add'] = 'tableHandler';
-$ophandler['object']['munin']['del'] = 'tableHandler';
 $ophandler['object']['ucs']['autoPopulateUCS'] = 'autoPopulateUCS';
 $ophandler['object']['ucs']['cleanupUCS'] = 'cleanupUCS';
 $delayauth['object-8021qports-save8021QConfig'] = TRUE;
@@ -682,28 +672,6 @@ $ophandler['myaccount']['interface']['reset'] = 'resetMyPreference';
 $ophandler['myaccount']['qlinks']['save'] = 'saveQuickLinks';
 $interface_requires['myaccount-*'] = 'interface-config.php';
 
-$page['cacti']['title'] = 'Cacti';
-$page['cacti']['parent'] = 'config';
-$tab['cacti']['default'] = 'View';
-$tab['cacti']['servers'] = 'Manage servers';
-$tabhandler['cacti']['default'] = 'renderCactiConfig';
-$tabhandler['cacti']['servers'] = 'renderCactiServersEditor';
-$ophandler['cacti']['servers']['add'] = 'tableHandler';
-$ophandler['cacti']['servers']['del'] = 'tableHandler';
-$ophandler['cacti']['servers']['upd'] = 'tableHandler';
-$interface_requires['cacti-*'] = 'interface-config.php';
-
-$page['munin']['title'] = 'Munin';
-$page['munin']['parent'] = 'config';
-$tab['munin']['default'] = 'View';
-$tab['munin']['servers'] = 'Manage servers';
-$tabhandler['munin']['default'] = 'renderMuninConfig';
-$tabhandler['munin']['servers'] = 'renderMuninServersEditor';
-$ophandler['munin']['servers']['add'] = 'tableHandler';
-$ophandler['munin']['servers']['del'] = 'tableHandler';
-$ophandler['munin']['servers']['upd'] = 'tableHandler';
-$interface_requires['munin-*'] = 'interface-config.php';
-
 $page['cableconf']['title'] = 'Patch cables';
 $page['cableconf']['parent'] = 'config';
 $tab['cableconf']['default'] = 'View';
@@ -898,6 +866,19 @@ $ophandler['cables']['amount']['inc'] = 'replenishPatchCable';
 $ophandler['cables']['amount']['set'] = 'setPatchCableAmount';
 $interface_requires['cables-*'] = 'interface-cables.php';
 
+$page['plugins']['title'] = 'Plugins';
+$page['plugins']['parent'] = 'config';
+$tab['plugins']['default'] = 'View';
+$tab['plugins']['edit'] = 'Edit';
+$tabhandler['plugins']['default'] = 'renderPluginConfig';
+$tabhandler['plugins']['edit'] = 'renderPluginEditor';
+$ophandler['plugins']['edit']['install'] = 'installPlugin';
+$ophandler['plugins']['edit']['uninstall'] = 'uninstallPlugin';
+$ophandler['plugins']['edit']['enable'] = 'tableHandler';
+$ophandler['plugins']['edit']['disable'] = 'tableHandler';
+$ophandler['plugins']['edit']['upgrade'] = 'upgradePlugin';
+$interface_requires['plugins-*'] = 'interface-config.php';
+
 $ajaxhandler['get-parent-node-options'] = 'getParentNodeOptionsAJAX';
 $ajaxhandler['get-location-select'] = 'getLocationSelectAJAX';
 $ajaxhandler['verifyCode'] = 'verifyCodeAJAX';
index 17208bc..b01b98e 100644 (file)
@@ -167,52 +167,6 @@ $opspec_list['object-editrspvs-updLB'] = array
                array ('url_argname' => 'vs_id', 'assertion' => 'uint'),
        ),
 );
-$opspec_list['object-cacti-add'] = array
-(
-       'table' => 'CactiGraph',
-       'action' => 'INSERT',
-       'arglist' => array
-       (
-               array ('url_argname' => 'object_id', 'assertion' => 'uint'),
-               array ('url_argname' => 'server_id', 'assertion' => 'uint'),
-               array ('url_argname' => 'graph_id', 'assertion' => 'uint'),
-               array ('url_argname' => 'caption', 'assertion' => 'string0'),
-       ),
-);
-$opspec_list['object-cacti-del'] = array
-(
-       'table' => 'CactiGraph',
-       'action' => 'DELETE',
-       'arglist' => array
-       (
-               array ('url_argname' => 'object_id', 'assertion' => 'uint'),
-               array ('url_argname' => 'server_id', 'assertion' => 'uint'),
-               array ('url_argname' => 'graph_id', 'assertion' => 'uint'),
-       ),
-);
-$opspec_list['object-munin-add'] = array
-(
-       'table' => 'MuninGraph',
-       'action' => 'INSERT',
-       'arglist' => array
-       (
-               array ('url_argname' => 'object_id', 'assertion' => 'uint'),
-               array ('url_argname' => 'server_id', 'assertion' => 'uint'),
-               array ('url_argname' => 'graph', 'assertion' => 'string'),
-               array ('url_argname' => 'caption', 'assertion' => 'string0'),
-       ),
-);
-$opspec_list['object-munin-del'] = array
-(
-       'table' => 'MuninGraph',
-       'action' => 'DELETE',
-       'arglist' => array
-       (
-               array ('url_argname' => 'object_id', 'assertion' => 'uint'),
-               array ('url_argname' => 'server_id', 'assertion' => 'uint'),
-               array ('url_argname' => 'graph', 'assertion' => 'string'),
-       ),
-);
 $opspec_list['ipv4rspool-editrslist-delRS'] = array
 (
        'table' => 'IPv4RS',
@@ -511,72 +465,6 @@ $opspec_list['dict-chapters-del'] = array
                array ('fix_argname' => 'sticky', 'fix_argvalue' => 'no'), # protect system chapters
        ),
 );
-$opspec_list['cacti-servers-add'] = array
-(
-       'table' => 'CactiServer',
-       'action' => 'INSERT',
-       'arglist' => array
-       (
-               array ('url_argname' => 'base_url', 'assertion' => 'string'),
-               array ('url_argname' => 'username', 'assertion' => 'string0'),
-               array ('url_argname' => 'password', 'assertion' => 'string0'),
-       ),
-);
-$opspec_list['cacti-servers-del'] = array
-(
-       'table' => 'CactiServer',
-       'action' => 'DELETE',
-       'arglist' => array
-       (
-               array ('url_argname' => 'id', 'assertion' => 'uint'),
-       ),
-);
-$opspec_list['cacti-servers-upd'] = array
-(
-       'table' => 'CactiServer',
-       'action' => 'UPDATE',
-       'set_arglist' => array
-       (
-               array ('url_argname' => 'base_url', 'assertion' => 'string'),
-               array ('url_argname' => 'username', 'assertion' => 'string0'),
-               array ('url_argname' => 'password', 'assertion' => 'string0'),
-       ),
-       'where_arglist' => array
-       (
-               array ('url_argname' => 'id', 'assertion' => 'uint'),
-       ),
-);
-$opspec_list['munin-servers-add'] = array
-(
-       'table' => 'MuninServer',
-       'action' => 'INSERT',
-       'arglist' => array
-       (
-               array ('url_argname' => 'base_url', 'assertion' => 'string')
-       ),
-);
-$opspec_list['munin-servers-del'] = array
-(
-       'table' => 'MuninServer',
-       'action' => 'DELETE',
-       'arglist' => array
-       (
-               array ('url_argname' => 'id', 'assertion' => 'uint'),
-       ),
-);
-$opspec_list['munin-servers-upd'] = array
-(
-       'table' => 'MuninServer',
-       'action' => 'UPDATE',
-       'set_arglist' => array
-       (
-               array ('url_argname' => 'base_url', 'assertion' => 'string'),
-       ),
-       'where_arglist' => array
-       (
-               array ('url_argname' => 'id', 'assertion' => 'uint'),
-       ),
-);
 $opspec_list['cables-heaps-add'] = array
 (
        'table' => 'PatchCableHeap',
@@ -725,6 +613,32 @@ $opspec_list['cableconf-oifcompat-del'] = array
                array ('url_argname' => 'oif_id', 'assertion' => 'uint'),
        ),
 );
+$opspec_list['plugins-edit-disable'] = array
+(
+       'table' => 'Plugin',
+       'action' => 'UPDATE',
+       'set_arglist' => array
+       (
+               array ('fix_argname' => 'state', 'fix_argvalue' => 'disabled'),
+       ),
+       'where_arglist' => array
+       (
+               array ('url_argname' => 'name', 'assertion' => 'string'),
+       ),
+);
+$opspec_list['plugins-edit-enable'] = array
+(
+       'table' => 'Plugin',
+       'action' => 'UPDATE',
+       'set_arglist' => array
+       (
+               array ('fix_argname' => 'state', 'fix_argvalue' => 'enabled'),
+       ),
+       'where_arglist' => array
+       (
+               array ('url_argname' => 'name', 'assertion' => 'string'),
+       ),
+);
 
 function setFuncMessages ($funcname, $messages)
 {
@@ -1673,9 +1587,6 @@ function resetUIConfig()
                'MGMT_PROTOS' => 'ssh: {$typeid_4}; telnet: {$typeid_8}',
                'SYNC_8021Q_LISTSRC' => '',
                'QUICK_LINK_PAGES' => 'depot,ipv4space,rackspace',
-               'CACTI_LISTSRC' => 'false',
-               'CACTI_RRA_ID' => '1',
-               'MUNIN_LISTSRC' => 'false',
                'VIRTUAL_OBJ_LISTSRC' => '1504,1505,1506,1507',
                'DATETIME_ZONE' => 'UTC',
                'DATETIME_FORMAT' => '%Y-%m-%d',
@@ -1689,6 +1600,7 @@ function resetUIConfig()
        );
        foreach ($defaults as $name => $value)
                setConfigVar ($name, $value);
+       callHook ('resetUIConfig_hook');
        showFuncMessage (__FUNCTION__, 'OK');
 }
 
@@ -3867,3 +3779,72 @@ function destroyTag()
 
        tableHandler();
 }
+
+function installPlugin ()
+{
+       global $sic;
+       assertStringArg ('name');
+
+       try
+       {
+               if (! is_callable ("plugin_${sic['name']}_install"))
+                       throw new RackTablesError ("The ${sic['name']} plugin is missing or cannot be installed", RackTablesError::MISCONFIGURED);
+               $plugin = getPlugin ($sic['name']);
+               call_user_func ("plugin_${sic['name']}_install");
+               commitInstallPlugin ($plugin['name'], $plugin['longname'], $plugin['code_version'], $plugin['home_url']);
+       }
+       catch (Exception $e)
+       {
+               showError ('Install failed: ' . $e->getMessage());
+               return;
+       }
+       showSuccess ('Installed plugin: ' . $sic['name']);
+}
+
+function uninstallPlugin ()
+{
+       global $sic;
+       assertStringArg ('name');
+
+       try
+       {
+               if (! is_callable ("plugin_${sic['name']}_uninstall"))
+                       throw new RackTablesError ("The ${sic['name']} plugin is missing or cannot be uninstalled", RackTablesError::MISCONFIGURED);
+               call_user_func ("plugin_${sic['name']}_uninstall");
+               commitUninstallPlugin ($sic['name']);
+       }
+       catch (Exception $e)
+       {
+               showError ('Uninstall failed: ' . $e->getMessage());
+               return;
+       }
+       showSuccess ('Uninstalled plugin: ' . $sic['name']);
+}
+
+function upgradePlugin ()
+{
+       global $sic;
+       assertStringArg ('name');
+
+       try
+       {
+               if (! is_callable ("plugin_${sic['name']}_upgrade"))
+                       throw new RackTablesError ("The ${sic['name']} plugin is missing or cannot be upgraded", RackTablesError::MISCONFIGURED);
+               $plugin = getPlugin ($sic['name']);
+               call_user_func ("plugin_${sic['name']}_upgrade");
+               // get details from the plugin code itself
+               $code_plugin = call_user_func ("plugin_${sic['name']}_info");
+               usePreparedUpdateBlade
+               (
+                       'Plugin',
+                       array ('longname' => $code_plugin['longname'], 'version' => $code_plugin['version'], 'home_url' => $code_plugin['home_url']),
+                       array ('name' => $sic['name'])
+               );
+       }
+       catch (Exception $e)
+       {
+               showError ('Upgrade failed: ' . $e->getMessage());
+               return;
+       }
+       showSuccess ('Upgraded plugin: ' . $sic['name']);
+}
index c7b7996..67c18ca 100644 (file)
@@ -49,7 +49,7 @@ function dispatchImageRequest()
                        throw castRackImageException ($e);
                }
                dispatchMiniRackThumbRequest (getBypassValue());
-               break;
+               return;
        case 'midirack': // rack security context
                $pageno = 'rack';
                $tabno = 'default';
@@ -68,36 +68,17 @@ function dispatchImageRequest()
                // Scaling or highlighting implies no caching and thus no extra wrapper code around.
                header ('Content-Type: image/png');
                printRackThumbImage (getBypassValue(), $scale, $object_id);
-               break;
+               return;
        case 'preview': // file security context
                $pageno = 'file';
                $tabno = 'download';
                fixContext();
                assertPermission();
                renderImagePreview (getBypassValue());
-               break;
-       case 'cactigraph':
-               $pageno = 'object';
-               $tabno = 'cacti';
-               fixContext();
-               assertPermission();
-               $graph_id = genericAssertion ('graph_id', 'uint');
-               if (! array_key_exists ($graph_id, getCactiGraphsForObject (getBypassValue())))
-                       throw new InvalidRequestArgException ('graph_id', $graph_id);
-               proxyCactiRequest (genericAssertion ('server_id', 'uint'), $graph_id);
-               break;
-       case 'muningraph':
-               $pageno = 'object';
-               $tabno = 'munin';
-               fixContext();
-               assertPermission();
-               $graph = genericAssertion ('graph', 'string');
-               if (! array_key_exists ($graph, getMuninGraphsForObject (getBypassValue())))
-                       throw new InvalidRequestArgException ('graph', $graph);
-               proxyMuninRequest (genericAssertion ('server_id', 'uint'), $graph);
-               break;
+               return;
        default:
-               throw new InvalidRequestArgException ('img', $img);
+               if (! callHook ('dispatchImageRequest_hook'))
+                       throw new InvalidRequestArgException ('img', $img);
        }
 }
 
@@ -523,122 +504,6 @@ function proxyStaticURI ($URI)
        fclose ($fh);
 }
 
-function proxyCactiRequest ($server_id, $graph_id)
-{
-       $ret = array();
-       $servers = getCactiServers();
-       if (! array_key_exists ($server_id, $servers))
-               throw new InvalidRequestArgException ('server_id', $server_id, 'there is no such server');
-       $cacti_url = $servers[$server_id]['base_url'];
-       $url = "${cacti_url}/graph_image.php?action=view&local_graph_id=${graph_id}&rra_id=" . getConfigVar ('CACTI_RRA_ID');
-       $postvars = 'action=login&login_username=' . $servers[$server_id]['username'];
-       $postvars .= '&login_password=' . $servers[$server_id]['password'];
-
-       $session = curl_init();
-//     curl_setopt ($session, CURLOPT_VERBOSE, TRUE);
-
-       // Initial options up here so a specific type can override them
-       curl_setopt ($session, CURLOPT_FOLLOWLOCATION, FALSE);
-       curl_setopt ($session, CURLOPT_TIMEOUT, 10);
-       curl_setopt ($session, CURLOPT_RETURNTRANSFER, TRUE);
-       curl_setopt ($session, CURLOPT_URL, $url);
-
-       if (isset($_SESSION['CACTICOOKIE'][$cacti_url]))
-               curl_setopt ($session, CURLOPT_COOKIE, $_SESSION['CACTICOOKIE'][$cacti_url]);
-
-       // Request the image
-       $ret['contents'] = curl_exec ($session);
-       $ret['type'] = curl_getinfo ($session, CURLINFO_CONTENT_TYPE);
-       $ret['size'] = curl_getinfo ($session, CURLINFO_SIZE_DOWNLOAD);
-
-       // Not an image, probably the login page
-       if (preg_match ('/^text\/html.*/i', $ret['type']))
-       {
-               // Request to set the cookies
-               curl_setopt ($session, CURLOPT_HEADER, TRUE);
-               curl_setopt ($session, CURLOPT_COOKIE, "");     // clear the old cookie
-               $headers = curl_exec ($session);
-
-               // Get the cookies from the headers
-               preg_match('/Set-Cookie: ([^;]*)/i', $headers, $cookies);
-               array_shift($cookies);  // Remove 'Set-Cookie: ...' value
-               $cookie_header = implode(";", $cookies);
-               $_SESSION['CACTICOOKIE'][$cacti_url] = $cookie_header; // store for later use by this user
-
-               // CSRF security in 0.8.8h, regexp version
-               if (preg_match("/sid:([a-z0-9,]+)\"/", $ret['contents'], $csf_output))
-               {
-                       if (array_key_exists(1, $csf_output))
-                               $postvars .="&__csrf_magic=$csf_output[1]";
-               }
-               // POST Login
-               curl_setopt ($session, CURLOPT_COOKIE, $cookie_header);
-               curl_setopt ($session, CURLOPT_HEADER, FALSE);
-               curl_setopt ($session, CURLOPT_POST, TRUE);
-               curl_setopt ($session, CURLOPT_POSTFIELDS, $postvars);
-               curl_exec ($session);
-
-               // Request the image
-               curl_setopt ($session, CURLOPT_HTTPGET, TRUE);
-               $ret['contents'] = curl_exec ($session);
-               $ret['type'] = curl_getinfo ($session, CURLINFO_CONTENT_TYPE);
-               $ret['size'] = curl_getinfo ($session, CURLINFO_SIZE_DOWNLOAD);
-       }
-
-       curl_close ($session);
-
-       if ($ret['type'] != NULL)
-               header("Content-Type: {$ret['type']}");
-       if ($ret['size'] > 0)
-               header("Content-Length: {$ret['size']}");
-
-       echo $ret['contents'];
-}
-
-function proxyMuninRequest ($server_id, $graph)
-{
-       try
-       {
-               list ($host, $domain) = getMuninNameAndDomain (getBypassValue());
-       }
-       catch (InvalidArgException $e)
-       {
-               throw new RTImageError ('munin_graph');
-       }
-
-       $ret = array();
-       $servers = getMuninServers();
-       if (! array_key_exists ($server_id, $servers))
-               throw new InvalidRequestArgException ('server_id', $server_id, 'there is no such server');
-       $munin_url = $servers[$server_id]['base_url'];
-       $url = "${munin_url}/${domain}/${host}.${domain}/${graph}-day.png";
-
-       $session = curl_init();
-
-       // Initial options up here so a specific type can override them
-       curl_setopt ($session, CURLOPT_FOLLOWLOCATION, FALSE);
-       curl_setopt ($session, CURLOPT_TIMEOUT, 10);
-       curl_setopt ($session, CURLOPT_RETURNTRANSFER, TRUE);
-       curl_setopt ($session, CURLOPT_URL, $url);
-
-       if (isset($_SESSION['MUNINCOOKIE'][$munin_url]))
-               curl_setopt ($session, CURLOPT_COOKIE, $_SESSION['MUNINCOOKIE'][$munin_url]);
-
-       // Request the image
-       $ret['contents'] = curl_exec ($session);
-       $ret['type'] = curl_getinfo ($session, CURLINFO_CONTENT_TYPE);
-       $ret['size'] = curl_getinfo ($session, CURLINFO_SIZE_DOWNLOAD);
-
-       curl_close ($session);
-
-       if ($ret['type'] != NULL)
-               header ("Content-Type: {$ret['type']}");
-       if ($ret['size'] > 0)
-               header ("Content-Length: {$ret['size']}");
-
-       echo $ret['contents'];
-}
-
 function printSVGMessageBar ($text = 'lost message', $textattrs = array(), $rectattrs = array())
 {
        $mytextattrs = array
index 649d692..c3bfd04 100644 (file)
@@ -286,34 +286,6 @@ function triggerIPAddressLog ()
        return '';
 }
 
-function triggerCactiGraphs ()
-{
-       if (! count (getCactiServers()))
-               return '';
-       if
-       (
-               count (getCactiGraphsForObject (getBypassValue())) ||
-               considerConfiguredConstraint (spotEntity ('object', getBypassValue()), 'CACTI_LISTSRC')
-       )
-               return 'std';
-       else
-               return '';
-}
-
-function triggerMuninGraphs()
-{
-       if (! count (getMuninServers()))
-               return '';
-       if
-       (
-               count (getMuninGraphsForObject (getBypassValue())) ||
-               considerConfiguredConstraint (spotEntity ('object', getBypassValue()), 'MUNIN_LISTSRC')
-       )
-               return 'std';
-       else
-               return '';
-}
-
 function trigger_ucs()
 {
        return checkTypeAndAttribute
index 6cb3ceb..6a53c87 100644 (file)
@@ -163,6 +163,12 @@ New IPV4_TREE_SHOW_UNALLOCATED configuration option introduced to disable
 dsplaying unallocated networks in IPv4 space tree. Setting it also disables
 the "knight" feature.
 ENDOFTEXT
+,
+       '0.21.0' => <<<ENDOFTEXT
+This release introduces a new plugin architecture.  If you experience issues
+after the upgrade, try disabling plugins.
+Refer to <a href="http://wiki.racktables.org/index.php/RackTablesAdminGuide#Plugins">the wiki</a> for more information.
+ENDOFTEXT
 ,
 );
 
@@ -1229,6 +1235,40 @@ ENDOFTRIGGER;
                        $query[] = "UPDATE Port SET label = NULL WHERE label = ''";
                        $query[] = "DELETE FROM RackThumbnail";
                        $query[] = "ALTER TABLE TagTree ADD COLUMN color mediumint unsigned DEFAULT NULL AFTER tag";
+
+                       $query[] = "
+CREATE TABLE `Plugin` (
+  `name` char(255) NOT NULL,
+  `longname` char(255) NOT NULL,
+  `version` char(64) NOT NULL,
+  `home_url` char(255) NOT NULL,
+  `state` enum('disabled','enabled') NOT NULL default 'disabled',
+  PRIMARY KEY (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci";
+
+                       // if the Cacti or Munin plugins are being used, mark them as enabled, otherwise uninstall them
+                       // from now on, upgrades will be handled by the plugins themselves
+                       $result = $dbxlink->query ('SELECT COUNT(*) FROM CactiServer');
+                       if ($result->fetchColumn() > 0)
+                               $query[] = "INSERT INTO Plugin VALUES ('cacti','Cacti','1.0','http://www.racktables.org/','enabled')";
+                       else
+                       {
+                               $query[] = "DELETE FROM Config WHERE varname IN ('CACTI_LISTSRC','CACTI_RRA_ID')";
+                               $query[] = "DROP TABLE `CactiGraph`";
+                               $query[] = "DROP TABLE `CactiServer`";
+                       }
+                       unset ($result);
+                       $result = $dbxlink->query ('SELECT COUNT(*) FROM MuninServer');
+                       if ($result->fetchColumn() > 0)
+                               $query[] = "INSERT INTO Plugin VALUES ('munin','Munin','1.0','http://www.racktables.org/','enabled')";
+                       else
+                       {
+                               $query[] = "DELETE FROM Config WHERE varname = 'MUNIN_LISTSRC'";
+                               $query[] = "DROP TABLE `MuninGraph`";
+                               $query[] = "DROP TABLE `MuninServer`";
+                       }
+                       unset ($result);
+
                        $query[] = "UPDATE Config SET varvalue = '0.21.0' WHERE varname = 'DB_VERSION'";
                        break;
                case 'dictionary':
diff --git a/wwwroot/pix/tango-go-up.png b/wwwroot/pix/tango-go-up.png
new file mode 100644 (file)
index 0000000..8660003
Binary files /dev/null and b/wwwroot/pix/tango-go-up.png differ