pico

created pr with 54.1 on 2025-03-19T22:54:22Z · by c8ef7d19
cmds
checkout latest patchset:
ssh pr.pico.sh print 54 | git am -3
checkout any patchset in a patch request:
ssh pr.pico.sh print 54.[rev] | git am -3
add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh pr add 54
+2 -1 Makefile #
......@@ -126,10 +126,11 @@ migrate:
126126 $(DOCKER_CMD) exec -i $(DB_CONTAINER) psql -U $(PGUSER) -d $(PGDATABASE) < ./sql/migrations/20241114_add_namespace_to_analytics.sql
127127 $(DOCKER_CMD) exec -i $(DB_CONTAINER) psql -U $(PGUSER) -d $(PGDATABASE) < ./sql/migrations/20241125_add_content_type_to_analytics.sql
128128 $(DOCKER_CMD) exec -i $(DB_CONTAINER) psql -U $(PGUSER) -d $(PGDATABASE) < ./sql/migrations/20241202_add_more_idx_analytics.sql
129+ $(DOCKER_CMD) exec -i $(DB_CONTAINER) psql -U $(PGUSER) -d $(PGDATABASE) < ./sql/migrations/20250319_add_tuns_event_logs_table.sql
129130 .PHONY: migrate
130131
131132 latest:
132- $(DOCKER_CMD) exec -i $(DB_CONTAINER) psql -U $(PGUSER) -d $(PGDATABASE) < ./sql/migrations/20241202_add_more_idx_analytics.sql
133+ $(DOCKER_CMD) exec -i $(DB_CONTAINER) psql -U $(PGUSER) -d $(PGDATABASE) < ./sql/migrations/20250319_add_tuns_event_logs_table.sql
133134 .PHONY: latest
134135
135136 psql:
+35 -0 pkg/apps/auth/api.go #
......@@ -20,6 +20,7 @@ import (
2020 "github.com/picosh/pico/pkg/db/postgres"
2121 "github.com/picosh/pico/pkg/shared"
2222 "github.com/picosh/utils"
23+ "github.com/picosh/utils/pipe"
2324 "github.com/picosh/utils/pipe/metrics"
2425 "github.com/prometheus/client_golang/prometheus/promhttp"
2526 )
......@@ -723,6 +724,39 @@ func metricDrainSub(ctx context.Context, dbpool db.DB, logger *slog.Logger, secr
723724 }
724725 }
725726
727+func tunsEventLogDrainSub(ctx context.Context, dbpool db.DB, logger *slog.Logger, secret string) {
728+ drain := pipe.NewReconnectReadWriteCloser(
729+ ctx,
730+ logger,
731+ shared.NewPicoPipeClient(),
732+ "tuns-event-drain-sub",
733+ "sub tuns-event-drain -k",
734+ 100,
735+ 10*time.Millisecond,
736+ )
737+
738+ for {
739+ scanner := bufio.NewScanner(drain)
740+ scanner.Buffer(make([]byte, 32*1024), 32*1024)
741+ for scanner.Scan() {
742+ line := scanner.Text()
743+ clean := strings.TrimSpace(line)
744+ var log db.TunsEventLog
745+ err := json.Unmarshal([]byte(clean), &log)
746+ if err != nil {
747+ logger.Error("could not unmarshal line", "err", err)
748+ continue
749+ }
750+
751+ logger.Info("inserting tuns event log", "log", log)
752+ err = dbpool.InsertTunsEventLog(&log)
753+ if err != nil {
754+ logger.Error("could not insert tuns event log", "err", err)
755+ }
756+ }
757+ }
758+}
759+
726760 func authMux(apiConfig *shared.ApiConfig) *http.ServeMux {
727761 serverRoot, err := fs.Sub(embedFS, "public")
728762 if err != nil {
......@@ -792,6 +826,7 @@ func StartApiServer() {
792826
793827 // gather metrics in the auth service
794828 go metricDrainSub(ctx, db, logger, cfg.Secret)
829+ go tunsEventLogDrainSub(ctx, db, logger, cfg.Secret)
795830
796831 defer ctx.Done()
797832
+17 -0 pkg/db/db.go #
......@@ -323,6 +323,20 @@ type UserServiceStats struct {
323323 LatestUpdatedAt time.Time
324324 }
325325
326+type TunsEventLog struct {
327+ ID string `json:"id"`
328+ ServerID string `json:"server_id"`
329+ Time *time.Time `json:"time"`
330+ User string `json:"user"`
331+ UserId string `json:"user_id"`
332+ RemoteAddr string `json:"remote_addr"`
333+ EventType string `json:"event_type"`
334+ TunnelType string `json:"tunnel_type"`
335+ ConnectionType string `json:"connection_type"`
336+ TunnelAddrs []string `json:"tunnel_addrs"`
337+ CreatedAt *time.Time `json:"created_at"`
338+}
339+
326340 var NameValidator = regexp.MustCompile("^[a-zA-Z0-9]{1,50}$")
327341 var DenyList = []string{
328342 "admin",
......@@ -415,5 +429,8 @@ type DB interface {
415429
416430 FindUserStats(userID string) (*UserStats, error)
417431
432+ InsertTunsEventLog(log *TunsEventLog) error
433+ FindTunsEventLog(userID, addr string) ([]*TunsEventLog, error)
434+
418435 Close() error
419436 }
+39 -0 pkg/db/postgres/storage.go #
......@@ -1796,6 +1796,45 @@ func (me *PsqlDB) findPagesStats(userID string) (*db.UserServiceStats, error) {
17961796 return &stats, nil
17971797 }
17981798
1799+func (me *PsqlDB) InsertTunsEventLog(log *db.TunsEventLog) error {
1800+ _, err := me.Db.Exec(
1801+ `INSERT INTO tuns_event_logs
1802+ (user_id, server_id, remote_addr, tunnel_type, connection_type, tunnel_addrs)
1803+ VALUES
1804+ ($1, $2, $3, $4, $5, $6)`,
1805+ log.UserId, log.ServerID, log.RemoteAddr, log.TunnelType, log.ConnectionType, log.TunnelAddrs,
1806+ )
1807+ return err
1808+}
1809+
1810+func (me *PsqlDB) FindTunsEventLog(userID, addr string) ([]*db.TunsEventLog, error) {
1811+ logs := []*db.TunsEventLog{}
1812+ rs, err := me.Db.Query(
1813+ `SELECT user_id, server_id, remote_addr, tunnel_type, connection_type, tunnel_addrs, created_at
1814+ FROM tuns_event_logs WHERE user_id=$1 AND tunnel_addrs @> ARRAY[$2] ORDER BY created_at DESC`, userID, addr)
1815+ if err != nil {
1816+ return nil, err
1817+ }
1818+
1819+ for rs.Next() {
1820+ log := db.TunsEventLog{}
1821+ err := rs.Scan(
1822+ &log.ID, &log.UserId, &log.ServerID, &log.RemoteAddr,
1823+ &log.TunnelType, &log.ConnectionType, &log.TunnelAddrs, &log.ConnectionType, &log.CreatedAt,
1824+ )
1825+ if err != nil {
1826+ return nil, err
1827+ }
1828+ logs = append(logs, &log)
1829+ }
1830+
1831+ if rs.Err() != nil {
1832+ return nil, rs.Err()
1833+ }
1834+
1835+ return logs, nil
1836+}
1837+
17991838 func (me *PsqlDB) FindUserStats(userID string) (*db.UserStats, error) {
18001839 stats := db.UserStats{}
18011840 rs, err := me.Db.Query(`SELECT cur_space, count(id), min(created_at), max(created_at), max(updated_at) FROM posts WHERE user_id=$1 GROUP BY cur_space`, userID)
+8 -0 pkg/db/stub/stub.go #
......@@ -268,3 +268,11 @@ func (me *StubDB) FindTagsForUser(userID string, tag string) ([]string, error) {
268268 func (me *StubDB) FindUserStats(userID string) (*db.UserStats, error) {
269269 return nil, notImpl
270270 }
271+
272+func (me *StubDB) InsertTunsEventLog(log *db.TunsEventLog) error {
273+ return notImpl
274+}
275+
276+func (me *StubDB) FindTunsEventLog(userID, addr string) ([]*db.TunsEventLog, error) {
277+ return nil, notImpl
278+}
+63 -13 pkg/tui/tuns.go #
......@@ -15,6 +15,7 @@ import (
1515 "git.sr.ht/~rockorager/vaxis/vxfw/list"
1616 "git.sr.ht/~rockorager/vaxis/vxfw/richtext"
1717 "git.sr.ht/~rockorager/vaxis/vxfw/text"
18+ "github.com/picosh/pico/pkg/db"
1819 "github.com/picosh/pico/pkg/shared"
1920 "github.com/picosh/utils/pipe"
2021 )
......@@ -78,21 +79,25 @@ type ResultLogLineLoaded struct {
7879
7980 type TunsLoaded struct{}
8081
82+type EventLogsLoaded struct{}
83+
8184 type TunsPage struct {
8285 shared *SharedModel
8386
84- loading bool
85- err error
86- tuns []TunsClientSimple
87- selected string
88- focus string
89- leftPane list.Dynamic
90- rightPane *Pager
91- logs []*ResultLog
92- logList list.Dynamic
93- ctx context.Context
94- done context.CancelFunc
95- isAdmin bool
87+ loading bool
88+ err error
89+ tuns []TunsClientSimple
90+ selected string
91+ focus string
92+ leftPane list.Dynamic
93+ rightPane *Pager
94+ logs []*ResultLog
95+ logList list.Dynamic
96+ ctx context.Context
97+ done context.CancelFunc
98+ isAdmin bool
99+ eventLogs []*db.TunsEventLog
100+ eventLogList list.Dynamic
96101 }
97102
98103 func NewTunsPage(shrd *SharedModel) *TunsPage {
......@@ -103,6 +108,7 @@ func NewTunsPage(shrd *SharedModel) *TunsPage {
103108 }
104109 m.leftPane = list.Dynamic{DrawCursor: true, Builder: m.getLeftWidget}
105110 m.logList = list.Dynamic{DrawCursor: true, Builder: m.getLogWidget}
111+ m.eventLogList = list.Dynamic{DrawCursor: true, Builder: m.getEventLogWidget}
106112 ff, _ := shrd.Dbpool.FindFeatureForUser(m.shared.User.ID, "admin")
107113 if ff != nil {
108114 m.isAdmin = true
......@@ -145,6 +151,24 @@ func (m *TunsPage) getLogWidget(i uint, cursor uint) vxfw.Widget {
145151 return txt
146152 }
147153
154+func (m *TunsPage) getEventLogWidget(i uint, cursor uint) vxfw.Widget {
155+ if int(i) >= len(m.tuns) {
156+ return nil
157+ }
158+
159+ log := m.eventLogs[i]
160+ style := vaxis.Style{Foreground: green}
161+ if log.EventType == "disconnect" {
162+ style = vaxis.Style{Foreground: red}
163+ }
164+ txt := richtext.New([]vaxis.Segment{
165+ {Text: log.CreatedAt.Format(time.RFC3339) + " "},
166+ {Text: log.EventType, Style: style},
167+ })
168+ txt.Softwrap = false
169+ return txt
170+}
171+
148172 func (m *TunsPage) connectToLogs() error {
149173 ctx, cancel := context.WithCancel(m.shared.Session.Context())
150174 m.ctx = ctx
......@@ -208,6 +232,8 @@ func (m *TunsPage) HandleEvent(ev vaxis.Event, ph vxfw.EventPhase) (vxfw.Command
208232 m.logList.SetCursor(uint(len(m.logs) - 1))
209233 }
210234 return vxfw.RedrawCmd{}, nil
235+ case EventLogsLoaded:
236+ return vxfw.RedrawCmd{}, nil
211237 case TunsLoaded:
212238 m.focus = "tuns"
213239 return vxfw.BatchCmd([]vxfw.Command{
......@@ -218,6 +244,8 @@ func (m *TunsPage) HandleEvent(ev vaxis.Event, ph vxfw.EventPhase) (vxfw.Command
218244 if msg.Matches(vaxis.KeyEnter) {
219245 m.selected = m.tuns[m.leftPane.Cursor()].TunAddress
220246 m.logs = []*ResultLog{}
247+ m.eventLogs = []*db.TunsEventLog{}
248+ go m.fetchEventLogs()
221249 return vxfw.RedrawCmd{}, nil
222250 }
223251 if msg.Matches(vaxis.KeyTab) {
......@@ -325,7 +353,20 @@ func (m *TunsPage) Draw(ctx vxfw.DrawContext) (vxfw.Surface, error) {
325353 Characters: vaxis.Characters,
326354 Max: vxfw.Size{
327355 Width: uint16(rightPaneW) - 4,
328- Height: ctx.Max.Height - uint16(ah) - 3,
356+ Height: 15,
357+ },
358+ })
359+ rightSurf.AddChild(0, ah, surf)
360+ ah += int(surf.Size.Height)
361+
362+ brd = NewBorder(&m.eventLogList)
363+ brd.Label = "conn events"
364+ m.focusBorder(brd)
365+ surf, _ = brd.Draw(vxfw.DrawContext{
366+ Characters: vaxis.Characters,
367+ Max: vxfw.Size{
368+ Width: uint16(rightPaneW) - 4,
369+ Height: 15,
329370 },
330371 })
331372 rightSurf.AddChild(0, ah, surf)
......@@ -376,6 +417,15 @@ func fetch(fqdn, auth string) (map[string]*TunsClient, error) {
376417 return data.Clients, nil
377418 }
378419
420+func (m *TunsPage) fetchEventLogs() {
421+ logs, err := m.shared.Dbpool.FindTunsEventLog(m.shared.User.ID, m.selected)
422+ if err != nil {
423+ m.err = err
424+ return
425+ }
426+ m.eventLogs = logs
427+}
428+
379429 func (m *TunsPage) fetchTuns() {
380430 tMap, err := fetch("tuns.sh", m.shared.Cfg.TunsSecret)
381431 if err != nil {
+17 -0 sql/migrations/20250319_add_tuns_event_logs_table.sql #
......@@ -0,0 +1,17 @@
1+CREATE TABLE IF NOT EXISTS tuns_event_logs (
2+ id uuid NOT NULL DEFAULT uuid_generate_v4(),
3+ user_id uuid NOT NULL,
4+ server_id text,
5+ remote_addr text,
6+ event_type text,
7+ tunnel_type text,
8+ connection_type text,
9+ tunnel_addrs text[],
10+ created_at timestamp without time zone NOT NULL DEFAULT NOW(),
11+ CONSTRAINT tuns_event_logs_pkey PRIMARY KEY (id),
12+ CONSTRAINT fk_tuns_event_logs_user
13+ FOREIGN KEY(user_id)
14+ REFERENCES app_users(id)
15+ ON DELETE CASCADE
16+ ON UPDATE CASCADE
17+);
Back to top