Commit 43460176 by Bence Dányi

Merge branch 'firewall-gui'

parents 76c164a5 6cb66a3f
...@@ -38,6 +38,9 @@ nosetests.xml ...@@ -38,6 +38,9 @@ nosetests.xml
*.css *.css
*.min.js *.min.js
!bootstrap.min.js
!firewall_gui/static/css/*
# Other # Other
*.swp *.swp
*~ *~
......
...@@ -88,6 +88,7 @@ STATICFILES_DIRS = ( ...@@ -88,6 +88,7 @@ STATICFILES_DIRS = (
# Don't forget to use absolute paths, not relative paths. # Don't forget to use absolute paths, not relative paths.
'/opt/webadmin/cloud/one/static', '/opt/webadmin/cloud/one/static',
'/opt/webadmin/cloud/cloud/static', '/opt/webadmin/cloud/cloud/static',
'/opt/webadmin/cloud/firewall_gui/static',
) )
# List of finder classes that know how to find static files in # List of finder classes that know how to find static files in
...@@ -156,6 +157,7 @@ INSTALLED_APPS = ( ...@@ -156,6 +157,7 @@ INSTALLED_APPS = (
'cloud', 'cloud',
'store', 'store',
'firewall', 'firewall',
'firewall_gui',
'south', 'south',
'djcelery', 'djcelery',
'kombu.transport.django', 'kombu.transport.django',
......
...@@ -92,4 +92,40 @@ urlpatterns = patterns('', ...@@ -92,4 +92,40 @@ urlpatterns = patterns('',
url(r'^stat/$', 'one.views.stat'), url(r'^stat/$', 'one.views.stat'),
url(r'^sites/(?P<site>[a-zA-Z0-9]+)/$', 'one.views.sites'), url(r'^sites/(?P<site>[a-zA-Z0-9]+)/$', 'one.views.sites'),
url(r'^accounts/(?P<site>profile)/$', 'one.views.sites'), url(r'^accounts/(?P<site>profile)/$', 'one.views.sites'),
url(r'^firewall/$', 'firewall_gui.views.index'),
url(r'^firewall/rules/(?P<id>\d+)/$', 'firewall_gui.views.show_rule'),
url(r'^firewall/hosts/(?P<id>\d+)/$', 'firewall_gui.views.show_host'),
url(r'^firewall/vlans/(?P<id>\d+)/$', 'firewall_gui.views.show_vlan'),
url(r'^firewall/vlangroups/(?P<id>\d+)/$', 'firewall_gui.views.show_vlangroup'),
url(r'^firewall/hostgroups/(?P<id>\d+)/$', 'firewall_gui.views.show_hostgroup'),
url(r'^firewall/records/(?P<id>\d+)/$', 'firewall_gui.views.show_record'),
url(r'^firewall/domains/(?P<id>\d+)/$', 'firewall_gui.views.show_domain'),
url(r'^firewall/(?P<name>\w+)/$', 'firewall_gui.views.list_entities'),
url(r'^firewall/autocomplete/(?P<entity>\w+)/$', 'firewall_gui.views.autocomplete'),
url(r'^firewall/rules/save/$', 'firewall_gui.views.save_rule'),
url(r'^firewall/hosts/save/$', 'firewall_gui.views.save_host'),
url(r'^firewall/vlans/save/$', 'firewall_gui.views.save_vlan'),
url(r'^firewall/vlangroups/save/$', 'firewall_gui.views.save_vlangroup'),
url(r'^firewall/hostgroups/save/$', 'firewall_gui.views.save_hostgroup'),
url(r'^firewall/domains/save/$', 'firewall_gui.views.save_domain'),
url(r'^firewall/records/save/$', 'firewall_gui.views.save_record'),
url(r'^firewall/(?P<name>\w+)/(?P<id>\d+)/delete/', 'firewall_gui.views.delete_entity'),
url(r'^firewall/rules/new/$', 'firewall_gui.views.show_rule'),
url(r'^firewall/hosts/new/$', 'firewall_gui.views.show_host'),
url(r'^firewall/vlans/new/$', 'firewall_gui.views.show_vlan'),
url(r'^firewall/vlangroups/new/$', 'firewall_gui.views.show_vlangroup'),
url(r'^firewall/hostgroups/new/$', 'firewall_gui.views.show_hostgroup'),
url(r'^firewall/domains/new/$', 'firewall_gui.views.show_domain'),
url(r'^firewall/records/new/$', 'firewall_gui.views.show_record'),
# url(r'^firewall/vlangroups/save/$', 'firewall_gui.views.save_vlangroup'),
# url(r'^firewall/hostgroups/save/$', 'firewall_gui.views.save_hostgroup'),
# url(r'^firewall/domains/save/$', 'firewall_gui.views.save_domain'),
# url(r'^firewall/records/save/$', 'firewall_gui.views.save_record'),
) )
...@@ -14,6 +14,12 @@ import random ...@@ -14,6 +14,12 @@ import random
settings = django.conf.settings.FIREWALL_SETTINGS settings = django.conf.settings.FIREWALL_SETTINGS
class Rule(models.Model): class Rule(models.Model):
"""
Common firewall rule
Rule can be applied to: Host, Firewall, Vlan
"""
CHOICES_type = (('host', 'host'), ('firewall', 'firewall'), CHOICES_type = (('host', 'host'), ('firewall', 'firewall'),
('vlan', 'vlan')) ('vlan', 'vlan'))
CHOICES_proto = (('tcp', 'tcp'), ('udp', 'udp'), ('icmp', 'icmp')) CHOICES_proto = (('tcp', 'tcp'), ('udp', 'udp'), ('icmp', 'icmp'))
......
/*!
* Bootstrap Responsive v2.2.2
*
* Copyright 2012 Twitter, Inc
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Designed and built with all the love in the world @twitter by @mdo and @fat.
*/@-ms-viewport{width:device-width}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}}
This source diff could not be displayed because it is too large. You can view the blob instead.
/*! project specific CSS goes here. */
\ No newline at end of file
/*!
* Bootstrap.js by @fat & @mdo
* Copyright 2012 Twitter, Inc.
* http://www.apache.org/licenses/LICENSE-2.0.txt
*/
!function($){"use strict";$(function(){$.support.transition=function(){var transitionEnd=function(){var name,el=document.createElement("bootstrap"),transEndEventNames={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(name in transEndEventNames)if(void 0!==el.style[name])return transEndEventNames[name]}();return transitionEnd&&{end:transitionEnd}}()})}(window.jQuery),!function($){"use strict";var dismiss='[data-dismiss="alert"]',Alert=function(el){$(el).on("click",dismiss,this.close)};Alert.prototype.close=function(e){function removeElement(){$parent.trigger("closed").remove()}var $parent,$this=$(this),selector=$this.attr("data-target");selector||(selector=$this.attr("href"),selector=selector&&selector.replace(/.*(?=#[^\s]*$)/,"")),$parent=$(selector),e&&e.preventDefault(),$parent.length||($parent=$this.hasClass("alert")?$this:$this.parent()),$parent.trigger(e=$.Event("close")),e.isDefaultPrevented()||($parent.removeClass("in"),$.support.transition&&$parent.hasClass("fade")?$parent.on($.support.transition.end,removeElement):removeElement())};var old=$.fn.alert;$.fn.alert=function(option){return this.each(function(){var $this=$(this),data=$this.data("alert");data||$this.data("alert",data=new Alert(this)),"string"==typeof option&&data[option].call($this)})},$.fn.alert.Constructor=Alert,$.fn.alert.noConflict=function(){return $.fn.alert=old,this},$(document).on("click.alert.data-api",dismiss,Alert.prototype.close)}(window.jQuery),!function($){"use strict";var Button=function(element,options){this.$element=$(element),this.options=$.extend({},$.fn.button.defaults,options)};Button.prototype.setState=function(state){var d="disabled",$el=this.$element,data=$el.data(),val=$el.is("input")?"val":"html";state+="Text",data.resetText||$el.data("resetText",$el[val]()),$el[val](data[state]||this.options[state]),setTimeout(function(){"loadingText"==state?$el.addClass(d).attr(d,d):$el.removeClass(d).removeAttr(d)},0)},Button.prototype.toggle=function(){var $parent=this.$element.closest('[data-toggle="buttons-radio"]');$parent&&$parent.find(".active").removeClass("active"),this.$element.toggleClass("active")};var old=$.fn.button;$.fn.button=function(option){return this.each(function(){var $this=$(this),data=$this.data("button"),options="object"==typeof option&&option;data||$this.data("button",data=new Button(this,options)),"toggle"==option?data.toggle():option&&data.setState(option)})},$.fn.button.defaults={loadingText:"loading..."},$.fn.button.Constructor=Button,$.fn.button.noConflict=function(){return $.fn.button=old,this},$(document).on("click.button.data-api","[data-toggle^=button]",function(e){var $btn=$(e.target);$btn.hasClass("btn")||($btn=$btn.closest(".btn")),$btn.button("toggle")})}(window.jQuery),!function($){"use strict";var Carousel=function(element,options){this.$element=$(element),this.options=options,"hover"==this.options.pause&&this.$element.on("mouseenter",$.proxy(this.pause,this)).on("mouseleave",$.proxy(this.cycle,this))};Carousel.prototype={cycle:function(e){return e||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval($.proxy(this.next,this),this.options.interval)),this},to:function(pos){var $active=this.$element.find(".item.active"),children=$active.parent().children(),activePos=children.index($active),that=this;if(!(pos>children.length-1||0>pos))return this.sliding?this.$element.one("slid",function(){that.to(pos)}):activePos==pos?this.pause().cycle():this.slide(pos>activePos?"next":"prev",$(children[pos]))},pause:function(e){return e||(this.paused=!0),this.$element.find(".next, .prev").length&&$.support.transition.end&&(this.$element.trigger($.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){return this.sliding?void 0:this.slide("next")},prev:function(){return this.sliding?void 0:this.slide("prev")},slide:function(type,next){var e,$active=this.$element.find(".item.active"),$next=next||$active[type](),isCycling=this.interval,direction="next"==type?"left":"right",fallback="next"==type?"first":"last",that=this;if(this.sliding=!0,isCycling&&this.pause(),$next=$next.length?$next:this.$element.find(".item")[fallback](),e=$.Event("slide",{relatedTarget:$next[0]}),!$next.hasClass("active")){if($.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(e),e.isDefaultPrevented())return;$next.addClass(type),$next[0].offsetWidth,$active.addClass(direction),$next.addClass(direction),this.$element.one($.support.transition.end,function(){$next.removeClass([type,direction].join(" ")).addClass("active"),$active.removeClass(["active",direction].join(" ")),that.sliding=!1,setTimeout(function(){that.$element.trigger("slid")},0)})}else{if(this.$element.trigger(e),e.isDefaultPrevented())return;$active.removeClass("active"),$next.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return isCycling&&this.cycle(),this}}};var old=$.fn.carousel;$.fn.carousel=function(option){return this.each(function(){var $this=$(this),data=$this.data("carousel"),options=$.extend({},$.fn.carousel.defaults,"object"==typeof option&&option),action="string"==typeof option?option:options.slide;data||$this.data("carousel",data=new Carousel(this,options)),"number"==typeof option?data.to(option):action?data[action]():options.interval&&data.cycle()})},$.fn.carousel.defaults={interval:5e3,pause:"hover"},$.fn.carousel.Constructor=Carousel,$.fn.carousel.noConflict=function(){return $.fn.carousel=old,this},$(document).on("click.carousel.data-api","[data-slide]",function(e){var href,$this=$(this),$target=$($this.attr("data-target")||(href=$this.attr("href"))&&href.replace(/.*(?=#[^\s]+$)/,"")),options=$.extend({},$target.data(),$this.data());$target.carousel(options),e.preventDefault()})}(window.jQuery),!function($){"use strict";var Collapse=function(element,options){this.$element=$(element),this.options=$.extend({},$.fn.collapse.defaults,options),this.options.parent&&(this.$parent=$(this.options.parent)),this.options.toggle&&this.toggle()};Collapse.prototype={constructor:Collapse,dimension:function(){var hasWidth=this.$element.hasClass("width");return hasWidth?"width":"height"},show:function(){var dimension,scroll,actives,hasData;if(!this.transitioning){if(dimension=this.dimension(),scroll=$.camelCase(["scroll",dimension].join("-")),actives=this.$parent&&this.$parent.find("> .accordion-group > .in"),actives&&actives.length){if(hasData=actives.data("collapse"),hasData&&hasData.transitioning)return;actives.collapse("hide"),hasData||actives.data("collapse",null)}this.$element[dimension](0),this.transition("addClass",$.Event("show"),"shown"),$.support.transition&&this.$element[dimension](this.$element[0][scroll])}},hide:function(){var dimension;this.transitioning||(dimension=this.dimension(),this.reset(this.$element[dimension]()),this.transition("removeClass",$.Event("hide"),"hidden"),this.$element[dimension](0))},reset:function(size){var dimension=this.dimension();return this.$element.removeClass("collapse")[dimension](size||"auto")[0].offsetWidth,this.$element[null!==size?"addClass":"removeClass"]("collapse"),this},transition:function(method,startEvent,completeEvent){var that=this,complete=function(){"show"==startEvent.type&&that.reset(),that.transitioning=0,that.$element.trigger(completeEvent)};this.$element.trigger(startEvent),startEvent.isDefaultPrevented()||(this.transitioning=1,this.$element[method]("in"),$.support.transition&&this.$element.hasClass("collapse")?this.$element.one($.support.transition.end,complete):complete())},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var old=$.fn.collapse;$.fn.collapse=function(option){return this.each(function(){var $this=$(this),data=$this.data("collapse"),options="object"==typeof option&&option;data||$this.data("collapse",data=new Collapse(this,options)),"string"==typeof option&&data[option]()})},$.fn.collapse.defaults={toggle:!0},$.fn.collapse.Constructor=Collapse,$.fn.collapse.noConflict=function(){return $.fn.collapse=old,this},$(document).on("click.collapse.data-api","[data-toggle=collapse]",function(e){var href,$this=$(this),target=$this.attr("data-target")||e.preventDefault()||(href=$this.attr("href"))&&href.replace(/.*(?=#[^\s]+$)/,""),option=$(target).data("collapse")?"toggle":$this.data();$this[$(target).hasClass("in")?"addClass":"removeClass"]("collapsed"),$(target).collapse(option)})}(window.jQuery),!function($){"use strict";function clearMenus(){$(toggle).each(function(){getParent($(this)).removeClass("open")})}function getParent($this){var $parent,selector=$this.attr("data-target");return selector||(selector=$this.attr("href"),selector=selector&&/#/.test(selector)&&selector.replace(/.*(?=#[^\s]*$)/,"")),$parent=$(selector),$parent.length||($parent=$this.parent()),$parent}var toggle="[data-toggle=dropdown]",Dropdown=function(element){var $el=$(element).on("click.dropdown.data-api",this.toggle);$("html").on("click.dropdown.data-api",function(){$el.parent().removeClass("open")})};Dropdown.prototype={constructor:Dropdown,toggle:function(){var $parent,isActive,$this=$(this);if(!$this.is(".disabled, :disabled"))return $parent=getParent($this),isActive=$parent.hasClass("open"),clearMenus(),isActive||$parent.toggleClass("open"),$this.focus(),!1},keydown:function(e){var $this,$items,$parent,isActive,index;if(/(38|40|27)/.test(e.keyCode)&&($this=$(this),e.preventDefault(),e.stopPropagation(),!$this.is(".disabled, :disabled"))){if($parent=getParent($this),isActive=$parent.hasClass("open"),!isActive||isActive&&27==e.keyCode)return $this.click();$items=$("[role=menu] li:not(.divider):visible a",$parent),$items.length&&(index=$items.index($items.filter(":focus")),38==e.keyCode&&index>0&&index--,40==e.keyCode&&$items.length-1>index&&index++,~index||(index=0),$items.eq(index).focus())}}};var old=$.fn.dropdown;$.fn.dropdown=function(option){return this.each(function(){var $this=$(this),data=$this.data("dropdown");data||$this.data("dropdown",data=new Dropdown(this)),"string"==typeof option&&data[option].call($this)})},$.fn.dropdown.Constructor=Dropdown,$.fn.dropdown.noConflict=function(){return $.fn.dropdown=old,this},$(document).on("click.dropdown.data-api touchstart.dropdown.data-api",clearMenus).on("click.dropdown touchstart.dropdown.data-api",".dropdown form",function(e){e.stopPropagation()}).on("touchstart.dropdown.data-api",".dropdown-menu",function(e){e.stopPropagation()}).on("click.dropdown.data-api touchstart.dropdown.data-api",toggle,Dropdown.prototype.toggle).on("keydown.dropdown.data-api touchstart.dropdown.data-api",toggle+", [role=menu]",Dropdown.prototype.keydown)}(window.jQuery),!function($){"use strict";var Modal=function(element,options){this.options=options,this.$element=$(element).delegate('[data-dismiss="modal"]',"click.dismiss.modal",$.proxy(this.hide,this)),this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};Modal.prototype={constructor:Modal,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var that=this,e=$.Event("show");this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.escape(),this.backdrop(function(){var transition=$.support.transition&&that.$element.hasClass("fade");that.$element.parent().length||that.$element.appendTo(document.body),that.$element.show(),transition&&that.$element[0].offsetWidth,that.$element.addClass("in").attr("aria-hidden",!1),that.enforceFocus(),transition?that.$element.one($.support.transition.end,function(){that.$element.focus().trigger("shown")}):that.$element.focus().trigger("shown")}))},hide:function(e){e&&e.preventDefault(),e=$.Event("hide"),this.$element.trigger(e),this.isShown&&!e.isDefaultPrevented()&&(this.isShown=!1,this.escape(),$(document).off("focusin.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),$.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal())},enforceFocus:function(){var that=this;$(document).on("focusin.modal",function(e){that.$element[0]===e.target||that.$element.has(e.target).length||that.$element.focus()})},escape:function(){var that=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.modal",function(e){27==e.which&&that.hide()}):this.isShown||this.$element.off("keyup.dismiss.modal")},hideWithTransition:function(){var that=this,timeout=setTimeout(function(){that.$element.off($.support.transition.end),that.hideModal()},500);this.$element.one($.support.transition.end,function(){clearTimeout(timeout),that.hideModal()})},hideModal:function(){this.$element.hide().trigger("hidden"),this.backdrop()},removeBackdrop:function(){this.$backdrop.remove(),this.$backdrop=null},backdrop:function(callback){var animate=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var doAnimate=$.support.transition&&animate;this.$backdrop=$('<div class="modal-backdrop '+animate+'" />').appendTo(document.body),this.$backdrop.click("static"==this.options.backdrop?$.proxy(this.$element[0].focus,this.$element[0]):$.proxy(this.hide,this)),doAnimate&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),doAnimate?this.$backdrop.one($.support.transition.end,callback):callback()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),$.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one($.support.transition.end,$.proxy(this.removeBackdrop,this)):this.removeBackdrop()):callback&&callback()}};var old=$.fn.modal;$.fn.modal=function(option){return this.each(function(){var $this=$(this),data=$this.data("modal"),options=$.extend({},$.fn.modal.defaults,$this.data(),"object"==typeof option&&option);data||$this.data("modal",data=new Modal(this,options)),"string"==typeof option?data[option]():options.show&&data.show()})},$.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},$.fn.modal.Constructor=Modal,$.fn.modal.noConflict=function(){return $.fn.modal=old,this},$(document).on("click.modal.data-api",'[data-toggle="modal"]',function(e){var $this=$(this),href=$this.attr("href"),$target=$($this.attr("data-target")||href&&href.replace(/.*(?=#[^\s]+$)/,"")),option=$target.data("modal")?"toggle":$.extend({remote:!/#/.test(href)&&href},$target.data(),$this.data());e.preventDefault(),$target.modal(option).one("hide",function(){$this.focus()})})}(window.jQuery),!function($){"use strict";var Tooltip=function(element,options){this.init("tooltip",element,options)};Tooltip.prototype={constructor:Tooltip,init:function(type,element,options){var eventIn,eventOut;this.type=type,this.$element=$(element),this.options=this.getOptions(options),this.enabled=!0,"click"==this.options.trigger?this.$element.on("click."+this.type,this.options.selector,$.proxy(this.toggle,this)):"manual"!=this.options.trigger&&(eventIn="hover"==this.options.trigger?"mouseenter":"focus",eventOut="hover"==this.options.trigger?"mouseleave":"blur",this.$element.on(eventIn+"."+this.type,this.options.selector,$.proxy(this.enter,this)),this.$element.on(eventOut+"."+this.type,this.options.selector,$.proxy(this.leave,this))),this.options.selector?this._options=$.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(options){return options=$.extend({},$.fn[this.type].defaults,options,this.$element.data()),options.delay&&"number"==typeof options.delay&&(options.delay={show:options.delay,hide:options.delay}),options},enter:function(e){var self=$(e.currentTarget)[this.type](this._options).data(this.type);return self.options.delay&&self.options.delay.show?(clearTimeout(this.timeout),self.hoverState="in",this.timeout=setTimeout(function(){"in"==self.hoverState&&self.show()},self.options.delay.show),void 0):self.show()},leave:function(e){var self=$(e.currentTarget)[this.type](this._options).data(this.type);return this.timeout&&clearTimeout(this.timeout),self.options.delay&&self.options.delay.hide?(self.hoverState="out",this.timeout=setTimeout(function(){"out"==self.hoverState&&self.hide()},self.options.delay.hide),void 0):self.hide()},show:function(){var $tip,inside,pos,actualWidth,actualHeight,placement,tp;if(this.hasContent()&&this.enabled){switch($tip=this.tip(),this.setContent(),this.options.animation&&$tip.addClass("fade"),placement="function"==typeof this.options.placement?this.options.placement.call(this,$tip[0],this.$element[0]):this.options.placement,inside=/in/.test(placement),$tip.detach().css({top:0,left:0,display:"block"}).insertAfter(this.$element),pos=this.getPosition(inside),actualWidth=$tip[0].offsetWidth,actualHeight=$tip[0].offsetHeight,inside?placement.split(" ")[1]:placement){case"bottom":tp={top:pos.top+pos.height,left:pos.left+pos.width/2-actualWidth/2};break;case"top":tp={top:pos.top-actualHeight,left:pos.left+pos.width/2-actualWidth/2};break;case"left":tp={top:pos.top+pos.height/2-actualHeight/2,left:pos.left-actualWidth};break;case"right":tp={top:pos.top+pos.height/2-actualHeight/2,left:pos.left+pos.width}}$tip.offset(tp).addClass(placement).addClass("in")}},setContent:function(){var $tip=this.tip(),title=this.getTitle();$tip.find(".tooltip-inner")[this.options.html?"html":"text"](title),$tip.removeClass("fade in top bottom left right")},hide:function(){function removeWithAnimation(){var timeout=setTimeout(function(){$tip.off($.support.transition.end).detach()},500);$tip.one($.support.transition.end,function(){clearTimeout(timeout),$tip.detach()})}var $tip=this.tip();return $tip.removeClass("in"),$.support.transition&&this.$tip.hasClass("fade")?removeWithAnimation():$tip.detach(),this},fixTitle:function(){var $e=this.$element;($e.attr("title")||"string"!=typeof $e.attr("data-original-title"))&&$e.attr("data-original-title",$e.attr("title")||"").removeAttr("title")},hasContent:function(){return this.getTitle()},getPosition:function(inside){return $.extend({},inside?{top:0,left:0}:this.$element.offset(),{width:this.$element[0].offsetWidth,height:this.$element[0].offsetHeight})},getTitle:function(){var title,$e=this.$element,o=this.options;return title=$e.attr("data-original-title")||("function"==typeof o.title?o.title.call($e[0]):o.title)},tip:function(){return this.$tip=this.$tip||$(this.options.template)},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(e){var self=$(e.currentTarget)[this.type](this._options).data(this.type);self[self.tip().hasClass("in")?"hide":"show"]()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}};var old=$.fn.tooltip;$.fn.tooltip=function(option){return this.each(function(){var $this=$(this),data=$this.data("tooltip"),options="object"==typeof option&&option;data||$this.data("tooltip",data=new Tooltip(this,options)),"string"==typeof option&&data[option]()})},$.fn.tooltip.Constructor=Tooltip,$.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover",title:"",delay:0,html:!1},$.fn.tooltip.noConflict=function(){return $.fn.tooltip=old,this}}(window.jQuery),!function($){"use strict";var Popover=function(element,options){this.init("popover",element,options)};Popover.prototype=$.extend({},$.fn.tooltip.Constructor.prototype,{constructor:Popover,setContent:function(){var $tip=this.tip(),title=this.getTitle(),content=this.getContent();$tip.find(".popover-title")[this.options.html?"html":"text"](title),$tip.find(".popover-content")[this.options.html?"html":"text"](content),$tip.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var content,$e=this.$element,o=this.options;return content=$e.attr("data-content")||("function"==typeof o.content?o.content.call($e[0]):o.content)},tip:function(){return this.$tip||(this.$tip=$(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}});var old=$.fn.popover;$.fn.popover=function(option){return this.each(function(){var $this=$(this),data=$this.data("popover"),options="object"==typeof option&&option;data||$this.data("popover",data=new Popover(this,options)),"string"==typeof option&&data[option]()})},$.fn.popover.Constructor=Popover,$.fn.popover.defaults=$.extend({},$.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"></div></div></div>'}),$.fn.popover.noConflict=function(){return $.fn.popover=old,this}}(window.jQuery),!function($){"use strict";function ScrollSpy(element,options){var href,process=$.proxy(this.process,this),$element=$(element).is("body")?$(window):$(element);this.options=$.extend({},$.fn.scrollspy.defaults,options),this.$scrollElement=$element.on("scroll.scroll-spy.data-api",process),this.selector=(this.options.target||(href=$(element).attr("href"))&&href.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=$("body"),this.refresh(),this.process()}ScrollSpy.prototype={constructor:ScrollSpy,refresh:function(){var $targets,self=this;this.offsets=$([]),this.targets=$([]),$targets=this.$body.find(this.selector).map(function(){var $el=$(this),href=$el.data("target")||$el.attr("href"),$href=/^#\w/.test(href)&&$(href);return $href&&$href.length&&[[$href.position().top+self.$scrollElement.scrollTop(),href]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){self.offsets.push(this[0]),self.targets.push(this[1])})},process:function(){var i,scrollTop=this.$scrollElement.scrollTop()+this.options.offset,scrollHeight=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,maxScroll=scrollHeight-this.$scrollElement.height(),offsets=this.offsets,targets=this.targets,activeTarget=this.activeTarget;if(scrollTop>=maxScroll)return activeTarget!=(i=targets.last()[0])&&this.activate(i);for(i=offsets.length;i--;)activeTarget!=targets[i]&&scrollTop>=offsets[i]&&(!offsets[i+1]||offsets[i+1]>=scrollTop)&&this.activate(targets[i])},activate:function(target){var active,selector;this.activeTarget=target,$(this.selector).parent(".active").removeClass("active"),selector=this.selector+'[data-target="'+target+'"],'+this.selector+'[href="'+target+'"]',active=$(selector).parent("li").addClass("active"),active.parent(".dropdown-menu").length&&(active=active.closest("li.dropdown").addClass("active")),active.trigger("activate")}};var old=$.fn.scrollspy;$.fn.scrollspy=function(option){return this.each(function(){var $this=$(this),data=$this.data("scrollspy"),options="object"==typeof option&&option;data||$this.data("scrollspy",data=new ScrollSpy(this,options)),"string"==typeof option&&data[option]()})},$.fn.scrollspy.Constructor=ScrollSpy,$.fn.scrollspy.defaults={offset:10},$.fn.scrollspy.noConflict=function(){return $.fn.scrollspy=old,this},$(window).on("load",function(){$('[data-spy="scroll"]').each(function(){var $spy=$(this);$spy.scrollspy($spy.data())})})}(window.jQuery),!function($){"use strict";var Tab=function(element){this.element=$(element)};Tab.prototype={constructor:Tab,show:function(){var previous,$target,e,$this=this.element,$ul=$this.closest("ul:not(.dropdown-menu)"),selector=$this.attr("data-target");selector||(selector=$this.attr("href"),selector=selector&&selector.replace(/.*(?=#[^\s]*$)/,"")),$this.parent("li").hasClass("active")||(previous=$ul.find(".active:last a")[0],e=$.Event("show",{relatedTarget:previous}),$this.trigger(e),e.isDefaultPrevented()||($target=$(selector),this.activate($this.parent("li"),$ul),this.activate($target,$target.parent(),function(){$this.trigger({type:"shown",relatedTarget:previous})})))},activate:function(element,container,callback){function next(){$active.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),element.addClass("active"),transition?(element[0].offsetWidth,element.addClass("in")):element.removeClass("fade"),element.parent(".dropdown-menu")&&element.closest("li.dropdown").addClass("active"),callback&&callback()}var $active=container.find("> .active"),transition=callback&&$.support.transition&&$active.hasClass("fade");transition?$active.one($.support.transition.end,next):next(),$active.removeClass("in")}};var old=$.fn.tab;$.fn.tab=function(option){return this.each(function(){var $this=$(this),data=$this.data("tab");data||$this.data("tab",data=new Tab(this)),"string"==typeof option&&data[option]()})},$.fn.tab.Constructor=Tab,$.fn.tab.noConflict=function(){return $.fn.tab=old,this},$(document).on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(e){e.preventDefault(),$(this).tab("show")})}(window.jQuery),!function($){"use strict";var Typeahead=function(element,options){this.$element=$(element),this.options=$.extend({},$.fn.typeahead.defaults,options),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.source=this.options.source,this.$menu=$(this.options.menu),this.shown=!1,this.listen()};Typeahead.prototype={constructor:Typeahead,select:function(){var val=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(val)).change(),this.hide()},updater:function(item){return item},show:function(){var pos=$.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});return this.$menu.insertAfter(this.$element).css({top:pos.top+pos.height,left:pos.left}).show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(){var items;return this.query=this.$element.val(),!this.query||this.query.length<this.options.minLength?this.shown?this.hide():this:(items=$.isFunction(this.source)?this.source(this.query,$.proxy(this.process,this)):this.source,items?this.process(items):this)},process:function(items){var that=this;return items=$.grep(items,function(item){return that.matcher(item)}),items=this.sorter(items),items.length?this.render(items.slice(0,this.options.items)).show():this.shown?this.hide():this},matcher:function(item){return~item.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(items){for(var item,beginswith=[],caseSensitive=[],caseInsensitive=[];item=items.shift();)item.toLowerCase().indexOf(this.query.toLowerCase())?~item.indexOf(this.query)?caseSensitive.push(item):caseInsensitive.push(item):beginswith.push(item);return beginswith.concat(caseSensitive,caseInsensitive)},highlighter:function(item){var query=this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&");return item.replace(RegExp("("+query+")","ig"),function($1,match){return"<strong>"+match+"</strong>"})},render:function(items){var that=this;return items=$(items).map(function(i,item){return i=$(that.options.item).attr("data-value",item),i.find("a").html(that.highlighter(item)),i[0]}),items.first().addClass("active"),this.$menu.html(items),this},next:function(){var active=this.$menu.find(".active").removeClass("active"),next=active.next();next.length||(next=$(this.$menu.find("li")[0])),next.addClass("active")},prev:function(){var active=this.$menu.find(".active").removeClass("active"),prev=active.prev();prev.length||(prev=this.$menu.find("li").last()),prev.addClass("active")},listen:function(){this.$element.on("blur",$.proxy(this.blur,this)).on("keypress",$.proxy(this.keypress,this)).on("keyup",$.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown",$.proxy(this.keydown,this)),this.$menu.on("click",$.proxy(this.click,this)).on("mouseenter","li",$.proxy(this.mouseenter,this))},eventSupported:function(eventName){var isSupported=eventName in this.$element;return isSupported||(this.$element.setAttribute(eventName,"return;"),isSupported="function"==typeof this.$element[eventName]),isSupported},move:function(e){if(this.shown){switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()}},keydown:function(e){this.suppressKeyPressRepeat=~$.inArray(e.keyCode,[40,38,9,13,27]),this.move(e)},keypress:function(e){this.suppressKeyPressRepeat||this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},blur:function(){var that=this;setTimeout(function(){that.hide()},150)},click:function(e){e.stopPropagation(),e.preventDefault(),this.select()},mouseenter:function(e){this.$menu.find(".active").removeClass("active"),$(e.currentTarget).addClass("active")}};var old=$.fn.typeahead;$.fn.typeahead=function(option){return this.each(function(){var $this=$(this),data=$this.data("typeahead"),options="object"==typeof option&&option;data||$this.data("typeahead",data=new Typeahead(this,options)),"string"==typeof option&&data[option]()})},$.fn.typeahead.defaults={source:[],items:8,menu:'<ul class="typeahead dropdown-menu"></ul>',item:'<li><a href="#"></a></li>',minLength:1},$.fn.typeahead.Constructor=Typeahead,$.fn.typeahead.noConflict=function(){return $.fn.typeahead=old,this},$(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(e){var $this=$(this);$this.data("typeahead")||(e.preventDefault(),$this.typeahead($this.data()))})}(window.jQuery),!function($){"use strict";var Affix=function(element,options){this.options=$.extend({},$.fn.affix.defaults,options),this.$window=$(window).on("scroll.affix.data-api",$.proxy(this.checkPosition,this)).on("click.affix.data-api",$.proxy(function(){setTimeout($.proxy(this.checkPosition,this),1)},this)),this.$element=$(element),this.checkPosition()};Affix.prototype.checkPosition=function(){if(this.$element.is(":visible")){var affix,scrollHeight=$(document).height(),scrollTop=this.$window.scrollTop(),position=this.$element.offset(),offset=this.options.offset,offsetBottom=offset.bottom,offsetTop=offset.top,reset="affix affix-top affix-bottom";"object"!=typeof offset&&(offsetBottom=offsetTop=offset),"function"==typeof offsetTop&&(offsetTop=offset.top()),"function"==typeof offsetBottom&&(offsetBottom=offset.bottom()),affix=null!=this.unpin&&scrollTop+this.unpin<=position.top?!1:null!=offsetBottom&&position.top+this.$element.height()>=scrollHeight-offsetBottom?"bottom":null!=offsetTop&&offsetTop>=scrollTop?"top":!1,this.affixed!==affix&&(this.affixed=affix,this.unpin="bottom"==affix?position.top-scrollTop:null,this.$element.removeClass(reset).addClass("affix"+(affix?"-"+affix:"")))}};var old=$.fn.affix;$.fn.affix=function(option){return this.each(function(){var $this=$(this),data=$this.data("affix"),options="object"==typeof option&&option;data||$this.data("affix",data=new Affix(this,options)),"string"==typeof option&&data[option]()})},$.fn.affix.Constructor=Affix,$.fn.affix.defaults={offset:0},$.fn.affix.noConflict=function(){return $.fn.affix=old,this},$(window).on("load",function(){$('[data-spy="affix"]').each(function(){var $spy=$(this),data=$spy.data();data.offset=data.offset||{},data.offsetBottom&&(data.offset.bottom=data.offsetBottom),data.offsetTop&&(data.offset.top=data.offsetTop),$spy.affix(data)})})}(window.jQuery);
\ No newline at end of file
/**
* Getter for user cookies
* @param {String} name Cookie name
* @return {String} Cookie value
*/
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
/**
* Extract CSRF token for AJAX calls
*/
var csrftoken = getCookie('csrftoken');
function csrfSafeMethod(method) {
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
crossDomain: false,
beforeSend: function(xhr, settings) {
//attach CSRF token to every AJAX call
if (!csrfSafeMethod(settings.type)) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
function makeAddRemove($scope, name, model) {
$scope['add' + name] = function(entity) {
for (var i in $scope.entity[model]) {
var item = $scope.entity[model][i];
if (item.name == entity && item.__destroyed) {
item.__destroyed = false;
return;
} else if (item.name == entity) {
return;
}
}
$scope.entity[model].push({
name: entity,
__created: true,
});
}
$scope['remove' + name] = function(entity) {
for (var i in $scope.entity[model]) {
var item = $scope.entity[model][i];
if (item.name == entity.name && item.__created) {
$scope.entity[model].splice(i, 1);
} else if (item.name == entity.name) {
item.__destroyed = true;
return;
}
}
}
}
/**
* List of firewall collections, controllers/routes will be dynamically created from them.
*
* E.g., from the `rule` controller, the RESTful url `/rules/` will be generated,
* and the `/static/partials/rule-list.html` template will be used.
* @type {Array}
*/
var controllers = {
rule: function($scope) {
$('#targetName').typeahead({
source: function(query, process) {
$.ajax({
url: '/firewall/autocomplete/' + $scope.entity.target.type + '/',
type: 'post',
data: 'name=' + query,
success: function autocompleteSuccess(data) {
process(data.map(function(obj) {
return obj.name;
}));
}
});
},
matcher: function() {
return true;
},
updater: function(item) {
var self = this;
$scope.$apply(function() {
$scope.entity.target.name = item;
})
return item;
}
});
},
host: function($scope) {
makeAddRemove($scope, 'HostGroup', 'groups');
},
vlan: function($scope) {
makeAddRemove($scope, 'Vlan', 'vlans');
},
vlangroup: function($scope) {
makeAddRemove($scope, 'Vlan', 'vlans');
},
hostgroup: function() {},
firewall: function() {},
domain: function() {},
record: function() {},
blacklist: function() {},
}
/**
* Configures AngularJS with the defined controllers
*/
var module = angular.module('firewall', []).config(
['$routeProvider',
function($routeProvider) {
for (var controller in controllers) {
var init = controllers[controller];
$routeProvider.when('/' + controller + 's/', {
templateUrl: '/static/partials/' + controller + '-list.html',
controller: ListController('/firewall/' + controller + 's/')
}).when('/' + controller + 's/:id/', {
templateUrl: '/static/partials/' + controller + '-edit.html',
controller: EntityController('/firewall/' + controller + 's/', init)
});
}
$routeProvider.otherwise({
redirectTo: '/rules/'
});
}
]);
/**
* Generate range [a, b)
* @param {Number} a Lower limit
* @param {Number} b Upper limit
* @return {Array} Number from a to b
*/
function range(a, b) {
var res = [];
do res.push(a++);
while (a < b)
return res;
}
/**
* Smart (recursive) match function for any object
* @param {Object} obj Object to be checked
* @param {String} query Regexp to be checked against
* @return {Boolean} True, if object matches (somehow) with query
*/
function matchAnything(obj, query) {
var expr = new RegExp(query, 'i')
for (var i in obj) {
var prop = obj[i];
if (typeof prop === 'number' && prop == query) return true;
if (typeof prop === 'string' && prop.match(expr)) return true;
if (typeof prop === 'object' && matchAnything(prop, query)) return true;
}
return false;
}
/**
* Factory for the given collection URL
* @param {String} url REST endpoint for collection
* @return {Function} ListController for the given REST endpoint
*/
function ListController(url) {
/**
* ListController for the given REST endpoint
* @param {Object} $scope Current controllers scope
* @param {Object} $http Helper for AJAX calls
*/
return function($scope, $http) {
$scope.page = 1;
var rules = [];
var pageSize = 10;
var itemCount = 0;
/**
* Does filtering&paging
* @return {Array} Items to be displayed
*/
$scope.getPage = function() {
var res = [];
if ($scope.query) {
for (var i in rules) {
var rule = rules[i];
if (matchAnything(rule, $scope.query)) {
res.push(rule);
}
}
} else {
res = rules;
}
$scope.pages = range(1, Math.ceil(res.length / pageSize));
$scope.page = Math.min($scope.page, $scope.pages.length);
return res.slice(($scope.page - 1) * pageSize, $scope.page * pageSize);
};
/**
* Setter for current page
* @param {Number} page Page to navigate to
*/
$scope.setPage = function(page) {
$scope.page = page;
};
/**
* Jumps to the next page (if available)
*/
$scope.nextPage = function() {
$scope.page = Math.min($scope.page + 1, $scope.pages.length);
};
/**
* Jumps to the previous page (if available)
*/
$scope.prevPage = function() {
$scope.page = Math.max($scope.page - 1, 1);
};
$scope.deleteEntity = function(id) {
$.ajax({
url: url.split('/')[2] + '/' + id + '/delete/',
type: 'post',
success: reloadList
});
};
function reloadList() {
$http.get(url).success(function success(data) {
rules = data;
$scope.pages = range(1, Math.ceil(data.length / pageSize));
});
}
reloadList();
}
}
/**
* Factory for the given URL
* @param {Object} url REST endpoint of the model
* @param {Object} init Init function for model-specic behaviour
*/
function EntityController(url, init) {
/**
* Entity Controller for the given model URL
* @param {Object} $scope Current controllers scope
* @param {Object} $http Helper for AJAX calls
* @param {Object} $routeParams Helper for route parameter parsing
*/
return function($scope, $http, $routeParams) {
init($scope);
var id = $routeParams.id;
$scope.errors = {};
/**
* Generic filter for collections
*
* Hides destroyed items
* @param {Object} item Current item in collection
* @return {Boolean} Item should be displayed, or not
*/
$scope.destroyed = function(item) {
return !item.__destroyed;
}
$scope.hasError = function(name) {
return $scope.errors[name] ? 'error' : null;
}
$scope.getError = function(name) {
return $scope.errors[name] ? $scope.errors[name] : '';
}
$scope.save = function() {
$scope.errors = {};
$.ajax({
url: url + 'save/',
type: 'post',
data: JSON.stringify($scope.entity),
success: function(data) {
console.log(data);
$scope.$apply(function() {
$scope.errors = {};
});
window.location.hash = '/' + url.split('/')[2] + '/' + data + '/';
}
}).error(function(data) {
try {
data = JSON.parse(data.responseText);
var newErrors = {};
for (var i in data) {
var id = $('#' + i).length ? i : 'targetName';
newErrors[id] = data[i];
}
$scope.$apply(function() {
$scope.errors = newErrors;
})
} catch (ex) {
}
})
}
function reloadEntity() {
$http.get(url + id + '/').success(function success(data) {
$scope.entity = data;
$('input[type=text], input[type=number], select, textarea, .has-tooltip').tooltip({
placement: 'right'
});
['vlan', 'vlangroup', 'host', 'hostgroup', 'firewall', 'owner', 'domain', 'record'].forEach(function(t) {
$('.' + t).typeahead({
/**
* Typeahead does AJAX queries
* @param {String} query Partial name of the entity
* @param {Function} process Callback function after AJAX returned result
*/
source: function(query, process) {
$.ajax({
url: '/firewall/autocomplete/' + t + '/',
type: 'post',
data: 'name=' + query,
success: function autocompleteSuccess(data) {
process(data.map(function(obj) {
return obj.name;
}));
}
});
},
/**
* Filtering is done on server-side, show all results
* @return {Boolean} Always true, so all result are visible
*/
matcher: function() {
return true;
},
/**
* Typeahead does not trigger proper DOM events, so we have to refresh
* the model manually.
* @param {String} item Selected entity name
* @return {String} Same as `item`, the input value is set to this
*/
updater: function(item) {
var self = this;
console.log(this);
$scope.$apply(function() {
var model = self.$element[0].getAttribute('ng-model').split('.')[1];
console.log(self.$element[0].getAttribute('ng-model'), model);
try {
$scope.entity[model].name = item;
} catch (ex) {
try {
$scope[self.$element[0].getAttribute('ng-model')] = item;
} catch (ex) {
}
}
})
return item;
}
});
})
});
}
reloadEntity();
}
}
<div class="navbar">
<div class="navbar-inner">
<div class="pagination pull-left">
<ul>
<li ng-click="prevPage()" ng-class="{disabled: page == 1}">
<a href>Előző</a>
</li>
<li ng-repeat="_page in pages" ng-click="setPage(_page)" ng-class="{active: _page == page}">
<a href>{{_page}}</a>
</li>
<li ng-click="nextPage()" ng-class="{disabled: page == pages.length}">
<a href>Next</a>
</li>
</ul>
</div>
<form class="navbar-search" style="margin: 20px">
<input type="text" class="search-query" placeholder="Search" ng-model="query">
</form>
<a style="margin-top: 19px" href="#/blacklists/new" class="btn">New blacklist</a>
</div>
</div>
<table class="table table-striped">
<tr>
<th>Név</th>
<th>IPv4</th>
<th>Host</th>
<th>Indok</th>
<th>Üzenet</th>
<th colspan="2">Típus</th>
</tr>
<tr ng-repeat="blacklist in getPage()">
<td>
<a href="#/blacklists/{{blacklist.id}}">{{blacklist.name}}</a>
</td>
<td>{{blacklist.ipv4}}</td>
<td>
<a href="#/hosts/{{blacklist.host.id}}">{{blacklist.host.hostname}}</a>
</td>
<td>{{blacklist.reason}}</td>
<td>{{blacklist.snort_message}}</td>
<td>{{blacklist.type}}</td>
<td>
<a class="btn" href="#/blacklists/{{blacklist.id}}/">Szerkesztés</a>
<a class="btn btn-danger" href="#/blacklists/{{blacklist.id}}/delete/">Törlés</a>
</td>
</tr>
</table>
<form class="form-horizontal">
<div class="span5">
<div class="control-group">
<label class="control-label" for="ID">ID</label>
<div class="controls">
<span class="has-tooltip" title="Built in ID, do not change this">
<input
class="input-mini"
type="text"
id="ID"
placeholder="ID"
value="{{entity.id}}"
disabled="disabled" />
</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="created_at">Created at</label>
<div class="controls">
<span class="has-tooltip" title="Entity was created at this date">
<input
class="input"
type="text"
id="created_at"
ng-model="entity.created_at"
disabled="disabled" />
</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="modified_at">Modified at</label>
<div class="controls">
<span class="has-tooltip" title="Entity was last modified at this date">
<input
class="input"
type="text"
id="modified_at"
ng-model="entity.modified_at"
disabled="disabled" />
</span>
</div>
</div>
<div class="control-group" ng-class="hasError('name')">
<label class="control-label" for="name">Name</label>
<div class="controls">
<input
type="text"
id="name"
ng-model="entity.name"
title="Domain name" />
<span class="help-inline" ng-bind="getError('name')"></span>
</div>
</div>
</div>
<div class="span5">
<div class="control-group" ng-class="hasError('owner')">
<label class="control-label" for="owner">Owner</label>
<div class="controls">
<input
type="text"
class="owner"
autocomplete="off"
id="owner"
ng-model="entity.owner.name"
title="Owners name" />
<span class="help-inline" ng-bind="getError('owner')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('ttl')">
<label class="control-label" for="ttl">TTL</label>
<div class="controls">
<input
type="text"
autocomplete="off"
id="ttl"
ng-model="entity.ttl"
title="Time To Live" />
<span class="help-inline" ng-bind="getError('ttl')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('description')">
<label class="control-label" for="description">Description</label>
<div class="controls">
<textarea rows="2"
id="description"
ng-model="entity.description"
title="Short description">
</textarea>
<span class="help-inline" ng-bind="getError('description')"></span>
</div>
</div>
<div class="control-group">
<div class="controls">
<button type="submit" class="btn" ng-click="save()">Save</button>
</div>
</div>
</div>
</form>
<div class="navbar">
<div class="navbar-inner">
<div class="pagination pull-left">
<ul>
<li ng-click="prevPage()" ng-class="{disabled: page == 1}">
<a href>Előző</a>
</li>
<li ng-repeat="_page in pages" ng-click="setPage(_page)" ng-class="{active: _page == page}">
<a href>{{_page}}</a>
</li>
<li ng-click="nextPage()" ng-class="{disabled: page == pages.length}">
<a href>Next</a>
</li>
</ul>
</div>
<form class="navbar-search" style="margin: 20px">
<input type="text" class="search-query" placeholder="Search" ng-model="query">
</form>
<a style="margin-top: 19px" href="#/domains/new" class="btn">New domain</a>
</div>
</div>
<table class="table table-striped">
<tr>
<th>Név</th>
<th>TTL</th>
<th>Leírás</th>
<th colspan="2">Tulajdonos</th>
</tr>
<tr ng-repeat="domain in getPage()">
<td>
<a href="#/domains/{{domain.id}}">{{domain.name}}</a>
</td>
<td>{{domain.ttl}}</td>
<td>{{domain.description}}</td>
<td>{{domain.owner.name}}</td>
<td>
<a class="btn" href="#/domains/{{domain.id}}/">Szerkesztés</a>
<a class="btn btn-danger" href="#/domains/{{domain.id}}/delete/">Törlés</a>
</td>
</tr>
</table>
<div class="navbar">
<div class="navbar-inner">
<div class="pagination pull-left">
<ul>
<li ng-click="prevPage()" ng-class="{disabled: page == 1}">
<a href>Előző</a>
</li>
<li ng-repeat="_page in pages" ng-click="setPage(_page)" ng-class="{active: _page == page}">
<a href>{{_page}}</a>
</li>
<li ng-click="nextPage()" ng-class="{disabled: page == pages.length}">
<a href>Next</a>
</li>
</ul>
</div>
<form class="navbar-search" style="margin: 20px">
<input type="text" class="search-query" placeholder="Search" ng-model="query">
</form>
<a style="margin-top: 19px" href="#/firewalls/new" class="btn">New firewall</a>
</div>
</div>
<table class="table table-striped">
<tr>
<th>Név</th>
<th colspan="2">Tulajdonos</th>
</tr>
<tr ng-repeat="firewall in getPage()">
<td>
<a href="#/firewalls/{{firewall.id}}">{{firewall.name}}</a>
</td>
<td>{{firewall.owner.name}}</td>
<td>
<a class="btn" href="#/firewalls/{{firewall.id}}/">Szerkesztés</a>
<a class="btn btn-danger" href="#/firewalls/{{firewall.id}}/delete/">Törlés</a>
</td>
</tr>
</table>
<form class="form-horizontal">
<div class="span5">
<div class="control-group">
<label class="control-label" for="ID">ID</label>
<div class="controls">
<span class="has-tooltip" title="Built in ID, do not change this">
<input
class="input-mini"
type="text"
id="ID"
placeholder="ID"
value="{{entity.id}}"
disabled="disabled" />
</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="created_at">Created at</label>
<div class="controls">
<span class="has-tooltip" title="Entity was created at this date">
<input
class="input"
type="text"
id="created_at"
ng-model="entity.created_at"
disabled="disabled">
</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="modified_at">Modified at</label>
<div class="controls">
<span class="has-tooltip" title="Entity was last modified at this date">
<input
class="input"
type="text"
id="modified_at"
ng-model="entity.modified_at"
disabled="disabled" />
</span>
</div>
</div>
<div class="control-group" ng-class="hasError('name')">
<label class="control-label" for="name">Name</label>
<div class="controls">
<input
type="text"
id="name"
ng-model="entity.name"
title="Name of the host" />
<span class="help-inline" ng-bind="getError('name')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('mac')">
<label class="control-label" for="mac">MAC</label>
<div class="controls">
<input
type="text"
id="mac"
ng-model="entity.mac"
title="MAC address" />
<span class="help-inline" ng-bind="getError('mac')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('ipv4')">
<label class="control-label" for="ipv4">IPv4</label>
<div class="controls">
<input
type="text"
id="ipv4"
ng-model="entity.ipv4"
title="IPv4 address" />
<span class="help-inline" ng-bind="getError('ipv4')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('pub_ipv4')">
<label class="control-label" for="pub_ipv4">Public IPv4</label>
<div class="controls">
<input
type="text"
id="pub_ipv4"
ng-model="entity.pub_ipv4"
title="Public IPv4 address" />
<span class="help-inline" ng-bind="getError('pub_ipv4')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('ipv6')">
<label class="control-label" for="ipv6">IPv6</label>
<div class="controls">
<input
type="text"
class="input-xlarge"
id="ipv6"
ng-model="entity.ipv6"
title="IPv6 address" />
<span class="help-inline" ng-bind="getError('ipv6')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('owner')">
<label class="control-label" for="owner">Owner</label>
<div class="controls">
<input
type="text"
class="owner"
autocomplete="off"
id="owner"
ng-model="entity.owner.name"
title="Owners name" />
<span class="help-inline" ng-bind="getError('owner')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('vlan')">
<label class="control-label" for="vlan">Vlan</label>
<div class="controls">
<input
type="text"
autocomplete="off"
class="vlan"
id="vlan"
ng-model="entity.vlan.name"
title="Vlan of this host #TODO" />
<span class="help-inline" ng-bind="getError('vlan')"></span>
</div>
</div>
</div>
<div class="span5">
<div class="control-group" ng-class="hasError('description')">
<label class="control-label" for="description">Description</label>
<div class="controls">
<textarea rows="4"
id="description"
ng-model="entity.description"
title="Short description">
</textarea>
<span class="help-inline" ng-bind="getError('description')"></span>
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" ng-model="entity.shared_ip"> Shared IP
</label>
</div>
</div>
<div class="control-group" ng-class="hasError('comment')">
<label class="control-label" for="comment">Comment</label>
<div class="controls">
<textarea rows="2"
id="comment"
ng-model="entity.comment"
title="Some comments... or something like that #TODO">
</textarea>
<span class="help-inline" ng-bind="getError('comment')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('groups')">
<label class="control-label" for="hostgroups">Host groups</label>
<div class="controls">
<div class="well well-small">
<span class="label label-info" ng-repeat="group in entity.groups | filter: destroyed">
<a href="#/hostgroups/{{group.id}}">{{group.name}}</a>
<a href ng-click="removeHostGroup(group)"><i class="icon-remove"></i></a>
</span>
</div>
<div class="input-append has-tooltip" title="Hostgroups of this host">
<input class="span2 hostgroup" id="groups" type="text" ng-model="newGroup">
<button class="btn" type="button" ng-click="addHostGroup(newGroup)">Add</button>
<span class="help-inline" ng-bind="getError('groups')"></span>
</div>
</div>
</div>
<div class="control-group">
<div class="controls">
<button type="submit" class="btn" ng-click="save()">Save</button>
</div>
</div>
</div>
</form>
<div class="span12">
<h3>Rules belonging to this host</h3>
<table class="table table-striped">
<thead>
<tr>
<th>Direction</th>
<th>Protocol</th>
<th>Accept</th>
<th>NAT</th>
<th colspan="2">Owner</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="rule in entity.rules">
<td>{{rule.direction}}</td>
<td>{{rule.proto}}</td>
<td>{{rule.accept}}</td>
<td>{{rule.nat}}</td>
<td>{{rule.owner.name}}</td>
<td>
<a class="btn" href="#/rules/{{rule.id}}/">Edit</a>
<a class="btn btn-danger" href="#/rules/{{rule.id}}/delete/">Delete</a>
</td>
</tr>
</tbody>
</table>
</div>
<div class="navbar">
<div class="navbar-inner">
<div class="pagination pull-left">
<ul>
<li ng-click="prevPage()" ng-class="{disabled: page == 1}">
<a href>Előző</a>
</li>
<li ng-repeat="_page in pages" ng-click="setPage(_page)" ng-class="{active: _page == page}">
<a href>{{_page}}</a>
</li>
<li ng-click="nextPage()" ng-class="{disabled: page == pages.length}">
<a href>Next</a>
</li>
</ul>
</div>
<form class="navbar-search" style="margin: 20px">
<input type="text" class="search-query" placeholder="Search" ng-model="query">
</form>
<a style="margin-top: 20px" href="#/hosts/new" class="btn">New host</a>
</div>
</div>
<table class="table table-striped">
<tr>
<th>Név</th>
<th>Vlan</th>
<th>IPv4</th>
<th>Csoportok</th>
<th>Tulajdonos</th>
<th colspan="2">Leírás</th>
</tr>
<tr ng-repeat="host in getPage()">
<td>{{host.name}}</td>
<td>
<a href="#/hosts/{{host.vlan.id}}">{{host.vlan.name}}</a>
</td>
<td>{{host.ipv4}}</td>
<td>
<div class="well well-small">
<span class="label label-info" ng-repeat="group in host.groups">
<a href="#/hostgroups/{{group.id}}">{{group.name}}</a>
</span>
</div>
</td>
<td>{{host.owner.name}}</td>
<td style="width: 200px;">{{host.description}}</td>
<td>
<a class="btn" href="#/hosts/{{host.id}}/">Szerkesztés</a>
<a class="btn btn-danger" href="#/hosts/{{host.id}}/delete/">Törlés</a>
</td>
</tr>
</table>
<form class="form-horizontal">
<div class="span5">
<div class="control-group">
<label class="control-label" for="ID">ID</label>
<div class="controls">
<span class="has-tooltip" title="Built in ID, do not change this">
<input
class="input-mini"
type="text"
id="ID"
placeholder="ID"
value="{{entity.id}}"
disabled="disabled" />
</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="created_at">Created at</label>
<div class="controls">
<span class="has-tooltip" title="Entity was created at this date">
<input
class="input"
type="text"
id="created_at"
ng-model="entity.created_at"
disabled="disabled" />
</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="modified_at">Modified at</label>
<div class="controls">
<span class="has-tooltip" title="Entity was last modified at this date">
<input
class="input"
type="text"
id="modified_at"
ng-model="entity.modified_at"
disabled="disabled" />
</span>
</div>
</div>
<div class="control-group" ng-class="hasError('name')">
<label class="control-label" for="name">Name</label>
<div class="controls">
<input
type="text"
id="name"
ng-model="entity.name"
title="Name for this Hostgroup" />
<span class="help-inline" ng-bind="getError('name')"></span>
</div>
</div>
</div>
<div class="span5">
<div class="control-group" ng-class="hasError('owner')">
<label class="control-label" for="owner">Owner</label>
<div class="controls">
<input
type="text"
class="owner"
autocomplete="off"
id="owner"
ng-model="entity.owner.name"
title="Owners name" />
<span class="help-inline" ng-bind="getError('owner')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('description')">
<label class="control-label" for="description">Description</label>
<div class="controls">
<textarea rows="4"
id="description"
ng-model="entity.description"
title="Short description">
</textarea>
<span class="help-inline" ng-bind="getError('description')"></span>
</div>
</div>
<div class="control-group">
<div class="controls">
<button type="submit" class="btn" ng-click="save()">Save</button>
</div>
</div>
</div>
</form>
<div class="span12">
<h3>Rules belonging to this hostgroup</h3>
<table class="table table-striped">
<thead>
<tr>
<th>Direction</th>
<th>Protocol</th>
<th>Accept</th>
<th>NAT</th>
<th colspan="2">Owner</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="rule in entity.rules">
<td>{{rule.direction}}</td>
<td>{{rule.proto}}</td>
<td>{{rule.accept}}</td>
<td>{{rule.nat}}</td>
<td>{{rule.owner.name}}</td>
<td>
<a class="btn" href="#/rules/{{rule.id}}/">Edit</a>
<a class="btn btn-danger" href="#/rules/{{rule.id}}/delete/">Delete</a>
</td>
</tr>
</tbody>
</table>
</div>
<div class="span12">
<h3>Hosts belonging to this hostgroup</h3>
<div class="well well-small">
<span class="label label-info" ng-repeat="host in entity.hosts">
<a href="#/hosts/{{host.id}}">{{host.name}}</a>
</span>
</div>
</div>
<div class="navbar">
<div class="navbar-inner">
<div class="pagination pull-left">
<ul>
<li ng-click="prevPage()" ng-class="{disabled: page == 1}">
<a href>Előző</a>
</li>
<li ng-repeat="_page in pages" ng-click="setPage(_page)" ng-class="{active: _page == page}">
<a href>{{_page}}</a>
</li>
<li ng-click="nextPage()" ng-class="{disabled: page == pages.length}">
<a href>Next</a>
</li>
</ul>
</div>
<form class="navbar-search" style="margin: 20px">
<input type="text" class="search-query" placeholder="Search" ng-model="query">
</form>
<a style="margin-top: 19px" href="#/hostgroups/new" class="btn">New hostgroup</a>
</div>
</div>
<table class="table table-striped">
<tr>
<th>Név</th>
<th>Tulajdonos</th>
<th colspan="2">Leírás</th>
</tr>
<tr ng-repeat="group in getPage()">
<td>
<a href="#/hostgroups/{{group.id}}">{{group.name}}</td>
<td>{{group.owner.name}}</td>
<td style="width: 200px;">{{group.description}}</td>
<td>
<a class="btn" href="#/hostgroups/{{group.id}}/">Szerkesztés</a>
<a class="btn btn-danger" href="#/hostgroups/{{group.id}}/delete/">Törlés</a>
</td>
</tr>
</table>
<form class="form-horizontal">
<div class="span5">
<div class="control-group">
<label class="control-label" for="ID">ID</label>
<div class="controls">
<span class="has-tooltip" title="Built in ID, do not change this">
<input
class="input-mini"
type="text"
id="ID"
placeholder="ID"
value="{{entity.id}}"
disabled="disabled" />
</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="created_at">Created at</label>
<div class="controls">
<span class="has-tooltip" title="Entity was created at this date">
<input
class="input"
type="text"
id="created_at"
ng-model="entity.created_at"
disabled="disabled" />
</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="modified_at">Modified at</label>
<div class="controls">
<span class="has-tooltip" title="Entity was last modified at this date">
<input
class="input"
type="text"
id="modified_at"
ng-model="entity.modified_at"
disabled="disabled" />
</span>
</div>
</div>
<div class="control-group" ng-class="hasError('name')">
<label class="control-label" for="name">Name</label>
<div class="controls">
<input
type="text"
id="name"
ng-model="entity.name"
title="Name of the DNS record" />
<span class="help-inline" ng-bind="getError('name')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('domain')">
<label class="control-label" for="domain">Domain</label>
<div class="controls">
<input
type="text"
id="domain"
class="domain"
ng-model="entity.domain.name"
title="Domain belonging to this record" />
<span class="help-inline" ng-bind="getError('domain')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('host')">
<label class="control-label" for="host">Host</label>
<div class="controls">
<input
type="text"
class="host"
id="host"
ng-model="entity.host.name"
title="Host belonging to this record" />
<span class="help-inline" ng-bind="getError('host')"></span>
</div>
</div>
</div>
<div class="span5">
<div class="control-group" ng-class="hasError('type')">
<label class="control-label" for="type">Type</label>
<div class="controls">
<select id="type" ng-model="entity.type" title="Record type">
<option value="A">A</option>
<option value="CNAME">CNAME</option>
<option value="AAAA">AAAA</option>
<option value="MX">MX</option>
<option value="NS">NS</option>
<option value="PTR">PTR</option>
</select>
<span class="help-inline" ng-bind="getError('type')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('owner')">
<label class="control-label" for="owner">Owner</label>
<div class="controls">
<input
type="text"
class="owner"
autocomplete="off"
id="owner"
ng-model="entity.owner.name"
title="Owners name" />
<span class="help-inline" ng-bind="getError('owner')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('address')">
<label class="control-label" for="address">Address</label>
<div class="controls">
<input
type="text"
autocomplete="off"
id="address"
ng-model="entity.address"
title="Address #TODO" />
<span class="help-inline" ng-bind="getError('address')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('ttl')">
<label class="control-label" for="ttl">TTL</label>
<div class="controls">
<input
type="text"
autocomplete="off"
id="ttl"
ng-model="entity.ttl"
title="Time To Live" />
<span class="help-inline" ng-bind="getError('ttl')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('description')">
<label class="control-label" for="description">Description</label>
<div class="controls">
<textarea rows="2"
id="description"
ng-model="entity.description"
title="Short description">
</textarea>
<span class="help-inline" ng-bind="getError('description')"></span>
</div>
</div>
<div class="control-group">
<div class="controls">
<button type="submit" class="btn" ng-click="save()">Save</button>
</div>
</div>
</div>
</form>
<div class="navbar">
<div class="navbar-inner">
<div class="pagination pull-left">
<ul>
<li ng-click="prevPage()" ng-class="{disabled: page == 1}">
<a href>Előző</a>
</li>
<li ng-repeat="_page in pages" ng-click="setPage(_page)" ng-class="{active: _page == page}">
<a href>{{_page}}</a>
</li>
<li ng-click="nextPage()" ng-class="{disabled: page == pages.length}">
<a href>Next</a>
</li>
</ul>
</div>
<form class="navbar-search" style="margin: 20px">
<input type="text" class="search-query" placeholder="Search" ng-model="query">
</form>
<a style="margin-top: 19px" href="#/records/new" class="btn">New record</a>
</div>
</div>
<table class="table table-striped">
<tr>
<th>Név</th>
<th>Típus</th>
<th>Domain</th>
<th>Hoszt</th>
<th>Cím</th>
<th>TTL</th>
<th>Leírás</th>
<th colspan="2">Tulajdonos</th>
</tr>
<tr ng-repeat="record in getPage()">
<td>
<a href="#/records/{{record.id}}">{{record.name}}</a>
</td>
<td>{{record.type}}</td>
<td>
<a href="#/domains/{{record.domain.id}}">{{record.domain.name}}</a>
</td>
<td>
<a href="#/hosts/{{record.host.id}}">{{record.host.name}}</a>
</td>
<td>{{record.address}}</td>
<td>{{record.ttl}}</td>
<td>{{record.owner.name}}</td>
<td>
<a class="btn" href="#/records/{{record.id}}/">Szerkesztés</a>
<a class="btn btn-danger" href="#/records/{{record.id}}/delete/">Törlés</a>
</td>
</tr>
</table>
<form class="form-horizontal">
<div class="span5">
<div class="control-group">
<label class="control-label" for="ID">ID</label>
<div class="controls">
<span class="has-tooltip" title="Built in ID, do not change this">
<input
class="input-mini"
type="text"
id="ID"
placeholder="ID"
value="{{entity.id}}"
disabled="disabled">
</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="created_at">Created at</label>
<div class="controls">
<span class="has-tooltip" title="The entity was created at this date">
<input
class="input"
type="text"
id="created_at"
ng-model="entity.created_at"
disabled="disabled" />
</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="modified_at">Modified at</label>
<div class="controls">
<span class="has-tooltip" title="The entity was last modified at this date">
<input
class="input"
type="text"
id="modified_at"
ng-model="entity.modified_at"
disabled="disabled" />
</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="targetType">Target type</label>
<div class="controls">
<select id="targetType"
ng-model="entity.target.type"
title="Entity type of the rules target">
<option value="vlan">Vlan</option>
<option value="vlangroup">VlanGroup</option>
<option value="host">Host</option>
<option value="hostgroup">HostGroup</option>
<option value="firewall">Firewall</option>
</select>
</div>
</div>
<div class="control-group" ng-class="hasError('targetName')">
<label class="control-label" for="targetName">Target</label>
<div class="controls">
<input
title="Target of the rule, can be a Vlan, Vlangroup, Host, Hostgroup or a Firewall"
type="text"
autocomplete="off"
id="targetName"
ng-model="entity.target.name" />
<span class="help-inline" ng-bind="getError('targetName')"></span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="direction">Direction</label>
<div class="controls">
<select id="direction"
ng-options="choice[0] as choice[1] for choice in entity.direction.choices"
ng-model="entity.direction.value"
title="Traffic direction">
</select>
</div>
</div>
<div class="control-group">
<label class="control-label" for="proto">Protocol</label>
<div class="controls">
<select id="proto"
ng-options="choice[0] as choice[1] for choice in entity.proto.choices"
ng-model="entity.proto.value"
title="Protocol filter">
<option value="">all</option>
</select>
</div>
</div>
<div class="control-group" ng-class="hasError('owner')">
<label class="control-label" for="owner">Owner</label>
<div class="controls">
<input
title="Owner of this rule"
type="text"
class="owner"
autocomplete="off"
id="owner"
ng-model="entity.owner.name" />
<span class="help-inline" ng-bind="getError('owner')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('foreign_network')">
<label class="control-label" for="foreign_network">Foreign network</label>
<div class="controls">
<input
title="Foreign Network"
type="text"
data-provide="typeahead"
autocomplete="off"
id="foreign_network"
class="vlangroup"
ng-model="entity.foreignNetwork.name" />
<span class="help-inline" ng-bind="getError('foreign_network')"></span>
</div>
</div>
</div>
<div class="span5">
<div class="control-group">
<label class="control-label" for="description">Description</label>
<div class="controls">
<textarea id="description"
ng-model="entity.description"
rows="2"
title="Short description of the rule">
</textarea>
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" ng-model="entity.nat"> NAT
</label>
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" ng-model="entity.accept"> Accept
</label>
</div>
</div>
<div class="control-group" ng-class="hasError('dport')">
<label class="control-label" for="dport">Destination port</label>
<div class="controls">
<input
class="input-mini"
type="number"
id="dport"
ng-model="entity.dport"
title="Destination port">
<span class="help-inline" ng-bind="getError('dport')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('nat_dport')">
<label class="control-label" for="nat_dport">NAT Destination port</label>
<div class="controls">
<input
class="input-mini"
type="number"
id="nat_dport"
ng-model="entity.nat_dport"
title="NAT Destination port">
<span class="help-inline" ng-bind="getError('nat_dport')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('sport')">
<label class="control-label" for="sport">Source port</label>
<div class="controls">
<input
class="input-mini"
type="number"
id="sport"
ng-model="entity.sport"
title="Source port">
<span class="help-inline" ng-bind="getError('sport')"></span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="extra">Extra</label>
<div class="controls">
<textarea id="extra"
ng-model="entity.extra"
rows="2"
title="I have no idea what is this... #TODO">
</textarea>
</div>
</div>
<div class="control-group">
<div class="controls">
<button type="submit" class="btn" ng-click="save()">Save</button>
</div>
</div>
</div>
</form>
<div class="navbar">
<div class="navbar-inner">
<div class="pagination pull-left">
<ul>
<li ng-click="prevPage()" ng-class="{disabled: page == 1}">
<a href>Előző</a>
</li>
<li ng-repeat="_page in pages" ng-click="setPage(_page)" ng-class="{active: _page == page}">
<a href>{{_page}}</a>
</li>
<li ng-click="nextPage()" ng-class="{disabled: page == pages.length}">
<a href>Next</a>
</li>
</ul>
</div>
<form class="navbar-search" style="margin: 20px">
<input type="text" class="search-query" placeholder="Search" ng-model="query">
</form>
<a style="margin-top: 20px" href="#/rules/new" class="btn">New rule</a>
</div>
</div>
<table class="table table-striped">
<tr>
<th>Irány</th>
<th>Protokoll</th>
<th>Cél</th>
<th>Idegen hálózat</th>
<th>Tulajdonos</th>
<th colspan="2">Megjegyzés</th>
</tr>
<tr ng-repeat="rule in getPage()">
<td>{{rule.direction}}</td>
<td>{{rule.proto}}</td>
<td>
<a href="#/{{rule.target.type}}s/{{rule.target.id}}">{{rule.target.name}}</a>
</td>
<td>
<a href="#/vlangroups/{{rule.foreignNetwork.id}}">{{rule.foreignNetwork.name}}</a>
</td>
<td>{{rule.owner.name}}</td>
<td>{{rule.description}}</td>
<td>
<a class="btn" href="#/rules/{{rule.id}}/">Szerkesztés</a>
<button class="btn btn-danger" ng-click="deleteEntity(rule.id)">Törlés</button>
</td>
</tr>
</table>
<form class="form-horizontal">
<div class="span5">
<div class="control-group">
<label class="control-label" for="ID">ID</label>
<div class="controls">
<span class="has-tooltip" title="Built in ID, do not change this">
<input
class="input-mini"
type="text"
id="ID"
placeholder="ID"
value="{{entity.id}}"
disabled="disabled" />
</span>
</div>
</div>
<div class="control-group" ng-class="hasError('vid')">
<label class="control-label" for="VID">VID</label>
<div class="controls">
<input
class="input-mini"
type="text"
id="VID"
placeholder="ID"
ng-model="entity.vid"
title="Vlan ID, you can change this" />
<span class="help-inline" ng-bind="getError('vid')"></span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="created_at">Created at</label>
<div class="controls">
<span class="has-tooltip" title="The entity was created at this date">
<input
class="input"
type="text"
id="created_at"
ng-model="entity.created_at"
disabled="disabled" />
</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="modified_at">Modified at</label>
<div class="controls">
<span class="has-tooltip" title="The entity was last modified at this date">
<input
class="input"
type="text"
id="modified_at"
ng-model="entity.modified_at"
disabled="disabled" />
</span>
</div>
</div>
<div class="control-group" ng-class="hasError('name')">
<label class="control-label" for="name">Name</label>
<div class="controls">
<input
type="text"
id="name"
ng-model="entity.name"
title="Name of this Vlan" />
<span class="help-inline" ng-bind="getError('name')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('ipv4')">
<label class="control-label" for="ipv4">IPv4</label>
<div class="controls">
<input
type="text"
id="ipv4"
ng-model="entity.ipv4"
title="IPv4 address of this Vlan (with subnet mask)" />
<span class="help-inline" ng-bind="getError('ipv4')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('ipv6')">
<label class="control-label" for="ipv6">IPv6</label>
<div class="controls">
<input
type="text"
class="input-xlarge"
id="ipv6"
ng-model="entity.ipv6"
title="IPv6 address of this Vlan (with subnet mask)" />
<span class="help-inline" ng-bind="getError('ipv6')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('net4')">
<label class="control-label" for="net4">Net4</label>
<div class="controls">
<input
type="text"
id="net4"
ng-model="entity.net4"
title="Net4 address of this Vlan (with subnet mask)" />
<span class="help-inline" ng-bind="getError('net4')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('net6')">
<label class="control-label" for="net6">Net6</label>
<div class="controls">
<input
type="text"
class="input-xlarge"
id="net6"
ng-model="entity.net6"
title="Net6 address of this Vlan (with subnet mask)" />
<span class="help-inline" ng-bind="getError('net6')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('snat_ip')">
<label class="control-label" for="snat_ip">NAT</label>
<div class="controls">
<input
type="text"
id="snat_ip"
ng-model="entity.nat"
title="NAT IP for this Vlan" />
<span class="help-inline" ng-bind="getError('snat_ip')"></span>
</div>
</div>
</div>
<div class="span5">
<div class="control-group" ng-class="hasError('owner')">
<label class="control-label" for="owner">Owner</label>
<div class="controls">
<input
type="text"
class="owner"
autocomplete="off"
id="owner"
ng-model="entity.owner.name"
title="Owners name" />
<span class="help-inline" ng-bind="getError('owner')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('interface')">
<label class="control-label" for="interface">Interface</label>
<div class="controls">
<input
type="text"
autocomplete="off"
id="interface"
ng-model="entity.interface"
title="#TODO" />
<span class="help-inline" ng-bind="getError('interface')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('domain')">
<label class="control-label" for="domain">Domain</label>
<div class="controls">
<input
type="text"
class="domain"
data-provide="typeahead"
autocomplete="off"
id="domain"
ng-model="entity.domain.name"
title="Domain name" />
<span class="help-inline" ng-bind="getError('domain')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('reverse_domain')">
<label class="control-label" for="reverse_domain">Reverse domain</label>
<div class="controls">
<input
type="text"
autocomplete="off"
id="reverse_domain"
ng-model="entity.reverse_domain"
title="Reverse domain name" />
<span class="help-inline" ng-bind="getError('reverse_domain')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('description')">
<label class="control-label" for="description">Description</label>
<div class="controls">
<textarea id="description"
rows="2"
ng-model="entity.description"
title="Short description">
</textarea>
<span class="help-inline" ng-bind="getError('description')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('comment')">
<label class="control-label" for="comment">Comment</label>
<div class="controls">
<textarea id="comment"
rows="2"
ng-model="entity.comment"
title="Some comments... #TODO">
</textarea>
<span class="help-inline" ng-bind="getError('comment')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('dhcp_pool')">
<label class="control-label" for="dhcp_pool">DHCP Pool</label>
<div class="controls">
<textarea id="dhcp_pool"
rows="2"
ng-model="entity.dhcp_pool"
title="Some DHCP Pool related thing... #TODO">
</textarea>
<span class="help-inline" ng-bind="getError('dhcp_pool')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('vlans')">
<label class="control-label" for="vlans">NAT to (?)</label>
<div class="controls">
<div class="well well-small">
<span class="label label-info" ng-repeat="vlan in entity.vlans | filter: destroyed">
<a href="#/vlans/{{vlan.id}}">{{vlan.name}}</a>
<a href ng-click="removeVlan(vlan)"><i class="icon-remove"></i></a>
</span>
</div>
<div class="input-append has-tooltip" title="#TODO">
<input class="span2 vlan" id="vlans" type="text" ng-model="newVlan">
<button class="btn" type="button" ng-click="addVlan(newVlan)">Add</button>
</div>
<span class="help-inline" ng-bind="getError('vlans')"></span>
</div>
</div>
<div class="control-group">
<div class="controls">
<button type="submit" class="btn" ng-click="save()">Save</button>
</div>
</div>
</div>
</form>
<div class="span12">
<h3>Rules belonging to this vlan</h3>
<table class="table table-striped">
<thead>
<tr>
<th>Direction</th>
<th>Protocol</th>
<th>Accept</th>
<th>NAT</th>
<th colspan="2">Owner</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="rule in entity.rules">
<td>{{rule.direction}}</td>
<td>{{rule.proto}}</td>
<td>{{rule.accept}}</td>
<td>{{rule.nat}}</td>
<td>{{rule.owner.name}}</td>
<td>
<a class="btn" href="#/rules/{{rule.id}}/">Edit</a>
<a class="btn btn-danger" href="#/rules/{{rule.id}}/delete/">Delete</a>
</td>
</tr>
</tbody>
</table>
</div>
<div class="navbar">
<div class="navbar-inner">
<div class="pagination pull-left">
<ul>
<li ng-click="prevPage()" ng-class="{disabled: page == 1}">
<a href>Előző</a>
</li>
<li ng-repeat="_page in pages" ng-click="setPage(_page)" ng-class="{active: _page == page}">
<a href>{{_page}}</a>
</li>
<li ng-click="nextPage()" ng-class="{disabled: page == pages.length}">
<a href>Next</a>
</li>
</ul>
</div>
<form class="navbar-search" style="margin: 20px">
<input type="text" class="search-query" placeholder="Search" ng-model="query">
</form>
<a style="margin-top: 19px" href="#/vlans/new" class="btn">New vlan</a>
</div>
</div>
<table class="table table-striped">
<tr>
<th>Vid</th>
<th>Név</th>
<th>IPv4</th>
<th>IPv6</th>
<th>Leírás</th>
<th colspan="2">Domain</th>
</tr>
<tr ng-repeat="vlan in getPage()">
<td>{{vlan.vid}}</td>
<td>
<a href="#/vlans/{{vlan.id}}">{{vlan.name}}</a>
</td>
<td>{{vlan.ipv4}}</td>
<td>{{vlan.ipv6}}</td>
<td style="width: 200px;">{{vlan.description}}</td>
<td>
<a href="#/vlans/{{vlan.domain.id}}">{{vlan.domain.name}}</a>
</td>
<td>
<a class="btn" href="#/vlans/{{vlan.id}}/">Szerkesztés</a>
<a class="btn btn-danger" href="#/vlans/{{vlan.id}}/delete/">Törlés</a>
</td>
</tr>
</table>
<form class="form-horizontal">
<div class="span5">
<div class="control-group">
<label class="control-label" for="ID">ID</label>
<div class="controls">
<span class="has-tooltip" title="Built in ID, do not change this">
<input
class="input-mini"
type="text"
id="ID"
placeholder="ID"
value="{{entity.id}}"
disabled="disabled" />
</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="created_at">Created at</label>
<div class="controls">
<span class="has-tooltip" title="The entity was created at this date">
<input
class="input"
type="text"
id="created_at"
ng-model="entity.created_at"
disabled="disabled" />
</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="modified_at">Modified at</label>
<div class="controls">
<span class="has-tooltip" title="The entity was last modified at this date">
<input
class="input"
type="text"
id="modified_at"
ng-model="entity.modified_at"
disabled="disabled" />
</span>
</div>
</div>
<div class="control-group" ng-class="hasError('name')">
<label class="control-label" for="name">Name</label>
<div class="controls">
<input
type="text"
id="name"
ng-model="entity.name"
title="Name for this Vlangroup" />
<span class="help-inline" ng-bind="getError('name')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('owner')">
<label class="control-label" for="owner">Owner</label>
<div class="controls">
<input
type="text"
class="owner"
autocomplete="off"
id="owner"
ng-model="entity.owner.name"
title="Owners name" />
<span class="help-inline" ng-bind="getError('owner')"></span>
</div>
</div>
</div>
<div class="span5">
<div class="control-group" ng-class="hasError('description')">
<label class="control-label" for="description">Description</label>
<div class="controls">
<textarea id="description"
rows="2"
ng-model="entity.description"
title="Short description">
</textarea>
<span class="help-inline" ng-bind="getError('description')"></span>
</div>
</div>
<div class="control-group" ng-class="hasError('vlans')">
<label class="control-label" for="vlans">Vlans</label>
<div class="controls">
<div class="well well-small">
<span class="label label-info" ng-repeat="vlan in entity.vlans | filter: destroyed">
<a href="#/vlans/{{vlan.id}}">{{vlan.name}}</a>
<a href ng-click="removeVlan(vlan)"><i class="icon-remove"></i></a>
</span>
</div>
<div class="input-append has-tooltip" title="Vlan members of this group">
<input
class="span2 vlan"
id="vlans"
type="text"
ng-model="newVlan">
<button class="btn" type="button" ng-click="addVlan(newVlan)">Add</button>
</div>
<span class="help-inline" ng-bind="getError('vlans')"></span>
</div>
</div>
<div class="control-group">
<div class="controls">
<button type="submit" class="btn" ng-click="save()">Save</button>
</div>
</div>
</div>
</form>
<div class="span12">
<h3>Rules belonging to this vlan</h3>
<table class="table table-striped">
<thead>
<tr>
<th>Direction</th>
<th>Protocol</th>
<th>Accept</th>
<th>NAT</th>
<th colspan="2">Owner</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="rule in entity.rules">
<td>{{rule.direction}}</td>
<td>{{rule.proto}}</td>
<td>{{rule.accept}}</td>
<td>{{rule.nat}}</td>
<td>{{rule.owner.name}}</td>
<td>
<a class="btn" href="#/rules/{{rule.id}}/">Edit</a>
<a class="btn btn-danger" href="#/rules/{{rule.id}}/delete/">Delete</a>
</td>
</tr>
</tbody>
</table>
</div>
<div class="navbar">
<div class="navbar-inner">
<div class="pagination pull-left">
<ul>
<li ng-click="prevPage()" ng-class="{disabled: page == 1}">
<a href>Előző</a>
</li>
<li ng-repeat="_page in pages" ng-click="setPage(_page)" ng-class="{active: _page == page}">
<a href>{{_page}}</a>
</li>
<li ng-click="nextPage()" ng-class="{disabled: page == pages.length}">
<a href>Next</a>
</li>
</ul>
</div>
<form class="navbar-search" style="margin: 20px">
<input type="text" class="search-query" placeholder="Search" ng-model="query">
</form>
<a style="margin-top: 19px" href="#/vlangroups/new" class="btn">New vlangroup</a>
</div>
</div>
<table class="table table-striped">
<tr>
<th>Név</th>
<th>Vlanok</th>
<th>Leírás</th>
<th colspan="2">Tulajdonos</th>
</tr>
<tr ng-repeat="group in getPage()">
<td>
<a href="#/vlangroups/{{group.id}}">{{group.name}}</a>
</td>
<td>
<div class="well well-small">
<span class="label label-info" ng-repeat="vlan in group.vlans">
<a href="#/vlans/{{vlan.id}}">{{vlan.name}}</a>
</span>
</div>
</td>
<td style="width: 200px;">{{group.description}}</td>
<td>{{group.owner.name}}</td>
<td>
<a class="btn" href="#/vlangroups/{{group.id}}/">Szerkesztés</a>
<a class="btn btn-danger" href="#/vlangroups/{{group.id}}/delete/">Törlés</a>
</td>
</tr>
</table>
{% templatetag openblock %} extends "base.html" {% templatetag closeblock %}
{% templatetag openblock %} block title {% templatetag closeblock %}Page Not found{% templatetag openblock %} endblock {% templatetag closeblock %}
{% templatetag openblock %} block page_title {% templatetag closeblock %}Page Not found{% templatetag openblock %} endblock page_title {% templatetag closeblock %}
{% templatetag openblock %} block content {% templatetag closeblock %}
<p>This is not the page you were looking for.</p>
{% templatetag openblock %} endblock content {% templatetag closeblock %}
\ No newline at end of file
<h1>Whoops!</h1>
\ No newline at end of file
{% load i18n %}
{% load l10n %}
{% load staticfiles %}
{% get_current_language as lang %}
<!DOCTYPE html>
<html lang="en" ng-app="firewall">
<head>
<meta charset="utf-8">
<title>{% block title %}Firewall GUI{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
<link href='http://fonts.googleapis.com/css?family=Source+Sans+Pro:200,400&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
<link href="{% static "css/bootstrap.min.css" %}" rel="stylesheet">
<style>
body {
font-family: 'Source Sans Pro', sans-serif;
padding-top: 60px;
}
h1, h2, h3, h4, h5 {
font-weight: 200;
}
textarea, input, select {
font-family: 'Source Sans Pro';
}
.label {
margin: 3px;
padding: 5px;
}
.label a {
color: white;
}
select:focus {
outline: none;
}
</style>
<link href="{% static "css/bootstrap-responsive.min.css" %}" rel="stylesheet">
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.min.js"></script>
<link href="{% static "css/project.css" %}" rel="stylesheet">
{% block extra_css %}{% endblock %}
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="brand" href="#">Firewall GUI</a>
<div class="nav-collapse collapse">
<ul class="nav">
<li><a href="#/rules/">{% trans "Firewall rules" %}</a></li>
<li><a href="#/vlans/">{% trans "Vlans" %}</a></li>
<li><a href="#/vlangroups/">{% trans "Vlan groups" %}</a></li>
<li><a href="#/hostgroups/">{% trans "Host groups" %}</a></li>
<li><a href="#/hosts/">{% trans "Hosts" %}</a></li>
<li><a href="#/firewalls/">{% trans "Firewalls" %}</a></li>
<li><a href="#/domains/">{% trans "Domains" %}</a></li>
<li><a href="#/records/">{% trans "DNS records" %}</a></li>
<li><a href="#/blacklists/">{% trans "Blacklists" %}</a></li>
</ul>
</div>
</div>
</div>
</div>
<div class="container">
<h1>{% block page_title %}Szia bd, ez itt a tűzfaladmin!{% endblock %}</h1>
<div ng-view></div>
</div>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{% static "js/bootstrap.min.js" %}"></script>
<script src="{% static "js/project.js" %}"></script>
<script>
{% block extra_js %}
{% endblock %}
</script>
</body>
</html>
from django.http import HttpResponse
from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import user_passes_test
from django.contrib.auth.models import User
from firewall.fw import *
from firewall.models import *
def req_staff(user):
''' decorator function for user permission checking '''
return user.is_staff
def index(request):
return render(request, 'firewall/index.html')
def map_rule_target(rule):
''' get the actual target from rule field (vlan|vlangroup|host|hostgroup|firewall) '''
return {
'name': rule.vlan.name,
'id': rule.vlan.id,
'type': 'vlan',
} if rule.vlan else {
'name': rule.vlangroup.name,
'id': rule.vlangroup.id,
'type': 'vlangroup',
} if rule.vlangroup else {
'name': rule.hostgroup.name,
'id': rule.hostgroup.id,
'type': 'hostgroup',
} if rule.hostgroup else {
'name': rule.firewall.name,
'id': rule.firewall.id,
'type': 'firewall',
} if rule.firewall else {
'name': rule.host.hostname,
'id': rule.host.id,
'type': 'host',
}
def json_attr(entity, attr):
''' jsonify the `attr` attribute of `entity` '''
# an objects name usually is in the `name` attribute, but not always (thanks bd!), so put here the exceptions
common_names = {
'host': 'hostname',
'owner': 'username',}
try:
# return something that can be converted to JSON, based on the `attr` field type
return {
# if `attr` is an entity, parse its name&id
'ForeignKey': lambda entity: {
'id': entity.id,
'name': getattr(entity, common_names[attr] if attr in common_names.keys() else 'name')
} if entity else None,
# if `attr` is a date, format it with isoformat
'DateTimeField': lambda entity: entity.isoformat(),
# if `attr` is a Crazy ManyToMany field, fetch all objects, and get their name&id
'ManyToManyField': lambda field: [{
'id': entity.id,
'name': getattr(entity, common_names[attr] if attr in common_names.keys() else 'name')
} for entity in field.all()]
}[entity._meta.get_field_by_name(attr)[0].__class__.__name__](getattr(entity, attr))
except Exception as e:
# if `attr` is something else, we hope it can be converted to JSON
return getattr(entity, attr)
def make_entity_lister(entity_type, mapping):
''' makes a function that lists the given entities '''
def jsonify(entity):
''' jsonify one entity '''
result = {}
for attr in mapping:
# if `attr` is a tuple, the first element is the key in the JSON, the second is a function that calculates the value
if type(attr) is tuple:
result[attr[0]] = attr[1](entity)
else:
# if `attr` is just a string, the try to jsonify the corresponding model attribute
result[attr] = json_attr(entity, attr)
return result
def entity_lister(request):
''' jsonify all objects of the given model type '''
return [jsonify(entity) for entity in entity_type.objects.all()]
return entity_lister
@user_passes_test(req_staff)
def list_entities(request, name):
return HttpResponse(json.dumps({
'rules': make_entity_lister(Rule, [
'id',
('target',map_rule_target),
'r_type',
('direction',lambda rule: rule.get_direction_display()),
'proto',
'owner',
'foreign_network',
'created_at',
'modified_at',
'nat',
'accept',
'description']),
'hosts': make_entity_lister(Host, [
'id',
'reverse',
'ipv4',
'shared_ip',
'description',
'comment',
'location',
'vlan',
'owner',
'created_at',
'modified_at',
'groups']),
'vlans': make_entity_lister(Vlan, [
'id',
'vid',
'name',
('ipv4', lambda vlan: vlan.ipv4+'/'+str(vlan.prefix4)),
('ipv6', lambda vlan: vlan.ipv6+'/'+str(vlan.prefix6)),
('nat', lambda vlan: vlan.snat_ip),
'description',
'domain']),
'vlangroups': make_entity_lister(VlanGroup, [
'id',
'name',
'vlans',
'description',
'owner',
'created_at',
'modified_at']),
'hostgroups': make_entity_lister(Group, [
'id',
'name',
'description',
'owner',
'created_at',
'modified_at']),
'firewalls': make_entity_lister(Firewall, ['id', 'name']),
'domains': make_entity_lister(Domain, [
'id',
'name',
'created_at',
'modified_at',
'ttl',
'description',
'owner']),
'records': make_entity_lister(Record, [
'id',
'name',
'domain',
'host',
'type',
'address',
'ttl',
'owner',
'description',
'modified_at',
'created_at']),
'blacklists': make_entity_lister(Blacklist, [
'id',
'host',
'reason',
'snort_message',
'type',
'created_at',
'modified_at',
'ipv4']),
}[name](request)), content_type='application/json')
def show_rule(request, id=None):
try:
rule = Rule.objects.get(id=id)
rule = {
'id': rule.id,
'target': {
'name': rule.vlan.name,
'id': rule.vlan.id,
'type': 'vlan',
} if rule.vlan else {
'name': rule.vlangroup.name,
'id': rule.vlangroup.id,
'type': 'vlangroup',
} if rule.vlangroup else {
'name': rule.hostgroup.name,
'id': rule.hostgroup.id,
'type': 'hostgroup',
} if rule.hostgroup else {
'name': rule.firewall.name,
'id': rule.firewall.id,
'type': 'firewall',
} if rule.firewall else {
'name': rule.host.hostname,
'id': rule.host.id,
'type': 'host',
},
'type': rule.r_type,
'direction': {
'value': rule.direction,
'choices': Rule._meta.get_field_by_name('direction')[0].choices,
},
'proto': {
'value': rule.proto,
'choices': Rule._meta.get_field_by_name('proto')[0].choices,
},
'owner': {
'name': str(rule.owner),
'id': rule.owner.id
},
'foreignNetwork': {
'name': rule.foreign_network.name,
'id': rule.foreign_network.id,
},
'created_at': rule.created_at.isoformat(),
'modified_at': rule.modified_at.isoformat(),
'nat': rule.nat,
'accept': rule.accept,
'description': rule.description,
'dport': rule.dport,
'sport': rule.sport,
'extra': rule.extra,
'nat_dport': rule.nat_dport
}
except:
rule = {
'id': None,
'target': { 'id': None, 'name': None, 'type': None },
'type': None,
'direction': {
'value': None,
'choices': Rule._meta.get_field_by_name('direction')[0].choices,
},
'proto': {
'value': None,
'choices': Rule._meta.get_field_by_name('proto')[0].choices,
},
'owner': {
'id': None,
'name': None,
},
'foreignNetwork': { 'name': None, 'id': None },
'created_at': None,
'modified_at': None,
'nat': False,
'accept': False,
'description': '',
'dport': None,
'sport': None,
'extra': '',
'nat_dport': None,
}
return HttpResponse(json.dumps(rule), content_type='application/json')
def show_host(request, id=None):
try:
host = Host.objects.get(id=id)
host = {
'id': host.id,
'reverse': host.reverse,
'name': host.hostname,
'mac': host.mac,
'ipv4': host.ipv4,
'ipv6': host.ipv6,
'pub_ipv4': host.pub_ipv4,
'shared_ip': host.shared_ip,
'description': host.description,
'comment': host.comment,
'location': host.location,
'vlan': {
'name': host.vlan.name,
'id': host.vlan.id
},
'owner': {
'name': str(host.owner),
'id': host.owner.id
},
'created_at': host.created_at.isoformat(),
'modified_at': host.modified_at.isoformat(),
'groups': [{
'name': group.name,
'id': group.id,
} for group in host.groups.all()],
'rules': [{
'id': rule.id,
'direction': rule.get_direction_display(),
'proto': rule.proto,
'owner': {
'id': rule.owner.id,
'name': str(rule.owner),
},
'accept': rule.accept,
'nat': rule.nat
} for rule in host.rules.all()]
}
except:
host = {
'id': None,
'reverse': None,
'name': None,
'mac': None,
'ipv4': None,
'ipv6': None,
'pub_ipv4': None,
'shared_ip': False,
'description': '',
'comment': '',
'location': '',
'vlan': {
'name': None,
},
'owner': {
'name': None,
},
'created_at': None,
'modified_at': None,
'groups': [],
'rules': []
}
return HttpResponse(json.dumps(host), content_type='application/json')
def show_vlan(request, id=None):
try:
vlan = Vlan.objects.get(id=id)
vlan = {
'id': vlan.id,
'vid': vlan.vid,
'name': vlan.name,
'ipv4': vlan.ipv4+'/'+str(vlan.prefix4),
'ipv6': vlan.ipv6+'/'+str(vlan.prefix6),
'net4': vlan.net4+'/'+str(vlan.prefix4),
'net6': vlan.net6+'/'+str(vlan.prefix6),
'nat': vlan.snat_ip,
'description': vlan.description,
'comment': vlan.comment,
'reverse_domain': vlan.reverse_domain,
'dhcp_pool': vlan.dhcp_pool,
'interface': vlan.interface,
'created_at': vlan.created_at.isoformat(),
'modified_at': vlan.modified_at.isoformat(),
'owner': {
'name': str(vlan.owner),
'id': vlan.owner.id
} if vlan.owner else None,
'domain': {
'id': vlan.domain.id,
'name': vlan.domain.name,
},
'rules': [{
'id': rule.id,
'direction': rule.get_direction_display(),
'proto': rule.proto,
'owner': {
'id': rule.owner.id,
'name': str(rule.owner),
},
'accept': rule.accept,
'nat': rule.nat
} for rule in vlan.rules.all()],
'vlans': [{
'id': vlan.id,
'name': vlan.name,
} for vlan in vlan.snat_to.all()]
}
except:
vlan = {
'id': None,
'vid': None,
'name': None,
'ipv4': None,
'ipv6': None,
'nat': '',
'description': '',
'comment': '',
'reverse_domain': '',
'dhcp_pool': '',
'interface': '',
'created_at': None,
'modified_at': None,
'owner': {
'name': None,
},
'domain': {
'name': None,
},
'rules': [],
'vlans': []
}
return HttpResponse(json.dumps(vlan), content_type='application/json')
def show_vlangroup(request, id=None):
try:
group = VlanGroup.objects.get(id=id)
group = {
'id': group.id,
'name': group.name,
'vlans': [{
'id': vlan.id,
'name': vlan.name
} for vlan in group.vlans.all()],
'description': group.description,
'owner': {
'id': group.owner.id,
'name': str(group.owner)
},
'created_at': group.created_at.isoformat(),
'modified_at': group.modified_at.isoformat(),
'rules': [{
'id': rule.id,
'direction': rule.get_direction_display(),
'proto': rule.proto,
'owner': {
'id': rule.owner.id,
'name': str(rule.owner),
},
'accept': rule.accept,
'nat': rule.nat
} for rule in group.rules.all()]
}
except:
group = {
'id': None,
'name': None,
'vlans': [],
'description': '',
'owner': {
'name': None
},
'created_at': None,
'modified_at': None,
'rules': []
}
return HttpResponse(json.dumps(group), content_type='application/json')
def show_hostgroup(request, id=None):
try:
group = Group.objects.get(id=id)
group = {
'id': group.id,
'name': group.name,
'description': group.description,
'owner': {
'id': group.owner.id,
'name': str(group.owner),
},
'created_at': group.created_at.isoformat(),
'modified_at': group.modified_at.isoformat(),
'hosts': [{
'id': host.id,
'name': host.hostname
} for host in group.host_set.all()],
'rules': [{
'id': rule.id,
'direction': rule.get_direction_display(),
'proto': rule.proto,
'owner': {
'id': rule.owner.id,
'name': str(rule.owner),
},
'accept': rule.accept,
'nat': rule.nat
} for rule in group.rules.all()]
}
except:
group = {
'id': None,
'name': None,
'description': '',
'owner': {
'name': None,
},
'created_at': None,
'modified_at': None,
'hosts': [],
'rules': []
}
return HttpResponse(json.dumps(group), content_type='application/json')
def show_record(request, id=None):
try:
record = Record.objects.get(id=id)
record = {
'id': record.id,
'name': record.name,
'domain': {
'id': record.domain.id,
'name': record.domain.name
},
'host': {
'id': record.host.id,
'name': record.host.hostname
} if record.host else None,
'type': record.type,
'address': record.address,
'ttl': record.ttl,
'owner': {
'id': record.owner.id,
'name': record.owner.username
},
'description': record.description,
'created_at': record.created_at.isoformat(),
'modified_at': record.modified_at.isoformat(),
}
except:
record = {
'id': None,
'name': None,
'domain': {
'name': None
},
'host': {
'name': None
},
'type': None,
'address': None,
'ttl': None,
'owner': {
'name': None
},
'description': '',
'created_at': None,
'modified_at': None,
}
return HttpResponse(json.dumps(record), content_type='application/json')
def show_domain(request, id=None):
try:
domain = Domain.objects.get(id=id)
domain = {
'id': domain.id,
'name': domain.name,
'owner': {
'id': domain.owner.id,
'name': domain.owner.username,
},
'created_at': domain.created_at.isoformat(),
'modified_at': domain.modified_at.isoformat(),
'ttl': domain.ttl,
'description': domain.description
}
except:
domain = {
'id': None,
'name': None,
'owner': {
'name': None,
},
'created_at': None,
'modified_at': None,
'ttl': None,
'description': ''
}
return HttpResponse(json.dumps(domain), content_type='application/json')
def make_autocomplete(entity, name='name'):
def autocomplete(request):
return HttpResponse(json.dumps([{
'id': object.id,
'name': getattr(object, name)
} for object in entity.objects.filter(**{
name+'__icontains': request.POST['name']
})[:5]]), content_type='application/json')
return autocomplete
def autocomplete(request, entity):
try:
return {
'vlan': make_autocomplete(Vlan),
'vlangroup': make_autocomplete(VlanGroup),
'host': make_autocomplete(Host, 'hostname'),
'hostgroup': make_autocomplete(Group),
'firewall': make_autocomplete(Firewall),
'domain': make_autocomplete(Domain),
'record': make_autocomplete(Record),
'owner': make_autocomplete(User, 'username')
}[entity](request)
except Exception as e:
return HttpResponse('>:-3'+str(e), status=500)
def set_field(object, attr, errors, **kwargs):
try:
model = getattr(object.__class__, attr).field.rel.to
setattr(object, attr, model.objects.get(**kwargs))
except Exception as e:
errors[attr] = ('%(model)s with the name "%(name)s" does not exists!') % {
'model': model.__name__,
'name': kwargs.values()[0]
}
@user_passes_test(req_staff)
def save_rule(request):
data = json.loads(request.body)
if 'id' in data and data['id']:
rule = get_object_or_404(Rule, id=data['id'])
else:
rule = Rule()
errors = {}
rule.direction = data['direction']['value']
rule.description = data['description']
rule.dport = data['dport']
rule.sport = data['sport']
rule.proto = data['proto']['value']
rule.extra = data['extra']
rule.accept = data['accept']
rule.nat = data['nat']
rule.nat_dport = data['nat_dport']
rule.r_type = data['target']['type']
set_field(rule, 'owner', errors, username=data['owner']['name'])
for attr in ['host', 'hostgroup', 'vlan', 'vlangroup', 'firewall']:
searchBy = 'name' if attr != 'host' else 'hostname'
if data['target']['type'] == attr:
set_field(rule, attr, errors, **{searchBy: data['target']['name']})
else:
setattr(rule, attr, None)
set_field(rule, 'foreign_network', errors, name=data['foreignNetwork']['name'])
try:
rule.full_clean()
except Exception as e:
errors = dict(errors.items() + e.message_dict.items())
if len(errors) > 0:
return HttpResponse(json.dumps(errors), content_type='application/json', status=400)
rule.save()
return HttpResponse(rule.id)
@user_passes_test(req_staff)
def save_host(request):
data = json.loads(request.body)
if 'id' in data and data['id']:
host = get_object_or_404(Host, id=data['id'])
else:
host = Host()
errors = {}
host.reverse = data['reverse']
host.hostname = data['name']
host.mac = data['mac']
host.ipv4 = data['ipv4']
host.ipv6 = data['ipv6']
host.pub_ipv4 = data['pub_ipv4']
host.shared_ip = data['shared_ip']
host.description = data['description']
host.comment = data['comment']
host.location = data['location']
set_field(host, 'vlan', errors, name=data['vlan']['name'])
set_field(host, 'owner', errors, username=data['owner']['name'])
for group in data['groups']:
try:
if '__destroyed' in group and group['__destroyed']:
group_object = Group.objects.get(name = group['name'])
host.groups.remove(group_object)
elif '__created' in group and group['__created']:
group_object = Group.objects.get(name = group['name'])
host.groups.add(group_object)
except Exception as e:
errors['groups'] = ('Group with the name "%(name)s" does not exists!') % {
'name': group['name']
}
try:
host.full_clean()
except Exception as e:
errors = dict(errors.items() + e.message_dict.items())
if len(errors) > 0:
return HttpResponse(json.dumps(errors), content_type='application/json', status=400)
host.save()
return HttpResponse(host.id)
@user_passes_test(req_staff)
def save_vlan(request):
data = json.loads(request.body)
if 'id' in data and data['id']:
vlan = get_object_or_404(Vlan, id=data['id'])
else:
vlan = Vlan()
errors = {}
vlan.vid = data['vid']
vlan.name = data['name']
vlan.ipv4 = data['ipv4'].split('/')[0]
vlan.net4 = data['net4'].split('/')[0]
vlan.prefix4 = data['ipv4'].split('/')[1]
vlan.ipv6 = data['ipv6'].split('/')[0]
vlan.net6 = data['net6'].split('/')[0]
vlan.prefix6 = data['ipv6'].split('/')[1]
if data['ipv4'].split('/')[1] != data['net4'].split('/')[1]:
errors['ipv4'] = 'Netmask legth should be equal!'
errors['net4'] = 'Netmask legth should be equal!'
if data['ipv6'].split('/')[1] != data['net6'].split('/')[1]:
errors['ipv6'] = 'Netmask legth should be equal!'
errors['net6'] = 'Netmask legth should be equal!'
vlan.snat_ip = data['nat']
vlan.description = data['description']
vlan.comment = data['comment']
vlan.reverse_domain = data['reverse_domain']
vlan.dhcp_pool = data['dhcp_pool']
vlan.interface = data['interface']
set_field(vlan, 'owner', errors, username=data['owner']['name'])
set_field(vlan, 'domain', errors, name=data['domain']['name'])
for group in data['vlans']:
try:
if '__destroyed' in group and group['__destroyed']:
nat_to = Vlan.objects.get(name = group['name'])
vlan.snat_to.remove(nat_to)
elif '__created' in group and group['__created']:
nat_to = Vlan.objects.get(name = group['name'])
vlan.snat_to.add(nat_to)
except Exception as e:
errors['vlans'] = ('Vlan with the name "%(name)s" does not exists!') % {
'name': group['name']
}
try:
vlan.full_clean()
except Exception as e:
errors = dict(errors.items() + e.message_dict.items())
if len(errors) > 0:
return HttpResponse(json.dumps(errors), content_type='application/json', status=400)
vlan.save()
return HttpResponse(vlan.id)
@user_passes_test(req_staff)
def save_vlangroup(request):
data = json.loads(request.body)
if 'id' in data and data['id']:
vlangroup = get_object_or_404(VlanGroup, id=data['id'])
else:
vlangroup = VlanGroup()
errors = {}
vlangroup.name = data['name']
vlangroup.description = data['description']
for vlan in data['vlans']:
try:
if '__destroyed' in vlan and vlan['__destroyed']:
vlan_obj = Vlan.objects.get(name = vlan['name'])
vlangroup.vlans.remove(vlan_obj)
elif '__created' in vlan and vlan['__created']:
vlan_obj = Vlan.objects.get(name = vlan['name'])
vlangroup.vlans.add(vlan_obj)
except Exception as e:
errors['vlans'] = ('Vlan with the name "%(name)s" does not exists!') % {
'name': vlan['name']
}
set_field(vlangroup, 'owner', errors, username=data['owner']['name'])
try:
vlangroup.full_clean()
except Exception as e:
errors = dict(errors.items() + e.message_dict.items())
if len(errors) > 0:
return HttpResponse(json.dumps(errors), content_type='application/json', status=400)
vlangroup.save()
return HttpResponse(vlangroup.id)
@user_passes_test(req_staff)
def save_hostgroup(request):
data = json.loads(request.body)
if 'id' in data and data['id']:
hostgroup = get_object_or_404(Group, id=data['id'])
else:
hostgroup = Group()
errors = {}
hostgroup.name = data['name']
hostgroup.description = data['description']
set_field(hostgroup, 'owner', errors, username=data['owner']['name'])
try:
hostgroup.full_clean()
except Exception as e:
errors = dict(errors.items() + e.message_dict.items())
if len(errors) > 0:
return HttpResponse(json.dumps(errors), content_type='application/json', status=400)
hostgroup.save()
return HttpResponse(hostgroup.id)
@user_passes_test(req_staff)
def save_domain(request):
data = json.loads(request.body)
if 'id' in data and data['id']:
domain = get_object_or_404(Domain, id=data['id'])
else:
domain = Domain()
errors = {}
domain.name = data['name']
domain.ttl = data['ttl']
domain.description = data['description']
set_field(domain, 'owner', errors, username=data['owner']['name'])
try:
domain.full_clean()
except Exception as e:
errors = dict(errors.items() + e.message_dict.items())
if len(errors) > 0:
return HttpResponse(json.dumps(errors), content_type='application/json', status=400)
domain.save()
return HttpResponse(domain.id)
@user_passes_test(req_staff)
def save_record(request):
data = json.loads(request.body)
if 'id' in data and data['id']:
record = get_object_or_404(Record, id=data['id'])
else:
record = Record()
errors = {}
record.name = data['name']
record.ttl = data['ttl']
record.description = data['description']
record.type = data['type']
record.address = data['address']
set_field(record, 'owner', errors, username=data['owner']['name'])
set_field(record, 'domain', errors, name=data['domain']['name'])
try:
record.full_clean()
except Exception as e:
errors = dict(errors.items() + e.message_dict.items())
if len(errors) > 0:
return HttpResponse(json.dumps(errors), content_type='application/json', status=400)
record.save()
return HttpResponse(record.id)
@user_passes_test(req_staff)
def delete_entity(request, name, id):
model = {
'rules': Rule,
'hosts': Host,
'hostgroups': Group,
'vlans': Vlan,
'vlangroups': VlanGroup,
'firewalls': Firewall,
'domains': Domain,
'records': Record,
'blacklists': Blacklist
}[name]
model.objects.get(id=id).delete()
return HttpResponse('KTHXBYE')
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment