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  }