webui: topology graph facet

https://fedorahosted.org/freeipa/ticket/4286

Reviewed-By: Martin Babinsky <mbabinsk@redhat.com>
This commit is contained in:
Petr Vobornik 2015-11-12 18:28:01 +01:00
parent 24fead79cb
commit 68f6c2c7dc
6 changed files with 367 additions and 3 deletions

View File

@ -173,6 +173,7 @@
"classes": [
"IPA.widget",
"*_widget",
"*Widget",
"widgets.*",
"IPA.action_panel"
]

View File

@ -220,6 +220,18 @@ var IPA = function () {
}
}));
batch.add_command(rpc.command({
entity: 'domainlevel',
method: 'get',
retry: false,
on_success: function(data, text_status, xhr) {
that.domain_level = data.result;
},
on_error: function(xhr, text_status, error_thrown) {
that.domain_level = 0;
}
}));
batch.execute();
};

View File

@ -224,6 +224,10 @@ var nav = {};
entity: 'domainlevel',
facet: 'details',
hidden: true
},
{
facet: 'topology-graph',
hidden: true
}
]
},

View File

@ -5,7 +5,12 @@
define([
'dojo/_base/lang',
'dojo/_base/declare',
'dojo/Evented',
'dojo/Stateful',
'dojo/Deferred',
'dojo/on',
'dojo/promise/all',
'dojo/when',
'./builder',
'./ipa',
'./jquery',
@ -18,10 +23,17 @@ define([
'./details',
'./facet',
'./field',
'./facets/ActionMixin',
'./facets/HeaderMixin',
'./facets/Facet',
'./topology_graph',
// plain imports
'./search',
'./entity'],
function(lang, declare, on, builder, IPA, $, menu, metadata_provider,
phases, reg, rpc, text, mod_details, mod_facet, mod_field) {
function(lang, declare, Evented, Stateful, Deferred, on, all, when,
builder, IPA, $, menu, metadata_provider, phases, reg, rpc,
text, mod_details, mod_facet, mod_field, ActionMixin,
HeaderMixin, Facet, topology_graph) {
/**
* Topology module
* @class
@ -29,13 +41,16 @@ define([
*/
var topology = IPA.topology = {
required_domain_level: 1,
search_facet_group: {
name: 'search',
label: '@i18n:tabs.topology',
facets: {
suffix_search: 'topologysuffix_search',
server_search: 'server_search',
domainlevel: 'domainlevel_details'
domainlevel: 'domainlevel_details',
topologygraph: 'topology-graph'
}
}
};
@ -330,6 +345,323 @@ topology.domainlevel_set_action = function(spec) {
};
topology.topology_graph_facet_spec = {
name: 'topology-graph',
'class': 'topology-graph container-fluid',
label: 'Topology Graph',
tab_label: 'Topology Graph',
facet_groups: [topology.search_facet_group],
facet_group: 'search',
actions: ['refresh'],
control_buttons: [
{
name: 'refresh',
label: '@i18n:buttons.refresh',
icon: 'fa-refresh'
}
],
widgets: [
{
$type: 'activity',
name: 'activity',
text: 'Working',
visible: false
},
{
$type: 'topology-graph',
name: 'topology-graph'
}
]
};
/**
* Facet containing topology graph
*
* @class
*/
topology.TopologyGraphFacet = declare([Facet, ActionMixin, HeaderMixin], {
init: function(spec) {
this.inherited(arguments);
var graph = this.get_widget('topology-graph');
on(this, 'show', lang.hitch(this, function(args) {
graph.update();
}));
},
refresh: function() {
var graph = this.get_widget('topology-graph');
graph.update();
}
});
/**
* Graph widget encapsulates and supply data to graph component
*
* Graph is show only with domain level 1.
*
* @class
*/
topology.TopologyGraphWidget = declare([Stateful, Evented], {
graph: null,
// nodes
container_node: null,
el: null,
disabled_view_el: null,
topology_view_el: null,
visualization_cnt_el: null,
_get_servers: function() {
var deferred = new Deferred();
var s_promise = rpc.command({
entity: 'server',
method: 'find',
options: {
sizelimit: 0
}
}).execute();
when(s_promise, lang.hitch(this, function(results) {
// suffices load success
var servers = results.data.result.result;
deferred.resolve(servers);
}), function(results) {
deferred.reject({
message: 'unable to load servers',
results: results
});
});
return deferred.promise;
},
_get_suffices: function() {
var deferred = new Deferred();
function get_suffices() {
return rpc.command({
entity: 'topologysuffix',
method: 'find',
options: {
sizelimit: 0
}
}).execute();
}
function get_segments(suffix_name) {
return rpc.command({
entity: 'topologysegment',
method: 'find',
args: [suffix_name],
options: {
sizelimit: 0
}
}).execute();
}
var suff_promise = get_suffices();
when(suff_promise, lang.hitch(this, function(results) {
// suffices load success
var suffices = results.data.result.result;
var segment_promises = [];
for (var i=0,l=suffices.length; i<l; i++) {
var suffix = suffices[i];
var promise = get_segments(suffix['cn'][0]);
segment_promises.push(promise);
}
all(segment_promises).then(lang.hitch(this, function(results) {
// segments load success
for (var j=0,l=results.length; j<l; j++) {
suffices[j].segments = results[j].data.result.result;
}
deferred.resolve(suffices);
}), lang.hitch(this, function(results) {
// segments load failed
deferred.reject({
message: 'unable to load segments',
results: results
});
}));
}), lang.hitch(this, function(results) {
// suffix load failed
deferred.reject({
message: 'unable to load suffices',
results: results
});
}));
return deferred.promise;
},
_transform_data: function(servers, suffices) {
var i,l;
var nodes = [];
var links = [];
var node_map = {};
function add_to_targets(source, target, link) {
if (!source.targets[target.id]) {
source.targets[target.id] = [];
}
source.targets[target.id].push(link);
source.targets[target.id].sort(function(a, b) {
return a.suffix.cn[0] > b.suffix.cn[0];
});
}
for (i=0,l=servers.length; i<l; i++) {
var server = servers[i];
var name = server.cn[0];
var node = {
id: name,
data: server,
targets: {}
};
node_map[name] = i;
nodes.push(node);
}
for (i=0,l=suffices.length; i<l; i++) {
var suffix = suffices[i];
for (var j=0,l2=suffix.segments.length; j<l2; j++) {
var segment = suffix.segments[j];
var direction = segment.iparepltoposegmentdirection[0];
var source_cn = segment.iparepltoposegmentleftnode[0];
var target_cn = segment.iparepltoposegmentrightnode[0];
// check for invalid segments - can happen if there is
// some issue with topo plugin
if (node_map[source_cn] === undefined ||
node_map[target_cn] === undefined) {
window.console.log('dangling segment: ' + segment.cn[0]);
continue; // skip invalid segments
}
var link = {
source: node_map[source_cn],
target: node_map[target_cn],
left: true,
right: true,
suffix: suffix
};
if (direction === 'left') {
link.right = false;
} else if (direction === 'right') {
link.left = false;
}
links.push(link);
var src_node = nodes[link.source];
var target_node = nodes[link.target];
add_to_targets(src_node, target_node, link);
add_to_targets(target_node, src_node, link);
}
}
var data = {
nodes: nodes,
links: links,
suffices: suffices
};
return data;
},
_get_data: function() {
var deferred = new Deferred();
var segments = this._get_suffices();
var masters = this._get_servers();
all([masters, segments]).then(lang.hitch(this, function(raw) {
var data = this._transform_data(raw[0], raw[1]);
deferred.resolve(data);
}), function(error) {
deferred.reject(error);
});
return deferred.promise;
},
update: function() {
if (IPA.domain_level < topology.required_domain_level) return;
when(this._get_data()).then(lang.hitch(this, function(data) {
if (!this.graph) {
this.graph = new topology_graph.TopoGraph({
nodes: data.nodes,
links: data.links,
suffices: data.suffices
});
this._bind_graph_events(this.graph);
this.graph.initialize(this.visualization_cnt_el);
} else {
this.graph.update(data.nodes, data.links, data.suffices);
}
}), function(error) {
IPA.notify(error.message, 'error');
});
},
_bind_graph_events: function(graph) {
},
render: function() {
this.el = $('<div/>', { 'class': this.css_class });
if (IPA.domain_level < topology.required_domain_level) {
this._render_disabled_view().appendTo(this.el);
} else {
this._render_topology_view().appendTo(this.el);
}
if (this.container_node) {
this.el.appendTo(this.container_node);
}
return this.el;
},
_render_disabled_view: function() {
if (this.disabled_view_el) return this.disabled_view_el;
this.disabled_view_el = $('<div/>', { 'class': 'disabled-view' });
var msg = text.get('@i18n:objects.topology.insufficient_domain_level');
msg = msg.replace('${domainlevel}', topology.required_domain_level);
$('<div/>')
.append(
$('<p/>', {
text: msg
})
)
.appendTo(this.disabled_view_el);
return this.disabled_view_el;
},
_render_topology_view: function() {
if (this.topology_view_el) return this.topology_view_el;
this.topology_view_el = $('<div/>', { 'class': 'topology-view' });
this.visualization_cnt_el = $('<div/>', { 'class': 'visualization' }).
appendTo(this.topology_view_el);
return this.topology_view_el;
},
_init_widgets: function() {
},
constructor: function(spec) {
lang.mixin(this, spec);
this._init_widgets();
}
});
/**
* Topology suffix entity specification object
@ -363,6 +695,8 @@ topology.domainlevel_spec = make_domainlevel_spec();
topology.register = function() {
var e = reg.entity;
var a = reg.action;
var fa = reg.facet;
var w = reg.widget;
e.register({type: 'topologysuffix', spec: topology.suffix_spec});
e.register({type: 'topologysegment', spec: topology.segment_spec});
@ -370,6 +704,13 @@ topology.register = function() {
e.register({type: 'domainlevel', spec: topology.domainlevel_spec});
a.register('domainlevel_set', topology.domainlevel_set_action);
w.register('topology-graph', topology.TopologyGraphWidget);
fa.register({
type: 'topology-graph',
ctor: topology.TopologyGraphFacet,
spec: topology.topology_graph_facet_spec
});
};
phases.on('registration', topology.register);

View File

@ -572,6 +572,7 @@
"topology": {
"segment_details": "Segment details",
"replication_config": "Replication configuration",
"insufficient_domain_level" : "Managed topology requires minimal domain level ${domainlevel}"
},
"trust": {
"account": "Account",
@ -966,6 +967,10 @@
},
"summary": null,
"value": "ad"
},
{
"error": null,
"result": 1
}
]
}

View File

@ -719,6 +719,7 @@ class i18n_messages(Command):
"topology": {
"segment_details": _("Segment details"),
"replication_config": _("Replication configuration"),
"insufficient_domain_level" : _("Managed topology requires minimal domain level ${domainlevel}"),
},
"trust": {
"account": _("Account"),