├── 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 |
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 |
5 |
6 |
7 |
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 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
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 |
--------------------------------------------------------------------------------
![]()
4 | 5 |