201 lines
4.0 KiB
Go
201 lines
4.0 KiB
Go
package web
|
|
|
|
import (
|
|
"context"
|
|
"embed"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"charm.land/bubbletea/v2"
|
|
"charm.land/lipgloss/v2"
|
|
"github.com/LazyBachelor/LazyPM/internal/app"
|
|
"github.com/LazyBachelor/LazyPM/internal/models"
|
|
"github.com/LazyBachelor/LazyPM/internal/utils/browser"
|
|
"github.com/LazyBachelor/LazyPM/pkg/web/handler"
|
|
"github.com/LazyBachelor/LazyPM/pkg/web/server"
|
|
)
|
|
|
|
type Config = models.Config
|
|
type ValidationFeedback = models.ValidationFeedback
|
|
|
|
type Web struct {
|
|
feedbackChan chan ValidationFeedback
|
|
quitChan chan bool
|
|
submitChan chan<- models.ValidationTrigger
|
|
}
|
|
|
|
func New() *Web {
|
|
return &Web{}
|
|
}
|
|
|
|
//go:embed assets/*
|
|
var assets embed.FS
|
|
|
|
type tuiModel struct {
|
|
width int
|
|
height int
|
|
address string
|
|
quitChan chan struct{}
|
|
}
|
|
|
|
func (m tuiModel) Init() tea.Cmd {
|
|
return nil
|
|
}
|
|
|
|
func (m tuiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
switch msg := msg.(type) {
|
|
case tea.WindowSizeMsg:
|
|
m.width = msg.Width
|
|
m.height = msg.Height
|
|
return m, nil
|
|
case tea.KeyPressMsg:
|
|
switch msg.String() {
|
|
case "q", "esc", "ctrl+c":
|
|
select {
|
|
case <-m.quitChan:
|
|
default:
|
|
close(m.quitChan)
|
|
}
|
|
return m, tea.Quit
|
|
}
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
func (m tuiModel) View() tea.View {
|
|
content := lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, fmt.Sprintf(
|
|
"Web server running at %s\n\nPress q, esc, or Ctrl+C to stop the task and server.\n",
|
|
m.address,
|
|
))
|
|
v := tea.NewView(content)
|
|
v.AltScreen = true
|
|
return v
|
|
}
|
|
|
|
func (w *Web) Run(ctx context.Context, config Config) error {
|
|
app, cleanup, err := app.New(ctx, config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer cleanup()
|
|
|
|
httpServer := server.NewServer(server.Server{
|
|
Address: config.WebAddress,
|
|
Assets: assets,
|
|
App: app,
|
|
})
|
|
|
|
address := config.WebAddress
|
|
if !strings.Contains(address, "http") {
|
|
address = "http://localhost" + address
|
|
}
|
|
|
|
serverErr := make(chan error, 1)
|
|
uiQuitChan := make(chan struct{})
|
|
|
|
var screen *tea.Program
|
|
var screenDone chan struct{}
|
|
|
|
if w.quitChan != nil {
|
|
screen = tea.NewProgram(
|
|
tuiModel{
|
|
address: address,
|
|
quitChan: uiQuitChan,
|
|
},
|
|
)
|
|
|
|
screenDone = make(chan struct{})
|
|
|
|
go func() {
|
|
defer close(screenDone)
|
|
screen.Run()
|
|
}()
|
|
}
|
|
|
|
sigChan := make(chan os.Signal, 1)
|
|
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
|
defer signal.Stop(sigChan)
|
|
|
|
fmt.Println("Starting server at", address+"...")
|
|
go func() {
|
|
if err := httpServer.ListenAndServe(); err != nil &&
|
|
!errors.Is(err, http.ErrServerClosed) {
|
|
serverErr <- err
|
|
}
|
|
}()
|
|
|
|
if w.feedbackChan != nil {
|
|
go func() {
|
|
for feedback := range w.feedbackChan {
|
|
handler.SetTaskFeedback(feedback)
|
|
}
|
|
}()
|
|
}
|
|
|
|
if w.submitChan != nil {
|
|
handler.SetSubmitChan(w.submitChan)
|
|
}
|
|
|
|
var shutdownMsg string
|
|
|
|
if w.quitChan != nil {
|
|
|
|
if err := browser.Open(address); err != nil {
|
|
fmt.Printf("failed to open browser: %s\n", err)
|
|
}
|
|
select {
|
|
case <-w.quitChan:
|
|
shutdownMsg = "Task completed! Ending task and server..."
|
|
case <-uiQuitChan:
|
|
shutdownMsg = "Quit key pressed. Ending task and server..."
|
|
case sig := <-sigChan:
|
|
shutdownMsg = fmt.Sprintf("Received signal %s. Ending task and server...", sig)
|
|
case <-ctx.Done():
|
|
shutdownMsg = "Context cancelled. Ending task and server..."
|
|
case err := <-serverErr:
|
|
if screen != nil {
|
|
screen.Quit()
|
|
<-screenDone
|
|
}
|
|
return err
|
|
}
|
|
} else {
|
|
select {
|
|
case sig := <-sigChan:
|
|
shutdownMsg = fmt.Sprintf("Received signal %s. Closing server...", sig)
|
|
case <-ctx.Done():
|
|
shutdownMsg = "Context cancelled. Closing server..."
|
|
case err := <-serverErr:
|
|
return err
|
|
}
|
|
}
|
|
|
|
if screen != nil {
|
|
screen.Quit()
|
|
<-screenDone
|
|
}
|
|
|
|
fmt.Println(shutdownMsg)
|
|
|
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
return httpServer.Shutdown(shutdownCtx)
|
|
}
|
|
|
|
func (w *Web) SetChannels(feedbackChan chan ValidationFeedback, quitChan chan bool) {
|
|
w.feedbackChan = feedbackChan
|
|
w.quitChan = quitChan
|
|
}
|
|
|
|
func (w *Web) SetSubmitChan(submitChan chan<- models.ValidationTrigger) {
|
|
w.submitChan = submitChan
|
|
}
|