mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 08:49:13 +00:00
feat: show irc network status in settings list
This commit is contained in:
parent
dcd1d458cf
commit
f103dff221
6 changed files with 221 additions and 11 deletions
|
@ -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"`
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
//
|
||||
}
|
||||
|
|
|
@ -27,10 +27,13 @@ type channelHealth struct {
|
|||
name string
|
||||
monitoring bool
|
||||
monitoringSince time.Time
|
||||
lastPing time.Time
|
||||
lastAnnounce time.Time
|
||||
}
|
||||
|
||||
func (h *channelHealth) SetLastAnnounce() {
|
||||
h.lastAnnounce = time.Now()
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
network *domain.IrcNetwork
|
||||
filterService filter.Service
|
||||
|
@ -44,11 +47,15 @@ type Handler struct {
|
|||
stopped chan struct{}
|
||||
cancel context.CancelFunc
|
||||
|
||||
lastPing time.Time
|
||||
lastAnnounce time.Time
|
||||
lastPing time.Time
|
||||
lastAnnounce 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 +70,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 +82,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 +235,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 +307,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 +319,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 +330,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 +350,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 {
|
||||
|
@ -358,6 +392,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 +433,16 @@ 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.monitoring = true
|
||||
v.monitoringSince = time.Now()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -570,6 +616,15 @@ func (s *Handler) isValidAnnouncer(nick string) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (s *Handler) isValidChannel(channel string) bool {
|
||||
_, ok := s.validChannels[channel]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Handler) setLastAnnounce() {
|
||||
s.lastAnnounce = time.Now()
|
||||
}
|
||||
|
@ -578,6 +633,14 @@ func (s *Handler) GetLastAnnounce() time.Time {
|
|||
return s.lastAnnounce
|
||||
}
|
||||
|
||||
//func (s *Handler) setConnectedSince() {
|
||||
// s.network.ConnectedSince = time.Now()
|
||||
//}
|
||||
//
|
||||
//func (s *Handler) GetConnectedSince() time.Time {
|
||||
// return s.lastAnnounce
|
||||
//}
|
||||
|
||||
func (s *Handler) setLastPing() {
|
||||
s.lastPing = time.Now()
|
||||
}
|
||||
|
@ -586,6 +649,10 @@ func (s *Handler) GetLastPing() time.Time {
|
|||
return s.lastPing
|
||||
}
|
||||
|
||||
func (s *Handler) GetChannelHealth() map[string]*channelHealth {
|
||||
return s.channelHealth
|
||||
}
|
||||
|
||||
// irc line can contain lots of extra stuff like color so lets clean that
|
||||
func cleanMessage(message string) string {
|
||||
var regexMessageClean = `\x0f|\x1f|\x02|\x03(?:[\d]{1,2}(?:,[\d]{1,2})?)?`
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -134,7 +134,17 @@ const ListItem = ({ idx, network }: any) => {
|
|||
/>
|
||||
</Switch>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">{network.name}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white relative">
|
||||
<span className="relative inline-flex items-center">
|
||||
{network.connected ? (
|
||||
<span className="mr-3 flex h-3 w-3 relative">
|
||||
<span className="animate-ping inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
|
||||
<span className="inline-flex absolute rounded-full h-3 w-3 bg-green-500"></span>
|
||||
</span>
|
||||
) : <span className="mr-3 flex h-3 w-3 rounded-full opacity-75 bg-red-400" />}
|
||||
{network.name}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400"><span>{network.server}:{network.port}</span> {network.tls && <span className="ml-2 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 dark:bg-green-300 text-green-800 dark:text-green-900">TLS</span>}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">{network.nickserv?.account}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue