瞭解linux
的人應該據說過Newt
,Newt
是一個爲RedHat
安裝程序而設計的基於文本的窗口開發工具,它是由c
語言編寫並不依賴X
包,linux
下的dialog
和whiptail
都是基於它。而咱們今天討論的snack
則是Newt
提供的python
接口,redhat
的系統都自帶這個模塊,本文就如何使用snack
製做僞終端頁面展開講解,並配合代碼展現實現效果。html
爲啥說是最佳實踐呢?由於我使用snack
的過程當中,上網查閱相關資料,發現有關信息甚少。偶爾幾篇文章都是處於API
或者Demo
的級別,而且講的都不全,更別說高級擴展功能了。我正好工做須要給咱們的一個系統作一個終端部署控制檯UI
,因此我就使用了python snack
來實現,期間不斷新需求,不斷迭代,從基本頁面到增刪改查,再到校驗、再到配置導入、再到進度條等等,不斷的迭代開發讓我對snack
不斷地加深認知,它支持的或不支持的我都想辦法一一解決,因此在這把我這段時間的收貨進行總結並分享給須要的人。前端
本文實踐的需求是作一個部署控制檯工具,該工具主要分爲三個階段:基礎配置、高級配置和部署進度。基礎配置頁面須要咱們建立一些主機,填寫一些主機的信息,好比IP
、Hostname
和Password
,而後高級配置咱們也須要建立一些主機,不過咱們能夠複用基礎設置的主機,因此咱們的工具要支持在高級配置中導入基礎配置的功能,在高級配置中咱們還有一個全局配置,也就是不限於單個主機的配置(其中具體部署原理和是非,我就很少展開贅述,這不是本文的重點)。最後就是進度條頁面,咱們能夠展現部署的過程階段和相關時間信息。vue
項目地址: https://github.com/tony-yin/B...
先上幾道涼菜,給你們開開胃。所謂的涼菜就是介紹一下python snack
的基礎組件,基礎組件不少,相似html
,主要有:python
而後就是一些組合組件,也就是基於上述基礎組件封裝而獲得,主要有:linux
上面這些組件就是全部的基礎組件(組合組件也算基礎組件),這些組件最終呈現還須要grid
和form
這兩個組件,grid
表示「網格」的意思,跟html
中的table
相似,由行和列組成,咱們的基礎組件須要放在網格中來實現頁面佈局;而form
也相似「表單」,咱們須要把grid
填充到form
中,運行後,就能夠看到圖形化頁面了。git
通過上面基礎組件的介紹,想必你對snack
的組件有了充分的瞭解,這時候你能夠參考文末的refer
作幾個小demo
,作了以後你會發現頁面是出來了,emmm... 但是感受好繁瑣哦,不少重複性的代碼,並且頁面佈局也怪怪的,若是要把佈局搞好,又須要加不少代碼。github
咱們把用基礎組件的階段稱之爲「遠古時代」,每作一個window
,都得一瓦一磚地慢慢堆砌,這樣效率過低了,因此咱們急需一波「工業革命」來提升生產力。數組
python snack
彷佛考慮到了這個問題,它在上述基礎組件以外還提供了dialog
相關組件,dialog
組件即集大成者,一個dialog
組件就是一個window
,也就是咱們上面所說的form
,而且該form
中填充了必需的各類基礎組件,dialog
組件主要有:iview
當今社會,你們吃慣了大魚大肉,反而更是想念農村的野味。同理,咱們用慣了「工業革命」的產物,發現雖然可用,可是僅僅停留在基礎可用級別上,想換換樣式,加加本身的定製化需求,都是有限的,徹底達不到新需求的技術實現要求。因此,咱們不能只知道用別人實現的現成的產物,咱們能夠嘗試着「返璞歸真」一下,迴歸最初的「遠古時代」,本身實現一把「工業革命」。所謂的「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
徹底是不同的,工做中運用會不斷有新需求,不斷精益求精,不斷深刻。因此以工做做爲平臺,實現本身的技術價值,會有很大的成就感,與你們共勉咯。(#^.^#)