diff --git a/go.mod b/go.mod index 0c7573f..a92c027 100644 --- a/go.mod +++ b/go.mod @@ -20,11 +20,9 @@ require ( github.com/spf13/viper v1.9.0 github.com/stretchr/testify v1.7.0 golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 - golang.org/x/mod v0.5.1 // indirect golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect - golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac golang.org/x/tools v0.1.8 // indirect gopkg.in/ini.v1 v1.64.0 // indirect diff --git a/go.sum b/go.sum index a4ae3ea..12084c8 100644 --- a/go.sum +++ b/go.sum @@ -1162,8 +1162,6 @@ golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 h1:TyHqChC80pFkXWraUUf6RuB5IqFdQieMLwwCJokV2pc= -golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -1254,7 +1252,6 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= @@ -1468,7 +1465,6 @@ modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= -modernc.org/cc/v3 v3.35.18 h1:rMZhRcWrba0y3nVmdiQ7kxAgOOSq2m2f2VzjHLgEs6U= modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/cc/v3 v3.35.20/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/cc/v3 v3.35.22 h1:BzShpwCAP7TWzFppM4k2t03RhXhgYqaibROWkrWq7lE= @@ -1502,10 +1498,8 @@ modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7I modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU= modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc= modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM= -modernc.org/ccgo/v3 v3.12.65/go.mod h1:D6hQtKxPNZiY6wDBtehSGKFKmyXn53F8nGTpH+POmS4= modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ= modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84= -modernc.org/ccgo/v3 v3.12.73 h1:AMk4wEpzWjpODXohKvvnlwLob4Xk8tq3we6CwYh88mA= modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ= modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY= modernc.org/ccgo/v3 v3.12.84/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w= @@ -1517,6 +1511,7 @@ modernc.org/ccgo/v3 v3.12.95/go.mod h1:ZcLyvtocXYi8uF+9Ebm3G8EF8HNY5hGomBqthDp4e modernc.org/ccgo/v3 v3.13.1/go.mod h1:aBYVOUfIlcSnrsRVU8VRS35y2DIfpgkmVkYZ0tpIXi4= modernc.org/ccgo/v3 v3.14.0 h1:Zr1Ny9+7r5yAiXpBdgp8XiXqkNA4ARrRphHGHVXeAp0= modernc.org/ccgo/v3 v3.14.0/go.mod h1:hBrkiBlUwvr5vV/ZH9YzXIp982jKE8Ek8tR1ytoAL6Q= +modernc.org/ccorpus v1.11.1 h1:K0qPfpVG1MJh5BYazccnmhywH4zHuOgJXgbjzyp6dWA= modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= @@ -1549,10 +1544,8 @@ modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw= modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M= modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18= modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8= -modernc.org/libc v1.11.70/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw= modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw= modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0= -modernc.org/libc v1.11.82 h1:CSl/6n4odvPYWKKqBtFb8e0ZWVTjxDqwxTjaoee9V7E= modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI= modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE= modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY= @@ -1576,20 +1569,14 @@ modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14= modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM= modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.14.1 h1:jthfQCbWKfbK/lvZSjFEpBk0QzIBN6pQbFdDqBMR490= -modernc.org/sqlite v1.14.1/go.mod h1:04Lqa+3PuAEUhAPAPWeDMljT4UYA31nb2DHTFG47L1g= modernc.org/sqlite v1.14.3 h1:psrTwgpEujgWEP3FNdsC9yNh5tSeA77U0GeWhHH4XmQ= modernc.org/sqlite v1.14.3/go.mod h1:xMpicS1i2MJ4C8+Ap0vYBqTwYfpFvdnPE6brbFOtV2Y= modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= -modernc.org/tcl v1.8.13 h1:V0sTNBw0Re86PvXZxuCub3oO9WrSTqALgrwNZNvLFGw= -modernc.org/tcl v1.8.13/go.mod h1:V+q/Ef0IJaNUSECieLU4o+8IScapxnMyFV6i/7uQlAY= modernc.org/tcl v1.9.2 h1:YA87dFLOsR2KqMka371a2Xgr+YsyUwo7OmHVSv/kztw= modernc.org/tcl v1.9.2/go.mod h1:aw7OnlIoiuJgu1gwbTZtrKnGpDqH9wyH++jZcxdqNsg= modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.2.19 h1:BGyRFWhDVn5LFS5OcX4Yd/MlpRTOc7hOPTdcIpCiUao= -modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY= modernc.org/z v1.2.20 h1:DyboxM1sJR2NB803j2StnbnL6jcQXz273OhHDGu8dGk= modernc.org/z v1.2.20/go.mod h1:zU9FiF4PbHdOTUxw+IF8j7ArBMRPsHgq10uVPt6xTzo= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/internal/domain/indexer.go b/internal/domain/indexer.go index 702eea6..bd7ce9a 100644 --- a/internal/domain/indexer.go +++ b/internal/domain/indexer.go @@ -72,6 +72,24 @@ type IndexerIRC struct { Settings []IndexerSetting `json:"settings"` } +func (i IndexerIRC) ValidAnnouncer(announcer string) bool { + for _, a := range i.Announcers { + if a == announcer { + return true + } + } + return false +} + +func (i IndexerIRC) ValidChannel(channel string) bool { + for _, a := range i.Channels { + if a == channel { + return true + } + } + return false +} + type IndexerParse struct { Type string `json:"type"` Lines []IndexerParseExtract `json:"lines"` diff --git a/internal/domain/irc.go b/internal/domain/irc.go index f04b540..06bbf09 100644 --- a/internal/domain/irc.go +++ b/internal/domain/irc.go @@ -34,6 +34,44 @@ type IrcNetwork struct { ConnectedSince *time.Time `json:"connected_since"` } +type IrcNetworkWithHealth struct { + ID int64 `json:"id"` + Name string `json:"name"` + Enabled bool `json:"enabled"` + Server string `json:"server"` + Port int `json:"port"` + TLS bool `json:"tls"` + Pass string `json:"pass"` + InviteCommand string `json:"invite_command"` + NickServ NickServ `json:"nickserv,omitempty"` + //Channels []IrcChannel `json:"channels"` + Channels []ChannelWithHealth `json:"channels"` + //Channels []struct { + // IrcChannel + // ChannelHealth + //} `json:"channels"` + Connected bool `json:"connected"` + ConnectedSince time.Time `json:"connected_since"` +} + +type ChannelWithHealth struct { + ID int64 `json:"id"` + Enabled bool `json:"enabled"` + Name string `json:"name"` + Password string `json:"password"` + Detached bool `json:"detached"` + Monitoring bool `json:"monitoring"` + MonitoringSince time.Time `json:"monitoring_since"` + LastAnnounce time.Time `json:"last_announce"` +} + +type ChannelHealth struct { + Name string `json:"name"` + Monitoring bool `json:"monitoring"` + MonitoringSince time.Time `json:"monitoring_since"` + LastAnnounce time.Time `json:"last_announce"` +} + type IrcRepo interface { StoreNetwork(network *IrcNetwork) error UpdateNetwork(ctx context.Context, network *IrcNetwork) error diff --git a/internal/http/irc.go b/internal/http/irc.go index 4c2dc5a..8591eb5 100644 --- a/internal/http/irc.go +++ b/internal/http/irc.go @@ -13,6 +13,7 @@ import ( type ircService interface { ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error) + GetNetworksWithHealth(ctx context.Context) ([]domain.IrcNetworkWithHealth, error) DeleteNetwork(ctx context.Context, id int64) error GetNetworkByID(id int64) (*domain.IrcNetwork, error) StoreNetwork(ctx context.Context, network *domain.IrcNetwork) error @@ -46,7 +47,7 @@ func (h ircHandler) Routes(r chi.Router) { func (h ircHandler) listNetworks(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - networks, err := h.service.ListNetworks(ctx) + networks, err := h.service.GetNetworksWithHealth(ctx) if err != nil { // } diff --git a/internal/irc/handler.go b/internal/irc/handler.go index 95e5df5..140b0bb 100644 --- a/internal/irc/handler.go +++ b/internal/irc/handler.go @@ -27,10 +27,20 @@ type channelHealth struct { name string monitoring bool monitoringSince time.Time - lastPing time.Time lastAnnounce time.Time } +// SetLastAnnounce set last announce to now +func (h *channelHealth) SetLastAnnounce() { + h.lastAnnounce = time.Now() +} + +// SetMonitoring set monitoring and time +func (h *channelHealth) SetMonitoring() { + h.monitoring = true + h.monitoringSince = time.Now() +} + type Handler struct { network *domain.IrcNetwork filterService filter.Service @@ -44,11 +54,14 @@ type Handler struct { stopped chan struct{} cancel context.CancelFunc - lastPing time.Time - lastAnnounce time.Time + lastPing time.Time + connected bool + connectedSince time.Time + // tODO disconnectedTime validAnnouncers map[string]struct{} - channels map[string]channelHealth + validChannels map[string]struct{} + channelHealth map[string]*channelHealth } func NewHandler(network domain.IrcNetwork, filterService filter.Service, releaseService release.Service, definitions []domain.IndexerDefinition) *Handler { @@ -63,6 +76,8 @@ func NewHandler(network domain.IrcNetwork, filterService filter.Service, release definitions: definitions, announceProcessors: map[string]announce.Processor{}, validAnnouncers: map[string]struct{}{}, + validChannels: map[string]struct{}{}, + channelHealth: map[string]*channelHealth{}, } // Networks can be shared by multiple indexers but channels are unique @@ -73,12 +88,22 @@ func NewHandler(network domain.IrcNetwork, filterService filter.Service, release channel = strings.ToLower(channel) h.announceProcessors[channel] = announce.NewAnnounceProcessor(definition, filterService, releaseService) + + h.channelHealth[channel] = &channelHealth{ + name: channel, + monitoring: false, + } } // create map of valid announcers for _, announcer := range definition.IRC.Announcers { h.validAnnouncers[announcer] = struct{}{} } + + // create map of valid channels + for _, ch := range definition.IRC.Channels { + h.validChannels[ch] = struct{}{} + } } return h @@ -216,10 +241,19 @@ func (s *Handler) Run() error { s.client = client + // set connected since now + s.connectedSince = time.Now() + s.connected = true + // Connect err = client.RunContext(ctx) if err != nil { log.Error().Err(err).Msgf("could not connect to %v", addr) + + // set connected false if we loose connection or stop + s.connectedSince = time.Time{} + s.connected = false + return err } @@ -279,7 +313,7 @@ func (s *Handler) onConnect(channels []domain.IrcChannel) error { if s.network.NickServ.Password != "" { err := s.handleNickServPRIVMSG(s.network.NickServ.Account, s.network.NickServ.Password) if err != nil { - log.Error().Err(err).Msgf("error nickserv: %v", s.network.Name) + log.Error().Stack().Err(err).Msgf("error nickserv: %v", s.network.Name) return err } identified = true @@ -291,7 +325,7 @@ func (s *Handler) onConnect(channels []domain.IrcChannel) error { err := s.handleInvitePRIVMSG(s.network.InviteCommand) if err != nil { - log.Error().Err(err).Msgf("error sending connect command %v to network: %v", s.network.InviteCommand, s.network.Name) + log.Error().Stack().Err(err).Msgf("error sending connect command %v to network: %v", s.network.InviteCommand, s.network.Name) return err } @@ -302,7 +336,7 @@ func (s *Handler) onConnect(channels []domain.IrcChannel) error { for _, channel := range channels { err := s.HandleJoinChannel(channel.Name, channel.Password) if err != nil { - log.Error().Err(err) + log.Error().Stack().Err(err) return err } } @@ -322,6 +356,12 @@ func (s *Handler) onMessage(msg *irc.Message) error { message := msg.Trailing() // TODO add network + // check if message is from a valid channel, if not return + validChannel := s.isValidChannel(*channel) + if !validChannel { + return nil + } + // check if message is from announce bot, if not return validAnnouncer := s.isValidAnnouncer(*announcer) if !validAnnouncer { @@ -332,8 +372,6 @@ func (s *Handler) onMessage(msg *irc.Message) error { cleanedMsg := cleanMessage(message) log.Debug().Msgf("%v: %v %v: %v", s.network.Server, *channel, *announcer, cleanedMsg) - s.setLastAnnounce() - if err := s.sendToAnnounceProcessor(*channel, cleanedMsg); err != nil { log.Error().Stack().Err(err).Msgf("could not queue line: %v", cleanedMsg) return err @@ -358,6 +396,13 @@ func (s *Handler) sendToAnnounceProcessor(channel string, msg string) error { return err } + v, ok := s.channelHealth[channel] + if !ok { + return nil + } + + v.SetLastAnnounce() + return nil } @@ -392,11 +437,15 @@ func (s *Handler) HandleJoinChannel(channel string, password string) error { err := s.client.Write(m.String()) if err != nil { - log.Error().Err(err).Msgf("error handling join: %v", m.String()) + log.Error().Stack().Err(err).Msgf("error handling join: %v", m.String()) return err } - //log.Info().Msgf("Monitoring channel %v %s", s.network.Name, channel) + // only set values if channel is found in map + v, ok := s.channelHealth[channel] + if ok { + v.SetMonitoring() + } return nil } @@ -561,6 +610,7 @@ func (s *Handler) handlePing(msg *irc.Message) error { return nil } +// check if announcer is one from the list in the definition func (s *Handler) isValidAnnouncer(nick string) bool { _, ok := s.validAnnouncers[nick] if !ok { @@ -570,12 +620,14 @@ func (s *Handler) isValidAnnouncer(nick string) bool { return true } -func (s *Handler) setLastAnnounce() { - s.lastAnnounce = time.Now() -} +// check if channel is one from the list in the definition +func (s *Handler) isValidChannel(channel string) bool { + _, ok := s.validChannels[channel] + if !ok { + return false + } -func (s *Handler) GetLastAnnounce() time.Time { - return s.lastAnnounce + return true } func (s *Handler) setLastPing() { diff --git a/internal/irc/service.go b/internal/irc/service.go index 57188c0..a37d372 100644 --- a/internal/irc/service.go +++ b/internal/irc/service.go @@ -3,6 +3,7 @@ package irc import ( "context" "fmt" + "strings" "sync" "github.com/autobrr/autobrr/internal/domain" @@ -18,6 +19,7 @@ type Service interface { StopHandlers() StopNetwork(name string) error ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error) + GetNetworksWithHealth(ctx context.Context) ([]domain.IrcNetworkWithHealth, error) GetNetworkByID(id int64) (*domain.IrcNetwork, error) DeleteNetwork(ctx context.Context, id int64) error StoreNetwork(ctx context.Context, network *domain.IrcNetwork) error @@ -349,12 +351,86 @@ func (s *service) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error) } n.Channels = append(n.Channels, channels...) + // TODO get handler channelHealth + ret = append(ret, n) } return ret, nil } +func (s *service) GetNetworksWithHealth(ctx context.Context) ([]domain.IrcNetworkWithHealth, error) { + networks, err := s.repo.ListNetworks(ctx) + if err != nil { + log.Error().Err(err).Msgf("failed to list networks: %v", err) + return nil, err + } + + var ret []domain.IrcNetworkWithHealth + + for _, n := range networks { + netw := domain.IrcNetworkWithHealth{ + ID: n.ID, + Name: n.Name, + Enabled: n.Enabled, + Server: n.Server, + Port: n.Port, + TLS: n.TLS, + Pass: n.Pass, + InviteCommand: n.InviteCommand, + NickServ: n.NickServ, + Connected: false, + } + + handler, ok := s.handlers[n.Server] + if ok { + // only set connected and connected since if we have an active handler and connection + if handler.conn != nil { + netw.Connected = handler.connected + netw.ConnectedSince = handler.connectedSince + } + } + + channels, err := s.repo.ListChannels(n.ID) + if err != nil { + log.Error().Msgf("failed to list channels for network %q: %v", n.Server, err) + return nil, err + } + + // combine from repo and handler + for _, channel := range channels { + ch := domain.ChannelWithHealth{ + ID: channel.ID, + Enabled: channel.Enabled, + Name: channel.Name, + Password: channel.Password, + Detached: channel.Detached, + //Monitoring: false, + //MonitoringSince: time.Time{}, + //LastAnnounce: time.Time{}, + } + + // only check if we have a handler + if handler != nil { + name := strings.ToLower(channel.Name) + + chan1, ok := handler.channelHealth[name] + if ok { + ch.Monitoring = chan1.monitoring + ch.MonitoringSince = chan1.monitoringSince + ch.LastAnnounce = chan1.lastAnnounce + } + } + + netw.Channels = append(netw.Channels, ch) + } + + ret = append(ret, netw) + } + + return ret, nil +} + func (s *service) DeleteNetwork(ctx context.Context, id int64) error { network, err := s.GetNetworkByID(id) if err != nil { diff --git a/web/src/screens/Releases.tsx b/web/src/screens/Releases.tsx index a46de84..d49b2e2 100644 --- a/web/src/screens/Releases.tsx +++ b/web/src/screens/Releases.tsx @@ -140,7 +140,7 @@ export function ReleaseStatusCell({ value, column, row }: ReleaseStatusCellProps } return (
- {value.map(v =>
{statusMap[v.status]}
)} + {value.map((v, idx) =>
{statusMap[v.status]}
)}
) } diff --git a/web/src/screens/settings/Irc.tsx b/web/src/screens/settings/Irc.tsx index d29da13..0101818 100644 --- a/web/src/screens/settings/Irc.tsx +++ b/web/src/screens/settings/Irc.tsx @@ -2,21 +2,56 @@ import { useEffect } from "react"; import { IrcNetworkAddForm, IrcNetworkUpdateForm } from "../../forms"; import { useToggle } from "../../hooks/hooks"; import { useQuery } from "react-query"; -import { Switch } from "@headlessui/react"; -import { classNames } from "../../utils"; import { EmptySimple } from "../../components/emptystates"; import APIClient from "../../api/APIClient"; +import { formatDistanceToNowStrict, formatISO9075 } from "date-fns"; interface IrcNetwork { id: number; name: string; enabled: boolean; addr: string; + server: string; + port: string; nick: string; username: string; realname: string; pass: string; - // connect_commands: string; + connected: boolean; + connected_since: string; + tls: boolean; + nickserv: { + account: string; + } + channels: Channel[] +} + +interface Channel { + id: number; + enabled: boolean; + name: string; + password: string; + detached: boolean; + monitoring: boolean; + monitoring_since: string; + last_announce: string; +} + +function IsEmptyDate(date: string) { + if (date !== "0001-01-01T00:00:00Z") { + return formatDistanceToNowStrict( + new Date(date), + { addSuffix: true } + ) + } + return "n/a" +} + +function simplifyDate(date: string) { + if (date !== "0001-01-01T00:00:00Z") { + return formatISO9075(new Date(date)) + } + return "n/a" } function IrcSettings() { @@ -25,7 +60,7 @@ function IrcSettings() { useEffect(() => { }, []); - const { data } = useQuery('networks', APIClient.irc.getNetworks, + const { data } = useQuery('networks', APIClient.irc.getNetworks, { refetchOnWindowFocus: false } @@ -40,7 +75,7 @@ function IrcSettings() {

IRC

- IRC networks and channels. + IRC networks and channels. Click on a network to view channel status.

@@ -54,96 +89,122 @@ function IrcSettings() {
-
- {data && data.length > 0 ? -
-
-
- - - - - - - - - - - - {data && data.map((network: IrcNetwork, idx) => ( - - ))} - -
- Enabled - - Network - - Server - - Nick - - Edit -
-
-
-
- : - } -
+ {data && data.length > 0 ? +
+
    +
  1. + {/*
    Enabled
    */} +
    Network
    +
    Server
    +
    Nick
    +
  2. + + {data && data.map((network: IrcNetwork, idx) => ( + + ))} +
+
+ : } ) } -const ListItem = ({ idx, network }: any) => { +interface LiItemProps { + idx: number; + network: IrcNetwork; +} + +const LiItem = ({ idx, network }: LiItemProps) => { const [updateIsOpen, toggleUpdate] = useToggle(false) + const [edit, toggleEdit] = useToggle(false); return ( - - - - - Enable -