| @@ -0,0 +1,30 @@ | ||
| 1 | +.agent-diagram { | |
| 2 | + position: relative; | |
| 3 | + z-index: auto; | |
| 4 | + | |
| 5 | +  svg.diagram { | |
| 6 | + position: absolute; | |
| 7 | + z-index: 1; | |
| 8 | + } | |
| 9 | + | |
| 10 | +  .overlay-container { | |
| 11 | + position: absolute; | |
| 12 | + top: 0; | |
| 13 | + left: 0; | |
| 14 | + z-index: auto; | |
| 15 | + | |
| 16 | +    .overlay { | |
| 17 | + position: relative; | |
| 18 | + z-index: auto; | |
| 19 | + width: 100%; | |
| 20 | + height: 100%; | |
| 21 | + | |
| 22 | +      .badge { | |
| 23 | + position: absolute; | |
| 24 | + display: none; | |
| 25 | + color: white !important; | |
| 26 | + z-index: 2; | |
| 27 | + } | |
| 28 | + } | |
| 29 | + } | |
| 30 | +} | 
| @@ -6,7 +6,7 @@ module DotHelper | ||
| 6 | 6 | dot.close_write | 
| 7 | 7 | dot.read | 
| 8 | 8 | } rescue false) | 
| 9 | - svg.html_safe | |
| 9 | + decorate_svg(svg, agents).html_safe | |
| 10 | 10 | else | 
| 11 | 11 |        tag('img', src: URI('https://chart.googleapis.com/chart').tap { |uri| | 
| 12 | 12 | uri.query = URI.encode_www_form(cht: 'gv', chl: agents_dot(agents)) | 
| @@ -161,4 +161,85 @@ module DotHelper | ||
| 161 | 161 | } | 
| 162 | 162 | } | 
| 163 | 163 | end | 
| 164 | + | |
| 165 | + def decorate_svg(xml, agents) | |
| 166 | +    svg = Nokogiri::XML(xml).at('svg') | |
| 167 | + | |
| 168 | +    Nokogiri::HTML::Document.new.tap { |doc| | |
| 169 | +      doc << root = Nokogiri::XML::Node.new('div', doc) { |div| | |
| 170 | + div['class'] = 'agent-diagram' | |
| 171 | + } | |
| 172 | + | |
| 173 | + svg['class'] = 'diagram' | |
| 174 | + | |
| 175 | + root << svg | |
| 176 | +      root << overlay_container = Nokogiri::XML::Node.new('div', doc) { |div| | |
| 177 | + div['class'] = 'overlay-container' | |
| 178 | +        div['style'] = "width: #{svg['width']}; height: #{svg['height']}" | |
| 179 | + } | |
| 180 | +      overlay_container << overlay = Nokogiri::XML::Node.new('div', doc) { |div| | |
| 181 | + div['class'] = 'overlay' | |
| 182 | + } | |
| 183 | + | |
| 184 | +      svg.xpath('//xmlns:g[@class="node"]', svg.namespaces).each { |node| | |
| 185 | +        agent_id = (node.xpath('./xmlns:title/text()', svg.namespaces).to_s[/\d+/] or next).to_i | |
| 186 | +        agent = agents.find { |a| a.id == agent_id } | |
| 187 | + | |
| 188 | + count = agent.events_count | |
| 189 | + next unless count && count > 0 | |
| 190 | + | |
| 191 | +        overlay << Nokogiri::XML::Node.new('a', doc) { |badge| | |
| 192 | + badge['id'] = id = 'b%d' % agent_id | |
| 193 | + badge['class'] = 'badge' | |
| 194 | + badge['href'] = events_path(agent: agent) | |
| 195 | + badge['target'] = '_blank' | |
| 196 | + badge.content = count.to_s | |
| 197 | + | |
| 198 | + node['data-badge-id'] = id | |
| 199 | + | |
| 200 | +          badge << Nokogiri::XML::Node.new('span', doc) { |label| | |
| 201 | + # a dummy label only to obtain the background color | |
| 202 | + label['class'] = [ | |
| 203 | + 'label', | |
| 204 | + if agent.disabled? | |
| 205 | + 'label-warning' | |
| 206 | + elsif agent.working? | |
| 207 | + 'label-success' | |
| 208 | + else | |
| 209 | + 'label-danger' | |
| 210 | + end | |
| 211 | +            ].join(' ') | |
| 212 | + label['style'] = 'display: none'; | |
| 213 | + } | |
| 214 | + } | |
| 215 | + } | |
| 216 | + | |
| 217 | +      root << Nokogiri::XML::Node.new('script', doc) { |script| | |
| 218 | + script.content = <<-SCRIPT | |
| 219 | +$(function () { | |
| 220 | +  var svg = document.querySelector('.agent-diagram svg.diagram'); | |
| 221 | +  var overlay = document.querySelector('.agent-diagram .overlay'); | |
| 222 | +  var getTopLeft = function (node) { | |
| 223 | + var bbox = node.getBBox(); | |
| 224 | + var point = svg.createSVGPoint(); | |
| 225 | + point.x = bbox.x + bbox.width; | |
| 226 | + point.y = bbox.y; | |
| 227 | + return point.matrixTransform(node.getCTM()); | |
| 228 | + }; | |
| 229 | +  $(svg).find('g.node[data-badge-id]').each(function () { | |
| 230 | + var tl = getTopLeft(this) | |
| 231 | +    $('#' + this.getAttribute('data-badge-id'), overlay).each(function () { | |
| 232 | + var badge = $(this); | |
| 233 | +      badge.css({ | |
| 234 | + left: tl.x - badge.outerWidth() * (2/3), | |
| 235 | + top: tl.y - badge.outerHeight() * (1/3), | |
| 236 | +        'background-color': badge.find('.label').css('background-color') | |
| 237 | + }).show(); | |
| 238 | + }); | |
| 239 | + }); | |
| 240 | +}) | |
| 241 | + SCRIPT | |
| 242 | + } | |
| 243 | +    }.at('div.agent-diagram').to_s | |
| 244 | + end | |
| 164 | 245 | end |