summaryrefslogtreecommitdiffstats
path: root/install
diff options
context:
space:
mode:
Diffstat (limited to 'install')
-rw-r--r--install/ui/less/widgets.less59
-rw-r--r--install/ui/src/freeipa/topology_graph.js347
2 files changed, 382 insertions, 24 deletions
diff --git a/install/ui/less/widgets.less b/install/ui/less/widgets.less
index 56a310462..9ee79b9cb 100644
--- a/install/ui/less/widgets.less
+++ b/install/ui/less/widgets.less
@@ -150,42 +150,61 @@ tbody:empty { display: none; }
.topology-view {
svg {
- background-color: #FFF;
- cursor: default;
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- -o-user-select: none;
- user-select: none;
+ background-color: #FFF;
+ cursor: default;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ -o-user-select: none;
+ user-select: none;
}
+
path.link {
- fill: none;
- stroke-width: 4px;
- cursor: pointer;
+ fill: none;
+ stroke: #000;
+ stroke-width: 4px;
+ cursor: pointer;
+ .dragline {
+ pointer-events: none;
+ }
}
- .marker {
- stroke: rgba(0, 0, 0);
+ .plus {
+ font-size: .9em;
+ font-family: FontAwesome;
+ fill: #fff;
+ }
+
+ .adder_label {
+ font-weight: bold;
}
- path.link.selected {
- stroke-dasharray: 10,2;
+ path.adder {
+ cursor: pointer;
+ }
+
+ .selected {
+ stroke-dasharray: 10,2;
+ }
+
+ .marker {
+ stroke: rgba(0, 0, 0);
}
circle.node {
- stroke-width: 1.5px;
- cursor: pointer;
+ stroke-width: 1.5px;
+ cursor: pointer;
}
text {
- font: 16px sans-serif;
- pointer-events: none;
+ font: 16px sans-serif;
+ pointer-events: none;
}
text.id {
- text-anchor: middle;
- font-weight: bold;
+ text-anchor: middle;
+ font-weight: bold;
}
}
diff --git a/install/ui/src/freeipa/topology_graph.js b/install/ui/src/freeipa/topology_graph.js
index 332411f94..ce2ebeaff 100644
--- a/install/ui/src/freeipa/topology_graph.js
+++ b/install/ui/src/freeipa/topology_graph.js
@@ -29,13 +29,21 @@ var topology_graph = {
topology_graph.TopoGraph = declare([Evented], {
width: 960,
height: 500,
+ _adder_anim_duration: 200,
+ _adder_inner_radius: 15,
+ _adder_outer_radius: 30,
_colors: d3.scale.category10(),
_svg : null,
_path: null,
_circle: null,
+ _create_agreement: null,
_selected_link: null,
_mousedown_link: null,
+ _source_node: null,
+ _source_node_html: null,
+ _target_node: null,
+ _drag_line: null,
/**
* Nodes - IPA servers
@@ -115,31 +123,89 @@ topology_graph.TopoGraph = declare([Evented], {
node.x = Number(this._get_local_storage_attr(node.id, 'x'));
node.y = Number(this._get_local_storage_attr(node.id, 'y'));
}
+ node.ca_adder = d3.svg.arc()
+ .innerRadius(this._adder_inner_radius)
+ .outerRadius(this._adder_inner_radius)
+ .startAngle(2 * (Math.PI/180))
+ .endAngle(178 * (Math.PI/180));
+
+ node.domain_adder = d3.svg.arc()
+ .innerRadius(this._adder_inner_radius)
+ .outerRadius(this._adder_inner_radius)
+ .startAngle(182 * (Math.PI/180))
+ .endAngle(358 * (Math.PI/180));
+
+ node.drag_mode = false;
}
this._init_layout();
this._define_shapes();
// handles to link and node element groups
+ // the order of adding shapes is important because of order of showing
+ // them
this._path = this._svg.append('svg:g')
.classed('shapes', true)
.selectAll('path');
+ this._drag_line = this._svg.append('svg:g')
+ .classed('shapes', true)
+ .append('path')
+ .style('marker-end', 'url(#end-arrow)')
+ .attr('class', 'link dragline hidden')
+ .attr('d', 'M0,0L0,0')
+ .on('click', function() {
+ d3.event.preventDefault();
+ d3.event.stopPropagation();
+
+ this._create_agreement = false;
+ this.reset_mouse_vars();
+
+ this._drag_line
+ .classed('hidden', true)
+ .style('marker-end', '');
+
+ this.restart();
+
+ }.bind(this));
+
this._circle = this._svg.append('svg:g')
.classed('shapes', true)
.selectAll('g');
this._selected_link = null;
- this._mouseup_node = null;
this._mousedown_link = null;
+ this._selected_node = null;
+ this._source_node = null;
+ this._target_node = null;
this.restart();
},
_create_svg: function(container) {
+ var self = this;
+
this._svg = d3.select(container[0]).
append('svg').
attr('width', this.width).
- attr('height', this.height);
+ attr('height', this.height).
+ on('mousemove', mousemove);
+
+ function mousemove(d) {
+ if (!self._source_node && !self._create_agreement) return;
+
+ var translate = self._get_stored_transformation();
+ var x = self._source_node.x;
+ var y = self._source_node.y;
+
+ var mouse_x = x + d3.mouse(self._source_node_html)[0];
+ var mouse_y = y + d3.mouse(self._source_node_html)[1];
+
+ // update drag line
+ self._drag_line.attr('d', 'M' + x + ',' + y + 'L' + mouse_x + ',' + mouse_y);
+
+ self.restart();
+ }
+
},
_init_layout: function() {
@@ -342,11 +408,28 @@ topology_graph.TopoGraph = declare([Evented], {
this._append_suffix_hint(suffix, x, y);
y += 30;
}
-
- this._circle_color = this._colors(1);
},
/**
+ * Returns lenght of string with set class in pixels
+ */
+ _count_string_size: function(str, cls) {
+ if (!str) return 0;
+
+ cls = cls || '';
+
+ var node = this._svg.append('text')
+ .classed(cls, true)
+ .text(str);
+
+ var length = node.node().getComputedTextLength();
+
+ node.remove();
+
+ return length;
+ },
+
+ /**
* Restart the simulation to reflect changes in data/state
*/
restart: function() {
@@ -422,6 +505,8 @@ topology_graph.TopoGraph = declare([Evented], {
.on("dragend", dragended);
function dragstarted(d) {
+ d.drag_mode = true;
+ hide_semicircles.bind(this, d)();
d3.event.sourceEvent.stopPropagation();
// Store the original value of fixed and set the node fixed.
d.fixed = d.fixed << 1;
@@ -437,11 +522,213 @@ topology_graph.TopoGraph = declare([Evented], {
}
function dragended(d) {
+ d.drag_mode = false;
// Restore old value of fixed.
d.fixed = d.fixed >> 1;
self._layout.resume();
}
+ function add_labels(type, color, adder_group) {
+ var label_radius = 3;
+
+ var plus = adder_group
+ .append('text')
+ .classed('plus', true)
+ .classed(type + '_plus', true)
+ .text('\uf067');
+
+ var label = adder_group.append('path')
+ .attr('id', type + '_label');
+
+ if (type === 'ca') {
+ plus.attr('dx', '18')
+ .attr('dy', '4');
+ var adder_label = adder_group.append('text')
+ .append('textPath')
+ .classed('adder_label', true)
+ .style('fill', color)
+ .attr('xlink:href', '#' + type + '_label')
+ .text(type);
+
+ var str_size = self._count_string_size(type, 'adder_label');
+ var str_translate = str_size + self._adder_outer_radius + 3;
+ label.attr('d', 'M 33 3 L ' + str_translate + ' 3');
+
+ adder_group.insert('rect', 'text')
+ .attr('x', '33')
+ .attr('y', '-11')
+ .attr('rx', label_radius)
+ .attr('ry', label_radius)
+ .attr('width', str_size)
+ .attr('height', '18')
+ .style("fill", "white");
+ }
+ else {
+ plus.attr('dx', '-26')
+ .attr('dy', '4');
+ adder_label = adder_group.append('text')
+ .append('textPath')
+ .classed('adder_label', true)
+ .style('fill', color)
+ .attr('xlink:href', '#' + type + '_label')
+ .text(type);
+
+ str_size = self._count_string_size(type, 'adder_label');
+ str_translate = str_size + self._adder_outer_radius + 3;
+ label.attr('d', 'M -' + str_translate + ' 3 L -33 3');
+
+ adder_group.insert('rect', 'text')
+ .attr('x', '-'+str_translate)
+ .attr('y', '-11')
+ .attr('rx', label_radius)
+ .attr('ry', label_radius)
+ .attr('width', str_size)
+ .attr('height', '18')
+ .style('fill', 'white');
+ }
+ }
+
+ function create_semicircle(d, type) {
+ var color = d3.rgb(self._colors(type)).toString();
+ var adder_group = d3.select(this).select('g');
+ var scale = '1.05';
+
+ adder_group.append("path")
+ .classed(type+'_adder', true)
+ .classed('adder', true)
+ .attr("d", d[type + '_adder'])
+ .attr("fill", color)
+ .on('mouseover', function(d) {
+ window.clearTimeout(d._timeout_hide);
+
+ d3.select(this).attr('transform', 'scale('+scale+')');
+ adder_group.select('text.' + type + '_plus')
+ .attr('transform', 'scale('+scale+')');
+ })
+ .on('mouseout', function(d) {
+ d3.select(this).attr('transform', '');
+ adder_group.select('text.' + type + '_plus')
+ .attr('transform', '');
+ })
+ .on('click', function(d) {
+ d3.event.preventDefault();
+ d3.event.stopPropagation();
+ self.emit('link-selected', { link: null });
+
+ hide_semicircles.bind(this, d)();
+
+ // select node
+ if (!self._source_node) {
+ self._source_node = d;
+ self._source_node_html = d3.select(this)
+ .select('circle').node();
+ self._create_agreement = true;
+ }
+
+ self._selected_link = null;
+
+ var translate = self._get_stored_transformation();
+ var x = self._source_node.x;
+ var y = self._source_node.y;
+
+ // add position of node + translation of whole graph + relative
+ // position of the mouse
+ var mouse_x = d.x + d3.mouse(this)[0];
+ var mouse_y = d.y + d3.mouse(this)[1];
+
+ // reposition drag line
+ self._drag_line
+ .style('marker-end', 'url(#' + type + '-end-arrow)')
+ .style('stroke', color)
+ .classed('hidden', false)
+ .attr('suffix', type)
+ .attr('d', 'M' + x + ',' + y +
+ 'L' + mouse_x + ',' + mouse_y);
+
+ self.restart();
+ }.bind(this))
+ .on('mousedown.drag', function() {
+ d3.event.preventDefault();
+ d3.event.stopPropagation();
+ })
+ .transition()
+ .duration(self._adder_anim_duration)
+ .attr("d", d[type + '_adder']
+ .outerRadius(self._adder_outer_radius))
+ .each('end', function() {
+ add_labels(type, color, adder_group);
+ });
+ }
+
+ function show_semicircles(d) {
+
+ if(!d3.select(this).select('g path').empty()) return;
+
+ if (!d.drag_mode && !self._create_agreement) {
+
+ // append invisible circle which covers spaces between node
+ // and adders it prevents hiding adders when mouse is on the space
+ d3.select(this).append('g')
+ .append('circle')
+ .attr('r', self._adder_outer_radius)
+ .style('opacity', 0);
+ create_semicircle.bind(this, d, 'ca')();
+
+ create_semicircle.bind(this, d, 'domain')();
+
+ //move the identification text
+ d3.select(this).select('text')
+ .transition()
+ .duration(self._adder_anim_duration)
+ .attr('dy', '45');
+ }
+ }
+
+ function hide_semicircles(d) {
+ var curr_nod = d3.select(this);
+ curr_nod.selectAll('.plus,.adder_label,rect')
+ .transition()
+ .ease('exp')
+ .duration(100)
+ .style('font-size', '0px')
+ .remove();
+ curr_nod.select('path.domain_adder')
+ .transition()
+ .attr("d", d.domain_adder
+ .outerRadius(self._adder_inner_radius))
+ .duration(self._adder_anim_duration);
+ curr_nod.select('path.ca_adder')
+ .transition()
+ .attr("d", d.ca_adder
+ .outerRadius(self._adder_inner_radius))
+ .duration(self._adder_anim_duration);
+ curr_nod.select('g')
+ .transition()
+ .duration(self._adder_anim_duration)
+ .remove();
+ curr_nod.select('text')
+ .transition()
+ .attr('dy', '30')
+ .duration(self._adder_anim_duration);
+ }
+
+ function is_suffix_shown(suffix) {
+ var links = self._source_node.targets[self._target_node.id];
+
+ if (!links) return false;
+
+ for (var i=0, l=links.length; i<l; i++) {
+ var link = links[i];
+ if (link.suffix.cn[0] === suffix) {
+ self._selected_link = link;
+ self.emit('link-selected', { link: link });
+ return true;
+ }
+ }
+
+ return false;
+ }
+
// add new nodes
var g = this._circle.enter()
.append('svg:g')
@@ -453,6 +740,49 @@ topology_graph.TopoGraph = declare([Evented], {
d.fixed = d.fixed ^ 1;
self._layout.resume();
})
+ .on('mouseover', function(d) {
+ window.clearTimeout(d._timeout_hide);
+ show_semicircles.bind(this, d)();
+ d3.select('circle.cover').classed('cover', true);
+ })
+ .on('mouseout', function(d) {
+ d._timeout_hide = window.setTimeout(hide_semicircles
+ .bind(this, d), 50);
+ })
+ .on('click', function(d) {
+ if (!self._create_agreement) return;
+
+ d3.event.preventDefault();
+ d3.event.stopPropagation();
+
+ if (self._source_node !== d) {
+ self._target_node = d;
+ var source = self._source_node;
+ var target = self._target_node;
+ var suffix = self._drag_line.attr('suffix');
+ var direction = 'left';
+ var link = {
+ source: source,
+ target: target,
+ suffix: suffix,
+ left: false,
+ right: false
+ };
+
+ if (!is_suffix_shown(suffix)) {
+ link[direction] = true;
+ self.emit('add-agreement', link);
+ }
+ }
+
+ self._drag_line
+ .classed('hidden', true)
+ .attr('suffix', '')
+ .style('marker-end', '');
+
+ self.restart();
+ self.reset_mouse_vars();
+ })
.call(drag);
g.append('svg:circle')
@@ -488,6 +818,15 @@ topology_graph.TopoGraph = declare([Evented], {
.attr("transform", transform);
},
+ reset_mouse_vars: function() {
+ this._source_node = null;
+ this._source_node_html = null;
+ this._target_node = null;
+ this._mousedown_link = null;
+ this._create_agreement = null;
+
+ },
+
resize: function(height, width) {
if (!(isNaN(height) || isNaN(width))) {
this.height = height < 0 ? 0 : height;