Reimplement the Satellite integration in Python
The AngularJS implementation proved hard to maintain, and brittle. Angulars obtuse error messages did not help. Reimplementing this in Python with standard Horizon tables proved faster than trying to solve the issues with the JS implementation. Change-Id: I3cd80d14505b278273b19bfd6b875326c3b72f6a
This commit is contained in:
parent
05c3a99b3f
commit
67cf6e1c04
@ -18,6 +18,7 @@ import horizon
|
||||
from tuskar_ui.infrastructure import dashboard
|
||||
from tuskar_ui.infrastructure.nodes.panel import Nodes as TuskarNodes
|
||||
|
||||
|
||||
class Nodes(horizon.Panel):
|
||||
name = _("Nodes")
|
||||
slug = "nodes"
|
||||
|
36
tuskar_sat_ui/nodes/tables.py
Normal file
36
tuskar_sat_ui/nodes/tables.py
Normal file
@ -0,0 +1,36 @@
|
||||
# -*- coding: utf8 -*-
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from horizon import tables
|
||||
|
||||
|
||||
def get_errata_link(errata):
|
||||
return "http://www.google.com"
|
||||
|
||||
|
||||
class ErrataTable(tables.DataTable):
|
||||
title = tables.Column('title',
|
||||
link=get_errata_link,
|
||||
verbose_name=_("Title"))
|
||||
type = tables.Column('type',
|
||||
verbose_name=_("Type"))
|
||||
id = tables.Column('id',
|
||||
verbose_name=_("Errata ID"))
|
||||
issued = tables.Column('issued',
|
||||
verbose_name=_("Date Issued"))
|
||||
|
||||
class Meta:
|
||||
name = "erratatable"
|
||||
verbose_name = _("Errata")
|
||||
template = "horizon/common/_enhanced_data_table.html"
|
76
tuskar_sat_ui/nodes/tabs.py
Normal file
76
tuskar_sat_ui/nodes/tabs.py
Normal file
@ -0,0 +1,76 @@
|
||||
# -*- coding: utf8 -*-
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import requests
|
||||
import urllib
|
||||
|
||||
from collections import namedtuple
|
||||
from horizon import tabs
|
||||
from tuskar_ui.infrastructure.nodes import tabs as nodes_tabs
|
||||
from .tables import ErrataTable
|
||||
|
||||
|
||||
ErrataItem = namedtuple('ErrataItem', ['title', 'type', 'id', 'issued'])
|
||||
|
||||
|
||||
class DetailOverviewTab(nodes_tabs.DetailOverviewTab):
|
||||
template_name = 'infrastructure/nodes/_detail_overview_sat.html'
|
||||
|
||||
def get_context_data(self, request):
|
||||
result = super(DetailOverviewTab, self).get_context_data(request)
|
||||
if result['node'].uuid is None:
|
||||
return result
|
||||
|
||||
# Some currently hardcoded values:
|
||||
mac = '"52:54:00:4F:D8:65"' # Hardcode for now
|
||||
host = 'http://sat-perf-04.idm.lab.bos.redhat.com' # Hardcode for now
|
||||
auth = ('admin', 'changeme')
|
||||
|
||||
# Get the errata here
|
||||
host = host.strip('/') # Get rid of any trailing slash in the host url
|
||||
|
||||
# Pick up the UUID from the MAC address This makes no sense, as we
|
||||
# need both MAC address and the interface, and we don't have the
|
||||
# interface, so we need to make multiple slow searches. If the
|
||||
# Satellite UUID isn't the same as this one, and it probably isn't we
|
||||
# need to store a mapping somewhere.
|
||||
url = '{host}/katello/api/v2/systems'.format(host=host)
|
||||
for interface in ['eth0', 'eth1', 'en0', 'en1']:
|
||||
|
||||
q = 'facts.net.interface.{iface}.mac_address:{mac}'.format(
|
||||
iface=interface, mac=mac)
|
||||
r = requests.get(url, params={'search': q}, auth=auth)
|
||||
results = r.json()['results']
|
||||
if results:
|
||||
break
|
||||
else:
|
||||
# No node found
|
||||
result['errata'] = None
|
||||
return result
|
||||
|
||||
uuid = results[0]['uuid']
|
||||
errata_url = '{host}/katello/api/v2/systems/{id}/errata'
|
||||
r = requests.get(errata_url.format(host=host, id=uuid), auth=auth)
|
||||
errata = r.json()['results']
|
||||
if not errata:
|
||||
result['errata'] = None
|
||||
else:
|
||||
data = [ErrataItem(x['title'], x['type'], x['id'], x['issued'])
|
||||
for x in errata]
|
||||
result['errata'] = ErrataTable(request, data=data)
|
||||
return result
|
||||
|
||||
|
||||
class NodeDetailTabs(tabs.TabGroup):
|
||||
slug = "node_details"
|
||||
tabs = (DetailOverviewTab,)
|
@ -13,7 +13,8 @@
|
||||
# under the License.
|
||||
|
||||
from tuskar_ui.infrastructure.nodes import views
|
||||
from tuskar_sat_ui.nodes import tabs
|
||||
|
||||
|
||||
class DetailView(views.DetailView):
|
||||
template_name = 'infrastructure/nodes/sat_detail.html'
|
||||
tab_group_class = tabs.NodeDetailTabs
|
||||
|
@ -1,84 +0,0 @@
|
||||
angular.module('hz').factory('Base64', function() {
|
||||
var keyStr = 'ABCDEFGHIJKLMNOP' +
|
||||
'QRSTUVWXYZabcdef' +
|
||||
'ghijklmnopqrstuv' +
|
||||
'wxyz0123456789+/' +
|
||||
'=';
|
||||
return {
|
||||
encode: function (input) {
|
||||
var output = "";
|
||||
var chr1, chr2, chr3 = "";
|
||||
var enc1, enc2, enc3, enc4 = "";
|
||||
var i = 0;
|
||||
|
||||
do {
|
||||
chr1 = input.charCodeAt(i++);
|
||||
chr2 = input.charCodeAt(i++);
|
||||
chr3 = input.charCodeAt(i++);
|
||||
|
||||
enc1 = chr1 >> 2;
|
||||
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
|
||||
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
|
||||
enc4 = chr3 & 63;
|
||||
|
||||
if (isNaN(chr2)) {
|
||||
enc3 = enc4 = 64;
|
||||
} else if (isNaN(chr3)) {
|
||||
enc4 = 64;
|
||||
}
|
||||
|
||||
output = output +
|
||||
keyStr.charAt(enc1) +
|
||||
keyStr.charAt(enc2) +
|
||||
keyStr.charAt(enc3) +
|
||||
keyStr.charAt(enc4);
|
||||
chr1 = chr2 = chr3 = "";
|
||||
enc1 = enc2 = enc3 = enc4 = "";
|
||||
} while (i < input.length);
|
||||
|
||||
return output;
|
||||
},
|
||||
|
||||
decode: function (input) {
|
||||
var output = "";
|
||||
var chr1, chr2, chr3 = "";
|
||||
var enc1, enc2, enc3, enc4 = "";
|
||||
var i = 0;
|
||||
|
||||
// remove all characters that are not A-Z, a-z, 0-9, +, /, or =
|
||||
var base64test = /[^A-Za-z0-9\+\/\=]/g;
|
||||
if (base64test.exec(input)) {
|
||||
alert("There were invalid base64 characters in the input text.\n" +
|
||||
"Valid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\n" +
|
||||
"Expect errors in decoding.");
|
||||
}
|
||||
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
|
||||
|
||||
do {
|
||||
enc1 = keyStr.indexOf(input.charAt(i++));
|
||||
enc2 = keyStr.indexOf(input.charAt(i++));
|
||||
enc3 = keyStr.indexOf(input.charAt(i++));
|
||||
enc4 = keyStr.indexOf(input.charAt(i++));
|
||||
|
||||
chr1 = (enc1 << 2) | (enc2 >> 4);
|
||||
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
|
||||
chr3 = ((enc3 & 3) << 6) | enc4;
|
||||
|
||||
output = output + String.fromCharCode(chr1);
|
||||
|
||||
if (enc3 != 64) {
|
||||
output = output + String.fromCharCode(chr2);
|
||||
}
|
||||
if (enc4 != 64) {
|
||||
output = output + String.fromCharCode(chr3);
|
||||
}
|
||||
|
||||
chr1 = chr2 = chr3 = "";
|
||||
enc1 = enc2 = enc3 = enc4 = "";
|
||||
|
||||
} while (i < input.length);
|
||||
|
||||
return output;
|
||||
}
|
||||
};
|
||||
});
|
@ -1,96 +0,0 @@
|
||||
angular.module('hz').factory
|
||||
('SatelliteErrata', ['$resource', 'Base64',
|
||||
function ($resource, Base64) {
|
||||
|
||||
var getAuthToken = function(username, password) {
|
||||
var tokenize = username + ':' + password;
|
||||
tokenize = Base64.encode(tokenize);
|
||||
return "Basic " + tokenize;
|
||||
};
|
||||
|
||||
var SatelliteErrata = $resource('https://sat-perf-05.idm.lab.bos.redhat.com/katello/api/v2/systems/:id/errata', {id: '@uuid'}, {
|
||||
get: {
|
||||
method: 'GET',
|
||||
isArray: false,
|
||||
headers: { 'Authorization': getAuthToken('admin', 'changeme') }
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return SatelliteErrata;
|
||||
|
||||
}]);
|
||||
|
||||
angular.module('hz').directive({
|
||||
satelliteErrata: [ function () {
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
// require: '^file',
|
||||
transclude: true,
|
||||
scope: {
|
||||
uuid: '='
|
||||
},
|
||||
controller: ['$scope', 'SatelliteErrata', '$http', 'Base64', 'ngTableParams', '$filter',
|
||||
function ($scope, SatelliteErrata, $http, Base64, ngTableParams, $filter) {
|
||||
|
||||
var baseUrl = 'https://sat-perf-05.idm.lab.bos.redhat.com';
|
||||
|
||||
var defaultParams = function (data) {
|
||||
var params = new ngTableParams({
|
||||
page: 1, // show first page
|
||||
count: 10 // count per page
|
||||
|
||||
}, {
|
||||
total: data.length, // length of data
|
||||
getData: function($defer, params) {
|
||||
var filteredData = params.filter() ?
|
||||
$filter('filter')(data, params.filter()) :
|
||||
data;
|
||||
var orderedData = params.sorting() ?
|
||||
$filter('orderBy')(filteredData, params.orderBy()) :
|
||||
data;
|
||||
$defer.resolve(orderedData.slice((params.page() - 1) * params.count(), params.page() * params.count()));
|
||||
}
|
||||
});
|
||||
|
||||
return params;
|
||||
};
|
||||
|
||||
$scope.errataLink = function (errata) {
|
||||
return baseUrl + '/content_hosts/' + $scope.uuid + '/errata/' + errata.errata_id;
|
||||
};
|
||||
|
||||
var payload = SatelliteErrata.get({id: $scope.uuid});
|
||||
payload.$promise.then(
|
||||
function() {
|
||||
$scope.errata = payload.results
|
||||
$scope.errataParams = defaultParams($scope.errata);
|
||||
});
|
||||
|
||||
|
||||
}],
|
||||
template:
|
||||
'<table ng-table="errataParams" class="table">\n' +
|
||||
'<tr ng-repeat="e in $data">\n' +
|
||||
'<td data-title="\'Title\'" sortable="\'title\'">\n' +
|
||||
'<a ng-href="{$errataLink(e)$}">{{e.title}}</a>\n' +
|
||||
'</td>\n' +
|
||||
'<td data-title="\'Type\'" sortable="\'type\'">{{ e.type }}</td>\n' +
|
||||
'<td data-title="\'Errata ID\'" sortable="\'type\'">{{ e.errata_id }}</td>\n' +
|
||||
'<td data-title="\'Date Issued\'" sortable="\'issued\'">{{ e.issued }}</td>\n' +
|
||||
'</tr>\n'+
|
||||
'</table>\n',
|
||||
link: function (scope, element, attrs, modelCtrl, transclude) {
|
||||
scope.modelCtrl = modelCtrl;
|
||||
scope.$transcludeFn = transclude;
|
||||
}
|
||||
};
|
||||
}]
|
||||
});
|
||||
|
||||
angular.module('hz').controller({
|
||||
ErrataController: ['$scope',
|
||||
function ($scope ) {
|
||||
|
||||
}]});
|
@ -0,0 +1,17 @@
|
||||
{% extends "infrastructure/nodes/_detail_overview.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block additional_data %}
|
||||
|
||||
{% if node.uuid %}
|
||||
<div>
|
||||
<h3>{% trans "Errata" %}</h3>
|
||||
{% if errata %}
|
||||
{{ errata.render }}
|
||||
{% else %}
|
||||
<p>{% trans "No errata found" %}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
@ -1,183 +0,0 @@
|
||||
{% extends 'infrastructure/base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include 'horizon/common/_page_header.html' with title=title %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block js %}
|
||||
{{ block.super }}
|
||||
<script src='{{ STATIC_URL }}infrastructure/js/angular/horizon.base64.js' type='text/javascript' charset='utf-8'></script>
|
||||
<script src='{{ STATIC_URL }}infrastructure/js/angular/horizon.node_errata.js' type='text/javascript' charset='utf-8'></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="node node-details">
|
||||
<div class="node-title">
|
||||
<dl>
|
||||
<span class="powerstate">{% trans "Powered" %} {{ node.power_state|default:"—" }}</span>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="node node-deployment">
|
||||
<h4>{% trans "Deployment" %}</h4>
|
||||
<dl class="clearfix">
|
||||
<dt>{% trans "Deployment Role" %}</dt>
|
||||
{% if stack and role %}
|
||||
<dd><a href="{% url 'horizon:infrastructure:overcloud:role' stack.id role.id %}">{{ role.name }}</a></dd>
|
||||
{% else %}
|
||||
<dd>—</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "Provisioning" %}</dt>
|
||||
<dd>
|
||||
{{ node.provisioning_status|default:"—" }}
|
||||
{% if node.instance_uuid %}
|
||||
<br />{{ node.instance.created }}
|
||||
{% endif %}
|
||||
</dd>
|
||||
<dt>{% trans "Image" %}</dt>
|
||||
<dd>{{ node.image_name|default:"—" }}</dd>
|
||||
<dt>{% trans "Instance UUID" %}</dt>
|
||||
<dd>{{ node.instance_uuid|default:"—" }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="node node-detail">
|
||||
<h4>{% trans "Inventory" %}</h4>
|
||||
<dl>
|
||||
<dt>{% trans "Node UUID" %}</dt>
|
||||
<dd>{{ node.uuid|default:"—" }}</dd>
|
||||
<dt>{% trans "Driver" %}</dt>
|
||||
<dd class="dropdown">
|
||||
<span class="dropdown-toggle" type="button" data-toggle="dropdown">
|
||||
{{ node.driver|default:"—" }}
|
||||
<span class="caret"></span>
|
||||
</span>
|
||||
<ul class="dropdown-menu">
|
||||
<li><dt>IP Address</dt> <dd>{{ node.driver_info.ipmi_address|default:"—" }}</dd></li>
|
||||
<li><dt>IPMI User</dt> <dd>{{ node.driver_info.ipmi_username|default:"—" }}</dd></li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt>{% trans "Network Cards" %}</dt>
|
||||
<dd class="dropdown">
|
||||
<span class="dropdown-toggle" type="button" data-toggle="dropdown">
|
||||
{{ node.addresses|length }}
|
||||
<span class="caret"></span>
|
||||
</span>
|
||||
<ul class="dropdown-menu">
|
||||
{% for address in node.addresses %}
|
||||
<li>{{ address }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</dd>
|
||||
<dt>{% trans "Registered HW" %}</dt>
|
||||
<dd>
|
||||
{{ node.cpus|default:"—" }} {% trans "CPU" %}<br />
|
||||
{{ node.memory_mb|default:"—" }} {% trans "RAM (MB)" %}<br />
|
||||
{{ node.local_gb|default:"—" }} {% trans "HDD (GB)" %}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div id="node-performance" class="node">
|
||||
<h4>{% trans "Performance and Capacity" %}</h4>
|
||||
|
||||
{% if meter_conf %}
|
||||
<br />
|
||||
{% url 'horizon:infrastructure:nodes:performance' node.uuid as node_perf_url %}
|
||||
|
||||
<div id="ceilometer-stats" class="clearfix">
|
||||
<form class="form-horizontal" id="linechart_general_form">
|
||||
<div class="control-group">
|
||||
<label for="date_options" class="control-label">{% trans "Period" %}: </label>
|
||||
<div class="controls">
|
||||
<select data-line-chart-command="select_box_change"
|
||||
id="date_options"
|
||||
name="date_options"
|
||||
class="span2">
|
||||
<option value="1" selected="selected">{% trans "Last day" %}</option>
|
||||
<option value="7">{% trans "Last week" %}</option>
|
||||
<option value="{% now 'j' %}">{% trans "Month to date" %}</option>
|
||||
<option value="15">{% trans "Last 15 days" %}</option>
|
||||
<option value="30">{% trans "Last 30 days" %}</option>
|
||||
<option value="365">{% trans "Last year" %}</option>
|
||||
<option value="other">{% trans "Other" %}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" id="date_from">
|
||||
<label for="date_from" class="control-label">{% trans "From" %}: </label>
|
||||
<div class="controls">
|
||||
<input data-line-chart-command="date_picker_change"
|
||||
type="text"
|
||||
id="date_from"
|
||||
name="date_from"
|
||||
class="span2 example"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" id="date_to">
|
||||
<label for="date_to" class="control-label">{% trans "To" %}: </label>
|
||||
<div class="controls">
|
||||
<input data-line-chart-command="date_picker_change"
|
||||
type="text"
|
||||
name="date_to"
|
||||
class="span2 example"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
if (typeof $ !== 'undefined') {
|
||||
show_hide_datepickers();
|
||||
} else {
|
||||
addHorizonLoadEvent(function() {
|
||||
show_hide_datepickers();
|
||||
});
|
||||
}
|
||||
|
||||
function show_hide_datepickers() {
|
||||
var date_options = $("#date_options");
|
||||
date_options.change(function(evt) {
|
||||
if ($(this).find("option:selected").val() == "other"){
|
||||
evt.stopPropagation();
|
||||
$("#date_from .controls input, #date_to .controls input").val('');
|
||||
$("#date_from, #date_to").show();
|
||||
} else {
|
||||
$("#date_from, #date_to").hide();
|
||||
}
|
||||
});
|
||||
if (date_options.find("option:selected").val() == "other"){
|
||||
$("#date_from, #date_to").show();
|
||||
} else {
|
||||
$("#date_from, #date_to").hide();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="node-charts" class="clearfix">
|
||||
{% for meter_label, url_part, y_max in meter_conf %}
|
||||
<div class="span3">
|
||||
{% include "infrastructure/_performance_chart.html" with label=meter_label y_max=y_max url=node_perf_url|add:"?"|add:url_part only %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% else %}
|
||||
{% trans 'Metering service is not enabled.' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if node.uuid %}
|
||||
<br />
|
||||
<br />
|
||||
<div>
|
||||
<h4>{% trans "Errata" %}</h4>
|
||||
<div ng-controller="ErrataController">
|
||||
<div satellite-errata uuid="'{{ node.uuid }}'"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
Loading…
Reference in New Issue
Block a user