├── .gitignore ├── README.md ├── app ├── __init__.py ├── forms.py ├── models.py ├── static │ ├── assets │ │ └── flash │ │ │ └── ZeroClipboard.swf │ ├── css │ │ ├── bootstrap-theme.css │ │ ├── bootstrap-theme.css.map │ │ ├── bootstrap-theme.min.css │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ ├── dashboard.css │ │ └── theme.css │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ ├── img │ │ ├── favicon.ico │ │ └── loading.gif │ └── js │ │ ├── bootstrap.js │ │ ├── bootstrap.min.js │ │ └── theme.js ├── templates │ ├── base.html │ ├── editor.html │ ├── hadoop.html │ ├── index.html │ ├── login.html │ ├── operations.html │ ├── racktables.html │ ├── signup.html │ └── status.html ├── utils │ ├── __init__.py │ └── operations.py └── views.py ├── config.py ├── flask └── requirements.txt ├── run.py ├── screenshots ├── editor.png ├── hadoop.png ├── index.png ├── login.png ├── operations.png ├── racktables.png ├── signup.png └── status.png └── scripts ├── racktables.py └── requirements.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | /sshkeys/ 4 | /tmp/ 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Instruction 2 | 3 | #### 1. Clone the repo 4 | 5 | ``` 6 | cd ~ 7 | git clone https://github.com/mcsrainbow/devopsdemo 8 | ``` 9 | 10 | #### 2. Configure the environment 11 | 12 | ``` 13 | cd devopsdemo/scripts 14 | ./requirements.sh all 15 | ``` 16 | 17 | #### 3. Run the app 18 | 19 | ``` 20 | cd ~/devopsdemo 21 | . flask/bin/activate 22 | ./run.py 23 | ``` 24 | 25 | ### Screenshots 26 | 27 | ![image](screenshots/signup.png) 28 | 29 | ![image](screenshots/login.png) 30 | 31 | ![image](screenshots/index.png) 32 | 33 | ![image](screenshots/status.png) 34 | 35 | ![image](screenshots/operations.png) 36 | 37 | ![image](screenshots/racktables.png) 38 | 39 | ![image](screenshots/hadoop.png) 40 | 41 | ![image](screenshots/editor.png) 42 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask.ext.sqlalchemy import SQLAlchemy 3 | from flask.ext.login import LoginManager 4 | 5 | app = Flask(__name__, static_url_path='') 6 | app.config.from_object('config') 7 | db = SQLAlchemy(app) 8 | lm = LoginManager() 9 | lm.init_app(app) 10 | lm.login_view = 'login' 11 | 12 | from app import views, models 13 | -------------------------------------------------------------------------------- /app/forms.py: -------------------------------------------------------------------------------- 1 | from flask.ext.wtf import Form 2 | from wtforms import StringField, BooleanField, SelectField, TextAreaField, SubmitField, \ 3 | PasswordField, ValidationError, RadioField 4 | from wtforms.validators import DataRequired, Email, EqualTo 5 | from app import db 6 | 7 | class SignupForm(Form): 8 | firstname = StringField('First name', validators=[DataRequired("Please enter your first name.")]) 9 | lastname = StringField('Last name', validators=[DataRequired("Please enter your last name.")]) 10 | email = StringField('Email', validators=[DataRequired("Please enter your email address."), 11 | Email("Please enter your email address.")]) 12 | password = PasswordField('Password', validators=[DataRequired("Please enter a password."), 13 | EqualTo('confirm', message="Passwords must match")]) 14 | confirm = PasswordField('Repeat Password') 15 | 16 | class LoginForm(Form): 17 | email = StringField('Email', validators=[DataRequired("Please enter your email address."), 18 | Email("Please enter your email address.")]) 19 | password = PasswordField('Password', validators=[DataRequired("Please enter a password.")]) 20 | remember_me = BooleanField('remember_me', default=False) 21 | 22 | class OperationsForm(Form): 23 | do_action = StringField('Action') 24 | hostname = StringField('Hostname', validators=[DataRequired()]) 25 | cmd = StringField('Command', validators=[DataRequired()]) 26 | 27 | class RacktablesForm(Form): 28 | do_action = StringField('Action') 29 | objectname = StringField('Object Name') 30 | objecttype_choices = [('online_mode','Online Mode'), 31 | ('offline_mode','Offline Mode'), 32 | ('patch_panel','Patch Panel'), 33 | ('network_switch','Network Switch'), 34 | ('pdu','PDU'), 35 | ('network_security','Network Security')] 36 | objecttype = RadioField('Object Type', choices=objecttype_choices) 37 | rackspace = StringField('Rackspace') 38 | rackposition_choices = [('none','rackposition'), 39 | ('left','left'),('right','right'), 40 | ('front','front'),('interior','interior'),('back','back')] 41 | rackposition = SelectField('Rackposition',choices=rackposition_choices) 42 | 43 | class EditorForm(Form): 44 | do_action = StringField('Action') 45 | file_path = StringField('File Path', validators=[DataRequired()]) 46 | file_data = TextAreaField('File Data') 47 | 48 | class HadoopForm(Form): 49 | do_action = StringField('Action') 50 | slave_hostname = StringField('Slave Hostname') 51 | choices = [('none','master_hostname'), 52 | ('idc1-hnn1','idc1-hnn1'),('idc2-hnn1','idc2-hnn1'), 53 | ('idc1-hrm1','idc1-hrm1'),('idc2-hrm1','idc2-hrm1')] 54 | master_hostname = SelectField('Master Hostname',choices=choices) 55 | -------------------------------------------------------------------------------- /app/models.py: -------------------------------------------------------------------------------- 1 | from werkzeug import generate_password_hash, check_password_hash 2 | from app import db 3 | 4 | class User(db.Model): 5 | __tablename__ = 'users' 6 | uid = db.Column(db.Integer, primary_key = True) 7 | firstname = db.Column(db.String(100)) 8 | lastname = db.Column(db.String(100)) 9 | email = db.Column(db.String(120), unique=True) 10 | passwdhash = db.Column(db.String(54)) 11 | 12 | def __init__(self,firstname,lastname,email,password): 13 | self.firstname = firstname.title() 14 | self.lastname = lastname.title() 15 | self.email = email.lower() 16 | self.set_password(password) 17 | 18 | def is_authenticated(self): 19 | return True 20 | 21 | def is_active(self): 22 | return True 23 | 24 | def is_anonymous(self): 25 | return False 26 | 27 | def get_id(self): 28 | return unicode(str(self.uid)) 29 | 30 | def set_password(self, password): 31 | self.passwdhash = generate_password_hash(password) 32 | 33 | def check_password(self, password): 34 | return check_password_hash(self.passwdhash, password) 35 | -------------------------------------------------------------------------------- /app/static/assets/flash/ZeroClipboard.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcsrainbow/devopsdemo/3e60246969c4d4f3b74d2ef4237e2b3963d48b04/app/static/assets/flash/ZeroClipboard.swf -------------------------------------------------------------------------------- /app/static/css/bootstrap-theme.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.4 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | .btn-default, 8 | .btn-primary, 9 | .btn-success, 10 | .btn-info, 11 | .btn-warning, 12 | .btn-danger { 13 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .2); 14 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 15 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 16 | } 17 | .btn-default:active, 18 | .btn-primary:active, 19 | .btn-success:active, 20 | .btn-info:active, 21 | .btn-warning:active, 22 | .btn-danger:active, 23 | .btn-default.active, 24 | .btn-primary.active, 25 | .btn-success.active, 26 | .btn-info.active, 27 | .btn-warning.active, 28 | .btn-danger.active { 29 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 30 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 31 | } 32 | .btn-default .badge, 33 | .btn-primary .badge, 34 | .btn-success .badge, 35 | .btn-info .badge, 36 | .btn-warning .badge, 37 | .btn-danger .badge { 38 | text-shadow: none; 39 | } 40 | .btn:active, 41 | .btn.active { 42 | background-image: none; 43 | } 44 | .btn-default { 45 | text-shadow: 0 1px 0 #fff; 46 | background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%); 47 | background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%); 48 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0)); 49 | background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%); 50 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); 51 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 52 | background-repeat: repeat-x; 53 | border-color: #dbdbdb; 54 | border-color: #ccc; 55 | } 56 | .btn-default:hover, 57 | .btn-default:focus { 58 | background-color: #e0e0e0; 59 | background-position: 0 -15px; 60 | } 61 | .btn-default:active, 62 | .btn-default.active { 63 | background-color: #e0e0e0; 64 | border-color: #dbdbdb; 65 | } 66 | .btn-default.disabled, 67 | .btn-default:disabled, 68 | .btn-default[disabled] { 69 | background-color: #e0e0e0; 70 | background-image: none; 71 | } 72 | .btn-primary { 73 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%); 74 | background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%); 75 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88)); 76 | background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%); 77 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0); 78 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 79 | background-repeat: repeat-x; 80 | border-color: #245580; 81 | } 82 | .btn-primary:hover, 83 | .btn-primary:focus { 84 | background-color: #265a88; 85 | background-position: 0 -15px; 86 | } 87 | .btn-primary:active, 88 | .btn-primary.active { 89 | background-color: #265a88; 90 | border-color: #245580; 91 | } 92 | .btn-primary.disabled, 93 | .btn-primary:disabled, 94 | .btn-primary[disabled] { 95 | background-color: #265a88; 96 | background-image: none; 97 | } 98 | .btn-success { 99 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); 100 | background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%); 101 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641)); 102 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); 103 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); 104 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 105 | background-repeat: repeat-x; 106 | border-color: #3e8f3e; 107 | } 108 | .btn-success:hover, 109 | .btn-success:focus { 110 | background-color: #419641; 111 | background-position: 0 -15px; 112 | } 113 | .btn-success:active, 114 | .btn-success.active { 115 | background-color: #419641; 116 | border-color: #3e8f3e; 117 | } 118 | .btn-success.disabled, 119 | .btn-success:disabled, 120 | .btn-success[disabled] { 121 | background-color: #419641; 122 | background-image: none; 123 | } 124 | .btn-info { 125 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 126 | background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 127 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2)); 128 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); 129 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); 130 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 131 | background-repeat: repeat-x; 132 | border-color: #28a4c9; 133 | } 134 | .btn-info:hover, 135 | .btn-info:focus { 136 | background-color: #2aabd2; 137 | background-position: 0 -15px; 138 | } 139 | .btn-info:active, 140 | .btn-info.active { 141 | background-color: #2aabd2; 142 | border-color: #28a4c9; 143 | } 144 | .btn-info.disabled, 145 | .btn-info:disabled, 146 | .btn-info[disabled] { 147 | background-color: #2aabd2; 148 | background-image: none; 149 | } 150 | .btn-warning { 151 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 152 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 153 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316)); 154 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); 155 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); 156 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 157 | background-repeat: repeat-x; 158 | border-color: #e38d13; 159 | } 160 | .btn-warning:hover, 161 | .btn-warning:focus { 162 | background-color: #eb9316; 163 | background-position: 0 -15px; 164 | } 165 | .btn-warning:active, 166 | .btn-warning.active { 167 | background-color: #eb9316; 168 | border-color: #e38d13; 169 | } 170 | .btn-warning.disabled, 171 | .btn-warning:disabled, 172 | .btn-warning[disabled] { 173 | background-color: #eb9316; 174 | background-image: none; 175 | } 176 | .btn-danger { 177 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 178 | background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 179 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a)); 180 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); 181 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); 182 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 183 | background-repeat: repeat-x; 184 | border-color: #b92c28; 185 | } 186 | .btn-danger:hover, 187 | .btn-danger:focus { 188 | background-color: #c12e2a; 189 | background-position: 0 -15px; 190 | } 191 | .btn-danger:active, 192 | .btn-danger.active { 193 | background-color: #c12e2a; 194 | border-color: #b92c28; 195 | } 196 | .btn-danger.disabled, 197 | .btn-danger:disabled, 198 | .btn-danger[disabled] { 199 | background-color: #c12e2a; 200 | background-image: none; 201 | } 202 | .thumbnail, 203 | .img-thumbnail { 204 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 205 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 206 | } 207 | .dropdown-menu > li > a:hover, 208 | .dropdown-menu > li > a:focus { 209 | background-color: #e8e8e8; 210 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 211 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 212 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 213 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 214 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 215 | background-repeat: repeat-x; 216 | } 217 | .dropdown-menu > .active > a, 218 | .dropdown-menu > .active > a:hover, 219 | .dropdown-menu > .active > a:focus { 220 | background-color: #2e6da4; 221 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 222 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 223 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 224 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 225 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 226 | background-repeat: repeat-x; 227 | } 228 | .navbar-default { 229 | background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%); 230 | background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%); 231 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8)); 232 | background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%); 233 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 234 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 235 | background-repeat: repeat-x; 236 | border-radius: 4px; 237 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 238 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 239 | } 240 | .navbar-default .navbar-nav > .open > a, 241 | .navbar-default .navbar-nav > .active > a { 242 | background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); 243 | background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); 244 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2)); 245 | background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%); 246 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0); 247 | background-repeat: repeat-x; 248 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 249 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 250 | } 251 | .navbar-brand, 252 | .navbar-nav > li > a { 253 | text-shadow: 0 1px 0 rgba(255, 255, 255, .25); 254 | } 255 | .navbar-inverse { 256 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%); 257 | background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%); 258 | background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222)); 259 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%); 260 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); 261 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 262 | background-repeat: repeat-x; 263 | } 264 | .navbar-inverse .navbar-nav > .open > a, 265 | .navbar-inverse .navbar-nav > .active > a { 266 | background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%); 267 | background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%); 268 | background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f)); 269 | background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%); 270 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0); 271 | background-repeat: repeat-x; 272 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 273 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 274 | } 275 | .navbar-inverse .navbar-brand, 276 | .navbar-inverse .navbar-nav > li > a { 277 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); 278 | } 279 | .navbar-static-top, 280 | .navbar-fixed-top, 281 | .navbar-fixed-bottom { 282 | border-radius: 0; 283 | } 284 | @media (max-width: 767px) { 285 | .navbar .navbar-nav .open .dropdown-menu > .active > a, 286 | .navbar .navbar-nav .open .dropdown-menu > .active > a:hover, 287 | .navbar .navbar-nav .open .dropdown-menu > .active > a:focus { 288 | color: #fff; 289 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 290 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 291 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 292 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 293 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 294 | background-repeat: repeat-x; 295 | } 296 | } 297 | .alert { 298 | text-shadow: 0 1px 0 rgba(255, 255, 255, .2); 299 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 300 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 301 | } 302 | .alert-success { 303 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 304 | background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 305 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc)); 306 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 307 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); 308 | background-repeat: repeat-x; 309 | border-color: #b2dba1; 310 | } 311 | .alert-info { 312 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 313 | background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 314 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0)); 315 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 316 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); 317 | background-repeat: repeat-x; 318 | border-color: #9acfea; 319 | } 320 | .alert-warning { 321 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 322 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 323 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0)); 324 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 325 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); 326 | background-repeat: repeat-x; 327 | border-color: #f5e79e; 328 | } 329 | .alert-danger { 330 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 331 | background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 332 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3)); 333 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 334 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); 335 | background-repeat: repeat-x; 336 | border-color: #dca7a7; 337 | } 338 | .progress { 339 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 340 | background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 341 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5)); 342 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); 343 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); 344 | background-repeat: repeat-x; 345 | } 346 | .progress-bar { 347 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%); 348 | background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%); 349 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090)); 350 | background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%); 351 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0); 352 | background-repeat: repeat-x; 353 | } 354 | .progress-bar-success { 355 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); 356 | background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%); 357 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44)); 358 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 359 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 360 | background-repeat: repeat-x; 361 | } 362 | .progress-bar-info { 363 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 364 | background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 365 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5)); 366 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 367 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 368 | background-repeat: repeat-x; 369 | } 370 | .progress-bar-warning { 371 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 372 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 373 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f)); 374 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 375 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 376 | background-repeat: repeat-x; 377 | } 378 | .progress-bar-danger { 379 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); 380 | background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%); 381 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c)); 382 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 383 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 384 | background-repeat: repeat-x; 385 | } 386 | .progress-bar-striped { 387 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 388 | background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 389 | background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 390 | } 391 | .list-group { 392 | border-radius: 4px; 393 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 394 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 395 | } 396 | .list-group-item.active, 397 | .list-group-item.active:hover, 398 | .list-group-item.active:focus { 399 | text-shadow: 0 -1px 0 #286090; 400 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%); 401 | background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%); 402 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a)); 403 | background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%); 404 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0); 405 | background-repeat: repeat-x; 406 | border-color: #2b669a; 407 | } 408 | .list-group-item.active .badge, 409 | .list-group-item.active:hover .badge, 410 | .list-group-item.active:focus .badge { 411 | text-shadow: none; 412 | } 413 | .panel { 414 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 415 | box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 416 | } 417 | .panel-default > .panel-heading { 418 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 419 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 420 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 421 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 422 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 423 | background-repeat: repeat-x; 424 | } 425 | .panel-primary > .panel-heading { 426 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 427 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 428 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 429 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 430 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 431 | background-repeat: repeat-x; 432 | } 433 | .panel-success > .panel-heading { 434 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 435 | background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 436 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6)); 437 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); 438 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); 439 | background-repeat: repeat-x; 440 | } 441 | .panel-info > .panel-heading { 442 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 443 | background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 444 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3)); 445 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); 446 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); 447 | background-repeat: repeat-x; 448 | } 449 | .panel-warning > .panel-heading { 450 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 451 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 452 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc)); 453 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); 454 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); 455 | background-repeat: repeat-x; 456 | } 457 | .panel-danger > .panel-heading { 458 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 459 | background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 460 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc)); 461 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); 462 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); 463 | background-repeat: repeat-x; 464 | } 465 | .well { 466 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 467 | background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 468 | background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5)); 469 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); 470 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 471 | background-repeat: repeat-x; 472 | border-color: #dcdcdc; 473 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 474 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 475 | } 476 | /*# sourceMappingURL=bootstrap-theme.css.map */ 477 | -------------------------------------------------------------------------------- /app/static/css/bootstrap-theme.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["less/theme.less","less/mixins/vendor-prefixes.less","bootstrap-theme.css","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":"AAcA;;;;;;EAME,0CAAA;ECgDA,6FAAA;EACQ,qFAAA;EC5DT;AFgBC;;;;;;;;;;;;EC2CA,0DAAA;EACQ,kDAAA;EC7CT;AFVD;;;;;;EAiBI,mBAAA;EECH;AFiCC;;EAEE,wBAAA;EE/BH;AFoCD;EGnDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EAgC2C,2BAAA;EAA2B,oBAAA;EEzBvE;AFLC;;EAEE,2BAAA;EACA,8BAAA;EEOH;AFJC;;EAEE,2BAAA;EACA,uBAAA;EEMH;AFHC;;;EAGE,2BAAA;EACA,wBAAA;EEKH;AFUD;EGpDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EEgCD;AF9BC;;EAEE,2BAAA;EACA,8BAAA;EEgCH;AF7BC;;EAEE,2BAAA;EACA,uBAAA;EE+BH;AF5BC;;;EAGE,2BAAA;EACA,wBAAA;EE8BH;AFdD;EGrDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EEyDD;AFvDC;;EAEE,2BAAA;EACA,8BAAA;EEyDH;AFtDC;;EAEE,2BAAA;EACA,uBAAA;EEwDH;AFrDC;;;EAGE,2BAAA;EACA,wBAAA;EEuDH;AFtCD;EGtDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EEkFD;AFhFC;;EAEE,2BAAA;EACA,8BAAA;EEkFH;AF/EC;;EAEE,2BAAA;EACA,uBAAA;EEiFH;AF9EC;;;EAGE,2BAAA;EACA,wBAAA;EEgFH;AF9DD;EGvDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EE2GD;AFzGC;;EAEE,2BAAA;EACA,8BAAA;EE2GH;AFxGC;;EAEE,2BAAA;EACA,uBAAA;EE0GH;AFvGC;;;EAGE,2BAAA;EACA,wBAAA;EEyGH;AFtFD;EGxDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EEoID;AFlIC;;EAEE,2BAAA;EACA,8BAAA;EEoIH;AFjIC;;EAEE,2BAAA;EACA,uBAAA;EEmIH;AFhIC;;;EAGE,2BAAA;EACA,wBAAA;EEkIH;AFxGD;;EChBE,oDAAA;EACQ,4CAAA;EC4HT;AFnGD;;EGzEI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EHwEF,2BAAA;EEyGD;AFvGD;;;EG9EI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH8EF,2BAAA;EE6GD;AFpGD;EG3FI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ECnBF,qEAAA;EJ6GA,oBAAA;EC/CA,6FAAA;EACQ,qFAAA;EC0JT;AF/GD;;EG3FI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EF2CF,0DAAA;EACQ,kDAAA;ECoKT;AF5GD;;EAEE,gDAAA;EE8GD;AF1GD;EG9GI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ECnBF,qEAAA;EF+OD;AFlHD;;EG9GI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EF2CF,yDAAA;EACQ,iDAAA;EC0LT;AF5HD;;EAYI,2CAAA;EEoHH;AF/GD;;;EAGE,kBAAA;EEiHD;AF5FD;EAfI;;;IAGE,aAAA;IG3IF,0EAAA;IACA,qEAAA;IACA,+FAAA;IAAA,wEAAA;IACA,6BAAA;IACA,wHAAA;ID0PD;EACF;AFxGD;EACE,+CAAA;ECzGA,4FAAA;EACQ,oFAAA;ECoNT;AFhGD;EGpKI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH4JF,uBAAA;EE4GD;AFvGD;EGrKI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH4JF,uBAAA;EEoHD;AF9GD;EGtKI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH4JF,uBAAA;EE4HD;AFrHD;EGvKI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH4JF,uBAAA;EEoID;AFrHD;EG/KI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDuSH;AFlHD;EGzLI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED8SH;AFxHD;EG1LI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDqTH;AF9HD;EG3LI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED4TH;AFpID;EG5LI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDmUH;AF1ID;EG7LI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED0UH;AF7ID;EGhKI,+MAAA;EACA,0MAAA;EACA,uMAAA;EDgTH;AFzID;EACE,oBAAA;EC5JA,oDAAA;EACQ,4CAAA;ECwST;AF1ID;;;EAGE,+BAAA;EGjNE,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH+MF,uBAAA;EEgJD;AFrJD;;;EAQI,mBAAA;EEkJH;AFxID;ECjLE,mDAAA;EACQ,2CAAA;EC4TT;AFlID;EG1OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED+WH;AFxID;EG3OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDsXH;AF9ID;EG5OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED6XH;AFpJD;EG7OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDoYH;AF1JD;EG9OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED2YH;AFhKD;EG/OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDkZH;AFhKD;EGtPI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EHoPF,uBAAA;ECzMA,2FAAA;EACQ,mFAAA;ECgXT","file":"bootstrap-theme.css","sourcesContent":["\n//\n// Load core variables and mixins\n// --------------------------------------------------\n\n@import \"variables.less\";\n@import \"mixins.less\";\n\n\n//\n// Buttons\n// --------------------------------------------------\n\n// Common styles\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0,0,0,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n // Reset the shadow\n &:active,\n &.active {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n .badge {\n text-shadow: none;\n }\n}\n\n// Mixin for generating new styles\n.btn-styles(@btn-color: #555) {\n #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%));\n .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners; see https://github.com/twbs/bootstrap/issues/10620\n background-repeat: repeat-x;\n border-color: darken(@btn-color, 14%);\n\n &:hover,\n &:focus {\n background-color: darken(@btn-color, 12%);\n background-position: 0 -15px;\n }\n\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n border-color: darken(@btn-color, 14%);\n }\n\n &.disabled,\n &:disabled,\n &[disabled] {\n background-color: darken(@btn-color, 12%);\n background-image: none;\n }\n}\n\n// Common styles\n.btn {\n // Remove the gradient for the pressed/active state\n &:active,\n &.active {\n background-image: none;\n }\n}\n\n// Apply the mixin to the buttons\n.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; }\n.btn-primary { .btn-styles(@btn-primary-bg); }\n.btn-success { .btn-styles(@btn-success-bg); }\n.btn-info { .btn-styles(@btn-info-bg); }\n.btn-warning { .btn-styles(@btn-warning-bg); }\n.btn-danger { .btn-styles(@btn-danger-bg); }\n\n\n//\n// Images\n// --------------------------------------------------\n\n.thumbnail,\n.img-thumbnail {\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n\n\n//\n// Dropdowns\n// --------------------------------------------------\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%));\n background-color: darken(@dropdown-link-hover-bg, 5%);\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n background-color: darken(@dropdown-link-active-bg, 5%);\n}\n\n\n//\n// Navbar\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n border-radius: @navbar-border-radius;\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: darken(@navbar-default-link-active-bg, 5%); @end-color: darken(@navbar-default-link-active-bg, 2%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.075));\n }\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255,255,255,.25);\n}\n\n// Inverted navbar\n.navbar-inverse {\n #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered; see https://github.com/twbs/bootstrap/issues/10257\n\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: @navbar-inverse-link-active-bg; @end-color: lighten(@navbar-inverse-link-active-bg, 2.5%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.25));\n }\n\n .navbar-brand,\n .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0,0,0,.25);\n }\n}\n\n// Undo rounded corners in static and fixed navbars\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n\n// Fix active state of dropdown items in collapsed mode\n@media (max-width: @grid-float-breakpoint-max) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: #fff;\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n }\n }\n}\n\n\n//\n// Alerts\n// --------------------------------------------------\n\n// Common styles\n.alert {\n text-shadow: 0 1px 0 rgba(255,255,255,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05);\n .box-shadow(@shadow);\n}\n\n// Mixin for generating new styles\n.alert-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%));\n border-color: darken(@color, 15%);\n}\n\n// Apply the mixin to the alerts\n.alert-success { .alert-styles(@alert-success-bg); }\n.alert-info { .alert-styles(@alert-info-bg); }\n.alert-warning { .alert-styles(@alert-warning-bg); }\n.alert-danger { .alert-styles(@alert-danger-bg); }\n\n\n//\n// Progress bars\n// --------------------------------------------------\n\n// Give the progress background some depth\n.progress {\n #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg)\n}\n\n// Mixin for generating new styles\n.progress-bar-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%));\n}\n\n// Apply the mixin to the progress bars\n.progress-bar { .progress-bar-styles(@progress-bar-bg); }\n.progress-bar-success { .progress-bar-styles(@progress-bar-success-bg); }\n.progress-bar-info { .progress-bar-styles(@progress-bar-info-bg); }\n.progress-bar-warning { .progress-bar-styles(@progress-bar-warning-bg); }\n.progress-bar-danger { .progress-bar-styles(@progress-bar-danger-bg); }\n\n// Reset the striped class because our mixins don't do multiple gradients and\n// the above custom styles override the new `.progress-bar-striped` in v3.2.0.\n.progress-bar-striped {\n #gradient > .striped();\n}\n\n\n//\n// List groups\n// --------------------------------------------------\n\n.list-group {\n border-radius: @border-radius-base;\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%);\n #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%));\n border-color: darken(@list-group-active-border, 7.5%);\n\n .badge {\n text-shadow: none;\n }\n}\n\n\n//\n// Panels\n// --------------------------------------------------\n\n// Common styles\n.panel {\n .box-shadow(0 1px 2px rgba(0,0,0,.05));\n}\n\n// Mixin for generating new styles\n.panel-heading-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%));\n}\n\n// Apply the mixin to the panel headings only\n.panel-default > .panel-heading { .panel-heading-styles(@panel-default-heading-bg); }\n.panel-primary > .panel-heading { .panel-heading-styles(@panel-primary-heading-bg); }\n.panel-success > .panel-heading { .panel-heading-styles(@panel-success-heading-bg); }\n.panel-info > .panel-heading { .panel-heading-styles(@panel-info-heading-bg); }\n.panel-warning > .panel-heading { .panel-heading-styles(@panel-warning-heading-bg); }\n.panel-danger > .panel-heading { .panel-heading-styles(@panel-danger-heading-bg); }\n\n\n//\n// Wells\n// --------------------------------------------------\n\n.well {\n #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg);\n border-color: darken(@well-bg, 10%);\n @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They will be removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility){\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n",".btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.btn-default:active,\n.btn-primary:active,\n.btn-success:active,\n.btn-info:active,\n.btn-warning:active,\n.btn-danger:active,\n.btn-default.active,\n.btn-primary.active,\n.btn-success.active,\n.btn-info.active,\n.btn-warning.active,\n.btn-danger.active {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-default .badge,\n.btn-primary .badge,\n.btn-success .badge,\n.btn-info .badge,\n.btn-warning .badge,\n.btn-danger .badge {\n text-shadow: none;\n}\n.btn:active,\n.btn.active {\n background-image: none;\n}\n.btn-default {\n background-image: -webkit-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);\n background-image: -o-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);\n background-image: linear-gradient(to bottom, #ffffff 0%, #e0e0e0 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #dbdbdb;\n text-shadow: 0 1px 0 #fff;\n border-color: #ccc;\n}\n.btn-default:hover,\n.btn-default:focus {\n background-color: #e0e0e0;\n background-position: 0 -15px;\n}\n.btn-default:active,\n.btn-default.active {\n background-color: #e0e0e0;\n border-color: #dbdbdb;\n}\n.btn-default.disabled,\n.btn-default:disabled,\n.btn-default[disabled] {\n background-color: #e0e0e0;\n background-image: none;\n}\n.btn-primary {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #245580;\n}\n.btn-primary:hover,\n.btn-primary:focus {\n background-color: #265a88;\n background-position: 0 -15px;\n}\n.btn-primary:active,\n.btn-primary.active {\n background-color: #265a88;\n border-color: #245580;\n}\n.btn-primary.disabled,\n.btn-primary:disabled,\n.btn-primary[disabled] {\n background-color: #265a88;\n background-image: none;\n}\n.btn-success {\n background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);\n background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);\n background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #3e8f3e;\n}\n.btn-success:hover,\n.btn-success:focus {\n background-color: #419641;\n background-position: 0 -15px;\n}\n.btn-success:active,\n.btn-success.active {\n background-color: #419641;\n border-color: #3e8f3e;\n}\n.btn-success.disabled,\n.btn-success:disabled,\n.btn-success[disabled] {\n background-color: #419641;\n background-image: none;\n}\n.btn-info {\n background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #28a4c9;\n}\n.btn-info:hover,\n.btn-info:focus {\n background-color: #2aabd2;\n background-position: 0 -15px;\n}\n.btn-info:active,\n.btn-info.active {\n background-color: #2aabd2;\n border-color: #28a4c9;\n}\n.btn-info.disabled,\n.btn-info:disabled,\n.btn-info[disabled] {\n background-color: #2aabd2;\n background-image: none;\n}\n.btn-warning {\n background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #e38d13;\n}\n.btn-warning:hover,\n.btn-warning:focus {\n background-color: #eb9316;\n background-position: 0 -15px;\n}\n.btn-warning:active,\n.btn-warning.active {\n background-color: #eb9316;\n border-color: #e38d13;\n}\n.btn-warning.disabled,\n.btn-warning:disabled,\n.btn-warning[disabled] {\n background-color: #eb9316;\n background-image: none;\n}\n.btn-danger {\n background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #b92c28;\n}\n.btn-danger:hover,\n.btn-danger:focus {\n background-color: #c12e2a;\n background-position: 0 -15px;\n}\n.btn-danger:active,\n.btn-danger.active {\n background-color: #c12e2a;\n border-color: #b92c28;\n}\n.btn-danger.disabled,\n.btn-danger:disabled,\n.btn-danger[disabled] {\n background-color: #c12e2a;\n background-image: none;\n}\n.thumbnail,\n.img-thumbnail {\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n background-color: #e8e8e8;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n background-color: #2e6da4;\n}\n.navbar-default {\n background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n background-image: -o-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .active > a {\n background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);\n -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);\n}\n.navbar-inverse {\n background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222222 100%);\n background-image: -o-linear-gradient(top, #3c3c3c 0%, #222222 100%);\n background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .active > a {\n background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);\n -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n}\n.navbar-inverse .navbar-brand,\n.navbar-inverse .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n@media (max-width: 767px) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a,\n .navbar .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #fff;\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n }\n}\n.alert {\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.alert-success {\n background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);\n border-color: #b2dba1;\n}\n.alert-info {\n background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);\n border-color: #9acfea;\n}\n.alert-warning {\n background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);\n border-color: #f5e79e;\n}\n.alert-danger {\n background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);\n border-color: #dca7a7;\n}\n.progress {\n background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);\n}\n.progress-bar {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);\n}\n.progress-bar-success {\n background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);\n}\n.progress-bar-info {\n background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);\n}\n.progress-bar-warning {\n background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);\n}\n.progress-bar-danger {\n background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);\n}\n.progress-bar-striped {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.list-group {\n border-radius: 4px;\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 #286090;\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);\n border-color: #2b669a;\n}\n.list-group-item.active .badge,\n.list-group-item.active:hover .badge,\n.list-group-item.active:focus .badge {\n text-shadow: none;\n}\n.panel {\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.panel-default > .panel-heading {\n background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n}\n.panel-primary > .panel-heading {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n}\n.panel-success > .panel-heading {\n background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);\n}\n.panel-info > .panel-heading {\n background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);\n}\n.panel-warning > .panel-heading {\n background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);\n}\n.panel-danger > .panel-heading {\n background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);\n}\n.well {\n background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);\n border-color: #dcdcdc;\n -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n}\n/*# sourceMappingURL=bootstrap-theme.css.map */","// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n"]} -------------------------------------------------------------------------------- /app/static/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.4 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default:disabled,.btn-default[disabled]{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary:disabled,.btn-primary[disabled]{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success:disabled,.btn-success[disabled]{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info:disabled,.btn-info[disabled]{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning:disabled,.btn-warning[disabled]{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger:disabled,.btn-danger[disabled]{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} -------------------------------------------------------------------------------- /app/static/css/dashboard.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Base structure 3 | */ 4 | 5 | /* Move down content because we have a fixed navbar that is 50px tall */ 6 | body { 7 | padding-top: 50px; 8 | } 9 | 10 | 11 | /* 12 | * Global add-ons 13 | */ 14 | 15 | .sub-header { 16 | padding-bottom: 10px; 17 | border-bottom: 1px solid #eee; 18 | } 19 | 20 | /* 21 | * Top navigation 22 | * Hide default border to remove 1px line. 23 | */ 24 | .navbar-fixed-top { 25 | border: 0; 26 | } 27 | 28 | /* 29 | * Sidebar 30 | */ 31 | 32 | /* Hide for mobile, show later */ 33 | .sidebar { 34 | display: none; 35 | } 36 | @media (min-width: 768px) { 37 | .sidebar { 38 | position: fixed; 39 | top: 51px; 40 | bottom: 0; 41 | left: 0; 42 | z-index: 1000; 43 | display: block; 44 | padding: 20px; 45 | overflow-x: hidden; 46 | overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ 47 | background-color: #f5f5f5; 48 | border-right: 1px solid #eee; 49 | } 50 | } 51 | 52 | /* Sidebar navigation */ 53 | .nav-sidebar { 54 | margin-right: -21px; /* 20px padding + 1px border */ 55 | margin-bottom: 20px; 56 | margin-left: -20px; 57 | } 58 | .nav-sidebar > li > a { 59 | padding-right: 20px; 60 | padding-left: 20px; 61 | } 62 | .nav-sidebar > .active > a, 63 | .nav-sidebar > .active > a:hover, 64 | .nav-sidebar > .active > a:focus { 65 | color: #fff; 66 | background-color: #428bca; 67 | } 68 | 69 | 70 | /* 71 | * Main content 72 | */ 73 | 74 | .main { 75 | padding: 20px; 76 | } 77 | @media (min-width: 768px) { 78 | .main { 79 | padding-right: 40px; 80 | padding-left: 40px; 81 | } 82 | } 83 | .main .page-header { 84 | margin-top: 0; 85 | } 86 | 87 | 88 | /* 89 | * Placeholder dashboard ideas 90 | */ 91 | 92 | .placeholders { 93 | margin-bottom: 30px; 94 | text-align: center; 95 | } 96 | .placeholders h4 { 97 | margin-bottom: 0; 98 | } 99 | .placeholder { 100 | margin-bottom: 20px; 101 | } 102 | .placeholder img { 103 | display: inline-block; 104 | border-radius: 50%; 105 | } 106 | -------------------------------------------------------------------------------- /app/static/css/theme.css: -------------------------------------------------------------------------------- 1 | pre { 2 | font-family: Menlo, Consolas, 'DejaVu Sans Mono'; 3 | font-size: 0.8em; 4 | } 5 | -------------------------------------------------------------------------------- /app/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcsrainbow/devopsdemo/3e60246969c4d4f3b74d2ef4237e2b3963d48b04/app/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /app/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcsrainbow/devopsdemo/3e60246969c4d4f3b74d2ef4237e2b3963d48b04/app/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /app/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcsrainbow/devopsdemo/3e60246969c4d4f3b74d2ef4237e2b3963d48b04/app/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /app/static/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcsrainbow/devopsdemo/3e60246969c4d4f3b74d2ef4237e2b3963d48b04/app/static/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /app/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcsrainbow/devopsdemo/3e60246969c4d4f3b74d2ef4237e2b3963d48b04/app/static/img/favicon.ico -------------------------------------------------------------------------------- /app/static/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcsrainbow/devopsdemo/3e60246969c4d4f3b74d2ef4237e2b3963d48b04/app/static/img/loading.gif -------------------------------------------------------------------------------- /app/static/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.4 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.4",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.4",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.4",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.4",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.4",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(this.options.viewport.selector||this.options.viewport),this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c&&c.$tip&&c.$tip.is(":visible")?void(c.hoverState="in"):(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.options.container?a(this.options.container):this.$element.parent(),p=this.getPosition(o);h="bottom"==h&&k.bottom+m>p.bottom?"top":"top"==h&&k.top-mp.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.width&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type)})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.4",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.4",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.4",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=a(document.body).height();"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); -------------------------------------------------------------------------------- /app/static/js/theme.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcsrainbow/devopsdemo/3e60246969c4d4f3b74d2ef4237e2b3963d48b04/app/static/js/theme.js -------------------------------------------------------------------------------- /app/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | DevOpsDemo 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 61 | 62 |
63 |
64 | 80 |
81 | {% with messages = get_flashed_messages() %} 82 | {% if messages %} 83 |
    84 | 89 |
90 | {% endif %} 91 | {% endwith %} 92 | {% block content %}{% endblock %} 93 |
94 |
95 | 96 | 97 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /app/templates/editor.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% set active_page = "editor" %} 3 | {% block content %} 4 | 5 |
6 | {{ form.hidden_tag() }} 7 |
8 |
9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 |
{{ form.file_path(size=40, class="form-control", placeholder="/path/to/file") }}
20 | 21 | 22 |
28 | {% if file_path and not file_access %} 29 | 32 | {% elif failed_backup %} 33 | 36 | {% elif succeeded_backup %} 37 | 40 | {% elif file_no_change %} 41 | 44 | {% endif %} 45 | 46 | 47 | 48 | 49 |
{{ form.file_data(cols=100, rows=20, wrap="off", class="form-control", placeholder="Please input file path then click 'Read' button.") }}
50 |
51 |
52 |
53 | {% endblock %} 54 | -------------------------------------------------------------------------------- /app/templates/hadoop.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% set active_page = "hadoop" %} 3 | {% block content %} 4 | 5 |
6 | {{ form.hidden_tag() }} 7 |
8 |
9 |
10 |
11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 |
{{ form.slave_hostname(size=31, class="form-control", placeholder="slave_hostname eg: idc1-hmr50") }}     16 | {{ form.master_hostname(class="form-control") }} 17 |
 
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 |
33 | 34 | 37 | 38 |
39 | {% if failed_host %} 40 | 43 | {% elif failed_auth %} 44 | 47 | {% elif none_host %} 48 | 51 | {% elif failed_cmd %} 52 | 55 |
56 |
57 |

Output

58 |
59 |
60 | {% if out.stderr %} 61 |
{{ out.stderr }}
62 | {% endif %} 63 | {% if out.stdout %} 64 |
{{ out.stdout }}
65 | {% endif %} 66 |
67 |
68 | {% elif succeeded_cmd %} 69 | 72 |
73 |
74 |

Output

75 |
76 |
77 |
{{ out.stdout }}
78 |
79 |
80 | {% endif %} 81 |
82 | {% endblock %} 83 | -------------------------------------------------------------------------------- /app/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% set active_page = "index" %} 3 | {% block content %} 4 | 5 |
6 |

Hi, {{ g.user.firstname }}!

7 | {% for post in posts %} 8 |

{{ post.author.nickname }} says: {{ post.body }}

9 | {% endfor %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% set active_page = "login" %} 3 | {% block content %} 4 |
5 | {{ form.hidden_tag() }} 6 | 7 | 8 | 9 | 10 | 11 | 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 |
Email:{{ form.email(size=20, class="form-control") }}
  
Password:  {{ form.password(size=20, class="form-control") }}
  
 {{ form.remember_me }} Remember Me
  
 
  
40 | {% if failed_auth %} 41 | 44 | {% endif %} 45 | {% for message in form.email.errors %} 46 | 49 | {% endfor %} 50 | {% for message in form.password.errors %} 51 | 54 | {% endfor %} 55 |
56 | {% endblock %} 57 | -------------------------------------------------------------------------------- /app/templates/operations.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% set active_page = "operations" %} 3 | {% block content %} 4 |
5 | {{ form.hidden_tag() }} 6 |
7 |
8 |

9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
{{ form.hostname(size=25, class="form-control", placeholder="hostname") }}        {{ form.cmd(size=50, class="form-control", placeholder="command") }}        
20 |
21 |
22 |
23 | 24 | 27 | 28 |
29 | {% if failed_host %} 30 | 33 | {% elif failed_auth %} 34 | 37 | {% elif blacklisted_word %} 38 | 41 | {% elif failed_cmd %} 42 | 45 |
46 |
47 |

Output

48 |
49 |
50 | {% if out.stderr %} 51 |
{{ out.stderr }}
52 | {% endif %} 53 | {% if out.stdout %} 54 |
{{ out.stdout }}
55 | {% endif %} 56 |
57 |
58 | {% elif succeeded_cmd %} 59 | 62 |
63 |
64 |

Output

65 |
66 |
67 |
{{ out.stdout }}
68 |
69 |
70 | {% endif %} 71 |
72 | {% endblock %} 73 | -------------------------------------------------------------------------------- /app/templates/racktables.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% set active_page = "racktables" %} 3 | {% block content %} 4 | 5 |
6 | {{ form.hidden_tag() }} 7 |
8 |
9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 29 | 30 | 37 | 38 | 45 | 46 | 53 | 54 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
{{ form.objectname(size=31, class="form-control", placeholder="hostname/objectname/rackspace") }}
 
20 | {% for subfield in form.objecttype %} 21 | {% if subfield.data == 'online_mode' %} 22 | {{ subfield(checked="checked") }} Online Mode       23 | {% endif %} 24 | {% if subfield.data == 'offline_mode' %} 25 | {{ subfield }} Offline Mode       26 | {% endif %} 27 | {% endfor %} 28 |        31 | {% for subfield in form.objecttype %} 32 | {% if subfield.data == 'patch_panel' %} 33 | {{ subfield }} Patch Panel       34 | {% endif %} 35 | {% endfor %} 36 |        39 | {% for subfield in form.objecttype %} 40 | {% if subfield.data == 'network_switch' %} 41 | {{ subfield }} Network Switch       42 | {% endif %} 43 | {% endfor %} 44 |        47 | {% for subfield in form.objecttype %} 48 | {% if subfield.data == 'network_security' %} 49 | {{ subfield }} Network Security       50 | {% endif %} 51 | {% endfor %} 52 |        55 | {% for subfield in form.objecttype %} 56 | {% if subfield.data == 'pdu' %} 57 | {{ subfield }} PDU       58 | {% endif %} 59 | {% endfor %} 60 |
 
{{ form.rackspace(size=20, class="form-control", placeholder="rackspace") }}      {{ form.rackposition(class="form-control") }}
 
75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 |
87 |
88 |
89 | 90 | 93 | 94 |
95 | {% if failed_cmd %} 96 | 99 |
100 |
101 |

Output

102 |
103 |
104 | {% if out.stderr %} 105 |
{{ out.stderr }}
106 | {% endif %} 107 | {% if out.stdout %} 108 |
{{ out.stdout }}
109 | {% endif %} 110 |
111 |
112 | {% elif succeeded_cmd %} 113 | 116 |
117 |
118 |

Output

119 |
120 |
121 |
{{ out.stdout }}
122 |
123 |
124 | {% endif %} 125 |
126 | {% endblock %} 127 | -------------------------------------------------------------------------------- /app/templates/signup.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% set active_page = "signup" %} 3 | {% block content %} 4 |
5 | {{ form.hidden_tag() }} 6 | 7 | 8 | 9 | 10 | 11 | 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 | 55 |
First name:{{ form.firstname(size=25, class="form-control") }}
  
Last name:{{ form.lastname(size=25, class="form-control") }}
  
Email Address:{{ form.email(size=25, class="form-control") }}
  
Password:{{ form.password(size=25, class="form-control") }}
  
Repeat Password:  {{ form.confirm(size=25, class="form-control") }}
  
 
  
56 | {% for message in form.firstname.errors %} 57 | 60 | {% endfor %} 61 | {% for message in form.lastname.errors %} 62 | 65 | {% endfor %} 66 | {% for message in form.email.errors %} 67 | 70 | {% endfor %} 71 | {% for message in form.password.errors %} 72 | 75 | {% endfor %} 76 |
77 | {% endblock %} 78 | -------------------------------------------------------------------------------- /app/templates/status.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% set active_page = "status" %} 3 | {% block content %} 4 |
5 |
6 |

7 |
8 |
9 |
10 |

FQDN: {{ os_fqdn }}

11 |

Release: {{ os_release }}

12 |

Memory: {{ os_memory }}M

13 |

CPU: {{ cpu_type }}, {{cpu_cores}} Core(s)

14 |

Network: {{ os_network }}

15 |

Disk: {{ disk_usage }}

16 |

Stats:
17 | {% for top_info_item in top_info_list %} 18 | {{ top_info_item }}
19 | {% endfor %} 20 |

21 |
22 |
23 |
24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /app/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcsrainbow/devopsdemo/3e60246969c4d4f3b74d2ef4237e2b3963d48b04/app/utils/__init__.py -------------------------------------------------------------------------------- /app/utils/operations.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python 2 | 3 | ''' 4 | A simple interface to execute shell commands. 5 | Reference: fabric/operations.py. 6 | 7 | Examples: 8 | >>> from operations import local,remote,get,put 9 | 10 | >>> out = local('uname -r') 11 | >>> print out 12 | 2.6.32 13 | >>> print out.stdout 14 | 2.6.32 15 | >>> print out.failed 16 | False 17 | >>> print out.succeeded 18 | True 19 | 20 | >>> out = remote('hostname --fqdn',hostname='heylinux.com',username='jobs', 21 | pkey='/path/to/rsa',port=8022) 22 | >>> print out 23 | heylinux.com 24 | >>> print out.failed 25 | False 26 | >>> print out.succeeded 27 | True 28 | 29 | >>> out = get('/tmp/remote.txt','/tmp/local.txt',hostname='heylinux.com',username='jobs', 30 | pkey='/path/to/dsa',pkey_type='dsa',port=8022) 31 | >>> print out.failed 32 | True 33 | >>> print out.succeeded 34 | False 35 | >>> print out.stderr 36 | No such file or directory 37 | 38 | >>> out = put('/tmp/local.txt','/tmp/remote.txt',hostname='heylinux.com',username='jobs', 39 | password='apple') 40 | >>> print out.failed 41 | False 42 | >>> print out.succeeded 43 | True 44 | ''' 45 | 46 | import subprocess 47 | import paramiko 48 | 49 | class _AttributeString(str): 50 | """ 51 | Simple string subclass to allow arbitrary attribute access. 52 | """ 53 | @property 54 | def stdout(self): 55 | return str(self) 56 | 57 | def local(cmd, capture=True, shell=None): 58 | out_stream = subprocess.PIPE 59 | err_stream = subprocess.PIPE 60 | p = subprocess.Popen(cmd, shell=True, stdout=out_stream, stderr=err_stream, executable=shell) 61 | (stdout, stderr) = p.communicate() 62 | 63 | out = _AttributeString(stdout.strip() if stdout else "") 64 | err = _AttributeString(stderr.strip() if stderr else "") 65 | 66 | out.cmd = cmd 67 | out.failed = False 68 | out.return_code = p.returncode 69 | out.stderr = err 70 | if out.return_code != 0: 71 | out.failed = True 72 | out.succeeded = not out.failed 73 | 74 | return out 75 | 76 | def remote(cmd, hostname, username, password=None, pkey=None, pkey_type="rsa", port=22): 77 | p = paramiko.SSHClient() 78 | p.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 79 | 80 | if pkey is not None: 81 | if pkey_type == "dsa": 82 | pkey = paramiko.DSSKey.from_private_key_file(pkey) 83 | else: 84 | pkey = paramiko.RSAKey.from_private_key_file(pkey) 85 | p.connect(hostname=hostname, username=username, pkey=pkey, port=port) 86 | else: 87 | p.connect(hostname=hostname, username=username, password=password, port=port) 88 | 89 | (stdin, stdout, stderr) = p.exec_command(cmd) 90 | 91 | stdout_str="" 92 | stderr_str="" 93 | for line in stdout.readlines(): 94 | stdout_str = stdout_str + line 95 | for line in stderr.readlines(): 96 | stderr_str = stderr_str + line 97 | 98 | out = _AttributeString(stdout_str.strip() if stdout else "") 99 | err = _AttributeString(stderr_str.strip() if stderr else "") 100 | 101 | out.cmd = cmd 102 | out.failed = False 103 | out.return_code = stdout.channel.recv_exit_status() 104 | out.stderr = err 105 | if out.return_code != 0: 106 | out.failed = True 107 | out.succeeded = not out.failed 108 | 109 | p.close() 110 | return out 111 | 112 | def sftp(src_path, dest_path, hostname, username, password=None, pkey=None, pkey_type="rsa", 113 | port=22, transfer_type=None): 114 | p = paramiko.Transport((hostname,port)) 115 | 116 | if pkey is not None: 117 | if pkey_type == "dsa": 118 | pkey = paramiko.DSSKey.from_private_key_file(pkey) 119 | else: 120 | pkey = paramiko.RSAKey.from_private_key_file(pkey) 121 | p.connect(username=username, pkey=pkey) 122 | else: 123 | p.connect(username=username, password=password) 124 | 125 | sftp = paramiko.SFTPClient.from_transport(p) 126 | 127 | out = _AttributeString() 128 | out.failed = False 129 | out.stderr = None 130 | 131 | if transfer_type is not None: 132 | try: 133 | if transfer_type == "get": 134 | sftp.get(src_path, dest_path) 135 | if transfer_type == "put": 136 | sftp.put(src_path, dest_path) 137 | except Exception, e: 138 | out.failed = True 139 | out.stderr = e.args[1] 140 | 141 | out.succeeded = not out.failed 142 | 143 | p.close() 144 | return out 145 | 146 | def get(remote_path, local_path, hostname, username, password=None, pkey=None, pkey_type="rsa", port=22): 147 | return sftp(remote_path, local_path, hostname, username, password=password, pkey=pkey, pkey_type=pkey_type, 148 | port=port, transfer_type="get") 149 | 150 | def put(local_path, remote_path, hostname, username, password=None, pkey=None, pkey_type="rsa", port=22): 151 | return sftp(local_path, remote_path, hostname, username, password=password, pkey=pkey, pkey_type=pkey_type, 152 | port=port, transfer_type="put") 153 | -------------------------------------------------------------------------------- /app/views.py: -------------------------------------------------------------------------------- 1 | from flask import render_template, flash, redirect, session, url_for, request, g 2 | from flask.ext.login import login_user, logout_user, current_user, login_required 3 | from app import app, db, lm 4 | from app.models import User 5 | 6 | 7 | @lm.user_loader 8 | def load_user(uid): 9 | return User.query.get(int(uid)) 10 | 11 | 12 | @app.before_request 13 | def before_request(): 14 | g.user = current_user 15 | 16 | 17 | @app.route('/') 18 | @app.route('/index') 19 | @login_required 20 | def index(): 21 | posts = [{ 'author': { 'nickname': 'Dong' }, 22 | 'body': 'Bootstrap is beautiful, and Flask is cool!' }] 23 | return render_template('index.html',posts=posts) 24 | 25 | 26 | @app.route('/status') 27 | @login_required 28 | def status(): 29 | from app.utils.operations import local 30 | from decimal import Decimal 31 | 32 | os_fqdn = local('hostname --fqdn') 33 | 34 | os_release = local('cat /etc/*-release |head -n 1 |cut -d= -f2 |sed s/\\"//g') 35 | 36 | mem_kb = local("""grep -w "MemTotal" /proc/meminfo |awk '{print $2}'""") 37 | mem_mb = Decimal(mem_kb) / 1024 38 | os_memory = round(mem_mb,2) 39 | 40 | cpu_type = local("""grep 'model name' /proc/cpuinfo |uniq |awk -F : '{print $2}' |sed 's/^[ \t]*//g' |sed 's/ \+/ /g'""") 41 | cpu_cores = local("""grep 'processor' /proc/cpuinfo |sort |uniq |wc -l""") 42 | 43 | nics = local("""/sbin/ifconfig |grep "Link encap" |awk '{print $1}' |grep -wv 'lo' |xargs""") 44 | nics_list = nics.split() 45 | t_nic_info = "" 46 | for i in nics_list: 47 | ipaddr = local("""/sbin/ifconfig %s |grep -w "inet addr" |cut -d: -f2 | awk '{print $1}'""" % (i)) 48 | if ipaddr: 49 | t_nic_info = t_nic_info + i + ":" + ipaddr + ", " 50 | 51 | disk_usage = local("""df -hP |grep -Ev 'Filesystem|tmpfs' |awk '{print $3"/"$2" "$5" "$6", "}' |xargs""") 52 | 53 | top_info = local('top -b1 -n1 |head -n 5') 54 | top_info_list = top_info.split('\n') 55 | 56 | return render_template('status.html', 57 | os_fqdn=os_fqdn, 58 | os_release=os_release, 59 | os_memory=os_memory, 60 | cpu_type=cpu_type, 61 | cpu_cores=cpu_cores, 62 | os_network=t_nic_info, 63 | disk_usage=disk_usage, 64 | top_info_list=top_info_list) 65 | 66 | 67 | @app.route('/operations', methods=['GET', 'POST']) 68 | @login_required 69 | def operations(): 70 | import paramiko 71 | from app.utils.operations import remote 72 | from config import basedir 73 | from app.forms import OperationsForm 74 | 75 | def isup(hostname): 76 | import socket 77 | 78 | conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 79 | conn.settimeout(2) 80 | try: 81 | conn.connect((hostname,22)) 82 | conn.close() 83 | except: 84 | return False 85 | return True 86 | 87 | form = OperationsForm() 88 | if form.validate_on_submit(): 89 | username = 'dong' 90 | pkey = basedir + '/sshkeys/id_rsa' 91 | 92 | hostname = form.hostname.data 93 | cmd = form.cmd.data 94 | 95 | if not isup(hostname): 96 | return render_template('operations.html',form=form,failed_host=hostname) 97 | 98 | blacklist = ['reboot', 'shutdown', 'poweroff', 99 | 'rm', 'mv', '-delete', 'source', 'sudo', 100 | '<', '<<', '>>', '>'] 101 | for item in blacklist: 102 | if item in cmd.split(): 103 | return render_template('operations.html',form=form,blacklisted_word=item) 104 | 105 | try: 106 | out = remote(cmd,hostname=hostname,username=username,pkey=pkey) 107 | except paramiko.AuthenticationException: 108 | return render_template('operations.html',form=form,hostname=hostname,failed_auth=True) 109 | 110 | failed_cmd = out.failed 111 | succeeded_cmd = out.succeeded 112 | 113 | return render_template('operations.html', 114 | form=form, 115 | cmd=cmd, 116 | hostname=hostname, 117 | out=out, 118 | failed_cmd=failed_cmd, 119 | succeeded_cmd=succeeded_cmd) 120 | 121 | return render_template('operations.html',form=form) 122 | 123 | 124 | @app.route('/racktables', methods=['GET', 'POST']) 125 | @login_required 126 | def racktables(): 127 | from app.utils.operations import local 128 | from config import basedir 129 | from app.forms import RacktablesForm 130 | 131 | form = RacktablesForm() 132 | if form.validate_on_submit(): 133 | param_do = form.do_action.data 134 | objectname = form.objectname.data 135 | objecttype = form.objecttype.data 136 | param_s = form.rackspace.data 137 | param_p = form.rackposition.data 138 | 139 | script = basedir + "/scripts/racktables.py" 140 | 141 | if param_do == 'help': 142 | cmd = "{0} -h".format(script) 143 | 144 | if param_do == 'get': 145 | cmd = "{0}".format(script) 146 | if objectname: 147 | cmd = "{0} {1}".format(script,objectname) 148 | 149 | if param_do == 'list': 150 | cmd = "{0} {1} -l".format(script,objectname) 151 | 152 | if param_do == 'read': 153 | cmd = "{0} {1} -r".format(script,objectname) 154 | if objecttype == 'offline_mode': 155 | cmd = cmd + " -o" 156 | if objecttype == 'patch_panel': 157 | cmd = cmd + " -b" 158 | if objecttype == 'network_switch': 159 | cmd = cmd + " -n" 160 | if objecttype == 'network_security': 161 | cmd = cmd + " -f" 162 | if objecttype == 'pdu': 163 | cmd = cmd + " -u" 164 | 165 | if param_do == 'write': 166 | cmd = "{0} {1} -w".format(script,objectname) 167 | if param_s: 168 | cmd = cmd + " -s {0}".format(param_s) 169 | if param_p != 'none': 170 | cmd = cmd + " -p {0}".format(param_p) 171 | if objecttype == 'offline_mode': 172 | cmd = cmd + " -o" 173 | if objecttype == 'patch_panel': 174 | cmd = cmd + " -b" 175 | if objecttype == 'network_switch': 176 | cmd = cmd + " -n" 177 | if objecttype == 'network_security': 178 | cmd = cmd + " -f" 179 | if objecttype == 'pdu': 180 | cmd = cmd + " -u" 181 | 182 | if param_do == 'delete': 183 | cmd = "{0} {1} -d".format(script,objectname) 184 | 185 | out = local(cmd) 186 | 187 | failed_cmd = out.failed 188 | succeeded_cmd = out.succeeded 189 | 190 | return render_template('racktables.html', 191 | form=form, 192 | cmd=cmd, 193 | out=out, 194 | failed_cmd=failed_cmd, 195 | succeeded_cmd=succeeded_cmd) 196 | 197 | return render_template('racktables.html',form=form) 198 | 199 | 200 | @app.route('/hadoop', methods=['GET', 'POST']) 201 | @login_required 202 | def hadoop(): 203 | import paramiko 204 | from app.utils.operations import remote 205 | from config import basedir 206 | from app.forms import HadoopForm 207 | 208 | def isup(hostname): 209 | import socket 210 | 211 | conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 212 | conn.settimeout(2) 213 | try: 214 | conn.connect((hostname,22)) 215 | conn.close() 216 | except: 217 | return False 218 | return True 219 | 220 | form = HadoopForm() 221 | if form.validate_on_submit(): 222 | username = 'dong' 223 | pkey = basedir + '/sshkeys/id_rsa' 224 | 225 | param_do = form.do_action.data 226 | slave_hostname = form.slave_hostname.data 227 | master_hostname = form.master_hostname.data 228 | 229 | if master_hostname == 'none': 230 | return render_template('hadoop.html',form=form,none_host=True) 231 | 232 | if master_hostname in ['idc1-hnn1', 'idc2-hnn1']: 233 | script = '/root/bin/excludedn' 234 | 235 | if master_hostname in ['idc1-hrm1', 'idc2-hrm1']: 236 | script = '/root/bin/excludeyn' 237 | 238 | if param_do == 'exclude': 239 | cmd = "sudo {0} {1}".format(script,slave_hostname) 240 | 241 | if param_do == 'recover': 242 | cmd = "sudo {0} -r {1}".format(script,slave_hostname) 243 | 244 | if not isup(master_hostname): 245 | return render_template('hadoop.html',form=form,failed_host=master_hostname) 246 | 247 | try: 248 | out = remote(cmd,hostname=master_hostname,username=username,pkey=pkey) 249 | except paramiko.AuthenticationException: 250 | return render_template('hadoop.html',form=form,master_hostname=master_hostname,failed_auth=True) 251 | 252 | failed_cmd = out.failed 253 | succeeded_cmd = out.succeeded 254 | 255 | return render_template('hadoop.html', 256 | form=form, 257 | cmd=cmd, 258 | master_hostname=master_hostname, 259 | out=out, 260 | failed_cmd=failed_cmd, 261 | succeeded_cmd=succeeded_cmd) 262 | 263 | return render_template('hadoop.html',form=form) 264 | 265 | 266 | @app.route('/editor', methods=['GET', 'POST']) 267 | @login_required 268 | def editor(): 269 | import os 270 | import time 271 | from hashlib import md5 272 | from app.utils.operations import local 273 | from app.forms import EditorForm 274 | 275 | form = EditorForm() 276 | if form.validate_on_submit(): 277 | param_do = form.do_action.data 278 | file_path = form.file_path.data 279 | 280 | if param_do == 'read': 281 | file_access = os.access(file_path, os.W_OK) 282 | if not file_access: 283 | return render_template('editor.html', 284 | form=form, 285 | file_path=file_path, 286 | file_access=file_access) 287 | 288 | with open(file_path, 'rb') as f: 289 | file_data = f.read() 290 | f.closed 291 | form.file_data.data=file_data 292 | return render_template('editor.html', 293 | form=form, 294 | file_path=file_path, 295 | file_access=file_access) 296 | 297 | if param_do == 'save': 298 | file_access = os.access(file_path, os.W_OK) 299 | if not file_access: 300 | return render_template('editor.html', 301 | form=form, 302 | file_path=file_path, 303 | file_access=file_access) 304 | 305 | file_md5sum = md5(open(file_path, 'rb').read()).hexdigest() 306 | form_md5sum = md5(form.file_data.data.replace('\r\n','\n')).hexdigest() 307 | if file_md5sum == form_md5sum: 308 | return render_template('editor.html', 309 | form=form, 310 | file_path=file_path, 311 | file_access=file_access, 312 | file_no_change=True) 313 | 314 | 315 | postfix = time.strftime("%Y%m%d%H%M%S") 316 | file_backup = file_path + "." + postfix 317 | 318 | backup_out = local("cp -p {0} {1}".format(file_path,file_backup)) 319 | succeeded_backup = backup_out.succeeded 320 | failed_backup = backup_out.failed 321 | 322 | file = open(file_path, 'wb') 323 | file.write(form.file_data.data.replace('\r\n','\n')) 324 | file.close() 325 | 326 | return render_template('editor.html', 327 | form=form, 328 | file_path=file_path, 329 | file_access=file_access, 330 | postfix=postfix, 331 | backup_out=backup_out, 332 | failed_backup=failed_backup, 333 | succeeded_backup=succeeded_backup) 334 | 335 | return render_template('editor.html',form=form) 336 | 337 | 338 | @app.route('/signup', methods=['GET', 'POST']) 339 | def signup(): 340 | from app.forms import SignupForm 341 | 342 | form = SignupForm() 343 | if form.validate_on_submit(): 344 | user = User.query.filter_by(email=form.email.data.lower()).first() 345 | if user is not None: 346 | form.email.errors.append("The Email address is already taken.") 347 | return render_template('signup.html', form=form) 348 | 349 | newuser = User(form.firstname.data,form.lastname.data,form.email.data,form.password.data) 350 | db.session.add(newuser) 351 | db.session.commit() 352 | 353 | session['email'] = newuser.email 354 | return redirect(url_for('login')) 355 | 356 | return render_template('signup.html', form=form) 357 | 358 | 359 | @app.route('/login', methods=['GET', 'POST']) 360 | def login(): 361 | if g.user is not None and g.user.is_authenticated(): 362 | return redirect(url_for('index')) 363 | 364 | from app.forms import LoginForm 365 | 366 | form = LoginForm() 367 | if form.validate_on_submit(): 368 | session['remember_me'] = form.remember_me.data 369 | user = User.query.filter_by(email=form.email.data.lower()).first() 370 | if user and user.check_password(form.password.data): 371 | session['email'] = form.email.data 372 | login_user(user,remember=session['remember_me']) 373 | return redirect(url_for('index')) 374 | else: 375 | return render_template('login.html',form=form,failed_auth=True) 376 | 377 | return render_template('login.html',form=form) 378 | 379 | 380 | @app.route('/logout') 381 | def logout(): 382 | logout_user() 383 | return redirect(url_for('index')) 384 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | import os 2 | basedir = os.path.abspath(os.path.dirname(__file__)) 3 | 4 | CSRF_ENABLED = True 5 | SECRET_KEY = 'devopsdemo_secret_key' 6 | 7 | SQLALCHEMY_DATABASE_URI = 'mysql://demo_user:demo_pass@localhost/devopsdemo' 8 | -------------------------------------------------------------------------------- /flask/requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | flask-login 3 | flask-openid 4 | flask-mail 5 | flask-sqlalchemy 6 | flask-wtf 7 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | #!flask/bin/python 2 | 3 | from app import app 4 | app.run(host='0.0.0.0', debug=True) 5 | -------------------------------------------------------------------------------- /screenshots/editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcsrainbow/devopsdemo/3e60246969c4d4f3b74d2ef4237e2b3963d48b04/screenshots/editor.png -------------------------------------------------------------------------------- /screenshots/hadoop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcsrainbow/devopsdemo/3e60246969c4d4f3b74d2ef4237e2b3963d48b04/screenshots/hadoop.png -------------------------------------------------------------------------------- /screenshots/index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcsrainbow/devopsdemo/3e60246969c4d4f3b74d2ef4237e2b3963d48b04/screenshots/index.png -------------------------------------------------------------------------------- /screenshots/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcsrainbow/devopsdemo/3e60246969c4d4f3b74d2ef4237e2b3963d48b04/screenshots/login.png -------------------------------------------------------------------------------- /screenshots/operations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcsrainbow/devopsdemo/3e60246969c4d4f3b74d2ef4237e2b3963d48b04/screenshots/operations.png -------------------------------------------------------------------------------- /screenshots/racktables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcsrainbow/devopsdemo/3e60246969c4d4f3b74d2ef4237e2b3963d48b04/screenshots/racktables.png -------------------------------------------------------------------------------- /screenshots/signup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcsrainbow/devopsdemo/3e60246969c4d4f3b74d2ef4237e2b3963d48b04/screenshots/signup.png -------------------------------------------------------------------------------- /screenshots/status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcsrainbow/devopsdemo/3e60246969c4d4f3b74d2ef4237e2b3963d48b04/screenshots/status.png -------------------------------------------------------------------------------- /scripts/racktables.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python 2 | #-*- coding:utf-8 -*- 3 | 4 | # Author: Dong Guo 5 | # Last Modified: 2015/04/30 6 | 7 | import os 8 | import sys 9 | from decimal import Decimal 10 | import requests 11 | from requests.auth import HTTPBasicAuth 12 | 13 | # import urllib to encode the data for POST 14 | from urllib import quote_plus 15 | 16 | # torndb is a lightweight wrapper around MySQLdb 17 | try: 18 | from torndb import Connection 19 | except ImportError: 20 | sys.stderr.write("ERROR: Requires torndb, try 'yum install MySQL-python' then 'pip install torndb'.\n") 21 | sys.exit(1) 22 | 23 | # racktables server address, database, username, password, headers, auth 24 | rt_server = "real_rt_server" 25 | rt_webuser = "real_rt_webuser" 26 | rt_webpass = "real_rt_webpass" 27 | rt_dbname = "real_rt_dbname" 28 | rt_dbuser = "real_rt_dbuser" 29 | rt_dbpass = "real_rt_dbpass" 30 | 31 | rt_headers = {'Content-Type': 'application/x-www-form-urlencoded'} 32 | rt_auth = HTTPBasicAuth(rt_webuser, rt_webpass) 33 | 34 | def parse_opts(): 35 | """Help messages (-h, --help) for racktables.py""" 36 | 37 | import textwrap 38 | import argparse 39 | 40 | parser = argparse.ArgumentParser( 41 | formatter_class=argparse.RawDescriptionHelpFormatter, 42 | description=textwrap.dedent( 43 | ''' 44 | examples: 45 | {0} idc1-server1 46 | {0} idc1-server1 -r 47 | {0} idc1-server1 -d 48 | {0} idc1-server1 -w 49 | {0} idc1-server1 -w -s IDC1:P1:C1:1 50 | {0} idc1-server2 -o -w -s IDC1:P1:C1:2 51 | {0} idc1-server2 -o -r 52 | {0} idc2-server1 -w -s IDC2:P2:C1:1,2 53 | {0} idc2-server2 -w -s IDC2:P1:C2:20 -p left 54 | {0} idc2-server3 -w -s IDC2:P1:C2:20 -p right 55 | {0} BlankIDC2P1C2U9 -b -w -s IDC2:P1:C2:9 56 | {0} BlankIDC2P1C2U9 -b -r 57 | {0} BlankIDC2P1C2U9 -d 58 | {0} IDC2:P1:C2 -l 59 | {0} idc2-switch1 -n -w -s IDC2:P3:C3:30 60 | {0} idc2-switch1 -n -r 61 | {0} idc2-switch1 -d 62 | {0} idc1-firewall1 -f -w -s IDC2:P3:C3:40 63 | {0} idc1-firewall1 -f -r 64 | {0} idc1-firewall1 -d 65 | {0} idc2-pdu1 -u -w -s IDC2:P3:C2:32 66 | {0} idc2-pdu1 -u -d 67 | '''.format(__file__) 68 | )) 69 | parser.add_argument('hostname', action="store", type=str) 70 | exclusion_1 = parser.add_mutually_exclusive_group() 71 | exclusion_1.add_argument('-r', action="store_true", default=False,help='read from database') 72 | exclusion_1.add_argument('-d', action="store_true", default=False,help='delete from database') 73 | exclusion_1.add_argument('-w', action="store_true", default=False,help='write to database') 74 | exclusion_1.add_argument('-l', action="store_true", default=False,help='list hosts and devices of the rackspace') 75 | exclusion_2 = parser.add_mutually_exclusive_group() 76 | exclusion_2.add_argument('-o', action="store_true", default=False,help='offline mode for Type as Server') 77 | exclusion_2.add_argument('-b', action="store_true", default=False,help='set Type as PatchPanel') 78 | exclusion_2.add_argument('-n', action="store_true", default=False,help='set Type as NetworkSwitch') 79 | exclusion_2.add_argument('-f', action="store_true", default=False,help='set Type as NetworkSecurity') 80 | exclusion_2.add_argument('-u', action="store_true", default=False,help='set Type as PDU') 81 | parser.add_argument('-s', metavar='rackspace', type=str, help='rackspace informations') 82 | parser.add_argument('-p', metavar='rackposition', type=str, choices=['left','right','front','interior','back'], help='rackspace detailed position') 83 | 84 | args = parser.parse_args() 85 | return {'hostname':args.hostname, 'read':args.r, 'delete':args.d, 86 | 'offline':args.o, 'blank':args.b, 'switch':args.n, 'security':args.f, 'pdu':args.u, 87 | 'write':args.w, 'rackspace':args.s, 'rackposition':args.p, 'list':args.l } 88 | 89 | class _AttributeString(str): 90 | """ 91 | Simple string subclass to allow arbitrary attribute access. 92 | """ 93 | @property 94 | def stdout(self): 95 | return str(self) 96 | 97 | def remote(cmd, hostname, username, password=None, pkey=None, pkey_type="rsa", port=22): 98 | import warnings 99 | with warnings.catch_warnings(): 100 | warnings.simplefilter("ignore") 101 | import paramiko 102 | 103 | p = paramiko.SSHClient() 104 | p.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 105 | 106 | if pkey is not None: 107 | if pkey_type == "dsa": 108 | pkey = paramiko.DSSKey.from_private_key_file(pkey) 109 | else: 110 | pkey = paramiko.RSAKey.from_private_key_file(pkey) 111 | p.connect(hostname=hostname, username=username, pkey=pkey, port=port) 112 | else: 113 | p.connect(hostname=hostname, username=username, password=password, port=port) 114 | 115 | (stdin, stdout, stderr) = p.exec_command(cmd) 116 | 117 | stdout_str="" 118 | stderr_str="" 119 | for line in stdout.readlines(): 120 | stdout_str = stdout_str + line 121 | for line in stderr.readlines(): 122 | stderr_str = stderr_str + line 123 | 124 | out = _AttributeString(stdout_str.strip() if stdout else "") 125 | err = _AttributeString(stderr_str.strip() if stderr else "") 126 | 127 | out.cmd = cmd 128 | out.failed = False 129 | out.return_code = stdout.channel.recv_exit_status() 130 | out.stderr = err 131 | if out.return_code != 0: 132 | out.failed = True 133 | out.succeeded = not out.failed 134 | 135 | p.close() 136 | return out 137 | 138 | def run(cmd,hostname=False): 139 | if not hostname: 140 | hostname = opts['hostname'] 141 | 142 | username = "root" 143 | pkey = "/root/.ssh/id_dsa" 144 | pkey_type = 'dsa' 145 | 146 | out = remote(cmd, hostname=hostname, username=username, pkey=pkey, pkey_type=pkey_type) 147 | 148 | return out 149 | 150 | def sudo(cmd,hostname=False): 151 | cmd_sudo = 'sudo ' + cmd 152 | 153 | if not hostname: 154 | out = run(cmd_sudo) 155 | else: 156 | out = run(cmd_sudo,hostname) 157 | 158 | return out 159 | 160 | def isup(host): 161 | """Check if host is up""" 162 | 163 | import socket 164 | 165 | conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 166 | conn.settimeout(3) 167 | try: 168 | conn.connect((host,22)) 169 | conn.close() 170 | except: 171 | print "Failed to connect to host {0} port 22: Network is unreachable".format(host) 172 | return False 173 | return True 174 | 175 | class GetServerInfo(object): 176 | """Remotely get informations""" 177 | 178 | def __init__(self): 179 | pass 180 | 181 | def get_server_type(self): 182 | xapi_sign = run('rpm -q --quiet xapi-xe') 183 | if xapi_sign.succeeded: 184 | return "XenServer" 185 | 186 | ec2_sign = run("""curl http://169.254.169.254/latest/meta-data/hostname --connect-timeout 1 |egrep -wq 'ec2.internal|compute.internal'""") 187 | if ec2_sign.succeeded: 188 | return "EC2" 189 | 190 | xen_sign = run('ps -e | egrep -wq "xenbus|xenwatch"') 191 | if xen_sign.succeeded: 192 | return "VM" 193 | 194 | return "Server" 195 | 196 | def get_fqdn(self): 197 | hostname = run('hostname |cut -d. -f1') 198 | fqdn = run('hostname -f') 199 | if fqdn.failed: 200 | fqdn = hostname 201 | return fqdn 202 | 203 | def get_os_release(self): 204 | os_release = run('cat /etc/*-release |head -n 1 |cut -d= -f2 |sed s/\\"//g') 205 | 206 | os_mode = run('uname -m') 207 | return os_release + ", " + os_mode 208 | 209 | def get_memory(self): 210 | output = run("""grep -w "MemTotal" /proc/meminfo |awk '{print $2}'""") 211 | t_mem_g = Decimal(output) / 1024 / 1024 212 | return round(t_mem_g,2) 213 | 214 | def get_swap(self): 215 | output = run("""grep -w "SwapTotal" /proc/meminfo |awk '{print $2}'""") 216 | t_swap_g = Decimal(output) / 1024 /1024 217 | return round(t_swap_g,2) 218 | 219 | def get_cpu(self): 220 | cpu_type = run("""grep 'model name' /proc/cpuinfo |uniq |awk -F : '{print $2}' |sed 's/^[ \t]*//g' |sed 's/ \+/ /g'""") 221 | cpu_cores = run("""grep 'processor' /proc/cpuinfo |sort |uniq |wc -l""") 222 | return {'cpu_cores':cpu_cores, 'cpu_type':cpu_type} 223 | 224 | def get_disk(self): 225 | disk = sudo("""fdisk -l 2>/dev/null |grep -v "/dev/mapper" |grep "Disk /dev" |awk '{print $2" "$3" "$4}'|grep -Ev '^$|^doesn' |sort |xargs""") 226 | return disk 227 | 228 | def get_network(self): 229 | output = run("""/sbin/ifconfig |grep "Link encap" |awk '{print $1}' |grep -wv 'lo' |xargs""") 230 | nics = output.split() 231 | t_nic_info = "" 232 | for i in nics: 233 | ipaddr = run("""/sbin/ifconfig %s |grep -w "inet addr" |cut -d: -f2 | awk '{print $1}'""" % (i)) 234 | if ipaddr: 235 | t_nic_info = t_nic_info + i + ":" + ipaddr + ", " 236 | return t_nic_info 237 | 238 | def get_vm_list(self): 239 | output = sudo("""xl list-vm |awk '{print $3}' |grep -vw name |sort -n |xargs""") 240 | vm_list = ','.join(output.split()) 241 | return vm_list 242 | 243 | def get_rst_on(self): 244 | colo_prefix = run("""hostname -s |egrep 'sc2-|iad2-' |cut -d- -f1""") 245 | xs_pool_master = colo_prefix + '-vm1001' 246 | 247 | if not isup(xs_pool_master): 248 | return False 249 | 250 | vm_uuid = sudo("""xe vm-list |grep -B1 -w %s |awk '{if ($1 == "uuid") print $NF}'""" % (opts['hostname']),hostname=xs_pool_master) 251 | rst_uuid = sudo("""xe vm-param-get uuid={0} param-name=resident-on""".format(vm_uuid),hostname=xs_pool_master) 252 | rst_name = sudo("""xe vm-list params |egrep 'name-label|resident-on' |grep -B1 %s |grep -w "Control domain" |awk -F ": " '{print $NF}' |cut -d. -f1""" % (rst_uuid),hostname=xs_pool_master) 253 | return rst_name 254 | 255 | def get_xs_memory(self): 256 | t_mem_m = sudo('xl info |grep total_memory |cut -d : -f 2') 257 | t_mem_g = int(t_mem_m) / 1024 258 | return t_mem_g 259 | 260 | def get_xs_cpu(self): 261 | cpu_cores = sudo("""xe host-cpu-info |grep -w cpu_count |awk -F ': ' '{print $2}'""") 262 | cpu_type = run("""grep 'model name' /proc/cpuinfo |uniq |awk -F : '{print $2}' |sed 's/^[ \t]*//g' |sed 's/ \+/ /g'""") 263 | return {'cpu_cores':cpu_cores, 'cpu_type':cpu_type} 264 | 265 | def get_ec2_pubname(self): 266 | ec2_pubname = run("""curl http://169.254.169.254/latest/meta-data/public-hostname --connect-timeout 1""") 267 | return ec2_pubname 268 | 269 | def sum_info(): 270 | """Get & Print all informations""" 271 | 272 | server_info = GetServerInfo() 273 | 274 | server_type = server_info.get_server_type() 275 | print "TYPE: " + server_type 276 | 277 | fqdn = server_info.get_fqdn() 278 | print "FQDN: " + fqdn 279 | 280 | os_release = server_info.get_os_release() 281 | print "OS: " + os_release 282 | 283 | if server_type == "XenServer": 284 | memory = server_info.get_xs_memory() 285 | else: 286 | memory = server_info.get_memory() 287 | print "MEMORY: " + str(memory) + " GB" 288 | 289 | swap = server_info.get_swap() 290 | print "SWAP: " + str(swap) + " GB" 291 | 292 | if server_type == "XenServer": 293 | cpu_info = server_info.get_xs_cpu() 294 | else: 295 | cpu_info = server_info.get_cpu() 296 | print "CPU: " + cpu_info['cpu_cores'] + " Cores, " + cpu_info['cpu_type'] 297 | 298 | disk = server_info.get_disk() 299 | print "DISK: " + disk 300 | 301 | network = server_info.get_network() 302 | print "NETWORK: " + network 303 | 304 | vm_list = "" 305 | if server_type == "XenServer": 306 | vm_list = server_info.get_vm_list() 307 | print "VMLIST: " + vm_list 308 | 309 | xs_name = "" 310 | if server_type == "VM": 311 | xs_name = server_info.get_rst_on() 312 | print "RESIDENT-ON: " + xs_name 313 | 314 | ec2_pubname = "" 315 | if server_type == "EC2": 316 | ec2_pubname = server_info.get_ec2_pubname() 317 | print "PUBNAME: " + ec2_pubname 318 | 319 | return {'server_type':server_type, 'fqdn':fqdn, 'hostname':opts['hostname'],'os_release':os_release, 320 | 'memory':memory,'swap':swap, 'cpu_cores':cpu_info['cpu_cores'], 'cpu_type':cpu_info['cpu_type'], 321 | 'disk':disk,'network':network, 'vm_list':vm_list, 'resident_on':xs_name, 'ec2_pubname':ec2_pubname} 322 | 323 | def read_db(info): 324 | """Get info from Racktables DB""" 325 | 326 | # connect to racktables db 327 | db = Connection(rt_server,rt_dbname,rt_dbuser,rt_dbpass) 328 | 329 | # check if object_id already exists 330 | object_id = "" 331 | for item in db.query("select * from Object where name='{0}'".format(info['hostname'])): 332 | object_id = item.id 333 | if not object_id: 334 | print "Object:{0} does not exist".format(info['hostname']) 335 | return False 336 | 337 | # get the location info 338 | rack_id_list = [] 339 | unit_no_list = [] 340 | for item in db.query("select rack_id,unit_no from RackSpace where object_id=(select id from Object where name='{0}')".format(info['hostname'])): 341 | rack_id_list.append(int(item.rack_id)) 342 | unit_no_list.append(int(item.unit_no)) 343 | if not item: 344 | print "Object:{0} does not have location info".format(info['hostname']) 345 | return False 346 | rack_id = ','.join(str(i) for i in list(set(rack_id_list))) 347 | unit_no = ','.join(str(i) for i in list(set(unit_no_list))) 348 | 349 | location_name = "" 350 | row_name = "" 351 | rack_name = "" 352 | for item in db.query("select location_name,row_name,name from Rack where id='{0}'".format(rack_id)): 353 | location_name = item.location_name 354 | row_name = item.row_name 355 | rack_name = item.name 356 | if not location_name or not row_name or not rack_name: 357 | print "Object:{0} does not have location info".format(info['hostname']) 358 | return False 359 | print "RACKSPACE: {0}:{1}:{2}:{3}".format(location_name,row_name,rack_name,unit_no) 360 | 361 | # close db 362 | db.close() 363 | 364 | return {'location_name':location_name, 'row_name':row_name, 'rack_name':rack_name, 'unit_no':unit_no} 365 | 366 | def update_db(info): 367 | """Automate server audit into Racktables""" 368 | 369 | # connect to racktables db 370 | db = Connection(rt_server,rt_dbname,rt_dbuser,rt_dbpass) 371 | 372 | # get object_type_id 373 | for item in db.query("select * from Dictionary where dict_value='{0}'".format(info['server_type'])): 374 | object_type_id = item.dict_key 375 | 376 | # delete object if already exists 377 | delete_object(info) 378 | 379 | # create object 380 | url = """http://{0}/racktables/index.php?module=redirect&page=depot&tab=addmore&op=addObjects""".format(rt_server) 381 | if info['server_type'] in ["Server","XenServer"]: 382 | payload = """0_object_type_id={0}&0_object_name={1}&0_object_label=&0_object_asset_no={1}&got_fast_data=Go%21"""\ 383 | .format(object_type_id,info['hostname']) 384 | if info['server_type'] in ["VM","EC2"]: 385 | payload = """virtual_objects=&0_object_type_id={0}&0_object_name={1}&got_fast_data=Go%21"""\ 386 | .format(object_type_id,info['hostname']) 387 | 388 | req = requests.post(url, data=payload, headers=rt_headers, auth=rt_auth) 389 | if req.status_code != requests.codes.ok: 390 | print "Failed to create object: {0}".format(info['hostname']) 391 | return False 392 | else: 393 | print "OK - Created object: {0}".format(info['hostname']) 394 | 395 | object_id = "" 396 | # get object_id 397 | for item in db.query("select * from Object where name='{0}'".format(info['hostname'])): 398 | object_id = item.id 399 | if not object_id: 400 | print "Failed to get object_id" 401 | return False 402 | 403 | # get os_release_id 404 | os_release_key = "" 405 | for item in db.query("select * from Dictionary where dict_value='{0}'".format(info['os_release'])): 406 | os_release_key = item.dict_key 407 | if not os_release_key: 408 | print "Failed to get object_type_id, please add '{0}' to 'Configuration - Dictionary - Server OS type'.".format(info['os_release']) 409 | return False 410 | 411 | # update the informations of object, all post data formats were got by firebug on firefox 412 | url = """http://{0}/racktables/index.php?module=redirect&page=object&tab=edit&op=update""".format(rt_server) 413 | if info['server_type'] == "Server": 414 | payload = """object_id={0}&object_type_id={1}&object_name={2}&object_label=&object_asset_no={2}&0_attr_id=14&0_value=&1_attr_id=10000&1_value={3}\ 415 | &2_attr_id=10004&2_value={4}&3_attr_id=3&3_value={5}&4_attr_id=2&4_value=0&5_attr_id=26&5_value=0&6_attr_id=10006&6_value={6}\ 416 | &7_attr_id=10003&7_value={7}&8_attr_id=1&8_value=&9_attr_id=28&9_value=&10_attr_id=21&10_value=&11_attr_id=4&11_value={8}\ 417 | &12_attr_id=24&12_value=&13_attr_id=10005&13_value={9}&14_attr_id=25&14_value=&num_attrs=15&object_comment=&submit.x=15&submit.y=13"""\ 418 | .format(object_id,object_type_id,info['hostname'],info['cpu_cores'],quote_plus(info['disk']),info['fqdn'], 419 | info['memory'],quote_plus(info['network']),os_release_key,info['swap']) 420 | if info['server_type'] == "XenServer": 421 | payload = """object_id={0}&object_type_id={1}&object_name={2}&object_label=&object_asset_no={2}&0_attr_id=14&0_value=&1_attr_id=10000&1_value={3}\ 422 | &2_attr_id=10004&2_value={4}&3_attr_id=3&3_value={5}&4_attr_id=26&4_value=0&5_attr_id=10006&5_value={6}&6_attr_id=10003&6_value={7}\ 423 | &7_attr_id=1&7_value=&8_attr_id=28&8_value=&9_attr_id=4&9_value={8}&10_attr_id=24&10_value=&11_attr_id=10005&11_value={9}\ 424 | &12_attr_id=25&12_value=&13_attr_id=10008&13_value={10}&num_attrs=14&object_comment=&submit.x=18&submit.y=21"""\ 425 | .format(object_id,object_type_id,info['hostname'],info['cpu_cores'],quote_plus(info['disk']),info['fqdn'], 426 | info['memory'],quote_plus(info['network']),os_release_key,info['swap'],quote_plus(info['vm_list'])) 427 | if info['server_type'] == "EC2": 428 | payload = """object_id={0}&object_type_id={1}&object_name={2}&object_label=&object_asset_no=&0_attr_id=14&0_value=&1_attr_id=10000&1_value={3}\ 429 | &2_attr_id=10004&2_value={4}&3_attr_id=3&3_value={5}&4_attr_id=26&4_value=0&5_attr_id=10006&5_value={6}&6_attr_id=10003&6_value={7}\ 430 | &7_attr_id=10010&7_value={8}&8_attr_id=4&8_value={9}&9_attr_id=24&9_value=&10_attr_id=10005&10_value={10}&num_attrs=11&object_comment=&submit.x=19&submit.y=27"""\ 431 | .format(object_id,object_type_id,info['hostname'],info['cpu_cores'],quote_plus(info['disk']),info['fqdn'], 432 | info['memory'],quote_plus(info['network']),info['ec2_pubname'],os_release_key,info['swap']) 433 | if info['server_type'] == "VM": 434 | payload = """object_id={0}&object_type_id={1}&object_name={2}&object_label=&object_asset_no=&0_attr_id=14&0_value=&1_attr_id=10000&1_value={3}\ 435 | &2_attr_id=10004&2_value={4}&3_attr_id=3&3_value={5}&4_attr_id=26&4_value=0&5_attr_id=10006&5_value={6}&6_attr_id=10003&6_value={7}\ 436 | &7_attr_id=10007&7_value={8}&8_attr_id=4&8_value={9}&9_attr_id=24&9_value=&10_attr_id=10005&10_value={10}&num_attrs=11&object_comment=&submit.x=25&submit.y=14'"""\ 437 | .format(object_id,object_type_id,info['hostname'],info['cpu_cores'],quote_plus(info['disk']),info['fqdn'], 438 | info['memory'],quote_plus(info['network']),info['resident_on'],os_release_key,info['swap']) 439 | 440 | req = requests.post(url, data=payload, headers=rt_headers, auth=rt_auth) 441 | if req.status_code != requests.codes.ok: 442 | print "Failed to update attributes" 443 | return False 444 | print "OK - Updated attributes" 445 | 446 | # ec2 servers don't need to update the ip pool 447 | if info['server_type'] not in ["EC2"]: 448 | # update the ip pool 449 | nics = ("".join(info['network'].split())).split(',') 450 | for i in nics: 451 | nic_info = i.split(':') 452 | nic_name = "".join(nic_info[0:1]) 453 | nic_addr = "".join(nic_info[1:2]) 454 | # check if nic_name is not correct 455 | if nic_name.isalnum(): 456 | # create nic 457 | url = """http://{0}/racktables/index.php?module=redirect&page=object&tab=ip&op=add""".format(rt_server) 458 | payload = """object_id={0}&bond_name={1}&ip={2}&bond_type=regular&submit.x=11&submit.y=6"""\ 459 | .format(object_id,nic_name,nic_addr) 460 | req = requests.post(url, data=payload, headers=rt_headers, auth=rt_auth) 461 | if req.status_code != requests.codes.ok: 462 | print "Failed to update ip pool for {0}:{1}".format(nic_name,nic_addr) 463 | return False 464 | print "OK - Updated ip pool for {0}:{1}".format(nic_name,nic_addr) 465 | 466 | # virtual servers don't need to update the rackspace 467 | if info['server_type'] not in ["EC2","VM"]: 468 | # update rack info 469 | update_rack(info,object_id) 470 | 471 | # close db 472 | db.close() 473 | 474 | # end 475 | return True 476 | 477 | def update_blank_switch_security_pdu_offline(info): 478 | """Automate server autodir for PatchPanel/NetworkSwitch/NetworkSecurity/PDU into Racktables or as offline mode""" 479 | 480 | # connect to racktables db 481 | db = Connection(rt_server,rt_dbname,rt_dbuser,rt_dbpass) 482 | 483 | # delete object if already exists 484 | delete_object(info) 485 | 486 | # create object 487 | url = """http://{0}/racktables/index.php?module=redirect&page=depot&tab=addmore&op=addObjects""".format(rt_server) 488 | if opts['blank']: 489 | payload = """0_object_type_id=9&0_object_name={0}&0_object_label=&0_object_asset_no={0}&got_fast_data=Go%21"""\ 490 | .format(info['hostname']) 491 | if opts['switch']: 492 | payload = """0_object_type_id=8&0_object_name={0}&0_object_label=&0_object_asset_no={0}&got_fast_data=Go%21"""\ 493 | .format(info['hostname']) 494 | if opts['security']: 495 | payload = """0_object_type_id=798&0_object_name={0}&0_object_label=&0_object_asset_no={0}&got_fast_data=Go%21"""\ 496 | .format(info['hostname']) 497 | if opts['pdu']: 498 | payload = """0_object_type_id=2&0_object_name={0}&0_object_label=&0_object_asset_no={0}&got_fast_data=Go%21"""\ 499 | .format(info['hostname']) 500 | if opts['offline']: 501 | payload = """0_object_type_id=4&0_object_name={0}&0_object_label=&0_object_asset_no={0}&got_fast_data=Go%21"""\ 502 | .format(info['hostname']) 503 | 504 | req = requests.post(url, data=payload, headers=rt_headers, auth=rt_auth) 505 | if req.status_code != requests.codes.ok: 506 | print "Failed to create object: {0}".format(info['hostname']) 507 | return False 508 | else: 509 | print "OK - Created object: {0}".format(info['hostname']) 510 | 511 | # get object_id 512 | for item in db.query("select * from Object where name='{0}'".format(info['hostname'])): 513 | object_id = item.id 514 | if not object_id: 515 | print "Failed to get object_id" 516 | return False 517 | 518 | # update rack info 519 | update_rack(info,object_id) 520 | 521 | # close db 522 | db.close() 523 | 524 | # end 525 | return True 526 | 527 | def update_rack(info,object_id): 528 | """Automate server audit for rack info into Racktables""" 529 | 530 | # connect to racktables db 531 | db = Connection(rt_server,rt_dbname,rt_dbuser,rt_dbpass) 532 | 533 | # update the rackspace 534 | if opts['rackspace']: 535 | rs_info = opts['rackspace'].split(':') 536 | colo = "".join(rs_info[0:1]) 537 | row = "".join(rs_info[1:2]) 538 | rack = "".join(rs_info[2:3]) 539 | atom = "".join(rs_info[3:4]) 540 | if not atom: 541 | print "The rackspace is not correct" 542 | return False 543 | 544 | # get rack_id 545 | for item in db.query("select * from Rack where name = '{0}' and location_name = '{1}' and row_name = '{2}'".format(rack,colo,row)): 546 | rack_id = item.id 547 | if not rack_id: 548 | print "Failed to get rack_id" 549 | return False 550 | 551 | atom_list = atom.split(',') 552 | atom_data = [] 553 | for i in atom_list: 554 | if opts['rackposition']: 555 | if opts['rackposition'] in ['left', 'front']: 556 | atom_data.append("&atom_{0}_{1}_0=on".format(rack_id,i)) 557 | if opts['rackposition'] in ['right', 'back']: 558 | atom_data.append("&atom_{0}_{1}_2=on".format(rack_id,i)) 559 | if opts['rackposition'] in ['interior']: 560 | atom_data.append("&atom_{0}_{1}_1=on".format(rack_id,i)) 561 | else: 562 | atom_data.append("&atom_{0}_{1}_0=on&atom_{0}_{1}_1=on&atom_{0}_{1}_2=on".format(rack_id,i)) 563 | atom_url = "".join(atom_data) 564 | 565 | url = """http://{0}/racktables/index.php?module=redirect&page=object&tab=rackspace&op=updateObjectAllocation""".format(rt_server) 566 | payload = """object_id={0}&rackmulti%5B%5D={1}&comment=&got_atoms=Save{2}"""\ 567 | .format(object_id,rack_id,atom_url) 568 | req = requests.post(url, data=payload, headers=rt_headers, auth=rt_auth) 569 | if req.status_code != requests.codes.ok: 570 | print "Failed to update rackspace" 571 | return False 572 | print "OK - Updated rackspace" 573 | 574 | # close db 575 | db.close() 576 | 577 | # end 578 | return True 579 | 580 | def delete_object(info): 581 | """Delete object from DB""" 582 | 583 | # connect to racktables db 584 | db = Connection(rt_server,rt_dbname,rt_dbuser,rt_dbpass) 585 | 586 | # check if object_id already exists, then create object if not 587 | object_id = "" 588 | for item in db.query("select * from Object where name='{0}'".format(info['hostname'])): 589 | object_id = item.id 590 | 591 | # delete object if already exists 592 | if object_id: 593 | url = """http://{0}/racktables/index.php?module=redirect&op=deleteObject&page=depot&tab=addmore&object_id={1}"""\ 594 | .format(rt_server,object_id) 595 | req = requests.get(url,auth=rt_auth) 596 | if req.status_code != requests.codes.ok: 597 | print "Failed to delete the existing object: {0}".format(info['hostname']) 598 | return False 599 | else: 600 | print "OK - Deleted the existing object: {0}".format(info['hostname']) 601 | 602 | # close db 603 | db.close() 604 | 605 | return True 606 | 607 | def list_object(info): 608 | """List the objects of the given rackspace""" 609 | 610 | # connect to racktables db 611 | db = Connection(rt_server,rt_dbname,rt_dbuser,rt_dbpass) 612 | 613 | # check if rackspace is correct 614 | rs_info = opts['hostname'].split(':') 615 | colo = "".join(rs_info[0:1]) 616 | row = "".join(rs_info[1:2]) 617 | rack = "".join(rs_info[2:3]) 618 | if not rack: 619 | print "The rackspace is not correct" 620 | return False 621 | 622 | # get rack_id 623 | for item in db.query("select * from Rack where name = '{0}' and location_name = '{1}' and row_name = '{2}'".format(rack,colo,row)): 624 | rack_id = item.id 625 | if not rack_id: 626 | print "Failed to get rack_id" 627 | return False 628 | 629 | # get object_id 630 | object_id_list = [] 631 | for item in db.query("select * from RackSpace where rack_id={0}".format(rack_id)): 632 | object_id_list.append(item.object_id) 633 | if len(object_id_list) == 0: 634 | print "Failed to get object_id" 635 | return False 636 | 637 | # get rid of the duplicated items then sort and read one by one 638 | for object_id in sorted(list(set(object_id_list))): 639 | for item in db.query("select * from Object where id={0}".format(object_id)): 640 | object_name = item.name 641 | object_type_id = item.objtype_id 642 | for item in db.query("select * from Dictionary where dict_key={0}".format(object_type_id)): 643 | object_type_name = item.dict_value 644 | print "{0}: {1}".format(object_type_name,object_name) 645 | 646 | # close db 647 | db.close() 648 | 649 | return True 650 | 651 | if __name__=='__main__': 652 | # show help messages if no parameter 653 | argv_len = len(sys.argv) 654 | if argv_len < 2: 655 | os.system(__file__ + " -h") 656 | sys.exit(1) 657 | opts = parse_opts() 658 | 659 | if not opts['list'] and not opts['delete'] and not opts['blank'] and not opts['switch'] and not opts['security'] \ 660 | and not opts['pdu'] and not opts['offline']: 661 | # check if host is up 662 | hostup = isup(opts['hostname']) 663 | 664 | if hostup: 665 | # get info 666 | print "========================================" 667 | print "Getting informations from '{0}'...".format(opts['hostname']) 668 | print "========================================" 669 | info = sum_info() 670 | 671 | # update racktables 672 | if opts['write']: 673 | if info['server_type'] in ["Server","XenServer"]: 674 | print "========================================" 675 | print "Getting informations from DB..." 676 | print "========================================" 677 | read_db(info) 678 | print "========================================" 679 | print "Updating racktables..." 680 | print "========================================" 681 | update_db(info) 682 | else: 683 | info = {'hostname':opts['hostname']} 684 | else: 685 | info = {'hostname':opts['hostname']} 686 | if opts['blank'] or opts['switch'] or opts['security'] or opts['offline'] or opts['pdu']: 687 | if opts['write']: 688 | print "========================================" 689 | print "Updating racktables..." 690 | print "========================================" 691 | update_blank_switch_security_pdu_offline(info) 692 | if opts['list']: 693 | print "========================================" 694 | print "Getting objects in rackspace:'{0}'...".format(opts['hostname']) 695 | print "========================================" 696 | list_object(info) 697 | 698 | # read racktables 699 | if opts['read'] or opts['delete']: 700 | print "========================================" 701 | print "Getting informations from DB..." 702 | print "========================================" 703 | read_db(info) 704 | 705 | # delete object from db 706 | if opts['delete']: 707 | print "========================================" 708 | print "Deleting Object: '{0}' from DB...".format(opts['hostname']) 709 | print "========================================" 710 | delete_object(info) 711 | -------------------------------------------------------------------------------- /scripts/requirements.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function require_os(){ 4 | sudo yum groupinstall "Development tools" 5 | sudo yum install zlib-devel bzip2-devel openssl-devel ncurses-devel 6 | sudo yum install python-setuptools python-devel 7 | } 8 | 9 | function require_virtualenv(){ 10 | sudo yum install http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm 11 | sudo yum install python-pip 12 | sudo easy_install virtualenv 13 | } 14 | 15 | function require_flask(){ 16 | mkdir -p ~/devopsdemo 17 | cd ~/devopsdemo 18 | virtualenv flask 19 | . flask/bin/activate 20 | pip install -r flask/requirements.txt 21 | } 22 | 23 | function require_mysql(){ 24 | cd ~/devopsdemo 25 | . flask/bin/activate 26 | sudo yum install http://www.percona.com/redir/downloads/percona-release/redhat/latest/percona-release-0.1-3.noarch.rpm 27 | sudo yum install Percona-Server-devel Percona-Server-shared-compat Percona-Server-client-55 Percona-Server-shared-55 28 | sudo yum install Percona-Server-server-55 29 | easy_install -U distribute 30 | pip install mysql-python torndb 31 | } 32 | 33 | function require_misc(){ 34 | cd ~/devopsdemo 35 | . flask/bin/activate 36 | pip install argparse requests paramiko 37 | mkdir -p sshkeys tmp 38 | } 39 | 40 | case $1 in 41 | all) 42 | require_os 43 | require_virtualenv 44 | require_flask 45 | require_mysql 46 | require_misc 47 | ;; 48 | os) 49 | require_os 50 | ;; 51 | virtualenv) 52 | require_virtualenv 53 | ;; 54 | flask) 55 | require_flask 56 | ;; 57 | mysql) 58 | require_mysql 59 | ;; 60 | misc) 61 | require_misc 62 | ;; 63 | *) 64 | echo $"Usage: $0 {all|os|virtualenv|flask|mysql|misc}" 65 | exit 2 66 | esac 67 | --------------------------------------------------------------------------------