0){this.$element.data("active",items[0])}else{this.$element.data("active",null)}if(this.options.addItem){items.push(this.options.addItem)}if(this.options.items=="all"){return this.render(items).show()}else{return this.render(items.slice(0,this.options.items)).show()}},matcher:function(item){var it=this.displayText(item);return~it.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(items){var beginswith=[];var caseSensitive=[];var caseInsensitive=[];var item;while(item=items.shift()){var it=this.displayText(item);if(!it.toLowerCase().indexOf(this.query.toLowerCase()))beginswith.push(item);else if(~it.indexOf(this.query))caseSensitive.push(item);else caseInsensitive.push(item)}return beginswith.concat(caseSensitive,caseInsensitive)},highlighter:function(item){var html=$("
");var query=this.query;var i=item.toLowerCase().indexOf(query.toLowerCase());var len=query.length;var leftPart;var middlePart;var rightPart;var strong;if(len===0){return html.text(item).html()}while(i>-1){leftPart=item.substr(0,i);middlePart=item.substr(i,len);rightPart=item.substr(i+len);strong=$(" ").text(middlePart);html.append(document.createTextNode(leftPart)).append(strong);item=rightPart;i=item.toLowerCase().indexOf(query.toLowerCase())}return html.append(document.createTextNode(item)).html()},render:function(items){var that=this;var self=this;var activeFound=false;var data=[];var _category=that.options.separator;$.each(items,function(key,value){if(key>0&&value[_category]!==items[key-1][_category]){data.push({__type:"divider"})}if(value[_category]&&(key===0||value[_category]!==items[key-1][_category])){data.push({__type:"category",name:value[_category]})}data.push(value)});items=$(data).map(function(i,item){if((item.__type||false)=="category"){return $(that.options.headerHtml).text(item.name)[0]}if((item.__type||false)=="divider"){return $(that.options.headerDivider)[0]}var text=self.displayText(item);i=$(that.options.item).data("value",item);i.find("a").html(that.highlighter(text,item));if(text==self.$element.val()){i.addClass("active");self.$element.data("active",item);activeFound=true}return i[0]});if(this.autoSelect&&!activeFound){items.filter(":not(.dropdown-header)").first().addClass("active");this.$element.data("active",items.first().data("value"))}this.$menu.html(items);return this},displayText:function(item){return typeof item!=="undefined"&&typeof item.name!="undefined"&&item.name||item},next:function(event){var active=this.$menu.find(".active").removeClass("active");var next=active.next();if(!next.length){next=$(this.$menu.find("li")[0])}next.addClass("active")},prev:function(event){var active=this.$menu.find(".active").removeClass("active");var prev=active.prev();if(!prev.length){prev=this.$menu.find("li").last()}prev.addClass("active")},listen:function(){this.$element.on("focus",$.proxy(this.focus,this)).on("blur",$.proxy(this.blur,this)).on("keypress",$.proxy(this.keypress,this)).on("input",$.proxy(this.input,this)).on("keyup",$.proxy(this.keyup,this));if(this.eventSupported("keydown")){this.$element.on("keydown",$.proxy(this.keydown,this))}this.$menu.on("click",$.proxy(this.click,this)).on("mouseenter","li",$.proxy(this.mouseenter,this)).on("mouseleave","li",$.proxy(this.mouseleave,this)).on("mousedown",$.proxy(this.mousedown,this))},destroy:function(){this.$element.data("typeahead",null);this.$element.data("active",null);this.$element.off("focus").off("blur").off("keypress").off("input").off("keyup");if(this.eventSupported("keydown")){this.$element.off("keydown")}this.$menu.remove();this.destroyed=true},eventSupported:function(eventName){var isSupported=eventName in this.$element;if(!isSupported){this.$element.setAttribute(eventName,"return;");isSupported=typeof this.$element[eventName]==="function"}return isSupported},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:if(e.shiftKey)return;e.preventDefault();this.prev();break;case 40:if(e.shiftKey)return;e.preventDefault();this.next();break}},keydown:function(e){this.suppressKeyPressRepeat=~$.inArray(e.keyCode,[40,38,9,13,27]);if(!this.shown&&e.keyCode==40){this.lookup()}else{this.move(e)}},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},input:function(e){var currentValue=this.$element.val()||this.$element.text();if(this.value!==currentValue){this.value=currentValue;this.lookup()}},keyup:function(e){if(this.destroyed){return}switch(e.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break}},focus:function(e){if(!this.focused){this.focused=true;if(this.options.showHintOnFocus&&this.skipShowHintOnFocus!==true){if(this.options.showHintOnFocus==="all"){this.lookup("")}else{this.lookup()}}}if(this.skipShowHintOnFocus){this.skipShowHintOnFocus=false}},blur:function(e){if(!this.mousedover&&!this.mouseddown&&this.shown){this.hide();this.focused=false}else if(this.mouseddown){this.skipShowHintOnFocus=true;this.$element.focus();this.mouseddown=false}},click:function(e){e.preventDefault();this.skipShowHintOnFocus=true;this.select();this.$element.focus();this.hide()},mouseenter:function(e){this.mousedover=true;this.$menu.find(".active").removeClass("active");$(e.currentTarget).addClass("active")},mouseleave:function(e){this.mousedover=false;if(!this.focused&&this.shown)this.hide()},mousedown:function(e){this.mouseddown=true;this.$menu.one("mouseup",function(e){this.mouseddown=false}.bind(this))}};var old=$.fn.typeahead;$.fn.typeahead=function(option){var arg=arguments;if(typeof option=="string"&&option=="getActive"){return this.data("active")}return this.each(function(){var $this=$(this);var data=$this.data("typeahead");var options=typeof option=="object"&&option;if(!data)$this.data("typeahead",data=new Typeahead(this,options));if(typeof option=="string"&&data[option]){if(arg.length>1){data[option].apply(data,Array.prototype.slice.call(arg,1))}else{data[option]()}}})};$.fn.typeahead.defaults={source:[],items:8,menu:'',item:' ',minLength:1,scrollHeight:0,autoSelect:true,afterSelect:$.noop,addItem:false,delay:0,separator:"category",headerHtml:'',headerDivider:' '};$.fn.typeahead.Constructor=Typeahead;$.fn.typeahead.noConflict=function(){$.fn.typeahead=old;return this};$(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(e){var $this=$(this);if($this.data("typeahead"))return;$this.typeahead($this.data())})});
2 |
--------------------------------------------------------------------------------
/Static/js/jquery.json-view.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * json-view - jQuery collapsible JSON plugin
3 | * @version v1.0.0
4 | * @link http://github.com/bazh/jquery.json-view
5 | * @license MIT
6 | */
7 | !function(e){"use strict";var n=function(n){var a=e(" ",{"class":"collapser",on:{click:function(){var n=e(this);n.toggleClass("collapsed");var a=n.parent().children(".block"),p=a.children("ul");n.hasClass("collapsed")?(p.hide(),a.children(".dots, .comments").show()):(p.show(),a.children(".dots, .comments").hide())}}});return n&&a.addClass("collapsed"),a},a=function(a,p){var t=e.extend({},{nl2br:!0},p),r=function(e){return e.toString()?e.toString().replace(/&/g,"&").replace(/"/g,""").replace(//g,">"):""},s=function(n,a){return e(" ",{"class":a,html:r(n)})},l=function(a,p){switch(e.type(a)){case"object":p||(p=0);var c=e(" ",{"class":"block"}),d=Object.keys(a).length;if(!d)return c.append(s("{","b")).append(" ").append(s("}","b"));c.append(s("{","b"));var i=e("",{"class":"obj collapsible level"+p});return e.each(a,function(a,t){d--;var r=e(" ").append(s('"',"q")).append(a).append(s('"',"q")).append(": ").append(l(t,p+1));-1===["object","array"].indexOf(e.type(t))||e.isEmptyObject(t)||r.prepend(n()),d>0&&r.append(","),i.append(r)}),c.append(i),c.append(s("...","dots")),c.append(s("}","b")),c.append(1===Object.keys(a).length?s("// 1 item","comments"):s("// "+Object.keys(a).length+" items","comments")),c;case"array":p||(p=0);var d=a.length,c=e(" ",{"class":"block"});if(!d)return c.append(s("[","b")).append(" ").append(s("]","b"));c.append(s("[","b"));var i=e("",{"class":"obj collapsible level"+p});return e.each(a,function(a,t){d--;var r=e(" ").append(l(t,p+1));-1===["object","array"].indexOf(e.type(t))||e.isEmptyObject(t)||r.prepend(n()),d>0&&r.append(","),i.append(r)}),c.append(i),c.append(s("...","dots")),c.append(s("]","b")),c.append(1===a.length?s("// 1 item","comments"):s("// "+a.length+" items","comments")),c;case"string":if(a=r(a),/^(http|https|file):\/\/[^\s]+$/i.test(a))return e(" ").append(s('"',"q")).append(e(" ",{href:a,text:a})).append(s('"',"q"));if(t.nl2br){var o=/\n/g;o.test(a)&&(a=(a+"").replace(o," "))}var u=e(" ",{"class":"str"}).html(a);return e(" ").append(s('"',"q")).append(u).append(s('"',"q"));case"number":return s(a.toString(),"num");case"undefined":return s("undefined","undef");case"null":return s("null","null");case"boolean":return s(a?"true":"false","bool")}};return l(a)};return e.fn.jsonView=function(n,p){var t=e(this);if(p=e.extend({},{nl2br:!0},p),"string"==typeof n)try{n=JSON.parse(n)}catch(r){}return t.append(e("
",{"class":"json-view"}).append(a(n,p))),t}}(jQuery);
--------------------------------------------------------------------------------
/Templates/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidholiday/foxy/d2e4bbc0d118a7cf1d4792a4976d3c1d1b338c9c/Templates/__init__.py
--------------------------------------------------------------------------------
/Templates/html_templates.py:
--------------------------------------------------------------------------------
1 | import string
2 |
3 | import sys
4 | import os
5 | sys.path.insert(0, os.getcwd() + '/Web')
6 | import constants
7 |
8 |
9 |
10 |
11 | '''
12 | html templates used by foxy to build the webapp
13 | '''
14 |
15 |
16 |
17 |
18 | def get_page_template():
19 | return string.Template("""
20 |
21 |
22 |
23 |
24 | foxy
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
55 |
56 |
57 |
58 |
59 |
67 |
68 |
69 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | $CONTAINER_PANELS
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | """)
100 |
101 |
102 |
103 |
104 | def get_dropdown_item_template():
105 | return string.Template("""
106 | $CONTAINER_NAME
107 | """)
108 |
109 |
110 |
111 |
112 | def get_container_panel_template():
113 | return string.Template("""
114 |
115 |
116 | $CONTAINER_PANEL_CONTENTS
117 |
118 |
119 | """)
120 |
121 |
122 |
123 |
124 | def get_container_panel_content_template():
125 | return string.Template("""
126 |
127 |
$CONTAINER_NAME
128 |
129 |
130 |
135 |
136 | $TAB_CONTENT
137 |
138 |
""")
139 |
140 |
141 |
142 |
143 | def get_container_tab_content_template():
144 | return string.Template("""
145 |
146 | $TABLES
147 |
148 |
149 |
166 |
""")
167 |
168 |
169 |
170 |
171 | def get_container_port_category_table_template():
172 | return string.Template("""
173 |
176 |
177 |
178 |
179 | NAME
180 | EXPOSED AS
181 | MAPPED AS
182 | ATTRIBUTES
183 |
184 |
185 |
186 | $TABLE_ROWS
187 |
188 |
189 |
""")
190 |
191 |
192 |
193 |
194 | def get_container_port_category_table_row_template(port, containerFoxyDataDict):
195 | foxyPort = get_foxy_port(port)
196 | foxyPortNameKey = foxyPort + "." + constants.FOXY_PORT_NAME_KEY
197 |
198 | return string.Template("""
199 |
200 | """ + containerFoxyDataDict[foxyPortNameKey] + """
201 | """ + port + """
202 | """ + containerFoxyDataDict[constants.DOCKER_PORT_KEY][port][constants.DOCKER_PORTS_HOST_IP_KEY] +
203 | """ : """ +
204 | containerFoxyDataDict[constants.DOCKER_PORT_KEY][port][constants.DOCKER_PORTS_HOST_PORT_KEY] +
205 | """
206 | $ATTRIBUTES """)
207 |
208 |
209 |
210 |
211 | # some lists have the '/tcp' tag on the port and some don't
212 | # until I get around to normalizing the data this kludge will have to do...
213 | def get_foxy_port(port):
214 | return port.replace(constants.DOCKER_PORTS_VALUE_SUFFIX, '')
215 |
216 |
217 |
218 |
--------------------------------------------------------------------------------
/Templates/templates.py:
--------------------------------------------------------------------------------
1 | import string
2 |
3 | import sys
4 | import os
5 | sys.path.insert(0, os.getcwd() + '/Web')
6 | import constants
7 |
8 |
9 |
10 |
11 | '''
12 | html templates used by foxy to build the webapp
13 | '''
14 |
15 |
16 |
17 |
18 | def get_page_template():
19 | return string.Template("""
20 |
21 |
22 |
23 |
24 | foxy
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
79 |
80 |
81 |
82 |
83 |
84 |
90 |
91 |
92 |
93 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | $CONTAINER_PANELS
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | """)
123 |
124 |
125 |
126 |
127 | def get_dropdown_item_template():
128 | return string.Template("""
129 | $CONTAINER_NAME
130 | """)
131 |
132 |
133 |
134 |
135 | def get_container_panel_template():
136 | return string.Template("""
137 |
138 |
139 | $CONTAINER_PANEL_CONTENTS
140 |
141 |
142 | """)
143 |
144 |
145 |
146 |
147 | def get_container_panel_content_template():
148 | return string.Template("""
149 |
150 |
$CONTAINER_NAME
151 |
152 |
153 |
154 | ports
155 | info
156 |
161 |
162 | $BUTTON_LABEL Container
163 |
164 |
182 |
183 |
184 | $TAB_CONTENT
185 |
186 |
187 | """)
188 |
189 |
190 |
191 |
192 | def get_container_tab_content_template():
193 | return string.Template("""
194 |
195 | $TABLES
196 |
197 |
198 |
216 |
""")
217 |
218 |
219 |
220 |
221 | def get_container_port_category_table_template():
222 | return string.Template("""
223 |
226 |
227 |
228 |
229 | NAME
230 | EXPOSED AS
231 | MAPPED AS
232 | ATTRIBUTES
233 |
234 |
235 |
236 | $TABLE_ROWS
237 |
238 |
239 |
""")
240 |
241 |
242 |
243 |
244 | def get_container_port_category_table_row_template(port, containerFoxyDataDict):
245 | foxyPort = get_foxy_port(port)
246 | foxyPortNameKey = foxyPort + "." + constants.FOXY_PORT_NAME_KEY
247 |
248 | return string.Template("""
249 |
250 | """ + containerFoxyDataDict[foxyPortNameKey] + """
251 | """ + port + """
252 | """ + containerFoxyDataDict[constants.DOCKER_PORT_KEY][port][constants.DOCKER_PORTS_HOST_IP_KEY] +
253 | """ : """ +
254 | containerFoxyDataDict[constants.DOCKER_PORT_KEY][port][constants.DOCKER_PORTS_HOST_PORT_KEY] +
255 | """
256 | $ATTRIBUTES """)
257 |
258 |
259 |
260 |
261 | # some lists have the '/tcp' tag on the port and some don't
262 | # until I get around to normalizing the data this kludge will have to do...
263 | def get_foxy_port(port):
264 | return port.replace(constants.DOCKER_PORTS_VALUE_SUFFIX, '')
265 |
266 |
267 |
268 |
--------------------------------------------------------------------------------
/Web/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidholiday/foxy/d2e4bbc0d118a7cf1d4792a4976d3c1d1b338c9c/Web/__init__.py
--------------------------------------------------------------------------------
/Web/constants.py:
--------------------------------------------------------------------------------
1 | '''
2 | constants file to help keep the foxy code DRY
3 | '''
4 |
5 |
6 | DEFAULT_DASH_NAME_PREFIX = "dockerdash for "
7 | DASH_NAME_ARG = "dashboard_name"
8 | CONTAINER_NAME_ARG = "container_name"
9 | TOP_LEVEL_METATDATA_FILTER = "foxy."
10 |
11 | DOCKER_PORT_KEY = "Ports"
12 | DOCKER_PORTS_HOST_PORT_KEY = "HostPort"
13 | DOCKER_PORTS_HOST_IP_KEY = "HostIp"
14 | DOCKER_PORTS_VALUE_SUFFIX = "/tcp"
15 |
16 | FOXY_PORT_NAME_KEY = "name"
17 | FOXY_PORT_GROUP_KEY = "group"
18 | FOXY_PORT_ATTRIBUTE_KEY = "attribute"
19 |
20 | FOXY_WEB_ATTRIBUTE = "web"
21 |
22 | FOXY_PANEL_COLOR_LIST = ["panel-success", "panel-info", "panel-warning", "panel-danger"]
23 |
24 | RELATIVE_PATH_TO_JSON = '/Data/json/'
25 |
26 | FILE_AND_DIV_PORTS_SUFFIX = "_ports"
27 | FILE_AND_DIV_INFO_SUFFIX = "_info"
28 | PANEL_DIV_ID_SUFFIX = "_panel"
29 |
--------------------------------------------------------------------------------
/Web/foxy.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import logging
4 | import argparse
5 | import simplejson as json
6 | import getpass
7 | import subprocess
8 | import socket
9 | import io
10 | import constants
11 | import html_generator
12 | import os
13 | import ast
14 |
15 |
16 | '''
17 | driver for the foxy web app. main() querries current state of docker and builds the
18 | appropriate json and html files to render that information in the application.
19 | '''
20 |
21 |
22 | def generate_app():
23 | main()
24 |
25 |
26 | def main():
27 | setup_logging()
28 | parser = get_parser()
29 | parsedArgsDict = parse_args(parser)
30 | containerNames = get_container_names(parsedArgsDict)
31 | containerInfoDict = get_container_info_dict(containerNames)
32 | foxyDataDict = get_foxydata_dict(containerInfoDict)
33 | make_foxy_files(containerInfoDict, foxyDataDict)
34 |
35 |
36 | def setup_logging():
37 | """
38 |
39 | """
40 | logging.basicConfig(
41 | level=logging.INFO,
42 | format="%(asctime)s - [%(levelname)s] [%(threadName)s] (%(module)s:%(lineno)d) %(message)s", )
43 |
44 |
45 | def get_parser():
46 | """
47 |
48 | """
49 | parser = argparse.ArgumentParser(\
50 | description="creates a dashboard that lists port resolutions for a given docker container.")
51 |
52 | parser.add_argument("-d", \
53 | "--%s" % (constants.DASH_NAME_ARG,), \
54 | help="the display name for this dashboard instance.")
55 |
56 | parser.add_argument("-c",
57 | "--%s" % (constants.CONTAINER_NAME_ARG,),
58 | help="the name of the container you wish to create a dock for \
59 | (default is to list all running containers in the dash).")
60 | return parser
61 |
62 |
63 | def parse_args(parser):
64 | """
65 |
66 | """
67 | isArgKosher = arg_check(parser, argparse.ArgumentParser)
68 | if (isArgKosher != True):
69 | raise ValueError("this method only accepts instances of argparse.ArgumentParser!")
70 |
71 | args = parser.parse_args()
72 | parsedArgsDict = vars(args)
73 |
74 | if (parsedArgsDict.get(constants.DASH_NAME_ARG)):
75 |
76 | parsedArgsDict[constants.DASH_NAME_ARG] = \
77 | "%s %s@%s" % (parsedArgsDict.get(constants.DASH_NAME_ARG), getpass.getuser(), socket.gethostname())
78 |
79 | else:
80 | parsedArgsDict[constants.DASH_NAME_ARG] = \
81 | "%s %s@%s" % (constants.DEFAULT_DASH_NAME_PREFIX, getpass.getuser(), socket.gethostname())
82 |
83 | return parsedArgsDict
84 |
85 |
86 | def get_container_names(parsedArgsDict):
87 | """
88 |
89 | """
90 | isArgKosher = arg_check(parsedArgsDict, dict)
91 | if (isArgKosher != True):
92 | raise ValueError("this method only accepts instances of dict")
93 |
94 | containerNames = list()
95 |
96 | if (parsedArgsDict[constants.CONTAINER_NAME_ARG]):
97 | containerNames.append(parsedArgsDict[constants.CONTAINER_NAME_ARG])
98 | else:
99 |
100 | output = subprocess.Popen(["docker", "ps", "-a", "--format", 'table {{.Names}}'], \
101 | stdout=subprocess.PIPE).communicate()[0]
102 |
103 | outputList = output.split()
104 | del outputList[0]
105 | containerNames = containerNames + outputList
106 |
107 | return containerNames
108 |
109 |
110 | def get_container_info_dict(containerNames):
111 | """
112 |
113 | """
114 | containerInfoDict = dict()
115 | for containerName in containerNames:
116 | output = subprocess.Popen(["docker", "inspect", "--format='{{json .}}'", containerName],
117 | stdout=subprocess.PIPE).communicate()[0]
118 | # no fucking idea why this suddenly became an issue, but it is so blah.
119 | # basically {output} is bookended by single quotes which the json.loads()
120 | # operation isn't handling well. the following strips the leading and trailing
121 | # single quote to facillitate json parsing.
122 | # moreover .strip() will probably work here but it's being a butthole
123 | # and only removing the leading ' so fuckit I'm doing it the overly
124 | # complicated way
125 | unfunkified_output = ast.literal_eval(output)
126 | containerInfoDict[containerName] = json.loads(unfunkified_output)
127 |
128 | return containerInfoDict
129 |
130 |
131 | def get_foxydata_dict(containerInfoDict):
132 | """
133 | """
134 | foxyDataDict = dict()
135 | for key, valueDict in containerInfoDict.iteritems():
136 |
137 | logging.info("container is: %s" % (key) )
138 | #valueDict = json.loads(value)
139 | metaDataDict = valueDict['Config']['Labels']
140 |
141 | portsDict = dict()
142 | if valueDict['State']['Running'] == True:
143 | portsDict = valueDict['NetworkSettings']['Ports']
144 |
145 | # because the vals are given to us as lists of dicts and we need them to be dicts
146 | for k, v in portsDict.iteritems():
147 | if v is not None:
148 | portsDict[k] = v[0]
149 |
150 |
151 | foxyFilter = constants.TOP_LEVEL_METATDATA_FILTER
152 | containerFoxyDataDict = filter_by_namespace(metaDataDict, foxyFilter)
153 | containerFoxyDataDict[constants.DOCKER_PORT_KEY] = portsDict
154 | foxyDataDict[key] = containerFoxyDataDict
155 |
156 | return foxyDataDict
157 |
158 |
159 | def filter_by_namespace(valueDict, filterValue):
160 | """
161 | filters foxy metadata by namespace, returns set of 's that DO NOT contain the
162 | filtered portion of the namespace.
163 | """
164 | return { k[ len(filterValue): ] : v \
165 | for k, v in valueDict.iteritems() \
166 | if k.startswith(filterValue) }
167 |
168 |
169 | def make_foxy_files(containerInfoDict, foxyDataDict):
170 | serialize_inner_dict_as_json('info', containerInfoDict)
171 | serialize_inner_dict_as_json('foxydata', foxyDataDict)
172 |
173 | foxyColorIndex = 0
174 | containerPanels = []
175 | for k, v in containerInfoDict.iteritems():
176 |
177 | if v['State']['Running'] == False:
178 | panelType = "panel-default"
179 | else:
180 | panelType = "panel-info"
181 | #panelType = constants.FOXY_PANEL_COLOR_LIST[foxyColorIndex]
182 | #
183 | #foxyColorIndex = (foxyColorIndex + 1) \
184 | # if (foxyColorIndex + 1 < len(constants.FOXY_PANEL_COLOR_LIST)) \
185 | # else (0)
186 |
187 | containerName = k
188 | categoryToPortsDict = get_category_to_ports_dict(containerName, foxyDataDict)
189 | #portToDataDict = foxyDataDict[containerName][constants.DOCKER_PORT_KEY]
190 |
191 | containerPanel = html_generator.get_container_panel(panelType,
192 | containerName,
193 | categoryToPortsDict,
194 | foxyDataDict,
195 | containerInfoDict)
196 | containerPanels.append(containerPanel)
197 |
198 | page = html_generator.get_page(containerInfoDict, containerPanels)
199 | f = open('./Static/index.html', 'w')
200 | f.write(page)
201 | f.close()
202 |
203 |
204 | def get_category_to_ports_dict(containerName, foxyDataDict):
205 | containerFoxyDataDict = foxyDataDict[containerName]
206 | containerPortsDict = containerFoxyDataDict[constants.DOCKER_PORT_KEY]
207 |
208 | categoryToPortsDict = dict()
209 | for k, v in containerPortsDict.iteritems():
210 | exposedPort = k.replace(constants.DOCKER_PORTS_VALUE_SUFFIX, '')
211 | foxyKey = exposedPort + ".group"
212 |
213 | if foxyKey in foxyDataDict[containerName]:
214 | group = foxyDataDict[containerName][foxyKey]
215 | #group = str(group)
216 |
217 | if group in categoryToPortsDict:
218 | categoryToPortsDict[group].append(exposedPort)
219 | else:
220 | categoryToPortsDict[group] = [exposedPort]
221 |
222 | return categoryToPortsDict
223 |
224 |
225 | # assumes dicts of dicts!
226 | def serialize_inner_dict_as_json(filenameSuffix, dictionary):
227 |
228 | for k, v in dictionary.iteritems():
229 | filename = k + "_" + filenameSuffix + ".json"
230 | v_asJSON = json.dumps(v, ensure_ascii=False)
231 | dataDirectory = os.getcwd() + constants.RELATIVE_PATH_TO_JSON
232 | with io.open(dataDirectory + filename, 'w', encoding="utf-8") as outfile:
233 | outfile.write(unicode(v_asJSON))
234 |
235 |
236 | def arg_check(arg, clazz):
237 | """
238 |
239 | """
240 | return isinstance(arg, clazz)
241 |
242 |
243 | if __name__ == "__main__": main()
244 |
245 |
246 |
247 |
248 |
--------------------------------------------------------------------------------
/Web/foxydriver.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import cherrypy
4 | import foxy
5 | import os
6 | import subprocess
7 | import constants
8 |
9 |
10 | class Foxy(object):
11 |
12 | @cherrypy.expose
13 | def index(self):
14 | foxy.generate_app()
15 | return file("./Static/index.html")
16 |
17 | @cherrypy.expose
18 | def start(self, container):
19 | output = subprocess.Popen(["docker", "start", container], stdout=subprocess.PIPE).communicate()[0]
20 | raise cherrypy.HTTPRedirect("/#" + container + constants.PANEL_DIV_ID_SUFFIX)
21 |
22 | @cherrypy.expose
23 | def stop(self, container):
24 | output = subprocess.Popen(["docker", "stop", container], stdout=subprocess.PIPE).communicate()[0]
25 | raise cherrypy.HTTPRedirect("/#" + container + constants.PANEL_DIV_ID_SUFFIX)
26 |
27 |
28 | if __name__ == '__main__':
29 | cherrypy.config.update({'server.socket_port': 1701})
30 |
31 | conf = {
32 | '/': {
33 | 'tools.sessions.on': True,
34 | 'tools.staticdir.root': os.path.abspath(os.getcwd())
35 | },
36 | '/Static': {
37 | 'tools.staticdir.on': True,
38 | 'tools.staticdir.dir': './Static'
39 | },
40 | '/Data': {
41 | 'tools.staticdir.on': True,
42 | 'tools.staticdir.dir': './Data'
43 | }
44 | }
45 | cherrypy.quickstart(Foxy(), '/', conf)
46 |
--------------------------------------------------------------------------------
/Web/html_generator.py:
--------------------------------------------------------------------------------
1 | import constants
2 | import json
3 |
4 | import sys
5 | import os
6 | #sys.path.insert(0, os.getcwd() + '/Templates')
7 | from Templates import templates
8 |
9 |
10 |
11 | '''
12 | methods to generate the static html needed to drive the foxy webapp.
13 | *NOTE* you should only need to call the get_page method to generate
14 | the index file.
15 | '''
16 |
17 |
18 |
19 |
20 | def get_page(containerInfoDict, containerPanels):
21 |
22 | # dropdown
23 | dropdownItems = ""
24 | tags = ""
25 | for k, v in containerInfoDict.iteritems():
26 | containerName = k
27 | containerPanelName = containerName + "_panel"
28 | dropdownItemTemplate = templates.get_dropdown_item_template()
29 |
30 | dropdownItemContent = dropdownItemTemplate.substitute(CONTAINER_NAME = containerName,
31 | CONTAINER_PANEL_NAME = containerPanelName)
32 |
33 | dropdownItems = dropdownItems + dropdownItemContent
34 | tags = tags + """ " """ + containerName + """ ", """
35 |
36 | # content panels
37 | sep = "\n"
38 | containerPanelString = sep.join(containerPanels)
39 | pageTemplate = templates.get_page_template();
40 |
41 | page = pageTemplate.substitute(CONTAINER_PANELS = containerPanelString,
42 | DROPDOWN_ITEMS = dropdownItems,
43 | TAGS = tags)
44 |
45 | return page
46 |
47 |
48 |
49 |
50 | def get_container_panel(panelType, containerName, categoryToPortsDict, foxyDataDict, containerInfo):
51 |
52 | buttonType = ""
53 | buttonLabel = ""
54 |
55 | if containerInfo[containerName]['State']['Running'] == True:
56 | buttonType = "btn-danger"
57 | buttonLabel = "Stop"
58 | buttonURL = "/stop/?container=" + containerName
59 | else:
60 | buttonType = "btn-success"
61 | buttonLabel = "Start"
62 | buttonURL = "/start/?container=" + containerName
63 |
64 | containerPanelContents = get_container_panel_contents(containerName,
65 | categoryToPortsDict,
66 | foxyDataDict,
67 | buttonType,
68 | buttonLabel,
69 | buttonURL)
70 |
71 | containerPanelName = containerName + "_panel"
72 | containerPanelTemplate = templates.get_container_panel_template()
73 | containerPanel = containerPanelTemplate.substitute(CONTAINER_PANEL_NAME = containerPanelName,
74 | CONTAINER_PANEL_CONTENTS = containerPanelContents,
75 | PANEL_TYPE = panelType)
76 | return containerPanel
77 |
78 |
79 |
80 |
81 | def get_container_panel_contents(containerName,
82 | categoryToPortsDict,
83 | foxyDataDict,
84 | buttonType,
85 | buttonLabel,
86 | buttonURL):
87 |
88 | containerPanelContentTemplate = templates.get_container_panel_content_template()
89 | containerPanelTabContent = get_container_tab_content(containerName, categoryToPortsDict, foxyDataDict)
90 |
91 | portsDivID = containerName + "_ports"
92 | infoDivID = containerName + "_info"
93 |
94 | containerPanelContent = containerPanelContentTemplate.substitute(CONTAINER_NAME = containerName,
95 | TAB_CONTENT = containerPanelTabContent,
96 | BUTTON_TYPE = buttonType,
97 | BUTTON_LABEL = buttonLabel,
98 | BUTTON_URL = buttonURL,
99 | PORTS_DIV_ID = portsDivID,
100 | INFO_DIV_ID = infoDivID)
101 | return containerPanelContent
102 |
103 |
104 |
105 |
106 | def get_container_tab_content(containerName, categoryToPortsDict, foxyDataDict):
107 | tables = get_container_port_tables(containerName, categoryToPortsDict, foxyDataDict)
108 | containerInfoURL = "." + constants.RELATIVE_PATH_TO_JSON + containerName + "_info.json"
109 | tab_content_template = templates.get_container_tab_content_template()
110 |
111 | portsDivID = containerName + constants.FILE_AND_DIV_PORTS_SUFFIX
112 | infoDivID = containerName + constants.FILE_AND_DIV_INFO_SUFFIX
113 |
114 | tab_content = tab_content_template.substitute(TABLES = tables,
115 | CONTAINER_INFO_URL = containerInfoURL,
116 | PORTS_DIV_ID = portsDivID,
117 | INFO_DIV_ID = infoDivID)
118 |
119 | return tab_content
120 |
121 |
122 |
123 |
124 | def get_container_port_tables(containerName, categoryToPortsDict, foxyDataDict):
125 | containerFoxyDataDict = foxyDataDict[containerName]
126 |
127 | returnVal = ""
128 |
129 | for category, portDict in categoryToPortsDict.iteritems():
130 | portCategoryTable = get_container_port_category_table(category, portDict, containerFoxyDataDict)
131 | returnVal = returnVal + portCategoryTable
132 |
133 | return returnVal
134 |
135 |
136 |
137 |
138 |
139 | def get_container_port_category_table(category, portDict, containerFoxyDataDict):
140 | rows = ""
141 | for port in portDict:
142 | portKey = port + constants.DOCKER_PORTS_VALUE_SUFFIX
143 | rows = rows + get_container_port_category_table_row(portKey, containerFoxyDataDict)
144 |
145 | tableTemplate = templates.get_container_port_category_table_template()
146 | table = tableTemplate.substitute(TABLE_ROWS = rows, CATEGORY_NAME = category)
147 | return table
148 |
149 |
150 |
151 |
152 | def get_container_port_category_table_row(port, containerFoxyDataDict):
153 | row_template = templates.get_container_port_category_table_row_template(port, containerFoxyDataDict)
154 | foxyPort = templates.get_foxy_port(port)
155 | foxyAttributeKey = foxyPort + "." + constants.FOXY_PORT_ATTRIBUTE_KEY
156 |
157 | if foxyAttributeKey in containerFoxyDataDict:
158 | attribute = containerFoxyDataDict[foxyAttributeKey]
159 |
160 | html_a_fied_attributes = \
161 | get_container_port_category_table_row_attribute(attribute, port, containerFoxyDataDict)
162 |
163 | row = row_template.substitute(ATTRIBUTES = html_a_fied_attributes)
164 | else:
165 | row = row_template.substitute(ATTRIBUTES = '')
166 |
167 |
168 |
169 | return row
170 |
171 |
172 |
173 | def get_container_port_category_table_row_attribute(attribute, port, containerFoxyDataDict):
174 |
175 | returnVal = ""
176 |
177 | if (attribute == constants.FOXY_WEB_ATTRIBUTE):
178 | hostIP = containerFoxyDataDict[constants.DOCKER_PORT_KEY][port][constants.DOCKER_PORTS_HOST_IP_KEY]
179 | hostPort = containerFoxyDataDict[constants.DOCKER_PORT_KEY][port][constants.DOCKER_PORTS_HOST_PORT_KEY]
180 | foxyLink = "http://" + hostIP + ":" + str(hostPort)
181 |
182 | returnVal = returnVal + """ """ + \
184 | """ """ + \
185 | str(attribute) + \
186 | """ """
187 | else:
188 | returnVal = returnVal + """""" + \
189 | str(attribute) + \
190 | """ """
191 |
192 | return returnVal
193 |
194 |
195 |
196 |
--------------------------------------------------------------------------------
/Web/pyserv.py:
--------------------------------------------------------------------------------
1 | import SimpleHTTPServer
2 | import SocketServer
3 |
4 | PORT = 8000
5 |
6 | Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
7 |
8 | httpd = SocketServer.TCPServer(("", PORT), Handler)
9 |
10 | print "serving at port", PORT
11 | httpd.serve_forever()
12 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidholiday/foxy/d2e4bbc0d118a7cf1d4792a4976d3c1d1b338c9c/__init__.py
--------------------------------------------------------------------------------
/foxy_looking_foxy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidholiday/foxy/d2e4bbc0d118a7cf1d4792a4976d3c1d1b338c9c/foxy_looking_foxy.png
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 | from setuptools import setup, find_packages
3 |
4 |
5 | # Utility function to read the README file.
6 | # Used for the long_description. It's nice, because now 1) we have a top level
7 | # README file and 2) it's easier to type in the README file than to put a raw
8 | # string in below ...
9 | def read(fname):
10 | return open(os.path.join(os.path.dirname(__file__), fname)).read()
11 |
12 | setup(
13 | name = "foxy",
14 | version = "0.0.1",
15 | author = "David Holiday",
16 | author_email = "david.holiday@clickfox.com",
17 | description = ("a rad dashboard for local docker development."),
18 | license = "MIT",
19 | keywords = "docker dashboard gui rad",
20 | url = "http://www.clickfox.com",
21 | packages=find_packages(),
22 | install_requires=['cherrypy'],
23 | long_description=read('README.md'),
24 | classifiers=[
25 | "Development Status :: 3 - Alpha",
26 | "Topic :: Utilities",
27 | "License :: OSI Approved :: MIT License",
28 | ],
29 | )
--------------------------------------------------------------------------------