共计 5952 个字符,预计需要花费 15 分钟才能阅读完成。
了解 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/B…
开胃凉菜
远古时代
先上几道凉菜,给大家开开胃。所谓的凉菜就是介绍一下 python snack
的基础组件,基础组件很多,类似html
,主要有:
- Textbox
- TextboxReflowed
- Button
- Compactbutton
- Checkbox
- Listbox
- SingleRadioButton
- Scale
- Entry
然后就是一些组合组件,也就是基于上述基础组件封装而得到,主要有:
- RadioBar
- ButtonBar
- CheckboxTree
上面这些组件就是所有的基础组件(组合组件也算基础组件),这些组件最终呈现还需要 grid
和form
这两个组件,grid
表示“网格”的意思,跟 html
中的 table
类似,由行和列组成,我们的基础组件需要放在网格中来实现页面布局;而 form
也类似“表单”,我们需要把 grid
填充到 form
中,运行后,就可以看到图形化页面了。
工业革命
经过上面基础组件的介绍,想必你对 snack
的组件有了充分的了解,这时候你可以参考文末的 refer
做几个小demo
,做了之后你会发现页面是出来了,emmm… 可是感觉好繁琐哦,很多重复性的代码,而且页面布局也怪怪的,如果要把布局搞好,又需要加很多代码。
我们把用基础组件的阶段称之为“远古时代”,每做一个window
,都得一瓦一砖地慢慢堆砌,这样效率太低了,所以我们急需一波“工业革命”来提高生产力。
python snack
似乎考虑到了这个问题,它在上述基础组件之外还提供了 dialog
相关组件,dialog
组件即集大成者,一个 dialog
组件就是一个 window
,也就是我们上面所说的form
,并且该form
中填充了必需的各种基础组件,dialog
组件主要有:
- ListboxChoiceWindow
- ButtonChoiceWindow
- EntryWindow
返璞归真
当今社会,大家吃惯了大鱼大肉,反而更是想念农村的野味。同理,我们用惯了“工业革命”的产物,发现虽然可用,但是仅仅停留在基础可用级别上,想换换样式,加加自己的定制化需求,都是有限的,完全达不到新需求的技术实现要求。所以,我们不能只知道用别人实现的现成的产物,我们可以尝试着“返璞归真”一下,回归最初的“远古时代”,自己实现一把“工业革命”。所谓的“dialog”组件无非也就是基础组件的封装而已,我们也可以自己实现一套自己的组件库,这个在前端是非常流行的,例如 font-awesome
、iview
、ant-design
等等。这里我们自己实现了以下dialog
:
- ExtButtonChoiceWindow
- ExtAlert
- ExtCheckboxWindow
- ExtListboxChoiceWindow
- ExtEntryWindow
- ExtPwdEntryWindow
- ExtProgressWindow
- ExtTextWindow
扩展的功能主要有:
- 热键支持扩展
- 按钮样式扩展
- 布局大小自动化扩展
- 暗文输入框扩展
- 弹出窗口扩展
- 进度条窗口信息展示扩展
- 动态展示扩展
扩展组件库地址: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
完全是不一样的,工作中运用会不断有新需求,不断精益求精,不断深入。所以以工作作为平台,实现自己的技术价值,会有很大的成就感,与大家共勉咯。(#^.^#)