use qstr in templates

This commit is contained in:
Oliver Gorwits
2014-01-26 13:35:53 +00:00
parent ddd29a845e
commit 00a23958a7
30 changed files with 87 additions and 46 deletions

View File

@@ -7,6 +7,8 @@ package App::Netdisco::DB::Result::Device;
use strict;
use warnings;
use URI::Escape;
use base 'DBIx::Class::Core';
__PACKAGE__->table("device");
__PACKAGE__->add_columns(
@@ -273,4 +275,20 @@ Number of seconds which have elapsed since the value of C<last_arpnip>.
sub since_last_arpnip { return (shift)->get_column('since_last_arpnip') }
=head1 ADDITIONAL METHODS
=head2 qstr
Returns a query string suitable for including in Netdisco URLs which will
reference this device.
=cut
sub qstr {
my $row = shift;
return sprintf 'q=%s&uuid=%s',
uri_escape($row->dns || $row->ip),
uri_escape($row->ip);
}
1;

View File

@@ -4,6 +4,7 @@ use strict;
use warnings;
use base 'DBIx::Class::Core';
use URI::Escape;
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
@@ -58,4 +59,16 @@ __PACKAGE__->add_columns(
},
);
sub left_qstr {
my $row = shift;
return sprintf 'q=%s&uuid=%s',
uri_escape($row->left_dns || $row->left_ip), uri_escape($row->left_ip);
}
sub right_qstr {
my $row = shift;
return sprintf 'q=%s&uuid=%s',
uri_escape($row->right_dns || $row->right_ip), uri_escape($row->right_ip);
}
1;

View File

@@ -10,6 +10,7 @@ use Socket6 (); # to ensure dependency is met
use HTML::Entities (); # to ensure dependency is met
use URI::QueryParam (); # part of URI, to add helper methods
use Path::Class 'dir';
use URI::Escape;
use App::Netdisco::Web::AuthN;
use App::Netdisco::Web::Static;
@@ -66,6 +67,11 @@ hook 'before_template' => sub {
$tokens->{$_} = $tokens->{$_}->path_query
for qw/search_node search_device device_ports/;
# precreated query string which uses uuid
$tokens->{qstr} = sprintf 'q=%s&uuid=%s',
uri_escape(param('q') || ''),
uri_escape(param('uuid') || param('q') || '');
# allow very long lists of ports
$Template::Directive::WHILE_MAX = 10_000;

View File

@@ -6,6 +6,7 @@ use Dancer::Plugin::DBIC;
use Dancer::Plugin::Auth::Extensible;
use App::Netdisco::Web::Plugin;
use URI::Escape;
register_device_tab({ tag => 'netmap', label => 'Neighbors' });
@@ -35,7 +36,8 @@ sub _add_children {
push @legit, $c;
push @{$ptr}, {
name => _get_name($c),
fullname => (var('devices')->{$c} || $c),
qstr => (sprintf 'q=%s&uuid=%s',
uri_escape(var('devices')->{$c} || $c), uri_escape($c)),
ip => $c,
};
}
@@ -93,7 +95,8 @@ get '/ajax/data/device/netmap' => require_login sub {
my %tree = (
ip => $start,
name => _get_name($start),
fullname => (var('devices')->{$start} || $start),
qstr => (sprintf 'q=%s&uuid=%s',
uri_escape(var('devices')->{$start} || $start), uri_escape($start)),
children => [],
);

View File

@@ -42,9 +42,9 @@
</span>
[% ELSIF task.tag == 'portlog' %]
<span id="nd_device-name">
<a href="[% device_ports %]&q=[% params.q | uri %]&c_admin=on">[% params.q %]</a>
<a href="[% device_ports %]&[% qstr %]&c_admin=on">[% params.q %]</a>
-
<a href="[% device_ports %]&q=[% params.q | uri %]&f=[% params.f | uri %]&c_admin=on">[% params.f %]</a>
<a href="[% device_ports %]&[% qstr %]&f=[% params.f | uri %]&c_admin=on">[% params.f %]</a>
</span>
[% ELSIF task.provides_csv %]
<span id="nd_device-name">

View File

@@ -36,7 +36,7 @@
<td class="nd_center-cell">[% row.status.ucfirst | html_entity %]</td>
[% END %]
<td class="nd_center-cell"><a class="nd_linkcell"
href="[% uri_for('/device') %]?q=[% row.device | uri %]">[% row.device | html_entity %]</a></td>
href="[% uri_for('/device') %]?q=[% row.device | uri %]&uuid=[% row.device | uri %]">[% row.device | html_entity %]</a></td>
<td class="nd_center-cell">[% row.port | html_entity %]</td>
<td class="nd_center-cell">[% row.subaction | html_entity %]</td>
<td class="nd_center-cell">[% row.username | html_entity %]</td>

View File

@@ -22,7 +22,7 @@
<tbody>
[% FOREACH row IN orphans %]
<tr>
<td><a href="[% uri_for('/device') %]?q=[% row.dns || row.ip | uri %]">
<td><a href="[% uri_for('/device') %]?q=[% row.dns || row.ip | uri %]&uuid=[% row.ip | uri %]">
[% row.dns || row.name || row.ip | html_entity %]</a></td>
<td>
[% IF row.location %]
@@ -75,7 +75,7 @@
<tbody>
[% FOREACH row IN network %]
<tr>
<td><a href="[% uri_for('/device') %]?tab=netmap&q=[% row.dns || row.ip | uri %]">
<td><a href="[% uri_for('/device') %]?tab=netmap&q=[% row.dns || row.ip | uri %]&uuid=[% row.ip | uri %]">
[% row.dns || row.name || row.ip | html_entity %]</a></td>
<td>
[% IF row.location %]

View File

@@ -21,7 +21,7 @@
[% SET count = count + 1 %]
<tr>
<td class="nd_center-cell"><a class="nd_linkcell"
href="[% uri_for('/device') %]?q=[% row.dns | uri %]">[% row.dns | html_entity %]</a></td>
href="[% uri_for('/device') %]?[% row.qstr %]">[% row.dns | html_entity %]</a></td>
<td class="nd_center-cell">[% row.ip | html_entity %]</td>
<td class="nd_center-cell">
<input data-form="update" name="ports" type="number" value="[% row.port_count | html_entity %]">

View File

@@ -16,7 +16,7 @@
<tr>
<td class="nd_center-cell">[% row.action.ucfirst | html_entity %]</td>
<td class="nd_center-cell"><a class="nd_linkcell"
href="[% uri_for('/device') %]?q=[% row.device | uri %]">[% row.device | html_entity %]</a></td>
href="[% uri_for('/device') %]?q=[% row.device | uri %]&uuid=[% row.device | uri %]">[% row.device | html_entity %]</a></td>
<td class="nd_center-cell">[% row.started | html_entity %]</td>
<td class="nd_center-cell">[% row.finished | html_entity %]</td>
<td class="nd_center-cell">[% row.elapsed | html_entity %]</td>

View File

@@ -42,11 +42,11 @@
[% WHILE (row = results.next) %]
[% SET count = count + 1 %]
<tr>
<td class="nd_center-cell"><a class="nd_linkcell" href="[% uri_for('/device') %]?q=[% row.dev1 | uri %]">
<td class="nd_center-cell"><a class="nd_linkcell" href="[% uri_for('/device') %]?[% row.device1.qstr %]">
[% (row.device1.dns || row.device1.name || row.device1.ip) | html_entity %]</a>
</td>
<td class="nd_center-cell">[% row.port1 | html_entity %]</td>
<td class="nd_center-cell"><a class="nd_linkcell" href="[% uri_for('/device') %]?q=[% row.dev2 | uri %]">
<td class="nd_center-cell"><a class="nd_linkcell" href="[% uri_for('/device') %]?[% row.device2.qstr %]">
[% (row.device2.dns || row.device2.name || row.device2.ip) | html_entity %]</a></td>
<td class="nd_center-cell">[% row.port2 | html_entity %]</td>
<td class="nd_center-cell">

View File

@@ -10,9 +10,9 @@
</tbody>
[% FOREACH row IN results %]
<tr>
<td><a href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&f=[% row.port | uri %]">
<td><a href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&uuid=[% row.ip | uri %]&f=[% row.port | uri %]">
[% row.dns || row.name || row.ip | html_entity %] ( [% row.port | html_entity %] ) </a></td>
<td><a href="[% search_node %]&q=[% row.remote_ip | uri %]">
<td><a href="[% search_node %]&q=[% row.remote_ip | uri %]&uuid=[% row.remote_ip | uri %]">
[% row.remote_ip | html_entity %]</a>
([% row.remote_port | html_entity %])
[% ' id: '_ row.remote_id IF row.remote_id %]

View File

@@ -14,7 +14,7 @@
<td>[% row.alias | html_entity %]</a>
<td>[% row.dns | html_entity %]</a>
<td class="nd_center-cell"><a class="nd_linkcell"
href="[% device_ports %]&q=[% params.q | uri %]&f=[% row.port | uri %]">[% row.port | html_entity %]</a></td>
href="[% device_ports %]&[% row.device.qstr %]&f=[% row.port | uri %]">[% row.port | html_entity %]</a></td>
<td>[% row.device_port.name | html_entity %]</td>
<td><a class="nd_linkcell"
href="[% search_device %]&q=[% row.subnet | uri %]&ip=[% row.subnet | uri %]">[% row.subnet | html_entity %]</a></td>

View File

@@ -56,8 +56,7 @@ function to_class(name) { return 'nd_' + name.replace(/\./g, "_") }
// handler for clicking on a circle - redirect to that device's netmap
function circleClick(d) {
window.location = '[% uri_for('/device') %]?tab=netmap'
+ '&q=' + d.fullname
window.location = '[% uri_for('/device') %]?tab=netmap&' + d.qstr
+ '&depth=[% params.depth | uri %]'
+ '&vlan=[% params.vlan | uri %]';
}
@@ -92,7 +91,7 @@ $.getJSON('[% uri_for('/ajax/data/device/alldevicelinks') %]', function(data) {
// draw the tree
d3.json("[% uri_for('/ajax/data/device/netmap') %]?"
+ '&q=[% params.q | uri %]'
+ '[% qstr %]'
+ '&depth=[% params.depth | uri %]'
+ '&vlan=[% params.vlan | uri %]', function(error, root) {
var tree = d3.layout.tree()

View File

@@ -67,7 +67,7 @@
data-animation="" data-title="Click to Enable"></i>
[% END %]
<a class="nd_log-icon"
href="[% uri_for('/admin/portlog') %]?q=[% device.dns || device.ip | uri %]&f=[% row.port | uri %]">
href="[% uri_for('/admin/portlog') %]?[% device.qstr %]&f=[% row.port | uri %]">
<i class="icon-file-text-alt"
rel="tooltip" data-placement="top" data-offset="3"
data-animation="" data-title="View Port Log"></i>
@@ -76,14 +76,14 @@
<td nowrap>
[% END %]
<a class="nd_this-port-only nd_port-only-first" href="[% uri_for('/device',
self_options) %]&q=[% params.q | uri %]&f=[% row.port | uri %]&prefer=port">
self_options) %]&[% qstr %]&f=[% row.port | uri %]&prefer=port">
[% IF row.is_master %]
<small><i class="icon-group muted"></i></small>&nbsp;
[% END %]
[% row.port | html_entity %]</a>
[% IF row.slave_of %]<br/>
<a class="nd_this-port-only" href="[% uri_for('/device',
self_options) %]&q=[% params.q | uri %]&f=[% row.slave_of | uri %]&prefer=port">
self_options) %]&[% qstr %]&f=[% row.slave_of | uri %]&prefer=port">
[% row.slave_of | html_entity %]</a>
[% END %]
</td>
@@ -240,7 +240,8 @@
<i class="icon-signal"></i>&nbsp;
[% END %]
<a href="[% uri_for('/device',
self_options) %]&q=[% row.neighbor.dns || row.neighbor.ip | uri %]&f=[% row.remote_port | uri %]&prefer=port">
self_options) %]&q=[% row.neighbor.dns || row.neighbor.ip | uri
%]&uuid=[% row.neighbor.ip | uri %]f=[% row.remote_port | uri %]&prefer=port">
[% row.neighbor.dns.remove(settings.domain_suffix) || row.neighbor.ip | html_entity %]
[% ' - ' IF row.remote_port %][% row.remote_port | html_entity %]</a><br/>
[% IF params.neigh_id and (row.remote_id or row.remote_type) %]
@@ -254,7 +255,7 @@
[% ELSIF row.remote_type AND row.remote_type.match('(cisco\s+AIR-[L|C]?AP|-K9W8-|^AP:\s)') %]
<i class="icon-signal"></i>&nbsp;
[% END %]
<a href="[% search_node %]&q=[% row.remote_ip | uri %]">
<a href="[% search_node %]&q=[% row.remote_ip | uri %]&uuid=[% row.remote_ip | uri %]">
[% row.remote_ip | html_entity %]
[% ' - ' IF row.remote_port %][% row.remote_port | html_entity %]</a><br/>
[% IF params.neigh_id and (row.remote_id or row.remote_type) %]

View File

@@ -30,7 +30,7 @@
[% NEXT UNLESS p.channel # No channel port is admin down %]
<tr>
<td>
<a href="[% device_ports %]&q=[% results.$row.device.dns || results.$row.device.ip | uri %]&f=[% p.port | uri %]">
<a href="[% device_ports %]&[% results.$row.device.qstr %]&f=[% p.port | uri %]">
[% p.port | html_entity %]</a></td>
<td>[% p.name %]</td>
<td>[% p.descr %]</td>

View File

@@ -10,7 +10,7 @@
</tbody>
[% FOREACH row IN results %]
<tr>
<td class="nd_center-cell"><a href="[% search_device %]&q=[% row.dns || row.ip | uri %]">
<td class="nd_center-cell"><a href="[% search_device %]&[% row.qstr %]">
[% row.dns || row.name || row.ip | html_entity %]</a>
<td class="nd_center-cell">[% row.alias | html_entity %]</td>
<td class="nd_center-cell">[% row.contact | html_entity %]</td>
@@ -18,4 +18,4 @@
</tr>
[% END %]
</tbody>
</table>
</table>

View File

@@ -20,7 +20,7 @@
[Not Set]
[% END %]
</td>
<td><a href="[% uri_for('/device') %]?q=[% row.dns || row.ip | uri %]">[% row.dns || row.ip | html_entity %]</a></td>
<td><a href="[% uri_for('/device') %]?[% row.qstr %]">[% row.dns || row.ip | html_entity %]</a></td>
<td><a href="[% search_device %]&q=[% row.name | uri %]&name=[% row.name | uri %]">
[% row.name | html_entity %]</a>
</td>

View File

@@ -14,13 +14,13 @@
<tr>
<td class="nd_center-cell">[% row.left_dns || row.left_ip | html_entity %]</td>
<td class="nd_center-cell"><a class="nd_linkcell"
href="[% device_ports %]&q=[% row.left_dns || row.left_ip | uri %]&f=[% row.left_port | uri %]&c_duplex=on">
href="[% device_ports %]&[% row.left_qstr %]&f=[% row.left_port | uri %]&c_duplex=on">
[% row.left_port | html_entity %]</a></td>
<td class="nd_center-cell">[% row.left_duplex.ucfirst | html_entity %]</td>
<td class="nd_center-cell">[% row.right_dns || row.right_ip | html_entity %]</a>
<td class="nd_center-cell"><a class="nd_linkcell"
href="[% device_ports %]&q=[% row.right_dns || row.right_ip | uri %]&f=[% row.right_port | uri %]&c_duplex=on">
href="[% device_ports %]&[% row.right_qstr %]&f=[% row.right_port | uri %]&c_duplex=on">
[% row.right_port | html_entity %]</a></td>
<td class="nd_center-cell">[% row.right_duplex.ucfirst | html_entity %]</td>
</tr>

View File

@@ -12,7 +12,7 @@
<tr>
<td>[% row.device.dns || row.device.ip | html_entity %]</td>
<td class="nd_center-cell"><a class="nd_linkcell"
href="[% device_ports %]&q=[% row.device.dns || row.device.ip | uri %]&f=[% row.port | uri %]&c_duplex=on">
href="[% device_ports %]&[% row.device.qstr %]&f=[% row.port | uri %]&c_duplex=on">
[% row.port | html_entity %]</a></td>
<td class="nd_center-cell">[% row.name | html_entity %]</td>
<td class="nd_center-cell">[% row.duplex.ucfirst | html_entity %]</td>

View File

@@ -15,7 +15,7 @@
[% '&nbsp;<i class="icon-book text-warning"></i>&nbsp;' IF NOT row.active %]
</td>
[% ELSIF row.time_last %]
<td><a href="[% search_device %]&q=[% row.ip | uri %]">[% row.ip | html_entity %]</a>
<td><a href="[% search_device %]&q=[% row.ip | uri %]&uuid=[% row.ip | uri %]">[% row.ip | html_entity %]</a>
</td>
[% ELSE %]
<td>[% row.ip | html_entity %]</td>

View File

@@ -14,7 +14,7 @@
<tr>
<td class="nd_center-cell">[% row.dns || row.name || row.ip | html_entity %]</td>
<td class="nd_center-cell"><a class="nd_linkcell"
href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&f=[% row.port | uri %]">
href="[% device_ports %]&[% row.qstr %]&f=[% row.port | uri %]">
[% row.port | html_entity %]</a></td>
<td class="nd_center-cell">[% row.remote_id | html_entity %]</td>
<td class="nd_center-cell"><a class="nd_linkcell"

View File

@@ -12,7 +12,7 @@
[% FOREACH row IN results %]
<tr>
<td>[% row.dns || row.name || row.ip | html_entity %]</td>
<td class="nd_center-cell"><a href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&f=[% row.port | uri %]&c_nodes=on">
<td class="nd_center-cell"><a href="[% device_ports %]&[% row.qstr %]&f=[% row.port | uri %]&c_nodes=on">
[% row.port | html_entity %]</a></td>
<td class="nd_center-cell">[% row.description | html_entity %]</td>
<td class="nd_center-cell">[% row.up_admin | html_entity %]</td>

View File

@@ -12,7 +12,7 @@
[% FOREACH row IN results %]
<tr>
<td>[% row.dns || row.name || row.ip | html_entity %]</td>
<td class="nd_center-cell"><a href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&f=[% row.port | uri %]&c_nodes=on">
<td class="nd_center-cell"><a href="[% device_ports %]&[% row.qstr %]&f=[% row.port | uri %]&c_nodes=on">
[% row.port | html_entity %]</a></td>
<td class="nd_center-cell">[% row.description | html_entity %]</td>
<td class="nd_center-cell">[% row.stp | html_entity %]</td>

View File

@@ -12,7 +12,8 @@
[% FOREACH row IN results %]
<tr>
<td>[% row.dns || row.name || row.ip | html_entity %]</td>
<td class="nd_center-cell"><a href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&f=[% row.port | uri %]&c_nodes=on">
<td class="nd_center-cell"><a href="[% device_ports %]&q=[% row.dns || row.ip | uri
%]&uuid=[% row.ip | uri %]&f=[% row.port | uri %]&c_nodes=on">
[% row.port | html_entity %]</a></td>
<td class="nd_center-cell">[% row.description | html_entity %]</td>
<td class="nd_center-cell">[% row.mac_count | format_number %]</td>

View File

@@ -11,7 +11,7 @@
</tbody>
[% WHILE (row = results.next) %]
<tr>
<td><a href="[% device_ports %]&q=[% row.dns || row.ip | uri %]">[% row.dns || row.ip | html_entity %]</a></td>
<td><a href="[% device_ports %]&[% row.qstr %]">[% row.dns || row.ip | html_entity %]</a></td>
<td class="nd_center-cell">[% row.port_count %]</td>
<td class="nd_center-cell">[% row.ports_in_use %]</td>
<td class="nd_center-cell">[% row.ports_shutdown %]</td>

View File

@@ -14,7 +14,7 @@
</tbody>
[% WHILE (row = results.next) %]
<tr>
<td><a href="[% uri_for('/device') %]?q=[% row.dns || row.ip | uri %]">[% row.dns || row.ip | html_entity %]</a></td>
<td><a href="[% uri_for('/device') %]?[% row.qstr %]">[% row.dns || row.ip | html_entity %]</a></td>
<td>[% row.contact | html_entity %]</td>
<td>[% row.location | html_entity %]</td>
<td>[% row.name | html_entity %]</td>

View File

@@ -40,7 +40,7 @@
[% END %]
<td>Switch Port</td>
<td><a class="nd_linkcell"
href="[% device_ports %]&q=[% node.device.dns || node.switch | uri %]&f=[% node.port | uri %]&c_nodes=on&c_neighbors=on">
href="[% device_ports %]&[% node.device.qstr %]&f=[% node.port | uri %]&c_nodes=on&c_neighbors=on">
[% node.switch | html_entity %] - [% node.port | html_entity %]</a>
[% '&nbsp;<i class="icon-book text-warning"></i>&nbsp;' IF NOT node.active %]
[% IF node.device.dns AND node.device_port AND node.device_port.name %]

View File

@@ -70,7 +70,7 @@
[% END %]
<td>Switch Port</td>
<td><a class="nd_linkcell"
href="[% device_ports %]&q=[% node.device.dns || node.switch | uri %]&f=[% node.port | uri %]&c_nodes=on&c_neighbors=on">
href="[% device_ports %]&[% node.device.qstr %]&f=[% node.port | uri %]&c_nodes=on&c_neighbors=on">
[% node.switch | html_entity %] - [% node.port | html_entity %]</a>
[% '&nbsp;<i class="icon-book text-warning"></i>&nbsp;' IF NOT node.active %]
[% IF node.device.dns AND node.device_port AND node.device_port.name %]
@@ -105,7 +105,7 @@
[% END %]
<td>Switch Port</td>
<td><a class="nd_linkcell"
href="[% device_ports %]&q=[% port.device.dns || port.ip | uri %]&f=[% port.port | uri %]&c_mac=on&c_nodes=on&c_neighbors=on">
href="[% device_ports %]&[% port.device.qstr %]&f=[% port.port | uri %]&c_mac=on&c_nodes=on&c_neighbors=on">
[% port.ip | html_entity %] - [% port.descr | html_entity %]</a>
[% IF port.device.dns AND port.name %]
([% port.device.dns | html_entity %] - [% port.name | html_entity %])

View File

@@ -11,7 +11,7 @@
[% WHILE (row = results.next) %]
<tr>
<td>[% row.name | html_entity %]</td>
<td><a href="[% device_ports %]&q=[% row.device.dns || row.ip | uri %]&f=[% row.port | uri %]">
<td><a href="[% device_ports %]&[% row.device.qstr %]&f=[% row.port | uri %]">
[% row.ip | html_entity %] [ [% row.port | html_entity %] ]</a>
[% ' (' _ row.device.dns _ ')' IF row.device.dns %]
</td>

View File

@@ -13,17 +13,17 @@
[% WHILE (row = results.next) %]
<tr>
<td><a class="nd_linkcell nd_stealth-link"
href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&f=[% row.vlan.vlan | uri %]">[% row.vlan.vlan | html_entity %]</a></td>
href="[% device_ports %]&[% row.sqtr %]&f=[% row.vlan.vlan | uri %]">[% row.vlan.vlan | html_entity %]</a></td>
<td><a class="nd_linkcell"
href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&f=[% row.vlan.vlan | uri %]">[% row.dns || row.ip | html_entity %]</a></td>
href="[% device_ports %]&[% row.qstr %]&f=[% row.vlan.vlan | uri %]">[% row.dns || row.ip | html_entity %]</a></td>
<td><a class="nd_linkcell nd_stealth-link"
href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&f=[% row.vlan.vlan | uri %]">[% row.vlan.description | html_entity %]</a></td>
href="[% device_ports %]&[% row.qstr %]&f=[% row.vlan.vlan | uri %]">[% row.vlan.description | html_entity %]</a></td>
<td><a class="nd_linkcell nd_stealth-link"
href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&f=[% row.vlan.vlan | uri %]">[% row.model | html_entity %]</a></td>
href="[% device_ports %]&[% row.qstr %]&f=[% row.vlan.vlan | uri %]">[% row.model | html_entity %]</a></td>
<td><a class="nd_linkcell nd_stealth-link"
href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&f=[% row.vlan.vlan | uri %]">[% row.os | html_entity %]</a></td>
href="[% device_ports %]&[% row.qstr %]&f=[% row.vlan.vlan | uri %]">[% row.os | html_entity %]</a></td>
<td><a class="nd_linkcell nd_stealth-link"
href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&f=[% row.vlan.vlan | uri %]">[% row.vendor | html_entity %]</a></td>
href="[% device_ports %]&[% row.qstr %]&f=[% row.vlan.vlan | uri %]">[% row.vendor | html_entity %]</a></td>
</tr>
[% END %]
</tbody>