@tony-yin
2018-06-04T19:23:06.000000Z
字数 5998
阅读 748
Python
了解linux
的人应该听说过Newt
,Newt
是一个为RedHat
安装程序而设计的基于文本的窗口开发工具,它是由c
语言编写并不依赖X
包,linux
下的dialog
和whiptail
都是基于它。而我们今天讨论的snack
则是Newt
提供的python
接口,redhat
的系统都自带这个模块,本文就如何使用snack
制作伪终端页面展开讲解,并配合代码展示实现效果。
为啥说是最佳实践呢?因为我使用snack
的过程中,上网查阅相关资料,发现有关信息甚少。偶尔几篇文章都是处于API
或者Demo
的级别,并且讲的都不全,更别说高级扩展功能了。我正好工作需要给我们的一个系统做一个终端部署控制台UI
,所以我就使用了python snack
来实现,期间不断新需求,不断迭代,从基本页面到增删改查,再到校验、再到配置导入、再到进度条等等,不断的迭代开发让我对snack
不断地加深认知,它支持的或不支持的我都想办法一一解决,所以在这把我这段时间的收货进行总结并分享给需要的人。
本文实践的需求是做一个部署控制台工具,该工具主要分为三个阶段:基础配置、高级配置和部署进度。基础配置页面需要我们创建一些主机,填写一些主机的信息,比如IP
、Hostname
和Password
,然后高级配置我们也需要创建一些主机,不过我们可以复用基础设置的主机,所以我们的工具要支持在高级配置中导入基础配置的功能,在高级配置中我们还有一个全局配置,也就是不限于单个主机的配置(其中具体部署原理和是非,我就不多展开赘述,这不是本文的重点)。最后就是进度条页面,我们可以展示部署的过程阶段和相关时间信息。
项目地址:https://github.com/tony-yin/Best-practice-of-python-snack
先上几道凉菜,给大家开开胃。所谓的凉菜就是介绍一下python snack
的基础组件,基础组件很多,类似html
,主要有:
然后就是一些组合组件,也就是基于上述基础组件封装而得到,主要有:
上面这些组件就是所有的基础组件(组合组件也算基础组件),这些组件最终呈现还需要grid
和form
这两个组件,grid
表示“网格”的意思,跟html
中的table
类似,由行和列组成,我们的基础组件需要放在网格中来实现页面布局;而form
也类似“表单”,我们需要把grid
填充到form
中,运行后,就可以看到图形化页面了。
经过上面基础组件的介绍,想必你对snack
的组件有了充分的了解,这时候你可以参考文末的refer
做几个小demo
,做了之后你会发现页面是出来了,emmm... 可是感觉好繁琐哦,很多重复性的代码,而且页面布局也怪怪的,如果要把布局搞好,又需要加很多代码。
我们把用基础组件的阶段称之为“远古时代”,每做一个window
,都得一瓦一砖地慢慢堆砌,这样效率太低了,所以我们急需一波“工业革命”来提高生产力。
python snack
似乎考虑到了这个问题,它在上述基础组件之外还提供了dialog
相关组件,dialog
组件即集大成者,一个dialog
组件就是一个window
,也就是我们上面所说的form
,并且该form
中填充了必需的各种基础组件,dialog
组件主要有:
当今社会,大家吃惯了大鱼大肉,反而更是想念农村的野味。同理,我们用惯了“工业革命”的产物,发现虽然可用,但是仅仅停留在基础可用级别上,想换换样式,加加自己的定制化需求,都是有限的,完全达不到新需求的技术实现要求。所以,我们不能只知道用别人实现的现成的产物,我们可以尝试着“返璞归真”一下,回归最初的“远古时代”,自己实现一把“工业革命”。所谓的“dialog”组件无非也就是基础组件的封装而已,我们也可以自己实现一套自己的组件库,这个在前端是非常流行的,例如font-awesome
、iview
、ant-design
等等。这里我们自己实现了以下dialog
:
扩展的功能主要有:
扩展组件库地址:widget extend library
凉菜不够,正菜来凑。上面就是把python snack
的API
罗列了一下,做个小Demo
还行,但是距离产品化还很远,接下来我结合我做部署控制台工具的实践经历分享一下几个“正菜”,必须够硬,不接受反驳,不接受批评, O(∩_∩)O ~
python snack
提供了两种帮助用户使用的途径,一种是窗口下方的操作提示栏,另一个就是热键了。热键就是快捷键,比如我们可以敲击键盘上面的ESC
键实现页面的返回。我们可以通过调用grid
的runOnce
接口获取热键的输入,例如hotkey = g.runOnce()
,然后我们根据hotkey
的值进行判断并执行对应的操作。
当我们存在多个页面的时候,我们需要页面切换的功能,翻阅文档,并没有发现提供类似的功能。在我们的工具中,页面切换主要有两种方式,一种是点击button
,一种是热键,既然没有原生的页面切换接口,我们就根据触发方式手动切换页面。比如我们想实现页面1
点击next
按钮想跳转页面2
,那我们只需要获取button
的返回值,判断是否为next
,如果是next
,直接调用页面2
的方法即可,热键同理,即判断热键内容是否为对应热键。
ret, button, lb = ExtListboxChoiceWindow( screen,
'Distribute Storage Config',
'Distribute Storage Config',
ips,
buttons=("prev", "next", "exit"),
width=50,
height=5,
)
if button == "exit" or ret == "ESC":
screen.finish()
elif button == "prev":
Welcome_Deploy_Window()
elif button == "next":
Additional_Config_Window()
elif lb is not None:
Basic_Host_Window(lb)
增删改查永远是一个软件系统绕不开的基础功能。
“查”:
首先是整体查看,我们可以通过一个列表展示所有信息,这时候我们可以用ExtListboxChoiceWindow
组件来实现;然后就是单个查看了,我们可能有多条信息,我们想查看单个信息的详细内容时,我们可以通过点击具体的item
进入详细信息的dialog
,如何实现呢?listbox
中有一个current
的概念,也就是listbox
中每个li
的唯一标识,我们可以用列表的index
来填充,因为往往列表页面的信息也无非是数组或者是列表的方式,我们获取到当前的current
,即获取到数组的索引,然后就是根据索引查值了,我们再调用新增页面,将查到的值赋值到Textbox
即可,Textbox
有一个setText
就是做这个事情的。当然我们的ExtEntryWindow
组件也可以做到赋值填充。请参考上述代码中的lb
,其实就是listbox
的li.current()
接口。
“增”:
我们可以通过一个新增按钮或者listbox
中的一个li
作为新增按钮来触发新增操作,点击后出现一个dialog
,dialog
中有一些Textbox
、Radio
、Checkbox
等。
def Basic_Host_Window(current, data=None):
buttons = [ 'save', 'cancel', 'exit']
if not data:
data = ['IP Address:', 'Hostname:', 'Password:']
if current != 'add':
data = get_format_data(Basic_Config[current], BASIC_TYPE)
buttons.insert(1, 'Delete')
host = ExtEntryWindow(
screen,
'{} host'.format('Add' if current == 'add' else 'Edit'),
'Please fill storage host info.',
data,
width = 40,
entryWidth = 40,
buttons = buttons
)
“改”:
修改操作的方法是在list
页面选中需要修改的项,然后进入详情页面,可以查看之前创建时填写的信息,也就是我们在“查”中查看单个信息提到的方式,我们所要做的就是在用户点击save
按钮的时候,获取用户编辑后的数据,再进行一次修改即可,在我们工具中,此操作就是根据索引修改数组中对应索引的数据而已。
“删”:
有增就有删,这边我暂时还没实现批量删除的功能,一方面python snack
的支持功能有限,一方面时间有限,所以我只实现了单个删除的功能,在新增和编辑的页面添加一个delete
按钮即可,为了提醒用户错删,我们还要加上一个确认提示框。
if host[1] == "delete":
button = ExtButtonChoiceWindow( screen,
'Delete host',
'Are you sure to delete current host?'
)
if button == "ok":
del(Basic_Config[current])
else:
Basic_Host_Window(current)
构建自己的组件库真的很有必要,对于默认的button
样式,我真是吐槽到不想再吐槽,它居然还认为自己的border
很nice
?!所以最终构建自己的组件库的初衷就是想把各个dialog
中的button
改为compactbutton
,没办法,默认的dialog
组件不给改呀,所以我们得自己返璞归真一下。
当然我们做扩展组件库,也不是仅仅因为一个button
样式,还有很多新需求都要依赖自己扩展的组件。比如热键,原生dialog
无法支持热键;还有进度条的进度时间和任务信息展示;还有Gridform
的动态布局等等。具体就不一一介绍了,想深入了解的直接看代码,做个小Demo
,一目了然。
充实的正菜吃饱了,是时候来一波甜菜漱漱口,解解渴了。
在做进度条页面的时候,想除了显示进度任务完成信息之外,还想显示一下开始时间和花费时间。发现python
的time
模块比较坑爹,对于时间差的转换支持不行,查阅资料只发现datetime
可以将时间差转换为微秒、秒和小时三个单位,但是我想实现时间差的自动转换,也就是60s
自动转换为1min
,60min
转为1h
,24h
转为1d
,超越天为单位的我就不进行转换了,逻辑不难,只是拿出来分享给有需要的人,不必重复造轮子罢了。
def get_time_interval(start_time):
start_time = datetime.fromtimestamp(start_time)
now_time = datetime.fromtimestamp(time.time())
interval = (now_time - start_time).seconds
format_interval = get_format_interval(interval)
return format_interval
def get_format_interval(interval):
if interval < 60:
format_interval = "{}s".format(str(interval))
elif 60 <= interval < 60*60:
format_interval = "{}min {}s".format(
str(interval/60), str(interval%60))
elif 60*60 <= interval < 60*60*24:
format_interval = "{}h {}min {}s".format(
str(interval/(60*60)),
str(interval%(60*60)/60),
str(interval%(60*60)%60)
)
elif 60*60*24 <= interval:
format_interval = "{}d {}h {}min {}s".format(
str(interval/(60*60*24)),
str(interval%(60*60*24)/60*60),
str(interval%(60*60)/60),
str(interval%(60*60)%60)
)
return format_interval
原本只是想做一个终端图形化的进度条页面,但是后续需求越来越多,导致做成了一个部署控制台工具,整个工程开发和优化花了大约两个星期的时间,项目中遇到的很多难点和问题很多都与python snack
无关,所以没有做详细解释,就比如上述的甜菜,大家有兴趣的自行翻阅代码即可。
python snack
还有很多未知的我没有使用,比如checkbox tree
等,但我相信万变不离其宗,有了这次实践,其他组件的使用和扩展应该不会花很多时间,其实做这个东西,我最深的感触就是前端发展的迅速,python snack
是2000
年初的产物了,很多页面逻辑跟jQuery
比起来要弱的多,更别说现在的angular
,vue
等等了,但是领域不同,毕竟是伪终端页面,能做成这样已经不错了。如果是真正的桌面图形化界面(GUI
),有pyqt
这种神器,功能貌似很强大。
我在之前的一个项目中,就使用过python snack
做的控制台,当然当时不知道是用这个技术做的,当时觉得蛮牛的,尝试过修改终端文字成汉子,后来没有成功,便不了了之。这次机缘巧合,工作需要做这么一个控制台,在工作中学习和使用自己感兴趣的技术的感觉真是爽呀。工作中运用技术和自己业余时间学习新技术并做个小Demo
完全是不一样的,工作中运用会不断有新需求,不断精益求精,不断深入。所以以工作作为平台,实现自己的技术价值,会有很大的成就感,与大家共勉咯。(#^.^#)