github.com/oam-dev/kubevela@v1.9.11/references/cli/top/view/app.go (about) 1 /* 2 Copyright 2022 The KubeVela Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package view 18 19 import ( 20 "context" 21 "log" 22 "time" 23 24 "github.com/gdamore/tcell/v2" 25 "github.com/rivo/tview" 26 "k8s.io/client-go/rest" 27 "sigs.k8s.io/controller-runtime/pkg/client" 28 29 "github.com/oam-dev/kubevela/references/cli/top/component" 30 "github.com/oam-dev/kubevela/references/cli/top/config" 31 "github.com/oam-dev/kubevela/references/cli/top/model" 32 ) 33 34 // App application object 35 type App struct { 36 // ui 37 *component.App 38 // client is the k8s client 39 client client.Client 40 config config.Config 41 // command abstract interface action to a command 42 command *Command 43 content *PageStack 44 ctx context.Context 45 cancelFunc context.CancelFunc 46 } 47 48 const ( 49 delay = time.Second * 10 50 ) 51 52 // NewApp return a new app object 53 func NewApp(c client.Client, restConfig *rest.Config, namespace string) *App { 54 conf := config.Config{ 55 RestConfig: restConfig, 56 Theme: config.LoadThemeConfig(), 57 } 58 a := &App{ 59 App: component.NewApp(conf.Theme), 60 client: c, 61 config: conf, 62 ctx: context.Background(), 63 } 64 a.command = NewCommand(a) 65 a.content = NewPageStack(a) 66 a.ctx = context.WithValue(a.ctx, &model.CtxKeyNamespace, namespace) 67 return a 68 } 69 70 // Init the app 71 func (a *App) Init() { 72 a.command.Init() 73 a.content.Init() 74 a.content.AddListener(a.Menu()) 75 a.content.AddListener(a.Crumbs()) 76 77 a.App.Init() 78 a.layout() 79 80 a.bindKeys() 81 a.SetInputCapture(a.keyboard) 82 83 a.defaultView(nil) 84 } 85 86 func (a *App) layout() { 87 main := tview.NewFlex().SetDirection(tview.FlexRow) 88 main.SetBorder(true) 89 main.SetBorderColor(a.config.Theme.Border.App.Color()) 90 main.AddItem(a.buildHeader(), config.HeaderRowNum, 1, false) 91 main.AddItem(a.content, 0, 3, true) 92 main.AddItem(a.Crumbs(), config.FooterRowNum, 1, false) 93 a.Main.AddPage("main", main, true, false) 94 } 95 96 func (a *App) buildHeader() tview.Primitive { 97 info := a.InfoBoard() 98 info.Init(a.client, a.config.RestConfig) 99 header := tview.NewFlex() 100 header.SetDirection(tview.FlexColumn) 101 header.AddItem(info, 0, 3, false) 102 header.AddItem(a.Menu(), 0, 2, false) 103 header.AddItem(a.Logo(), config.LogoColumnNum, 3, false) 104 return header 105 } 106 107 // Run is the application running entrance 108 func (a *App) Run() error { 109 go func() { 110 a.QueueUpdateDraw(func() { 111 a.Main.SwitchToPage("main") 112 }) 113 }() 114 a.Refresh() 115 if err := a.Application.Run(); err != nil { 116 return err 117 } 118 return nil 119 } 120 121 // Refresh will refresh the ui after the delay time 122 func (a *App) Refresh() { 123 ctx := context.Background() 124 ctx, a.cancelFunc = context.WithCancel(ctx) 125 // system info board component 126 board := a.Components()["info"].(*component.InfoBoard) 127 go func() { 128 for { 129 select { 130 case <-ctx.Done(): 131 log.Printf("SystemInfo updater canceled!") 132 return 133 case <-time.After(delay): 134 board.UpdateInfo(a.client, a.config.RestConfig) 135 } 136 } 137 }() 138 } 139 140 // inject add a new component to the app's main view to refresh the content of the main view 141 func (a *App) inject(v model.View) { 142 v.Init() 143 a.content.PushView(v) 144 } 145 146 func (a *App) bindKeys() { 147 a.AddAction(model.KeyActions{ 148 component.KeyQ: model.KeyAction{Description: "Back", Action: a.Back, Visible: true, Shared: true}, 149 component.KeyHelp: model.KeyAction{Description: "Help", Action: a.helpView, Visible: true, Shared: true}, 150 }) 151 } 152 153 func (a *App) keyboard(event *tcell.EventKey) *tcell.EventKey { 154 if action, ok := a.HasAction(component.StandardizeKey(event)); ok { 155 return action.Action(event) 156 } 157 return event 158 } 159 160 // defaultView is the first view of running application 161 func (a *App) defaultView(event *tcell.EventKey) *tcell.EventKey { 162 a.command.run(a.ctx, "app") 163 return event 164 } 165 166 // helpView to display the view after pressing the Help key(?) 167 func (a *App) helpView(_ *tcell.EventKey) *tcell.EventKey { 168 top := a.content.TopView() 169 if top != nil && top.Name() == "Help" { 170 a.content.PopView() 171 return nil 172 } 173 helpView := NewHelpView(a) 174 a.inject(helpView) 175 return nil 176 } 177 178 // Back to return before view corresponding to the ESC key 179 func (a *App) Back(_ *tcell.EventKey) *tcell.EventKey { 180 if !a.content.IsLastView() { 181 a.content.PopView() 182 } 183 return nil 184 } 185 186 // Exist the app 187 func (a *App) Exist(_ *tcell.EventKey) *tcell.EventKey { 188 a.Stop() 189 return nil 190 } 191 192 // SwitchTheme switch page to the theme switch page 193 func (a *App) SwitchTheme(_ *tcell.EventKey) *tcell.EventKey { 194 closeFun := func() { 195 a.Main.RemovePage("theme") 196 } 197 selector := component.NewThemeSelector(a.config.Theme, closeFun) 198 selector.Init() 199 selector.Start() 200 201 a.Main.AddPage("theme", modal(selector.Frame, 45, 30), true, true) 202 a.Main.SwitchToPage("theme") 203 return nil 204 } 205 206 func modal(p tview.Primitive, width, height int) tview.Primitive { 207 return tview.NewFlex(). 208 AddItem(nil, 0, 1, false). 209 AddItem(tview.NewFlex().SetDirection(tview.FlexRow). 210 AddItem(nil, 0, 1, false). 211 AddItem(p, height, 1, true). 212 AddItem(nil, 0, 1, false), width, 1, true). 213 AddItem(nil, 0, 1, false) 214 }