├── static ├── images │ ├── favicon.ico │ ├── flags32.png │ ├── sort_asc.png │ ├── sort_both.png │ ├── sort_desc.png │ ├── Sorting icons.psd │ ├── back_disabled.png │ ├── back_enabled.png │ ├── forward_disabled.png │ ├── forward_enabled.png │ ├── back_enabled_hover.png │ ├── sort_asc_disabled.png │ ├── sort_desc_disabled.png │ └── forward_enabled_hover.png ├── admin │ ├── img │ │ ├── icon-no.gif │ │ ├── icon-yes.gif │ │ ├── nav-bg.gif │ │ ├── default-bg.gif │ │ ├── icon_alert.gif │ │ ├── icon_clock.gif │ │ ├── icon_error.gif │ │ ├── changelist-bg.gif │ │ ├── icon-unknown.gif │ │ ├── icon_addlink.gif │ │ ├── icon_calendar.gif │ │ ├── icon_success.gif │ │ ├── inline-delete.png │ │ ├── sorting-icons.gif │ │ ├── tooltag-add.png │ │ ├── deleted-overlay.gif │ │ ├── icon_changelink.gif │ │ ├── icon_deletelink.gif │ │ ├── icon_searchbox.png │ │ ├── inline-restore.png │ │ ├── nav-bg-grabber.gif │ │ ├── nav-bg-reverse.gif │ │ ├── nav-bg-selected.gif │ │ ├── selector-icons.gif │ │ ├── selector-search.gif │ │ ├── changelist-bg_rtl.gif │ │ ├── default-bg-reverse.gif │ │ ├── gis │ │ │ ├── move_vertex_on.png │ │ │ └── move_vertex_off.png │ │ ├── inline-delete-8bit.png │ │ ├── inline-splitter-bg.gif │ │ ├── tooltag-arrowright.png │ │ └── inline-restore-8bit.png │ ├── js │ │ ├── jquery.init.js │ │ ├── prepopulate.min.js │ │ ├── collapse.min.js │ │ ├── related-widget-wrapper.js │ │ ├── collapse.js │ │ ├── LICENSE-JQUERY.txt │ │ ├── prepopulate.js │ │ ├── timeparse.js │ │ ├── actions.min.js │ │ ├── inlines.min.js │ │ ├── SelectBox.js │ │ ├── admin │ │ │ └── RelatedObjectLookups.js │ │ ├── actions.js │ │ ├── urlify.js │ │ ├── calendar.js │ │ ├── core.js │ │ └── SelectFilter2.js │ └── css │ │ ├── dashboard.css │ │ ├── login.css │ │ ├── ie.css │ │ ├── rtl.css │ │ ├── changelists.css │ │ └── forms.css ├── glyphicons-halflings.png ├── glyphicons-halflings-white.png ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff ├── js │ ├── npm.js │ ├── bootstrap-number-input.min.js │ ├── dataTables.bootstrap.min.js │ ├── sorttable.min.js │ └── bootstrap-select.min.js └── css │ ├── bootstrap-datetimepicker.min.css │ ├── bootstrap-datepicker.min.css │ ├── dataTables.tableTools.min.css │ └── dataTables.bootstrap.min.css ├── requirements.txt ├── .gitignore ├── templates ├── navbar.html ├── main │ ├── new_task.html │ ├── task.html │ └── tasks.html ├── registration │ └── login.html └── base.html ├── main ├── __init__.py ├── management │ ├── __init__.py │ └── commands │ │ └── __init__.py ├── migrations │ ├── __init__.py │ ├── 0002_auto_20150818_1536.py │ └── 0001_initial.py ├── templatetags │ ├── __init__.py │ └── certego_tags.py ├── tests.py ├── urls.py ├── forms.py ├── admin.py ├── views.py ├── models.py └── api.py ├── pcapoptikon ├── __init__.py ├── wsgi.py ├── urls.py ├── authorization.py ├── fields.py └── settings.py ├── manage.py ├── reload.sh ├── Dockerfile ├── nagios └── check_queue.py ├── start.sh └── README.md /static/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/images/favicon.ico -------------------------------------------------------------------------------- /static/images/flags32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/images/flags32.png -------------------------------------------------------------------------------- /static/images/sort_asc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/images/sort_asc.png -------------------------------------------------------------------------------- /static/admin/img/icon-no.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/icon-no.gif -------------------------------------------------------------------------------- /static/admin/img/icon-yes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/icon-yes.gif -------------------------------------------------------------------------------- /static/admin/img/nav-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/nav-bg.gif -------------------------------------------------------------------------------- /static/images/sort_both.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/images/sort_both.png -------------------------------------------------------------------------------- /static/images/sort_desc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/images/sort_desc.png -------------------------------------------------------------------------------- /static/admin/img/default-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/default-bg.gif -------------------------------------------------------------------------------- /static/admin/img/icon_alert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/icon_alert.gif -------------------------------------------------------------------------------- /static/admin/img/icon_clock.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/icon_clock.gif -------------------------------------------------------------------------------- /static/admin/img/icon_error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/icon_error.gif -------------------------------------------------------------------------------- /static/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/glyphicons-halflings.png -------------------------------------------------------------------------------- /static/images/Sorting icons.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/images/Sorting icons.psd -------------------------------------------------------------------------------- /static/images/back_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/images/back_disabled.png -------------------------------------------------------------------------------- /static/images/back_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/images/back_enabled.png -------------------------------------------------------------------------------- /static/admin/img/changelist-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/changelist-bg.gif -------------------------------------------------------------------------------- /static/admin/img/icon-unknown.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/icon-unknown.gif -------------------------------------------------------------------------------- /static/admin/img/icon_addlink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/icon_addlink.gif -------------------------------------------------------------------------------- /static/admin/img/icon_calendar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/icon_calendar.gif -------------------------------------------------------------------------------- /static/admin/img/icon_success.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/icon_success.gif -------------------------------------------------------------------------------- /static/admin/img/inline-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/inline-delete.png -------------------------------------------------------------------------------- /static/admin/img/sorting-icons.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/sorting-icons.gif -------------------------------------------------------------------------------- /static/admin/img/tooltag-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/tooltag-add.png -------------------------------------------------------------------------------- /static/images/forward_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/images/forward_disabled.png -------------------------------------------------------------------------------- /static/images/forward_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/images/forward_enabled.png -------------------------------------------------------------------------------- /static/admin/img/deleted-overlay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/deleted-overlay.gif -------------------------------------------------------------------------------- /static/admin/img/icon_changelink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/icon_changelink.gif -------------------------------------------------------------------------------- /static/admin/img/icon_deletelink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/icon_deletelink.gif -------------------------------------------------------------------------------- /static/admin/img/icon_searchbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/icon_searchbox.png -------------------------------------------------------------------------------- /static/admin/img/inline-restore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/inline-restore.png -------------------------------------------------------------------------------- /static/admin/img/nav-bg-grabber.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/nav-bg-grabber.gif -------------------------------------------------------------------------------- /static/admin/img/nav-bg-reverse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/nav-bg-reverse.gif -------------------------------------------------------------------------------- /static/admin/img/nav-bg-selected.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/nav-bg-selected.gif -------------------------------------------------------------------------------- /static/admin/img/selector-icons.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/selector-icons.gif -------------------------------------------------------------------------------- /static/admin/img/selector-search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/selector-search.gif -------------------------------------------------------------------------------- /static/images/back_enabled_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/images/back_enabled_hover.png -------------------------------------------------------------------------------- /static/images/sort_asc_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/images/sort_asc_disabled.png -------------------------------------------------------------------------------- /static/images/sort_desc_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/images/sort_desc_disabled.png -------------------------------------------------------------------------------- /static/admin/img/changelist-bg_rtl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/changelist-bg_rtl.gif -------------------------------------------------------------------------------- /static/admin/img/default-bg-reverse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/default-bg-reverse.gif -------------------------------------------------------------------------------- /static/admin/img/gis/move_vertex_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/gis/move_vertex_on.png -------------------------------------------------------------------------------- /static/admin/img/inline-delete-8bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/inline-delete-8bit.png -------------------------------------------------------------------------------- /static/admin/img/inline-splitter-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/inline-splitter-bg.gif -------------------------------------------------------------------------------- /static/admin/img/tooltag-arrowright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/tooltag-arrowright.png -------------------------------------------------------------------------------- /static/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /static/images/forward_enabled_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/images/forward_enabled_hover.png -------------------------------------------------------------------------------- /static/admin/img/gis/move_vertex_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/gis/move_vertex_off.png -------------------------------------------------------------------------------- /static/admin/img/inline-restore-8bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/admin/img/inline-restore-8bit.png -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/certego/pcapoptikon/HEAD/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.11.29 2 | django-tastypie==0.14.2 3 | django-widget-tweaks==1.4.1 4 | hexdump==3.2 5 | idstools==0.5.0 6 | ipython==3.2.1 7 | jsonfield==1.0.3 8 | MySQL-python==1.2.5 9 | python-dateutil==2.4.2 10 | python-mimeparse==0.1.4 11 | pytz==2015.4 12 | simplejson==3.8.0 13 | six==1.9.0 14 | wheel==0.24.0 15 | -------------------------------------------------------------------------------- /static/admin/js/jquery.init.js: -------------------------------------------------------------------------------- 1 | /* Puts the included jQuery into our own namespace using noConflict and passing 2 | * it 'true'. This ensures that the included jQuery doesn't pollute the global 3 | * namespace (i.e. this preserves pre-existing values for both window.$ and 4 | * window.jQuery). 5 | */ 6 | var django = django || {}; 7 | django.jQuery = jQuery.noConflict(true); 8 | -------------------------------------------------------------------------------- /static/admin/js/prepopulate.min.js: -------------------------------------------------------------------------------- 1 | (function(b){b.fn.prepopulate=function(e,g){return this.each(function(){var a=b(this),d=function(){if(!a.data("_changed")){var f=[];b.each(e,function(h,c){c=b(c);c.val().length>0&&f.push(c.val())});a.val(URLify(f.join(" "),g))}};a.data("_changed",false);a.change(function(){a.data("_changed",true)});a.val()||b(e.join(",")).keyup(d).change(d).focus(d)})}})(django.jQuery); 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Python byte code 2 | *.pyc 3 | 4 | # Ignore certificates 5 | *.pem 6 | *.cert 7 | 8 | # Ignore OS generated files 9 | .DS_Store* 10 | .AppleDouble 11 | ehthumbs.db 12 | Icon? 13 | Thumbs.db 14 | .directory 15 | 16 | # Ignore development files 17 | docs/book/src/_build/ 18 | .idea/ 19 | 20 | # Ignore Django secret_key 21 | pcapoptikon/secret_key.py 22 | 23 | # Ignore others files 24 | .settings/ 25 | .project 26 | .pydevproject 27 | *.gz 28 | *.swp 29 | .ropeproject/ 30 | -------------------------------------------------------------------------------- /static/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /static/admin/css/dashboard.css: -------------------------------------------------------------------------------- 1 | /* DASHBOARD */ 2 | 3 | .dashboard .module table th { 4 | width: 100%; 5 | } 6 | 7 | .dashboard .module table td { 8 | white-space: nowrap; 9 | } 10 | 11 | .dashboard .module table td a { 12 | display: block; 13 | padding-right: .6em; 14 | } 15 | 16 | /* RECENT ACTIONS MODULE */ 17 | 18 | .module ul.actionlist { 19 | margin-left: 0; 20 | } 21 | 22 | ul.actionlist li { 23 | list-style-type: none; 24 | } 25 | 26 | ul.actionlist li { 27 | overflow: hidden; 28 | text-overflow: ellipsis; 29 | -o-text-overflow: ellipsis; 30 | } 31 | -------------------------------------------------------------------------------- /static/admin/js/collapse.min.js: -------------------------------------------------------------------------------- 1 | (function(a){a(document).ready(function(){a("fieldset.collapse").each(function(c,b){a(b).find("div.errors").length==0&&a(b).addClass("collapsed").find("h2").first().append(' ('+gettext("Show")+")")});a("fieldset.collapse a.collapse-toggle").click(function(){a(this).closest("fieldset").hasClass("collapsed")?a(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset",[a(this).attr("id")]):a(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", 2 | [a(this).attr("id")]);return false})})})(django.jQuery); 3 | -------------------------------------------------------------------------------- /templates/navbar.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /main/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # __init__.py 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | -------------------------------------------------------------------------------- /pcapoptikon/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # __init__.py 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | -------------------------------------------------------------------------------- /main/management/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # __init__.py 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | -------------------------------------------------------------------------------- /main/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # __init__.py 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | -------------------------------------------------------------------------------- /main/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # __init__.py 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | -------------------------------------------------------------------------------- /main/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # __init__.py 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | -------------------------------------------------------------------------------- /main/tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # tests.py 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | from django.test import TestCase 23 | 24 | # Create your tests here. 25 | -------------------------------------------------------------------------------- /static/admin/js/related-widget-wrapper.js: -------------------------------------------------------------------------------- 1 | django.jQuery(function($){ 2 | function updateLinks() { 3 | var $this = $(this); 4 | var siblings = $this.nextAll('.change-related, .delete-related'); 5 | if (!siblings.length) return; 6 | var value = $this.val(); 7 | if (value) { 8 | siblings.each(function(){ 9 | var elm = $(this); 10 | elm.attr('href', elm.attr('data-href-template').replace('__fk__', value)); 11 | }); 12 | } else siblings.removeAttr('href'); 13 | } 14 | var container = $(document); 15 | container.on('change', '.related-widget-wrapper select', updateLinks); 16 | container.find('.related-widget-wrapper select').each(updateLinks); 17 | container.on('click', '.related-widget-wrapper-link', function(event){ 18 | if (this.href) { 19 | showRelatedObjectPopup(this); 20 | } 21 | event.preventDefault(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /static/admin/js/collapse.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | $(document).ready(function() { 3 | // Add anchor tag for Show/Hide link 4 | $("fieldset.collapse").each(function(i, elem) { 5 | // Don't hide if fields in this fieldset have errors 6 | if ($(elem).find("div.errors").length == 0) { 7 | $(elem).addClass("collapsed").find("h2").first().append(' (' + gettext("Show") + 9 | ')'); 10 | } 11 | }); 12 | // Add toggle to anchor tag 13 | $("fieldset.collapse a.collapse-toggle").click(function(ev) { 14 | if ($(this).closest("fieldset").hasClass("collapsed")) { 15 | // Show 16 | $(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset", [$(this).attr("id")]); 17 | } else { 18 | // Hide 19 | $(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", [$(this).attr("id")]); 20 | } 21 | return false; 22 | }); 23 | }); 24 | })(django.jQuery); 25 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # manage.py 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | import os 23 | import sys 24 | 25 | if __name__ == "__main__": 26 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pcapoptikon.settings") 27 | 28 | from django.core.management import execute_from_command_line 29 | 30 | execute_from_command_line(sys.argv) 31 | -------------------------------------------------------------------------------- /main/urls.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # urls.py 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | from django.conf.urls import url 23 | 24 | from main import views 25 | 26 | urlpatterns = [ 27 | url(r'^task/((?P\d+)/)?$', views.task, name='task'), 28 | url(r'^new_task/$', views.new_task, name='new_task'), 29 | url(r'^$', views.tasks, name='tasks'), 30 | ] 31 | -------------------------------------------------------------------------------- /static/js/bootstrap-number-input.min.js: -------------------------------------------------------------------------------- 1 | (function(e){e.fn.bootstrapNumber=function(t){var n=e.extend({upClass:"default",downClass:"default",center:true},t);return this.each(function(t){function u(e){if(s&&eo){return false}i.val(e);return true}var r=e(this);var i=r.clone();var s=r.attr("min");var o=r.attr("max");var a=e("
");var f=e("").attr("class","btn btn-"+n.downClass).click(function(){u(parseInt(i.val())-1)});var l=e("").attr("class","btn btn-"+n.upClass).click(function(){u(parseInt(i.val())+1)});e("").append(f).appendTo(a);i.appendTo(a);if(i){i.css("text-align","center")}e("").append(l).appendTo(a);i.attr("type","text").keydown(function(t){if(e.inArray(t.keyCode,[46,8,9,27,13,110,190])!==-1||t.keyCode==65&&t.ctrlKey===true||t.keyCode>=35&&t.keyCode<=39){return}if((t.shiftKey||t.keyCode<48||t.keyCode>57)&&(t.keyCode<96||t.keyCode>105)){t.preventDefault()}var n=String.fromCharCode(t.which);var r=parseInt(i.val()+n);if(s&&ro){t.preventDefault()}});r.replaceWith(a)})}})(jQuery) -------------------------------------------------------------------------------- /static/admin/js/LICENSE-JQUERY.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 John Resig, http://jquery.com/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /main/forms.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # forms.py 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | from django import forms 23 | from main.models import * 24 | 25 | class TaskForm(forms.ModelForm): 26 | class Meta: 27 | model = Task 28 | exclude = [ 29 | 'user', 30 | 'submitted_on', 31 | 'status', 32 | 'results_dir', 33 | 'results' 34 | ] 35 | -------------------------------------------------------------------------------- /static/admin/css/login.css: -------------------------------------------------------------------------------- 1 | /* LOGIN FORM */ 2 | 3 | body.login { 4 | background: #eee; 5 | } 6 | 7 | .login #container { 8 | background: white; 9 | border: 1px solid #ccc; 10 | width: 28em; 11 | min-width: 300px; 12 | margin-left: auto; 13 | margin-right: auto; 14 | margin-top: 100px; 15 | } 16 | 17 | .login #content-main { 18 | width: 100%; 19 | } 20 | 21 | .login form { 22 | margin-top: 1em; 23 | } 24 | 25 | .login .form-row { 26 | padding: 4px 0; 27 | float: left; 28 | width: 100%; 29 | } 30 | 31 | .login .form-row label { 32 | padding-right: 0.5em; 33 | line-height: 2em; 34 | font-size: 1em; 35 | clear: both; 36 | color: #333; 37 | } 38 | 39 | .login .form-row #id_username, .login .form-row #id_password { 40 | clear: both; 41 | padding: 6px; 42 | width: 100%; 43 | -webkit-box-sizing: border-box; 44 | -moz-box-sizing: border-box; 45 | box-sizing: border-box; 46 | } 47 | 48 | .login span.help { 49 | font-size: 10px; 50 | display: block; 51 | } 52 | 53 | .login .submit-row { 54 | clear: both; 55 | padding: 1em 0 0 9.4em; 56 | } 57 | 58 | .login .password-reset-link { 59 | text-align: center; 60 | } 61 | -------------------------------------------------------------------------------- /pcapoptikon/wsgi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # wsgi.py 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | """ 23 | WSGI config for pcapoptikon project. 24 | 25 | It exposes the WSGI callable as a module-level variable named ``application``. 26 | 27 | For more information on this file, see 28 | https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/ 29 | """ 30 | 31 | import os 32 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pcapoptikon.settings") 33 | 34 | from django.core.wsgi import get_wsgi_application 35 | application = get_wsgi_application() 36 | -------------------------------------------------------------------------------- /static/admin/css/ie.css: -------------------------------------------------------------------------------- 1 | /* IE 6 & 7 */ 2 | 3 | /* Proper fixed width for dashboard in IE6 */ 4 | 5 | .dashboard #content { 6 | *width: 768px; 7 | } 8 | 9 | .dashboard #content-main { 10 | *width: 535px; 11 | } 12 | 13 | /* IE 6 ONLY */ 14 | 15 | /* Keep header from flowing off the page */ 16 | 17 | #container { 18 | _position: static; 19 | } 20 | 21 | /* Put the right sidebars back on the page */ 22 | 23 | .colMS #content-related { 24 | _margin-right: 0; 25 | _margin-left: 10px; 26 | _position: static; 27 | } 28 | 29 | /* Put the left sidebars back on the page */ 30 | 31 | .colSM #content-related { 32 | _margin-right: 10px; 33 | _margin-left: -115px; 34 | _position: static; 35 | } 36 | 37 | .form-row { 38 | _height: 1%; 39 | } 40 | 41 | /* Fix right margin for changelist filters in IE6 */ 42 | 43 | #changelist-filter ul { 44 | _margin-right: -10px; 45 | } 46 | 47 | /* IE ignores min-height, but treats height as if it were min-height */ 48 | 49 | .change-list .filtered { 50 | _height: 400px; 51 | } 52 | 53 | /* IE doesn't know alpha transparency in PNGs */ 54 | 55 | .inline-deletelink { 56 | background: transparent url(../img/inline-delete-8bit.png) no-repeat; 57 | } 58 | 59 | /* IE7 doesn't support inline-block */ 60 | .change-list ul.toplinks li { 61 | zoom: 1; 62 | *display: inline; 63 | } 64 | -------------------------------------------------------------------------------- /main/migrations/0002_auto_20150818_1536.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # 0002_auto_20150818_1536.py 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | from __future__ import unicode_literals 23 | 24 | from django.db import models, migrations 25 | from django.conf import settings 26 | 27 | 28 | class Migration(migrations.Migration): 29 | 30 | dependencies = [ 31 | ('main', '0001_initial'), 32 | ] 33 | 34 | operations = [ 35 | migrations.AlterField( 36 | model_name='task', 37 | name='user', 38 | field=models.ForeignKey(default=None, blank=True, to=settings.AUTH_USER_MODEL, null=True), 39 | ), 40 | ] 41 | -------------------------------------------------------------------------------- /main/templatetags/certego_tags.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # certego_tags.py 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | import hexdump 23 | from django.template.defaultfilters import register 24 | 25 | 26 | @register.filter("b64decode_hexdump") 27 | def b64decode_hexdump(value, default=""): 28 | if isinstance(value, unicode): 29 | return hexdump.hexdump( 30 | value.decode('base64'), 31 | result='return' 32 | ) 33 | elif isinstance(value, str): 34 | return hexdump.hexdump( 35 | unicode(value).decode('base64'), 36 | result='return' 37 | ) 38 | else: 39 | return default 40 | -------------------------------------------------------------------------------- /templates/main/new_task.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load widget_tweaks %} 3 | {% block title %}New task{% endblock %} 4 | {% block navbar %}{% include 'navbar.html' %}{% endblock %} 5 | {% block content %} 6 |
7 | {% csrf_token %} 8 | {% for key, error in form.errors.items %} 9 | 10 | {% endfor %} 11 | {% for error in form.non_field_errors %} 12 | 13 | {% endfor %} 14 |
15 |
16 |

New task

17 |
18 |
19 | {% for error in form.pcap_file.errors %} 20 | 21 | {% endfor %} 22 |
23 | {{ form.pcap_file.label_tag }} 24 | {{ form.pcap_file }} 25 |
26 |
27 |
28 | 29 |
30 | {% endblock %} 31 | {% block bottomscripts %} 32 | {% endblock %} 33 | {% block customcss %} 34 | {% endblock %} -------------------------------------------------------------------------------- /static/admin/js/prepopulate.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | $.fn.prepopulate = function(dependencies, maxLength) { 3 | /* 4 | Depends on urlify.js 5 | Populates a selected field with the values of the dependent fields, 6 | URLifies and shortens the string. 7 | dependencies - array of dependent fields ids 8 | maxLength - maximum length of the URLify'd string 9 | */ 10 | return this.each(function() { 11 | var prepopulatedField = $(this); 12 | 13 | var populate = function () { 14 | // Bail if the field's value has been changed by the user 15 | if (prepopulatedField.data('_changed')) { 16 | return; 17 | } 18 | 19 | var values = []; 20 | $.each(dependencies, function(i, field) { 21 | field = $(field); 22 | if (field.val().length > 0) { 23 | values.push(field.val()); 24 | } 25 | }); 26 | prepopulatedField.val(URLify(values.join(' '), maxLength)); 27 | }; 28 | 29 | prepopulatedField.data('_changed', false); 30 | prepopulatedField.change(function() { 31 | prepopulatedField.data('_changed', true); 32 | }); 33 | 34 | if (!prepopulatedField.val()) { 35 | $(dependencies.join(',')).keyup(populate).change(populate).focus(populate); 36 | } 37 | }); 38 | }; 39 | })(django.jQuery); 40 | -------------------------------------------------------------------------------- /pcapoptikon/urls.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # urls.py 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | from django.conf import settings 23 | from django.conf.urls import url, include 24 | from django.conf.urls.static import static 25 | from django.contrib import admin 26 | from django.contrib.auth.views import login, logout_then_login 27 | admin.autodiscover() 28 | 29 | from tastypie.api import Api 30 | from main.api import * 31 | 32 | v1_api = Api(api_name='v1') 33 | v1_api.register(TaskResource()) 34 | 35 | urlpatterns = [ 36 | # Admin views 37 | url(r'^admin/', admin.site.urls), 38 | url(r'^accounts/login/$', login, name="login"), 39 | url(r'^accounts/logout/$', logout_then_login, name="logout"), 40 | 41 | # APIs 42 | url(r'^api/', include(v1_api.urls)), 43 | 44 | # Other views 45 | url(r'', include('main.urls', namespace="main")), 46 | ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 47 | -------------------------------------------------------------------------------- /main/admin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # admin.py 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | from tastypie.admin import ApiKeyInline 23 | from tastypie.models import ApiAccess, ApiKey 24 | from django.contrib.auth.admin import UserAdmin 25 | from django.contrib.auth.models import User 26 | from django.contrib import admin 27 | from .models import Task 28 | 29 | 30 | class TaskAdmin(admin.ModelAdmin): 31 | list_display = ['id', 'pcap_file', 'submitted_on', 'user', 'status', 'results_dir'] 32 | list_editable = ['user', 'status'] 33 | list_filter = ['user', 'status'] 34 | search_fields = ['pcap_file', 'results'] 35 | date_hierarchy = 'submitted_on' 36 | 37 | class UserModelAdmin(UserAdmin): 38 | inlines = UserAdmin.inlines + [ApiKeyInline] 39 | 40 | #admin.site.register(ApiKey) 41 | admin.site.register(ApiAccess) 42 | admin.site.unregister(User) 43 | admin.site.register(User,UserModelAdmin) 44 | admin.site.register(Task, TaskAdmin) 45 | -------------------------------------------------------------------------------- /static/js/dataTables.bootstrap.min.js: -------------------------------------------------------------------------------- 1 | !function(){var e=function(e,t){"use strict";e.extend(!0,t.defaults,{dom:"<'row'<'col-sm-6'l><'col-sm-6'f>><'row'<'col-sm-12'tr>><'row'<'col-sm-6'i><'col-sm-6'p>>",renderer:"bootstrap"}),e.extend(t.ext.classes,{sWrapper:"dataTables_wrapper form-inline dt-bootstrap",sFilterInput:"form-control input-sm",sLengthSelect:"form-control input-sm"}),t.ext.renderer.pageButton.bootstrap=function(a,n,o,s,r,l){var i,d,c=new t.Api(a),u=a.oClasses,b=a.oLanguage.oPaginate,p=function(t,n){var s,f,T,m,g=function(t){t.preventDefault(),e(t.currentTarget).hasClass("disabled")||c.page(t.data.action).draw(!1)};for(s=0,f=n.length;f>s;s++)if(m=n[s],e.isArray(m))p(t,m);else{switch(i="",d="",m){case"ellipsis":i="…",d="disabled";break;case"first":i=b.sFirst,d=m+(r>0?"":" disabled");break;case"previous":i=b.sPrevious,d=m+(r>0?"":" disabled");break;case"next":i=b.sNext,d=m+(l-1>r?"":" disabled");break;case"last":i=b.sLast,d=m+(l-1>r?"":" disabled");break;default:i=m+1,d=r===m?"active":""}i&&(T=e("
  • ",{"class":u.sPageButton+" "+d,"aria-controls":a.sTableId,tabindex:a.iTabIndex,id:0===o&&"string"==typeof m?a.sTableId+"_"+m:null}).append(e("",{href:"#"}).html(i)).appendTo(t),a.oApi._fnBindAction(T,{action:m},g))}};p(e(n).empty().html('
      ').children("ul"),s)},t.TableTools&&(e.extend(!0,t.TableTools.classes,{container:"DTTT btn-group",buttons:{normal:"btn btn-default",disabled:"disabled"},collection:{container:"DTTT_dropdown dropdown-menu",buttons:{normal:"",disabled:"disabled"}},print:{info:"DTTT_print_info"},select:{row:"active"}}),e.extend(!0,t.TableTools.DEFAULTS.oTags,{collection:{container:"ul",button:"li",liner:"a"}}))};"function"==typeof define&&define.amd?define(["jquery","datatables"],e):"object"==typeof exports?e(require("jquery"),require("datatables")):jQuery&&e(jQuery,jQuery.fn.dataTable)}(window,document); -------------------------------------------------------------------------------- /reload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # reload.sh 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | 23 | function log() { 24 | echo "[$(date +'%y-%m-%d %H:%M:%S')] $@" 25 | } 26 | 27 | # Send all output (stdout + stderr) to a log file 28 | exec > >(tee /var/log/pcapoptikon_reload.log) 29 | exec 2>&1 30 | 31 | # Update sid-msg.map 32 | log "Updating sid-msg.map" 33 | /usr/share/oinkmaster/create-sidmap.pl /etc/suricata/rules > /etc/suricata/rules/sid-msg.map 34 | 35 | log "Stopping pcapoptikon's run_daemon and deleting pid file" 36 | kill $(cat /var/run/pcapoptikon-daemon.pid) 37 | sleep 5 38 | rm -f /var/run/pcapoptikon-daemon.pid 39 | 40 | log "Stopping suricata and deleting pid file" 41 | kill $(cat /var/run/suricata.pid) 42 | sleep 5 43 | rm -f /var/run/suricata.pid 44 | 45 | log "Starting up suricata and run_daemon again" 46 | /usr/bin/suricata -c /etc/suricata/suricata.yaml --unix-socket --pidfile /var/run/suricata.pid >/dev/null 2>&1 & 47 | /usr/bin/python /opt/pcapoptikon/manage.py run_daemon >/var/log/pcapoptikon_daemon.log 2>&1 & 48 | log $! > /var/run/pcapoptikon-daemon.pid 49 | 50 | log "Signatures reloaded" 51 | -------------------------------------------------------------------------------- /pcapoptikon/authorization.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # authorization.py 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | from django.contrib.auth.models import User 23 | from tastypie.authorization import DjangoAuthorization 24 | from tastypie.exceptions import Unauthorized 25 | 26 | class CertegoDjangoAuthorization(DjangoAuthorization): 27 | def read_list(self, object_list, bundle): 28 | """ 29 | Super-users can view the whole list; the others will only see their own tasks 30 | """ 31 | user = bundle.request.user 32 | if not user.is_superuser: 33 | object_list = object_list.filter(user=bundle.request.user) 34 | 35 | return super(CertegoDjangoAuthorization, self).read_list(object_list, bundle) 36 | 37 | def read_detail(self, object_list, bundle): 38 | """ 39 | Super-users can read any task; the others will only see their own 40 | """ 41 | user = bundle.request.user 42 | 43 | if not user.is_superuser: 44 | object_list = object_list.filter(user=bundle.request.user) 45 | 46 | return super(CertegoDjangoAuthorization, self).read_detail(object_list, bundle) 47 | -------------------------------------------------------------------------------- /templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load widget_tweaks %} 3 | {% block title %}Login{% endblock %} 4 | {% block content %} 5 |
      6 | {% csrf_token %} 7 |
      8 |
      9 |
      10 |
      11 |

      Login

      12 |
      13 |
      14 | {% if form.errors %} 15 |
      16 |
      17 | 20 |
      21 |
      22 | {% endif %} 23 |
      24 |
      25 | {{ form.username.label_tag }} 26 | {% render_field form.username class+="form-control" %} 27 |
      28 |
      29 | {{ form.password.label_tag }} 30 | {% render_field form.password class+="form-control" %} 31 |
      32 |
      33 |
      34 |
      35 | 36 |
      37 |
      38 |
      39 |
      40 |
      41 |
      42 | 43 |
      44 | {% endblock %} -------------------------------------------------------------------------------- /main/views.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # views.py 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | from django.shortcuts import render, get_object_or_404 23 | from django.http import HttpResponseRedirect 24 | from django.contrib.auth.decorators import login_required 25 | from django.core.urlresolvers import reverse 26 | from main.models import * 27 | from main.forms import * 28 | 29 | @login_required 30 | def tasks(request): 31 | context = { 32 | 'active_tab': 'tasks', 33 | } 34 | 35 | return render(request, 'main/tasks.html', context) 36 | 37 | @login_required 38 | def new_task(request): 39 | context = { 40 | 'active_tab': 'new_task', 41 | } 42 | 43 | if request.method == 'POST': 44 | context['form'] = TaskForm(request.POST or None, request.FILES) 45 | if context['form'].is_valid(): 46 | saved_task = context['form'].save(commit=False) 47 | saved_task.user = request.user 48 | saved_task.save() 49 | context['form'].save_m2m() 50 | 51 | return HttpResponseRedirect( 52 | reverse('main:tasks') 53 | ) 54 | else: 55 | context['form'] = TaskForm(None) 56 | 57 | return render(request, 'main/new_task.html', context) 58 | 59 | @login_required 60 | def task(request, task_id=None): 61 | context = { 62 | 'active_tab': 'tasks', 63 | 'task': get_object_or_404(Task, pk=task_id), 64 | } 65 | 66 | return render(request, 'main/task.html', context) 67 | -------------------------------------------------------------------------------- /main/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # 0001_initial.py 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | from __future__ import unicode_literals 23 | 24 | from django.db import models, migrations 25 | from django.conf import settings 26 | import main.models 27 | import jsonfield.fields 28 | 29 | 30 | class Migration(migrations.Migration): 31 | 32 | dependencies = [ 33 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 34 | ] 35 | 36 | operations = [ 37 | migrations.CreateModel( 38 | name='Task', 39 | fields=[ 40 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 41 | ('pcap_file', models.FileField(upload_to=b'extracted_files', max_length=512, verbose_name=b'PCAP File')), 42 | ('submitted_on', models.DateTimeField(default=main.models.add_now, verbose_name=b'Added on', blank=True)), 43 | ('status', models.IntegerField(default=0, blank=True, verbose_name=b'Task status', choices=[(-1, b'Failed'), (0, b'In queue'), (1, b'Processing'), (2, b'Queued'), (3, b'No further action required (completed or discarded)')])), 44 | ('results_dir', models.CharField(default=None, max_length=512, null=True, verbose_name=b'Temporary results dir', blank=True)), 45 | ('results', jsonfield.fields.JSONField(default=None, null=True, verbose_name=b'Results', blank=True)), 46 | ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), 47 | ], 48 | ), 49 | ] 50 | -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | 5 | 6 | PCAPOptikon: {% block title %}{% endblock %} 7 | 8 | {% block customheaders %}{% endblock %} 9 | 10 | 11 | 12 | 48 | 49 | 50 |
      62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # This is a Dockerfile for creating a PcapOptikon Container from the latest 2 | # Ubuntu base image. This is known bo be working on Ubuntu 14.04. It should work on any later version 3 | # This is a full installation of PcapOptikon including all the dependencies and Suricata. 4 | # The container will run Oinkmaster every 24 hours to update the Emergint Threats Open ruleset. 5 | FROM ubuntu:14.04 6 | MAINTAINER p.delsante@certego.net 7 | ENV LC_ALL C 8 | ENV DEBIAN_FRONTEND noninteractive 9 | EXPOSE 8000 10 | 11 | RUN apt-get update && apt-get -y dist-upgrade && apt-get -y install \ 12 | cpanminus \ 13 | expect \ 14 | git \ 15 | libmysqlclient-dev \ 16 | libssl-dev \ 17 | mysql-common \ 18 | mysql-client \ 19 | mysql-server \ 20 | oinkmaster \ 21 | python-dev \ 22 | python-mysqldb \ 23 | python-software-properties \ 24 | python-pip \ 25 | software-properties-common 26 | 27 | RUN add-apt-repository -y ppa:oisf/suricata-stable && apt-get update && apt-get -y install suricata 28 | 29 | RUN sed -ir '/^# more information.$/ a\ 30 | url = http://rules.emergingthreats.net/open/suricata/emerging.rules.tar.gz' /etc/oinkmaster.conf 31 | 32 | RUN sed -ir 's|^classification-file: /etc/suricata/classification.config$|classification-file: /etc/suricata/rules/classification.config|' /etc/suricata/suricata.yaml 33 | RUN sed -ir 's|^reference-file: /etc/suricata/reference.config$|reference-file: /etc/suricata/rules/reference.config|' /etc/suricata/suricata.yaml 34 | RUN sed -ir 's|#- rule-reload: true|- rule-reload: true|' /etc/suricata/suricata.yaml 35 | RUN sed -ir 's|^ checksum-valdation: yes| checksum-valdation: no|' /etc/suricata/suricata.yaml 36 | RUN sed -ir 's|^ checksum-checks: auto| checksum-checks: no|' /etc/suricata/suricata.yaml 37 | RUN touch /etc/suricata/threshold.config 38 | RUN mkdir -p /opt/pcapoptikon 39 | 40 | ADD requirements.txt /opt/pcapoptikon/requirements.txt 41 | RUN pip install -r /opt/pcapoptikon/requirements.txt 42 | 43 | ADD . /opt/pcapoptikon 44 | 45 | RUN service mysql start && \ 46 | mysqladmin create pcapoptikon && \ 47 | cd /opt/pcapoptikon/ && \ 48 | python manage.py migrate --noinput && \ 49 | echo "from django.contrib.auth.models import User; User.objects.create_superuser('admin', 'admin@example.com', 'admin')" | python manage.py shell 50 | 51 | VOLUME ["/var/lib/mysql"] 52 | 53 | CMD ["/opt/pcapoptikon/start.sh"] 54 | -------------------------------------------------------------------------------- /templates/main/task.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load certego_tags %} 3 | {% block title %}Task results{% endblock %} 4 | {% block navbar %}{% include 'navbar.html' %}{% endblock %} 5 | {% block breadcrumb %} 6 | 12 | {% endblock %} 13 | {% block content %} 14 |
      15 | {% if task.results %} 16 | {% for event in task.results %} 17 | 18 | {% if event.priority >= 3 %} 19 |
      20 | {% elif event.priority == 2 %} 21 |
      22 | {% elif event.priority <= 1 %} 23 |
      24 | {% endif %} 25 | [{{event.generator_id}}:{{event.signature_id}}:{{event.signature_revision}}] {{event.msg}} [Classification: {{event.classification}}] [Priority: {{event.priority}}]
      26 |
      27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | {% for packet in event.packets %} 42 | 43 | 44 | 45 | 46 | {% endfor %} 47 | 48 |
      Protocol:{{event.protocol}}
      Source:{{event.source_ip}}:{{event.sport_itype}}
      Destination:{{event.destination_ip}}:{{event.dport_icode}}
      Packet #{{forloop.counter}}
      {{packet.data|b64decode_hexdump}}
      49 |
      50 | {% endfor %} 51 | {% else %} 52 |

      No signatures

      53 | {% endif %} 54 |
      55 | {% endblock %} 56 | {% block bottomscripts %} 57 | {% endblock %} 58 | {% block customcss %} 59 | {% endblock %} -------------------------------------------------------------------------------- /main/models.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # models.py 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | import collections 23 | import jsonfield 24 | import pytz 25 | from datetime import datetime 26 | from django.db import models 27 | from django.contrib.auth.models import User 28 | from django.conf import settings 29 | from tastypie.models import create_api_key 30 | 31 | 32 | models.signals.post_save.connect(create_api_key, sender=User) 33 | 34 | # add_now() is used to create auto datetime fields with the correct timezone 35 | def add_now(): 36 | return datetime.now(pytz.timezone(settings.TIME_ZONE)) 37 | 38 | class Task(models.Model): 39 | STATUS_FAILED = -1 # Failed 40 | STATUS_NEW = 0 # In queue 41 | STATUS_PROCESSING = 1 # Processing 42 | STATUS_QUEUED = 2 # Processing 43 | STATUS_DONE = 3 # No further action required (completed or discarded) 44 | 45 | STATUS_CHOICES = ( 46 | (STATUS_FAILED, 'Failed'), 47 | (STATUS_NEW, 'In queue'), 48 | (STATUS_PROCESSING, 'Processing'), 49 | (STATUS_QUEUED, 'Queued'), 50 | (STATUS_DONE, 'No further action required (completed or discarded)'), 51 | ) 52 | 53 | pcap_file = models.FileField('PCAP File', upload_to='extracted_files', max_length=512, blank=False, null=False) 54 | submitted_on = models.DateTimeField('Added on', default=add_now, null=False, blank=True) 55 | user = models.ForeignKey(User, null=True, blank=True, default=None) 56 | status = models.IntegerField('Task status', default=STATUS_NEW, null=False, blank=True, choices=STATUS_CHOICES) 57 | results_dir = models.CharField('Temporary results dir', max_length=512, blank=True, null=True, default=None) 58 | results = jsonfield.JSONField('Results', null=True, blank=True, default=None) 59 | -------------------------------------------------------------------------------- /static/admin/js/timeparse.js: -------------------------------------------------------------------------------- 1 | var timeParsePatterns = [ 2 | // 9 3 | { re: /^\d{1,2}$/i, 4 | handler: function(bits) { 5 | if (bits[0].length == 1) { 6 | return '0' + bits[0] + ':00'; 7 | } else { 8 | return bits[0] + ':00'; 9 | } 10 | } 11 | }, 12 | // 13:00 13 | { re: /^\d{2}[:.]\d{2}$/i, 14 | handler: function(bits) { 15 | return bits[0].replace('.', ':'); 16 | } 17 | }, 18 | // 9:00 19 | { re: /^\d[:.]\d{2}$/i, 20 | handler: function(bits) { 21 | return '0' + bits[0].replace('.', ':'); 22 | } 23 | }, 24 | // 3 am / 3 a.m. / 3am 25 | { re: /^(\d+)\s*([ap])(?:.?m.?)?$/i, 26 | handler: function(bits) { 27 | var hour = parseInt(bits[1]); 28 | if (hour == 12) { 29 | hour = 0; 30 | } 31 | if (bits[2].toLowerCase() == 'p') { 32 | if (hour == 12) { 33 | hour = 0; 34 | } 35 | return (hour + 12) + ':00'; 36 | } else { 37 | if (hour < 10) { 38 | return '0' + hour + ':00'; 39 | } else { 40 | return hour + ':00'; 41 | } 42 | } 43 | } 44 | }, 45 | // 3.30 am / 3:15 a.m. / 3.00am 46 | { re: /^(\d+)[.:](\d{2})\s*([ap]).?m.?$/i, 47 | handler: function(bits) { 48 | var hour = parseInt(bits[1]); 49 | var mins = parseInt(bits[2]); 50 | if (mins < 10) { 51 | mins = '0' + mins; 52 | } 53 | if (hour == 12) { 54 | hour = 0; 55 | } 56 | if (bits[3].toLowerCase() == 'p') { 57 | if (hour == 12) { 58 | hour = 0; 59 | } 60 | return (hour + 12) + ':' + mins; 61 | } else { 62 | if (hour < 10) { 63 | return '0' + hour + ':' + mins; 64 | } else { 65 | return hour + ':' + mins; 66 | } 67 | } 68 | } 69 | }, 70 | // noon 71 | { re: /^no/i, 72 | handler: function(bits) { 73 | return '12:00'; 74 | } 75 | }, 76 | // midnight 77 | { re: /^mid/i, 78 | handler: function(bits) { 79 | return '00:00'; 80 | } 81 | } 82 | ]; 83 | 84 | function parseTimeString(s) { 85 | for (var i = 0; i < timeParsePatterns.length; i++) { 86 | var re = timeParsePatterns[i].re; 87 | var handler = timeParsePatterns[i].handler; 88 | var bits = re.exec(s); 89 | if (bits) { 90 | return handler(bits); 91 | } 92 | } 93 | return s; 94 | } 95 | -------------------------------------------------------------------------------- /pcapoptikon/fields.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # fields.py 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | import base64 23 | import os 24 | import mimetypes 25 | from tastypie.fields import FileField 26 | from django.core.files.uploadedfile import SimpleUploadedFile 27 | 28 | class Base64FileField(FileField): 29 | """ 30 | https://gist.github.com/klipstein/709890 31 | 32 | A django-tastypie field for handling file-uploads through raw post data. 33 | It uses base64 for en-/decoding the contents of the file. 34 | Usage: 35 | 36 | class MyResource(ModelResource): 37 | file_field = Base64FileField("file_field") 38 | 39 | class Meta: 40 | queryset = ModelWithFileField.objects.all() 41 | 42 | In the case of multipart for submission, it would also pass the filename. 43 | By using a raw post data stream, we have to pass the filename within our 44 | file_field structure: 45 | 46 | file_field = { 47 | "name": "myfile.png", 48 | "file": "longbase64encodedstring", 49 | "content_type": "image/png" # on hydrate optional 50 | } 51 | """ 52 | def dehydrate(self, bundle, for_list): 53 | if not bundle.data.has_key(self.instance_name) and hasattr(bundle.obj, self.instance_name): 54 | file_field = getattr(bundle.obj, self.instance_name) 55 | if file_field: 56 | try: 57 | content_type, encoding = mimetypes.guess_type(file_field.file.name) 58 | b64 = open(file_field.file.name, "rb").read().encode("base64") 59 | ret = { 60 | "name": os.path.basename(file_field.file.name), 61 | "file": b64, 62 | "content-type": content_type or "application/octet-stream" 63 | } 64 | return ret 65 | except: 66 | pass 67 | return None 68 | 69 | def hydrate(self, obj): 70 | value = super(FileField, self).hydrate(obj) 71 | if value: 72 | value = SimpleUploadedFile(value["name"], base64.b64decode(value["file"]), getattr(value, "content_type", "application/octet-stream")) 73 | return value 74 | -------------------------------------------------------------------------------- /static/admin/js/actions.min.js: -------------------------------------------------------------------------------- 1 | (function(a){var f;a.fn.actions=function(q){var b=a.extend({},a.fn.actions.defaults,q),g=a(this),e=!1,m=function(c){c?k():l();a(g).prop("checked",c).parent().parent().toggleClass(b.selectedClass,c)},h=function(){var c=a(g).filter(":checked").length;a(b.counterContainer).html(interpolate(ngettext("%(sel)s of %(cnt)s selected","%(sel)s of %(cnt)s selected",c),{sel:c,cnt:_actions_icnt},!0));a(b.allToggle).prop("checked",function(){var a;c==g.length?(a=!0,k()):(a=!1,n());return a})},k=function(){a(b.acrossClears).hide(); 2 | a(b.acrossQuestions).show();a(b.allContainer).hide()},p=function(){a(b.acrossClears).show();a(b.acrossQuestions).hide();a(b.actionContainer).toggleClass(b.selectedClass);a(b.allContainer).show();a(b.counterContainer).hide()},l=function(){a(b.acrossClears).hide();a(b.acrossQuestions).hide();a(b.allContainer).hide();a(b.counterContainer).show()},n=function(){l();a(b.acrossInput).val(0);a(b.actionContainer).removeClass(b.selectedClass)};a(b.counterContainer).show();a(this).filter(":checked").each(function(c){a(this).parent().parent().toggleClass(b.selectedClass); 3 | h();1==a(b.acrossInput).val()&&p()});a(b.allToggle).show().click(function(){m(a(this).prop("checked"));h()});a("a",b.acrossQuestions).click(function(c){c.preventDefault();a(b.acrossInput).val(1);p()});a("a",b.acrossClears).click(function(c){c.preventDefault();a(b.allToggle).prop("checked",!1);n();m(0);h()});f=null;a(g).click(function(c){c||(c=window.event);var d=c.target?c.target:c.srcElement;if(f&&a.data(f)!=a.data(d)&&!0===c.shiftKey){var e=!1;a(f).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass, 4 | d.checked);a(g).each(function(){if(a.data(this)==a.data(f)||a.data(this)==a.data(d))e=e?!1:!0;e&&a(this).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass,d.checked);f=d;h()});a("form#changelist-form table#result_list tr").find("td:gt(0) :input").change(function(){e=!0});a('form#changelist-form button[name="index"]').click(function(a){if(e)return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."))}); 5 | a('form#changelist-form input[name="_save"]').click(function(c){var d=!1;a("select option:selected",b.actionContainer).each(function(){a(this).val()&&(d=!0)});if(d)return e?confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.")):confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button."))})}; 6 | a.fn.actions.defaults={actionContainer:"div.actions",counterContainer:"span.action-counter",allContainer:"div.actions span.all",acrossInput:"div.actions input.select-across",acrossQuestions:"div.actions span.question",acrossClears:"div.actions span.clear",allToggle:"#action-toggle",selectedClass:"selected"}})(django.jQuery); 7 | -------------------------------------------------------------------------------- /main/api.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # api.py 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | import os 23 | from django.contrib.auth.models import User 24 | from tastypie.resources import ModelResource, ALL, ALL_WITH_RELATIONS 25 | from tastypie.fields import ListField, ForeignKey 26 | from tastypie.authentication import BasicAuthentication, ApiKeyAuthentication, SessionAuthentication, MultiAuthentication 27 | from pcapoptikon.authorization import CertegoDjangoAuthorization 28 | from pcapoptikon.fields import Base64FileField 29 | from main.models import * 30 | 31 | def is_post(bundle): 32 | if bundle.request.method == 'post': 33 | return True 34 | 35 | class UserResource(ModelResource): 36 | class Meta: 37 | queryset = User.objects.all() 38 | resource_name = 'user' 39 | authentication = MultiAuthentication(BasicAuthentication(), ApiKeyAuthentication(), SessionAuthentication()) 40 | authorization = CertegoDjangoAuthorization() 41 | allowed_methods = ['get'] 42 | fields = ['id', 'username'] 43 | ordering = ['id', 'username'] 44 | 45 | class TaskResource(ModelResource): 46 | pcap_file = Base64FileField("pcap_file", use_in=is_post) 47 | user = ForeignKey(UserResource, 'user', full=True) 48 | results = ListField(attribute='results', null=True, blank=True, default=None) 49 | 50 | def obj_create(self, bundle, **kwargs): 51 | return super(TaskResource, self).obj_create(bundle, user=bundle.request.user) 52 | 53 | def alter_list_data_to_serialize(self, request, data): 54 | for item in data['objects']: 55 | item.data['filename'] = os.path.basename(Task.objects.get(pk=item.data['id']).pcap_file.name) 56 | return data 57 | 58 | class Meta: 59 | queryset = Task.objects.all().order_by('-id') 60 | resource_name = 'task' 61 | allowed_methods = ['get', 'post'] 62 | authentication = MultiAuthentication(BasicAuthentication(), ApiKeyAuthentication(), SessionAuthentication()) 63 | authorization = CertegoDjangoAuthorization() 64 | filtering = { 65 | 'submitted_on': ALL, 66 | 'user': ALL, 67 | 'status': ALL, 68 | } 69 | ordering = ['id', 'submitted_on', 'status'] 70 | -------------------------------------------------------------------------------- /nagios/check_queue.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | import os 5 | import sys 6 | 7 | RET_OK = 0 8 | RET_WARN = 1 9 | RET_CRIT = 2 10 | RET_UNK = 3 11 | 12 | sys.path.append(os.path.realpath(os.path.join( 13 | os.path.dirname(__file__), 14 | '..' 15 | ))) 16 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pcapoptikon.settings") 17 | 18 | from main.models import Task 19 | 20 | 21 | help_all = ( 22 | "Check status of PCAPOptikon queues (new, processing and queued)" 23 | ) 24 | 25 | help_crit = ( 26 | "Critical threshold. If you specify a single integer, it will be applied " 27 | "to all three queues; else, just specify three comma-separated integers " 28 | "in this order: -c new,processing,queued" 29 | ) 30 | 31 | help_warn = ( 32 | "Warning threshold. If you specify a single integer, it will be applied " 33 | "to all three queues; else, just specify three comma-separated integers " 34 | "in this order: -w new,processing,queued" 35 | ) 36 | 37 | 38 | def parse_thresholds(val): 39 | try: 40 | if "," not in val: 41 | t = int(val) 42 | return (t, t, t) 43 | else: 44 | toks = val.split(',', 2) 45 | return (int(toks[0]), int(toks[1]), int(toks[2])) 46 | except: 47 | print( 48 | "Wrong argument: thresholds should be either a single integer " 49 | "or three comma-separated integers" 50 | ) 51 | sys.exit(RET_UNK) 52 | 53 | if __name__ == '__main__': 54 | parser = argparse.ArgumentParser(help_all) 55 | parser.add_argument( 56 | '-c', 57 | type=str, 58 | required=True, 59 | help=help_crit 60 | ) 61 | parser.add_argument( 62 | '-w', 63 | type=str, 64 | required=True, 65 | help=help_warn 66 | ) 67 | 68 | args = parser.parse_args() 69 | 70 | crit_thresholds = parse_thresholds(args.c) 71 | warn_thresholds = parse_thresholds(args.w) 72 | 73 | new = Task.objects.filter(status=Task.STATUS_NEW).count() 74 | queued = Task.objects.filter(status=Task.STATUS_QUEUED).count() 75 | processing = Task.objects.filter(status=Task.STATUS_PROCESSING).count() 76 | 77 | ret = RET_OK 78 | ret_msg = "OK" 79 | if ( 80 | new >= crit_thresholds[0] or 81 | queued >= crit_thresholds[1] or 82 | processing >= crit_thresholds[2] 83 | ): 84 | ret = RET_CRIT 85 | ret_msg = "CRITICAL" 86 | elif ( 87 | new >= warn_thresholds[0] or 88 | queued >= warn_thresholds[1] or 89 | processing >= warn_thresholds[2] 90 | ): 91 | ret = RET_WARN 92 | ret_msg = "WARNING" 93 | 94 | print( 95 | "{msg}: {new} new, {queued} queued and {processing} processing | " 96 | "new={new};{wnew};{cnew};; " 97 | "queued={queued};{wqueued};{cqueued};; " 98 | "processing={processing};{wprocessing};{cprocessing};;".format( 99 | msg=ret_msg, 100 | new=new, 101 | wnew=warn_thresholds[0], 102 | cnew=crit_thresholds[0], 103 | queued=queued, 104 | wqueued=warn_thresholds[1], 105 | cqueued=crit_thresholds[1], 106 | processing=processing, 107 | wprocessing=warn_thresholds[2], 108 | cprocessing=crit_thresholds[2], 109 | ) 110 | ) 111 | sys.exit(ret) 112 | -------------------------------------------------------------------------------- /templates/main/tasks.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Tasks list{% endblock %} 3 | {% block navbar %}{% include 'navbar.html' %}{% endblock %} 4 | {% block content %} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
      IDFileSubmitted onOwnerStatusHits
      19 | {% endblock %} 20 | {% block bottomscripts %} 21 | var table = $('#table_tasks').DataTable({ 22 | "processing": true, 23 | "serverSide": true, 24 | "stateSave": true, 25 | "jQueryUI": true, 26 | "autoWidth": false, 27 | "ordering": true, 28 | "order": [[0, 'desc']], 29 | "searching": false, 30 | "pagingType": "full_numbers", 31 | "pageLength": 25, 32 | "lengthMenu": [ [25, 50, 100, 200, -1], [25, 50, 100, 200, "All"] ], 33 | "language":{ 34 | "zeroRecords": "No records found.", 35 | "lengthMenu": "Display: _MENU_", 36 | "infoFiltered": "", 37 | "emptyTable": "Still no tasks here" 38 | }, 39 | "ajax": function(data, callback, settings) { 40 | data.limit = data.length; 41 | data.offset = data.start; 42 | if (data.order.length > 0){ 43 | var direction = (data.order[0]['dir'] == 'desc' ? '-' : ''); 44 | var column_name = data.columns[data.order[0]['column']]['data']; 45 | data.order_by = direction + column_name; 46 | } 47 | 48 | $.ajax({ 49 | url: "{% url "api_dispatch_list" resource_name="task" api_name="v1" %}", 50 | data: data, 51 | success: function(innerdata) { 52 | innerdata.draw = data.draw; 53 | innerdata.recordsTotal = innerdata.meta.total_count; 54 | innerdata.recordsFiltered = innerdata.meta.total_count; 55 | innerdata.data = innerdata.objects; 56 | $.each(innerdata.data, function(){ 57 | // Add links to IDs 58 | this.id = '' + this.id + ''; 59 | 60 | // Parse dates 61 | this.submitted_on = new Date(this.submitted_on); 62 | 63 | // Status to badge 64 | switch (this.status){ 65 | case -1: 66 | this.status = 'Failed'; 67 | break; 68 | case 0: 69 | this.status = 'New'; 70 | break; 71 | case 1: 72 | this.status = 'Processing'; 73 | break; 74 | case 2: 75 | this.status = 'Queued'; 76 | break; 77 | case 3: 78 | this.status = 'Completed'; 79 | break; 80 | default: 81 | this.status = 'Unknown'; 82 | } 83 | 84 | // Hits count 85 | if (this.results != null){ 86 | this.results = this.results.length; 87 | } 88 | else { 89 | this.results = 0; 90 | } 91 | }); 92 | }, 93 | dataType: 'json' 94 | }).done(function (d) { 95 | callback(d); 96 | }); 97 | }, 98 | "columns": [ 99 | { "data": "id" }, 100 | { "data": "filename" }, 101 | { "data": "submitted_on" }, 102 | { "data": "user.username" }, 103 | { "data": "status" }, 104 | { "data": "results" } 105 | ] 106 | }); 107 | {% endblock %} 108 | -------------------------------------------------------------------------------- /static/admin/js/inlines.min.js: -------------------------------------------------------------------------------- 1 | (function(a){a.fn.formset=function(g){var b=a.extend({},a.fn.formset.defaults,g),i=a(this);g=i.parent();var m=function(e,k,h){var j=RegExp("("+k+"-(\\d+|__prefix__))");k=k+"-"+h;a(e).prop("for")&&a(e).prop("for",a(e).prop("for").replace(j,k));if(e.id)e.id=e.id.replace(j,k);if(e.name)e.name=e.name.replace(j,k)},l=a("#id_"+b.prefix+"-TOTAL_FORMS").prop("autocomplete","off"),d=parseInt(l.val(),10),c=a("#id_"+b.prefix+"-MAX_NUM_FORMS").prop("autocomplete","off");l=c.val()===""||c.val()-l.val()>0;i.each(function(){a(this).not("."+ 2 | b.emptyCssClass).addClass(b.formCssClass)});if(i.length&&l){var f;if(i.prop("tagName")=="TR"){i=this.eq(-1).children().length;g.append(''+b.addText+"");f=g.find("tr:last a")}else{i.filter(":last").after('");f=i.filter(":last").next().find("a")}f.click(function(e){e.preventDefault();var k=a("#id_"+b.prefix+"-TOTAL_FORMS");e=a("#"+ 3 | b.prefix+"-empty");var h=e.clone(true);h.removeClass(b.emptyCssClass).addClass(b.formCssClass).attr("id",b.prefix+"-"+d);if(h.is("tr"))h.children(":last").append('");else h.is("ul")||h.is("ol")?h.append('
    • '+b.deleteText+"
    • "):h.children(":first").append(''+b.deleteText+""); 4 | h.find("*").each(function(){m(this,b.prefix,k.val())});h.insertBefore(a(e));a(k).val(parseInt(k.val(),10)+1);d+=1;c.val()!==""&&c.val()-k.val()<=0&&f.parent().hide();h.find("a."+b.deleteCssClass).click(function(j){j.preventDefault();j=a(this).parents("."+b.formCssClass);j.remove();d-=1;b.removed&&b.removed(j);j=a("."+b.formCssClass);a("#id_"+b.prefix+"-TOTAL_FORMS").val(j.length);if(c.val()===""||c.val()-j.length>0)f.parent().show();for(var n=0,o=j.length;n b) return 1; 100 | if (a < b) return -1; 101 | } 102 | catch (e) { 103 | // silently fail on IE 'unknown' exception 104 | } 105 | return 0; 106 | } ); 107 | }, 108 | select_all: function(id) { 109 | var box = document.getElementById(id); 110 | for (var i = 0; i < box.options.length; i++) { 111 | box.options[i].selected = 'selected'; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /pcapoptikon/settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # settings.py 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | 23 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 24 | import os 25 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 26 | 27 | 28 | # Quick-start development settings - unsuitable for production 29 | # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ 30 | 31 | # Unique secret key generator. 32 | # Secret key will be placed in secret_key.py file. 33 | try: 34 | from secret_key import * 35 | except ImportError: 36 | SETTINGS_DIR=os.path.abspath(os.path.dirname(__file__)) 37 | # Using the same generation schema of Django startproject. 38 | from django.utils.crypto import get_random_string 39 | key = get_random_string(50, "abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)") 40 | 41 | # Write secret_key.py 42 | with open(os.path.join(SETTINGS_DIR, "secret_key.py"), "w") as key_file: 43 | key_file.write("SECRET_KEY = \"{0}\"".format(key)) 44 | 45 | # Reload key. 46 | from secret_key import * 47 | 48 | # SECURITY WARNING: don't run with debug turned on in production! 49 | DEBUG = True 50 | 51 | TEMPLATE_DEBUG = True 52 | 53 | ALLOWED_HOSTS = [] 54 | 55 | 56 | # Application definition 57 | 58 | INSTALLED_APPS = ( 59 | 'django.contrib.admin', 60 | 'django.contrib.auth', 61 | 'django.contrib.contenttypes', 62 | 'django.contrib.sessions', 63 | 'django.contrib.messages', 64 | 'django.contrib.staticfiles', 65 | 'widget_tweaks', 66 | 'main', 67 | 'tastypie', 68 | ) 69 | 70 | MIDDLEWARE_CLASSES = ( 71 | 'django.contrib.sessions.middleware.SessionMiddleware', 72 | 'django.middleware.common.CommonMiddleware', 73 | 'django.middleware.csrf.CsrfViewMiddleware', 74 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 75 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 76 | 'django.contrib.messages.middleware.MessageMiddleware', 77 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 78 | ) 79 | 80 | ROOT_URLCONF = 'pcapoptikon.urls' 81 | 82 | WSGI_APPLICATION = 'pcapoptikon.wsgi.application' 83 | 84 | 85 | # Database 86 | # https://docs.djangoproject.com/en/1.7/ref/settings/#databases 87 | 88 | DATABASES = { 89 | 'default': { 90 | 'ENGINE': 'django.db.backends.mysql', 91 | 'NAME': 'pcapoptikon', 92 | 'USER': 'root', 93 | 'PASSWORD': '', 94 | 'HOST': '', 95 | 'PORT': '', 96 | 'OPTIONS': { "init_command": "SET storage_engine=INNODB, SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED", }, 97 | } 98 | } 99 | 100 | # Internationalization 101 | # https://docs.djangoproject.com/en/1.7/topics/i18n/ 102 | 103 | LANGUAGE_CODE = 'en-us' 104 | 105 | TIME_ZONE = 'UTC' 106 | 107 | USE_I18N = True 108 | 109 | USE_L10N = True 110 | 111 | USE_TZ = True 112 | 113 | 114 | # Static files (CSS, JavaScript, Images) 115 | # https://docs.djangoproject.com/en/1.7/howto/static-files/ 116 | 117 | STATIC_URL = '/static/' 118 | #STATIC_ROOT = os.path.join(BASE_DIR, 'static') 119 | STATICFILES_DIRS = ( 120 | os.path.join(BASE_DIR, "static"), 121 | ) 122 | 123 | # Media 124 | MEDIA_ROOT = '/var/tmp/django' 125 | MEDIA_URL = '/files/' 126 | 127 | # Templates 128 | TEMPLATE_DIRS = (os.path.join(BASE_DIR, 'templates'),) 129 | 130 | # Logging 131 | LOGGING = { 132 | 'version': 1, 133 | 'disable_existing_loggers': False, 134 | 'formatters': { 135 | 'simple': { 136 | 'format': u'%(asctime)s [%(name)s] [%(threadName)s] %(levelname)s: %(message)s' 137 | }, 138 | }, 139 | 'handlers': { 140 | 'log_to_stdout': { 141 | 'level': 'DEBUG', 142 | 'class': 'logging.StreamHandler', 143 | 'formatter': 'simple', 144 | }, 145 | }, 146 | 'loggers': { 147 | 'main': { 148 | 'handlers': ['log_to_stdout'], 149 | 'level': 'DEBUG', 150 | 'propagate': True, 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /static/css/bootstrap-datetimepicker.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Datetimepicker for Bootstrap v3 3 | //! version : 3.1.3 4 | * https://github.com/Eonasdan/bootstrap-datetimepicker/ 5 | */.bootstrap-datetimepicker-widget{top:0;left:0;width:250px;padding:4px;margin-top:1px;z-index:99999!important;border-radius:4px}.bootstrap-datetimepicker-widget.timepicker-sbs{width:600px}.bootstrap-datetimepicker-widget.bottom:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0,0,0,.2);position:absolute;top:-7px;left:7px}.bootstrap-datetimepicker-widget.bottom:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;top:-6px;left:8px}.bootstrap-datetimepicker-widget.top:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-top:7px solid #ccc;border-top-color:rgba(0,0,0,.2);position:absolute;bottom:-7px;left:6px}.bootstrap-datetimepicker-widget.top:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #fff;position:absolute;bottom:-6px;left:7px}.bootstrap-datetimepicker-widget .dow{width:14.2857%}.bootstrap-datetimepicker-widget.pull-right:before{left:auto;right:6px}.bootstrap-datetimepicker-widget.pull-right:after{left:auto;right:7px}.bootstrap-datetimepicker-widget>ul{list-style-type:none;margin:0}.bootstrap-datetimepicker-widget a[data-action]{padding:6px 0}.bootstrap-datetimepicker-widget a[data-action]:active{box-shadow:none}.bootstrap-datetimepicker-widget .timepicker-hour,.bootstrap-datetimepicker-widget .timepicker-minute,.bootstrap-datetimepicker-widget .timepicker-second{width:54px;font-weight:700;font-size:1.2em;margin:0}.bootstrap-datetimepicker-widget button[data-action]{padding:6px}.bootstrap-datetimepicker-widget table[data-hour-format="12"] .separator{width:4px;padding:0;margin:0}.bootstrap-datetimepicker-widget .datepicker>div{display:none}.bootstrap-datetimepicker-widget .picker-switch{text-align:center}.bootstrap-datetimepicker-widget table{width:100%;margin:0}.bootstrap-datetimepicker-widget td,.bootstrap-datetimepicker-widget th{text-align:center;border-radius:4px}.bootstrap-datetimepicker-widget td{height:54px;line-height:54px;width:54px}.bootstrap-datetimepicker-widget td.cw{font-size:10px;height:20px;line-height:20px;color:#777}.bootstrap-datetimepicker-widget td.day{height:20px;line-height:20px;width:20px}.bootstrap-datetimepicker-widget td.day:hover,.bootstrap-datetimepicker-widget td.hour:hover,.bootstrap-datetimepicker-widget td.minute:hover,.bootstrap-datetimepicker-widget td.second:hover{background:#eee;cursor:pointer}.bootstrap-datetimepicker-widget td.old,.bootstrap-datetimepicker-widget td.new{color:#777}.bootstrap-datetimepicker-widget td.today{position:relative}.bootstrap-datetimepicker-widget td.today:before{content:'';display:inline-block;border-left:7px solid transparent;border-bottom:7px solid #428bca;border-top-color:rgba(0,0,0,.2);position:absolute;bottom:4px;right:4px}.bootstrap-datetimepicker-widget td.active,.bootstrap-datetimepicker-widget td.active:hover{background-color:#428bca;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.bootstrap-datetimepicker-widget td.active.today:before{border-bottom-color:#fff}.bootstrap-datetimepicker-widget td.disabled,.bootstrap-datetimepicker-widget td.disabled:hover{background:0 0;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget td span{display:inline-block;width:54px;height:54px;line-height:54px;margin:2px 1.5px;cursor:pointer;border-radius:4px}.bootstrap-datetimepicker-widget td span:hover{background:#eee}.bootstrap-datetimepicker-widget td span.active{background-color:#428bca;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.bootstrap-datetimepicker-widget td span.old{color:#777}.bootstrap-datetimepicker-widget td span.disabled,.bootstrap-datetimepicker-widget td span.disabled:hover{background:0 0;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget th{height:20px;line-height:20px;width:20px}.bootstrap-datetimepicker-widget th.picker-switch{width:145px}.bootstrap-datetimepicker-widget th.next,.bootstrap-datetimepicker-widget th.prev{font-size:21px}.bootstrap-datetimepicker-widget th.disabled,.bootstrap-datetimepicker-widget th.disabled:hover{background:0 0;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget thead tr:first-child th{cursor:pointer}.bootstrap-datetimepicker-widget thead tr:first-child th:hover{background:#eee}.input-group.date .input-group-addon span{display:block;cursor:pointer;width:16px;height:16px}.bootstrap-datetimepicker-widget.left-oriented:before{left:auto;right:6px}.bootstrap-datetimepicker-widget.left-oriented:after{left:auto;right:7px}.bootstrap-datetimepicker-widget ul.list-unstyled li div.timepicker div.timepicker-picker table.table-condensed tbody>tr>td{padding:0!important}@media screen and (max-width:767px){.bootstrap-datetimepicker-widget.timepicker-sbs{width:283px}} -------------------------------------------------------------------------------- /static/admin/js/admin/RelatedObjectLookups.js: -------------------------------------------------------------------------------- 1 | // Handles related-objects functionality: lookup link for raw_id_fields 2 | // and Add Another links. 3 | 4 | function html_unescape(text) { 5 | // Unescape a string that was escaped using django.utils.html.escape. 6 | text = text.replace(/</g, '<'); 7 | text = text.replace(/>/g, '>'); 8 | text = text.replace(/"/g, '"'); 9 | text = text.replace(/'/g, "'"); 10 | text = text.replace(/&/g, '&'); 11 | return text; 12 | } 13 | 14 | // IE doesn't accept periods or dashes in the window name, but the element IDs 15 | // we use to generate popup window names may contain them, therefore we map them 16 | // to allowed characters in a reversible way so that we can locate the correct 17 | // element when the popup window is dismissed. 18 | function id_to_windowname(text) { 19 | text = text.replace(/\./g, '__dot__'); 20 | text = text.replace(/\-/g, '__dash__'); 21 | return text; 22 | } 23 | 24 | function windowname_to_id(text) { 25 | text = text.replace(/__dot__/g, '.'); 26 | text = text.replace(/__dash__/g, '-'); 27 | return text; 28 | } 29 | 30 | function showAdminPopup(triggeringLink, name_regexp) { 31 | var name = triggeringLink.id.replace(name_regexp, ''); 32 | name = id_to_windowname(name); 33 | var href = triggeringLink.href; 34 | if (href.indexOf('?') == -1) { 35 | href += '?_popup=1'; 36 | } else { 37 | href += '&_popup=1'; 38 | } 39 | var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); 40 | win.focus(); 41 | return false; 42 | } 43 | 44 | function showRelatedObjectLookupPopup(triggeringLink) { 45 | return showAdminPopup(triggeringLink, /^lookup_/); 46 | } 47 | 48 | function dismissRelatedLookupPopup(win, chosenId) { 49 | var name = windowname_to_id(win.name); 50 | var elem = document.getElementById(name); 51 | if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) { 52 | elem.value += ',' + chosenId; 53 | } else { 54 | document.getElementById(name).value = chosenId; 55 | } 56 | win.close(); 57 | } 58 | 59 | function showRelatedObjectPopup(triggeringLink) { 60 | var name = triggeringLink.id.replace(/^(change|add|delete)_/, ''); 61 | name = id_to_windowname(name); 62 | var href = triggeringLink.href; 63 | var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); 64 | win.focus(); 65 | return false; 66 | } 67 | 68 | function dismissAddRelatedObjectPopup(win, newId, newRepr) { 69 | // newId and newRepr are expected to have previously been escaped by 70 | // django.utils.html.escape. 71 | newId = html_unescape(newId); 72 | newRepr = html_unescape(newRepr); 73 | var name = windowname_to_id(win.name); 74 | var elem = document.getElementById(name); 75 | var o; 76 | if (elem) { 77 | var elemName = elem.nodeName.toUpperCase(); 78 | if (elemName == 'SELECT') { 79 | o = new Option(newRepr, newId); 80 | elem.options[elem.options.length] = o; 81 | o.selected = true; 82 | } else if (elemName == 'INPUT') { 83 | if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) { 84 | elem.value += ',' + newId; 85 | } else { 86 | elem.value = newId; 87 | } 88 | } 89 | // Trigger a change event to update related links if required. 90 | django.jQuery(elem).trigger('change'); 91 | } else { 92 | var toId = name + "_to"; 93 | o = new Option(newRepr, newId); 94 | SelectBox.add_to_cache(toId, o); 95 | SelectBox.redisplay(toId); 96 | } 97 | win.close(); 98 | } 99 | 100 | function dismissChangeRelatedObjectPopup(win, objId, newRepr, newId) { 101 | objId = html_unescape(objId); 102 | newRepr = html_unescape(newRepr); 103 | var id = windowname_to_id(win.name).replace(/^edit_/, ''); 104 | var selectsSelector = interpolate('#%s, #%s_from, #%s_to', [id, id, id]); 105 | var selects = django.jQuery(selectsSelector); 106 | selects.find('option').each(function() { 107 | if (this.value == objId) { 108 | this.innerHTML = newRepr; 109 | this.value = newId; 110 | } 111 | }); 112 | win.close(); 113 | }; 114 | 115 | function dismissDeleteRelatedObjectPopup(win, objId) { 116 | objId = html_unescape(objId); 117 | var id = windowname_to_id(win.name).replace(/^delete_/, ''); 118 | var selectsSelector = interpolate('#%s, #%s_from, #%s_to', [id, id, id]); 119 | var selects = django.jQuery(selectsSelector); 120 | selects.find('option').each(function() { 121 | if (this.value == objId) { 122 | django.jQuery(this).remove(); 123 | } 124 | }).trigger('change'); 125 | win.close(); 126 | }; 127 | 128 | // Kept for backward compatibility 129 | showAddAnotherPopup = showRelatedObjectPopup; 130 | dismissAddAnotherPopup = dismissAddRelatedObjectPopup; 131 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # start.sh 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | # 19 | # Author: Pietro Delsante 20 | # www.certego.net 21 | # 22 | 23 | function log() { 24 | echo "[$(date +'%y-%m-%d %H:%M:%S')] $@" 25 | } 26 | 27 | # Send all output (stdout + stderr) to a log file 28 | exec > >(tee /var/log/pcapoptikon_startup.log) 29 | exec 2>&1 30 | 31 | # Make sure that /var/log/mysql exists, else mysql start will fail 32 | mkdir -p /var/log/mysql 33 | # Also make sure that /var/log/suricata and /var/run/suricata exist 34 | mkdir -p /var/log/suricata 35 | mkdir -p /var/run/suricata 36 | 37 | # Start MySQL server 38 | log "Starting MySQL..." 39 | service mysql start 40 | 41 | # Ensure you got the latest pcapoptikon version 42 | log "Pulling PCAPOptikon repo..." 43 | cd /opt/pcapoptikon 44 | git pull origin master 45 | python manage.py migrate 46 | 47 | # Making sure that unified2-alert format is enabled in suricata.yaml 48 | sed -i '/- unified2-alert:/{n;s/enabled: no/enabled: yes/}' /etc/suricata/suricata.yaml 49 | 50 | # Adding local.rules to suricata.yaml if it's not there already 51 | touch /etc/suricata/rules/local.rules 52 | [ $(grep "local.rules" /etc/suricata/suricata.yaml | wc -l) -eq 0 ] && sed -i '/tor.rules/ a\ 53 | - local.rules' /etc/suricata/suricata.yaml 54 | 55 | # ETPro format: https://rules.emergingthreats.net//suricata/etpro.rules.tar.gz 56 | if [ $(echo $1 | egrep '^[0-9]{16}$' | wc -l) -eq 1 ]; then 57 | log "Using supplied OinkCode to set up ETPro ruleset in oinkmaster" 58 | sed -ir "s|^url = http://rules.emergingthreats.net/open/suricata/emerging.rules.tar.gz|url = https://rules.emergingthreats.net/$1/suricata/etpro.rules.tar.gz|" /etc/oinkmaster.conf 59 | sed -i 's/emerging-\(.*\)\.rules/\1.rules/g' /etc/suricata/suricata.yaml # ETPro rule files have a different name 60 | else 61 | log "Using default ET Open ruleset (to change this, please pass your ETPro oinkcode as the first param to $0" 62 | fi 63 | 64 | # Update suricata rules 65 | log "Running OinkMaster to update signatures..." 66 | oinkmaster -C /etc/oinkmaster.conf -o /etc/suricata/rules 67 | 68 | # Update sid-msg.map 69 | log "Updating sid-msg.map" 70 | /usr/share/oinkmaster/create-sidmap.pl /etc/suricata/rules > /etc/suricata/rules/sid-msg.map 71 | 72 | # Start Suricata 73 | log "Starting Suricata..." 74 | rm -f /var/run/suricata.pid 75 | /usr/bin/suricata -c /etc/suricata/suricata.yaml --unix-socket --pidfile /var/run/suricata.pid >/var/log/suricata/suricata.log 2>&1 & 76 | 77 | # Start the pcapoptikon HTTP server 78 | log "Starting the PCAPOptikon HTTP Server..." 79 | /usr/bin/python /opt/pcapoptikon/manage.py runserver 0.0.0.0:8000 >/var/log/pcapoptikon_web.log 2>&1 & 80 | log $! > /var/run/pcapoptikon-http.pid 81 | 82 | # Start the pcapoptikon daemon 83 | log "Starting the PCAPOptikon worker daemon..." 84 | /usr/bin/python /opt/pcapoptikon/manage.py run_daemon >/var/log/pcapoptikon_daemon.log 2>&1 & 85 | log $! > /var/run/pcapoptikon-daemon.pid 86 | 87 | # Give a hint about how to use 88 | log "Running on: http://"$(hostname -i)":8000/" 89 | log "Username: admin" 90 | log "Api-Key: "$(mysql -e 'SELECT `key` FROM tastypie_apikey WHERE user_id = (SELECT id FROM auth_user WHERE username = '"'"'admin'"'"');' pcapoptikon) 91 | 92 | # Run oinkmaster every 24 hours and restart suricata and pcapoptikon daemon to reload the rules 93 | while true; do 94 | sleep $(expr 60 \* 60 \* 24) 95 | 96 | log "Running oinkmaster to update rules" 97 | oinkmaster -C /etc/oinkmaster.conf -o /etc/suricata/rules 98 | 99 | log "Updating sid-msg.map" 100 | /usr/share/oinkmaster/create-sidmap.pl /etc/suricata/rules > /etc/suricata/rules/sid-msg.map 101 | 102 | log "Stopping pcapoptikon's run_daemon and deleting pid file" 103 | kill $(cat /var/run/pcapoptikon-daemon.pid) 104 | sleep 5 105 | rm -f /var/run/pcapoptikon-daemon.pid 106 | 107 | log "Stopping suricata and deleting pid file" 108 | kill $(cat /var/run/suricata.pid) 109 | sleep 5 110 | rm -f /var/run/suricata.pid 111 | 112 | log "Starting up suricata and run_daemon again" 113 | /usr/bin/suricata -c /etc/suricata/suricata.yaml --unix-socket --pidfile /var/run/suricata.pid >/dev/null 2>&1 & 114 | /usr/bin/python /opt/pcapoptikon/manage.py run_daemon >/var/log/pcapoptikon_daemon.log 2>&1 & 115 | log $! > /var/run/pcapoptikon-daemon.pid 116 | 117 | log "Signatures reloaded" 118 | done 119 | -------------------------------------------------------------------------------- /static/admin/css/rtl.css: -------------------------------------------------------------------------------- 1 | body { 2 | direction: rtl; 3 | } 4 | 5 | /* LOGIN */ 6 | 7 | .login .form-row { 8 | float: right; 9 | } 10 | 11 | .login .form-row label { 12 | float: right; 13 | padding-left: 0.5em; 14 | padding-right: 0; 15 | text-align: left; 16 | } 17 | 18 | .login .submit-row { 19 | clear: both; 20 | padding: 1em 9.4em 0 0; 21 | } 22 | 23 | /* GLOBAL */ 24 | 25 | th { 26 | text-align: right; 27 | } 28 | 29 | .module h2, .module caption { 30 | text-align: right; 31 | } 32 | 33 | .addlink, .changelink { 34 | padding-left: 0px; 35 | padding-right: 12px; 36 | background-position: 100% 0.2em; 37 | } 38 | 39 | .deletelink { 40 | padding-left: 0px; 41 | padding-right: 12px; 42 | background-position: 100% 0.25em; 43 | } 44 | 45 | .object-tools { 46 | float: left; 47 | } 48 | 49 | thead th:first-child, 50 | tfoot td:first-child { 51 | border-left: 1px solid #ddd !important; 52 | } 53 | 54 | /* LAYOUT */ 55 | 56 | #user-tools { 57 | right: auto; 58 | left: 0; 59 | text-align: left; 60 | } 61 | 62 | div.breadcrumbs { 63 | text-align: right; 64 | } 65 | 66 | #content-main { 67 | float: right; 68 | } 69 | 70 | #content-related { 71 | float: left; 72 | margin-left: -19em; 73 | margin-right: auto; 74 | } 75 | 76 | .colMS { 77 | margin-left: 20em !important; 78 | margin-right: 10px !important; 79 | } 80 | 81 | /* SORTABLE TABLES */ 82 | 83 | table thead th.sorted .sortoptions { 84 | float: left; 85 | } 86 | 87 | thead th.sorted .text { 88 | padding-right: 0; 89 | padding-left: 42px; 90 | } 91 | 92 | /* dashboard styles */ 93 | 94 | .dashboard .module table td a { 95 | padding-left: .6em; 96 | padding-right: 12px; 97 | } 98 | 99 | /* changelists styles */ 100 | 101 | .change-list .filtered { 102 | background: white url(../img/changelist-bg_rtl.gif) top left repeat-y !important; 103 | } 104 | 105 | .change-list .filtered table { 106 | border-left: 1px solid #ddd; 107 | border-right: 0px none; 108 | } 109 | 110 | #changelist-filter { 111 | right: auto; 112 | left: 0; 113 | border-left: 0px none; 114 | border-right: 1px solid #ddd; 115 | } 116 | 117 | .change-list .filtered .results, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { 118 | margin-right: 0px !important; 119 | margin-left: 160px !important; 120 | } 121 | 122 | #changelist-filter li.selected { 123 | border-left: 0px none; 124 | padding-left: 0px; 125 | margin-left: 0; 126 | border-right: 5px solid #ccc; 127 | padding-right: 5px; 128 | margin-right: -10px; 129 | } 130 | 131 | .filtered .actions { 132 | border-left:1px solid #DDDDDD; 133 | margin-left:160px !important; 134 | border-right: 0 none; 135 | margin-right:0 !important; 136 | } 137 | 138 | #changelist table tbody td:first-child, #changelist table tbody th:first-child { 139 | border-right: 0; 140 | border-left: 1px solid #ddd; 141 | } 142 | 143 | /* FORMS */ 144 | 145 | .aligned label { 146 | padding: 0 0 3px 1em; 147 | float: right; 148 | } 149 | 150 | .submit-row { 151 | text-align: left 152 | } 153 | 154 | .submit-row p.deletelink-box { 155 | float: right; 156 | } 157 | 158 | .submit-row .deletelink { 159 | background: url(../img/icon_deletelink.gif) 0 50% no-repeat; 160 | padding-right: 14px; 161 | } 162 | 163 | .vDateField, .vTimeField { 164 | margin-left: 2px; 165 | } 166 | 167 | form ul.inline li { 168 | float: right; 169 | padding-right: 0; 170 | padding-left: 7px; 171 | } 172 | 173 | input[type=submit].default, .submit-row input.default { 174 | float: left; 175 | } 176 | 177 | fieldset .field-box { 178 | float: right; 179 | margin-left: 20px; 180 | margin-right: 0; 181 | } 182 | 183 | .errorlist li { 184 | background-position: 100% .3em; 185 | padding: 4px 25px 4px 5px; 186 | } 187 | 188 | .errornote { 189 | background-position: 100% .3em; 190 | padding: 4px 25px 4px 5px; 191 | } 192 | 193 | /* WIDGETS */ 194 | 195 | .calendarnav-previous { 196 | top: 0; 197 | left: auto; 198 | right: 0; 199 | } 200 | 201 | .calendarnav-next { 202 | top: 0; 203 | right: auto; 204 | left: 0; 205 | } 206 | 207 | .calendar caption, .calendarbox h2 { 208 | text-align: center; 209 | } 210 | 211 | .selector { 212 | float: right; 213 | } 214 | 215 | .selector .selector-filter { 216 | text-align: right; 217 | } 218 | 219 | .inline-deletelink { 220 | float: left; 221 | } 222 | 223 | /* MISC */ 224 | 225 | .inline-related h2, .inline-group h2 { 226 | text-align: right 227 | } 228 | 229 | .inline-related h3 span.delete { 230 | padding-right: 20px; 231 | padding-left: inherit; 232 | left: 10px; 233 | right: inherit; 234 | float:left; 235 | } 236 | 237 | .inline-related h3 span.delete label { 238 | margin-left: inherit; 239 | margin-right: 2px; 240 | } 241 | 242 | /* IE7 specific bug fixes */ 243 | 244 | div.colM { 245 | position: relative; 246 | } 247 | 248 | .submit-row input { 249 | float: left; 250 | } 251 | -------------------------------------------------------------------------------- /static/admin/js/actions.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | var lastChecked; 3 | 4 | $.fn.actions = function(opts) { 5 | var options = $.extend({}, $.fn.actions.defaults, opts); 6 | var actionCheckboxes = $(this); 7 | var list_editable_changed = false; 8 | var checker = function(checked) { 9 | if (checked) { 10 | showQuestion(); 11 | } else { 12 | reset(); 13 | } 14 | $(actionCheckboxes).prop("checked", checked) 15 | .parent().parent().toggleClass(options.selectedClass, checked); 16 | }, 17 | updateCounter = function() { 18 | var sel = $(actionCheckboxes).filter(":checked").length; 19 | // _actions_icnt is defined in the generated HTML 20 | // and contains the total amount of objects in the queryset 21 | $(options.counterContainer).html(interpolate( 22 | ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), { 23 | sel: sel, 24 | cnt: _actions_icnt 25 | }, true)); 26 | $(options.allToggle).prop("checked", function() { 27 | var value; 28 | if (sel == actionCheckboxes.length) { 29 | value = true; 30 | showQuestion(); 31 | } else { 32 | value = false; 33 | clearAcross(); 34 | } 35 | return value; 36 | }); 37 | }, 38 | showQuestion = function() { 39 | $(options.acrossClears).hide(); 40 | $(options.acrossQuestions).show(); 41 | $(options.allContainer).hide(); 42 | }, 43 | showClear = function() { 44 | $(options.acrossClears).show(); 45 | $(options.acrossQuestions).hide(); 46 | $(options.actionContainer).toggleClass(options.selectedClass); 47 | $(options.allContainer).show(); 48 | $(options.counterContainer).hide(); 49 | }, 50 | reset = function() { 51 | $(options.acrossClears).hide(); 52 | $(options.acrossQuestions).hide(); 53 | $(options.allContainer).hide(); 54 | $(options.counterContainer).show(); 55 | }, 56 | clearAcross = function() { 57 | reset(); 58 | $(options.acrossInput).val(0); 59 | $(options.actionContainer).removeClass(options.selectedClass); 60 | }; 61 | // Show counter by default 62 | $(options.counterContainer).show(); 63 | // Check state of checkboxes and reinit state if needed 64 | $(this).filter(":checked").each(function(i) { 65 | $(this).parent().parent().toggleClass(options.selectedClass); 66 | updateCounter(); 67 | if ($(options.acrossInput).val() == 1) { 68 | showClear(); 69 | } 70 | }); 71 | $(options.allToggle).show().click(function() { 72 | checker($(this).prop("checked")); 73 | updateCounter(); 74 | }); 75 | $("a", options.acrossQuestions).click(function(event) { 76 | event.preventDefault(); 77 | $(options.acrossInput).val(1); 78 | showClear(); 79 | }); 80 | $("a", options.acrossClears).click(function(event) { 81 | event.preventDefault(); 82 | $(options.allToggle).prop("checked", false); 83 | clearAcross(); 84 | checker(0); 85 | updateCounter(); 86 | }); 87 | lastChecked = null; 88 | $(actionCheckboxes).click(function(event) { 89 | if (!event) { event = window.event; } 90 | var target = event.target ? event.target : event.srcElement; 91 | if (lastChecked && $.data(lastChecked) != $.data(target) && event.shiftKey === true) { 92 | var inrange = false; 93 | $(lastChecked).prop("checked", target.checked) 94 | .parent().parent().toggleClass(options.selectedClass, target.checked); 95 | $(actionCheckboxes).each(function() { 96 | if ($.data(this) == $.data(lastChecked) || $.data(this) == $.data(target)) { 97 | inrange = (inrange) ? false : true; 98 | } 99 | if (inrange) { 100 | $(this).prop("checked", target.checked) 101 | .parent().parent().toggleClass(options.selectedClass, target.checked); 102 | } 103 | }); 104 | } 105 | $(target).parent().parent().toggleClass(options.selectedClass, target.checked); 106 | lastChecked = target; 107 | updateCounter(); 108 | }); 109 | $('form#changelist-form table#result_list tr').find('td:gt(0) :input').change(function() { 110 | list_editable_changed = true; 111 | }); 112 | $('form#changelist-form button[name="index"]').click(function(event) { 113 | if (list_editable_changed) { 114 | return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost.")); 115 | } 116 | }); 117 | $('form#changelist-form input[name="_save"]').click(function(event) { 118 | var action_changed = false; 119 | $('select option:selected', options.actionContainer).each(function() { 120 | if ($(this).val()) { 121 | action_changed = true; 122 | } 123 | }); 124 | if (action_changed) { 125 | if (list_editable_changed) { 126 | return confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.")); 127 | } else { 128 | return confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button.")); 129 | } 130 | } 131 | }); 132 | }; 133 | /* Setup plugin defaults */ 134 | $.fn.actions.defaults = { 135 | actionContainer: "div.actions", 136 | counterContainer: "span.action-counter", 137 | allContainer: "div.actions span.all", 138 | acrossInput: "div.actions input.select-across", 139 | acrossQuestions: "div.actions span.question", 140 | acrossClears: "div.actions span.clear", 141 | allToggle: "#action-toggle", 142 | selectedClass: "selected" 143 | }; 144 | })(django.jQuery); 145 | -------------------------------------------------------------------------------- /static/css/bootstrap-datepicker.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Datepicker for Bootstrap 3 | * 4 | * Copyright 2012 Stefan Petre 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | */ 9 | .datepicker { 10 | top: 0; 11 | left: 0; 12 | padding: 4px; 13 | margin-top: 1px; 14 | -webkit-border-radius: 4px; 15 | -moz-border-radius: 4px; 16 | border-radius: 4px; 17 | /*.dow { 18 | border-top: 1px solid #ddd !important; 19 | }*/ 20 | 21 | } 22 | .datepicker:before { 23 | content: ''; 24 | display: inline-block; 25 | border-left: 7px solid transparent; 26 | border-right: 7px solid transparent; 27 | border-bottom: 7px solid #ccc; 28 | border-bottom-color: rgba(0, 0, 0, 0.2); 29 | position: absolute; 30 | top: -7px; 31 | left: 6px; 32 | } 33 | .datepicker:after { 34 | content: ''; 35 | display: inline-block; 36 | border-left: 6px solid transparent; 37 | border-right: 6px solid transparent; 38 | border-bottom: 6px solid #ffffff; 39 | position: absolute; 40 | top: -6px; 41 | left: 7px; 42 | } 43 | .datepicker > div { 44 | display: none; 45 | } 46 | .datepicker table { 47 | width: 100%; 48 | margin: 0; 49 | } 50 | .datepicker td, 51 | .datepicker th { 52 | text-align: center; 53 | width: 20px; 54 | height: 20px; 55 | -webkit-border-radius: 4px; 56 | -moz-border-radius: 4px; 57 | border-radius: 4px; 58 | } 59 | .datepicker td.day:hover { 60 | background: #eeeeee; 61 | cursor: pointer; 62 | } 63 | .datepicker td.day.disabled { 64 | color: #eeeeee; 65 | } 66 | .datepicker td.old, 67 | .datepicker td.new { 68 | color: #999999; 69 | } 70 | .datepicker td.active, 71 | .datepicker td.active:hover { 72 | color: #ffffff; 73 | background-color: #006dcc; 74 | background-image: -moz-linear-gradient(top, #0088cc, #0044cc); 75 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); 76 | background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); 77 | background-image: -o-linear-gradient(top, #0088cc, #0044cc); 78 | background-image: linear-gradient(to bottom, #0088cc, #0044cc); 79 | background-repeat: repeat-x; 80 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0); 81 | border-color: #0044cc #0044cc #002a80; 82 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 83 | *background-color: #0044cc; 84 | /* Darken IE7 buttons by default so they stand out more given they won't have borders */ 85 | 86 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 87 | color: #fff; 88 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 89 | } 90 | .datepicker td.active:hover, 91 | .datepicker td.active:hover:hover, 92 | .datepicker td.active:focus, 93 | .datepicker td.active:hover:focus, 94 | .datepicker td.active:active, 95 | .datepicker td.active:hover:active, 96 | .datepicker td.active.active, 97 | .datepicker td.active:hover.active, 98 | .datepicker td.active.disabled, 99 | .datepicker td.active:hover.disabled, 100 | .datepicker td.active[disabled], 101 | .datepicker td.active:hover[disabled] { 102 | color: #ffffff; 103 | background-color: #0044cc; 104 | *background-color: #003bb3; 105 | } 106 | .datepicker td.active:active, 107 | .datepicker td.active:hover:active, 108 | .datepicker td.active.active, 109 | .datepicker td.active:hover.active { 110 | background-color: #003399 \9; 111 | } 112 | .datepicker td span { 113 | display: block; 114 | width: 47px; 115 | height: 54px; 116 | line-height: 54px; 117 | float: left; 118 | margin: 2px; 119 | cursor: pointer; 120 | -webkit-border-radius: 4px; 121 | -moz-border-radius: 4px; 122 | border-radius: 4px; 123 | } 124 | .datepicker td span:hover { 125 | background: #eeeeee; 126 | } 127 | .datepicker td span.active { 128 | color: #ffffff; 129 | background-color: #006dcc; 130 | background-image: -moz-linear-gradient(top, #0088cc, #0044cc); 131 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); 132 | background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); 133 | background-image: -o-linear-gradient(top, #0088cc, #0044cc); 134 | background-image: linear-gradient(to bottom, #0088cc, #0044cc); 135 | background-repeat: repeat-x; 136 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0); 137 | border-color: #0044cc #0044cc #002a80; 138 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 139 | *background-color: #0044cc; 140 | /* Darken IE7 buttons by default so they stand out more given they won't have borders */ 141 | 142 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 143 | color: #fff; 144 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 145 | } 146 | .datepicker td span.active:hover, 147 | .datepicker td span.active:focus, 148 | .datepicker td span.active:active, 149 | .datepicker td span.active.active, 150 | .datepicker td span.active.disabled, 151 | .datepicker td span.active[disabled] { 152 | color: #ffffff; 153 | background-color: #0044cc; 154 | *background-color: #003bb3; 155 | } 156 | .datepicker td span.active:active, 157 | .datepicker td span.active.active { 158 | background-color: #003399 \9; 159 | } 160 | .datepicker td span.old { 161 | color: #999999; 162 | } 163 | .datepicker th.switch { 164 | width: 145px; 165 | } 166 | .datepicker th.next, 167 | .datepicker th.prev { 168 | font-size: 21px; 169 | } 170 | .datepicker thead tr:first-child th { 171 | cursor: pointer; 172 | } 173 | .datepicker thead tr:first-child th:hover { 174 | background: #eeeeee; 175 | } 176 | .input-append.date .add-on i, 177 | .input-prepend.date .add-on i { 178 | display: block; 179 | cursor: pointer; 180 | width: 16px; 181 | height: 16px; 182 | } -------------------------------------------------------------------------------- /static/css/dataTables.tableTools.min.css: -------------------------------------------------------------------------------- 1 | div.DTTT_container{position:relative;float:right;margin-bottom:1em}@media screen and (max-width:640px){div.DTTT_container{float:none!important;text-align:center}div.DTTT_container:after{visibility:hidden;display:block;content:"";clear:both;height:0}}a.DTTT_button,button.DTTT_button,div.DTTT_button{position:relative;display:inline-block;margin-right:3px;padding:5px 8px;border:1px solid #999;cursor:pointer;font-size:.88em;color:#000!important;-webkit-border-radius:2px;-moz-border-radius:2px;-ms-border-radius:2px;-o-border-radius:2px;border-radius:2px;-webkit-box-shadow:1px 1px 3px #ccc;-moz-box-shadow:1px 1px 3px #ccc;-ms-box-shadow:1px 1px 3px #ccc;-o-box-shadow:1px 1px 3px #ccc;box-shadow:1px 1px 3px #ccc;background:#fff;background:-webkit-linear-gradient(top,#fff 0,#f3f3f3 89%,#f9f9f9 100%);background:-moz-linear-gradient(top,#fff 0,#f3f3f3 89%,#f9f9f9 100%);background:-ms-linear-gradient(top,#fff 0,#f3f3f3 89%,#f9f9f9 100%);background:-o-linear-gradient(top,#fff 0,#f3f3f3 89%,#f9f9f9 100%);background:linear-gradient(top,#fff 0,#f3f3f3 89%,#f9f9f9 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f9f9f9', GradientType=0)}button.DTTT_button{height:30px;padding:3px 8px}.DTTT_button embed{outline:0}a.DTTT_button:hover,button.DTTT_button:hover,div.DTTT_button:hover{border:1px solid #666;text-decoration:none!important;-webkit-box-shadow:1px 1px 3px #999;-moz-box-shadow:1px 1px 3px #999;-ms-box-shadow:1px 1px 3px #999;-o-box-shadow:1px 1px 3px #999;box-shadow:1px 1px 3px #999;background:#f3f3f3;background:-webkit-linear-gradient(top,#f3f3f3 0,#e2e2e2 89%,#f4f4f4 100%);background:-moz-linear-gradient(top,#f3f3f3 0,#e2e2e2 89%,#f4f4f4 100%);background:-ms-linear-gradient(top,#f3f3f3 0,#e2e2e2 89%,#f4f4f4 100%);background:-o-linear-gradient(top,#f3f3f3 0,#e2e2e2 89%,#f4f4f4 100%);background:linear-gradient(top,#f3f3f3 0,#e2e2e2 89%,#f4f4f4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3f3f3', endColorstr='#f4f4f4', GradientType=0)}a.DTTT_button:focus,button.DTTT_button:focus,div.DTTT_button:focus{border:1px solid #426c9e;text-shadow:0 1px 0 #c4def1;outline:0;background-color:#a3d0ef 100%;background-image:-webkit-linear-gradient(top,#a3d0ef 0,#79ace9 65%,#a3d0ef 100%);background-image:-moz-linear-gradient(top,#a3d0ef 0,#79ace9 65%,#a3d0ef 100%);background-image:-ms-linear-gradient(top,#a3d0ef 0,#79ace9 65%,#a3d0ef 100%);background-image:-o-linear-gradient(top,#a3d0ef 0,#79ace9 65%,#a3d0ef 100%);background-image:linear-gradient(top,#a3d0ef 0,#79ace9 65%,#a3d0ef 100%);filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0, StartColorStr='#a3d0ef', EndColorStr='#a3d0ef')}a.DTTT_button:active,button.DTTT_button:active,div.DTTT_button:active{-webkit-box-shadow:inset 1px 1px 3px #999;-moz-box-shadow:inset 1px 1px 3px #999;box-shadow:inset 1px 1px 3px #999}a.DTTT_disabled,button.DTTT_disabled,div.DTTT_disabled{color:#999;border:1px solid #d0d0d0;background:#fff;background:-webkit-linear-gradient(top,#fff 0,#f9f9f9 89%,#fafafa 100%);background:-moz-linear-gradient(top,#fff 0,#f9f9f9 89%,#fafafa 100%);background:-ms-linear-gradient(top,#fff 0,#f9f9f9 89%,#fafafa 100%);background:-o-linear-gradient(top,#fff 0,#f9f9f9 89%,#fafafa 100%);background:linear-gradient(top,#fff 0,#f9f9f9 89%,#fafafa 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#fafafa', GradientType=0)}button.DTTT_button_collection span{padding-right:17px;background:url(../images/collection.png) center right no-repeat}button.DTTT_button_collection:hover span{padding-right:17px;background:url(../images/collection_hover.png) center right no-repeat #f0f0f0}table.DTTT_selectable tbody tr{cursor:pointer}table.dataTable tr.DTTT_selected.odd,table.dataTable tr.DTTT_selected.odd td.sorting_1,table.dataTable tr.DTTT_selected.odd td.sorting_2,table.dataTable tr.DTTT_selected.odd td.sorting_3{background-color:#9FAFD1}table.dataTable tr.DTTT_selected.even,table.dataTable tr.DTTT_selected.even td.sorting_1,table.dataTable tr.DTTT_selected.even td.sorting_2,table.dataTable tr.DTTT_selected.even td.sorting_3{background-color:#B0BED9}div.DTTT_collection{width:150px;padding:8px 8px 4px;border:1px solid #ccc;border:1px solid rgba(0,0,0,.4);background-color:#f3f3f3;background-color:rgba(255,255,255,.3);overflow:hidden;z-index:2002;-webkit-border-radius:5px;-moz-border-radius:5px;-ms-border-radius:5px;-o-border-radius:5px;border-radius:5px;-webkit-box-shadow:3px 3px 5px rgba(0,0,0,.3);-moz-box-shadow:3px 3px 5px rgba(0,0,0,.3);-ms-box-shadow:3px 3px 5px rgba(0,0,0,.3);-o-box-shadow:3px 3px 5px rgba(0,0,0,.3);box-shadow:3px 3px 5px rgba(0,0,0,.3)}div.DTTT_collection_background{background:url(../images/background.png) top left;z-index:2001}div.DTTT_collection a.DTTT_button,div.DTTT_collection button.DTTT_button,div.DTTT_collection div.DTTT_button{position:relative;left:0;right:0;display:block;float:none;margin-bottom:4px;-webkit-box-shadow:1px 1px 3px #999;-moz-box-shadow:1px 1px 3px #999;-ms-box-shadow:1px 1px 3px #999;-o-box-shadow:1px 1px 3px #999;box-shadow:1px 1px 3px #999}.DTTT_print_info{position:fixed;top:50%;left:50%;width:400px;height:150px;margin-left:-200px;margin-top:-75px;text-align:center;color:#333;padding:10px 30px;background:#fff;background:-webkit-linear-gradient(top,#fff 0,#f3f3f3 89%,#f9f9f9 100%);background:-moz-linear-gradient(top,#fff 0,#f3f3f3 89%,#f9f9f9 100%);background:-ms-linear-gradient(top,#fff 0,#f3f3f3 89%,#f9f9f9 100%);background:-o-linear-gradient(top,#fff 0,#f3f3f3 89%,#f9f9f9 100%);background:linear-gradient(top,#fff 0,#f3f3f3 89%,#f9f9f9 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f9f9f9', GradientType=0);opacity:.95;border:1px solid #000;border:1px solid rgba(0,0,0,.5);-webkit-border-radius:6px;-moz-border-radius:6px;-ms-border-radius:6px;-o-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0,0,0,.5);-moz-box-shadow:0 3px 7px rgba(0,0,0,.5);-ms-box-shadow:0 3px 7px rgba(0,0,0,.5);-o-box-shadow:0 3px 7px rgba(0,0,0,.5);box-shadow:0 3px 7px rgba(0,0,0,.5)}.DTTT_print_info h6{font-weight:400;font-size:28px;line-height:28px;margin:1em}.DTTT_print_info p{font-size:14px;line-height:20px} -------------------------------------------------------------------------------- /static/css/dataTables.bootstrap.min.css: -------------------------------------------------------------------------------- 1 | div.dataTables_length label{font-weight:400;text-align:left;white-space:nowrap}div.dataTables_length select{width:75px;display:inline-block}div.dataTables_filter{text-align:right}div.dataTables_filter label{font-weight:400;white-space:nowrap;text-align:left}div.dataTables_filter input{margin-left:.5em;display:inline-block}div.dataTables_info{padding-top:8px;white-space:nowrap}div.dataTables_paginate{margin:0;white-space:nowrap;text-align:right}div.dataTables_paginate ul.pagination{margin:2px 0;white-space:nowrap}@media screen and (max-width:767px){div.dataTables_filter,div.dataTables_info,div.dataTables_length,div.dataTables_paginate{text-align:center}}table.dataTable td,table.dataTable th{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}table.dataTable{clear:both;margin-top:6px!important;margin-bottom:6px!important;max-width:none!important}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_desc_disabled{cursor:pointer}table.dataTable thead .sorting{background:url(../images/sort_both.png) center right no-repeat}table.dataTable thead .sorting_asc{background:url(../images/sort_asc.png) center right no-repeat}table.dataTable thead .sorting_desc{background:url(../images/sort_desc.png) center right no-repeat}table.dataTable thead .sorting_asc_disabled{background:url(../images/sort_asc_disabled.png) center right no-repeat}table.dataTable thead .sorting_desc_disabled{background:url(../images/sort_desc_disabled.png) center right no-repeat}table.dataTable thead>tr>th{padding-left:18px;padding-right:18px}table.dataTable th:active{outline:0}div.dataTables_scrollHead table{margin-bottom:0!important;border-bottom-left-radius:0;border-bottom-right-radius:0}div.dataTables_scrollHead table thead tr:last-child td:first-child,div.dataTables_scrollHead table thead tr:last-child th:first-child{border-bottom-left-radius:0!important;border-bottom-right-radius:0!important}div.dataTables_scrollBody table{border-top:none;margin-top:0!important;margin-bottom:0!important}div.dataTables_scrollBody tbody tr:first-child td,div.dataTables_scrollBody tbody tr:first-child th{border-top:none}div.dataTables_scrollFoot table{margin-top:0!important;border-top:none}table.table-bordered.dataTable{border-collapse:separate!important}table.table-bordered thead td,table.table-bordered thead th{border-left-width:0;border-top-width:0}table.table-bordered tbody td,table.table-bordered tbody th{border-left-width:0;border-bottom-width:0}table.table-bordered td:last-child,table.table-bordered th:last-child{border-right-width:0}div.dataTables_scrollHead table.table-bordered{border-bottom-width:0}.table.dataTable tbody tr.active td,.table.dataTable tbody tr.active th{background-color:#08C;color:#fff}.table.dataTable tbody tr.active:hover td,.table.dataTable tbody tr.active:hover th{background-color:#0075b0!important}.table.dataTable tbody tr.active td>a,.table.dataTable tbody tr.active th>a{color:#fff}.table-striped.dataTable tbody tr.active:nth-child(odd) td,.table-striped.dataTable tbody tr.active:nth-child(odd) th{background-color:#017ebc}table.DTTT_selectable tbody tr{cursor:pointer}div.DTTT .btn{color:#333!important;font-size:12px}div.DTTT .btn:hover{text-decoration:none!important}ul.DTTT_dropdown.dropdown-menu{z-index:2003}ul.DTTT_dropdown.dropdown-menu a{color:#333!important}ul.DTTT_dropdown.dropdown-menu li{position:relative}ul.DTTT_dropdown.dropdown-menu li:hover a{background-color:#08c;color:#fff!important}div.DTTT_collection_background{z-index:2002}div.DTTT_print_info{position:fixed;top:50%;left:50%;width:400px;height:150px;margin-left:-200px;margin-top:-75px;text-align:center;color:#333;padding:10px 30px;opacity:.95;background-color:#fff;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0,0,0,.5);box-shadow:0 3px 7px rgba(0,0,0,.5)}div.DTTT_print_info h6{font-weight:400;font-size:28px;line-height:28px;margin:1em}div.DTTT_print_info p{font-size:14px;line-height:20px}div.dataTables_processing{position:absolute;top:50%;left:50%;width:100%;height:60px;margin-left:-50%;margin-top:-25px;padding-top:20px;padding-bottom:20px;text-align:center;font-size:1.2em;background:-webkit-gradient(linear,left top,right top,color-stop(0%,rgba(255,255,255,0)),color-stop(25%,rgba(255,255,255,.9)),color-stop(75%,rgba(255,255,255,.9)),color-stop(100%,rgba(255,255,255,0)));background:-webkit-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,.9) 25%,rgba(255,255,255,.9) 75%,rgba(255,255,255,0) 100%);background:-moz-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,.9) 25%,rgba(255,255,255,.9) 75%,rgba(255,255,255,0) 100%);background:-ms-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,.9) 25%,rgba(255,255,255,.9) 75%,rgba(255,255,255,0) 100%);background:-o-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,.9) 25%,rgba(255,255,255,.9) 75%,rgba(255,255,255,0) 100%);background:linear-gradient(to right,rgba(255,255,255,0) 0,rgba(255,255,255,.9) 25%,rgba(255,255,255,.9) 75%,rgba(255,255,255,0) 100%)}div.DTFC_LeftFootWrapper table,div.DTFC_LeftHeadWrapper table,div.DTFC_RightFootWrapper table,div.DTFC_RightHeadWrapper table,table.DTFC_Cloned tr.even{background-color:#fff;margin-bottom:0}div.DTFC_LeftHeadWrapper table,div.DTFC_RightHeadWrapper table{border-bottom:none!important;margin-bottom:0!important;border-top-right-radius:0!important;border-bottom-left-radius:0!important;border-bottom-right-radius:0!important}div.DTFC_LeftHeadWrapper table thead tr:last-child td:first-child,div.DTFC_LeftHeadWrapper table thead tr:last-child th:first-child,div.DTFC_RightHeadWrapper table thead tr:last-child td:first-child,div.DTFC_RightHeadWrapper table thead tr:last-child th:first-child{border-bottom-left-radius:0!important;border-bottom-right-radius:0!important}div.DTFC_LeftBodyWrapper table,div.DTFC_RightBodyWrapper table{border-top:none;margin:0!important}div.DTFC_LeftBodyWrapper tbody tr:first-child td,div.DTFC_LeftBodyWrapper tbody tr:first-child th,div.DTFC_RightBodyWrapper tbody tr:first-child td,div.DTFC_RightBodyWrapper tbody tr:first-child th{border-top:none}div.DTFC_LeftFootWrapper table,div.DTFC_RightFootWrapper table{border-top:none;margin-top:0!important}div.FixedHeader_Cloned table{margin:0!important} -------------------------------------------------------------------------------- /static/admin/js/urlify.js: -------------------------------------------------------------------------------- 1 | var LATIN_MAP = { 2 | 'À': 'A', 'Á': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A', 'Å': 'A', 'Æ': 'AE', 'Ç': 3 | 'C', 'È': 'E', 'É': 'E', 'Ê': 'E', 'Ë': 'E', 'Ì': 'I', 'Í': 'I', 'Î': 'I', 4 | 'Ï': 'I', 'Ð': 'D', 'Ñ': 'N', 'Ò': 'O', 'Ó': 'O', 'Ô': 'O', 'Õ': 'O', 'Ö': 5 | 'O', 'Ő': 'O', 'Ø': 'O', 'Ù': 'U', 'Ú': 'U', 'Û': 'U', 'Ü': 'U', 'Ű': 'U', 6 | 'Ý': 'Y', 'Þ': 'TH', 'Ÿ': 'Y', 'ß': 'ss', 'à':'a', 'á':'a', 'â': 'a', 'ã': 7 | 'a', 'ä': 'a', 'å': 'a', 'æ': 'ae', 'ç': 'c', 'è': 'e', 'é': 'e', 'ê': 'e', 8 | 'ë': 'e', 'ì': 'i', 'í': 'i', 'î': 'i', 'ï': 'i', 'ð': 'd', 'ñ': 'n', 'ò': 9 | 'o', 'ó': 'o', 'ô': 'o', 'õ': 'o', 'ö': 'o', 'ő': 'o', 'ø': 'o', 'ù': 'u', 10 | 'ú': 'u', 'û': 'u', 'ü': 'u', 'ű': 'u', 'ý': 'y', 'þ': 'th', 'ÿ': 'y' 11 | }; 12 | var LATIN_SYMBOLS_MAP = { 13 | '©':'(c)' 14 | }; 15 | var GREEK_MAP = { 16 | 'α':'a', 'β':'b', 'γ':'g', 'δ':'d', 'ε':'e', 'ζ':'z', 'η':'h', 'θ':'8', 17 | 'ι':'i', 'κ':'k', 'λ':'l', 'μ':'m', 'ν':'n', 'ξ':'3', 'ο':'o', 'π':'p', 18 | 'ρ':'r', 'σ':'s', 'τ':'t', 'υ':'y', 'φ':'f', 'χ':'x', 'ψ':'ps', 'ω':'w', 19 | 'ά':'a', 'έ':'e', 'ί':'i', 'ό':'o', 'ύ':'y', 'ή':'h', 'ώ':'w', 'ς':'s', 20 | 'ϊ':'i', 'ΰ':'y', 'ϋ':'y', 'ΐ':'i', 21 | 'Α':'A', 'Β':'B', 'Γ':'G', 'Δ':'D', 'Ε':'E', 'Ζ':'Z', 'Η':'H', 'Θ':'8', 22 | 'Ι':'I', 'Κ':'K', 'Λ':'L', 'Μ':'M', 'Ν':'N', 'Ξ':'3', 'Ο':'O', 'Π':'P', 23 | 'Ρ':'R', 'Σ':'S', 'Τ':'T', 'Υ':'Y', 'Φ':'F', 'Χ':'X', 'Ψ':'PS', 'Ω':'W', 24 | 'Ά':'A', 'Έ':'E', 'Ί':'I', 'Ό':'O', 'Ύ':'Y', 'Ή':'H', 'Ώ':'W', 'Ϊ':'I', 25 | 'Ϋ':'Y' 26 | }; 27 | var TURKISH_MAP = { 28 | 'ş':'s', 'Ş':'S', 'ı':'i', 'İ':'I', 'ç':'c', 'Ç':'C', 'ü':'u', 'Ü':'U', 29 | 'ö':'o', 'Ö':'O', 'ğ':'g', 'Ğ':'G' 30 | }; 31 | var RUSSIAN_MAP = { 32 | 'а':'a', 'б':'b', 'в':'v', 'г':'g', 'д':'d', 'е':'e', 'ё':'yo', 'ж':'zh', 33 | 'з':'z', 'и':'i', 'й':'j', 'к':'k', 'л':'l', 'м':'m', 'н':'n', 'о':'o', 34 | 'п':'p', 'р':'r', 'с':'s', 'т':'t', 'у':'u', 'ф':'f', 'х':'h', 'ц':'c', 35 | 'ч':'ch', 'ш':'sh', 'щ':'sh', 'ъ':'', 'ы':'y', 'ь':'', 'э':'e', 'ю':'yu', 36 | 'я':'ya', 37 | 'А':'A', 'Б':'B', 'В':'V', 'Г':'G', 'Д':'D', 'Е':'E', 'Ё':'Yo', 'Ж':'Zh', 38 | 'З':'Z', 'И':'I', 'Й':'J', 'К':'K', 'Л':'L', 'М':'M', 'Н':'N', 'О':'O', 39 | 'П':'P', 'Р':'R', 'С':'S', 'Т':'T', 'У':'U', 'Ф':'F', 'Х':'H', 'Ц':'C', 40 | 'Ч':'Ch', 'Ш':'Sh', 'Щ':'Sh', 'Ъ':'', 'Ы':'Y', 'Ь':'', 'Э':'E', 'Ю':'Yu', 41 | 'Я':'Ya' 42 | }; 43 | var UKRAINIAN_MAP = { 44 | 'Є':'Ye', 'І':'I', 'Ї':'Yi', 'Ґ':'G', 'є':'ye', 'і':'i', 'ї':'yi', 'ґ':'g' 45 | }; 46 | var CZECH_MAP = { 47 | 'č':'c', 'ď':'d', 'ě':'e', 'ň': 'n', 'ř':'r', 'š':'s', 'ť':'t', 'ů':'u', 48 | 'ž':'z', 'Č':'C', 'Ď':'D', 'Ě':'E', 'Ň': 'N', 'Ř':'R', 'Š':'S', 'Ť':'T', 49 | 'Ů':'U', 'Ž':'Z' 50 | }; 51 | var POLISH_MAP = { 52 | 'ą':'a', 'ć':'c', 'ę':'e', 'ł':'l', 'ń':'n', 'ó':'o', 'ś':'s', 'ź':'z', 53 | 'ż':'z', 'Ą':'A', 'Ć':'C', 'Ę':'E', 'Ł':'L', 'Ń':'N', 'Ó':'O', 'Ś':'S', 54 | 'Ź':'Z', 'Ż':'Z' 55 | }; 56 | var LATVIAN_MAP = { 57 | 'ā':'a', 'č':'c', 'ē':'e', 'ģ':'g', 'ī':'i', 'ķ':'k', 'ļ':'l', 'ņ':'n', 58 | 'š':'s', 'ū':'u', 'ž':'z', 'Ā':'A', 'Č':'C', 'Ē':'E', 'Ģ':'G', 'Ī':'I', 59 | 'Ķ':'K', 'Ļ':'L', 'Ņ':'N', 'Š':'S', 'Ū':'U', 'Ž':'Z' 60 | }; 61 | var ARABIC_MAP = { 62 | 'أ':'a', 'ب':'b', 'ت':'t', 'ث': 'th', 'ج':'g', 'ح':'h', 'خ':'kh', 'د':'d', 63 | 'ذ':'th', 'ر':'r', 'ز':'z', 'س':'s', 'ش':'sh', 'ص':'s', 'ض':'d', 'ط':'t', 64 | 'ظ':'th', 'ع':'aa', 'غ':'gh', 'ف':'f', 'ق':'k', 'ك':'k', 'ل':'l', 'م':'m', 65 | 'ن':'n', 'ه':'h', 'و':'o', 'ي':'y' 66 | }; 67 | var LITHUANIAN_MAP = { 68 | 'ą':'a', 'č':'c', 'ę':'e', 'ė':'e', 'į':'i', 'š':'s', 'ų':'u', 'ū':'u', 69 | 'ž':'z', 70 | 'Ą':'A', 'Č':'C', 'Ę':'E', 'Ė':'E', 'Į':'I', 'Š':'S', 'Ų':'U', 'Ū':'U', 71 | 'Ž':'Z' 72 | }; 73 | var SERBIAN_MAP = { 74 | 'ђ':'dj', 'ј':'j', 'љ':'lj', 'њ':'nj', 'ћ':'c', 'џ':'dz', 'đ':'dj', 75 | 'Ђ':'Dj', 'Ј':'j', 'Љ':'Lj', 'Њ':'Nj', 'Ћ':'C', 'Џ':'Dz', 'Đ':'Dj' 76 | }; 77 | var AZERBAIJANI_MAP = { 78 | 'ç':'c', 'ə':'e', 'ğ':'g', 'ı':'i', 'ö':'o', 'ş':'s', 'ü':'u', 79 | 'Ç':'C', 'Ə':'E', 'Ğ':'G', 'İ':'I', 'Ö':'O', 'Ş':'S', 'Ü':'U' 80 | }; 81 | 82 | var ALL_DOWNCODE_MAPS = [ 83 | LATIN_MAP, 84 | LATIN_SYMBOLS_MAP, 85 | GREEK_MAP, 86 | TURKISH_MAP, 87 | RUSSIAN_MAP, 88 | UKRAINIAN_MAP, 89 | CZECH_MAP, 90 | POLISH_MAP, 91 | LATVIAN_MAP, 92 | ARABIC_MAP, 93 | LITHUANIAN_MAP, 94 | SERBIAN_MAP, 95 | AZERBAIJANI_MAP 96 | ]; 97 | 98 | var Downcoder = { 99 | 'Initialize': function() { 100 | if (Downcoder.map) { // already made 101 | return; 102 | } 103 | Downcoder.map = {}; 104 | Downcoder.chars = []; 105 | for (var i=0; i5':" ▴";this.appendChild(sortrevind);return}if(this.className.search(/\bsorttable_sorted_reverse\b/)!=-1){sorttable.reverse(this.sorttable_tbody);this.className=this.className.replace("sorttable_sorted_reverse","sorttable_sorted");this.removeChild(document.getElementById("sorttable_sortrevind"));sortfwdind=document.createElement("span");sortfwdind.id="sorttable_sortfwdind";sortfwdind.innerHTML=stIsIE?' 6':" ▾";this.appendChild(sortfwdind);return}theadrow=this.parentNode;forEach(theadrow.childNodes,function(e){if(e.nodeType==1){e.className=e.className.replace("sorttable_sorted_reverse","");e.className=e.className.replace("sorttable_sorted","")}});sortfwdind=document.getElementById("sorttable_sortfwdind");if(sortfwdind){sortfwdind.parentNode.removeChild(sortfwdind)}sortrevind=document.getElementById("sorttable_sortrevind");if(sortrevind){sortrevind.parentNode.removeChild(sortrevind)}this.className+=" sorttable_sorted";sortfwdind=document.createElement("span");sortfwdind.id="sorttable_sortfwdind";sortfwdind.innerHTML=stIsIE?' 6':" ▾";this.appendChild(sortfwdind);row_array=[];col=this.sorttable_columnindex;rows=this.sorttable_tbody.rows;for(var t=0;t12){return sorttable.sort_ddmm}else if(second>12){return sorttable.sort_mmdd}else{sortfn=sorttable.sort_ddmm}}}}return sortfn},getInnerText:function(e){if(!e)return"";hasInputs=typeof e.getElementsByTagName=="function"&&e.getElementsByTagName("input").length;if(e.getAttribute("sorttable_customkey")!=null){return e.getAttribute("sorttable_customkey")}else if(typeof e.textContent!="undefined"&&!hasInputs){return e.textContent.replace(/^\s+|\s+$/g,"")}else if(typeof e.innerText!="undefined"&&!hasInputs){return e.innerText.replace(/^\s+|\s+$/g,"")}else if(typeof e.text!="undefined"&&!hasInputs){return e.text.replace(/^\s+|\s+$/g,"")}else{switch(e.nodeType){case 3:if(e.nodeName.toLowerCase()=="input"){return e.value.replace(/^\s+|\s+$/g,"")};case 4:return e.nodeValue.replace(/^\s+|\s+$/g,"");break;case 1:case 11:var t="";for(var n=0;n=0;t--){e.appendChild(newrows[t])}delete newrows},sort_numeric:function(e,t){aa=parseFloat(e[0].replace(/[^0-9.-]/g,""));if(isNaN(aa))aa=0;bb=parseFloat(t[0].replace(/[^0-9.-]/g,""));if(isNaN(bb))bb=0;return aa-bb},sort_alpha:function(e,t){if(e[0]==t[0])return 0;if(e[0]0){var o=e[s];e[s]=e[s+1];e[s+1]=o;i=true}}r--;if(!i)break;for(var s=r;s>n;--s){if(t(e[s],e[s-1])<0){var o=e[s];e[s]=e[s-1];e[s-1]=o;i=true}}n++}}};if(document.addEventListener){document.addEventListener("DOMContentLoaded",sorttable.init,false)}if(/WebKit/i.test(navigator.userAgent)){var _timer=setInterval(function(){if(/loaded|complete/.test(document.readyState)){sorttable.init()}},10)}window.onload=sorttable.init;dean_addEvent.guid=1;fixEvent.preventDefault=function(){this.returnValue=false};fixEvent.stopPropagation=function(){this.cancelBubble=true};if(!Array.forEach){Array.forEach=function(e,t,n){for(var r=0;r=0) && parseFloat(navigator.appVersion); 5 | var isIE = ((document.all) && (!isOpera)) && parseFloat(navigator.appVersion.split("MSIE ")[1].split(";")[0]); 6 | 7 | // Cross-browser event handlers. 8 | function addEvent(obj, evType, fn) { 9 | if (obj.addEventListener) { 10 | obj.addEventListener(evType, fn, false); 11 | return true; 12 | } else if (obj.attachEvent) { 13 | var r = obj.attachEvent("on" + evType, fn); 14 | return r; 15 | } else { 16 | return false; 17 | } 18 | } 19 | 20 | function removeEvent(obj, evType, fn) { 21 | if (obj.removeEventListener) { 22 | obj.removeEventListener(evType, fn, false); 23 | return true; 24 | } else if (obj.detachEvent) { 25 | obj.detachEvent("on" + evType, fn); 26 | return true; 27 | } else { 28 | return false; 29 | } 30 | } 31 | 32 | function cancelEventPropagation(e) { 33 | if (!e) e = window.event; 34 | e.cancelBubble = true; 35 | if (e.stopPropagation) e.stopPropagation(); 36 | } 37 | 38 | // quickElement(tagType, parentReference [, textInChildNode, attribute, attributeValue ...]); 39 | function quickElement() { 40 | var obj = document.createElement(arguments[0]); 41 | if (arguments[2]) { 42 | var textNode = document.createTextNode(arguments[2]); 43 | obj.appendChild(textNode); 44 | } 45 | var len = arguments.length; 46 | for (var i = 3; i < len; i += 2) { 47 | obj.setAttribute(arguments[i], arguments[i+1]); 48 | } 49 | arguments[1].appendChild(obj); 50 | return obj; 51 | } 52 | 53 | // "a" is reference to an object 54 | function removeChildren(a) { 55 | while (a.hasChildNodes()) a.removeChild(a.lastChild); 56 | } 57 | 58 | // ---------------------------------------------------------------------------- 59 | // Cross-browser xmlhttp object 60 | // from http://jibbering.com/2002/4/httprequest.html 61 | // ---------------------------------------------------------------------------- 62 | var xmlhttp; 63 | /*@cc_on @*/ 64 | /*@if (@_jscript_version >= 5) 65 | try { 66 | xmlhttp = new ActiveXObject("Msxml2.XMLHTTP"); 67 | } catch (e) { 68 | try { 69 | xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); 70 | } catch (E) { 71 | xmlhttp = false; 72 | } 73 | } 74 | @else 75 | xmlhttp = false; 76 | @end @*/ 77 | if (!xmlhttp && typeof XMLHttpRequest != 'undefined') { 78 | xmlhttp = new XMLHttpRequest(); 79 | } 80 | 81 | // ---------------------------------------------------------------------------- 82 | // Find-position functions by PPK 83 | // See http://www.quirksmode.org/js/findpos.html 84 | // ---------------------------------------------------------------------------- 85 | function findPosX(obj) { 86 | var curleft = 0; 87 | if (obj.offsetParent) { 88 | while (obj.offsetParent) { 89 | curleft += obj.offsetLeft - ((isOpera) ? 0 : obj.scrollLeft); 90 | obj = obj.offsetParent; 91 | } 92 | // IE offsetParent does not include the top-level 93 | if (isIE && obj.parentElement){ 94 | curleft += obj.offsetLeft - obj.scrollLeft; 95 | } 96 | } else if (obj.x) { 97 | curleft += obj.x; 98 | } 99 | return curleft; 100 | } 101 | 102 | function findPosY(obj) { 103 | var curtop = 0; 104 | if (obj.offsetParent) { 105 | while (obj.offsetParent) { 106 | curtop += obj.offsetTop - ((isOpera) ? 0 : obj.scrollTop); 107 | obj = obj.offsetParent; 108 | } 109 | // IE offsetParent does not include the top-level 110 | if (isIE && obj.parentElement){ 111 | curtop += obj.offsetTop - obj.scrollTop; 112 | } 113 | } else if (obj.y) { 114 | curtop += obj.y; 115 | } 116 | return curtop; 117 | } 118 | 119 | //----------------------------------------------------------------------------- 120 | // Date object extensions 121 | // ---------------------------------------------------------------------------- 122 | 123 | Date.prototype.getTwelveHours = function() { 124 | hours = this.getHours(); 125 | if (hours == 0) { 126 | return 12; 127 | } 128 | else { 129 | return hours <= 12 ? hours : hours-12 130 | } 131 | } 132 | 133 | Date.prototype.getTwoDigitMonth = function() { 134 | return (this.getMonth() < 9) ? '0' + (this.getMonth()+1) : (this.getMonth()+1); 135 | } 136 | 137 | Date.prototype.getTwoDigitDate = function() { 138 | return (this.getDate() < 10) ? '0' + this.getDate() : this.getDate(); 139 | } 140 | 141 | Date.prototype.getTwoDigitTwelveHour = function() { 142 | return (this.getTwelveHours() < 10) ? '0' + this.getTwelveHours() : this.getTwelveHours(); 143 | } 144 | 145 | Date.prototype.getTwoDigitHour = function() { 146 | return (this.getHours() < 10) ? '0' + this.getHours() : this.getHours(); 147 | } 148 | 149 | Date.prototype.getTwoDigitMinute = function() { 150 | return (this.getMinutes() < 10) ? '0' + this.getMinutes() : this.getMinutes(); 151 | } 152 | 153 | Date.prototype.getTwoDigitSecond = function() { 154 | return (this.getSeconds() < 10) ? '0' + this.getSeconds() : this.getSeconds(); 155 | } 156 | 157 | Date.prototype.getHourMinute = function() { 158 | return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute(); 159 | } 160 | 161 | Date.prototype.getHourMinuteSecond = function() { 162 | return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute() + ':' + this.getTwoDigitSecond(); 163 | } 164 | 165 | Date.prototype.strftime = function(format) { 166 | var fields = { 167 | c: this.toString(), 168 | d: this.getTwoDigitDate(), 169 | H: this.getTwoDigitHour(), 170 | I: this.getTwoDigitTwelveHour(), 171 | m: this.getTwoDigitMonth(), 172 | M: this.getTwoDigitMinute(), 173 | p: (this.getHours() >= 12) ? 'PM' : 'AM', 174 | S: this.getTwoDigitSecond(), 175 | w: '0' + this.getDay(), 176 | x: this.toLocaleDateString(), 177 | X: this.toLocaleTimeString(), 178 | y: ('' + this.getFullYear()).substr(2, 4), 179 | Y: '' + this.getFullYear(), 180 | '%' : '%' 181 | }; 182 | var result = '', i = 0; 183 | while (i < format.length) { 184 | if (format.charAt(i) === '%') { 185 | result = result + fields[format.charAt(i + 1)]; 186 | ++i; 187 | } 188 | else { 189 | result = result + format.charAt(i); 190 | } 191 | ++i; 192 | } 193 | return result; 194 | } 195 | 196 | // ---------------------------------------------------------------------------- 197 | // String object extensions 198 | // ---------------------------------------------------------------------------- 199 | String.prototype.pad_left = function(pad_length, pad_string) { 200 | var new_string = this; 201 | for (var i = 0; new_string.length < pad_length; i++) { 202 | new_string = pad_string + new_string; 203 | } 204 | return new_string; 205 | } 206 | 207 | String.prototype.strptime = function(format) { 208 | var split_format = format.split(/[.\-/]/); 209 | var date = this.split(/[.\-/]/); 210 | var i = 0; 211 | while (i < split_format.length) { 212 | switch (split_format[i]) { 213 | case "%d": 214 | var day = date[i]; 215 | break; 216 | case "%m": 217 | var month = date[i] - 1; 218 | break; 219 | case "%Y": 220 | var year = date[i]; 221 | break; 222 | case "%y": 223 | var year = date[i]; 224 | break; 225 | } 226 | ++i; 227 | }; 228 | return new Date(year, month, day); 229 | } 230 | 231 | // ---------------------------------------------------------------------------- 232 | // Get the computed style for and element 233 | // ---------------------------------------------------------------------------- 234 | function getStyle(oElm, strCssRule){ 235 | var strValue = ""; 236 | if(document.defaultView && document.defaultView.getComputedStyle){ 237 | strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule); 238 | } 239 | else if(oElm.currentStyle){ 240 | strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1){ 241 | return p1.toUpperCase(); 242 | }); 243 | strValue = oElm.currentStyle[strCssRule]; 244 | } 245 | return strValue; 246 | } 247 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PCAPOptikon 2 | PCAPOptikon is a project that will provide a web GUI and some REST APIs to analyze arbitrary PCAP files with Suricata IDS. 3 | 4 | PCAPOptikon uses Django 1.8 and TastyPie. 5 | 6 | ## Docker image 7 | Docker is surely the quickest way to get started with PCAPOptikon. You can download it by running: 8 | 9 | $ sudo docker pull certego/pcapoptikon 10 | 11 | Now, I would suggest you to create a _data-only_ docker container to grant the persistence of MySQL data (`certego/pcapoptikon` exposes the volume `/var/lib/mysql`): 12 | 13 | $ sudo docker create --name pcapoptikon_data certego/pcapoptikon 14 | 15 | Now, a couple other directories that you may want to create locally on your host are: 16 | 17 | $ mkdir -p /var/log/pcapoptikon 18 | $ mkdir -p /var/tmp/pcapoptikon 19 | 20 | You can then run your docker container this way: 21 | 22 | $ sudo docker run -d --name=pcapoptikon --volumes-from=pcapoptikon_data -v=/var/log/pcapoptikon:/var/log -v=/var/tmp/pcapoptikon:/var/tmp -p=8000:8000 certego/pcapoptikon 23 | 24 | This command will create a new daemonized docker container, called `pcapoptikon`, mounting `/var/lib/mysql` from `pcapoptikon_data` and mounting `/var/log` and `/var/tmp` from the folders you just created on the host. It will also expose the container's port 8000 on localhost, so that you can simply point your browser to: http://localhost:8000/ 25 | 26 | Default username and password are `admin:admin`. You will find some useful info on your host's `/var/log/pcapoptikon/pcapoptikon_startup.log` and in the other log files in the same folder. 27 | 28 | Should you own a valid ETPro oinkcode, you can supply it to the startup script that way: 29 | 30 | $ sudo docker run -d --name=pcapoptikon --volumes-from=pcapoptikon_data -v=/var/log/pcapoptikon:/var/log -v=/var/tmp/pcapoptikon:/var/tmp -p=8000:8000 certego/pcapoptikon /opt/pcapoptikon/start.sh 31 | 32 | Please note that, in this last case, you have to specify the docker instance's entry point `/opt/pcapoptikon/start.sh` yourself. 33 | 34 | ### Working with local.rules 35 | PCAPOptikon can be very useful when testing your own rules. You can do so by modifying the local.rules file. In this case, I suggest that you mount the whole rules folder as a volume on your local disk, so that you can easily modify them with your favourite editor. As an example: 36 | 37 | $ docker run -d --name=pcapoptikon --volumes-from=pcapoptikon_data -v=/var/log/pcapoptikon:/var/log -v=/var/tmp/pcapoptikon:/var/tmp -v=/var/tmp/suricata/rules:/etc/suricata/rules -p=8000:8000 certego/pcapoptikon 38 | 39 | This would mount PCAPOptikon's rules dir on your local `/var/tmp/suricata/rules` dir (please make sure it exists). 40 | 41 | To reload the newly modified rules, you can simply restart the whole docker container, but this will also run oinkmaster and download the whole ruleset from scratch. A quicker way is to use the included `reload.sh` script, that will manage all the necessary daemons for you: 42 | 43 | $ docker exec -ti pcapoptikon /opt/pcapoptikon/reload.sh 44 | 45 | This script has its own log file inside the container: `/var/log/pcapoptikon_reload.log`. 46 | 47 | ## Manual Install 48 | 49 | ### System-wide requirements 50 | Please make sure the following packages are installed: 51 | 52 | $ apt-get install mysql-server mysql-common mysql-client libmysqlclient-dev python-dev 53 | 54 | PCAPOptikon needs a Suricata instance running on the same host as the rest of the program. Installing it is pretty simple: 55 | 56 | $ sudo apt-get install software-properties-common python-software-properties 57 | $ sudo add-apt-repository -y ppa:oisf/suricata-stable 58 | $ sudo apt-get update 59 | $ sudo apt-get install suricata 60 | 61 | PCAPOptikon expects to find `classification.config` and `reference` under the folder `/etc/suricata/rules/` instead than just `/etc/suricata/` (which is the default), so please make sure that your `/etc/suricata/suricata.yaml` contains the following: 62 | 63 | classification-file: /etc/suricata/rules/classification.config 64 | reference-file: /etc/suricata/rules/reference.config 65 | 66 | It is also recommended that you install `oinkmaster` (or `pulledpork`) and configure it as appropriate (rule url, oinkcode, etc). 67 | 68 | You may also want to enable the following option in `/etc/suricata/suricata.yaml`: 69 | 70 | - rule-reload: true 71 | 72 | Doing so will let Suricata reload the rules whenever you send the process a `SIGUSR2` signal, without having to restart it. So, for example, you can run the following: 73 | 74 | $ oinkmaster -C /etc/oinkmaster.conf -o /etc/suricata/rules && killall -SIGUSR2 suricata 75 | 76 | If you're using the `./start.sh` script which comes with PCAPOptikon, it will do that for you. 77 | 78 | ### Python requirements 79 | First of all, this will clone PCAPOptikon in your current working directory: 80 | 81 | $ git clone https://github.com/certego/pcapoptikon.git 82 | 83 | **Please consider using VirtualEnv, especially if you already have other projects running on Django versions other than 1.8**. Installing VirtualEnv is extremely easy: 84 | 85 | $ sudo pip install virtualenv 86 | 87 | Actually, you only need sudo if you're installing `virtualenv` globally (which I suggest you to do). Now, `cd` to PCAPOptikon's root directory to create and activate your virtual environment: 88 | 89 | $ cd pcapoptikon 90 | $ virtualenv venv 91 | $ source venv/bin/activate 92 | 93 | That's all. The first command will create a folder named `venv`, with a copy of the Python executable, pip and some other tools; the second command will activate the virtual environment for you. From now on, every time you run `pip install`, the requested modules will be installed locally, without touching your global Python environment. 94 | 95 | Now make sure that the virtual environment is still active and then install the requirements by running: 96 | 97 | $ pip install -r requirements.txt 98 | 99 | Now, use pip to copy the `suricatasc` module from your system-wide python install: 100 | 101 | $ pip install --no-index --find-links=/usr/lib/pymodules/python2.7/ suricatasc 102 | 103 | Now you're ready to install PCAPOptikon itself. First, please create a new empty MySQL database and populate it: 104 | 105 | $ mysqladmin create pcapoptikon 106 | $ python manage.py migrate 107 | 108 | You are now ready to create a new user by running: 109 | 110 | $ python manage.py createsuperuser 111 | 112 | ## Running PCAPOptikon 113 | Launch the daemon with the following command (from PCAPOptikon's root directory): 114 | 115 | $ python manage.py run_daemon 116 | 117 | Now, you can run the web GUI with the following: 118 | 119 | $ python manage.py runserver 120 | 121 | By default, this will run the server on 127.0.0.1:8000 (you will only be able to reach it from localhost). If you wish to reach it from the local network, simply run: 122 | 123 | $ python manage.py runserver 0.0.0.0:8000 124 | 125 | or whatever other port you want. 126 | 127 | ## Using PCAPOptikon 128 | 129 | ### The GUI 130 | Simply point your browser to http://127.0.0.1:8000/ (or whatever ip/port you chose) and enjoy a simple web GUI that lets you submit new tasks and show their results. 131 | 132 | ### The Admin GUI 133 | PCAPOptikon includes a standard Django admin GUI that will help you manage your tasks (you can delete or modify them from there): http://127.0.0.1:8000/admin/ 134 | 135 | ### The APIs 136 | PCAPOptikon also includes a full REST API made with `django-tastypie`. To access it programatically, it's recommended that you use an API key. You can create one via the Admin GUI (see above). 137 | 138 | The API endpoint can be found at http://127.0.0.1:8000/api/v1/?format=json 139 | 140 | To list all the pending tasks, just visit: http://127.0.0.1:8000/api/v1/task/?format=json 141 | 142 | To view the status of task #1: http://127.0.0.1:8000/api/v1/task/1/?format=json 143 | 144 | Making a POST request to http://127.0.0.1:8000/api/v1/task/ will create a new task for you. Please note that the PCAP file you're sending will need to be encoded with base64. An example Python script making a POST request to create a new task is the following: 145 | 146 | import base64 147 | import json 148 | import os 149 | import requests 150 | 151 | 152 | api_url = "http://127.0.0.1:8000/api/v1/" 153 | api_user = "" 154 | api_key = "" 155 | pcap_file = "/path/to/dump.pcap" 156 | 157 | headers = { 158 | 'Authorization': 'ApiKey {}:{}'.format(api_user, api_key), 159 | 'Content-Type': 'application/json' 160 | } 161 | 162 | with open(pcap_file, 'rb') as i: 163 | payload = { 164 | 'pcap_file': { 165 | "name": os.path.basename(self.pcap_path), 166 | "file": base64.b64encode(i.read()), 167 | "content_type": "application/vnd.tcpdump.pcap", 168 | } 169 | } 170 | 171 | response = requests.post( 172 | os.path.join(api_url, 'task/'), 173 | data=json.dumps(payload), 174 | headers=headers 175 | ) 176 | 177 | if response.status_code == 201: 178 | retrieve_url = response.headers['Location'] 179 | log.debug("Will need to poll URL: {}".format(retrieve_url)) 180 | 181 | # Now you can start polling the address specified by retrieve_url 182 | # to retrieve the results, which will be available in a few seconds. 183 | else: 184 | # Something went wrong 185 | raise Exception("Got status code: {}".format(response.status_code)) 186 | -------------------------------------------------------------------------------- /static/admin/js/SelectFilter2.js: -------------------------------------------------------------------------------- 1 | /* 2 | SelectFilter2 - Turns a multiple-select box into a filter interface. 3 | 4 | Requires core.js, SelectBox.js and addevent.js. 5 | */ 6 | (function($) { 7 | function findForm(node) { 8 | // returns the node of the form containing the given node 9 | if (node.tagName.toLowerCase() != 'form') { 10 | return findForm(node.parentNode); 11 | } 12 | return node; 13 | } 14 | 15 | window.SelectFilter = { 16 | init: function(field_id, field_name, is_stacked, admin_static_prefix) { 17 | if (field_id.match(/__prefix__/)){ 18 | // Don't initialize on empty forms. 19 | return; 20 | } 21 | var from_box = document.getElementById(field_id); 22 | from_box.id += '_from'; // change its ID 23 | from_box.className = 'filtered'; 24 | 25 | var ps = from_box.parentNode.getElementsByTagName('p'); 26 | for (var i=0; i, because it just gets in the way. 29 | from_box.parentNode.removeChild(ps[i]); 30 | } else if (ps[i].className.indexOf("help") != -1) { 31 | // Move help text up to the top so it isn't below the select 32 | // boxes or wrapped off on the side to the right of the add 33 | // button: 34 | from_box.parentNode.insertBefore(ps[i], from_box.parentNode.firstChild); 35 | } 36 | } 37 | 38 | //
      or
      39 | var selector_div = quickElement('div', from_box.parentNode); 40 | selector_div.className = is_stacked ? 'selector stacked' : 'selector'; 41 | 42 | //
      43 | var selector_available = quickElement('div', selector_div); 44 | selector_available.className = 'selector-available'; 45 | var title_available = quickElement('h2', selector_available, interpolate(gettext('Available %s') + ' ', [field_name])); 46 | quickElement('img', title_available, '', 'src', admin_static_prefix + 'img/icon-unknown.gif', 'width', '10', 'height', '10', 'class', 'help help-tooltip', 'title', interpolate(gettext('This is the list of available %s. You may choose some by selecting them in the box below and then clicking the "Choose" arrow between the two boxes.'), [field_name])); 47 | 48 | var filter_p = quickElement('p', selector_available, '', 'id', field_id + '_filter'); 49 | filter_p.className = 'selector-filter'; 50 | 51 | var search_filter_label = quickElement('label', filter_p, '', 'for', field_id + "_input"); 52 | 53 | var search_selector_img = quickElement('img', search_filter_label, '', 'src', admin_static_prefix + 'img/selector-search.gif', 'class', 'help-tooltip', 'alt', '', 'title', interpolate(gettext("Type into this box to filter down the list of available %s."), [field_name])); 54 | 55 | filter_p.appendChild(document.createTextNode(' ')); 56 | 57 | var filter_input = quickElement('input', filter_p, '', 'type', 'text', 'placeholder', gettext("Filter")); 58 | filter_input.id = field_id + '_input'; 59 | 60 | selector_available.appendChild(from_box); 61 | var choose_all = quickElement('a', selector_available, gettext('Choose all'), 'title', interpolate(gettext('Click to choose all %s at once.'), [field_name]), 'href', 'javascript: (function(){ SelectBox.move_all("' + field_id + '_from", "' + field_id + '_to"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_add_all_link'); 62 | choose_all.className = 'selector-chooseall'; 63 | 64 | //
        65 | var selector_chooser = quickElement('ul', selector_div); 66 | selector_chooser.className = 'selector-chooser'; 67 | var add_link = quickElement('a', quickElement('li', selector_chooser), gettext('Choose'), 'title', gettext('Choose'), 'href', 'javascript: (function(){ SelectBox.move("' + field_id + '_from","' + field_id + '_to"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_add_link'); 68 | add_link.className = 'selector-add'; 69 | var remove_link = quickElement('a', quickElement('li', selector_chooser), gettext('Remove'), 'title', gettext('Remove'), 'href', 'javascript: (function(){ SelectBox.move("' + field_id + '_to","' + field_id + '_from"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_remove_link'); 70 | remove_link.className = 'selector-remove'; 71 | 72 | //
        73 | var selector_chosen = quickElement('div', selector_div); 74 | selector_chosen.className = 'selector-chosen'; 75 | var title_chosen = quickElement('h2', selector_chosen, interpolate(gettext('Chosen %s') + ' ', [field_name])); 76 | quickElement('img', title_chosen, '', 'src', admin_static_prefix + 'img/icon-unknown.gif', 'width', '10', 'height', '10', 'class', 'help help-tooltip', 'title', interpolate(gettext('This is the list of chosen %s. You may remove some by selecting them in the box below and then clicking the "Remove" arrow between the two boxes.'), [field_name])); 77 | 78 | var to_box = quickElement('select', selector_chosen, '', 'id', field_id + '_to', 'multiple', 'multiple', 'size', from_box.size, 'name', from_box.getAttribute('name')); 79 | to_box.className = 'filtered'; 80 | var clear_all = quickElement('a', selector_chosen, gettext('Remove all'), 'title', interpolate(gettext('Click to remove all chosen %s at once.'), [field_name]), 'href', 'javascript: (function() { SelectBox.move_all("' + field_id + '_to", "' + field_id + '_from"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_remove_all_link'); 81 | clear_all.className = 'selector-clearall'; 82 | 83 | from_box.setAttribute('name', from_box.getAttribute('name') + '_old'); 84 | 85 | // Set up the JavaScript event handlers for the select box filter interface 86 | addEvent(filter_input, 'keypress', function(e) { SelectFilter.filter_key_press(e, field_id); }); 87 | addEvent(filter_input, 'keyup', function(e) { SelectFilter.filter_key_up(e, field_id); }); 88 | addEvent(filter_input, 'keydown', function(e) { SelectFilter.filter_key_down(e, field_id); }); 89 | addEvent(from_box, 'change', function(e) { SelectFilter.refresh_icons(field_id) }); 90 | addEvent(to_box, 'change', function(e) { SelectFilter.refresh_icons(field_id) }); 91 | addEvent(from_box, 'dblclick', function() { SelectBox.move(field_id + '_from', field_id + '_to'); SelectFilter.refresh_icons(field_id); }); 92 | addEvent(to_box, 'dblclick', function() { SelectBox.move(field_id + '_to', field_id + '_from'); SelectFilter.refresh_icons(field_id); }); 93 | addEvent(findForm(from_box), 'submit', function() { SelectBox.select_all(field_id + '_to'); }); 94 | SelectBox.init(field_id + '_from'); 95 | SelectBox.init(field_id + '_to'); 96 | // Move selected from_box options to to_box 97 | SelectBox.move(field_id + '_from', field_id + '_to'); 98 | 99 | if (!is_stacked) { 100 | // In horizontal mode, give the same height to the two boxes. 101 | var j_from_box = $(from_box); 102 | var j_to_box = $(to_box); 103 | var resize_filters = function() { j_to_box.height($(filter_p).outerHeight() + j_from_box.outerHeight()); } 104 | if (j_from_box.outerHeight() > 0) { 105 | resize_filters(); // This fieldset is already open. Resize now. 106 | } else { 107 | // This fieldset is probably collapsed. Wait for its 'show' event. 108 | j_to_box.closest('fieldset').one('show.fieldset', resize_filters); 109 | } 110 | } 111 | 112 | // Initial icon refresh 113 | SelectFilter.refresh_icons(field_id); 114 | }, 115 | refresh_icons: function(field_id) { 116 | var from = $('#' + field_id + '_from'); 117 | var to = $('#' + field_id + '_to'); 118 | var is_from_selected = from.find('option:selected').length > 0; 119 | var is_to_selected = to.find('option:selected').length > 0; 120 | // Active if at least one item is selected 121 | $('#' + field_id + '_add_link').toggleClass('active', is_from_selected); 122 | $('#' + field_id + '_remove_link').toggleClass('active', is_to_selected); 123 | // Active if the corresponding box isn't empty 124 | $('#' + field_id + '_add_all_link').toggleClass('active', from.find('option').length > 0); 125 | $('#' + field_id + '_remove_all_link').toggleClass('active', to.find('option').length > 0); 126 | }, 127 | filter_key_press: function(event, field_id) { 128 | var from = document.getElementById(field_id + '_from'); 129 | // don't submit form if user pressed Enter 130 | if ((event.which && event.which == 13) || (event.keyCode && event.keyCode == 13)) { 131 | from.selectedIndex = 0; 132 | SelectBox.move(field_id + '_from', field_id + '_to'); 133 | from.selectedIndex = 0; 134 | event.preventDefault() 135 | return false; 136 | } 137 | }, 138 | filter_key_up: function(event, field_id) { 139 | var from = document.getElementById(field_id + '_from'); 140 | var temp = from.selectedIndex; 141 | SelectBox.filter(field_id + '_from', document.getElementById(field_id + '_input').value); 142 | from.selectedIndex = temp; 143 | return true; 144 | }, 145 | filter_key_down: function(event, field_id) { 146 | var from = document.getElementById(field_id + '_from'); 147 | // right arrow -- move across 148 | if ((event.which && event.which == 39) || (event.keyCode && event.keyCode == 39)) { 149 | var old_index = from.selectedIndex; 150 | SelectBox.move(field_id + '_from', field_id + '_to'); 151 | from.selectedIndex = (old_index == from.length) ? from.length - 1 : old_index; 152 | return false; 153 | } 154 | // down arrow -- wrap around 155 | if ((event.which && event.which == 40) || (event.keyCode && event.keyCode == 40)) { 156 | from.selectedIndex = (from.length == from.selectedIndex + 1) ? 0 : from.selectedIndex + 1; 157 | } 158 | // up arrow -- wrap around 159 | if ((event.which && event.which == 38) || (event.keyCode && event.keyCode == 38)) { 160 | from.selectedIndex = (from.selectedIndex == 0) ? from.length - 1 : from.selectedIndex - 1; 161 | } 162 | return true; 163 | } 164 | } 165 | 166 | })(django.jQuery); 167 | -------------------------------------------------------------------------------- /static/js/bootstrap-select.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * bootstrap-select v1.0 3 | * http://silviomoreto.github.io/bootstrap-select/ 4 | * 5 | * Copyright 2013 bootstrap-select 6 | * Licensed under the MIT license 7 | */ 8 | !function(b){var a=function(d,c,f){if(f){f.stopPropagation();f.preventDefault()}this.$element=b(d);this.$newElement=null;this.button=null;this.$menu=null;this.options=b.extend({},b.fn.selectpicker.defaults,this.$element.data(),typeof c=="object"&&c);if(this.options.title==null){this.options.title=this.$element.attr("title")}this.val=a.prototype.val;this.render=a.prototype.render;this.refresh=a.prototype.refresh;this.setStyle=a.prototype.setStyle;this.selectAll=a.prototype.selectAll;this.deselectAll=a.prototype.deselectAll;this.init()};a.prototype={constructor:a,init:function(c){this.$element.hide();this.multiple=this.$element.prop("multiple");var f=this.$element.attr("id");this.$newElement=this.createView();this.$element.after(this.$newElement);this.$menu=this.$newElement.find("> .dropdown-menu");this.button=this.$newElement.find("> button");if(f!==undefined){var d=this;this.button.attr("data-id",f);b('label[for="'+f+'"]').click(function(){d.button.focus()})}if(this.multiple){this.$newElement.addClass("show-tick")}this.checkDisabled();this.checkTabIndex();this.clickListener();this.render();this.liHeight();this.setWidth();this.setStyle();if(this.options.container){this.selectPosition()}},createDropdown:function(){var c="
        ";return b(c)},createView:function(){var c=this.createDropdown();var d=this.createLi();c.find("ul").append(d);return c},reloadLi:function(){this.destroyLi();var c=this.createLi();this.$newElement.find("ul").append(c)},destroyLi:function(){this.$newElement.find("li").remove()},createLi:function(){var e=this,d=[],c="";this.$element.find("option").each(function(h){var j=b(this);var g=j.attr("class")||"";var i=j.attr("style")||"";var n=j.data("content")?j.data("content"):j.html();var l=j.data("subtext")!==undefined?''+j.data("subtext")+"":"";var k=j.data("icon")!==undefined?' ':"";if(k!==""&&(j.is(":disabled")||j.parent().is(":disabled"))){k=""+k+""}if(!j.data("content")){n=k+''+n+l+""}if(e.options.hideDisabled&&(j.is(":disabled")||j.parent().is(":disabled"))){d.push('')}else{if(j.parent().is("optgroup")&&j.data("divider")!=true){if(j.index()==0){var m=j.parent().attr("label");var o=j.parent().data("subtext")!==undefined?''+j.parent().data("subtext")+"":"";var f=j.parent().data("icon")?' ':"";m=f+''+m+o+"";if(j[0].index!=0){d.push('
        '+m+"
        "+e.createA(n,"opt "+g,i))}else{d.push("
        "+m+"
        "+e.createA(n,"opt "+g,i))}}else{d.push(e.createA(n,"opt "+g,i))}}else{if(j.data("divider")==true){d.push('
        ')}else{if(b(this).data("hidden")==true){d.push("")}else{d.push(e.createA(n,g,i))}}}}});b.each(d,function(f,g){c+="
      • "+g+"
      • "});if(!this.multiple&&this.$element.find("option:selected").length==0&&!e.options.title){this.$element.find("option").eq(0).prop("selected",true).attr("selected","selected")}return b(c)},createA:function(e,c,d){return''+e+''},render:function(){var g=this;this.$element.find("option").each(function(h){g.setDisabled(h,b(this).is(":disabled")||b(this).parent().is(":disabled"));g.setSelected(h,b(this).is(":selected"))});var f=this.$element.find("option:selected").map(function(h,k){var l=b(this);var j=l.data("icon")&&g.options.showIcon?' ':"";var i;if(g.options.showSubtext&&l.attr("data-subtext")&&!g.multiple){i=' '+l.data("subtext")+""}else{i=""}if(l.data("content")&&g.options.showContent){return l.data("content")}else{if(l.attr("title")!=undefined){return l.attr("title")}else{return j+l.html()+i}}}).toArray();var e=!this.multiple?f[0]:f.join(", ");if(g.multiple&&g.options.selectedTextFormat.indexOf("count")>-1){var c=g.options.selectedTextFormat.split(">");var d=this.options.hideDisabled?":not([disabled])":"";if((c.length>1&&f.length>c[1])||(c.length==1&&f.length>=2)){e=g.options.countSelectedText.replace("{0}",f.length).replace("{1}",this.$element.find('option:not([data-divider="true"]):not([data-hidden="true"])'+d).length)}}if(!e){e=g.options.title!=undefined?g.options.title:g.options.noneSelectedText}g.$newElement.find(".filter-option").html(e)},setStyle:function(e,d){if(this.$element.attr("class")){this.$newElement.addClass(this.$element.attr("class").replace(/selectpicker|mobile-device/gi,""))}var c=e?e:this.options.style;if(d=="add"){this.button.addClass(c)}else{this.button.removeClass(this.options.style);this.button.addClass(c)}},liHeight:function(){var d=this.$newElement.clone();d.appendTo("body");var c=d.addClass("open").find(".dropdown-menu li > a").outerHeight();d.remove();this.$newElement.data("liHeight",c)},setSize:function(){var i=this,e=this.$newElement.find("> .dropdown-menu"),h=e.find(".inner"),k=h.find("li > a"),j=this.$newElement.outerHeight(),n=this.$newElement.data("liHeight"),g=e.find("li .divider").outerHeight(true),c=parseInt(e.css("padding-top"))+parseInt(e.css("padding-bottom"))+parseInt(e.css("border-top-width"))+parseInt(e.css("border-bottom-width")),d=this.options.hideDisabled?":not(.disabled)":"",f;if(this.options.size=="auto"){var o=function(){var r=i.$newElement.offset().top;var p=r-b(window).scrollTop();var u=b(window).height();var q=c+parseInt(e.css("margin-top"))+parseInt(e.css("margin-bottom"))+2;var t=u-p-j-q;var s;f=t;if(i.$newElement.hasClass("dropup")){f=p-q}if((e.find("li").length+e.find("dt").length)>3){s=n*3+q-2}else{s=0}e.css({"max-height":f+"px",overflow:"hidden","min-height":s+"px"});h.css({"max-height":(f-c)+"px","overflow-y":"auto"})};o();b(window).resize(o);b(window).scroll(o)}else{if(this.options.size&&this.options.size!="auto"&&e.find("li"+d).length>this.options.size){var m=e.find("li"+d+" > *").filter(":not(.div-contain)").slice(0,this.options.size).last().parent().index();var l=e.find("li").slice(0,m+1).find(".div-contain").length;f=n*this.options.size+l*g+c;e.css({"max-height":f+"px",overflow:"hidden"});h.css({"max-height":(f-c)+"px","overflow-y":"auto"})}}},setWidth:function(){var d=this.$newElement.find("> .dropdown-menu");if(this.options.width=="auto"){d.css("min-width","0");var c=d.css("width");this.$newElement.css("width",c)}else{if(this.options.width){this.$newElement.css("width",this.options.width)}}},selectPosition:function(){var h=this,d="
        ",e=b(d),g,f,c=function(i){e.addClass(i.attr("class").replace(/open/gi,""));g=i.offset();f=i[0].offsetHeight;e.css({top:g.top+f,left:g.left,width:i[0].offsetWidth,position:"absolute"})};this.$newElement.on("click",function(){c(b(this));e.toggleClass("open");e.append(h.$menu);e.appendTo(h.options.container);return false});b(window).resize(function(){c(h.$newElement)});b(window).scroll(function(){c(h.$newElement)});b("html").on("click",function(){e.removeClass("open")})},mobile:function(){this.$element.addClass("mobile-device").appendTo(this.$newElement);if(this.options.container){this.$menu.hide()}},refresh:function(){this.reloadLi();this.render();this.setWidth();this.setStyle();this.checkDisabled()},setSelected:function(c,d){this.$menu.find("li").eq(c).toggleClass("selected",d)},setDisabled:function(c,d){if(d){this.$menu.find("li").eq(c).addClass("disabled").find("a").attr("href","#").attr("tabindex",-1)}else{this.$menu.find("li").eq(c).removeClass("disabled").find("a").removeAttr("href").attr("tabindex",0)}},isDisabled:function(){return this.$element.is(":disabled")||this.$element.attr("readonly")},checkDisabled:function(){var c=this;if(this.isDisabled()){this.button.addClass("disabled");this.button.attr("tabindex","-1")}else{if(!this.isDisabled()&&this.button.hasClass("disabled")){this.button.removeClass("disabled");this.button.removeAttr("tabindex")}}this.button.click(function(){if(c.isDisabled()){return false}})},checkTabIndex:function(){if(this.$element.is("[tabindex]")){var c=this.$element.attr("tabindex");this.button.attr("tabindex",c)}},clickListener:function(){var c=this;b("body").on("touchstart.dropdown",".dropdown-menu",function(d){d.stopPropagation()});this.$newElement.on("click",function(){c.setSize()});this.$menu.on("click","li a",function(j){var d=b(this).parent().index(),i=b(this).parent(),h=c.$element.val();if(c.multiple){j.stopPropagation()}j.preventDefault();if(c.$element.not(":disabled")&&!b(this).parent().hasClass("disabled")){if(!c.multiple){c.$element.find("option").prop("selected",false);c.$element.find("option").eq(d).prop("selected",true)}else{var g=c.$element.find("option").eq(d);var f=g.prop("selected");g.prop("selected",!f)}c.button.focus();if(h!=c.$element.val()){c.$element.change()}}});this.$menu.on("click","li.disabled a, li dt, li .div-contain",function(d){d.preventDefault();d.stopPropagation();c.button.focus()});this.$element.on("change",function(d){c.render()})},val:function(c){if(c!=undefined){this.$element.val(c);this.$element.change();return this.$element}else{return this.$element.val()}},selectAll:function(){this.$element.find("option").prop("selected",true).attr("selected","selected");this.render()},deselectAll:function(){this.$element.find("option").prop("selected",false).removeAttr("selected");this.render()},keydown:function(n){var o,m,h,l,j,i,p,d,g;o=b(this);h=o.parent();m=b("[role=menu] li:not(.divider):visible a",h);if(!m.length){return}if(/(38|40)/.test(n.keyCode)){l=m.index(m.filter(":focus"));i=m.parent(":not(.disabled)").first().index();p=m.parent(":not(.disabled)").last().index();j=m.eq(l).parent().nextAll(":not(.disabled)").eq(0).index();d=m.eq(l).parent().prevAll(":not(.disabled)").eq(0).index();g=m.eq(j).parent().prevAll(":not(.disabled)").eq(0).index();if(n.keyCode==38){if(l!=g&&l>d){l=d}if(lp){l=p}}m.eq(l).focus()}else{var f={48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",59:";",65:"a",66:"b",67:"c",68:"d",69:"e",70:"f",71:"g",72:"h",73:"i",74:"j",75:"k",76:"l",77:"m",78:"n",79:"o",80:"p",81:"q",82:"r",83:"s",84:"t",85:"u",86:"v",87:"w",88:"x",89:"y",90:"z",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9"};var c=[];m.each(function(){if(b(this).parent().is(":not(.disabled)")){if(b.trim(b(this).text().toLowerCase()).substring(0,1)==f[n.keyCode]){c.push(b(this).parent().index())}}});var k=b(document).data("keycount");k++;b(document).data("keycount",k);var q=b.trim(b(":focus").text().toLowerCase()).substring(0,1);if(q!=f[n.keyCode]){k=1;b(document).data("keycount",k)}else{if(k>=c.length){b(document).data("keycount",0)}}m.eq(c[k-1]).focus()}if(/(13)/.test(n.keyCode)){b(":focus").click();h.addClass("open");b(document).data("keycount",0)}},hide:function(){this.$newElement.hide()},show:function(){this.$newElement.show()},destroy:function(){this.$newElement.remove();this.$element.remove()}};b.fn.selectpicker=function(e,f){var c=arguments;var g;var d=this.each(function(){if(b(this).is("select")){var m=b(this),l=m.data("selectpicker"),h=typeof e=="object"&&e;if(!l){m.data("selectpicker",(l=new a(this,h,f)))}else{if(h){for(var j in h){l.options[j]=h[j]}}}if(typeof e=="string"){var k=e;if(l[k] instanceof Function){[].shift.apply(c);g=l[k].apply(l,c)}else{g=l.options[k]}}}});if(g!=undefined){return g}else{return d}};b.fn.selectpicker.defaults={style:null,size:"auto",title:null,selectedTextFormat:"values",noneSelectedText:"Nothing selected",countSelectedText:"{0} of {1} selected",width:null,container:false,hideDisabled:false,showSubtext:false,showIcon:true,showContent:true};b(document).data("keycount",0).on("keydown","[data-toggle=dropdown], [role=menu]",a.prototype.keydown)}(window.jQuery); 9 | --------------------------------------------------------------------------------