From 67066b813354d49064cf77049f1d3b54c11609d9 Mon Sep 17 00:00:00 2001 From: Arsham Arya Date: Thu, 9 Nov 2023 00:35:01 +0330 Subject: [PATCH] add: logs streaming. --- 2do.md | 1 + models/queue.go | 69 +++++++++++++++++++++++++++++++++ telegram/handlers/containers.go | 47 +++++++++++----------- telegram/handlers/handlers.go | 4 +- telegram/keyboards/keyboards.go | 16 ++++---- 5 files changed, 105 insertions(+), 32 deletions(-) create mode 100644 models/queue.go diff --git a/2do.md b/2do.md index 313ed0c..8e1b124 100644 --- a/2do.md +++ b/2do.md @@ -10,6 +10,7 @@ - [x] Zero stats problem - [x] Container Start Stop handlers and functionality - [x] Welcome message should have button and better message +- [x] Logs streaming - [x] Add image handlers, next, prev, back - [x] Image size should be in human readable units - [ ] Image ID should be shorted (trimmed) diff --git a/models/queue.go b/models/queue.go new file mode 100644 index 0000000..4f7a479 --- /dev/null +++ b/models/queue.go @@ -0,0 +1,69 @@ +package models + +import "fmt" + +type Node struct { + Value string + Next *Node +} + +func newNode(value string, next *Node) *Node { + return &Node{ + Value: value, + Next: next, + } +} + +type Queue struct { + Length int + head *Node + tail *Node +} + +// Add to the end, remove from the beginning +func NewQueue() *Queue { + return &Queue{} +} + +func (q *Queue) isZeroOrOneNode() (bool, *Node) { + if q.Length == 0 { + return true, nil + } else if q.Length == 1 { + value := q.head + q.head = nil + q.tail = nil + q.Length-- + return true, value + } + return false, nil +} + +func (q *Queue) Push(value string) *Queue { + new_node := newNode(value, nil) + if q.Length == 0 { + q.head = new_node + } else { + q.tail.Next = new_node + } + q.tail = new_node + q.Length++ + return q +} + +func (q *Queue) Pop() *Node { + if ok, node := q.isZeroOrOneNode(); ok { + return node + } + node := q.head + q.head = q.head.Next + q.Length-- + return node +} + +func (q *Queue) String() string { + printable_list := "" + for node := q.head; node != nil; node = node.Next { + printable_list += fmt.Sprintln(node.Value) + } + return printable_list +} diff --git a/telegram/handlers/containers.go b/telegram/handlers/containers.go index 0dcff19..dba8940 100644 --- a/telegram/handlers/containers.go +++ b/telegram/handlers/containers.go @@ -75,8 +75,8 @@ func (h *handler) ContainersList(ctx telebot.Context) error { ) } -func (h *handler) Logs(ctx telebot.Context) error { - // TODO: A list (queue) of logs, append to the end and remove from the beginning +func (h *handler) ContainerLogs(ctx telebot.Context) error { + // TODO: Starting from the beginning might cause confusion in long stream of errors, we should have a navigate till to the end button. userID := ctx.Chat().ID index, err := strconv.Atoi(ctx.Data()) if err != nil { @@ -91,31 +91,31 @@ func (h *handler) Logs(ctx telebot.Context) error { } streamer := bufio.NewScanner(stream) - latestMsg := "" + queue := models.NewQueue() for streamer.Scan() { select { case <-quit: return nil default: - if newMsg := streamer.Text(); newMsg != latestMsg { - err := ctx.Edit( - newMsg, - keyboards.Back(index, true), - ) - if err != nil { - log.Gl.Error(err.Error()) - } - latestMsg = newMsg - } else { - log.Gl.Debug("same info") + newMsg := streamer.Text() + queue.Push(newMsg) + if queue.Length > 10 { // TODO: 10 should not be hard-coded + queue.Pop() } - time.Sleep(time.Second) + + // Omitted error by purpose (the error is just about not modified message because of repetitive content) + ctx.Edit( + queue.String(), + keyboards.Back(index, true), + ) + time.Sleep(time.Millisecond * 500) + // TODO: sleeping time, not hardcoded, not too much, not so little (under 500 millisecond would be annoying) } } return ctx.Respond() } -func (h *handler) Stats(ctx telebot.Context) error { +func (h *handler) ContainerStats(ctx telebot.Context) error { userID := ctx.Chat().ID index, err := strconv.Atoi(ctx.Data()) if err != nil { @@ -129,7 +129,7 @@ func (h *handler) Stats(ctx telebot.Context) error { log.Gl.Error(err.Error()) } streamer := bufio.NewScanner(stream) - latest_msg := "" + latestMsg := "" for streamer.Scan() { select { case <-quit: @@ -137,18 +137,21 @@ func (h *handler) Stats(ctx telebot.Context) error { return nil default: stats := models.Stats{} - json.Unmarshal(streamer.Bytes(), &stats) - msg := msgs.FmtStats(stats) - if msg != latest_msg { + err := json.Unmarshal(streamer.Bytes(), &stats) + if err != nil { + log.Gl.Error(err.Error()) + } + + if newMsg := msgs.FmtStats(stats); newMsg != latestMsg { err := ctx.Edit( - msg, + newMsg, keyboards.Back(index, true), telebot.ModeMarkdownV2, ) if err != nil { log.Gl.Error(err.Error()) } - latest_msg = msg + latestMsg = newMsg } time.Sleep(time.Second) } diff --git a/telegram/handlers/handlers.go b/telegram/handlers/handlers.go index dc6c190..23cdfdc 100644 --- a/telegram/handlers/handlers.go +++ b/telegram/handlers/handlers.go @@ -32,8 +32,8 @@ func Register(bot *telebot.Bot, docker contracts.Docker, session repo.Session) { h.bot.Handle(btns.ContNext.Key(), h.ContainersNavBtn) h.bot.Handle(btns.ContPrev.Key(), h.ContainersNavBtn) - h.bot.Handle(btns.ContLogs.Key(), h.Logs) - h.bot.Handle(btns.ContStats.Key(), h.Stats) + h.bot.Handle(btns.ContLogs.Key(), h.ContainerLogs) + h.bot.Handle(btns.ContStats.Key(), h.ContainerStats) h.bot.Handle(btns.ContBack.Key(), h.ContainersBackBtn) h.bot.Handle(btns.ContStop.Key(), h.ContainerStop) h.bot.Handle(btns.ContStart.Key(), h.ContainerStart) diff --git a/telegram/keyboards/keyboards.go b/telegram/keyboards/keyboards.go index 3ffaa10..82dacc3 100644 --- a/telegram/keyboards/keyboards.go +++ b/telegram/keyboards/keyboards.go @@ -24,17 +24,17 @@ func ContainersList(index int, containerIsOn bool) *telebot.ReplyMarkup { keyboard.Inline( telebot.Row{ - keyboard.Data("⬅", btns.ContPrev.String(), fmt.Sprint(index-1)), - keyboard.Data("➡", btns.ContNext.String(), fmt.Sprint(index+1)), + keyboard.Data("Prev ⬅", btns.ContPrev.String(), fmt.Sprint(index-1)), + keyboard.Data("Next ➡", btns.ContNext.String(), fmt.Sprint(index+1)), }, telebot.Row{ switchBtn(keyboard, index, containerIsOn), - keyboard.Data("Remove", btns.ContRemove.String(), fmt.Sprint(index)), - keyboard.Data("Rename", btns.ContRename.String(), fmt.Sprint(index)), + keyboard.Data("Remove 🗑", btns.ContRemove.String(), fmt.Sprint(index)), + keyboard.Data("Rename ✏️", btns.ContRename.String(), fmt.Sprint(index)), }, telebot.Row{ - keyboard.Data("Logs", btns.ContLogs.String(), fmt.Sprint(index)), - keyboard.Data("Stats", btns.ContStats.String(), fmt.Sprint(index)), + keyboard.Data("Logs 🪵", btns.ContLogs.String(), fmt.Sprint(index)), + keyboard.Data("Stats 📊", btns.ContStats.String(), fmt.Sprint(index)), }, ) @@ -74,8 +74,8 @@ func Back(index int, containerIsOn bool) *telebot.ReplyMarkup { func switchBtn(keyboard *telebot.ReplyMarkup, index int, containerIsOn bool) telebot.Btn { if containerIsOn { - return keyboard.Data("Stop", btns.ContStop.String(), fmt.Sprint(index)) + return keyboard.Data("Stop 🛑", btns.ContStop.String(), fmt.Sprint(index)) } else { - return keyboard.Data("Start", btns.ContStart.String(), fmt.Sprint(index)) + return keyboard.Data("Start 🏃", btns.ContStart.String(), fmt.Sprint(index)) } }