Skip to content

Commit

Permalink
implement image info buffer
Browse files Browse the repository at this point in the history
Resolves #48
  • Loading branch information
TomasTomecek committed Jan 2, 2016
1 parent 8fc4991 commit 23aaeb7
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 1 deletion.
18 changes: 18 additions & 0 deletions sen/docker_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ def short_id(self):
def display_time_created(self):
return humanize.naturaltime(self.created)

def display_formal_time_created(self):
# http://tools.ietf.org/html/rfc2822.html#section-3.3
return self.created.strftime("%d %b %Y, %H:%M:%S")

def inspect(self):
raise NotImplementedError()

Expand Down Expand Up @@ -243,6 +247,20 @@ def container_command(self):
return " ".join(cmd)
return ""

@property
def size(self):
"""
Size of all layers in bytes
:return: int
"""
return self.data["VirtualSize"]

@property
def labels(self):
labels = self.data["Labels"]
return labels

@property
def names(self):
if self._names is None:
Expand Down
14 changes: 14 additions & 0 deletions sen/tui/buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from sen.docker_backend import DockerContainer
from sen.exceptions import NotifyError
from sen.tui.constants import HELP_TEXT
from sen.tui.widgets.info import ImageInfoWidget
from sen.tui.widgets.list.main import MainListBox
from sen.tui.widgets.list.util import get_operation_notify_widget
from sen.tui.widgets.list.common import AsyncScrollableListBox, ScrollableListBox
Expand Down Expand Up @@ -59,6 +60,19 @@ def filter(self, s):
self.widget.set_body(widgets)


class ImageInfoBuffer(Buffer):
tag = "I"

def __init__(self, docker_image, ui):
"""
:param docker_image:
:param ui: ui object so we refresh
"""
self.display_name = docker_image.short_name
self.widget = ImageInfoWidget(ui, docker_image)
super().__init__()


class MainListBuffer(Buffer):
display_name = "Listing"
tag = "M"
Expand Down
6 changes: 5 additions & 1 deletion sen/tui/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
from sen.exceptions import NotifyError
from sen.tui.commands import search, filter
from sen.tui.statusbar import Footer
from sen.tui.buffer import LogsBuffer, MainListBuffer, InspectBuffer, HelpBuffer
from sen.tui.buffer import LogsBuffer, MainListBuffer, InspectBuffer, HelpBuffer, ImageInfoBuffer
from sen.tui.constants import PALLETE
from sen.docker_backend import DockerBackend

import urwid

from sen.util import log_traceback

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -159,6 +160,9 @@ def display_and_follow_logs(self, docker_container):
def inspect(self, docker_object):
self.add_and_display_buffer(InspectBuffer(docker_object))

def display_image_info(self, docker_image):
self.add_and_display_buffer(ImageInfoBuffer(docker_image, self))

def refresh_main_buffer(self, refresh_buffer=True):
assert self.main_list_buffer is not None
if refresh_buffer:
Expand Down
151 changes: 151 additions & 0 deletions sen/tui/widgets/info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
"""
Info widgets:
* display detailed info about an object
"""
import logging

import urwid

from sen.tui.widgets.list.util import get_map, RowWidget
from sen.tui.widgets.table import ResponsiveTable, assemble_rows
from sen.util import humanize_bytes
from sen.tui.widgets.list.base import VimMovementListBox
from sen.tui.widgets.util import AdHocAttrMap, get_basic_image_markup

logger = logging.getLogger(__name__)


class SelectableText(AdHocAttrMap):
def __init__(self, text, maps=None):
maps = maps or get_map()
super().__init__(urwid.Text(text, align="left", wrap="clip"), maps)

@property
def text(self):
return self._original_widget.text

def keypress(self, size, key):
""" get rid of tback: `AttributeError: 'Text' object has no attribute 'keypress'` """
return key


class LayerWidget(SelectableText):
def __init__(self, ui, docker_image, index=0):
self.ui = ui
self.docker_image = docker_image
separator = "└─"
if index == 0:
label = [separator]
else:
label = [2 * index * " " + separator]
super().__init__(label + get_basic_image_markup(docker_image))

def keypress(self, size, key):
logger.debug("%s %s %s", self.__class__, key, size)
if key == "enter":
self.ui.display_image_info(self.docker_image)
return
elif key == "i":
self.ui.inspect(self.docker_image) # FIXME: do this async
return
return key


class TagWidget(SelectableText):
def __init__(self, ui, docker_image, tag):
self.ui = ui
self.docker_image = docker_image
self.tag = tag
super().__init__(str(self.tag))

def keypress(self, size, key):
logger.debug("%s %s %s", self.__class__, key, size)
if key == "d":
self.docker_image.remove_tag(self.tag) # FIXME: do this async
# TODO: refresh
return
return key


class ImageInfoWidget(VimMovementListBox):
"""
display info about image
"""
def __init__(self, ui, docker_image):
self.ui = ui
self.docker_image = docker_image

self.walker = urwid.SimpleFocusListWalker([])

# self.widgets = []

self._basic_data()
self._containers()
self._image_names()
self._layers()
self._labels()

super().__init__(self.walker)

self.set_focus(0) # or assemble list first and then stuff it into walker

def _basic_data(self):
data = [
[SelectableText("Id", maps=get_map("main_list_green")),
SelectableText(self.docker_image.image_id)],
[SelectableText("Created", maps=get_map("main_list_green")),
SelectableText("{0}, {1}".format(self.docker_image.display_formal_time_created(),
self.docker_image.display_time_created()))],
[SelectableText("Size", maps=get_map("main_list_green")),
SelectableText(humanize_bytes(self.docker_image.size))],
[SelectableText("Command", maps=get_map("main_list_green")),
SelectableText(self.docker_image.container_command)],
]
self.walker.extend(assemble_rows(data))

def _image_names(self):
if not self.docker_image.names:
return
self.walker.append(RowWidget([SelectableText("")]))
self.walker.append(RowWidget([SelectableText("Image Names", maps=get_map("main_list_white"))]))
for n in self.docker_image.names:
self.walker.append(RowWidget([TagWidget(self.ui, self.docker_image, n)]))

def _layers(self):
self.walker.append(RowWidget([SelectableText("")]))
self.walker.append(RowWidget([SelectableText("Layers", maps=get_map("main_list_white"))]))

i = self.docker_image
index = 0
self.walker.append(RowWidget([LayerWidget(self.ui, self.docker_image, index=index)]))
while True:
index += 1
parent = i.parent_image
if parent:
self.walker.append(RowWidget([LayerWidget(self.ui, parent, index=index)]))
i = parent
else:
break

def _labels(self):
if not self.docker_image.labels:
return []
data = []
self.walker.append(RowWidget([SelectableText("")]))
self.walker.append(RowWidget([SelectableText("Labels", maps=get_map("main_list_white"))]))
for label_key, label_value in self.docker_image.labels.items():
data.append([SelectableText(label_key, maps=get_map("main_list_green")), SelectableText(label_value)])
self.walker.extend(assemble_rows(data))

def _containers(self):
if not self.docker_image.containers():
return
self.walker.append(RowWidget([SelectableText("")]))
self.walker.append(RowWidget([SelectableText("Containers", maps=get_map("main_list_white"))]))
for container in self.docker_image.containers():
self.walker.append(RowWidget([SelectableText(str(container))]))

def keypress(self, size, key):
logger.debug("%s, %s", key, size)
key = super().keypress(size, key)
return key
3 changes: 3 additions & 0 deletions sen/tui/widgets/list/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,9 @@ def do_and_report_on_fail(f, docker_object):
elif key == "f":
self.ui.run_in_background(do_and_report_on_fail, self.ui.display_and_follow_logs, self.focused_docker_object)
return
elif key == "enter":
self.ui.run_in_background(do_and_report_on_fail, self.ui.display_image_info, self.focused_docker_object)
return
elif key == "d":
self.ui.run_in_background(
run_and_report_on_fail,
Expand Down
13 changes: 13 additions & 0 deletions sen/tui/widgets/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,16 @@ def set_map(self, attrstring):
for a in self.attrs:
attr_map[a] = self.maps["focus"]
self.set_attr_map(attr_map)


def get_basic_image_markup(docker_image):
text_markup = [docker_image.short_id]

if docker_image.names:
text_markup.append(" ")
text_markup.append(("main_list_lg", docker_image.names[0].to_str()))

text_markup.append(" ")
text_markup.append(("main_list_ddg", docker_image.container_command))

return text_markup
24 changes: 24 additions & 0 deletions sen/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,27 @@ def setup_dirs():

def get_log_file_path():
return os.path.join(setup_dirs(), LOG_FILE_NAME)


def humanize_bytes(bytesize, precision=2):
"""
Humanize byte size figures
https://gist.github.com/moird/3684595
"""
abbrevs = (
(1 << 50, 'PB'),
(1 << 40, 'TB'),
(1 << 30, 'GB'),
(1 << 20, 'MB'),
(1 << 10, 'kB'),
(1, 'bytes')
)
if bytesize == 1:
return '1 byte'
for factor, suffix in abbrevs:
if bytesize >= factor:
break
if factor == 1:
precision = 0
return '%.*f %s' % (precision, bytesize / float(factor), suffix)

0 comments on commit 23aaeb7

Please sign in to comment.