Skip to content

Commit

Permalink
feat: blink when processing
Browse files Browse the repository at this point in the history
  • Loading branch information
somnek committed Jan 15, 2024
1 parent a137fa8 commit 818600e
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 21 deletions.
58 changes: 56 additions & 2 deletions helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,45 @@ type actionResult struct {
failed []Container
}

const (
on int = iota
off
)

func checkProcess(id string, processes map[string]string) bool {
if _, ok := processes[id]; ok {
return true
}
return false
}

// updatePendingProcesses cross check the actual state of container
// with the desired state in m.processes
// if the state match, remove the container from m.processes
// implies that the action is completed
// return the updated m.processes
func updatePendingProcesses(m model) map[string]string {
containers := m.containers
for _, c := range containers {
id := c.id
state := c.state
desiredState := m.processes[id]
if _, ok := m.processes[id]; ok {
if state == desiredState {
delete(m.processes, id)
}
}
}
return m.processes
}

// addProcess add Process to m.processes
// container Processes are used to control the blinkSwitch
func addProcess(m *model, id, desiredState string) {
logToFile(fmt.Sprintf("addProcess: %s %s", id, desiredState))
m.processes[id] = desiredState
}

func unpauseAndWriteLog(m model) (tea.Model, tea.Cmd) {
client, err := docker.NewClientFromEnv()
if err != nil {
Expand All @@ -32,6 +71,8 @@ func unpauseAndWriteLog(m model) (tea.Model, tea.Cmd) {
for _, c := range targets {
if c.state == "paused" {
go unpauseContainer(client, c.id)
desiredState := "running"
addProcess(&m, c.id, desiredState)
res.success = append(res.success, c)
} else {
res.failed = append(res.failed, c)
Expand Down Expand Up @@ -77,6 +118,8 @@ func pauseAndWriteLog(m model) (tea.Model, tea.Cmd) {
for _, c := range targets {
if c.state == "running" {
go pauseContainer(client, c.id)
desiredState := "paused"
addProcess(&m, c.id, desiredState)
res.success = append(res.success, c)
} else {
res.failed = append(res.failed, c)
Expand All @@ -94,7 +137,7 @@ func pauseAndWriteLog(m model) (tea.Model, tea.Cmd) {

if failedCount > 0 {
logs += fmt.Sprintf(
"🚧 %v container(s) is not running, skipping...\n",
"🚧 Unable to pause %v container(s), skipping...\n",
itemCountStyle.Render(fmt.Sprintf("%d", failedCount)))
}

Expand Down Expand Up @@ -122,6 +165,8 @@ func stopAndWriteLog(m model) (tea.Model, tea.Cmd) {
for _, c := range targets {
if c.state == "running" || c.state == "restarting" {
go stopContainer(client, c.id)
desiredState := "exited"
addProcess(&m, c.id, desiredState)
res.success = append(res.success, c)
} else {
res.failed = append(res.failed, c)
Expand All @@ -139,7 +184,7 @@ func stopAndWriteLog(m model) (tea.Model, tea.Cmd) {

if failedCount > 0 {
logs += fmt.Sprintf(
"🚧 unable to stop %v container(s), skipping...\n",
"🚧 Unable to stop %v container(s), skipping...\n",
itemCountStyle.Render(fmt.Sprintf("%d", failedCount)))
}

Expand Down Expand Up @@ -167,7 +212,10 @@ func startAndWriteLog(m model) (tea.Model, tea.Cmd) {
for _, c := range targets {
if c.state == "exited" || c.state == "created" {
go startContainer(client, c.id)
desiredState := "running"
addProcess(&m, c.id, desiredState)
res.success = append(res.success, c)

} else {
res.failed = append(res.failed, c)
}
Expand Down Expand Up @@ -211,6 +259,8 @@ func removeAndWriteLog(m model) (tea.Model, tea.Cmd) {
res := actionResult{}
for _, c := range targets {
go removeContainer(client, c.id)
desiredState := "x"
addProcess(&m, c.id, desiredState)
res.success = append(res.success, c)
}

Expand Down Expand Up @@ -248,6 +298,8 @@ func restartAndWriteLog(m model) (tea.Model, tea.Cmd) {
for _, c := range targets {
if c.state == "running" {
go restartContainer(client, c.id)
desiredState := "running"
addProcess(&m, c.id, desiredState)
res.success = append(res.success, c)
} else {
res.failed = append(res.failed, c)
Expand Down Expand Up @@ -293,6 +345,8 @@ func killAndWriteLog(m model) (tea.Model, tea.Cmd) {
for _, c := range targets {
if c.state == "running" {
killContainer(client, c.id)
desiredState := "x"
addProcess(&m, c.id, desiredState)
res.success = append(res.success, c)
} else {
res.failed = append(res.failed, c)
Expand Down
44 changes: 33 additions & 11 deletions model.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"time"

"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
)

Expand All @@ -26,40 +27,61 @@ const (
)

type model struct {
containers []Container
images []Image
cursor int
selected map[int]struct{}
keys keyMap
logs string
page int
width int
height int
containers []Container
images []Image
cursor int
selected map[int]struct{}
spinner spinner.Model
blinkSwitch int
// TODO: merge process into Container struct
processes map[string]string // map[containerID]desiredState
keys keyMap
logs string
page int
width int
height int
}

// fast tick rate doesn't seems to affect performance (average 20 container)
// TODO: change tickrate to 1s if theres no running docker action
const tickRate = 300 * time.Millisecond

// const tickRate = time.Second

type TickMsg struct {
Time time.Time
}

func doTick() tea.Cmd {
return tea.Tick(time.Second, func(t time.Time) tea.Msg {
return tea.Tick(tickRate, func(t time.Time) tea.Msg {
return TickMsg{Time: t}
})
}

func (m model) Init() tea.Cmd {
return doTick()
return tea.Batch(doTick(), m.spinner.Tick)
}

func initialModel() model {
cursor := 0
// containers
containers := getContainers()
images := getImages()
containers[cursor].desc = buildContainerDescShort(containers[cursor])

// spinner
s := spinner.New()
s.Spinner = spinner.Jump
s.Style = spinnerStyle

// processes
processes := make(map[string]string)
return model{
containers: containers,
images: images,
selected: make(map[int]struct{}),
processes: processes,
spinner: s,
page: pageContainer,
keys: keys,
}
Expand Down
25 changes: 20 additions & 5 deletions style.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ const (
black = lipgloss.Color("#3C3C3C")
lightPink = lipgloss.Color("#F9CFF2")
midPink = lipgloss.Color("#F786AA")
pitchBlack = lipgloss.Color("#1C1C1C")

paletteA1 = lipgloss.Color("#EF6461")
paletteA2 = lipgloss.Color("#E4B363")
paletteA3 = lipgloss.Color("#E8E9EB")
paletteA4 = lipgloss.Color("#E0DFD5")
paletteA5 = lipgloss.Color("#303A2B")
paletteA6 = lipgloss.Color("#313638")
paletteA7 = lipgloss.Color("#517664")
paletteA8 = lipgloss.Color("#2C5530")
paletteA9 = lipgloss.Color("#26547C")
paletteA10 = lipgloss.Color("#255C99")
)

const (
Expand All @@ -35,11 +47,11 @@ const (

var (
stateStyleMap = map[string]lipgloss.Style{
"created": lipgloss.NewStyle().Foreground(midPurple),
"running": lipgloss.NewStyle().Foreground(green),
"paused": lipgloss.NewStyle().Foreground(yellow),
"created": lipgloss.NewStyle().Foreground(paletteA5),
"running": lipgloss.NewStyle().Foreground(paletteA4),
"paused": lipgloss.NewStyle().Foreground(paletteA2),
"restarting": lipgloss.NewStyle().Foreground(orange),
"exited": lipgloss.NewStyle().Foreground(midPink),
"exited": lipgloss.NewStyle().Foreground(paletteA1),
"dead": lipgloss.NewStyle().Foreground(black),
}

Expand All @@ -55,7 +67,7 @@ var (

appStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
Foreground(black)
BorderForeground(black)
bodyStyle = lipgloss.NewStyle().
Align(lipgloss.Left)

Expand All @@ -79,4 +91,7 @@ var (
itemCountStyle = lipgloss.NewStyle().
Foreground(frenchBlue).
Bold(true)
spinnerStyle = lipgloss.NewStyle().
Foreground(celesBlue).
Bold(true)
)
18 changes: 18 additions & 0 deletions update.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,37 @@ package main

import (
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
)

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {

case spinner.TickMsg:
m.spinner, cmd = m.spinner.Update(msg)
return m, cmd

case TickMsg:
// containers
containers := getContainers()
m.containers = containers
// images
images := getImages()
m.images = images

// blink switch
if m.blinkSwitch == on {
m.blinkSwitch = off
} else {
m.blinkSwitch = on
}

// processes
m.processes = updatePendingProcesses(m)

logToFile(m.processes)
return m, doTick()

case tea.WindowSizeMsg:
Expand Down
10 changes: 10 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package main

import (
"log"
"math/rand"
"os"
"time"
)

func logToFile(texts ...any) {
Expand All @@ -16,3 +18,11 @@ func logToFile(texts ...any) {
log.Print(t)
}
}

func waitRandomSeconds() {
// Generate a random number between 1 and 10
seconds := rand.Intn(10) + 1

// Wait for the random number of seconds
time.Sleep(time.Duration(seconds) * time.Second)
}
10 changes: 7 additions & 3 deletions view.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,12 @@ func buildContainerView(m model) (string, string) {
bodyR = buildContainerDescShort(choice)
}

state := stateStyleMap[choice.state].Render("●")
isProcessing := checkProcess(choice.id, m.processes)
stateStyle := stateStyleMap[choice.state]
if isProcessing && m.blinkSwitch == on {
stateStyle = stateStyle.Copy().Foreground(pitchBlack)
}
state := stateStyle.Render("●")
name := choice.name
name = runewidth.Truncate(name, 25, "...")
if _, ok := m.selected[i]; ok {
Expand Down Expand Up @@ -140,8 +145,7 @@ func (m model) View() string {
}

// title
// title := strings.Repeat(" ", 36) + "🐳 Docker"
title := "🐳 Docker"
title := "🐳 Docker" + " " + m.spinner.View()
titleStyle.MarginLeft((m.width / 2) - (lipgloss.Width(title) / 2))
title = titleStyle.Render(title)

Expand Down

0 comments on commit 818600e

Please sign in to comment.