├── examples ├── acidashboard │ ├── .gitignore │ ├── widgets │ │ ├── text │ │ │ ├── text.coffee │ │ │ ├── text.html │ │ │ └── text.scss │ │ ├── clock │ │ │ ├── clock.html │ │ │ ├── clock.coffee │ │ │ └── clock.scss │ │ ├── iframe │ │ │ ├── iframe.html │ │ │ ├── iframe.scss │ │ │ └── iframe.coffee │ │ ├── image │ │ │ ├── image.html │ │ │ ├── image.coffee │ │ │ └── image.scss │ │ ├── google_pie │ │ │ ├── google_pie.html │ │ │ ├── google_pie.scss │ │ │ └── google_pie.coffee │ │ ├── list │ │ │ ├── list.coffee │ │ │ ├── list.html │ │ │ └── list.scss │ │ ├── graph │ │ │ ├── graph.html │ │ │ ├── graph.coffee │ │ │ └── graph.scss │ │ ├── comments │ │ │ ├── comments.html │ │ │ ├── comments.coffee │ │ │ └── comments.scss │ │ ├── meter │ │ │ ├── meter.html │ │ │ ├── meter.coffee │ │ │ └── meter.scss │ │ └── number │ │ │ ├── number.html │ │ │ ├── number.coffee │ │ │ └── number.scss │ ├── Gemfile │ ├── public │ │ ├── favicon.ico │ │ └── 404.html │ ├── assets │ │ ├── images │ │ │ └── logo.png │ │ ├── fonts │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.ttf │ │ │ └── fontawesome-webfont.woff │ │ ├── javascripts │ │ │ ├── application.coffee │ │ │ ├── gridster │ │ │ │ └── jquery.leanModal.min.js │ │ │ ├── dashing.gridster.coffee │ │ │ └── jquery.knob.js │ │ └── stylesheets │ │ │ ├── jquery.gridster.css │ │ │ ├── application.scss │ │ │ └── font-awesome.css │ ├── config.ru │ ├── dashboards │ │ ├── layout.erb │ │ └── aci.erb │ ├── README.md │ └── jobs │ │ └── apic.rb └── epgbdsubnet.rb ├── spec ├── spec_helper.rb ├── spec_naming.rb ├── spec_basic.rb └── spec_loader.rb ├── lib ├── acirb │ ├── version.rb │ ├── logging.rb │ ├── query.rb │ ├── loader.rb │ ├── naming.rb │ ├── events.rb │ ├── mo.rb │ └── restclient.rb └── acirb.rb ├── gems ├── acirb-1.0.4.1.gem ├── acirb-1.0.4.2.gem ├── acirb-1.0.4h.gem ├── acirb-1.1.1.1.gem ├── acirb-1.1.2.1.gem ├── acirb-1.1.2.2.gem ├── acirb-1.2.1.0.gem └── acirb-1.2.2.H.gem ├── .gitignore ├── env.sh ├── test.sh ├── package.sh ├── updatepysdk.sh ├── LICENSE ├── acirb.spec ├── README.md └── genrubyfrompy.py /examples/acidashboard/.gitignore: -------------------------------------------------------------------------------- 1 | *DS_STORE 2 | history.yml 3 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | SimpleCov.start 3 | -------------------------------------------------------------------------------- /lib/acirb/version.rb: -------------------------------------------------------------------------------- 1 | module ACIrb 2 | VERSION = '1.2.2.H' 3 | end 4 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/text/text.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Text extends Dashing.Widget 2 | -------------------------------------------------------------------------------- /gems/acirb-1.0.4.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datacenter/acirb/HEAD/gems/acirb-1.0.4.1.gem -------------------------------------------------------------------------------- /gems/acirb-1.0.4.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datacenter/acirb/HEAD/gems/acirb-1.0.4.2.gem -------------------------------------------------------------------------------- /gems/acirb-1.0.4h.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datacenter/acirb/HEAD/gems/acirb-1.0.4h.gem -------------------------------------------------------------------------------- /gems/acirb-1.1.1.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datacenter/acirb/HEAD/gems/acirb-1.1.1.1.gem -------------------------------------------------------------------------------- /gems/acirb-1.1.2.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datacenter/acirb/HEAD/gems/acirb-1.1.2.1.gem -------------------------------------------------------------------------------- /gems/acirb-1.1.2.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datacenter/acirb/HEAD/gems/acirb-1.1.2.2.gem -------------------------------------------------------------------------------- /gems/acirb-1.2.1.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datacenter/acirb/HEAD/gems/acirb-1.2.1.0.gem -------------------------------------------------------------------------------- /gems/acirb-1.2.2.H.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datacenter/acirb/HEAD/gems/acirb-1.2.2.H.gem -------------------------------------------------------------------------------- /examples/acidashboard/widgets/clock/clock.html: -------------------------------------------------------------------------------- 1 |

2 |

-------------------------------------------------------------------------------- /examples/acidashboard/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'dashing' 4 | 5 | gem 'acirb' 6 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/iframe/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/image/image.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib/acirb/model/*.rb 2 | lib/acirb/autoloadmap.rb 3 | lib/acirb/lookup.rb 4 | *.gem 5 | coverage 6 | pysdk 7 | env.sh 8 | -------------------------------------------------------------------------------- /env.sh: -------------------------------------------------------------------------------- 1 | export IP="apic" 2 | export APIC_URI="https://${IP}" 3 | export APIC_USERNAME="admin" 4 | export APIC_PASSWORD="password" 5 | -------------------------------------------------------------------------------- /examples/acidashboard/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datacenter/acirb/HEAD/examples/acidashboard/public/favicon.ico -------------------------------------------------------------------------------- /examples/acidashboard/widgets/google_pie/google_pie.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |
-------------------------------------------------------------------------------- /examples/acidashboard/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datacenter/acirb/HEAD/examples/acidashboard/assets/images/logo.png -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ -z $1 ] ; then 3 | source env.sh 4 | else 5 | source $1 6 | fi 7 | echo "APIC target = $APIC_URI" 8 | rspec -Ilib -fd spec/*.rb 9 | -------------------------------------------------------------------------------- /examples/acidashboard/assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datacenter/acirb/HEAD/examples/acidashboard/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /examples/acidashboard/assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datacenter/acirb/HEAD/examples/acidashboard/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /examples/acidashboard/assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datacenter/acirb/HEAD/examples/acidashboard/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /examples/acidashboard/widgets/iframe/iframe.scss: -------------------------------------------------------------------------------- 1 | .widget-iframe { 2 | padding: 3px 0px 0px 0px !important; 3 | 4 | iframe { 5 | width: 100%; 6 | height: 100%; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/list/list.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.List extends Dashing.Widget 2 | ready: -> 3 | if @get('unordered') 4 | $(@node).find('ol').remove() 5 | else 6 | $(@node).find('ul').remove() 7 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/graph/graph.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/text/text.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | 7 |

8 | -------------------------------------------------------------------------------- /lib/acirb.rb: -------------------------------------------------------------------------------- 1 | require 'acirb/autoloadmap' 2 | require 'acirb/loader' 3 | require 'acirb/lookup' 4 | require 'acirb/mo' 5 | require 'acirb/naming' 6 | require 'acirb/restclient' 7 | require 'acirb/version' 8 | require 'acirb/query' 9 | require 'acirb/events' 10 | 11 | module ACIrb 12 | end 13 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/comments/comments.html: -------------------------------------------------------------------------------- 1 |

2 |
3 |

4 |

5 |
6 | 7 |

8 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/image/image.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Image extends Dashing.Widget 2 | 3 | ready: -> 4 | # This is fired when the widget is done being rendered 5 | 6 | onData: (data) -> 7 | # Handle incoming data 8 | # You can access the html node of this widget with `@node` 9 | # Example: $(@node).fadeOut().fadeIn() will make the node flash each time data comes in. 10 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/meter/meter.html: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

8 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/iframe/iframe.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Iframe extends Dashing.Widget 2 | 3 | ready: -> 4 | # This is fired when the widget is done being rendered 5 | 6 | onData: (data) -> 7 | # Handle incoming data 8 | # You can access the html node of this widget with `@node` 9 | # Example: $(@node).fadeOut().fadeIn() will make the node flash each time data comes in. 10 | -------------------------------------------------------------------------------- /package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | # 3 | # This script cleans up directories that shouldn't be packaged and then zips up everything else 4 | # this is for Paul's use 5 | # 6 | basename=$(pwd | sed -e 's/^.*\///g') 7 | echo 'Cleaning up' 8 | mv pysdk /tmp 9 | rm -fv ../${basename}.zip 10 | cd .. 11 | echo 'Zipping' 12 | zip -r ${basename} ${basename}/* 13 | cd ${basename} 14 | echo 'Uncleaning up' 15 | mv /tmp/pysdk . 16 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/number/number.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | 7 |

8 | 9 |

10 | 11 |

12 | -------------------------------------------------------------------------------- /examples/acidashboard/config.ru: -------------------------------------------------------------------------------- 1 | require 'dashing' 2 | 3 | configure do 4 | set :auth_token, 'YOUR_AUTH_TOKEN' 5 | 6 | helpers do 7 | def protected! 8 | # Put any authentication code you want in here. 9 | # This method is run before accessing any resource. 10 | end 11 | end 12 | end 13 | 14 | map Sinatra::Application.assets_prefix do 15 | run Sinatra::Application.sprockets 16 | end 17 | 18 | run Sinatra::Application -------------------------------------------------------------------------------- /examples/acidashboard/widgets/meter/meter.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Meter extends Dashing.Widget 2 | 3 | @accessor 'value', Dashing.AnimatedValue 4 | 5 | constructor: -> 6 | super 7 | @observe 'value', (value) -> 8 | $(@node).find(".meter").val(value).trigger('change') 9 | 10 | ready: -> 11 | meter = $(@node).find(".meter") 12 | meter.attr("data-bgcolor", meter.css("background-color")) 13 | meter.attr("data-fgcolor", meter.css("color")) 14 | meter.knob() 15 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/clock/clock.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Clock extends Dashing.Widget 2 | 3 | ready: -> 4 | setInterval(@startTime, 500) 5 | 6 | startTime: => 7 | today = new Date() 8 | 9 | h = today.getHours() 10 | m = today.getMinutes() 11 | s = today.getSeconds() 12 | m = @formatTime(m) 13 | s = @formatTime(s) 14 | @set('time', h + ":" + m + ":" + s) 15 | @set('date', today.toDateString()) 16 | 17 | formatTime: (i) -> 18 | if i < 10 then "0" + i else i -------------------------------------------------------------------------------- /lib/acirb/logging.rb: -------------------------------------------------------------------------------- 1 | require 'logger' 2 | 3 | module Logging 4 | def logger 5 | @logger ||= Logging.logger_for(self.class.name) 6 | end 7 | 8 | @loggers = {} 9 | 10 | class << self 11 | def logger_for(classname) 12 | @loggers[classname] ||= configure_logger_for(classname) 13 | end 14 | 15 | def configure_logger_for(classname) 16 | logger = Logger.new(STDOUT) 17 | logger.progname = classname 18 | logger.level = Logger::INFO 19 | logger 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /updatepysdk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This script will ssh into the APIC IP below, and download the pysdk directories 4 | # trying a number of different locations, hoping it finds at least one that works 5 | # 6 | # Use this after downloading this package and if you don't have the correct pysdk 7 | # 8 | if [ -z $1 ] ; then 9 | source env.sh 10 | else 11 | source $1 12 | fi 13 | mkdir -p pysdk 14 | cd pysdk 15 | ssh $APIC_USERNAME@$IP \ 16 | 'cd /controller/ishell || 17 | cd /controller/mgmt ; 18 | tar zcf - insieme' | \ 19 | tar zxf - 20 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/clock/clock.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #dc5945; 5 | 6 | // ---------------------------------------------------------------------------- 7 | // Widget-clock styles 8 | // ---------------------------------------------------------------------------- 9 | .widget-clock { 10 | 11 | background-color: $background-color; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/image/image.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #4b4b4b; 5 | 6 | // ---------------------------------------------------------------------------- 7 | // Widget-image styles 8 | // ---------------------------------------------------------------------------- 9 | .widget-image { 10 | 11 | background-color: $background-color; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/list/list.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |
    4 |
  1. 5 | 6 | 7 |
  2. 8 |
9 | 10 | 16 | 17 |

18 |

19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015 Cisco Systems, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/comments/comments.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Comments extends Dashing.Widget 2 | 3 | @accessor 'quote', -> 4 | "“#{@get('current_comment')?.body}”" 5 | 6 | ready: -> 7 | @currentIndex = 0 8 | @commentElem = $(@node).find('.comment-container') 9 | @nextComment() 10 | @startCarousel() 11 | 12 | onData: (data) -> 13 | @currentIndex = 0 14 | 15 | startCarousel: -> 16 | setInterval(@nextComment, 8000) 17 | 18 | nextComment: => 19 | comments = @get('comments') 20 | if comments 21 | @commentElem.fadeOut => 22 | @currentIndex = (@currentIndex + 1) % comments.length 23 | @set 'current_comment', comments[@currentIndex] 24 | @commentElem.fadeIn() 25 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/google_pie/google_pie.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #fb9618; 5 | 6 | // ---------------------------------------------------------------------------- 7 | // Widget-GoogleGauge styles 8 | // ---------------------------------------------------------------------------- 9 | .widget-google-pie { 10 | 11 | background-color: $background-color; 12 | position: relative; 13 | 14 | .title { 15 | position: absolute; 16 | top: 5px; 17 | left: 0px; 18 | right: 0px; 19 | } 20 | 21 | .chart { 22 | position: absolute; 23 | left: 0px; 24 | top: 0px; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /examples/acidashboard/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | This Dashboard doesn't exist. 5 | 17 | 18 | 19 | 20 | 21 |
22 |

Drats! That Dashboard doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/number/number.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Number extends Dashing.Widget 2 | @accessor 'current', Dashing.AnimatedValue 3 | 4 | @accessor 'difference', -> 5 | if @get('last') 6 | last = parseInt(@get('last')) 7 | current = parseInt(@get('current')) 8 | if last != 0 9 | diff = Math.abs(Math.round((current - last) / last * 100)) 10 | "#{diff}%" 11 | else 12 | "" 13 | 14 | @accessor 'arrow', -> 15 | if @get('last') 16 | if parseInt(@get('current')) > parseInt(@get('last')) then 'icon-arrow-up' else 'icon-arrow-down' 17 | 18 | onData: (data) -> 19 | if data.status 20 | # clear existing "status-*" classes 21 | $(@get('node')).attr 'class', (i,c) -> 22 | c.replace /\bstatus-\S+/g, '' 23 | # add new class 24 | $(@get('node')).addClass "status-#{data.status}" 25 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/text/text.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #ec663c; 5 | 6 | $title-color: rgba(255, 255, 255, 0.7); 7 | $moreinfo-color: rgba(255, 255, 255, 0.7); 8 | 9 | // ---------------------------------------------------------------------------- 10 | // Widget-text styles 11 | // ---------------------------------------------------------------------------- 12 | .widget-text { 13 | 14 | background-color: $background-color; 15 | 16 | .title { 17 | color: $title-color; 18 | } 19 | 20 | .more-info { 21 | color: $moreinfo-color; 22 | } 23 | 24 | .updated-at { 25 | color: rgba(255, 255, 255, 0.7); 26 | } 27 | 28 | 29 | &.large h3 { 30 | font-size: 65px; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /acirb.spec: -------------------------------------------------------------------------------- 1 | require './lib/acirb/version' 2 | 3 | Gem::Specification.new do |s| 4 | s.name = 'acirb' 5 | s.version = ACIrb::VERSION 6 | s.date = Time.new().strftime('%Y-%m-%d') 7 | s.summary = 'ACIrb - Cisco APIC Ruby SDK' 8 | s.description = 'Cisco APIC Ruby SDK' 9 | s.authors = ['Paul Lesiak', 'Jerrod Carpenter'] 10 | s.email = 'palesiak@cisco.com' 11 | s.files = Dir.glob('{lib}/*') 12 | s.files = Dir.glob('{lib}/**/*') 13 | s.homepage = 'http://github.com/datacenter/acirb' 14 | s.license = 'Private' 15 | s.add_runtime_dependency 'httpclient', '~> 2.6', '>= 2.6.0.1' 16 | s.add_runtime_dependency 'nokogiri', '~> 1.6', '>= 1.6.0' 17 | s.add_runtime_dependency 'json', '~> 1.8', '>= 1.8.0' 18 | s.add_runtime_dependency 'websocket', '~> 1.0' 19 | s.add_development_dependency 'simplecov', '~> 0' 20 | s.add_development_dependency 'rspec', '~> 0' 21 | end 22 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/comments/comments.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #eb9c3c; 5 | 6 | $title-color: rgba(255, 255, 255, 0.7); 7 | $moreinfo-color: rgba(255, 255, 255, 0.7); 8 | 9 | // ---------------------------------------------------------------------------- 10 | // Widget-comment styles 11 | // ---------------------------------------------------------------------------- 12 | .widget-comments { 13 | 14 | background-color: $background-color; 15 | 16 | .title { 17 | color: $title-color; 18 | margin-bottom: 15px; 19 | } 20 | 21 | .name { 22 | padding-left: 5px; 23 | } 24 | 25 | .comment-container { 26 | display: none; 27 | } 28 | 29 | .more-info { 30 | color: $moreinfo-color; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/meter/meter.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #9c4274; 5 | 6 | $title-color: rgba(255, 255, 255, 0.7); 7 | $moreinfo-color: rgba(255, 255, 255, 0.3); 8 | 9 | $meter-background: darken($background-color, 15%); 10 | 11 | // ---------------------------------------------------------------------------- 12 | // Widget-meter styles 13 | // ---------------------------------------------------------------------------- 14 | .widget-meter { 15 | 16 | background-color: $background-color; 17 | 18 | input.meter { 19 | background-color: $meter-background; 20 | color: #fff; 21 | } 22 | 23 | .title { 24 | color: $title-color; 25 | } 26 | 27 | .more-info { 28 | color: $moreinfo-color; 29 | } 30 | 31 | .updated-at { 32 | color: rgba(0, 0, 0, 0.3); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /examples/acidashboard/assets/javascripts/application.coffee: -------------------------------------------------------------------------------- 1 | # dashing.js is located in the dashing framework 2 | # It includes jquery & batman for you. 3 | #= require dashing.js 4 | 5 | #= require_directory . 6 | #= require_tree ../../widgets 7 | 8 | console.log("Yeah! The dashboard has started!") 9 | 10 | Dashing.on 'ready', -> 11 | Dashing.widget_margins ||= [5, 5] 12 | Dashing.widget_base_dimensions ||= [300, 360] 13 | Dashing.numColumns ||= 4 14 | 15 | contentWidth = (Dashing.widget_base_dimensions[0] + Dashing.widget_margins[0] * 2) * Dashing.numColumns 16 | 17 | Batman.setImmediate -> 18 | $('.gridster').width(contentWidth) 19 | $('.gridster ul:first').gridster 20 | widget_margins: Dashing.widget_margins 21 | widget_base_dimensions: Dashing.widget_base_dimensions 22 | avoid_overlapped_widgets: !Dashing.customGridsterLayout 23 | draggable: 24 | stop: Dashing.showGridsterInstructions 25 | start: -> Dashing.currentWidgetPositions = Dashing.getWidgetPositions() 26 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/number/number.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #47bbb3; 5 | $value-color: #fff; 6 | 7 | $title-color: rgba(255, 255, 255, 0.7); 8 | $moreinfo-color: rgba(255, 255, 255, 0.7); 9 | 10 | // ---------------------------------------------------------------------------- 11 | // Widget-number styles 12 | // ---------------------------------------------------------------------------- 13 | .widget-number { 14 | 15 | background-color: $background-color; 16 | 17 | .title { 18 | color: $title-color; 19 | } 20 | 21 | .value { 22 | color: $value-color; 23 | } 24 | 25 | .change-rate { 26 | font-weight: 500; 27 | font-size: 30px; 28 | color: $value-color; 29 | } 30 | 31 | .more-info { 32 | color: $moreinfo-color; 33 | } 34 | 35 | .updated-at { 36 | color: rgba(0, 0, 0, 0.3); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /examples/acidashboard/assets/javascripts/gridster/jquery.leanModal.min.js: -------------------------------------------------------------------------------- 1 | // leanModal v1.1 by Ray Stone - http://finelysliced.com.au 2 | // Dual licensed under the MIT and GPL 3 | 4 | (function($){$.fn.extend({leanModal:function(options){var defaults={top:100,overlay:0.5,closeButton:null};var overlay=$("
");$("body").append(overlay);options=$.extend(defaults,options);return this.each(function(){var o=options;$(this).click(function(e){var modal_id=$(this).attr("href");$("#lean_overlay").click(function(){close_modal(modal_id)});$(o.closeButton).click(function(){close_modal(modal_id)});var modal_height=$(modal_id).outerHeight();var modal_width=$(modal_id).outerWidth(); 5 | $("#lean_overlay").css({"display":"block",opacity:0});$("#lean_overlay").fadeTo(200,o.overlay);$(modal_id).css({"display":"block","position":"fixed","opacity":0,"z-index":11000,"left":50+"%","margin-left":-(modal_width/2)+"px","top":o.top+"px"});$(modal_id).fadeTo(200,1);e.preventDefault()})});function close_modal(modal_id){$("#lean_overlay").fadeOut(200);$(modal_id).css({"display":"none"})}}})})(jQuery); 6 | -------------------------------------------------------------------------------- /examples/acidashboard/dashboards/layout.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <%= yield_content(:title) %> 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | <%= yield %> 22 |
23 | 24 | <% if development? %> 25 |
26 |

Paste the following at the top of <%= params[:dashboard] %>.erb

27 | 28 |
29 | Save this layout 30 | <% end %> 31 | 32 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/graph/graph.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Graph extends Dashing.Widget 2 | 3 | @accessor 'current', -> 4 | return @get('displayedValue') if @get('displayedValue') 5 | points = @get('points') 6 | if points 7 | points[points.length - 1].y 8 | 9 | ready: -> 10 | container = $(@node).parent() 11 | # Gross hacks. Let's fix this. 12 | width = (Dashing.widget_base_dimensions[0] * container.data("sizex")) + Dashing.widget_margins[0] * 2 * (container.data("sizex") - 1) 13 | height = (Dashing.widget_base_dimensions[1] * container.data("sizey")) 14 | @graph = new Rickshaw.Graph( 15 | element: @node 16 | width: width 17 | height: height 18 | renderer: @get("graphtype") 19 | series: [ 20 | { 21 | color: "#fff", 22 | data: [{x:0, y:0}] 23 | } 24 | ] 25 | ) 26 | 27 | @graph.series[0].data = @get('points') if @get('points') 28 | 29 | x_axis = new Rickshaw.Graph.Axis.Time(graph: @graph) 30 | y_axis = new Rickshaw.Graph.Axis.Y(graph: @graph, tickFormat: Rickshaw.Fixtures.Number.formatKMBT) 31 | @graph.render() 32 | 33 | onData: (data) -> 34 | if @graph 35 | @graph.series[0].data = data.points 36 | @graph.render() 37 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/google_pie/google_pie.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.GooglePie extends Dashing.Widget 2 | 3 | ready: -> 4 | container = $(@node).parent() 5 | # Gross hacks. Let's fix this. 6 | width = (Dashing.widget_base_dimensions[0] * container.data("sizex")) + Dashing.widget_margins[0] * 2 * (container.data("sizex") - 1) 7 | height = (Dashing.widget_base_dimensions[1] * container.data("sizey")) 8 | 9 | colors = null 10 | if @get('colors') 11 | colors = @get('colors').split(/\s*,\s*/) 12 | 13 | @chart = new google.visualization.PieChart($(@node).find(".chart")[0]) 14 | @options = 15 | height: height 16 | width: width 17 | colors: colors 18 | is3D: @get('is_3d') 19 | pieHole: @get('pie_hole') 20 | pieStartAngle: @get('pie_start_angle') 21 | backgroundColor: 'transparent' 22 | legend: 23 | position: @get('legend_position') 24 | 25 | if @get('slices') 26 | @data = google.visualization.arrayToDataTable @get('slices') 27 | else 28 | @data = google.visualization.arrayToDataTable [] 29 | 30 | @chart.draw @data, @options 31 | 32 | onData: (data) -> 33 | if @chart 34 | @data = google.visualization.arrayToDataTable data.slices 35 | @chart.draw @data, @options 36 | -------------------------------------------------------------------------------- /examples/acidashboard/README.md: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | - On Ubuntu, you will need to run the following to get some dependencies 3 | ``` 4 | sudo apt-get install ruby-dev ruby-libxml zlib1g-dev nodejs 5 | ``` 6 | 7 | # Installation 8 | - Make sure you have ruby gems installed 9 | - https://rubygems.org/pages/download 10 | - Next, install dashing following the steps on the website 11 | - http://shopify.github.io/dashing/ 12 | 13 | Or, just run the following at your terminal. 14 | **Note**: you may need to prefix this with sudo depending on your installation 15 | ``` 16 | gem install dashing bundler 17 | gem install acirb 18 | ``` 19 | - Change into the acirb/examples/acidashboard folder 20 | ``` 21 | cd acirb/examples/acidashboard 22 | ``` 23 | - Modify jobs/apic.erb to include your APIC IP address and credentials with an account that can query the objects being polled 24 | ``` 25 | apicuri = 'https://apic' 26 | username = 'admin' 27 | password = 'password' 28 | ``` 29 | - Run the "bundle" command 30 | ``` 31 | bundle 32 | ``` 33 | # Running 34 | - Run "dashing start" to start the dashboard 35 | ``` 36 | dashing start 37 | ``` 38 | - Access your local web server at http://localhost:3030 39 | 40 | # More information 41 | Check out http://shopify.github.com/dashing for more information about dashing 42 | -------------------------------------------------------------------------------- /examples/acidashboard/assets/javascripts/dashing.gridster.coffee: -------------------------------------------------------------------------------- 1 | #= require_directory ./gridster 2 | 3 | # This file enables gridster integration (http://gridster.net/) 4 | # Delete it if you'd rather handle the layout yourself. 5 | # You'll miss out on a lot if you do, but we won't hold it against you. 6 | 7 | Dashing.gridsterLayout = (positions) -> 8 | Dashing.customGridsterLayout = true 9 | positions = positions.replace(/^"|"$/g, '') 10 | positions = $.parseJSON(positions) 11 | widgets = $("[data-row^=]") 12 | for widget, index in widgets 13 | $(widget).attr('data-row', positions[index].row) 14 | $(widget).attr('data-col', positions[index].col) 15 | 16 | Dashing.getWidgetPositions = -> 17 | $(".gridster ul:first").gridster().data('gridster').serialize() 18 | 19 | Dashing.showGridsterInstructions = -> 20 | newWidgetPositions = Dashing.getWidgetPositions() 21 | 22 | unless JSON.stringify(newWidgetPositions) == JSON.stringify(Dashing.currentWidgetPositions) 23 | Dashing.currentWidgetPositions = newWidgetPositions 24 | $('#save-gridster').slideDown() 25 | $('#gridster-code').text(" 26 | 31 | ") 32 | 33 | $ -> 34 | $('#save-gridster').leanModal() 35 | 36 | $('#save-gridster').click -> 37 | $('#save-gridster').slideUp() 38 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/list/list.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #12b0c5; 5 | $value-color: #fff; 6 | 7 | $title-color: rgba(255, 255, 255, 0.7); 8 | $label-color: rgba(255, 255, 255, 0.7); 9 | $moreinfo-color: rgba(255, 255, 255, 0.7); 10 | 11 | // ---------------------------------------------------------------------------- 12 | // Widget-list styles 13 | // ---------------------------------------------------------------------------- 14 | .widget-list { 15 | 16 | background-color: $background-color; 17 | vertical-align: top; 18 | 19 | .title { 20 | color: $title-color; 21 | } 22 | 23 | ol, ul { 24 | margin: 0 15px; 25 | text-align: left; 26 | font-size: 12px; 27 | color: $label-color; 28 | } 29 | 30 | ol { 31 | list-style-position: inside; 32 | } 33 | 34 | li { 35 | margin-bottom: 5px; 36 | } 37 | 38 | .list-nostyle { 39 | list-style: none; 40 | } 41 | 42 | .label { 43 | color: $label-color; 44 | } 45 | 46 | .value { 47 | float: right; 48 | margin-left: 12px; 49 | font-weight: 600; 50 | color: $value-color; 51 | } 52 | 53 | .updated-at { 54 | color: rgba(0, 0, 0, 0.3); 55 | } 56 | 57 | .more-info { 58 | color: $moreinfo-color; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /examples/acidashboard/widgets/graph/graph.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #dc5945; 5 | 6 | $title-color: rgba(255, 255, 255, 0.7); 7 | $moreinfo-color: rgba(255, 255, 255, 0.3); 8 | $tick-color: rgba(0, 0, 0, 0.4); 9 | 10 | 11 | // ---------------------------------------------------------------------------- 12 | // Widget-graph styles 13 | // ---------------------------------------------------------------------------- 14 | .widget-graph { 15 | 16 | background-color: $background-color; 17 | position: relative; 18 | 19 | 20 | svg { 21 | position: absolute; 22 | opacity: 0.4; 23 | fill-opacity: 0.4; 24 | left: 0px; 25 | top: 0px; 26 | } 27 | 28 | .title, .value { 29 | position: relative; 30 | z-index: 99; 31 | } 32 | 33 | .title { 34 | color: $title-color; 35 | } 36 | 37 | .more-info { 38 | color: $moreinfo-color; 39 | font-weight: 600; 40 | font-size: 20px; 41 | margin-top: 0; 42 | } 43 | 44 | .x_tick { 45 | position: absolute; 46 | bottom: 0; 47 | .title { 48 | font-size: 20px; 49 | color: $tick-color; 50 | opacity: 0.5; 51 | padding-bottom: 3px; 52 | } 53 | } 54 | 55 | .y_ticks { 56 | font-size: 20px; 57 | fill: $tick-color; 58 | fill-opacity: 1; 59 | } 60 | 61 | .domain { 62 | display: none; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /examples/acidashboard/assets/stylesheets/jquery.gridster.css: -------------------------------------------------------------------------------- 1 | /*! gridster.js - v0.1.0 - 2012-08-14 2 | * http://gridster.net/ 3 | * Copyright (c) 2012 ducksboard; Licensed MIT */ 4 | 5 | .gridster { 6 | position:relative; 7 | } 8 | 9 | .gridster > * { 10 | margin: 0 auto; 11 | -webkit-transition: height .4s; 12 | -moz-transition: height .4s; 13 | -o-transition: height .4s; 14 | -ms-transition: height .4s; 15 | transition: height .4s; 16 | } 17 | 18 | .gridster .gs_w{ 19 | z-index: 2; 20 | position: absolute; 21 | } 22 | 23 | .ready .gs_w:not(.preview-holder) { 24 | -webkit-transition: opacity .3s, left .3s, top .3s; 25 | -moz-transition: opacity .3s, left .3s, top .3s; 26 | -o-transition: opacity .3s, left .3s, top .3s; 27 | transition: opacity .3s, left .3s, top .3s; 28 | } 29 | 30 | .gridster .preview-holder { 31 | z-index: 1; 32 | position: absolute; 33 | background-color: #fff; 34 | border-color: #fff; 35 | opacity: 0.3; 36 | } 37 | 38 | .gridster .player-revert { 39 | z-index: 10!important; 40 | -webkit-transition: left .3s, top .3s!important; 41 | -moz-transition: left .3s, top .3s!important; 42 | -o-transition: left .3s, top .3s!important; 43 | transition: left .3s, top .3s!important; 44 | } 45 | 46 | .gridster .dragging { 47 | z-index: 10!important; 48 | -webkit-transition: all 0s !important; 49 | -moz-transition: all 0s !important; 50 | -o-transition: all 0s !important; 51 | transition: all 0s !important; 52 | } 53 | 54 | /* Uncomment this if you set helper : "clone" in draggable options */ 55 | /*.gridster .player { 56 | opacity:0; 57 | }*/ -------------------------------------------------------------------------------- /examples/epgbdsubnet.rb: -------------------------------------------------------------------------------- 1 | # EPG BD and Subnet relationship mapper 2 | # Will query for all EPGs in the fabric, and find the associated BD and subnets 3 | # available in that BD, and then print the results as a JSON document 4 | # 5 | # palesiak@cisco.com 6 | # 7 | 8 | require 'acirb' 9 | require 'json' 10 | 11 | apicuri = 'https://apic' 12 | username = 'admin' 13 | password = 'password' 14 | 15 | def class_query_children(options = {}) 16 | rest = options[:rest] 17 | cls = options[:cls] 18 | parent_dn = options[:parent_dn] 19 | child_class = options[:child_class] 20 | 21 | if parent_dn 22 | dnq = ACIrb::DnQuery.new(parent_dn) 23 | dnq.class_filter = cls 24 | dnq.query_target = 'subtree' 25 | dnq.subtree = 'children' 26 | dnq.subtree_class_filter = child_class if child_class 27 | return rest.query(dnq) 28 | else 29 | cq = ACIrb::ClassQuery.new(cls) 30 | cq.subtree = 'children' 31 | cq.subtree_class_filter = child_class if child_class 32 | return rest.query(cq) 33 | end 34 | end 35 | 36 | def find_matching_relation(relation_list, relation_prop, target_list, target_prop) 37 | matched_targets = [] 38 | relation_list.each do |mo| 39 | rel_prop = mo.send(relation_prop) 40 | target_list.each do |targetmo| 41 | tgt_prop = targetmo.send(target_prop) 42 | matched_targets.push(targetmo) if rel_prop == tgt_prop 43 | end 44 | end 45 | end 46 | 47 | rest = ACIrb::RestClient.new(url: apicuri, user: username, 48 | password: password) 49 | 50 | rest.format = 'json' 51 | tenants = rest.lookupByClass('fvTenant') 52 | aps = rest.lookupByClass('fvAp') 53 | 54 | epgs = class_query_children(rest: rest, cls: 'fvAEPg', 55 | child_class: 'fvRsBd,tagInst') 56 | 57 | bds = class_query_children(rest: rest, cls: 'fvBD', child_class: 'fvSubnet') 58 | 59 | epg_array = [] 60 | epgs.each do |epg| 61 | ap = epg.parent 62 | tenant = ap.parent 63 | find_matching_relation(epg.rsbd, 'tDn', bds, 'dn').each do |bd| 64 | epg_hash = { 65 | 'tenant' => tenant.name, 66 | 'ap' => ap.name, 67 | 'epg' => epg.name, 68 | 'bd' => bd.dn 69 | } 70 | epg_array.push(epg_hash) 71 | end 72 | end 73 | 74 | puts JSON.pretty_generate(epg_array) 75 | -------------------------------------------------------------------------------- /lib/acirb/query.rb: -------------------------------------------------------------------------------- 1 | require 'acirb/restclient' 2 | 3 | # rubocop:disable ClassLength 4 | # rubocop:disable FormatString 5 | module ACIrb 6 | # Generic Query Interface 7 | class Query 8 | attr_accessor :subtree, :class_filter, :query_target, :subtree_class_filter, 9 | :prop_filter, :subtree_prop_filter, :subtree_include, 10 | :page_size, :include_prop, :subscribe, :sort_order, :page 11 | 12 | def make_options 13 | query_params = [] 14 | 15 | query_params.push('rsp-subtree=%s' % @subtree) \ 16 | if @subtree 17 | query_params.push('target-subtree-class=%s' % @class_filter) \ 18 | if @class_filter 19 | query_params.push('query-target=%s' % @query_target) \ 20 | if @query_target 21 | query_params.push('rsp-subtree-class=%s' % @subtree_class_filter) \ 22 | if @subtree_class_filter 23 | query_params.push('query-target-filter=%s' % @prop_filter) \ 24 | if @prop_filter 25 | query_params.push('rsp-subtree-filter=%s' % @subtree_prop_filter) \ 26 | if @subtree_prop_filter 27 | query_params.push('rsp-subtree-include=%s' % @subtree_include) \ 28 | if @subtree_include 29 | query_params.push('page-size=%s' % @page_size) \ 30 | if @page_size 31 | query_params.push('page=%s' % @page) \ 32 | if @page 33 | query_params.push('order-by=%s' % @sort_order) \ 34 | if @sort_order 35 | query_params.push('rsp-prop-include=%s' % @include_prop) \ 36 | if @include_prop 37 | query_params.push('subscription=yes') \ 38 | if @subscribe 39 | 40 | if query_params.length > 0 41 | '?' + query_params.join('&') 42 | else 43 | '' 44 | end 45 | end 46 | end 47 | 48 | # Dn Query 49 | class DnQuery < Query 50 | attr_accessor :dn 51 | def initialize(dn) 52 | @dn = dn 53 | end 54 | 55 | def uri(format) 56 | '/api/mo/%s.%s%s' % [@dn, format, make_options] 57 | end 58 | end 59 | 60 | # Class Query 61 | class ClassQuery < Query 62 | attr_accessor :cls 63 | def initialize(cls) 64 | @cls = cls 65 | end 66 | 67 | def uri(format) 68 | '/api/class/%s.%s%s' % [@cls, format, make_options] 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /lib/acirb/loader.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'nokogiri' 3 | 4 | module ACIrb 5 | class Loader 6 | def self.load_xml_str(xml_str) 7 | doc = Nokogiri::XML(xml_str) 8 | load_xml(doc.root) 9 | end 10 | 11 | def self.load_xml(doc) 12 | dn_str = doc.attributes['dn'].to_s 13 | 14 | parent_mo = ACIrb::Naming.get_mo_from_dn(dn_str).parent if dn_str 15 | 16 | get_mo_from_xml(parent_mo, doc) 17 | end 18 | 19 | def self.get_mo_from_xml(parent_mo, element) 20 | class_name = element.name 21 | unless ACIrb::CLASSMAP.include?(class_name) 22 | fail 'Could not find class "%s" defined in "%s"' % \ 23 | [class_name, element.to_s] 24 | end 25 | 26 | mo = ACIrb.const_get(ACIrb::CLASSMAP[class_name]) 27 | 28 | create_attr = {} 29 | element.attributes.each do |k, v| 30 | create_attr[k.to_s] = v.to_s 31 | end 32 | create_attr[:mark_dirty] = false 33 | mo = mo.new(parent_mo, create_attr) 34 | 35 | element.elements.each do |e| 36 | mo.add_child(get_mo_from_xml(mo, e)) 37 | end 38 | 39 | mo 40 | end 41 | 42 | def self.load_json_str(json_data) 43 | doc = JSON.parse(json_data, symbolize_names: false) 44 | load_json(doc) 45 | end 46 | 47 | def self.load_json(doc) 48 | load_hash(doc) 49 | end 50 | 51 | def self.load_hash(hash) 52 | top = hash.keys[0] 53 | attrib = hash[top]['attributes'] || {} 54 | dn_str = attrib['dn'] 55 | 56 | parent_mo = ACIrb::Naming.get_mo_from_dn(dn_str).parent \ 57 | unless dn_str.nil? 58 | 59 | get_mo_from_hash(parent_mo, hash) 60 | end 61 | 62 | def self.get_mo_from_hash(parent_mo, hash) 63 | class_name = hash.keys[0] 64 | values = hash[class_name] 65 | 66 | unless ACIrb::CLASSMAP.include?(class_name) 67 | fail 'Could not find class "%s" defined in "%s"' % [class_name, hash] 68 | end 69 | 70 | mo = ACIrb.const_get(ACIrb::CLASSMAP[class_name]) 71 | 72 | create_attr = {} 73 | (values['attributes'] || {}).each do |propName, propVal| 74 | create_attr[propName.to_s] = propVal 75 | end 76 | 77 | create_attr[:mark_dirty] = false 78 | mo = mo.new(parent_mo, create_attr) 79 | 80 | (values['children'] || []).each do |child| 81 | mo.add_child(get_mo_from_hash(mo, child)) 82 | end 83 | 84 | mo 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /spec/spec_naming.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | SimpleCov.start 3 | require 'acirb' 4 | 5 | RSpec.describe 'ACIrb Naming' do 6 | it 'Verifies simple function of splitting method' do 7 | dn_str = 'uni' 8 | parts = ACIrb::Naming.split_dn_str(dn_str) 9 | expect(parts[0]).to eq('uni') 10 | end 11 | 12 | it 'Verifies nested function of splitting method' do 13 | dn_str = 'uni/tn-common/ap-ap/epg-test' 14 | parts = ACIrb::Naming.split_dn_str(dn_str) 15 | expect(parts[0]).to eq('uni') 16 | expect(parts[1]).to eq('tn-common') 17 | expect(parts[2]).to eq('ap-ap') 18 | expect(parts[3]).to eq('epg-test') 19 | end 20 | 21 | it 'Verifies nested function of splitting where an rn is where it doesnt belong' do 22 | dn_str = 'uni/tn-common/epg-test' 23 | parts = ACIrb::Naming.split_dn_str(dn_str) 24 | expect(parts[0]).to eq('uni') 25 | expect(parts[1]).to eq('tn-common') 26 | expect(parts[2]).to eq('epg-test') 27 | end 28 | 29 | it 'Simple: Creates an Mo from a Dn string' do 30 | mo = ACIrb::Naming.get_mo_from_dn('uni') 31 | expect(mo.class_name).to eq('pol.Uni') 32 | end 33 | 34 | it 'Nested: Creates an Mo from a Dn string' do 35 | mo = ACIrb::Naming.get_mo_from_dn('uni/tn-common') 36 | expect(mo.class_name).to eq('fv.Tenant') 37 | expect(mo.attributes['name']).to eq('common') 38 | end 39 | 40 | it 'Deep Nested: Creates an Mo from a Dn string' do 41 | mo = ACIrb::Naming.get_mo_from_dn('uni/tn-common/ap-app1/epg-epg1') 42 | expect(mo.class_name).to eq('fv.AEPg') 43 | expect(mo.attributes['name']).to eq('epg1') 44 | end 45 | 46 | it 'Deeper Nested: Creates an Mo from a Dn string' do 47 | mo = ACIrb::Naming.get_mo_from_dn('uni/tn-common/ap-app1/epg-epg1/rspathAtt-[test]') 48 | expect(mo.class_name).to eq('fv.RsPathAtt') 49 | expect(mo.parent.class_name).to eq('fv.AEPg') 50 | expect(mo.parent.parent.class_name).to eq('fv.Ap') 51 | expect(mo.parent.parent.parent.class_name).to eq('fv.Tenant') 52 | expect(mo.parent.parent.parent.parent.class_name).to eq('pol.Uni') 53 | end 54 | 55 | it 'Nested: Have a rn where it doesn\'t belong' do 56 | expect { ACIrb::Naming.get_mo_from_dn('uni/epg-common') }.to raise_error(RuntimeError) 57 | end 58 | 59 | it 'Complex Dn' do 60 | dn = 'uni/tn-ASA-F5-TEST/ap-qbo/epg-app/FI_C-qbo-app-compl-G-web-F-Firewall-N-AccessList' 61 | mo = ACIrb::Naming.get_mo_from_dn(dn) 62 | expect(mo.dn).to eq(dn) 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /examples/acidashboard/dashboards/aci.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %>Cisco ACI Dashboard<% end %> 2 | 3 | 6 | 12 | 13 | 14 |
15 | 53 |
54 | -------------------------------------------------------------------------------- /lib/acirb/naming.rb: -------------------------------------------------------------------------------- 1 | module ACIrb 2 | class Naming 3 | def self.split_outside_brackets(dn_str, splitChar) 4 | depth = 0 5 | place = 0 6 | last_split = 0 7 | pieces = [] 8 | while place < dn_str.length 9 | c = dn_str[place] 10 | if c == '[' 11 | depth += 1 12 | elsif c == ']' 13 | depth -= 1 14 | end 15 | if depth == 0 && c == splitChar && place != 0 16 | pieces.push(dn_str[last_split..place]) 17 | last_split = place + 1 18 | end 19 | place += 1 20 | end 21 | pieces.push(dn_str[last_split..place - 1]) if place != last_split 22 | 23 | pieces 24 | end 25 | 26 | def self.strip_last_delimiter(str, delim) 27 | if str[-1] == delim 28 | return str[0..-2] 29 | else 30 | return str 31 | end 32 | end 33 | 34 | def self.strip_outer_brackets(str) 35 | if str[0] == '[' && str[-1] == ']' 36 | return str[1..-2] 37 | else 38 | return str 39 | end 40 | end 41 | 42 | def self.split_dn_str(dn_str) 43 | rns = [] 44 | split_outside_brackets(dn_str, '/').each do |rn| 45 | rns.push(strip_last_delimiter(rn, '/')) 46 | end 47 | rns 48 | end 49 | 50 | def self.split_rn_str(rn_str, delims) 51 | rn_pieces = [] 52 | delims.each_with_index do |(delim, _has_prop), index| 53 | begin_delim = rn_str.split(delim) 54 | 55 | if index == delims.length - 1 56 | name_prop = begin_delim[1] 57 | else 58 | rn_str = begin_delim[1] 59 | end_delim = rn_str.split(delims[index + 1][0]) 60 | name_prop = end_delim[0] 61 | end 62 | 63 | rn_pieces.push(delim) 64 | rn_pieces.push(name_prop) 65 | end 66 | rn_pieces 67 | end 68 | 69 | def self.get_mo_from_dn(dn_str) 70 | rns = split_dn_str(dn_str) 71 | mo = ACIrb::TopRoot.new(nil) 72 | rns.each do |rn| 73 | mo = get_mo_from_rn(mo, rn) 74 | end 75 | mo 76 | end 77 | 78 | def self.get_mo_from_rn(parent_mo, rn_str) 79 | mo = get_class_from_child_prefix(parent_mo, rn_str).new(parent_mo) 80 | return mo if mo.naming_props.length == 0 81 | rn_pieces = split_rn_str(rn_str, mo.prefixes) 82 | rn_values = rn_pieces.values_at(*(1..rn_pieces.length - 1).step(2)) 83 | mo.naming_props.each_with_index do |prop_name, index| 84 | prop_val = rn_values[index] 85 | mo.set_prop(prop_name.to_s, strip_outer_brackets(prop_val)) 86 | end 87 | mo 88 | end 89 | 90 | def self.match_prefix_in_list(rn_str, prefix_list) 91 | matches = false 92 | prefix_match = '' 93 | prefix_list.each do |prefix| 94 | if rn_str.start_with?(prefix) 95 | matches = true 96 | prefix_match = prefix 97 | break 98 | end 99 | end 100 | 101 | return prefix_match if matches 102 | 103 | nil 104 | end 105 | 106 | def self.get_class_from_child_prefix(parent_mo, rn_str) 107 | prefix_to_class = {} 108 | parent_mo.child_classes.each do |c| 109 | cls = ACIrb.const_get(c) 110 | prefix_to_class[cls.prefix] = cls 111 | end 112 | 113 | lpm = prefix_to_class.keys.sort_by { |x| -1 * x.length } 114 | prefix_match = match_prefix_in_list(rn_str, lpm) 115 | 116 | if prefix_match 117 | return prefix_to_class[prefix_match] 118 | else 119 | fail 'Unknown child prefix ' + rn_str + ' in container class ' + 120 | parent_mo.class.to_s 121 | end 122 | end 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /lib/acirb/events.rb: -------------------------------------------------------------------------------- 1 | require 'acirb/restclient' 2 | require 'websocket' 3 | require 'socket' 4 | require 'openssl' 5 | require 'nokogiri' 6 | require 'json' 7 | 8 | # rubocop:disable ClassLength 9 | module ACIrb 10 | # Event channel interface 11 | 12 | class EventChannel 13 | attr_accessor :rest 14 | 15 | class ApicWebSocketRecvTimeout < StandardError 16 | end 17 | 18 | class WebSocketNoHandshake < StandardError 19 | end 20 | 21 | def initialize(rest, _options = {}) 22 | @rest = rest 23 | 24 | uri = URI.parse(@rest.baseurl) 25 | 26 | if uri.scheme == 'https' 27 | scheme = 'wss' 28 | secure = true 29 | else 30 | scheme = 'ws' 31 | secure = false 32 | end 33 | 34 | url = '%s://%s/socket%s' % [scheme, uri.host, rest.auth_cookie] 35 | 36 | @handshake = WebSocket::Handshake::Client.new(url: url) 37 | @frame = WebSocket::Frame::Incoming::Server.new(version: @handshake.version) 38 | 39 | @socket = TCPSocket.new(@handshake.host, uri.port) 40 | 41 | if secure 42 | puts 'connecting over secure websocket' 43 | ctx = OpenSSL::SSL::SSLContext.new 44 | 45 | ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE unless rest.verify 46 | 47 | ssl_sock = OpenSSL::SSL::SSLSocket.new(@socket, ctx) 48 | ssl_sock.sync_close = true 49 | ssl_sock.connect 50 | 51 | @transport_socket = @socket 52 | @socket = ssl_sock 53 | else 54 | @transport_socket = nil 55 | end 56 | 57 | @socket.write(@handshake.to_s) 58 | @socket.flush 59 | 60 | loop do 61 | data = @socket.getc 62 | next if data.nil? 63 | 64 | @handshake << data 65 | 66 | if @handshake.finished? 67 | fail @handshake.error.to_s unless @handshake.valid? 68 | @handshaked = true 69 | break 70 | end 71 | end 72 | end 73 | 74 | def send(data, type = :text) 75 | fail WebSocketNoHandshake unless @handshaked 76 | 77 | data = WebSocket::Frame::Outgoing::Client.new( 78 | version: @handshake.version, 79 | data: data, 80 | type: type 81 | ).to_s 82 | @socket.write data 83 | @socket.flush 84 | end 85 | 86 | def receive(timeout = nil) 87 | fail WebSocketNoHandshake unless @handshaked 88 | 89 | readable, writable, error = IO.select([@socket], nil, nil, timeout) 90 | if readable 91 | begin 92 | data = @socket.read_nonblock(1024) 93 | rescue Errno::EAGAIN, Errno::EWOULDBLOCK, (IO::WaitReadable if defined?(IO::WaitReadable)) => e 94 | puts '%s, retrying' % e 95 | retry 96 | end 97 | else 98 | fail ApicWebSocketRecvTimeout, 'Timeout for websocket read' 99 | end 100 | @frame << data 101 | 102 | messages = [] 103 | while message = @frame.next 104 | if message.type === :ping 105 | send(message.data, :pong) 106 | return messages 107 | end 108 | messages << message.to_s 109 | end 110 | 111 | events = [] 112 | messages.each do |msg| 113 | events += MoEvent.parse_event(self, msg.to_s) 114 | end 115 | events 116 | end 117 | 118 | def close 119 | @socket.close 120 | end 121 | end 122 | 123 | class MoEvent 124 | def initialize(_options = {}) 125 | end 126 | 127 | def self.parse_event(event_channel, event_str) 128 | subscription = nil 129 | events = [] 130 | 131 | if event_channel.rest.format == 'xml' 132 | doc = Nokogiri::XML(event_str) 133 | subscription_id = doc.at_css('imdata')['subscriptionId'] 134 | puts event_str 135 | doc.root.elements.each do |xml_obj| 136 | event = { 137 | type: xml_obj.attributes['status'].to_s, 138 | properties: Hash[xml_obj.attributes.map { |k, str| [k, str.value.to_s] }], 139 | class: xml_obj.name, 140 | subscription_id: subscription_id 141 | } 142 | events.push(event) 143 | end 144 | elsif event_channel.rest.format == 'json' 145 | doc = JSON.parse(event_str, symbolize_names: false) 146 | subscription_id = doc['subscriptionId'] 147 | imdata = doc['imdata'] 148 | imdata.each do |obj| 149 | cls = obj.keys[0] 150 | event = { 151 | type: obj[cls]['attributes']['status'].to_s, 152 | properties: Hash[obj[cls]['attributes'].map { |k, str| [k, str.to_s] }], 153 | class: cls, 154 | subscription_id: subscription_id[0] 155 | } 156 | events.push(event) 157 | end 158 | 159 | end 160 | 161 | events 162 | end 163 | end 164 | end 165 | -------------------------------------------------------------------------------- /spec/spec_basic.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'simplecov' 3 | SimpleCov.start 4 | require 'acirb' 5 | 6 | apicuri = ENV['APIC_URI'] 7 | username = ENV['APIC_USERNAME'] 8 | password = ENV['APIC_PASSWORD'] 9 | # password = 'wrong' 10 | 11 | testtenant = 'rubysdktest' 12 | # formats = ['xml', 'json'] 13 | formats = %w(xml json) 14 | debug = false 15 | 16 | formats.each do |format| 17 | RSpec.describe 'ACIrb Basic' do 18 | before(:each) do 19 | pending 'No apic target defined' unless apicuri && username && password 20 | @rest = ACIrb::RestClient.new(url: apicuri, user: username, format: format, 21 | password: password, debug: debug) 22 | end 23 | 24 | it '' + format + ' Creates a tenant named test and verifies its existence' do 25 | uni = ACIrb::PolUni.new(nil) 26 | expect(uni.dn).to eq('uni') 27 | tenant = ACIrb::FvTenant.new(uni, name: testtenant) 28 | expect(tenant.rn).to eq('tn-' + testtenant) 29 | expect(tenant.dn).to eq('uni/tn-' + testtenant) 30 | tenant.create(@rest) 31 | expect(tenant.exists(@rest, true)).to eq(true) 32 | end 33 | 34 | it '' + format + ' Performs a lookupByDn on the tenant created' do 35 | mo = @rest.lookupByDn('uni/tn-' + testtenant, subtree: 'full') 36 | expect(mo.rn).to eq('tn-' + testtenant) 37 | expect(mo.dn).to eq('uni/tn-' + testtenant) 38 | end 39 | 40 | it '' + format + ' Create an app profile under the created tenant' do 41 | mo = @rest.lookupByDn('uni/tn-' + testtenant, subtree: 'full') 42 | ap = ACIrb::FvAp.new(mo, name: 'app1') 43 | ap.create(@rest) 44 | expect(ap.exists(@rest, true)).to eq(true) 45 | end 46 | 47 | it '' + format + ' Create an EPG profile under the app profile' do 48 | mo = @rest.lookupByDn('uni/tn-' + testtenant, subtree: 'full') 49 | ap = ACIrb::FvAp.new(mo, name: 'app1') 50 | epg = ACIrb::FvAEPg.new(ap, name: 'epg1') 51 | ap.create(@rest) 52 | expect(ap.exists(@rest, true)).to eq(true) 53 | expect(epg.exists(@rest, true)).to eq(true) 54 | end 55 | 56 | it '' + format + ' Look up the EPG' do 57 | mo = @rest.lookupByDn('uni/tn-%s/ap-%s/epg-%s' % [testtenant, 'app1', 'epg1'], subtree: 'full') 58 | expect(mo.rn).to eq('epg-epg1') 59 | expect(mo.dn).to eq('uni/tn-%s/ap-%s/epg-%s' % [testtenant, 'app1', 'epg1']) 60 | end 61 | 62 | it '' + format + ' Modify the description of an EPG' do 63 | descr = 'This is a new description' 64 | mo = @rest.lookupByDn('uni/tn-%s/ap-%s/epg-%s' % [testtenant, 'app1', 'epg1'], subtree: 'full') 65 | mo.descr = descr 66 | mo.create(@rest) 67 | mo = @rest.lookupByDn('uni/tn-%s/ap-%s/epg-%s' % [testtenant, 'app1', 'epg1'], subtree: 'full') 68 | expect(mo.descr).to eq(descr) 69 | expect(mo.rn).to eq('epg-epg1') 70 | expect(mo.dn).to eq('uni/tn-%s/ap-%s/epg-%s' % [testtenant, 'app1', 'epg1']) 71 | end 72 | 73 | it '' + format + ' Lookup by class for all EPGs with subtree' do 74 | @rest.lookupByClass('fvAEPg', subtree: 'full') 75 | end 76 | 77 | it '' + format + ' Creates a static path binding' do 78 | epg = @rest.lookupByDn('uni/tn-%s/ap-%s/epg-%s' % [testtenant, 'app1', 'epg1'], subtree: 'full') 79 | path = @rest.lookupByClass('fabricPathEp')[0] 80 | pathatt = ACIrb::FvRsPathAtt.new(epg, tDn: path.dn, encap: 'vlan-101') 81 | pathatt.create(@rest) 82 | end 83 | 84 | it '' + format + ' Delete static path binding' do 85 | epg = @rest.lookupByDn('uni/tn-%s/ap-%s/epg-%s' % [testtenant, 'app1', 'epg1'], subtree: 'full') 86 | dnq = ACIrb::DnQuery.new(epg.dn) 87 | dnq.class_filter = 'fvRsPathAtt' 88 | dnq.query_target = 'children' 89 | pathatt = @rest.query(dnq)[0] 90 | pathatt.destroy(@rest) 91 | end 92 | 93 | it '' + format + ' Lookup by class for all Tenants with subtree' do 94 | mos = @rest.lookupByClass('fvTenant', subtree: 'full') 95 | 96 | count = 0 97 | 98 | mos.each do |mo| 99 | count += 1 if mo.attributes['name'] == 'common' || \ 100 | mo.attributes['name'] == 'mgmt' || \ 101 | mo.attributes['name'] == 'infra' 102 | end 103 | expect(count).to eq(3) 104 | end 105 | 106 | it '' + format + ' Does a complex Dn query' do 107 | dn = 'uni/tn-ASA-F5-TEST/ap-qbo/epg-app/FI_C-qbo-app-compl-G-web-' \ 108 | 'F-Firewall-N-AccessList' 109 | @rest.lookupByDn(dn, subtree: 'full') 110 | end 111 | 112 | it '' + format + ' Does a complex class query' do 113 | dnq = ACIrb::ClassQuery.new('acEntity') 114 | dnq.subtree = 'children' 115 | @rest.query(dnq) 116 | end 117 | 118 | it '' + format + ' Performs multiple queries on the same connection' do 119 | %w(acEntity fvTenant topSystem).each do |cls| 120 | dnq = ACIrb::ClassQuery.new(cls) 121 | dnq.subtree = 'children' 122 | dnq.page_size = 10 123 | @rest.query(dnq) 124 | end 125 | end 126 | 127 | it '' + format + ' Deletes the tenant created' do 128 | uni = ACIrb::PolUni.new(nil) 129 | tenant = ACIrb::FvTenant.new(uni, name: testtenant) 130 | tenant.destroy(@rest) 131 | expect(tenant.exists(@rest)).to eq(false) 132 | end 133 | end 134 | end 135 | -------------------------------------------------------------------------------- /spec/spec_loader.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | SimpleCov.start 3 | require 'acirb' 4 | 5 | RSpec.describe 'ACIrb Loader' do 6 | it 'Verifies the to_s method works' do 7 | xml = '' 8 | mo = ACIrb::Loader.load_xml_str(xml) 9 | puts mo 10 | end 11 | 12 | it 'Loads MO from XML under topRoot' do 13 | xml = '' 14 | mo = ACIrb::Loader.load_xml_str(xml) 15 | 16 | expect(mo.children[0].dn).to eq('uni') 17 | expect(mo.children[0].mo_type).to eq('polUni') 18 | expect(mo.children[0].children[0].dn).to eq('uni/tn-test') 19 | expect(mo.children[0].children[0].rn).to eq('tn-test') 20 | expect(mo.children[0].children[0].mo_type).to eq('fvTenant') 21 | expect(mo.children[0].children[0].name).to eq('test') 22 | expect(mo.root.mo_type).to eq('topRoot') 23 | end 24 | 25 | it 'Loads MO from XML under polUni' do 26 | xml = '' 27 | mo = ACIrb::Loader.load_xml_str(xml) 28 | expect(mo.dn).to eq('uni') 29 | expect(mo.children[0].dn).to eq('uni/tn-test') 30 | end 31 | 32 | it 'Loads MO from XML under specific object with dn' do 33 | xml = '' 34 | mo = ACIrb::Loader.load_xml_str(xml) 35 | expect(mo.dn).to eq('uni/tn-test') 36 | end 37 | 38 | it 'Loads MO from Hash under topRoot' do 39 | hash = { 40 | 'topRoot' => { 41 | 'children' => [ 42 | { 43 | 'polUni' => { 44 | 'children' => [ 45 | { 46 | 'fvTenant' => { 47 | 'children' => [ 48 | { 49 | 'fvAp' => { 50 | 'attributes' => { 51 | 'name' => 'WebApplication' 52 | }, 53 | 'children' => [ 54 | { 55 | 'fvAEPg' => { 56 | 'attributes' => { 57 | 'name' => 'WebTier' 58 | } 59 | } 60 | } 61 | ] 62 | } 63 | } 64 | ], 65 | 'attributes' => { 66 | 'name' => 'test' 67 | } 68 | } 69 | }, 70 | { 71 | 'fvTenant' => { 72 | 'attributes' => { 73 | 'name' => 'test2' 74 | } 75 | } 76 | } 77 | ] 78 | } 79 | } 80 | ] 81 | } 82 | } 83 | 84 | mo = ACIrb::Loader.load_hash(hash) 85 | expect(mo.dn).to eq('') 86 | expect(mo.children[0].dn).to eq('uni') 87 | expect(mo.children[0].children[0].dn).to eq('uni/tn-test') 88 | expect(mo.children[0].children[1].dn).to eq('uni/tn-test2') 89 | end 90 | 91 | it 'Loads MO from Hash under polUni' do 92 | hash = { 93 | 'polUni' => { 94 | 'children' => [ 95 | { 96 | 'fvTenant' => { 97 | 'children' => [ 98 | { 99 | 'fvAp' => { 100 | 'attributes' => { 101 | 'name' => 'WebApplication' 102 | }, 103 | 'children' => [ 104 | { 105 | 'fvAEPg' => { 106 | 'attributes' => { 107 | 'name' => 'WebTier' 108 | } 109 | } 110 | } 111 | ] 112 | } 113 | } 114 | ], 115 | 'attributes' => { 116 | 'name' => 'test' 117 | } 118 | } 119 | }, 120 | { 121 | 'fvTenant' => { 122 | 'attributes' => { 123 | 'name' => 'test2' 124 | } 125 | } 126 | } 127 | ] 128 | } 129 | } 130 | 131 | mo = ACIrb::Loader.load_hash(hash) 132 | expect(mo.dn).to eq('uni') 133 | expect(mo.children[0].dn).to eq('uni/tn-test') 134 | end 135 | 136 | it 'Loads config similar to the ruby manifest' do 137 | hash = { 138 | 'fvTenant' => { 139 | 'attributes' => { 140 | 'name' => 'test2', 141 | 'dn' => 'uni/tn-test2' 142 | }, 143 | 'children' => [ 144 | { 145 | 'fvBD' => { 146 | 'attributes' => { 147 | 'name' => 'BD1' 148 | } 149 | } 150 | }, 151 | { 152 | 'fvAp' => { 153 | 'attributes' => { 154 | 'name' => 'WebApplication' 155 | }, 156 | 'children' => [ 157 | { 158 | 'fvAEPg' => { 159 | 'attributes' => { 160 | 'name' => 'WebTier' 161 | } 162 | } 163 | } 164 | ] 165 | } 166 | } 167 | ] 168 | } 169 | } 170 | 171 | mo = ACIrb::Loader.load_hash(hash) 172 | expect(mo.dn).to eq('uni/tn-test2') 173 | end 174 | 175 | it 'Loads a non top object with dn defined' do 176 | hash = { 177 | 'fvBD' => { 178 | 'attributes' => { 179 | 'name' => 'bd1', 180 | 'dn' => 'uni/tn-test2/BD-bd1' 181 | } 182 | } 183 | } 184 | 185 | mo = ACIrb::Loader.load_hash(hash) 186 | expect(mo.dn).to eq('uni/tn-test2/BD-bd1') 187 | end 188 | end 189 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | ACIrb is a Ruby implementation of the Cisco APIC REST API. It enables direct manipulation of the Management Information Tree (MIT) through the REST API using standard ruby language options, allowing programmatic interaction with APIC to configure all aspects of the fabric. The project aims to be a thin layer over the object model, so creating code is simple. 3 | # Installation 4 | If you want the simple version, the gem for this is available on rubygems, so you can install via ruby gems. To install the latest version use the below command: 5 | ``` 6 | gem install acirb 7 | ``` 8 | If you want or need to install a specific version (such as if you are running APIC 1.0.4o and need to install ACIrb for that version), you can specify the -v tag to select a version of a gem: 9 | ``` 10 | # Install ACIrb for APIC 1.0.4o 11 | gem install acirb -v 1.0.4.1 12 | 13 | # Install ACIrb for APIC 1.1.1j 14 | gem install acirb -v 1.1.1.1 15 | ``` 16 | If you've checked out this repo and want to install the gem from the repo, you can also install it from a .gem file: 17 | ``` 18 | gem install acirb-version.gem 19 | ``` 20 | If you are building from scratch, when build.sh is run it will build the GEM and install it. You can just use **gem install** to install that generated gem 21 | 22 | # Samples 23 | ## Querying fabric health score 24 | Querying can be accomplished using the lookupByDn and lookupByClass helper methods, e.g.: 25 | ``` 26 | require 'acirb' 27 | 28 | apicuri = 'https://apic' 29 | username = 'admin' 30 | password = 'password' 31 | 32 | rest = ACIrb::RestClient.new(url: apicuri, user: username, 33 | password: password) 34 | 35 | health = rest.lookupByDn('topology/HDfabricOverallHealth5min-0', 36 | subtree: 'full') 37 | puts health.healthAvg 38 | ``` 39 | ## Creating a new tenant 40 | Object creation is simple -- just build the hierarchy of objects, and call .create to commit the changes: 41 | ``` 42 | apicuri = 'https://apic' 43 | username = 'admin' 44 | password = 'password' 45 | 46 | rest = ACIrb::RestClient.new(url: apicuri, user: username, 47 | password: password) 48 | 49 | uni = ACIrb::PolUni.new(nil) 50 | tenant = ACIrb::FvTenant.new(uni, name: 'NewTenant') 51 | tenant.create(rest) 52 | ``` 53 | ## Modifying stuff 54 | You don't always just want to query and create things -- somethings you need to change them. Luckily we can do that too. Let's say that we want to change the description on an EPG. This example will query for an EPG named 'test' in tenant 'test' and application 'test' and change the description on it: 55 | ``` 56 | apicuri = 'https://apic' 57 | username = 'admin' 58 | password = 'password' 59 | 60 | rest = ACIrb::RestClient.new(url: apicuri, user: username, 61 | password: password) 62 | 63 | mo = @rest.lookupByDn('uni/tn-test/ap-test/epg-test) 64 | mo.descr = 'Hey look I am a described' 65 | mo.create(@rest) 66 | ``` 67 | Note that we're using the mo.create() method here. Since APIC generally doesn't discriminate between updates and creations, we use the same API method to do this, so rest assured that when you call mo.create() on something that is already there, it will just make changes to that object. I guess we could add a .modify call to the MO class, but it would just be an alias to .create, and since we are all mature adults, I think we can accept this. If not, please open an issue and tell me why not. 68 | 69 | ## Deleting stuff 70 | We can use the example above to query for the EPG, and then delete it too, using the mo.destroy() method 71 | ``` 72 | apicuri = 'https://apic' 73 | username = 'admin' 74 | password = 'password' 75 | 76 | rest = ACIrb::RestClient.new(url: apicuri, user: username, 77 | password: password) 78 | 79 | mo = @rest.lookupByDn('uni/tn-test/ap-test/epg-test) 80 | mo.destroy(@rest) 81 | ``` 82 | Just be careful not to delete anything that is super critical, or else your boss will be mad at you 83 | 84 | ## More examples 85 | For more examples, please check out the [examples](examples) folder 86 | # Building 87 | Building is likely something most users won't need to do, however if you are adventurous you can give it a try. It consists of three steps: 88 | 89 | 1. Get the source code from git 90 | ``` 91 | git clone https://github.com/datacenter/acirb 92 | ``` 93 | 2. Copy the python model from an APIC 94 | To generate the model, you need a Python meta-model (pysdk). pysdk contains all of the objects in the ACI object model, with properties, attributes, values, validation and relationships, so it makes a good source for generating the model in other languages. You can get the contents from any APIC, and a reference script called **updatepysdk.sh** is included here, where you can simply substitute in the IP address and username for your own, run it, and it will place everything nicely into a pysdk folder sitting next to this README. 95 | ``` 96 | ./updatepysdk.sh 97 | ``` 98 | 3. Generate the ruby model and GEM 99 | 100 | At this point you can run **./build.sh** which will kick off **genrubyfrompython.py**, and a bunch of other scripts that generate the Ruby GEM and install it on your system. 101 | ``` 102 | ./build.sh 103 | ```` 104 | 105 | # Build Requirements 106 | To use the contents of pysdk, you'll need PyAML. You can get this using pip, easy_install or equivalent 107 | ``` 108 | pip install pyaml 109 | ``` 110 | To build some of the ruby dependencies, you'll need the ruby development files in your system. For debian/ubuntu based systems: 111 | ``` 112 | apt-get install ruby-dev 113 | ``` 114 | # Tests 115 | The spec folder contains a number of automated tests to do basic sanity testing of various functions. This can be invoked using typical rspec calls, however a few environment variables must be set pointing to the APIC for the spec_basic.rb suite of tests. 116 | 117 | export APIC_URI='https://apic' 118 | export APIC_USERNAME='admin' 119 | export APIC_PASSWORD='password' 120 | rspec -Ilib -fd spec/*.rb 121 | -------------------------------------------------------------------------------- /examples/acidashboard/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | /* 2 | //=require_directory . 3 | //=require_tree ../../widgets 4 | */ 5 | // ---------------------------------------------------------------------------- 6 | // Sass declarations 7 | // ---------------------------------------------------------------------------- 8 | $background-color: #222; 9 | $text-color: #fff; 10 | 11 | $background-warning-color-1: #e82711; 12 | $background-warning-color-2: #9b2d23; 13 | $text-warning-color: #fff; 14 | 15 | $background-danger-color-1: #eeae32; 16 | $background-danger-color-2: #ff9618; 17 | $text-danger-color: #fff; 18 | 19 | @-webkit-keyframes status-warning-background { 20 | 0% { background-color: $background-warning-color-1; } 21 | 50% { background-color: $background-warning-color-2; } 22 | 100% { background-color: $background-warning-color-1; } 23 | } 24 | @-webkit-keyframes status-danger-background { 25 | 0% { background-color: $background-danger-color-1; } 26 | 50% { background-color: $background-danger-color-2; } 27 | 100% { background-color: $background-danger-color-1; } 28 | } 29 | @mixin animation($animation-name, $duration, $function, $animation-iteration-count:""){ 30 | -webkit-animation: $animation-name $duration $function #{$animation-iteration-count}; 31 | -moz-animation: $animation-name $duration $function #{$animation-iteration-count}; 32 | -ms-animation: $animation-name $duration $function #{$animation-iteration-count}; 33 | } 34 | 35 | // ---------------------------------------------------------------------------- 36 | // Base styles 37 | // ---------------------------------------------------------------------------- 38 | html { 39 | font-size: 100%; 40 | -webkit-text-size-adjust: 100%; 41 | -ms-text-size-adjust: 100%; 42 | } 43 | 44 | body { 45 | margin: 0; 46 | background-color: $background-color; 47 | font-size: 20px; 48 | color: $text-color; 49 | font-family: 'Open Sans', "Helvetica Neue", Helvetica, Arial, sans-serif; 50 | } 51 | 52 | b, strong { 53 | font-weight: bold; 54 | } 55 | 56 | a { 57 | text-decoration: none; 58 | color: inherit; 59 | } 60 | 61 | img { 62 | border: 0; 63 | -ms-interpolation-mode: bicubic; 64 | vertical-align: middle; 65 | } 66 | 67 | img, object { 68 | max-width: 100%; 69 | } 70 | 71 | iframe { 72 | max-width: 100%; 73 | } 74 | 75 | table { 76 | border-collapse: collapse; 77 | border-spacing: 0; 78 | width: 100%; 79 | } 80 | 81 | td { 82 | vertical-align: middle; 83 | } 84 | 85 | ul, ol { 86 | padding: 0; 87 | margin: 0; 88 | } 89 | 90 | h1, h2, h3, h4, h5, p { 91 | padding: 0; 92 | margin: 0; 93 | } 94 | h1 { 95 | margin-bottom: 12px; 96 | text-align: center; 97 | font-size: 30px; 98 | font-weight: 400; 99 | } 100 | h2 { 101 | text-transform: uppercase; 102 | font-size: 76px; 103 | font-weight: 700; 104 | color: $text-color; 105 | } 106 | h3 { 107 | font-size: 25px; 108 | font-weight: 600; 109 | color: $text-color; 110 | } 111 | 112 | // ---------------------------------------------------------------------------- 113 | // Base widget styles 114 | // ---------------------------------------------------------------------------- 115 | .gridster { 116 | margin: 0px auto; 117 | } 118 | 119 | .icon-background { 120 | width: 100%!important; 121 | height: 100%; 122 | position: absolute; 123 | left: 0; 124 | top: 0; 125 | opacity: 0.1; 126 | font-size: 275px; 127 | text-align: center; 128 | margin-top: 82px; 129 | } 130 | 131 | .list-nostyle { 132 | list-style: none; 133 | } 134 | 135 | .gridster ul { 136 | list-style: none; 137 | } 138 | 139 | .gs_w { 140 | width: 100%; 141 | display: table; 142 | cursor: pointer; 143 | } 144 | 145 | .widget { 146 | padding: 25px 12px; 147 | text-align: center; 148 | width: 100%; 149 | display: table-cell; 150 | vertical-align: middle; 151 | } 152 | 153 | .widget.status-warning { 154 | background-color: $background-warning-color-1; 155 | @include animation(status-warning-background, 2s, ease, infinite); 156 | 157 | .icon-warning-sign { 158 | display: inline-block; 159 | } 160 | 161 | .title, .more-info { 162 | color: $text-warning-color; 163 | } 164 | } 165 | 166 | .widget.status-danger { 167 | color: $text-danger-color; 168 | background-color: $background-danger-color-1; 169 | @include animation(status-danger-background, 2s, ease, infinite); 170 | 171 | .icon-warning-sign { 172 | display: inline-block; 173 | } 174 | 175 | .title, .more-info { 176 | color: $text-danger-color; 177 | } 178 | } 179 | 180 | .more-info { 181 | font-size: 15px; 182 | position: absolute; 183 | bottom: 32px; 184 | left: 0; 185 | right: 0; 186 | } 187 | 188 | .updated-at { 189 | font-size: 15px; 190 | position: absolute; 191 | bottom: 12px; 192 | left: 0; 193 | right: 0; 194 | } 195 | 196 | #save-gridster { 197 | display: none; 198 | position: fixed; 199 | top: 0; 200 | margin: 0px auto; 201 | left: 50%; 202 | z-index: 1000; 203 | background: black; 204 | width: 190px; 205 | text-align: center; 206 | border: 1px solid white; 207 | border-top: 0px; 208 | margin-left: -95px; 209 | padding: 15px; 210 | } 211 | 212 | #save-gridster:hover { 213 | padding-top: 25px; 214 | } 215 | 216 | #saving-instructions { 217 | display: none; 218 | padding: 10px; 219 | width: 500px; 220 | height: 122px; 221 | z-index: 1000; 222 | background: white; 223 | top: 100px; 224 | color: black; 225 | font-size: 15px; 226 | padding-bottom: 4px; 227 | 228 | textarea { 229 | white-space: nowrap; 230 | width: 494px; 231 | height: 80px; 232 | } 233 | } 234 | 235 | #lean_overlay { 236 | position: fixed; 237 | z-index:100; 238 | top: 0px; 239 | left: 0px; 240 | height:100%; 241 | width:100%; 242 | background: #000; 243 | display: none; 244 | } 245 | 246 | #container { 247 | padding-top: 5px; 248 | } 249 | 250 | 251 | // ---------------------------------------------------------------------------- 252 | // Clearfix 253 | // ---------------------------------------------------------------------------- 254 | .clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; } 255 | .clearfix:after { clear: both; } 256 | .clearfix { zoom: 1; } 257 | 258 | -------------------------------------------------------------------------------- /examples/acidashboard/jobs/apic.rb: -------------------------------------------------------------------------------- 1 | require 'acirb' 2 | require 'time' 3 | 4 | apicuri = 'https://apic' 5 | username = 'admin' 6 | password = 'password' 7 | 8 | login_time = Time.new.to_f 9 | puts 'Connecting to APIC %s' % apicuri 10 | rest = ACIrb::RestClient.new(url: apicuri, user: username, 11 | password: password, format: 'json', debug: false) 12 | 13 | health_points = [] 14 | (1..100).each do |i| 15 | health_points << { x: i, y: 0 } 16 | end 17 | last_health_x = health_points.last[:x] 18 | 19 | thrupt_points = [] 20 | (1..100).each do |i| 21 | thrupt_points << { x: i, y: 0 } 22 | end 23 | last_thrupt_x = thrupt_points.last[:x] 24 | 25 | last_endpoint_count = 0 26 | last_actrlrule_count = 0 27 | last_freeport_count = 0 28 | last_tx = 0 29 | last_rx = 0 30 | 31 | def latest_endpoints(rest) 32 | cq = ACIrb::ClassQuery.new('fvCEp') 33 | cq.sort_order = 'fvCEp.modTs|desc' 34 | cq.page_size = '5' 35 | endpoints = rest.query(cq) 36 | 37 | endpoint_text = endpoints.map do |endpoint| 38 | { label: 'IP: %s MAC: %s' % [endpoint.ip, endpoint.mac], value: endpoint.modTs } 39 | end 40 | 41 | send_event('latest_endpoints', items: endpoint_text) 42 | end 43 | 44 | def update_actrlrule(rest, last_actrlrule_count) 45 | cq = ACIrb::ClassQuery.new('actrlRule') 46 | cq.subtree_include = 'count' 47 | actrlrule = rest.query(cq) 48 | if actrlrule 49 | count = actrlrule[0].count 50 | send_event('apic_actrlrule', current: count, 51 | last: last_actrlrule_count) 52 | end 53 | count 54 | end 55 | 56 | def update_endpointcount(rest, last_endpoint_count) 57 | cq = ACIrb::ClassQuery.new('fvCEp') 58 | cq.subtree_include = 'count' 59 | endpoints = rest.query(cq) 60 | if endpoints 61 | count = endpoints[0].count 62 | send_event('apic_endpoints', current: count, 63 | last: last_endpoint_count) 64 | end 65 | count 66 | end 67 | 68 | def update_freeports(rest, last_freeport_count) 69 | cq = ACIrb::ClassQuery.new('l1PhysIf') 70 | cq.subtree_prop_filter = 'eq(l1PhysIf.usage, "discovery")' 71 | cq.subtree_include = 'count' 72 | freeports = rest.query(cq) 73 | if freeports 74 | count = freeports[0].count 75 | send_event('apic_freeports', current: count, 76 | last: last_freeport_count) 77 | end 78 | count 79 | end 80 | 81 | def update_endpoint_chart(rest) 82 | cq = ACIrb::ClassQuery.new('fvCEp') 83 | cq.subtree_include = 'count' 84 | cq.prop_filter = 'wcard(fvCEp.lcC,"vmm")' 85 | vmm_endpoints = rest.query(cq) 86 | 87 | cq = ACIrb::ClassQuery.new('fvCEp') 88 | cq.subtree_include = 'count' 89 | all_endpoints = rest.query(cq) 90 | 91 | if vmm_endpoints && all_endpoints 92 | vmm_count = vmm_endpoints[0].count 93 | all_count = all_endpoints[0].count 94 | send_event('ep_chart', slices: [ 95 | ['End Point Type', 'Hosts'], 96 | ['VMM', vmm_count.to_i], 97 | ['Bare Metal', all_count.to_i - vmm_count.to_i] 98 | ]) 99 | end 100 | end 101 | 102 | def get_int_stats(rest) 103 | start = Time.new.to_f 104 | cq = ACIrb::ClassQuery.new('l1PhysIf') 105 | cq.subtree_include = 'stats' 106 | cq.subtree_class_filter = 'eqptEgrTotal5min,eqptIngrTotal5min' 107 | cq.prop_filter = 'eq(l1PhysIf.switchingSt,"enabled")' 108 | interfaces = rest.query(cq) 109 | puts '%d interfaces returned stats. %.2f seconds' % [interfaces.length, (Time.new.to_f - start)] 110 | interfaces 111 | end 112 | 113 | def update_unicast_per_second(last_tx, last_rx, interfaces) 114 | tx = 0 115 | rx = 0 116 | interfaces.each do |interface| 117 | begin 118 | interface.CDeqptEgrTotal5min.each do |egr| 119 | tx += egr.bytesRate.to_i 120 | end 121 | interface.CDeqptIngrTotal5min.each do |ingr| 122 | rx += ingr.bytesRate.to_i 123 | end 124 | rescue => e 125 | puts 'Exception %s' % e 126 | end 127 | end 128 | 129 | send_event('apic_packets_tx', current: tx, last: last_tx) 130 | send_event('apic_packets_rx', current: rx, last: last_rx) 131 | [tx, rx] 132 | end 133 | 134 | def update_thrupt(thrupt_points, last_x, interfaces) 135 | tx = 0 136 | rx = 0 137 | 138 | interfaces.each do |interface| 139 | begin 140 | interface.CDeqptEgrTotal5min.each do |egr| 141 | tx += egr.bytesRate.to_i 142 | end 143 | interface.CDeqptIngrTotal5min.each do |ingr| 144 | rx += ingr.bytesRate.to_i 145 | end 146 | rescue => e 147 | puts 'Exception %s' % e 148 | end 149 | end 150 | 151 | thrupt_points.shift 152 | last_x += 1 153 | thrupt_points << { x: last_x, y: (tx + rx) } 154 | send_event('apic_thrupt', points: thrupt_points) 155 | [last_x, thrupt_points] 156 | end 157 | 158 | def update_health(rest, points, last_x) 159 | dn = 'topology/HDfabricOverallHealth5min-0' 160 | health = rest.lookupByDn(dn) 161 | if health 162 | points.shift 163 | last_x += 1 164 | points << { x: last_x, y: health.healthAvg.to_i } 165 | send_event('apic_health', points: points) 166 | end 167 | [last_x, points] 168 | end 169 | 170 | scheduler = Rufus::Scheduler.start_new 171 | 172 | scheduler.every '%ds' % (rest.refresh_time.to_i / 2) do 173 | # refresh the apic session at the half life of the reauthentication time 174 | puts 'Refreshing APIC session' 175 | begin 176 | rest.refresh_session 177 | rescue 178 | rest.authenticate 179 | end 180 | login_time = Time.new.to_f 181 | end 182 | 183 | Thread.new do 184 | loop do 185 | puts 'Updating interface stats' 186 | interfaces = get_int_stats(rest) 187 | last_thrupt_x, thrupt_points = update_thrupt(thrupt_points, 188 | last_thrupt_x, interfaces) 189 | last_tx, last_rx = update_unicast_per_second(last_tx, last_rx, 190 | interfaces) 191 | sleep 3 192 | end 193 | end 194 | 195 | scheduler.every '10s' do 196 | puts 'Updating health and endpoint count' 197 | last_health_x, health_points = update_health(rest, health_points, 198 | last_health_x) 199 | last_endpoint_count = update_endpointcount(rest, last_endpoint_count) 200 | end 201 | 202 | scheduler.every '30s' do 203 | puts 'Updating access control rule count' 204 | last_actrlrule_count = update_actrlrule(rest, last_actrlrule_count) 205 | end 206 | 207 | scheduler.every '10s' do 208 | puts 'Updating count of end point types' 209 | 210 | update_endpoint_chart(rest) 211 | last_freeport_count = update_freeports(rest, last_freeport_count) 212 | end 213 | 214 | scheduler.every '60s' do 215 | puts 'Updating latest endpoints' 216 | latest_endpoints(rest) 217 | end 218 | -------------------------------------------------------------------------------- /lib/acirb/mo.rb: -------------------------------------------------------------------------------- 1 | require 'acirb/restclient' 2 | require 'rexml/document' 3 | require 'json' 4 | 5 | # rubocop:disable ClassLength 6 | module ACIrb 7 | # A generic managed object class 8 | class MO 9 | # Class variable properties 10 | class << self 11 | # for class variables 12 | attr_reader :prefix, :class_name, :child_classes, :props, 13 | :naming_props, :prefixes, :ruby_class, :containers, 14 | :read_only 15 | end 16 | 17 | # for instance variables 18 | attr_reader :children, :attributes 19 | attr_accessor :parent, :dirty_props 20 | 21 | # Internal: Returns class prefixes 22 | def prefixes 23 | self.class.prefixes 24 | end 25 | 26 | # Internal: Returns child classes 27 | def child_classes 28 | self.class.child_classes 29 | end 30 | 31 | # Internal: Returns containiner classes 32 | def containers 33 | self.class.containers 34 | end 35 | 36 | # Internal: Returns naming properties 37 | def naming_props 38 | self.class.naming_props 39 | end 40 | 41 | # Internal: Returns class properties 42 | def props 43 | self.class.props 44 | end 45 | 46 | # Internal: Returns object class name in APIC package.class notation 47 | def class_name 48 | self.class.class_name 49 | end 50 | 51 | # Internal: Returns class name as ruby package notation 52 | def ruby_class 53 | self.class.ruby_class 54 | end 55 | 56 | # Interal: Returns boolean for if this class is read only 57 | def read_only 58 | self.class.read_only 59 | end 60 | 61 | def initialize(create_parent, create_options = {}) 62 | # always mark dirty unless otherwise specified 63 | if create_options[:mark_dirty] == false 64 | mark_dirty = false 65 | else 66 | mark_dirty = true 67 | end 68 | create_options.delete(:mark_dirty) 69 | 70 | @attributes = {} 71 | @dirty_props = [] 72 | props.each do |prop, _flags| 73 | @attributes[prop.to_s] = '' 74 | end 75 | @children = [] 76 | 77 | if create_parent.nil? 78 | @parent = nil 79 | else 80 | @parent = create_parent 81 | @parent.add_child(self) 82 | end 83 | 84 | # for performance reasons, do not use set_prop here 85 | # and build the dn string after all attributes are set 86 | create_options.each do |k, v| 87 | flags = props[k.to_s] 88 | @attributes[k.to_s] = v.to_s 89 | @dirty_props.push(k.to_s) if mark_dirty 90 | end 91 | @attributes['dn'] = build_dn 92 | @attributes['rn'] = rn 93 | end 94 | 95 | # Internal: Adds another MO object as a child to this class 96 | def add_child(child) 97 | unless child.containers.include?(ruby_class) 98 | fail child.class.to_s + ' cannot be child of ' + self.class.to_s 99 | end 100 | @children.each do |mo| 101 | return nil if mo.dn == child.dn 102 | end 103 | @children.push(child) 104 | child.parent = self 105 | end 106 | 107 | def set_prop(key, val) 108 | key = key.to_s 109 | val = val.to_s 110 | 111 | return if key == 'dn' || key == 'rn' 112 | 113 | @attributes[key] = val 114 | @dirty_props.push(key) 115 | 116 | if naming_props.include? key 117 | dn_str = build_dn 118 | @attributes['dn'] = dn_str 119 | @attributes['rn'] = rn 120 | end 121 | end 122 | 123 | def root 124 | p = self 125 | p = p.parent until p.parent.nil? 126 | p 127 | end 128 | 129 | def get_attributes_to_include 130 | incl_attr = {} 131 | @attributes.each do |key, value| 132 | if props[key.to_s]['isDn'] == true || props[key.to_s]['isRn'] == true || @dirty_props.include?(key) || naming_props.include?(key) 133 | incl_attr[key.to_s] = value 134 | end 135 | end 136 | incl_attr 137 | end 138 | 139 | def get_children_to_include 140 | incl_children = [] 141 | @children.each do |child| 142 | incl_children.push(child) if child.read_only == false 143 | end 144 | incl_children 145 | end 146 | 147 | def to_xml 148 | # TODO: Use nokogiri here 149 | # https://github.com/sparklemotion/nokogiri/wiki/Cheat-sheet 150 | x = REXML::Element.new mo_type.to_s 151 | 152 | get_attributes_to_include.each do |key, value| 153 | x.attributes[key.to_s] = value 154 | end 155 | 156 | get_children_to_include.each do |child| 157 | x.add_element(child.to_xml) 158 | end 159 | x 160 | end 161 | 162 | def to_hash 163 | h = {} 164 | h[mo_type.to_s] = {} 165 | h[mo_type.to_s]['attributes'] = {} 166 | get_attributes_to_include.each do |key, value| 167 | h[mo_type.to_s]['attributes'][key.to_s] = value 168 | end 169 | h[mo_type.to_s]['attributes']['dn'] = dn 170 | h[mo_type.to_s]['children'] = [] if children.length > 0 171 | get_children_to_include.each do |child| 172 | h[mo_type.to_s]['children'].push(child.to_hash) 173 | end 174 | h 175 | end 176 | 177 | def to_json 178 | JSON.dump(to_hash) 179 | end 180 | 181 | def create(restclient) 182 | self.status = 'created,modified' 183 | restclient.post(data: self, 184 | url: "/api/mo/#{dn}.#{restclient.format}") 185 | @dirty_props = [] 186 | end 187 | 188 | def destroy(restclient) 189 | self.status = 'deleted' 190 | restclient.post(data: self, 191 | url: "/api/mo/#{dn}.#{restclient.format}") 192 | @dirty_props = [] 193 | end 194 | 195 | def exists(restclient, recurse = false) 196 | options = {} 197 | options[:subtree] = 'full' if recurse 198 | if restclient.lookupByDn(dn, options) 199 | if recurse == true 200 | children.each do |child| 201 | unless child.exists(restclient, recurse = true) 202 | return false 203 | end 204 | end 205 | end 206 | return true 207 | else 208 | return false 209 | end 210 | end 211 | 212 | def build_dn 213 | if @parent.nil? 214 | return rn 215 | else 216 | parent_dn = '' << @parent.dn 217 | if parent_dn == '' 218 | return rn 219 | else 220 | parent_dn << '/' 221 | parent_dn << rn 222 | return parent_dn 223 | end 224 | end 225 | end 226 | 227 | def mo_type 228 | self.class.class_name.delete('.') 229 | end 230 | 231 | def to_s 232 | def hash_to_table(h) 233 | len = h.keys.map(&:length).max 234 | ret = [] 235 | h.each do |k, v| 236 | ret.push(('%' + len.to_s + 's: %s') % [k.to_s, v.to_s]) 237 | end 238 | ret 239 | end 240 | header_table = hash_to_table( 241 | 'Class' => class_name 242 | ) 243 | attribute_table = hash_to_table(attributes) 244 | header_table.push(attribute_table) 245 | header_table.join("\n") 246 | end 247 | 248 | def method_missing(name, *args, &block) 249 | lookup_name = name.to_s 250 | return @attributes[lookup_name] if @attributes.key? lookup_name 251 | return set_prop(lookup_name.chomp('='), args[0]) if @attributes.key? lookup_name.chomp('=') 252 | child_matches = [] 253 | @children.each do |child| 254 | child_matches.push(child) if child.class.prefix.sub('-', '') == lookup_name 255 | end 256 | return child_matches if child_matches.length > 0 257 | super 258 | end 259 | end 260 | end 261 | -------------------------------------------------------------------------------- /genrubyfrompy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import pprint 4 | import os 5 | import sys 6 | import re 7 | import time 8 | from string import Template 9 | from collections import OrderedDict 10 | 11 | sys.path.append(os.path.dirname(os.path.realpath(__file__)) + '/pysdk') 12 | from insieme.pymit.pyaccess import PyClassDirectory 13 | 14 | 15 | def rubyClassName(className): 16 | rubyname = list(className) 17 | if len(rubyname) > 0: 18 | rubyname[0] = rubyname[0].upper() 19 | if '.' in rubyname: 20 | rubyname.remove('.') 21 | return ''.join(rubyname) 22 | 23 | 24 | def getChildren(pyClassDict): 25 | return [rubyClassName(x[0]) for x in pyClassDict['_children'].items()] 26 | 27 | 28 | def getProps(pyClassDict): 29 | # { 30 | # 'test' => {'isAdmin' => true}, 'test2' => {'isAdmin'=> true} 31 | # } 32 | prop_entries = [] 33 | flags_list = ['isAdmin', 'isImplicit', 'isCreateOnly', 'isDn', 'isRn', 'isExplicit'] 34 | 35 | for prop, flags in pyClassDict['_props'].items(): 36 | propflags = [] 37 | for flag in flags_list: 38 | if hasattr(flags, '_{}'.format(flag)): 39 | bo0l = 'true' if getattr(flags, '_{}'.format(flag)) else 'false' 40 | propflags.append("'{}' => {}".format(flag, bo0l)) 41 | prop_entries.append("'{}' => {{ {} }}".format(prop, ', '.join(propflags))) 42 | return '{{ {} }}'.format(',\n '.join(prop_entries)) 43 | # entries = ["%s => {".format(cls, props.get('isAdmin')) for cls,props in pyClassDict['_props'].items()] 44 | 45 | 46 | def getNamingProps(pyClassDict): 47 | return pyClassDict['_orderedNamingProps'] 48 | 49 | 50 | def getContainers(pyClassDict): 51 | return [rubyClassName(x[0]) for x in pyClassDict['_containers'].items()] 52 | # return ', '.join('\'%s\'' % c for c in pyClassDict['_containers'].keys()) 53 | 54 | 55 | def getRnFormatAsRuby(pyClassDict): 56 | a = pyClassDict['_rnFormat'] 57 | return '\'%s\'' % re.sub( 58 | '\%\(([a-zA-Z]+)\)s', '\' << @attributes[\'\\1\'] << \'', a) 59 | 60 | 61 | def getRnFunc(pyClassDict): 62 | ''' 63 | Generates the logic to create the relative name for the object, based on the rnPrefixes and orderedNames 64 | ''' 65 | # if len(pyClassDict['_orderedNamingProps']) == 0: 66 | # rnFunc = 'self.class.prefix' 67 | # elif len(pyClassDict['_orderedNamingProps']) == len(pyClassDict['_rnPrefixes']): 68 | # rnFunc = ' + '.join("'%s' + @options[:%s]" % t for t in zip(getRnPrefixes(pyClassDict), pyClassDict['_orderedNamingProps'])) 69 | # else: 70 | # rnFunc = getRnFormatAsRuby(pyClassDict) 71 | # print 'len of rnPrefixes is %d' % len(pyClassDict['_rnPrefixes']) 72 | # print 'len of naming props is %d' % len(pyClassDict['_orderedNamingProps']) 73 | # pprint.pprint(pyClassDict) 74 | # sys.exit(0) 75 | # rnFunc = 'self.class.prefix' 76 | rnFunc = getRnFormatAsRuby(pyClassDict) 77 | return rnFunc 78 | 79 | 80 | def getRnPrefixes(pyClassDict): 81 | if len(pyClassDict['_rnPrefixes']) > 0: 82 | return ', '.join(['[\'{0}\', {1}]'.format(x[0], str(x[1]).lower()) for x in pyClassDict['_rnPrefixes']]) 83 | else: 84 | return '' 85 | 86 | 87 | def getLabel(pyClassDict): 88 | return pyClassDict['_label'] 89 | 90 | 91 | def getRnPrefix(pyClassDict): 92 | if len(pyClassDict['_rnPrefixes']) > 0: 93 | # if len(pyClassDict['_rnPrefixes']) > 1: 94 | # print '%s has %d rnPrefixes' % (pyClassDict['_name'], 95 | # len(pyClassDict['_rnPrefixes'])) 96 | return pyClassDict['_rnPrefixes'][0][0] 97 | else: 98 | return '' 99 | 100 | 101 | def getClassName(pyClassDict): 102 | return pyClassDict['_name'] 103 | 104 | 105 | def getRnFormat(pyClassDict): 106 | return pyClassDict['_rnFormat'] 107 | 108 | def getReadOnly(pyClassDict): 109 | return 'true' if pyClassDict['_isReadOnly'] else 'false' 110 | 111 | def getRubyClassMap(classMap): 112 | rubyCode = Template("""# auto-generated code 113 | module ACIrb 114 | CLASSMAP = Hash.new 'None' 115 | $classMap 116 | def lookupClass(classname) 117 | return CLASSMAP[classname] 118 | end 119 | end 120 | """) 121 | vals = dict( 122 | classMap='\n '.join( 123 | ['CLASSMAP[\'{0}\'] = \'{1}\''.format(k, v) for k, v in classMap.items()]), 124 | ) 125 | 126 | return rubyCode.substitute(vals) 127 | 128 | def getRubyClass(pyClassDict): 129 | rubyCode = Template(""" class $rubyClassName < MO 130 | @class_name = '$objectName' 131 | @ruby_class = '$rubyClassName' 132 | @prefix = '$prefix' 133 | @prefixes = [$prefixes] 134 | @rn_format = '$rnFormat' 135 | @containers = $containers 136 | @props = $props 137 | @child_classes = $children 138 | @label = '$label' 139 | @naming_props = $namingProps 140 | @read_only = $readOnly 141 | 142 | def rn 143 | $rn 144 | end 145 | end 146 | """) 147 | vals = dict(rubyClassName=rubyClassName(pyClassDict['_name']), 148 | objectName=getClassName(pyClassDict), 149 | prefix=getRnPrefix(pyClassDict), 150 | prefixes=getRnPrefixes(pyClassDict), 151 | rnFormat=getRnFormat(pyClassDict), 152 | containers=getContainers(pyClassDict), 153 | props=getProps(pyClassDict), 154 | children=getChildren(pyClassDict), 155 | rn=getRnFunc(pyClassDict), 156 | label=getLabel(pyClassDict), 157 | namingProps=getNamingProps(pyClassDict), 158 | classNameShort=getClassName(pyClassDict).replace('.', ''), 159 | readOnly=getReadOnly(pyClassDict), 160 | ) 161 | return rubyCode.substitute(vals) 162 | 163 | 164 | def getRubyPackage(classDef): 165 | rubyCode = Template("""# auto-generated code 166 | require 'acirb/mo' 167 | module ACIrb 168 | $rubyClasses 169 | end 170 | """) 171 | vals = dict(rubyClasses=classDef, 172 | ) 173 | return rubyCode.substitute(vals) 174 | 175 | 176 | def getRubyAutoLoad(autoLoaderMap): 177 | 178 | rubyAutoLoad = '\n'.join([' ACIrb.autoload(\'{0}\', \'acirb/model/{1}\')'.format( 179 | rubyClass, rubyFile) for rubyClass, rubyFile in autoLoaderMap.items()]) 180 | 181 | rubyCode = Template("""# auto-generated code 182 | module ACIrb 183 | $rubyAutoLoad 184 | end 185 | """) 186 | vals = dict(rubyAutoLoad=rubyAutoLoad, 187 | ) 188 | return rubyCode.substitute(vals) 189 | 190 | 191 | def prettyprint(item, depth=0): 192 | if getattr(item, '__dict__', None): 193 | prettyprint(item.__dict__, depth + 1) 194 | elif isinstance(item, list): 195 | for i in item: 196 | print '-' * depth, type(i), i 197 | elif isinstance(item, set): 198 | for i in item: 199 | print '-' * depth, type(i), i 200 | elif isinstance(item, dict): 201 | for k, v in item.items(): 202 | print '-' * depth, k, ':' 203 | prettyprint(v, depth + 1) 204 | else: 205 | print '-' * depth, type(item), item 206 | 207 | 208 | def generateRuby(classdir): 209 | classMap = OrderedDict() 210 | packages = OrderedDict() 211 | ''' 212 | packages will be a dict of format: 213 | { 214 | packageName: (pkgCode (str), pkgClasses (list)) 215 | } 216 | ''' 217 | 218 | for item in classdir.getClasses(): 219 | pkgName = getClassName(item.__dict__).split('.')[0] 220 | className = getClassName(item.__dict__).replace('.', '') 221 | rubyName = rubyClassName(item.__dict__['_name']) 222 | 223 | classMap[className] = rubyName 224 | pkgCode, pkgClasses = packages.get(pkgName, ('', [])) 225 | pkgCode = pkgCode + getRubyClass(item.__dict__) 226 | pkgClasses.append(rubyName) 227 | packages[pkgName] = (pkgCode, pkgClasses) 228 | 229 | autoLoaderMap = OrderedDict() 230 | 231 | directory = os.path.join( 232 | os.path.abspath( 233 | os.path.dirname(__file__)), 234 | 'lib', 'acirb', 'model') 235 | 236 | if not os.path.exists(directory): 237 | os.makedirs(directory) 238 | 239 | for pkgname, payload in packages.items(): 240 | 241 | pkgCode, pkgClasses = payload 242 | 243 | for pkgClass in pkgClasses: 244 | autoLoaderMap[pkgClass] = '{0}.rb'.format(pkgname) 245 | 246 | fileName = os.path.join( 247 | os.path.abspath( 248 | os.path.dirname(__file__)), 249 | 'lib', 'acirb', 'model', pkgname + '.rb') 250 | 251 | # Create the package 252 | with open(fileName, 'w') as f: 253 | r = getRubyPackage(pkgCode) 254 | f.write(r) 255 | 256 | # Create the lookup map 257 | fileName = os.path.join( 258 | os.path.abspath( 259 | os.path.dirname(__file__)), 260 | 'lib', 'acirb', 'lookup.rb') 261 | with open(fileName, 'w') as f: 262 | f.write(getRubyClassMap(classMap)) 263 | 264 | # Create the autoloader 265 | fileName = os.path.join( 266 | os.path.abspath( 267 | os.path.dirname(__file__)), 268 | 'lib', 'acirb', 'autoloadmap.rb') 269 | with open(fileName, 'w') as f: 270 | f.write(getRubyAutoLoad(autoLoaderMap)) 271 | 272 | 273 | def main(): 274 | start = time.time() 275 | classdir = PyClassDirectory() 276 | generateRuby(classdir) 277 | print 'Completed in %.2f seconds' % (time.time() - start) 278 | 279 | if __name__ == '__main__': 280 | main() 281 | -------------------------------------------------------------------------------- /lib/acirb/restclient.rb: -------------------------------------------------------------------------------- 1 | require 'httpclient' 2 | require 'openssl' 3 | require 'nokogiri' 4 | require 'json' 5 | # require 'uri' 6 | 7 | # rubocop:disable ClassLength 8 | module ACIrb 9 | # REST client end point implementation 10 | 11 | class RestClient 12 | attr_accessor :format, :user, :password, :baseurl, :debug, :verify 13 | attr_reader :auth_cookie, :refresh_time 14 | 15 | class ApicAuthenticationError < StandardError 16 | end 17 | 18 | class ApicErrorResponse < StandardError 19 | end 20 | 21 | # Public: Initializes and establishes an authenticated session with APIC 22 | # REST endpoint 23 | # 24 | # options - Hash options used to specify connectivity 25 | # attributes (default: {}): 26 | # 27 | # :url - string URL of APIC, e.g., https://apic (required) 28 | # :user - string containing User ID for authentication (required) 29 | # :password - string containing Password for 30 | # authentication (required) 31 | # :debug - boolean true or false for including verbose REST output 32 | # (default: false) 33 | # :format - string 'xml' or 'json' specifying the format to use 34 | # for messaging to APIC. (default: xml) 35 | # :verify - boolean true or false for verifying the SSL 36 | # certificate. (default: false) 37 | # 38 | # Examples: 39 | # rest = ACIrb::RestClient.new(url: 'https://apic', user: 'admin', 40 | # password: 'password', format: 'json', 41 | # debug: false) 42 | def initialize(options = {}) 43 | uri = URI.parse(options[:url]) 44 | @baseurl = '%s://%s:%s' % [uri.scheme, uri.host, uri.port] 45 | @format = options[:format] ? options[:format] : 'xml' 46 | 47 | @user = options[:user] 48 | @password = options[:password] 49 | 50 | @verify = options[:verify] 51 | 52 | @client = HTTPClient.new 53 | 54 | @client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE \ 55 | unless options[:verify] && uri.scheme == 'https' 56 | 57 | @debug = options[:debug] 58 | 59 | @auth_cookie = '' 60 | 61 | authenticate if @user && @password 62 | end 63 | 64 | # Public: Authenticates the REST session with APIC 65 | # Sends a aaaLogin message to APIC and updates the following instance 66 | # variables: 67 | # @auth_cookie - session cookie 68 | # @refresh_time - session refresh timeout in seconds 69 | # 70 | # Returns nothing. 71 | def authenticate 72 | builder = Nokogiri::XML::Builder.new do |xml| 73 | xml.aaaUser(name: @user, pwd: @password) 74 | end 75 | post_url = URI.encode(@baseurl.to_s + '/api/mo/aaaLogin.xml') 76 | puts 'POST REQUEST', post_url if @debug 77 | puts 'POST BODY', builder.to_xml if @debug 78 | response = @client.post(post_url, body: builder.to_xml) 79 | puts 'POST RESPONSE: ', response.body if @debug 80 | doc = Nokogiri::XML(response.body) 81 | fail ApicAuthenticationError, 'Authentication error(%s): %s' % [doc.at_css('error')['code'], doc.at_css('error')['text']] \ 82 | if doc.at_css('error') 83 | fail ApicErrorResponse, 'Unexpected HTTP Error response code(%s): %s' % [response.code, response.body] if response.code != 200 84 | @auth_cookie = doc.at_css('aaaLogin')['token'] 85 | @refresh_time = doc.at_css('aaaLogin')['refreshTimeoutSeconds'] 86 | end 87 | 88 | # Public: Refreshes an existing RestClient object session 89 | # Sends a aaaRefresh message to APIC and updates the following instance 90 | # variables: 91 | # @auth_cookie - session cookie 92 | # @refresh_time - session refresh timeout in seconds 93 | # 94 | # Returns nothing. 95 | def refresh_session 96 | get_url = URI.encode(@baseurl.to_s + '/api/mo/aaaRefresh.xml') 97 | puts 'GET REQUEST', get_url if @debug 98 | response = @client.get(get_url) 99 | puts 'GET RESPONSE: ', response.body if @debug 100 | doc = Nokogiri::XML(response.body) 101 | fail ApicAuthenticationError, 'Authentication error(%s): %s' % [doc.at_css('error')['code'], doc.at_css('error')['text']] \ 102 | if doc.at_css('error') 103 | @auth_cookie = doc.at_css('aaaLogin')['token'] 104 | @refresh_time = doc.at_css('aaaLogin')['refreshTimeoutSeconds'] 105 | end 106 | 107 | # Internal: Escape URI before using with APIC REST interface 108 | # 109 | # uri - URI to escape 110 | # 111 | # Returns escaped URI as string 112 | 113 | def escape(uri) 114 | return URI.encode(uri, /[^\-_.!~*'()a-zA-Z\d;\/?:@&=+$,]/) 115 | end 116 | 117 | # Internal: Posts data to the APIC REST interface 118 | # 119 | # options - Hash options for defining post parameters (default: {}) 120 | # :url - relative URL for request (required) 121 | # :data - post payload to be included in the request (required) 122 | # 123 | # Returns results of parse_response, which will be the parsed results of 124 | # the XML or JSON payload represented as ACIrb::MO objects 125 | def post(options) 126 | post_url = self.escape(@baseurl.to_s + options[:url].to_s) 127 | 128 | data = options[:data] 129 | if @format == 'xml' 130 | data = data.to_xml 131 | elsif @format == 'json' 132 | data = data.to_json 133 | end 134 | 135 | puts 'POST REQUEST', post_url if @debug 136 | puts 'POST BODY', data if @debug 137 | response = @client.post(post_url, body: data) 138 | puts 'POST RESPONSE: ', response.body if @debug 139 | 140 | parse_response(response) 141 | end 142 | 143 | # Internal: Queries the APIC REST API for data 144 | # 145 | # options - Hash options for defining get parameters (default: {}) 146 | # :url - relative URL for request (required) 147 | # 148 | # Returns results of parse_response, which will be the parsed results of 149 | # the XML or JSON payload represented as ACIrb::MO objects 150 | def get(options) 151 | get_url = self.escape(@baseurl.to_s + options[:url].to_s) 152 | 153 | puts 'GET REQUEST', get_url if @debug 154 | response = @client.get(get_url) 155 | puts 'GET RESPONSE: ', response.body if @debug 156 | 157 | parse_response(response) 158 | end 159 | 160 | # Internal: Parses for error responses in APIC response payload 161 | # 162 | # doc - Nokigiri XML document or Hash array containing well formed 163 | # APIC response payload (required) 164 | def parse_error(doc) 165 | if format == 'xml' 166 | fail ApicErrorResponse, 'Error response from APIC (%s): "%s"' % \ 167 | [doc.at_css('error')['code'], doc.at_css('error')['text']] \ 168 | if doc.at_css('error') 169 | elsif format == 'json' 170 | fail ApicErrorResponse, 'Error response from APIC (%s): "%s"' % \ 171 | [doc['imdata'][0]['error']['attributes']['code'].to_s, \ 172 | doc['imdata'][0]['error']['attributes']['text'].to_s] \ 173 | if doc['imdata'].length > 0 && doc['imdata'][0].include?('error') 174 | end 175 | end 176 | 177 | # Internal: Parses APIC response payload into ACIrb::MO objects 178 | # 179 | # response - string containing the XML or JSON payload that will be 180 | # parsed according to the format defined at instance creation 181 | # (required) 182 | def parse_response(response) 183 | if format == 'xml' 184 | xml_data = response.body 185 | doc = Nokogiri::XML(xml_data) 186 | 187 | parse_error(doc) 188 | 189 | mos = [] 190 | doc.root.elements.each do |xml_obj| 191 | mo = ACIrb::Loader.load_xml(xml_obj) 192 | mos.push(mo) 193 | end 194 | 195 | return mos 196 | 197 | elsif format == 'json' 198 | json_data = response.body 199 | doc = JSON.parse(json_data) 200 | 201 | parse_error(doc) 202 | 203 | mos = [] 204 | doc['imdata'].each do |json_obj| 205 | mo = ACIrb::Loader.load_json(json_obj) 206 | mos.push(mo) 207 | end 208 | 209 | return mos 210 | end 211 | end 212 | 213 | # Public: Sends a query to APIC and returns the matching MO objects 214 | # 215 | # query_obj - ACIrb::Query object, typically either ACIrb::DnQuery or 216 | # ACIrb::ClassQuery which contains the query that will be issued 217 | # (required) 218 | # 219 | # Examples 220 | # dn_query = ACIrb::DnQuery.new('uni/tn-common') 221 | # dn_query.subtree = 'full' 222 | # mos = rest.query(dn_query) 223 | # 224 | # Returns array of ACIrb::MO objects for the query 225 | def query(query_obj) 226 | query_uri = query_obj.uri(@format) 227 | get(url: query_uri) 228 | end 229 | 230 | # Public: Sends an event subscription query to APIC 231 | # 232 | # query_obj - ACIrb::Query object, typically either ACIrb::DnQuery or 233 | # ACIrb::ClassQuery which contains the query that will be 234 | # issued. This query will have the .subscribe property set 235 | # to "yes" as part of the subscription process (required) 236 | # 237 | # Examples 238 | # # subscribe to all changes on fvCEp end points on fabric 239 | # # but restrict the results of the query to only include 1 240 | # # as to reduce the initial subscription time 241 | # class_query = ACIrb::ClassQuery.new('fvCEp') 242 | # class_query.page_size = '1' 243 | # class_query.page = '0' 244 | # subscription_id = rest.subscribe(class_query) 245 | # 246 | # Returns the subscription ID for the newly registered subscription 247 | def subscribe(query_obj) 248 | query_obj.subscribe = 'yes' 249 | query_uri = query_obj.uri(@format) 250 | 251 | get_url = self.escape(@baseurl.to_s + query_uri.to_s) 252 | 253 | puts 'GET REQUEST', get_url if @debug 254 | response = @client.get(get_url) 255 | puts 'GET RESPONSE: ', response.body if @debug 256 | 257 | if format == 'xml' 258 | xml_data = response.body 259 | doc = Nokogiri::XML(xml_data) 260 | parse_error(doc) 261 | subscriptionId = doc.at_css('imdata')['subscriptionId'] 262 | elsif format == 'json' 263 | json_data = response.body 264 | doc = JSON.parse(json_data) 265 | parse_error(doc) 266 | subscriptionId = doc['subscriptionId'] 267 | end 268 | 269 | subscriptionId 270 | end 271 | 272 | # Public: Refreshes an existing subscription query 273 | # 274 | # subscription_id - string containing the subscription ID for a previously 275 | # subscribed to query 276 | # 277 | # Examples 278 | # class_query = ACIrb::ClassQuery.new('fvCEp') 279 | # class_query.page_size = '1' 280 | # class_query.page = '0' 281 | # subscription_id = rest.subscribe(class_query) 282 | # sleep(50) 283 | # rest.refresh_subscription(subcription_id) 284 | # 285 | # Returns nothing. 286 | def refresh_subscription(subscription_id) 287 | query_uri = '/api/subscriptionRefresh.%s?id=%s' % [@format, subscription_id] 288 | get(url: query_uri) 289 | end 290 | 291 | # Public: Helper function that performs a simple lookup on a Dn 292 | # 293 | # dn - string containing distinguished name for the object to query 294 | # (required) 295 | # options - Hash options for defining query options (default: {}) 296 | # :subtree - specifies the subtree query options, which can be 297 | # children, full or self 298 | # Examples 299 | # mo = rest.lookupByDn('uni/tn-common', subtree: 'full') 300 | # 301 | # Returns a single ACIrb::MO object or nil if no response for the query 302 | # is received 303 | def lookupByDn(dn, options = {}) 304 | subtree = options[:subtree] 305 | dn_query = ACIrb::DnQuery.new(dn) 306 | dn_query.subtree = subtree 307 | 308 | mos = query(dn_query) 309 | if mos.length == 1 310 | return mos[0] 311 | else 312 | return nil 313 | end 314 | end 315 | 316 | # Public: Helper function that performs a simple lookup on a Class 317 | # 318 | # cls - string containing the class name to query (required) 319 | # options - Hash options for defining query options (default: {}) 320 | # :subtree - specifies the subtree query options, which can be 321 | # children, full or self 322 | # Examples 323 | # # return all L1 physical interfaces on the fabric with complete subtree 324 | # mo = rest.lookupByClass('l1PhysIf', subtree: 'full') 325 | # 326 | # Returns an array of ACIrb::MO objects for the query 327 | def lookupByClass(cls, options = {}) 328 | subtree = options[:subtree] 329 | cls_query = ACIrb::ClassQuery.new(cls) 330 | cls_query.subtree = subtree 331 | query(cls_query) 332 | end 333 | end 334 | end 335 | -------------------------------------------------------------------------------- /examples/acidashboard/assets/javascripts/jquery.knob.js: -------------------------------------------------------------------------------- 1 | /*!jQuery Knob*/ 2 | /** 3 | * Downward compatible, touchable dial 4 | * 5 | * Version: 1.2.0 (15/07/2012) 6 | * Requires: jQuery v1.7+ 7 | * 8 | * Copyright (c) 2012 Anthony Terrien 9 | * Under MIT and GPL licenses: 10 | * http://www.opensource.org/licenses/mit-license.php 11 | * http://www.gnu.org/licenses/gpl.html 12 | * 13 | * Thanks to vor, eskimoblood, spiffistan, FabrizioC 14 | */ 15 | $(function () { 16 | 17 | /** 18 | * Kontrol library 19 | */ 20 | "use strict"; 21 | 22 | /** 23 | * Definition of globals and core 24 | */ 25 | var k = {}, // kontrol 26 | max = Math.max, 27 | min = Math.min; 28 | 29 | k.c = {}; 30 | k.c.d = $(document); 31 | k.c.t = function (e) { 32 | return e.originalEvent.touches.length - 1; 33 | }; 34 | 35 | /** 36 | * Kontrol Object 37 | * 38 | * Definition of an abstract UI control 39 | * 40 | * Each concrete component must call this one. 41 | * 42 | * k.o.call(this); 43 | * 44 | */ 45 | k.o = function () { 46 | var s = this; 47 | 48 | this.o = null; // array of options 49 | this.$ = null; // jQuery wrapped element 50 | this.i = null; // mixed HTMLInputElement or array of HTMLInputElement 51 | this.g = null; // 2D graphics context for 'pre-rendering' 52 | this.v = null; // value ; mixed array or integer 53 | this.cv = null; // change value ; not commited value 54 | this.x = 0; // canvas x position 55 | this.y = 0; // canvas y position 56 | this.$c = null; // jQuery canvas element 57 | this.c = null; // rendered canvas context 58 | this.t = 0; // touches index 59 | this.isInit = false; 60 | this.fgColor = null; // main color 61 | this.pColor = null; // previous color 62 | this.dH = null; // draw hook 63 | this.cH = null; // change hook 64 | this.eH = null; // cancel hook 65 | this.rH = null; // release hook 66 | 67 | this.run = function () { 68 | var cf = function (e, conf) { 69 | var k; 70 | for (k in conf) { 71 | s.o[k] = conf[k]; 72 | } 73 | s.init(); 74 | s._configure() 75 | ._draw(); 76 | }; 77 | 78 | if(this.$.data('kontroled')) return; 79 | this.$.data('kontroled', true); 80 | 81 | this.extend(); 82 | this.o = $.extend( 83 | { 84 | // Config 85 | min : this.$.data('min') || 0, 86 | max : this.$.data('max') || 100, 87 | stopper : true, 88 | readOnly : this.$.data('readonly'), 89 | 90 | // UI 91 | cursor : (this.$.data('cursor') === true && 30) 92 | || this.$.data('cursor') 93 | || 0, 94 | thickness : this.$.data('thickness') || 0.35, 95 | width : this.$.data('width') || 200, 96 | height : this.$.data('height') || 200, 97 | displayInput : this.$.data('displayinput') == null || this.$.data('displayinput'), 98 | displayPrevious : this.$.data('displayprevious'), 99 | fgColor : this.$.data('fgcolor') || '#87CEEB', 100 | inline : false, 101 | 102 | // Hooks 103 | draw : null, // function () {} 104 | change : null, // function (value) {} 105 | cancel : null, // function () {} 106 | release : null // function (value) {} 107 | }, this.o 108 | ); 109 | 110 | // routing value 111 | if(this.$.is('fieldset')) { 112 | 113 | // fieldset = array of integer 114 | this.v = {}; 115 | this.i = this.$.find('input') 116 | this.i.each(function(k) { 117 | var $this = $(this); 118 | s.i[k] = $this; 119 | s.v[k] = $this.val(); 120 | 121 | $this.bind( 122 | 'change' 123 | , function () { 124 | var val = {}; 125 | val[k] = $this.val(); 126 | s.val(val); 127 | } 128 | ); 129 | }); 130 | this.$.find('legend').remove(); 131 | 132 | } else { 133 | // input = integer 134 | this.i = this.$; 135 | this.v = this.$.val(); 136 | (this.v == '') && (this.v = this.o.min); 137 | 138 | this.$.bind( 139 | 'change' 140 | , function () { 141 | s.val(s.$.val()); 142 | } 143 | ); 144 | } 145 | 146 | (!this.o.displayInput) && this.$.hide(); 147 | 148 | this.$c = $(''); 151 | this.c = this.$c[0].getContext("2d"); 152 | 153 | this.$ 154 | .wrap($('
')) 157 | .before(this.$c); 158 | 159 | if (this.v instanceof Object) { 160 | this.cv = {}; 161 | this.copy(this.v, this.cv); 162 | } else { 163 | this.cv = this.v; 164 | } 165 | 166 | this.$ 167 | .bind("configure", cf) 168 | .parent() 169 | .bind("configure", cf); 170 | 171 | this._listen() 172 | ._configure() 173 | ._xy() 174 | .init(); 175 | 176 | this.isInit = true; 177 | 178 | this._draw(); 179 | 180 | return this; 181 | }; 182 | 183 | this._draw = function () { 184 | 185 | // canvas pre-rendering 186 | var d = true, 187 | c = document.createElement('canvas'); 188 | 189 | c.width = s.o.width; 190 | c.height = s.o.height; 191 | s.g = c.getContext('2d'); 192 | 193 | s.clear(); 194 | 195 | s.dH 196 | && (d = s.dH()); 197 | 198 | (d !== false) && s.draw(); 199 | 200 | s.c.drawImage(c, 0, 0); 201 | c = null; 202 | }; 203 | 204 | this._touch = function (e) { 205 | 206 | var touchMove = function (e) { 207 | 208 | var v = s.xy2val( 209 | e.originalEvent.touches[s.t].pageX, 210 | e.originalEvent.touches[s.t].pageY 211 | ); 212 | 213 | if (v == s.cv) return; 214 | 215 | if ( 216 | s.cH 217 | && (s.cH(v) === false) 218 | ) return; 219 | 220 | 221 | s.change(v); 222 | s._draw(); 223 | }; 224 | 225 | // get touches index 226 | this.t = k.c.t(e); 227 | 228 | // First touch 229 | touchMove(e); 230 | 231 | // Touch events listeners 232 | k.c.d 233 | .bind("touchmove.k", touchMove) 234 | .bind( 235 | "touchend.k" 236 | , function () { 237 | k.c.d.unbind('touchmove.k touchend.k'); 238 | 239 | if ( 240 | s.rH 241 | && (s.rH(s.cv) === false) 242 | ) return; 243 | 244 | s.val(s.cv); 245 | } 246 | ); 247 | 248 | return this; 249 | }; 250 | 251 | this._mouse = function (e) { 252 | 253 | var mouseMove = function (e) { 254 | var v = s.xy2val(e.pageX, e.pageY); 255 | if (v == s.cv) return; 256 | 257 | if ( 258 | s.cH 259 | && (s.cH(v) === false) 260 | ) return; 261 | 262 | s.change(v); 263 | s._draw(); 264 | }; 265 | 266 | // First click 267 | mouseMove(e); 268 | 269 | // Mouse events listeners 270 | k.c.d 271 | .bind("mousemove.k", mouseMove) 272 | .bind( 273 | // Escape key cancel current change 274 | "keyup.k" 275 | , function (e) { 276 | if (e.keyCode === 27) { 277 | k.c.d.unbind("mouseup.k mousemove.k keyup.k"); 278 | 279 | if ( 280 | s.eH 281 | && (s.eH() === false) 282 | ) return; 283 | 284 | s.cancel(); 285 | } 286 | } 287 | ) 288 | .bind( 289 | "mouseup.k" 290 | , function (e) { 291 | k.c.d.unbind('mousemove.k mouseup.k keyup.k'); 292 | 293 | if ( 294 | s.rH 295 | && (s.rH(s.cv) === false) 296 | ) return; 297 | 298 | s.val(s.cv); 299 | } 300 | ); 301 | 302 | return this; 303 | }; 304 | 305 | this._xy = function () { 306 | var o = this.$c.offset(); 307 | this.x = o.left; 308 | this.y = o.top; 309 | return this; 310 | }; 311 | 312 | this._listen = function () { 313 | 314 | if (!this.o.readOnly) { 315 | this.$c 316 | .bind( 317 | "mousedown" 318 | , function (e) { 319 | e.preventDefault(); 320 | s._xy()._mouse(e); 321 | } 322 | ) 323 | .bind( 324 | "touchstart" 325 | , function (e) { 326 | e.preventDefault(); 327 | s._xy()._touch(e); 328 | } 329 | ); 330 | this.listen(); 331 | } else { 332 | this.$.attr('readonly', 'readonly'); 333 | } 334 | 335 | return this; 336 | }; 337 | 338 | this._configure = function () { 339 | 340 | // Hooks 341 | if (this.o.draw) this.dH = this.o.draw; 342 | if (this.o.change) this.cH = this.o.change; 343 | if (this.o.cancel) this.eH = this.o.cancel; 344 | if (this.o.release) this.rH = this.o.release; 345 | 346 | if (this.o.displayPrevious) { 347 | this.pColor = this.h2rgba(this.o.fgColor, "0.4"); 348 | this.fgColor = this.h2rgba(this.o.fgColor, "0.6"); 349 | } else { 350 | this.fgColor = this.o.fgColor; 351 | } 352 | 353 | return this; 354 | }; 355 | 356 | this._clear = function () { 357 | this.$c[0].width = this.$c[0].width; 358 | }; 359 | 360 | // Abstract methods 361 | this.listen = function () {}; // on start, one time 362 | this.extend = function () {}; // each time configure triggered 363 | this.init = function () {}; // each time configure triggered 364 | this.change = function (v) {}; // on change 365 | this.val = function (v) {}; // on release 366 | this.xy2val = function (x, y) {}; // 367 | this.draw = function () {}; // on change / on release 368 | this.clear = function () { this._clear(); }; 369 | 370 | // Utils 371 | this.h2rgba = function (h, a) { 372 | var rgb; 373 | h = h.substring(1,7) 374 | rgb = [parseInt(h.substring(0,2),16) 375 | ,parseInt(h.substring(2,4),16) 376 | ,parseInt(h.substring(4,6),16)]; 377 | return "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "," + a + ")"; 378 | }; 379 | 380 | this.copy = function (f, t) { 381 | for (var i in f) { t[i] = f[i]; } 382 | }; 383 | }; 384 | 385 | 386 | /** 387 | * k.Dial 388 | */ 389 | k.Dial = function () { 390 | k.o.call(this); 391 | 392 | this.startAngle = null; 393 | this.xy = null; 394 | this.radius = null; 395 | this.lineWidth = null; 396 | this.cursorExt = null; 397 | this.w2 = null; 398 | this.PI2 = 2*Math.PI; 399 | 400 | this.extend = function () { 401 | this.o = $.extend( 402 | { 403 | bgColor : this.$.data('bgcolor') || '#EEEEEE', 404 | angleOffset : this.$.data('angleoffset') || 0, 405 | angleArc : this.$.data('anglearc') || 360, 406 | inline : true 407 | }, this.o 408 | ); 409 | }; 410 | 411 | this.val = function (v) { 412 | if (null != v) { 413 | this.cv = this.o.stopper ? max(min(v, this.o.max), this.o.min) : v; 414 | this.v = this.cv; 415 | this.$.val(this.v); 416 | this._draw(); 417 | } else { 418 | return this.v; 419 | } 420 | }; 421 | 422 | this.xy2val = function (x, y) { 423 | var a, ret; 424 | 425 | a = Math.atan2( 426 | x - (this.x + this.w2) 427 | , - (y - this.y - this.w2) 428 | ) - this.angleOffset; 429 | 430 | if(this.angleArc != this.PI2 && (a < 0) && (a > -0.5)) { 431 | // if isset angleArc option, set to min if .5 under min 432 | a = 0; 433 | } else if (a < 0) { 434 | a += this.PI2; 435 | } 436 | 437 | ret = ~~ (0.5 + (a * (this.o.max - this.o.min) / this.angleArc)) 438 | + this.o.min; 439 | 440 | this.o.stopper 441 | && (ret = max(min(ret, this.o.max), this.o.min)); 442 | 443 | return ret; 444 | }; 445 | 446 | this.listen = function () { 447 | // bind MouseWheel 448 | var s = this, 449 | mw = function (e) { 450 | e.preventDefault(); 451 | 452 | var ori = e.originalEvent 453 | ,deltaX = ori.detail || ori.wheelDeltaX 454 | ,deltaY = ori.detail || ori.wheelDeltaY 455 | ,v = parseInt(s.$.val()) + (deltaX>0 || deltaY>0 ? 1 : deltaX<0 || deltaY<0 ? -1 : 0); 456 | 457 | if ( 458 | s.cH 459 | && (s.cH(v) === false) 460 | ) return; 461 | 462 | s.val(v); 463 | } 464 | , kval, to, m = 1, kv = {37:-1, 38:1, 39:1, 40:-1}; 465 | 466 | this.$ 467 | .bind( 468 | "keydown" 469 | ,function (e) { 470 | var kc = e.keyCode; 471 | kval = parseInt(String.fromCharCode(kc)); 472 | 473 | if (isNaN(kval)) { 474 | 475 | (kc !== 13) // enter 476 | && (kc !== 8) // bs 477 | && (kc !== 9) // tab 478 | && (kc !== 189) // - 479 | && e.preventDefault(); 480 | 481 | // arrows 482 | if ($.inArray(kc,[37,38,39,40]) > -1) { 483 | e.preventDefault(); 484 | 485 | var v = parseInt(s.$.val()) + kv[kc] * m; 486 | 487 | s.o.stopper 488 | && (v = max(min(v, s.o.max), s.o.min)); 489 | 490 | s.change(v); 491 | s._draw(); 492 | 493 | // long time keydown speed-up 494 | to = window.setTimeout( 495 | function () { m*=2; } 496 | ,30 497 | ); 498 | } 499 | } 500 | } 501 | ) 502 | .bind( 503 | "keyup" 504 | ,function (e) { 505 | if (isNaN(kval)) { 506 | if (to) { 507 | window.clearTimeout(to); 508 | to = null; 509 | m = 1; 510 | s.val(s.$.val()); 511 | } 512 | } else { 513 | // kval postcond 514 | (s.$.val() > s.o.max && s.$.val(s.o.max)) 515 | || (s.$.val() < s.o.min && s.$.val(s.o.min)); 516 | } 517 | 518 | } 519 | ); 520 | 521 | this.$c.bind("mousewheel DOMMouseScroll", mw); 522 | this.$.bind("mousewheel DOMMouseScroll", mw) 523 | }; 524 | 525 | this.init = function () { 526 | 527 | if ( 528 | this.v < this.o.min 529 | || this.v > this.o.max 530 | ) this.v = this.o.min; 531 | 532 | this.$.val(this.v); 533 | this.w2 = this.o.width / 2; 534 | this.cursorExt = this.o.cursor / 100; 535 | this.xy = this.w2; 536 | this.lineWidth = this.xy * this.o.thickness; 537 | this.radius = this.xy - this.lineWidth / 2; 538 | 539 | this.o.angleOffset 540 | && (this.o.angleOffset = isNaN(this.o.angleOffset) ? 0 : this.o.angleOffset); 541 | 542 | this.o.angleArc 543 | && (this.o.angleArc = isNaN(this.o.angleArc) ? this.PI2 : this.o.angleArc); 544 | 545 | // deg to rad 546 | this.angleOffset = this.o.angleOffset * Math.PI / 180; 547 | this.angleArc = this.o.angleArc * Math.PI / 180; 548 | 549 | // compute start and end angles 550 | this.startAngle = 1.5 * Math.PI + this.angleOffset; 551 | this.endAngle = 1.5 * Math.PI + this.angleOffset + this.angleArc; 552 | 553 | var s = max( 554 | String(Math.abs(this.o.max)).length 555 | , String(Math.abs(this.o.min)).length 556 | , 2 557 | ) + 2; 558 | 559 | this.o.displayInput 560 | && this.i.css({ 561 | 'width' : ((this.o.width / 2 + 4) >> 0) + 'px' 562 | ,'height' : ((this.o.width / 3) >> 0) + 'px' 563 | ,'position' : 'absolute' 564 | ,'vertical-align' : 'middle' 565 | ,'margin-top' : ((this.o.width / 3) >> 0) + 'px' 566 | ,'margin-left' : '-' + ((this.o.width * 3 / 4 + 2) >> 0) + 'px' 567 | ,'border' : 0 568 | ,'background' : 'none' 569 | ,'font' : 'bold ' + ((this.o.width / s) >> 0) + 'px Arial' 570 | ,'text-align' : 'center' 571 | ,'color' : this.o.fgColor 572 | ,'padding' : '0px' 573 | ,'-webkit-appearance': 'none' 574 | }) 575 | || this.i.css({ 576 | 'width' : '0px' 577 | ,'visibility' : 'hidden' 578 | }); 579 | }; 580 | 581 | this.change = function (v) { 582 | this.cv = v; 583 | this.$.val(v); 584 | }; 585 | 586 | this.angle = function (v) { 587 | return (v - this.o.min) * this.angleArc / (this.o.max - this.o.min); 588 | }; 589 | 590 | this.draw = function () { 591 | 592 | var c = this.g, // context 593 | a = this.angle(this.cv) // Angle 594 | , sat = this.startAngle // Start angle 595 | , eat = sat + a // End angle 596 | , sa, ea // Previous angles 597 | , r = 1; 598 | 599 | c.lineWidth = this.lineWidth; 600 | 601 | this.o.cursor 602 | && (sat = eat - this.cursorExt) 603 | && (eat = eat + this.cursorExt); 604 | 605 | c.beginPath(); 606 | c.strokeStyle = this.o.bgColor; 607 | c.arc(this.xy, this.xy, this.radius, this.endAngle, this.startAngle, true); 608 | c.stroke(); 609 | 610 | if (this.o.displayPrevious) { 611 | ea = this.startAngle + this.angle(this.v); 612 | sa = this.startAngle; 613 | this.o.cursor 614 | && (sa = ea - this.cursorExt) 615 | && (ea = ea + this.cursorExt); 616 | 617 | c.beginPath(); 618 | c.strokeStyle = this.pColor; 619 | c.arc(this.xy, this.xy, this.radius, sa, ea, false); 620 | c.stroke(); 621 | r = (this.cv == this.v); 622 | } 623 | 624 | c.beginPath(); 625 | c.strokeStyle = r ? this.o.fgColor : this.fgColor ; 626 | c.arc(this.xy, this.xy, this.radius, sat, eat, false); 627 | c.stroke(); 628 | }; 629 | 630 | this.cancel = function () { 631 | this.val(this.v); 632 | }; 633 | }; 634 | 635 | $.fn.dial = $.fn.knob = function (o) { 636 | return this.each( 637 | function () { 638 | var d = new k.Dial(); 639 | d.o = o; 640 | d.$ = $(this); 641 | d.run(); 642 | } 643 | ).parent(); 644 | }; 645 | 646 | }); -------------------------------------------------------------------------------- /examples/acidashboard/assets/stylesheets/font-awesome.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 3.2.1 3 | * the iconic font designed for Bootstrap 4 | * ------------------------------------------------------------------------------ 5 | * The full suite of pictographic icons, examples, and documentation can be 6 | * found at http://fontawesome.io. Stay up to date on Twitter at 7 | * http://twitter.com/fontawesome. 8 | * 9 | * License 10 | * ------------------------------------------------------------------------------ 11 | * - The Font Awesome font is licensed under SIL OFL 1.1 - 12 | * http://scripts.sil.org/OFL 13 | * - Font Awesome CSS, LESS, and SASS files are licensed under MIT License - 14 | * http://opensource.org/licenses/mit-license.html 15 | * - Font Awesome documentation licensed under CC BY 3.0 - 16 | * http://creativecommons.org/licenses/by/3.0/ 17 | * - Attribution is no longer required in Font Awesome 3.0, but much appreciated: 18 | * "Font Awesome by Dave Gandy - http://fontawesome.io" 19 | * 20 | * Author - Dave Gandy 21 | * ------------------------------------------------------------------------------ 22 | * Email: dave@fontawesome.io 23 | * Twitter: http://twitter.com/davegandy 24 | * Work: Lead Product Designer @ Kyruus - http://kyruus.com 25 | */ 26 | /* FONT PATH 27 | * -------------------------- */ 28 | @font-face { 29 | font-family: 'FontAwesome'; 30 | src: url('../assets/fontawesome-webfont.eot?v=3.2.1'); 31 | src: url('../assets/fontawesome-webfont.eot?#iefix&v=3.2.1') format('embedded-opentype'), url('../assets/fontawesome-webfont.woff?v=3.2.1') format('woff'), url('../assets/fontawesome-webfont.ttf?v=3.2.1') format('truetype'), url('../assets/fontawesome-webfont.svg#fontawesomeregular?v=3.2.1') format('svg'); 32 | font-weight: normal; 33 | font-style: normal; 34 | } 35 | /* FONT AWESOME CORE 36 | * -------------------------- */ 37 | [class^="icon-"], 38 | [class*=" icon-"] { 39 | font-family: FontAwesome; 40 | font-weight: normal; 41 | font-style: normal; 42 | text-decoration: inherit; 43 | -webkit-font-smoothing: antialiased; 44 | *margin-right: .3em; 45 | } 46 | [class^="icon-"]:before, 47 | [class*=" icon-"]:before { 48 | text-decoration: inherit; 49 | display: inline-block; 50 | speak: none; 51 | } 52 | /* makes the font 33% larger relative to the icon container */ 53 | .icon-large:before { 54 | vertical-align: -10%; 55 | font-size: 1.3333333333333333em; 56 | } 57 | /* makes sure icons active on rollover in links */ 58 | a [class^="icon-"], 59 | a [class*=" icon-"] { 60 | display: inline; 61 | } 62 | /* increased font size for icon-large */ 63 | [class^="icon-"].icon-fixed-width, 64 | [class*=" icon-"].icon-fixed-width { 65 | display: inline-block; 66 | width: 1.1428571428571428em; 67 | text-align: right; 68 | padding-right: 0.2857142857142857em; 69 | } 70 | [class^="icon-"].icon-fixed-width.icon-large, 71 | [class*=" icon-"].icon-fixed-width.icon-large { 72 | width: 1.4285714285714286em; 73 | } 74 | .icons-ul { 75 | margin-left: 2.142857142857143em; 76 | list-style-type: none; 77 | } 78 | .icons-ul > li { 79 | position: relative; 80 | } 81 | .icons-ul .icon-li { 82 | position: absolute; 83 | left: -2.142857142857143em; 84 | width: 2.142857142857143em; 85 | text-align: center; 86 | line-height: inherit; 87 | } 88 | [class^="icon-"].hide, 89 | [class*=" icon-"].hide { 90 | display: none; 91 | } 92 | .icon-muted { 93 | color: #eeeeee; 94 | } 95 | .icon-light { 96 | color: #ffffff; 97 | } 98 | .icon-dark { 99 | color: #333333; 100 | } 101 | .icon-border { 102 | border: solid 1px #eeeeee; 103 | padding: .2em .25em .15em; 104 | -webkit-border-radius: 3px; 105 | -moz-border-radius: 3px; 106 | border-radius: 3px; 107 | } 108 | .icon-2x { 109 | font-size: 2em; 110 | } 111 | .icon-2x.icon-border { 112 | border-width: 2px; 113 | -webkit-border-radius: 4px; 114 | -moz-border-radius: 4px; 115 | border-radius: 4px; 116 | } 117 | .icon-3x { 118 | font-size: 3em; 119 | } 120 | .icon-3x.icon-border { 121 | border-width: 3px; 122 | -webkit-border-radius: 5px; 123 | -moz-border-radius: 5px; 124 | border-radius: 5px; 125 | } 126 | .icon-4x { 127 | font-size: 4em; 128 | } 129 | .icon-4x.icon-border { 130 | border-width: 4px; 131 | -webkit-border-radius: 6px; 132 | -moz-border-radius: 6px; 133 | border-radius: 6px; 134 | } 135 | .icon-5x { 136 | font-size: 5em; 137 | } 138 | .icon-5x.icon-border { 139 | border-width: 5px; 140 | -webkit-border-radius: 7px; 141 | -moz-border-radius: 7px; 142 | border-radius: 7px; 143 | } 144 | .pull-right { 145 | float: right; 146 | } 147 | .pull-left { 148 | float: left; 149 | } 150 | [class^="icon-"].pull-left, 151 | [class*=" icon-"].pull-left { 152 | margin-right: .3em; 153 | } 154 | [class^="icon-"].pull-right, 155 | [class*=" icon-"].pull-right { 156 | margin-left: .3em; 157 | } 158 | /* BOOTSTRAP SPECIFIC CLASSES 159 | * -------------------------- */ 160 | /* Bootstrap 2.0 sprites.less reset */ 161 | [class^="icon-"], 162 | [class*=" icon-"] { 163 | display: inline; 164 | width: auto; 165 | height: auto; 166 | line-height: normal; 167 | vertical-align: baseline; 168 | background-image: none; 169 | background-position: 0% 0%; 170 | background-repeat: repeat; 171 | margin-top: 0; 172 | } 173 | /* more sprites.less reset */ 174 | .icon-white, 175 | .nav-pills > .active > a > [class^="icon-"], 176 | .nav-pills > .active > a > [class*=" icon-"], 177 | .nav-list > .active > a > [class^="icon-"], 178 | .nav-list > .active > a > [class*=" icon-"], 179 | .navbar-inverse .nav > .active > a > [class^="icon-"], 180 | .navbar-inverse .nav > .active > a > [class*=" icon-"], 181 | .dropdown-menu > li > a:hover > [class^="icon-"], 182 | .dropdown-menu > li > a:hover > [class*=" icon-"], 183 | .dropdown-menu > .active > a > [class^="icon-"], 184 | .dropdown-menu > .active > a > [class*=" icon-"], 185 | .dropdown-submenu:hover > a > [class^="icon-"], 186 | .dropdown-submenu:hover > a > [class*=" icon-"] { 187 | background-image: none; 188 | } 189 | /* keeps Bootstrap styles with and without icons the same */ 190 | .btn [class^="icon-"].icon-large, 191 | .nav [class^="icon-"].icon-large, 192 | .btn [class*=" icon-"].icon-large, 193 | .nav [class*=" icon-"].icon-large { 194 | line-height: .9em; 195 | } 196 | .btn [class^="icon-"].icon-spin, 197 | .nav [class^="icon-"].icon-spin, 198 | .btn [class*=" icon-"].icon-spin, 199 | .nav [class*=" icon-"].icon-spin { 200 | display: inline-block; 201 | } 202 | .nav-tabs [class^="icon-"], 203 | .nav-pills [class^="icon-"], 204 | .nav-tabs [class*=" icon-"], 205 | .nav-pills [class*=" icon-"], 206 | .nav-tabs [class^="icon-"].icon-large, 207 | .nav-pills [class^="icon-"].icon-large, 208 | .nav-tabs [class*=" icon-"].icon-large, 209 | .nav-pills [class*=" icon-"].icon-large { 210 | line-height: .9em; 211 | } 212 | .btn [class^="icon-"].pull-left.icon-2x, 213 | .btn [class*=" icon-"].pull-left.icon-2x, 214 | .btn [class^="icon-"].pull-right.icon-2x, 215 | .btn [class*=" icon-"].pull-right.icon-2x { 216 | margin-top: .18em; 217 | } 218 | .btn [class^="icon-"].icon-spin.icon-large, 219 | .btn [class*=" icon-"].icon-spin.icon-large { 220 | line-height: .8em; 221 | } 222 | .btn.btn-small [class^="icon-"].pull-left.icon-2x, 223 | .btn.btn-small [class*=" icon-"].pull-left.icon-2x, 224 | .btn.btn-small [class^="icon-"].pull-right.icon-2x, 225 | .btn.btn-small [class*=" icon-"].pull-right.icon-2x { 226 | margin-top: .25em; 227 | } 228 | .btn.btn-large [class^="icon-"], 229 | .btn.btn-large [class*=" icon-"] { 230 | margin-top: 0; 231 | } 232 | .btn.btn-large [class^="icon-"].pull-left.icon-2x, 233 | .btn.btn-large [class*=" icon-"].pull-left.icon-2x, 234 | .btn.btn-large [class^="icon-"].pull-right.icon-2x, 235 | .btn.btn-large [class*=" icon-"].pull-right.icon-2x { 236 | margin-top: .05em; 237 | } 238 | .btn.btn-large [class^="icon-"].pull-left.icon-2x, 239 | .btn.btn-large [class*=" icon-"].pull-left.icon-2x { 240 | margin-right: .2em; 241 | } 242 | .btn.btn-large [class^="icon-"].pull-right.icon-2x, 243 | .btn.btn-large [class*=" icon-"].pull-right.icon-2x { 244 | margin-left: .2em; 245 | } 246 | /* Fixes alignment in nav lists */ 247 | .nav-list [class^="icon-"], 248 | .nav-list [class*=" icon-"] { 249 | line-height: inherit; 250 | } 251 | /* EXTRAS 252 | * -------------------------- */ 253 | /* Stacked and layered icon */ 254 | .icon-stack { 255 | position: relative; 256 | display: inline-block; 257 | width: 2em; 258 | height: 2em; 259 | line-height: 2em; 260 | vertical-align: -35%; 261 | } 262 | .icon-stack [class^="icon-"], 263 | .icon-stack [class*=" icon-"] { 264 | display: block; 265 | text-align: center; 266 | position: absolute; 267 | width: 100%; 268 | height: 100%; 269 | font-size: 1em; 270 | line-height: inherit; 271 | *line-height: 2em; 272 | } 273 | .icon-stack .icon-stack-base { 274 | font-size: 2em; 275 | *line-height: 1em; 276 | } 277 | /* Animated rotating icon */ 278 | .icon-spin { 279 | display: inline-block; 280 | -moz-animation: spin 2s infinite linear; 281 | -o-animation: spin 2s infinite linear; 282 | -webkit-animation: spin 2s infinite linear; 283 | animation: spin 2s infinite linear; 284 | } 285 | /* Prevent stack and spinners from being taken inline when inside a link */ 286 | a .icon-stack, 287 | a .icon-spin { 288 | display: inline-block; 289 | text-decoration: none; 290 | } 291 | @-moz-keyframes spin { 292 | 0% { 293 | -moz-transform: rotate(0deg); 294 | } 295 | 100% { 296 | -moz-transform: rotate(359deg); 297 | } 298 | } 299 | @-webkit-keyframes spin { 300 | 0% { 301 | -webkit-transform: rotate(0deg); 302 | } 303 | 100% { 304 | -webkit-transform: rotate(359deg); 305 | } 306 | } 307 | @-o-keyframes spin { 308 | 0% { 309 | -o-transform: rotate(0deg); 310 | } 311 | 100% { 312 | -o-transform: rotate(359deg); 313 | } 314 | } 315 | @-ms-keyframes spin { 316 | 0% { 317 | -ms-transform: rotate(0deg); 318 | } 319 | 100% { 320 | -ms-transform: rotate(359deg); 321 | } 322 | } 323 | @keyframes spin { 324 | 0% { 325 | transform: rotate(0deg); 326 | } 327 | 100% { 328 | transform: rotate(359deg); 329 | } 330 | } 331 | /* Icon rotations and mirroring */ 332 | .icon-rotate-90:before { 333 | -webkit-transform: rotate(90deg); 334 | -moz-transform: rotate(90deg); 335 | -ms-transform: rotate(90deg); 336 | -o-transform: rotate(90deg); 337 | transform: rotate(90deg); 338 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); 339 | } 340 | .icon-rotate-180:before { 341 | -webkit-transform: rotate(180deg); 342 | -moz-transform: rotate(180deg); 343 | -ms-transform: rotate(180deg); 344 | -o-transform: rotate(180deg); 345 | transform: rotate(180deg); 346 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); 347 | } 348 | .icon-rotate-270:before { 349 | -webkit-transform: rotate(270deg); 350 | -moz-transform: rotate(270deg); 351 | -ms-transform: rotate(270deg); 352 | -o-transform: rotate(270deg); 353 | transform: rotate(270deg); 354 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); 355 | } 356 | .icon-flip-horizontal:before { 357 | -webkit-transform: scale(-1, 1); 358 | -moz-transform: scale(-1, 1); 359 | -ms-transform: scale(-1, 1); 360 | -o-transform: scale(-1, 1); 361 | transform: scale(-1, 1); 362 | } 363 | .icon-flip-vertical:before { 364 | -webkit-transform: scale(1, -1); 365 | -moz-transform: scale(1, -1); 366 | -ms-transform: scale(1, -1); 367 | -o-transform: scale(1, -1); 368 | transform: scale(1, -1); 369 | } 370 | /* ensure rotation occurs inside anchor tags */ 371 | a .icon-rotate-90:before, 372 | a .icon-rotate-180:before, 373 | a .icon-rotate-270:before, 374 | a .icon-flip-horizontal:before, 375 | a .icon-flip-vertical:before { 376 | display: inline-block; 377 | } 378 | /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen 379 | readers do not read off random characters that represent icons */ 380 | .icon-glass:before { 381 | content: "\f000"; 382 | } 383 | .icon-music:before { 384 | content: "\f001"; 385 | } 386 | .icon-search:before { 387 | content: "\f002"; 388 | } 389 | .icon-envelope-alt:before { 390 | content: "\f003"; 391 | } 392 | .icon-heart:before { 393 | content: "\f004"; 394 | } 395 | .icon-star:before { 396 | content: "\f005"; 397 | } 398 | .icon-star-empty:before { 399 | content: "\f006"; 400 | } 401 | .icon-user:before { 402 | content: "\f007"; 403 | } 404 | .icon-film:before { 405 | content: "\f008"; 406 | } 407 | .icon-th-large:before { 408 | content: "\f009"; 409 | } 410 | .icon-th:before { 411 | content: "\f00a"; 412 | } 413 | .icon-th-list:before { 414 | content: "\f00b"; 415 | } 416 | .icon-ok:before { 417 | content: "\f00c"; 418 | } 419 | .icon-remove:before { 420 | content: "\f00d"; 421 | } 422 | .icon-zoom-in:before { 423 | content: "\f00e"; 424 | } 425 | .icon-zoom-out:before { 426 | content: "\f010"; 427 | } 428 | .icon-power-off:before, 429 | .icon-off:before { 430 | content: "\f011"; 431 | } 432 | .icon-signal:before { 433 | content: "\f012"; 434 | } 435 | .icon-gear:before, 436 | .icon-cog:before { 437 | content: "\f013"; 438 | } 439 | .icon-trash:before { 440 | content: "\f014"; 441 | } 442 | .icon-home:before { 443 | content: "\f015"; 444 | } 445 | .icon-file-alt:before { 446 | content: "\f016"; 447 | } 448 | .icon-time:before { 449 | content: "\f017"; 450 | } 451 | .icon-road:before { 452 | content: "\f018"; 453 | } 454 | .icon-download-alt:before { 455 | content: "\f019"; 456 | } 457 | .icon-download:before { 458 | content: "\f01a"; 459 | } 460 | .icon-upload:before { 461 | content: "\f01b"; 462 | } 463 | .icon-inbox:before { 464 | content: "\f01c"; 465 | } 466 | .icon-play-circle:before { 467 | content: "\f01d"; 468 | } 469 | .icon-rotate-right:before, 470 | .icon-repeat:before { 471 | content: "\f01e"; 472 | } 473 | .icon-refresh:before { 474 | content: "\f021"; 475 | } 476 | .icon-list-alt:before { 477 | content: "\f022"; 478 | } 479 | .icon-lock:before { 480 | content: "\f023"; 481 | } 482 | .icon-flag:before { 483 | content: "\f024"; 484 | } 485 | .icon-headphones:before { 486 | content: "\f025"; 487 | } 488 | .icon-volume-off:before { 489 | content: "\f026"; 490 | } 491 | .icon-volume-down:before { 492 | content: "\f027"; 493 | } 494 | .icon-volume-up:before { 495 | content: "\f028"; 496 | } 497 | .icon-qrcode:before { 498 | content: "\f029"; 499 | } 500 | .icon-barcode:before { 501 | content: "\f02a"; 502 | } 503 | .icon-tag:before { 504 | content: "\f02b"; 505 | } 506 | .icon-tags:before { 507 | content: "\f02c"; 508 | } 509 | .icon-book:before { 510 | content: "\f02d"; 511 | } 512 | .icon-bookmark:before { 513 | content: "\f02e"; 514 | } 515 | .icon-print:before { 516 | content: "\f02f"; 517 | } 518 | .icon-camera:before { 519 | content: "\f030"; 520 | } 521 | .icon-font:before { 522 | content: "\f031"; 523 | } 524 | .icon-bold:before { 525 | content: "\f032"; 526 | } 527 | .icon-italic:before { 528 | content: "\f033"; 529 | } 530 | .icon-text-height:before { 531 | content: "\f034"; 532 | } 533 | .icon-text-width:before { 534 | content: "\f035"; 535 | } 536 | .icon-align-left:before { 537 | content: "\f036"; 538 | } 539 | .icon-align-center:before { 540 | content: "\f037"; 541 | } 542 | .icon-align-right:before { 543 | content: "\f038"; 544 | } 545 | .icon-align-justify:before { 546 | content: "\f039"; 547 | } 548 | .icon-list:before { 549 | content: "\f03a"; 550 | } 551 | .icon-indent-left:before { 552 | content: "\f03b"; 553 | } 554 | .icon-indent-right:before { 555 | content: "\f03c"; 556 | } 557 | .icon-facetime-video:before { 558 | content: "\f03d"; 559 | } 560 | .icon-picture:before { 561 | content: "\f03e"; 562 | } 563 | .icon-pencil:before { 564 | content: "\f040"; 565 | } 566 | .icon-map-marker:before { 567 | content: "\f041"; 568 | } 569 | .icon-adjust:before { 570 | content: "\f042"; 571 | } 572 | .icon-tint:before { 573 | content: "\f043"; 574 | } 575 | .icon-edit:before { 576 | content: "\f044"; 577 | } 578 | .icon-share:before { 579 | content: "\f045"; 580 | } 581 | .icon-check:before { 582 | content: "\f046"; 583 | } 584 | .icon-move:before { 585 | content: "\f047"; 586 | } 587 | .icon-step-backward:before { 588 | content: "\f048"; 589 | } 590 | .icon-fast-backward:before { 591 | content: "\f049"; 592 | } 593 | .icon-backward:before { 594 | content: "\f04a"; 595 | } 596 | .icon-play:before { 597 | content: "\f04b"; 598 | } 599 | .icon-pause:before { 600 | content: "\f04c"; 601 | } 602 | .icon-stop:before { 603 | content: "\f04d"; 604 | } 605 | .icon-forward:before { 606 | content: "\f04e"; 607 | } 608 | .icon-fast-forward:before { 609 | content: "\f050"; 610 | } 611 | .icon-step-forward:before { 612 | content: "\f051"; 613 | } 614 | .icon-eject:before { 615 | content: "\f052"; 616 | } 617 | .icon-chevron-left:before { 618 | content: "\f053"; 619 | } 620 | .icon-chevron-right:before { 621 | content: "\f054"; 622 | } 623 | .icon-plus-sign:before { 624 | content: "\f055"; 625 | } 626 | .icon-minus-sign:before { 627 | content: "\f056"; 628 | } 629 | .icon-remove-sign:before { 630 | content: "\f057"; 631 | } 632 | .icon-ok-sign:before { 633 | content: "\f058"; 634 | } 635 | .icon-question-sign:before { 636 | content: "\f059"; 637 | } 638 | .icon-info-sign:before { 639 | content: "\f05a"; 640 | } 641 | .icon-screenshot:before { 642 | content: "\f05b"; 643 | } 644 | .icon-remove-circle:before { 645 | content: "\f05c"; 646 | } 647 | .icon-ok-circle:before { 648 | content: "\f05d"; 649 | } 650 | .icon-ban-circle:before { 651 | content: "\f05e"; 652 | } 653 | .icon-arrow-left:before { 654 | content: "\f060"; 655 | } 656 | .icon-arrow-right:before { 657 | content: "\f061"; 658 | } 659 | .icon-arrow-up:before { 660 | content: "\f062"; 661 | } 662 | .icon-arrow-down:before { 663 | content: "\f063"; 664 | } 665 | .icon-mail-forward:before, 666 | .icon-share-alt:before { 667 | content: "\f064"; 668 | } 669 | .icon-resize-full:before { 670 | content: "\f065"; 671 | } 672 | .icon-resize-small:before { 673 | content: "\f066"; 674 | } 675 | .icon-plus:before { 676 | content: "\f067"; 677 | } 678 | .icon-minus:before { 679 | content: "\f068"; 680 | } 681 | .icon-asterisk:before { 682 | content: "\f069"; 683 | } 684 | .icon-exclamation-sign:before { 685 | content: "\f06a"; 686 | } 687 | .icon-gift:before { 688 | content: "\f06b"; 689 | } 690 | .icon-leaf:before { 691 | content: "\f06c"; 692 | } 693 | .icon-fire:before { 694 | content: "\f06d"; 695 | } 696 | .icon-eye-open:before { 697 | content: "\f06e"; 698 | } 699 | .icon-eye-close:before { 700 | content: "\f070"; 701 | } 702 | .icon-warning-sign:before { 703 | content: "\f071"; 704 | } 705 | .icon-plane:before { 706 | content: "\f072"; 707 | } 708 | .icon-calendar:before { 709 | content: "\f073"; 710 | } 711 | .icon-random:before { 712 | content: "\f074"; 713 | } 714 | .icon-comment:before { 715 | content: "\f075"; 716 | } 717 | .icon-magnet:before { 718 | content: "\f076"; 719 | } 720 | .icon-chevron-up:before { 721 | content: "\f077"; 722 | } 723 | .icon-chevron-down:before { 724 | content: "\f078"; 725 | } 726 | .icon-retweet:before { 727 | content: "\f079"; 728 | } 729 | .icon-shopping-cart:before { 730 | content: "\f07a"; 731 | } 732 | .icon-folder-close:before { 733 | content: "\f07b"; 734 | } 735 | .icon-folder-open:before { 736 | content: "\f07c"; 737 | } 738 | .icon-resize-vertical:before { 739 | content: "\f07d"; 740 | } 741 | .icon-resize-horizontal:before { 742 | content: "\f07e"; 743 | } 744 | .icon-bar-chart:before { 745 | content: "\f080"; 746 | } 747 | .icon-twitter-sign:before { 748 | content: "\f081"; 749 | } 750 | .icon-facebook-sign:before { 751 | content: "\f082"; 752 | } 753 | .icon-camera-retro:before { 754 | content: "\f083"; 755 | } 756 | .icon-key:before { 757 | content: "\f084"; 758 | } 759 | .icon-gears:before, 760 | .icon-cogs:before { 761 | content: "\f085"; 762 | } 763 | .icon-comments:before { 764 | content: "\f086"; 765 | } 766 | .icon-thumbs-up-alt:before { 767 | content: "\f087"; 768 | } 769 | .icon-thumbs-down-alt:before { 770 | content: "\f088"; 771 | } 772 | .icon-star-half:before { 773 | content: "\f089"; 774 | } 775 | .icon-heart-empty:before { 776 | content: "\f08a"; 777 | } 778 | .icon-signout:before { 779 | content: "\f08b"; 780 | } 781 | .icon-linkedin-sign:before { 782 | content: "\f08c"; 783 | } 784 | .icon-pushpin:before { 785 | content: "\f08d"; 786 | } 787 | .icon-external-link:before { 788 | content: "\f08e"; 789 | } 790 | .icon-signin:before { 791 | content: "\f090"; 792 | } 793 | .icon-trophy:before { 794 | content: "\f091"; 795 | } 796 | .icon-github-sign:before { 797 | content: "\f092"; 798 | } 799 | .icon-upload-alt:before { 800 | content: "\f093"; 801 | } 802 | .icon-lemon:before { 803 | content: "\f094"; 804 | } 805 | .icon-phone:before { 806 | content: "\f095"; 807 | } 808 | .icon-unchecked:before, 809 | .icon-check-empty:before { 810 | content: "\f096"; 811 | } 812 | .icon-bookmark-empty:before { 813 | content: "\f097"; 814 | } 815 | .icon-phone-sign:before { 816 | content: "\f098"; 817 | } 818 | .icon-twitter:before { 819 | content: "\f099"; 820 | } 821 | .icon-facebook:before { 822 | content: "\f09a"; 823 | } 824 | .icon-github:before { 825 | content: "\f09b"; 826 | } 827 | .icon-unlock:before { 828 | content: "\f09c"; 829 | } 830 | .icon-credit-card:before { 831 | content: "\f09d"; 832 | } 833 | .icon-rss:before { 834 | content: "\f09e"; 835 | } 836 | .icon-hdd:before { 837 | content: "\f0a0"; 838 | } 839 | .icon-bullhorn:before { 840 | content: "\f0a1"; 841 | } 842 | .icon-bell:before { 843 | content: "\f0a2"; 844 | } 845 | .icon-certificate:before { 846 | content: "\f0a3"; 847 | } 848 | .icon-hand-right:before { 849 | content: "\f0a4"; 850 | } 851 | .icon-hand-left:before { 852 | content: "\f0a5"; 853 | } 854 | .icon-hand-up:before { 855 | content: "\f0a6"; 856 | } 857 | .icon-hand-down:before { 858 | content: "\f0a7"; 859 | } 860 | .icon-circle-arrow-left:before { 861 | content: "\f0a8"; 862 | } 863 | .icon-circle-arrow-right:before { 864 | content: "\f0a9"; 865 | } 866 | .icon-circle-arrow-up:before { 867 | content: "\f0aa"; 868 | } 869 | .icon-circle-arrow-down:before { 870 | content: "\f0ab"; 871 | } 872 | .icon-globe:before { 873 | content: "\f0ac"; 874 | } 875 | .icon-wrench:before { 876 | content: "\f0ad"; 877 | } 878 | .icon-tasks:before { 879 | content: "\f0ae"; 880 | } 881 | .icon-filter:before { 882 | content: "\f0b0"; 883 | } 884 | .icon-briefcase:before { 885 | content: "\f0b1"; 886 | } 887 | .icon-fullscreen:before { 888 | content: "\f0b2"; 889 | } 890 | .icon-group:before { 891 | content: "\f0c0"; 892 | } 893 | .icon-link:before { 894 | content: "\f0c1"; 895 | } 896 | .icon-cloud:before { 897 | content: "\f0c2"; 898 | } 899 | .icon-beaker:before { 900 | content: "\f0c3"; 901 | } 902 | .icon-cut:before { 903 | content: "\f0c4"; 904 | } 905 | .icon-copy:before { 906 | content: "\f0c5"; 907 | } 908 | .icon-paperclip:before, 909 | .icon-paper-clip:before { 910 | content: "\f0c6"; 911 | } 912 | .icon-save:before { 913 | content: "\f0c7"; 914 | } 915 | .icon-sign-blank:before { 916 | content: "\f0c8"; 917 | } 918 | .icon-reorder:before { 919 | content: "\f0c9"; 920 | } 921 | .icon-list-ul:before { 922 | content: "\f0ca"; 923 | } 924 | .icon-list-ol:before { 925 | content: "\f0cb"; 926 | } 927 | .icon-strikethrough:before { 928 | content: "\f0cc"; 929 | } 930 | .icon-underline:before { 931 | content: "\f0cd"; 932 | } 933 | .icon-table:before { 934 | content: "\f0ce"; 935 | } 936 | .icon-magic:before { 937 | content: "\f0d0"; 938 | } 939 | .icon-truck:before { 940 | content: "\f0d1"; 941 | } 942 | .icon-pinterest:before { 943 | content: "\f0d2"; 944 | } 945 | .icon-pinterest-sign:before { 946 | content: "\f0d3"; 947 | } 948 | .icon-google-plus-sign:before { 949 | content: "\f0d4"; 950 | } 951 | .icon-google-plus:before { 952 | content: "\f0d5"; 953 | } 954 | .icon-money:before { 955 | content: "\f0d6"; 956 | } 957 | .icon-caret-down:before { 958 | content: "\f0d7"; 959 | } 960 | .icon-caret-up:before { 961 | content: "\f0d8"; 962 | } 963 | .icon-caret-left:before { 964 | content: "\f0d9"; 965 | } 966 | .icon-caret-right:before { 967 | content: "\f0da"; 968 | } 969 | .icon-columns:before { 970 | content: "\f0db"; 971 | } 972 | .icon-sort:before { 973 | content: "\f0dc"; 974 | } 975 | .icon-sort-down:before { 976 | content: "\f0dd"; 977 | } 978 | .icon-sort-up:before { 979 | content: "\f0de"; 980 | } 981 | .icon-envelope:before { 982 | content: "\f0e0"; 983 | } 984 | .icon-linkedin:before { 985 | content: "\f0e1"; 986 | } 987 | .icon-rotate-left:before, 988 | .icon-undo:before { 989 | content: "\f0e2"; 990 | } 991 | .icon-legal:before { 992 | content: "\f0e3"; 993 | } 994 | .icon-dashboard:before { 995 | content: "\f0e4"; 996 | } 997 | .icon-comment-alt:before { 998 | content: "\f0e5"; 999 | } 1000 | .icon-comments-alt:before { 1001 | content: "\f0e6"; 1002 | } 1003 | .icon-bolt:before { 1004 | content: "\f0e7"; 1005 | } 1006 | .icon-sitemap:before { 1007 | content: "\f0e8"; 1008 | } 1009 | .icon-umbrella:before { 1010 | content: "\f0e9"; 1011 | } 1012 | .icon-paste:before { 1013 | content: "\f0ea"; 1014 | } 1015 | .icon-lightbulb:before { 1016 | content: "\f0eb"; 1017 | } 1018 | .icon-exchange:before { 1019 | content: "\f0ec"; 1020 | } 1021 | .icon-cloud-download:before { 1022 | content: "\f0ed"; 1023 | } 1024 | .icon-cloud-upload:before { 1025 | content: "\f0ee"; 1026 | } 1027 | .icon-user-md:before { 1028 | content: "\f0f0"; 1029 | } 1030 | .icon-stethoscope:before { 1031 | content: "\f0f1"; 1032 | } 1033 | .icon-suitcase:before { 1034 | content: "\f0f2"; 1035 | } 1036 | .icon-bell-alt:before { 1037 | content: "\f0f3"; 1038 | } 1039 | .icon-coffee:before { 1040 | content: "\f0f4"; 1041 | } 1042 | .icon-food:before { 1043 | content: "\f0f5"; 1044 | } 1045 | .icon-file-text-alt:before { 1046 | content: "\f0f6"; 1047 | } 1048 | .icon-building:before { 1049 | content: "\f0f7"; 1050 | } 1051 | .icon-hospital:before { 1052 | content: "\f0f8"; 1053 | } 1054 | .icon-ambulance:before { 1055 | content: "\f0f9"; 1056 | } 1057 | .icon-medkit:before { 1058 | content: "\f0fa"; 1059 | } 1060 | .icon-fighter-jet:before { 1061 | content: "\f0fb"; 1062 | } 1063 | .icon-beer:before { 1064 | content: "\f0fc"; 1065 | } 1066 | .icon-h-sign:before { 1067 | content: "\f0fd"; 1068 | } 1069 | .icon-plus-sign-alt:before { 1070 | content: "\f0fe"; 1071 | } 1072 | .icon-double-angle-left:before { 1073 | content: "\f100"; 1074 | } 1075 | .icon-double-angle-right:before { 1076 | content: "\f101"; 1077 | } 1078 | .icon-double-angle-up:before { 1079 | content: "\f102"; 1080 | } 1081 | .icon-double-angle-down:before { 1082 | content: "\f103"; 1083 | } 1084 | .icon-angle-left:before { 1085 | content: "\f104"; 1086 | } 1087 | .icon-angle-right:before { 1088 | content: "\f105"; 1089 | } 1090 | .icon-angle-up:before { 1091 | content: "\f106"; 1092 | } 1093 | .icon-angle-down:before { 1094 | content: "\f107"; 1095 | } 1096 | .icon-desktop:before { 1097 | content: "\f108"; 1098 | } 1099 | .icon-laptop:before { 1100 | content: "\f109"; 1101 | } 1102 | .icon-tablet:before { 1103 | content: "\f10a"; 1104 | } 1105 | .icon-mobile-phone:before { 1106 | content: "\f10b"; 1107 | } 1108 | .icon-circle-blank:before { 1109 | content: "\f10c"; 1110 | } 1111 | .icon-quote-left:before { 1112 | content: "\f10d"; 1113 | } 1114 | .icon-quote-right:before { 1115 | content: "\f10e"; 1116 | } 1117 | .icon-spinner:before { 1118 | content: "\f110"; 1119 | } 1120 | .icon-circle:before { 1121 | content: "\f111"; 1122 | } 1123 | .icon-mail-reply:before, 1124 | .icon-reply:before { 1125 | content: "\f112"; 1126 | } 1127 | .icon-github-alt:before { 1128 | content: "\f113"; 1129 | } 1130 | .icon-folder-close-alt:before { 1131 | content: "\f114"; 1132 | } 1133 | .icon-folder-open-alt:before { 1134 | content: "\f115"; 1135 | } 1136 | .icon-expand-alt:before { 1137 | content: "\f116"; 1138 | } 1139 | .icon-collapse-alt:before { 1140 | content: "\f117"; 1141 | } 1142 | .icon-smile:before { 1143 | content: "\f118"; 1144 | } 1145 | .icon-frown:before { 1146 | content: "\f119"; 1147 | } 1148 | .icon-meh:before { 1149 | content: "\f11a"; 1150 | } 1151 | .icon-gamepad:before { 1152 | content: "\f11b"; 1153 | } 1154 | .icon-keyboard:before { 1155 | content: "\f11c"; 1156 | } 1157 | .icon-flag-alt:before { 1158 | content: "\f11d"; 1159 | } 1160 | .icon-flag-checkered:before { 1161 | content: "\f11e"; 1162 | } 1163 | .icon-terminal:before { 1164 | content: "\f120"; 1165 | } 1166 | .icon-code:before { 1167 | content: "\f121"; 1168 | } 1169 | .icon-reply-all:before { 1170 | content: "\f122"; 1171 | } 1172 | .icon-mail-reply-all:before { 1173 | content: "\f122"; 1174 | } 1175 | .icon-star-half-full:before, 1176 | .icon-star-half-empty:before { 1177 | content: "\f123"; 1178 | } 1179 | .icon-location-arrow:before { 1180 | content: "\f124"; 1181 | } 1182 | .icon-crop:before { 1183 | content: "\f125"; 1184 | } 1185 | .icon-code-fork:before { 1186 | content: "\f126"; 1187 | } 1188 | .icon-unlink:before { 1189 | content: "\f127"; 1190 | } 1191 | .icon-question:before { 1192 | content: "\f128"; 1193 | } 1194 | .icon-info:before { 1195 | content: "\f129"; 1196 | } 1197 | .icon-exclamation:before { 1198 | content: "\f12a"; 1199 | } 1200 | .icon-superscript:before { 1201 | content: "\f12b"; 1202 | } 1203 | .icon-subscript:before { 1204 | content: "\f12c"; 1205 | } 1206 | .icon-eraser:before { 1207 | content: "\f12d"; 1208 | } 1209 | .icon-puzzle-piece:before { 1210 | content: "\f12e"; 1211 | } 1212 | .icon-microphone:before { 1213 | content: "\f130"; 1214 | } 1215 | .icon-microphone-off:before { 1216 | content: "\f131"; 1217 | } 1218 | .icon-shield:before { 1219 | content: "\f132"; 1220 | } 1221 | .icon-calendar-empty:before { 1222 | content: "\f133"; 1223 | } 1224 | .icon-fire-extinguisher:before { 1225 | content: "\f134"; 1226 | } 1227 | .icon-rocket:before { 1228 | content: "\f135"; 1229 | } 1230 | .icon-maxcdn:before { 1231 | content: "\f136"; 1232 | } 1233 | .icon-chevron-sign-left:before { 1234 | content: "\f137"; 1235 | } 1236 | .icon-chevron-sign-right:before { 1237 | content: "\f138"; 1238 | } 1239 | .icon-chevron-sign-up:before { 1240 | content: "\f139"; 1241 | } 1242 | .icon-chevron-sign-down:before { 1243 | content: "\f13a"; 1244 | } 1245 | .icon-html5:before { 1246 | content: "\f13b"; 1247 | } 1248 | .icon-css3:before { 1249 | content: "\f13c"; 1250 | } 1251 | .icon-anchor:before { 1252 | content: "\f13d"; 1253 | } 1254 | .icon-unlock-alt:before { 1255 | content: "\f13e"; 1256 | } 1257 | .icon-bullseye:before { 1258 | content: "\f140"; 1259 | } 1260 | .icon-ellipsis-horizontal:before { 1261 | content: "\f141"; 1262 | } 1263 | .icon-ellipsis-vertical:before { 1264 | content: "\f142"; 1265 | } 1266 | .icon-rss-sign:before { 1267 | content: "\f143"; 1268 | } 1269 | .icon-play-sign:before { 1270 | content: "\f144"; 1271 | } 1272 | .icon-ticket:before { 1273 | content: "\f145"; 1274 | } 1275 | .icon-minus-sign-alt:before { 1276 | content: "\f146"; 1277 | } 1278 | .icon-check-minus:before { 1279 | content: "\f147"; 1280 | } 1281 | .icon-level-up:before { 1282 | content: "\f148"; 1283 | } 1284 | .icon-level-down:before { 1285 | content: "\f149"; 1286 | } 1287 | .icon-check-sign:before { 1288 | content: "\f14a"; 1289 | } 1290 | .icon-edit-sign:before { 1291 | content: "\f14b"; 1292 | } 1293 | .icon-external-link-sign:before { 1294 | content: "\f14c"; 1295 | } 1296 | .icon-share-sign:before { 1297 | content: "\f14d"; 1298 | } 1299 | .icon-compass:before { 1300 | content: "\f14e"; 1301 | } 1302 | .icon-collapse:before { 1303 | content: "\f150"; 1304 | } 1305 | .icon-collapse-top:before { 1306 | content: "\f151"; 1307 | } 1308 | .icon-expand:before { 1309 | content: "\f152"; 1310 | } 1311 | .icon-euro:before, 1312 | .icon-eur:before { 1313 | content: "\f153"; 1314 | } 1315 | .icon-gbp:before { 1316 | content: "\f154"; 1317 | } 1318 | .icon-dollar:before, 1319 | .icon-usd:before { 1320 | content: "\f155"; 1321 | } 1322 | .icon-rupee:before, 1323 | .icon-inr:before { 1324 | content: "\f156"; 1325 | } 1326 | .icon-yen:before, 1327 | .icon-jpy:before { 1328 | content: "\f157"; 1329 | } 1330 | .icon-renminbi:before, 1331 | .icon-cny:before { 1332 | content: "\f158"; 1333 | } 1334 | .icon-won:before, 1335 | .icon-krw:before { 1336 | content: "\f159"; 1337 | } 1338 | .icon-bitcoin:before, 1339 | .icon-btc:before { 1340 | content: "\f15a"; 1341 | } 1342 | .icon-file:before { 1343 | content: "\f15b"; 1344 | } 1345 | .icon-file-text:before { 1346 | content: "\f15c"; 1347 | } 1348 | .icon-sort-by-alphabet:before { 1349 | content: "\f15d"; 1350 | } 1351 | .icon-sort-by-alphabet-alt:before { 1352 | content: "\f15e"; 1353 | } 1354 | .icon-sort-by-attributes:before { 1355 | content: "\f160"; 1356 | } 1357 | .icon-sort-by-attributes-alt:before { 1358 | content: "\f161"; 1359 | } 1360 | .icon-sort-by-order:before { 1361 | content: "\f162"; 1362 | } 1363 | .icon-sort-by-order-alt:before { 1364 | content: "\f163"; 1365 | } 1366 | .icon-thumbs-up:before { 1367 | content: "\f164"; 1368 | } 1369 | .icon-thumbs-down:before { 1370 | content: "\f165"; 1371 | } 1372 | .icon-youtube-sign:before { 1373 | content: "\f166"; 1374 | } 1375 | .icon-youtube:before { 1376 | content: "\f167"; 1377 | } 1378 | .icon-xing:before { 1379 | content: "\f168"; 1380 | } 1381 | .icon-xing-sign:before { 1382 | content: "\f169"; 1383 | } 1384 | .icon-youtube-play:before { 1385 | content: "\f16a"; 1386 | } 1387 | .icon-dropbox:before { 1388 | content: "\f16b"; 1389 | } 1390 | .icon-stackexchange:before { 1391 | content: "\f16c"; 1392 | } 1393 | .icon-instagram:before { 1394 | content: "\f16d"; 1395 | } 1396 | .icon-flickr:before { 1397 | content: "\f16e"; 1398 | } 1399 | .icon-adn:before { 1400 | content: "\f170"; 1401 | } 1402 | .icon-bitbucket:before { 1403 | content: "\f171"; 1404 | } 1405 | .icon-bitbucket-sign:before { 1406 | content: "\f172"; 1407 | } 1408 | .icon-tumblr:before { 1409 | content: "\f173"; 1410 | } 1411 | .icon-tumblr-sign:before { 1412 | content: "\f174"; 1413 | } 1414 | .icon-long-arrow-down:before { 1415 | content: "\f175"; 1416 | } 1417 | .icon-long-arrow-up:before { 1418 | content: "\f176"; 1419 | } 1420 | .icon-long-arrow-left:before { 1421 | content: "\f177"; 1422 | } 1423 | .icon-long-arrow-right:before { 1424 | content: "\f178"; 1425 | } 1426 | .icon-apple:before { 1427 | content: "\f179"; 1428 | } 1429 | .icon-windows:before { 1430 | content: "\f17a"; 1431 | } 1432 | .icon-android:before { 1433 | content: "\f17b"; 1434 | } 1435 | .icon-linux:before { 1436 | content: "\f17c"; 1437 | } 1438 | .icon-dribbble:before { 1439 | content: "\f17d"; 1440 | } 1441 | .icon-skype:before { 1442 | content: "\f17e"; 1443 | } 1444 | .icon-foursquare:before { 1445 | content: "\f180"; 1446 | } 1447 | .icon-trello:before { 1448 | content: "\f181"; 1449 | } 1450 | .icon-female:before { 1451 | content: "\f182"; 1452 | } 1453 | .icon-male:before { 1454 | content: "\f183"; 1455 | } 1456 | .icon-gittip:before { 1457 | content: "\f184"; 1458 | } 1459 | .icon-sun:before { 1460 | content: "\f185"; 1461 | } 1462 | .icon-moon:before { 1463 | content: "\f186"; 1464 | } 1465 | .icon-archive:before { 1466 | content: "\f187"; 1467 | } 1468 | .icon-bug:before { 1469 | content: "\f188"; 1470 | } 1471 | .icon-vk:before { 1472 | content: "\f189"; 1473 | } 1474 | .icon-weibo:before { 1475 | content: "\f18a"; 1476 | } 1477 | .icon-renren:before { 1478 | content: "\f18b"; 1479 | } 1480 | --------------------------------------------------------------------------------