RT#39115: View SNMP info on svc_broadband service
authorJonathan Prykop <jonathan@freeside.biz>
Thu, 10 Dec 2015 05:39:52 +0000 (23:39 -0600)
committerJonathan Prykop <jonathan@freeside.biz>
Wed, 16 Dec 2015 05:39:43 +0000 (23:39 -0600)
FS/FS/part_export/broadband_snmp_get.pm [new file with mode: 0644]
httemplate/edit/elements/part_export/broadband_snmp.html
httemplate/edit/elements/part_export/broadband_snmp_get.html [new file with mode: 0644]
httemplate/elements/broadband_snmp_get-dialog.html [new file with mode: 0644]
httemplate/elements/select-mib-popup.html [deleted file]
httemplate/misc/select-mib-popup.html [new file with mode: 0644]
httemplate/misc/xmlhttp-broadband_snmp_get.cgi [new file with mode: 0644]
httemplate/view/svc_broadband.cgi

diff --git a/FS/FS/part_export/broadband_snmp_get.pm b/FS/FS/part_export/broadband_snmp_get.pm
new file mode 100644 (file)
index 0000000..faa51ed
--- /dev/null
@@ -0,0 +1,113 @@
+package FS::part_export::broadband_snmp_get;
+
+use strict;
+use vars qw(%info $DEBUG);
+use base 'FS::part_export';
+use SNMP;
+use Tie::IxHash;
+
+tie my %snmp_version, 'Tie::IxHash',
+  v1  => '1',
+  v2c => '2c'
+  # v3 unimplemented
+;
+
+tie my %options, 'Tie::IxHash',
+  'snmp_version' => {
+    label=>'SNMP version', 
+    type => 'select',
+    options => [ keys %snmp_version ],
+   },
+  'snmp_community' => { 'label'=>'Community', 'default'=>'public' },
+  'snmp_timeout' => { label=>'Timeout (seconds)', 'default'=>1 },
+  'snmp_oid' => { label=>'Object ID', multiple=>1 },
+;
+
+%info = (
+  'svc'     => 'svc_broadband',
+  'desc'    => 'Enable interface display of realtime SNMP get requests to service IP address',
+  'config_element' => '/edit/elements/part_export/broadband_snmp_get.html',
+  'options' => \%options,
+  'no_machine' => 1,
+  'notes'   => <<'END',
+Use this export to configure the community and object ids for displaying realtime 
+SNMP data from the service IP address when viewing a provisioned service.  Timeout is
+per object, and should be small enough for realtime use.  This export takes no action 
+during provisioning itself;  it is expected that snmp will be separately
+configured on the service machine.
+END
+);
+
+sub export_insert { ''; }
+sub export_replace { ''; }
+sub export_delete { ''; }
+sub export_suspend { ''; }
+sub export_unsuspend { ''; }
+
+=pod
+
+=head1 NAME
+
+FS::part_export::broadband_snmp_get
+
+=head1 SYNOPSIS
+
+Configuration for realtime snmp requests to svc_broadband IP address
+
+=head1 METHODS
+
+=cut
+
+=over 4
+
+=item snmp_results SVC
+
+Request statistics from SVC ip address.  Returns an array of hashes with keys 
+
+objectID
+
+label
+
+value
+
+error - error when attempting to load this object
+
+=cut
+
+sub snmp_results {
+  my ($self, $svc) = @_;
+  my $host = $svc->ip_addr;
+  my $comm = $self->option('snmp_community');
+  my $vers = $self->option('snmp_version');
+  my $time = ($self->option('snmp_timeout') || 1) * 1000;
+  my @oids = split("\n", $self->option('snmp_oid'));
+  my %connect = (
+    'DestHost'  => $host,
+    'Community' => $comm,
+    'Version'   => $vers,
+    'Timeout'   => $time,
+  );
+  my $snmp = new SNMP::Session(%connect);
+  return { 'error' => 'Error creating SNMP session' } unless $snmp;
+  return { 'error' => $snmp->{'ErrorStr'} } if $snmp->{'ErrorStr'};
+  my @out;
+  foreach my $oid (@oids) {
+    $oid = $SNMP::MIB{$oid}->{'objectID'} if $SNMP::MIB{$oid};
+    my $value = $snmp->get($oid.'.0');
+    if ($snmp->{'ErrorStr'}) {
+      push @out, { 'error' => $snmp->{'ErrorStr'} };
+      next;
+    }
+    my %result = map { $_ => $SNMP::MIB{$oid}{$_} } qw( objectID label value );
+    $result{'value'} = $value;
+    push @out, \%result;
+  }
+  return @out;      
+}
+
+=back
+
+=cut
+
+1;
+
index ebb765d..b4d57ed 100644 (file)
@@ -11,7 +11,7 @@
 <& /elements/tr-input-text.html,
   label   => 'Community',
   field   => 'community',
-  curr_value  => $part_export->option('community'),
+  curr_value  => $part_export->option('community') || $opt{'export_info'}->{'options'}->{'community'}->{'default'},
 &>
 <& /elements/tr-checkbox.html,
   label   => 'Send IP address changes to new address',
@@ -30,7 +30,7 @@ function open_select_mib(obj) {
   nd(1); // if there's already one open, close it
   var rownum = obj.rownum;
   var curr_oid = obj.form.elements['oid' + rownum].value || '';
-  var url = '<%$fsurl%>/elements/select-mib-popup.html?' +
+  var url = '<%$fsurl%>misc/select-mib-popup.html?' +
             'callback=receive_mib;' +
             'arg=' + rownum +
             ';curr_value=' + curr_oid;
@@ -45,8 +45,10 @@ function open_select_mib(obj) {
 function receive_mib(obj, rownum) {
   //console.log(JSON.stringify(obj));
   // we don't really need the numeric OID or any of the other properties
-  document.getElementById('oid'+rownum).value = obj.fullname;
+  var oidfield =   document.getElementById('oid'+rownum);
+  oidfield.value = obj.fullname;
   document.getElementById('datatype'+rownum).value = obj.type;
+  oidfield.onchange(); //should be same as datatype, only need to run one
 }
 </script>
 
diff --git a/httemplate/edit/elements/part_export/broadband_snmp_get.html b/httemplate/edit/elements/part_export/broadband_snmp_get.html
new file mode 100644 (file)
index 0000000..8b8717c
--- /dev/null
@@ -0,0 +1,77 @@
+<%doc>
+Quite a bit of false laziness with edit/elements/part_export/broadband_snmp.html
+</%doc>
+<& head.html, %opt &>
+<INPUT TYPE="hidden" NAME="options" VALUE="snmp_community,snmp_version,snmp_timeout">
+<& /elements/tr-select.html,
+  label   => 'SNMP version',
+  field   => 'snmp_version',
+  options => [ '1', '2c' ],
+  curr_value => $part_export->option('version') 
+&>
+<& /elements/tr-input-text.html,
+  label   => 'Community',
+  field   => 'snmp_community',
+  curr_value  => $part_export->option('community') || $opt{'export_info'}->{'options'}->{'snmp_community'}->{'default'},
+&>
+<& /elements/tr-input-text.html,
+  label   => 'Timeout (seconds)',
+  field   => 'snmp_timeout',
+  curr_value  => $part_export->option('timeout') || $opt{'export_info'}->{'options'}->{'snmp_timeout'}->{'default'},
+&>
+</TABLE>
+<script type="text/javascript">
+function open_select_mib_get(obj) {
+  nd(1); // if there's already one open, close it
+  var rownum = obj.rownum;
+  var curr_oid = obj.form.elements['snmp_oid' + rownum].value || '';
+  var url = '<%$fsurl%>misc/select-mib-popup.html?' +
+            'callback=receive_mib_get;' +
+            'arg=' + rownum +
+            ';curr_value=' + curr_oid;
+  overlib(
+    OLiframeContent(url, 550, 450, '<% $popup_name %>', 0, 'auto'),
+    CAPTION, 'Select MIB object', STICKY, AUTOSTATUSCAP,
+    MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK,
+    BGCOLOR, '#333399', CGCOLOR, '#333399',
+    CLOSETEXT, 'Close'
+  );
+}
+function receive_mib_get(obj, rownum) {
+  var oidfield = document.getElementById('snmp_oid'+rownum);
+  oidfield.value = obj.fullname;
+  oidfield.onchange();
+}
+</script>
+
+<table bgcolor="#cccccc" border=0 cellspacing=3>
+<TR><TH>Object ID</TH></TR>
+<TR id="broadband_snmp_get_template">
+  <TD>
+    <INPUT NAME="oid" ID="oid" SIZE="54">
+    <INPUT TYPE="button" VALUE="..." ID="openselector" onclick="open_select_mib_get(this)">
+  </TD>
+</TR>
+<& /elements/auto-table.html,
+  template_row  => 'broadband_snmp_get_template',
+  fieldorder    => ['oid'],
+  data          => \@data,
+  table         => 'snmp',
+&>
+<INPUT TYPE="hidden" NAME="multi_options" VALUE="snmp_oid">
+<& foot.html, %opt &>
+<%init>
+my %opt = @_;
+
+my $part_export = $opt{part_export} || FS::part_export->new;
+
+my @oids    = split("\n", $part_export->option('snmp_oid'));
+
+my @data;
+while (@oids) {
+  my @thisrow = (shift(@oids));
+  push @data, \@thisrow if grep length($_), @thisrow;
+}
+
+my $popup_name = 'popup-'.time."-$$-".rand() * 2**32;
+</%init>
diff --git a/httemplate/elements/broadband_snmp_get-dialog.html b/httemplate/elements/broadband_snmp_get-dialog.html
new file mode 100644 (file)
index 0000000..61bb9c7
--- /dev/null
@@ -0,0 +1,68 @@
+<%doc>
+Adds a link to display snmp statistics based on broadband_snmp_get export config.
+Performs necessary checks such that, if no such exports are configured for the passed 
+service, returns blank space (ie may be safely invoked even if no exports are configured.)
+
+  <& '/elements/broadband_snmp_get-dialog.html', svc => $svc &>
+
+</%doc>
+% if (@snmp) {
+<& '/elements/xmlhttp.html',
+   'url'  => $fsurl.'misc/xmlhttp-broadband_snmp_get.cgi',
+   'subs' => [ 'broadband_snmp_get_request' ]
+ &>
+<SCRIPT>
+function broadband_snmp_get (svcnum) {
+  var jqd = $( '#broadband_snmp_get_dialog' );
+  if (!jqd.dialog( 'isOpen' )) {
+    jqd.dialog( 'open' );
+  }
+  document.getElementById('broadband_snmp_get_dialog').innerHTML = '<B>Loading...</B>';
+  broadband_snmp_get_request('svcnum',svcnum,
+    function (result) {
+      var objects = JSON.parse(result) || [];
+      if (objects.length) {
+        var table = document.createElement('table');
+        for (i = 0; i < objects.length; i++) {
+          var row = document.createElement('tr');
+          var obj = objects[i];
+          if (obj.error) {
+            var cell = document.createElement('td');
+            cell.colSpan = '2';
+            cell.innerHTML = obj['error'];
+            row.appendChild(cell);
+          } else {
+              var cell = document.createElement('td');
+              cell.innerHTML = obj['label'];
+              row.appendChild(cell);
+              cell = document.createElement('td');
+              cell.innerHTML = obj['value'];
+              row.appendChild(cell);
+          }
+          table.appendChild(row);
+        }
+        var dialog = document.getElementById('broadband_snmp_get_dialog');
+        dialog.innerHTML = '';
+        dialog.appendChild(table);
+      }  // if objects.length
+    }  // function
+  ); // broadband_snmp_get_request
+} // broadband_snmp_get
+</SCRIPT>
+<SPAN ID="broadband_snmp_get_dialog"></SPAN>
+<SPAN ID="broadband_snmp_get_link">
+<A HREF="javascript: void(0)" onclick="broadband_snmp_get('<% $svcnum %>')">(snmp)</A>
+</SPAN>
+<SCRIPT>
+$( '#broadband_snmp_get_dialog' ).dialog({
+  position: { my: "left top", at: "left top", of: "#broadband_snmp_get_link" },
+  autoOpen: false,
+  title: 'SNMP',
+});
+</SCRIPT>
+% } #if @snmp
+<%init>
+my(%opt) = @_;
+my @snmp = $opt{'svc'}->cust_svc->part_svc->part_export('broadband_snmp_get');
+my $svcnum = $opt{'svc'}->svcnum;
+</%init>
diff --git a/httemplate/elements/select-mib-popup.html b/httemplate/elements/select-mib-popup.html
deleted file mode 100644 (file)
index f95ce2b..0000000
+++ /dev/null
@@ -1,186 +0,0 @@
-<& /elements/header-popup.html &>
-<DIV STYLE="visibility: hidden; position: absolute" ID="measurebox"></DIV>
-<TABLE WIDTH="100%">
-<TR>
-  <TD WIDTH="30%" ALIGN="right">Module:</TD>
-  <TD><SELECT ID="select_module"></SELECT></TD>
-</TR>
-<TR>
-  <TD ALIGN="right">Object:</TD>
-  <TD><INPUT TYPE="text" NAME="path" ID="input_path" SIZE=50 WIDTH="100%"></TD>
-</TR>
-<TR>
-  <TD COLSPAN=2>
-    <SELECT STYLE="width:100%" SIZE=12 ID="select_path"></SELECT>
-  </TD>
-</TR>
-<TR>
-  <TH ALIGN="center" COLSPAN=2 ID="mib_objectID"></TH>
-</TR>
-<TR>
-  <TD ALIGN="right">Module: </TD><TD ID="mib_moduleID"></TD>
-</TR>
-<TR>
-  <TD ALIGN="right">Data type: </TD><TD ID="mib_type"></TD>
-</TR>
-<TR>
-  <TH COLSPAN=2>
-    <BUTTON ID="submit_button" onclick="submit()" DISABLED=1>Continue</BUTTON>
-  </TH>
-</TR>
-</TABLE>
-<& /elements/xmlhttp.html,
-  url   => $p.'misc/xmlhttp-mib-browse.html',
-  subs  => [qw( search get_module_list )],
-&>
-<SCRIPT TYPE="text/javascript">
-
-var selected_mib;
-
-function show_info(state) {
-  document.getElementById('mib_objectID').style.display = 
-    document.getElementById('mib_moduleID').style.display = 
-    document.getElementById('mib_type').style.display = 
-    state ? '' : 'none';
-}
-
-function clear_list() {
-  var select_path = document.getElementById('select_path');
-  select_path.options.length = 0;
-}
-
-var measurebox = document.getElementById('measurebox');
-function add_item(value) {
-  var select_path = document.getElementById('select_path');
-  var input_path = document.getElementById('input_path');
-  var opt = document.createElement('option');
-  var v = value;
-  if ( v.match(/-$/) ) {
-    opt.className = 'leaf';
-    v = v.substring(0, v.length - 1);
-  }
-  var optvalue = v; // may not be the name we display
-  // shorten these if they don't fit in the box
-  if ( v.length > 30 ) { // unless they're already really short
-    measurebox.innerHTML = v;
-    while ( measurebox.clientWidth > select_path.clientWidth - 10
-            && v.match(/^\..*\./) ) {
-      v = v.replace(/^\.[^\.]+/, '');
-      measurebox.innerHTML = v;
-    }
-    if ( optvalue != v ) {
-      v = '...' + v;
-    }
-  }
-  opt.value = optvalue;
-  opt.text = v;
-  opt.selected = (input_path.value == v);
-  select_path.add(opt, null);
-}
-
-var timerID = 0;
-
-function populate(json_result) {
-  var result = JSON.parse(json_result);
-  clear_list();
-  for (var x in result['choices']) {
-    opt = document.createElement('option');
-    add_item(result['choices'][x]);
-  }
-  if ( result['objectID'] ) {
-    selected_mib = result;
-    show_info(true);
-    // show details on the selected node
-    document.getElementById('mib_objectID').innerHTML = result.objectID;
-    document.getElementById('mib_moduleID').innerHTML = result.moduleID;
-    document.getElementById('mib_type').innerHTML = result.type;
-    document.getElementById('submit_button').disabled = !result.type;
-  } else {
-    selected_mib = undefined;
-    show_info(false);
-  }
-}
-
-function populate_modules(json_result) {
-  var result = JSON.parse(json_result);
-  var select_module = document.getElementById('select_module');
-  var opt = document.createElement('option');
-  opt.value = 'ANY';
-  opt.text  = '(any)';
-  select_module.add(opt, null);
-  for (var x in result['modules']) {
-    opt = document.createElement('option');
-    opt.value = opt.text = result['modules'][x];
-    select_module.add(opt, null);
-  }
-}
-
-function dispatch_search() {
-  // called from the interval timer
-  var search_string = document.getElementById('select_module').value + ':' +
-                      document.getElementById('input_path').value;
-
-  search(search_string, populate);
-}
-
-function delayed_search() {
-  // onkeyup handler for the text input
-  // 500ms after the user stops typing, send the search request
-  if (timerID != 0) {
-    clearTimeout(timerID);
-  }
-  timerID = setTimeout(dispatch_search, 500);
-}
-
-function handle_choose_object() {
-  // onchange handler for the selector
-  // when the user picks an option, set the text input to that, and then
-  // search for it as though it was entered
-  var input_path = document.getElementById('input_path');
-  input_path.value = this.value;
-  dispatch_search();
-}
-
-function handle_choose_module() {
-  input_path.value = ''; // just to avoid confusion
-  delayed_search();
-}
-
-function submit() {
-% if ( $callback ) {
-  <% $callback %>;
-  parent.nd(1); // close popup
-% } else {
-  alert(document.getElementById('input_path').value);
-% }
-}
-
-var input_path = document.getElementById('input_path');
-input_path.onkeyup = delayed_search;
-var select_path = document.getElementById('select_path');
-select_path.onchange = handle_choose_object;
-var select_module = document.getElementById('select_module');
-select_module.onchange = handle_choose_module;
-% if ( $cgi->param('curr_value') ) {
-input_path.value = <% $cgi->param('curr_value') |js_string %>;
-% }
-dispatch_search();
-get_module_list('', populate_modules);
-
-</SCRIPT>
-<& /elements/footer.html &>
-<%init>
-my $callback = 'alert("(no callback defined)" + selected_mib.stringify)';
-$cgi->param('callback') =~ /^(\w+)$/;
-if ( $1 ) {
-  # construct the JS function call expresssion
-  $callback = 'window.parent.' . $1 . '(selected_mib';
-  foreach ($cgi->param('arg')) {
-    # pass-through arguments
-    /^(\w+)$/ or next;
-    $callback .= ",'$1'";
-  }
-  $callback .= ')';
-}
-
-</%init>
diff --git a/httemplate/misc/select-mib-popup.html b/httemplate/misc/select-mib-popup.html
new file mode 100644 (file)
index 0000000..f95ce2b
--- /dev/null
@@ -0,0 +1,186 @@
+<& /elements/header-popup.html &>
+<DIV STYLE="visibility: hidden; position: absolute" ID="measurebox"></DIV>
+<TABLE WIDTH="100%">
+<TR>
+  <TD WIDTH="30%" ALIGN="right">Module:</TD>
+  <TD><SELECT ID="select_module"></SELECT></TD>
+</TR>
+<TR>
+  <TD ALIGN="right">Object:</TD>
+  <TD><INPUT TYPE="text" NAME="path" ID="input_path" SIZE=50 WIDTH="100%"></TD>
+</TR>
+<TR>
+  <TD COLSPAN=2>
+    <SELECT STYLE="width:100%" SIZE=12 ID="select_path"></SELECT>
+  </TD>
+</TR>
+<TR>
+  <TH ALIGN="center" COLSPAN=2 ID="mib_objectID"></TH>
+</TR>
+<TR>
+  <TD ALIGN="right">Module: </TD><TD ID="mib_moduleID"></TD>
+</TR>
+<TR>
+  <TD ALIGN="right">Data type: </TD><TD ID="mib_type"></TD>
+</TR>
+<TR>
+  <TH COLSPAN=2>
+    <BUTTON ID="submit_button" onclick="submit()" DISABLED=1>Continue</BUTTON>
+  </TH>
+</TR>
+</TABLE>
+<& /elements/xmlhttp.html,
+  url   => $p.'misc/xmlhttp-mib-browse.html',
+  subs  => [qw( search get_module_list )],
+&>
+<SCRIPT TYPE="text/javascript">
+
+var selected_mib;
+
+function show_info(state) {
+  document.getElementById('mib_objectID').style.display = 
+    document.getElementById('mib_moduleID').style.display = 
+    document.getElementById('mib_type').style.display = 
+    state ? '' : 'none';
+}
+
+function clear_list() {
+  var select_path = document.getElementById('select_path');
+  select_path.options.length = 0;
+}
+
+var measurebox = document.getElementById('measurebox');
+function add_item(value) {
+  var select_path = document.getElementById('select_path');
+  var input_path = document.getElementById('input_path');
+  var opt = document.createElement('option');
+  var v = value;
+  if ( v.match(/-$/) ) {
+    opt.className = 'leaf';
+    v = v.substring(0, v.length - 1);
+  }
+  var optvalue = v; // may not be the name we display
+  // shorten these if they don't fit in the box
+  if ( v.length > 30 ) { // unless they're already really short
+    measurebox.innerHTML = v;
+    while ( measurebox.clientWidth > select_path.clientWidth - 10
+            && v.match(/^\..*\./) ) {
+      v = v.replace(/^\.[^\.]+/, '');
+      measurebox.innerHTML = v;
+    }
+    if ( optvalue != v ) {
+      v = '...' + v;
+    }
+  }
+  opt.value = optvalue;
+  opt.text = v;
+  opt.selected = (input_path.value == v);
+  select_path.add(opt, null);
+}
+
+var timerID = 0;
+
+function populate(json_result) {
+  var result = JSON.parse(json_result);
+  clear_list();
+  for (var x in result['choices']) {
+    opt = document.createElement('option');
+    add_item(result['choices'][x]);
+  }
+  if ( result['objectID'] ) {
+    selected_mib = result;
+    show_info(true);
+    // show details on the selected node
+    document.getElementById('mib_objectID').innerHTML = result.objectID;
+    document.getElementById('mib_moduleID').innerHTML = result.moduleID;
+    document.getElementById('mib_type').innerHTML = result.type;
+    document.getElementById('submit_button').disabled = !result.type;
+  } else {
+    selected_mib = undefined;
+    show_info(false);
+  }
+}
+
+function populate_modules(json_result) {
+  var result = JSON.parse(json_result);
+  var select_module = document.getElementById('select_module');
+  var opt = document.createElement('option');
+  opt.value = 'ANY';
+  opt.text  = '(any)';
+  select_module.add(opt, null);
+  for (var x in result['modules']) {
+    opt = document.createElement('option');
+    opt.value = opt.text = result['modules'][x];
+    select_module.add(opt, null);
+  }
+}
+
+function dispatch_search() {
+  // called from the interval timer
+  var search_string = document.getElementById('select_module').value + ':' +
+                      document.getElementById('input_path').value;
+
+  search(search_string, populate);
+}
+
+function delayed_search() {
+  // onkeyup handler for the text input
+  // 500ms after the user stops typing, send the search request
+  if (timerID != 0) {
+    clearTimeout(timerID);
+  }
+  timerID = setTimeout(dispatch_search, 500);
+}
+
+function handle_choose_object() {
+  // onchange handler for the selector
+  // when the user picks an option, set the text input to that, and then
+  // search for it as though it was entered
+  var input_path = document.getElementById('input_path');
+  input_path.value = this.value;
+  dispatch_search();
+}
+
+function handle_choose_module() {
+  input_path.value = ''; // just to avoid confusion
+  delayed_search();
+}
+
+function submit() {
+% if ( $callback ) {
+  <% $callback %>;
+  parent.nd(1); // close popup
+% } else {
+  alert(document.getElementById('input_path').value);
+% }
+}
+
+var input_path = document.getElementById('input_path');
+input_path.onkeyup = delayed_search;
+var select_path = document.getElementById('select_path');
+select_path.onchange = handle_choose_object;
+var select_module = document.getElementById('select_module');
+select_module.onchange = handle_choose_module;
+% if ( $cgi->param('curr_value') ) {
+input_path.value = <% $cgi->param('curr_value') |js_string %>;
+% }
+dispatch_search();
+get_module_list('', populate_modules);
+
+</SCRIPT>
+<& /elements/footer.html &>
+<%init>
+my $callback = 'alert("(no callback defined)" + selected_mib.stringify)';
+$cgi->param('callback') =~ /^(\w+)$/;
+if ( $1 ) {
+  # construct the JS function call expresssion
+  $callback = 'window.parent.' . $1 . '(selected_mib';
+  foreach ($cgi->param('arg')) {
+    # pass-through arguments
+    /^(\w+)$/ or next;
+    $callback .= ",'$1'";
+  }
+  $callback .= ')';
+}
+
+</%init>
diff --git a/httemplate/misc/xmlhttp-broadband_snmp_get.cgi b/httemplate/misc/xmlhttp-broadband_snmp_get.cgi
new file mode 100644 (file)
index 0000000..a6f9267
--- /dev/null
@@ -0,0 +1,35 @@
+<%doc>
+Requires arg $svcnum.  Returns JSON-encoded realtime snmp results 
+for configured broadband_snmp_get exports.
+</%doc>
+<% encode_json(\@result) %>\
+<%init>
+
+# access/agent permissions lifted from /view/elements/svc_Common.html
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('View customer services');
+
+my %arg = $cgi->param('arg');
+my $svc = qsearchs({
+  'select'    => 'svc_broadband.*',
+  'table'     => 'svc_broadband',
+  'addl_from' => ' LEFT JOIN cust_svc  USING ( svcnum  ) '.
+                 ' LEFT JOIN cust_pkg  USING ( pkgnum  ) '.
+                 ' LEFT JOIN cust_main USING ( custnum ) ',
+  'hashref'   => { 'svcnum' => $arg{'svcnum'} },
+  'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql(
+                            'null_right' => 'View/link unlinked services'
+                          ),
+}) or die "Unknown svcnum ".$arg{'svcnum'}." in svc_broadband table\n";
+
+my @part_export = $svc->cust_svc->part_svc->part_export('broadband_snmp_get');
+
+my @result;
+foreach my $part_export (@part_export) {
+  push @result, $part_export->snmp_results($svc);
+}
+
+</%init>
+
+
index 4935a10..bc272e8 100644 (file)
@@ -72,6 +72,7 @@ sub ip_addr {
   my $out = $ip_addr;
   $out .= ' (' . include('/elements/popup_link-ping.html', ip => $ip_addr) . ')'
     if $ip_addr;
+  $out .= include('/elements/broadband_snmp_get-dialog.html', svc => $svc);
   if ($svc->cust_svc->part_svc->part_export('cacti')) {
     $out .= ' (<A HREF="'
          .  popurl(2)