Skip to content

Commit

Permalink
Merge pull request #132 from kaytu-io/feat-adds-help
Browse files Browse the repository at this point in the history
fix: Adds help to status bar
  • Loading branch information
salehkhazaei committed May 17, 2024
2 parents a04b23e + 49b6980 commit 72fcc52
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 59 deletions.
38 changes: 11 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,67 +1,51 @@
<p align="center">
<a href="https://www.kaytu.io"><img src=".github/assets/Kaytu-New-Logo.svg" alt="Kaytu Logo" width="300" /></a>

<p align="center">Kaytu enables engineering, DevOps, and SRE teams to reduce cloud costs by recommending optimal workload configurations based on <b>actual-usage</b>, ensuring savings without compromise.
<p align="center">Kaytu recommends optimal workload configurations based on <b>actual-usage,</b>, ensuring savings without compromising reliability.
</p>

![Kaytu Gif](.github/assets/kaytu.gif)

## Overview


- **Ease of use**: One-line command. Use without modifying workloads or making configuration changes.
- **Base on actual Usage**: Analyzes the past seven days of usage from Cloud native monitoring (CloudWatch), including advanced AWS CloudWatch metrics (where available).
- **Optimize**: Optimize AWS EC2 Instances & AWS RDS Instances/Clusters.
- **Base on actual Usage**: Analyzes the past seven days of usage from Cloud native monitoring (CloudWatch).
- **Customize**: Optimize for region, CPU, memory, network performance, storage, licenses, and more to match your specific requirements.
- **Secure** - no credentials to share; extracts required metrics from the client side
- **Open-core philosophy** Use without fear of lock-in. The CLI is open-sourced, and the Server side will be open-sourced soon.
- **Coming Soon**: Non-Interactive mode, Azure support, GPU Optimization, Credit utilization for Burst instances, and Observability data from Prometheus

- **Open philosophy** Use without fear of lock-in. The CLI is open-sourced, and the Server side will be open-sourced soon.
- **Coming Soon**: Non-Interactive mode, GCP, Azure, GPU Optimization and Observability data from Prometheus
## Getting Started

### 1. Install Kaytu CLI

**MacOS**
```shell
brew tap kaytu-io/cli-tap && brew install kaytu
```

**Linux**
```shell
curl -fsSL https://raw.githubusercontent.com/kaytu-io/kaytu/main/scripts/install.sh | sh
```

**Windows (and all Binaries)**
Download Windows (Linux, and MacOS) binary from [releases](https://github.com/kaytu-io/kaytu/releases)


### 2. Login to AWS CLI

Kaytu works with your existing AWS CLI profile (read-only access required) to gather metrics.

To confirm your AWS CLI login is working correctly:

```
aws sts get-caller-identity
```
[Click here to see how to log in to AWS CLI.](https://docs.aws.amazon.com/signin/latest/userguide/command-line-sign-in.html)

We respect your privacy. Our open-source code guarantees that we never collect sensitive information like AWS credentials, IPs, tags, etc.

### 3. Run Kaytu CLI

```shell
kaytu
```

it will install aws plugin automatically.
now you can get optimizations by running:

This will run and install any plugins.
To see how you can optimize EC2 Instances, run this command:
```shell
kaytu optimize ec2-instance
```

Some optimizations such as RDS require login:
```shell
kaytu login

```
For RDS:
```shell
kaytu optimize rds-instance
```
8 changes: 4 additions & 4 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,13 +192,13 @@ func Execute() {
err := manager.NonInteractiveView.WaitAndShowResults(nonInteractiveFlag, csvExportFlag, jsonExportFlag)
return err
} else {
jobsController := controller.NewJobs()
statusBar := view.NewStatusBarView(jobsController)
jobsPage := view.NewJobsPage(jobsController)

helpController := controller.NewHelp()
helpPage := view.NewHelpPage(helpController)

jobsController := controller.NewJobs()
statusBar := view.NewStatusBarView(jobsController, helpController)
jobsPage := view.NewJobsPage(jobsController)

optimizationsController := controller.NewOptimizations()
optimizationsPage := view.NewOptimizationsView(optimizationsController, helpController, statusBar)
optimizationsDetailsPage := view.NewOptimizationDetailsView(optimizationsController, helpController, statusBar)
Expand Down
2 changes: 2 additions & 0 deletions pkg/style/style.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,7 @@ var (
StatusBarStyle = lipgloss.NewStyle().Background(lipgloss.Color("#222222")).Foreground(lipgloss.Color("#ffffff")).Width(9999)
JobsStatusStyle = lipgloss.NewStyle().Background(lipgloss.Color("#dd5200")).Foreground(lipgloss.Color("#ffffff"))
ErrorStatusStyle = lipgloss.NewStyle().Background(lipgloss.Color("#aa2222")).Foreground(lipgloss.Color("#ffffff"))

InfoStatusStyle = lipgloss.NewStyle().Background(lipgloss.Color("#3a3835")).Foreground(lipgloss.Color("#ffffff"))
InfoStatusStyle2 = lipgloss.NewStyle().Background(lipgloss.Color("#006d69")).Foreground(lipgloss.Color("#ffffff"))
)
5 changes: 3 additions & 2 deletions view/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ func (m *App) ChangePage(id PageEnum) tea.Cmd {
}

func (m *App) Init() tea.Cmd {
m.ChangePage(Page_Optimizations)
return tea.Batch(m.pages[m.activePageIdx].Init(), tea.EnterAltScreen, TickCmdWithDuration(100*time.Microsecond))
}

Expand All @@ -103,8 +104,8 @@ func (m *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg.String() {
case "ctrl+c":
return m, tea.Quit
case "ctrl+h":
changePageCmd = tea.Batch(changePageCmd, m.ChangePage(Page_Help))
//case "ctrl+h":
// changePageCmd = tea.Batch(changePageCmd, m.ChangePage(Page_Help))
case "ctrl+j":
changePageCmd = tea.Batch(changePageCmd, m.ChangePage(Page_Jobs))
case "esc":
Expand Down
20 changes: 11 additions & 9 deletions view/page_optimization.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,21 @@ func (m OptimizationsPage) OnClose() Page {
return m
}
func (m OptimizationsPage) OnOpen() Page {
return m
}

func (m OptimizationsPage) Init() tea.Cmd {
m.helpController.SetKeyMap([]string{
"↑/↓: move",
"←/→: scroll right and left in the table",
"enter: see details",
"p: change preferences for one item",
"P: change preferences for all items",
"pgdown/pgup: next/prev page",
"←/→: scroll in the table",
"enter: see resource details",
"p: change preferences",
"P: change preferences for all resources",
"r: load all items in current page",
"ctrl+j: list of jobs",
"q/ctrl+c: exit",
})
return m
}

func (m OptimizationsPage) Init() tea.Cmd {
return nil
}

Expand Down Expand Up @@ -183,7 +185,7 @@ func (m OptimizationsPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
newStatusBar, _ := m.statusBar.Update(msg)
m.statusBar = newStatusBar.(StatusBarView)

m.table = m.table.WithPageSize(m.GetHeight() - 8).WithMaxTotalWidth(m.GetWidth())
m.table = m.table.WithPageSize(m.GetHeight() - (7 + m.statusBar.Height())).WithMaxTotalWidth(m.GetWidth())

return m, cmd
}
Expand Down
4 changes: 3 additions & 1 deletion view/page_optimization_details.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,9 @@ func (m OptimizationDetailsPage) OnOpen() Page {
m.selectedDevice = ""
m.helpController.SetKeyMap([]string{
"↑/↓: move",
"esc/←: back to optimizations list",
"←/→: scroll in the table",
"enter: switch to device detail table",
"esc: back to optimizations list",
"q/ctrl+c: exit",
})
return m
Expand Down
4 changes: 2 additions & 2 deletions view/page_preferences_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ func (m PreferencesConfigurationPage) View() string {
//}
//builder.WriteString(style.SvcDisable.Render(" "))

visibleCount := m.GetHeight() - 5
visibleCount := m.GetHeight() - (4 + m.statusBar.Height())
builder.WriteString("\n")
if m.visibleStartIdx > 0 {
builder.WriteString(" ⇡⇡⇡")
Expand Down Expand Up @@ -247,7 +247,7 @@ func (m *PreferencesConfigurationPage) fixVisibleStartIdx() {
m.visibleStartIdx--
}

visibleCount := m.GetHeight() - 5
visibleCount := m.GetHeight() - (4 + m.statusBar.Height())
for m.focused >= m.visibleStartIdx+visibleCount {
m.visibleStartIdx++
}
Expand Down
53 changes: 39 additions & 14 deletions view/view_statusbar.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,64 @@ import (
)

type StatusBarView struct {
helpController *controller.Help
jobsController *controller.Jobs
content string
width int
}

func NewStatusBarView(JobsController *controller.Jobs) StatusBarView {
return StatusBarView{jobsController: JobsController}
func NewStatusBarView(JobsController *controller.Jobs, helpController *controller.Help) StatusBarView {
return StatusBarView{jobsController: JobsController, helpController: helpController}
}

func (v StatusBarView) Init() tea.Cmd { return nil }
func (v StatusBarView) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
runningCount, failedCount := len(v.jobsController.RunningJobs()), len(v.jobsController.FailedJobs())
switch msg := msg.(type) {
case tea.WindowSizeMsg:
v.width = msg.Width
}

failedJobs := v.jobsController.FailedJobs()
runningCount, failedCount := len(v.jobsController.RunningJobs()), len(failedJobs)

var status []string

var helpLines []string
w := 0

if runningCount > 0 {
line := fmt.Sprintf(" running jobs: %d ", runningCount)
w += len(line)
helpLines = append(helpLines, style.JobsStatusStyle.Render(line))
}

for idx, line := range v.helpController.Help() {
line = fmt.Sprintf(" %s ", line)
w += len(line)
if w > v.width {
helpLines = append(helpLines, "\n")
w = 0
}

if idx%2 == 0 {
helpLines = append(helpLines, style.InfoStatusStyle.Render(line))
} else {
helpLines = append(helpLines, style.InfoStatusStyle2.Render(line))
}
}
status = append(status, strings.Join(helpLines, "")+"\n")

if err := v.jobsController.GetError(); len(err) > 0 {
status = append(status, style.ErrorStatusStyle.Render(strings.TrimSpace(err))+"\n")
}
if runningCount > 0 {
status = append(status, style.JobsStatusStyle.Render(fmt.Sprintf(" running jobs: %d ", runningCount)))
}
if failedCount > 0 {
status = append(status, style.JobsStatusStyle.Render(fmt.Sprintf(" failed jobs: %d ", failedCount)))
status = append(status, style.ErrorStatusStyle.Render(fmt.Sprintf("failed job: %s, press ctrl+j to see more", failedJobs[0]))+"\n")
}
if runningCount > 0 || failedCount > 0 {
status = append(status, style.InfoStatusStyle.Render(fmt.Sprintf(" press ctrl+j to see list of jobs ")))
}

status = append(status, style.InfoStatusStyle.Render(fmt.Sprintf(" press ctrl+h to see help page ")))

v.content = strings.Join(status, "")
return v, nil
}
func (v StatusBarView) View() string {
return style.StatusBarStyle.Render(v.content)
return v.content
}

func (v StatusBarView) Height() int {
Expand Down

0 comments on commit 72fcc52

Please sign in to comment.