├── .github
└── workflows
│ └── release.yml
├── .gitignore
├── Dockerfile
├── README.md
├── html
├── css
│ ├── bootstrap.min.css
│ └── style.css
├── fonts
│ ├── glyphicons-halflings-regular.eot
│ ├── glyphicons-halflings-regular.svg
│ ├── glyphicons-halflings-regular.ttf
│ ├── glyphicons-halflings-regular.woff
│ └── glyphicons-halflings-regular.woff2
├── index.html
└── js
│ ├── bootstrap.min.js
│ ├── jquery-2.2.1.min.js
│ └── script.js
├── httpd.js
├── img
├── all-containers-mem.png
├── all-containers-net.png
└── selected-container.png
├── multi-arch-manifest.yaml
├── stats.js
└── supervisord.conf
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | workflow_dispatch:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 | - name: Set up QEMU
15 | uses: docker/setup-qemu-action@v3
16 | - name: Set up Docker Buildx
17 | uses: docker/setup-buildx-action@v3
18 | - name: Cache Docker layers
19 | uses: actions/cache@v4
20 | with:
21 | path: /tmp/.buildx-cache
22 | key: ${{ runner.os }}-buildx-${{ github.sha }}
23 | restore-keys: |
24 | ${{ runner.os }}-buildx-
25 | - name: Log into registry
26 | if: github.event_name != 'pull_request'
27 | uses: docker/login-action@v3
28 | with:
29 | registry: docker.io
30 | username: virtualzone
31 | password: ${{ secrets.CI_REGISTRY_PASSWORD }}
32 | - name: Build and push
33 | uses: docker/build-push-action@v5
34 | with:
35 | context: .
36 | platforms: linux/amd64,linux/arm64
37 | push: true
38 | tags: |
39 | virtualzone/docker-container-stats:latest
40 | cache-from: type=local,src=/tmp/.buildx-cache
41 | cache-to: type=local,dest=/tmp/.buildx-cache-new
42 | - name: Docker Hub Description
43 | uses: peter-evans/dockerhub-description@v4
44 | with:
45 | username: virtualzone
46 | password: ${{ secrets.CI_REGISTRY_PASSWORD }}
47 | repository: virtualzone/docker-container-stats
48 | readme-filepath: ./README.md
49 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | db/
3 | test/
4 | package-lock.json
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:3.19
2 | ARG BUILD_DATE
3 | ARG VCS_REF
4 | LABEL org.label-schema.build-date=$BUILD_DATE \
5 | org.label-schema.name="Docker Container Stats" \
6 | org.label-schema.description="Monitor your docker containers with this web interface." \
7 | org.label-schema.vcs-ref=$VCS_REF \
8 | org.label-schema.vcs-url="https://github.com/virtualzone/docker-container-stats" \
9 | org.label-schema.schema-version="1.0"
10 |
11 | RUN apk --update add --no-cache \
12 | supervisor \
13 | nodejs \
14 | npm \
15 | docker \
16 | sqlite
17 |
18 | RUN mkdir -p /opt/docker-stats/db
19 | RUN apk --update add --no-cache --virtual .build-deps \
20 | make python3 gcc g++ libc-dev && \
21 | cd /opt/docker-stats && \
22 | npm update && \
23 | npm install express sqlite3 body-parser moment --build-from-source=sqlite3 && \
24 | apk del .build-deps
25 |
26 | ADD stats.js /opt/docker-stats/
27 | ADD httpd.js /opt/docker-stats/
28 | ADD html/ /opt/docker-stats/html/
29 | ADD supervisord.conf /etc/supervisord.conf
30 |
31 | EXPOSE 8080
32 | CMD ["/usr/bin/supervisord"]
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Docker Container Stats
2 | A web interface for viewing historical and current statistics per docker container (cpu, mem, net i/o, block i/o) - in a docker container.
3 |
4 | Pull and run daemonized:
5 | ```
6 | docker pull virtualzone/docker-container-stats
7 | docker run \
8 | -d \
9 | -p 8080:8080 \
10 | --volume=/var/run/docker.sock:/var/run/docker.sock:ro \
11 | --volume=/home/docker/storage/stats/db:/opt/docker-stats/db \
12 | --name stats \
13 | -e STATS_UPDATE_INTERVAL=10 \
14 | virtualzone/docker-container-stats
15 | ```
16 |
17 | Docker-Compose:
18 |
19 | ```
20 | version: '3.6'
21 | services:
22 | stats:
23 | image: virtualzone/docker-container-stats
24 | container_name: 'stats'
25 | ports:
26 | - '8080:8080'
27 | environment:
28 | STATS_UPDATE_INTERVAL: 10
29 | volumes:
30 | - '/var/run/docker.sock:/var/run/docker.sock:ro'
31 | - '/home/docker/storage/stats/db:/opt/docker-stats/db'
32 | ```
33 |
34 |
35 | To view your stats, open a web browser and visit http://localhost:8080 (replace localhost with your docker host's hostname or ip address).
36 |
37 | Mounting the volume /var/run/docker.sock (read-only) is required so that the docker container can retrieve the statistics for the containers.
38 |
39 | Mounting the volume /opt/docker-stats/db is optional. You can use it if you want to persist the SQLite database.
40 |
41 | We strongly recommend not making your stats available online. To password protect your statistics, you can use a frontend web server/proxy (Apache, nginx, ...).
42 |
43 | ## Screenshots
44 | 
45 |
46 | 
47 |
48 | 
49 |
--------------------------------------------------------------------------------
/html/css/style.css:
--------------------------------------------------------------------------------
1 | .header {
2 | padding-top: 20px;
3 | padding-bottom: 20px;
4 | border-bottom: 1px solid #e5e5e5;
5 | margin-bottom: 20px;
6 | }
7 |
8 | .header h3 {
9 | margin-top: 0;
10 | margin-bottom: 0;
11 | line-height: 40px;
12 | }
13 |
14 | .chart {
15 | width: 100%;
16 | height: 500px;
17 | display: none;
18 | }
19 |
20 | #container-stats {
21 | margin-top: 50px;
22 | }
23 |
24 | .divider {
25 | margin-top: 20px;
26 | margin-bottom: 20px;
27 | border-bottom: 1px solid #e5e5e5;
28 | }
29 |
30 | footer {
31 | display: block;
32 | margin-left: auto;
33 | margin-right: auto;
34 | margin-top: 50px;
35 | margin-bottom: 50px;
36 | }
37 |
38 | @media (min-width: 768px) {
39 | .container {
40 | max-width: 100%;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/html/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualzone/docker-container-stats/31c7bf43dda665ba4915901e7e4337d6a394b827/html/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/html/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualzone/docker-container-stats/31c7bf43dda665ba4915901e7e4337d6a394b827/html/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/html/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualzone/docker-container-stats/31c7bf43dda665ba4915901e7e4337d6a394b827/html/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/html/fonts/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualzone/docker-container-stats/31c7bf43dda665ba4915901e7e4337d6a394b827/html/fonts/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
25 |
26 |
27 |
35 |
36 |
37 |
38 |
39 |
40 | Name or ID |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
Latest
52 |
53 |
54 |
55 | Time |
56 | CPU |
57 | Mem |
58 | Net In |
59 | Net Out |
60 | Block In |
61 | Block Out |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
Mem
71 |
72 |
No data for specified zoom level.
73 |
74 |
75 |
CPU
76 |
77 |
No data for specified zoom level.
78 |
79 |
80 |
Net In
81 |
82 |
No data for specified zoom level.
83 |
84 |
85 |
Net Out
86 |
87 |
No data for specified zoom level.
88 |
89 |
90 |
Block In
91 |
92 |
No data for specified zoom level.
93 |
94 |
95 |
Block Out
96 |
97 |
No data for specified zoom level.
98 |
99 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/html/js/bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.3.6 (http://getbootstrap.com)
3 | * Copyright 2011-2015 Twitter, Inc.
4 | * Licensed under the MIT license
5 | */
6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>2)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 3")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.6",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.6",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.6",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.6",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.6",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&j