--- /dev/null
+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;
+
<& /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',
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;
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>
--- /dev/null
+<%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>
--- /dev/null
+<%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>
+++ /dev/null
-<& /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>
--- /dev/null
+<& /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>
--- /dev/null
+<%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>
+
+
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)