1package ssh
 2
 3import (
 4	"os"
 5	"time"
 6
 7	tea "github.com/charmbracelet/bubbletea"
 8	"github.com/charmbracelet/soft-serve/pkg/access"
 9	"github.com/charmbracelet/soft-serve/pkg/backend"
10	"github.com/charmbracelet/soft-serve/pkg/config"
11	"github.com/charmbracelet/soft-serve/pkg/proto"
12	"github.com/charmbracelet/soft-serve/pkg/ui/common"
13	"github.com/charmbracelet/ssh"
14	"github.com/charmbracelet/wish"
15	bm "github.com/charmbracelet/wish/bubbletea"
16	"github.com/muesli/termenv"
17	"github.com/prometheus/client_golang/prometheus"
18	"github.com/prometheus/client_golang/prometheus/promauto"
19)
20
21var tuiSessionCounter = promauto.NewCounterVec(prometheus.CounterOpts{
22	Namespace: "soft_serve",
23	Subsystem: "ssh",
24	Name:      "tui_session_total",
25	Help:      "The total number of TUI sessions",
26}, []string{"repo", "term"})
27
28var tuiSessionDuration = promauto.NewCounterVec(prometheus.CounterOpts{
29	Namespace: "soft_serve",
30	Subsystem: "ssh",
31	Name:      "tui_session_seconds_total",
32	Help:      "The total number of TUI sessions",
33}, []string{"repo", "term"})
34
35// SessionHandler is the soft-serve bubbletea ssh session handler.
36// This middleware must be run after the ContextMiddleware.
37func SessionHandler(s ssh.Session) *tea.Program {
38	pty, _, active := s.Pty()
39	if !active {
40		return nil
41	}
42
43	ctx := s.Context()
44	be := backend.FromContext(ctx)
45	cfg := config.FromContext(ctx)
46	cmd := s.Command()
47	initialRepo := ""
48	if len(cmd) == 1 {
49		initialRepo = cmd[0]
50		auth := be.AccessLevelByPublicKey(ctx, initialRepo, s.PublicKey())
51		if auth < access.ReadOnlyAccess {
52			wish.Fatalln(s, proto.ErrUnauthorized)
53			return nil
54		}
55	}
56
57	renderer := bm.MakeRenderer(s)
58	if testrun, ok := os.LookupEnv("SOFT_SERVE_NO_COLOR"); ok && testrun == "1" {
59		// Disable colors when running tests.
60		renderer.SetColorProfile(termenv.Ascii)
61	}
62
63	c := common.NewCommon(ctx, renderer, pty.Window.Width, pty.Window.Height)
64	c.SetValue(common.ConfigKey, cfg)
65	m := NewUI(c, initialRepo)
66	opts := bm.MakeOptions(s)
67	opts = append(opts,
68		tea.WithAltScreen(),
69		tea.WithoutCatchPanics(),
70		tea.WithMouseCellMotion(),
71		tea.WithContext(ctx),
72	)
73	p := tea.NewProgram(m, opts...)
74
75	tuiSessionCounter.WithLabelValues(initialRepo, pty.Term).Inc()
76
77	start := time.Now()
78	go func() {
79		<-ctx.Done()
80		tuiSessionDuration.WithLabelValues(initialRepo, pty.Term).Add(time.Since(start).Seconds())
81	}()
82
83	return p
84}