added http server
This commit is contained in:
parent
ab17255bdc
commit
eef2b4b938
Binary file not shown.
70
HTTPServer.go
Normal file
70
HTTPServer.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tmpl *template.Template
|
||||||
|
|
||||||
|
type HTTPServer struct {
|
||||||
|
router *mux.Router
|
||||||
|
Log4OMRepo Log4OMContactsRepository
|
||||||
|
Repo FlexDXClusterRepository
|
||||||
|
Log *log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPServer(cRepo Log4OMContactsRepository, fRepo FlexDXClusterRepository, log *log.Logger) *HTTPServer {
|
||||||
|
|
||||||
|
gRouter := mux.NewRouter()
|
||||||
|
|
||||||
|
return &HTTPServer{
|
||||||
|
router: gRouter,
|
||||||
|
Log4OMRepo: cRepo,
|
||||||
|
Repo: fRepo,
|
||||||
|
Log: log,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPServer) SetRoutes() {
|
||||||
|
s.router.HandleFunc("/", s.Homepage)
|
||||||
|
s.router.HandleFunc("/spots", s.GetSpots).Methods("GET")
|
||||||
|
s.router.HandleFunc("/spotscount", s.GetSpotsCount).Methods("GET")
|
||||||
|
s.router.HandleFunc("/spotters", s.GetSpotters).Methods("GET")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPServer) StartHTTPServer() {
|
||||||
|
tmpl, _ = template.ParseGlob("templates/*.html")
|
||||||
|
s.SetRoutes()
|
||||||
|
s.Log.Infof("starting HTTP server on %s:%s", Cfg.HTTPServer.Host, Cfg.HTTPServer.Port)
|
||||||
|
err := http.ListenAndServe(Cfg.HTTPServer.Host+":"+Cfg.HTTPServer.Port, s.router)
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Warn("cannot start HTTP server: ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPServer) Homepage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
err := tmpl.ExecuteTemplate(w, "home.html", nil)
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Error("error executing home template: ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPServer) GetSpots(w http.ResponseWriter, r *http.Request) {
|
||||||
|
spots := s.Repo.GetAllSpots("25")
|
||||||
|
tmpl.ExecuteTemplate(w, "spot", spots)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPServer) GetSpotsCount(w http.ResponseWriter, r *http.Request) {
|
||||||
|
spots := s.Repo.GetAllSpots("0")
|
||||||
|
count := len(spots)
|
||||||
|
tmpl.ExecuteTemplate(w, "spotCount", count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPServer) GetSpotters(w http.ResponseWriter, r *http.Request) {
|
||||||
|
spotters := s.Repo.GetSpotters()
|
||||||
|
tmpl.ExecuteTemplate(w, "spotters", spotters)
|
||||||
|
}
|
26
TCPClient.go
26
TCPClient.go
@ -12,6 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var spotRe *regexp.Regexp = regexp.MustCompile(`DX\sde\s([\w\d]+).*:\s+(\d+.\d)\s+([\w\d]+)\s+(CW|SSB|FT8|FT4|RTTY|USB|LSB)?\s+(.*)\s\s\s+([\d]+\w{1})`)
|
var spotRe *regexp.Regexp = regexp.MustCompile(`DX\sde\s([\w\d]+).*:\s+(\d+.\d)\s+([\w\d]+)\s+(CW|SSB|FT8|FT4|RTTY|USB|LSB)?\s+(.*)\s\s\s+([\d]+\w{1})`)
|
||||||
|
var count int = 0
|
||||||
|
|
||||||
type TCPClient struct {
|
type TCPClient struct {
|
||||||
Login string
|
Login string
|
||||||
@ -67,7 +68,7 @@ func (c *TCPClient) StartClient() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.Log.Error("cannot connect to Telnet Client:", err)
|
c.Log.Error("cannot connect to Telnet Client:", err)
|
||||||
}
|
}
|
||||||
c.Log.Infof("connected to %s:%s", c.Address, c.Port)
|
c.Log.Infof("connected to DX cluster %s:%s", c.Address, c.Port)
|
||||||
|
|
||||||
err = c.Conn.SetKeepAlive(true)
|
err = c.Conn.SetKeepAlive(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -77,6 +78,14 @@ func (c *TCPClient) StartClient() {
|
|||||||
c.Reader = bufio.NewReader(c.Conn)
|
c.Reader = bufio.NewReader(c.Conn)
|
||||||
c.Writer = bufio.NewWriter(c.Conn)
|
c.Writer = bufio.NewWriter(c.Conn)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for message := range c.TCPServer.CmdChan {
|
||||||
|
message := message + "\n"
|
||||||
|
c.Log.Infof("Received DX Command: %s", message)
|
||||||
|
c.WriteString(message)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
go c.ReadLine()
|
go c.ReadLine()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,6 +116,7 @@ func (c *TCPClient) SetFilters() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *TCPClient) ReadLine() {
|
func (c *TCPClient) ReadLine() {
|
||||||
|
|
||||||
for {
|
for {
|
||||||
message, err := c.Reader.ReadString('\n')
|
message, err := c.Reader.ReadString('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -125,6 +135,11 @@ func (c *TCPClient) ReadLine() {
|
|||||||
|
|
||||||
// Send the spot message to TCP server
|
// Send the spot message to TCP server
|
||||||
if len(c.TCPServer.Clients) > 0 {
|
if len(c.TCPServer.Clients) > 0 {
|
||||||
|
if count == 0 {
|
||||||
|
// wait 3 seconds before sending messages to allow the client to connect
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
count++
|
||||||
|
}
|
||||||
c.MsgChan <- message
|
c.MsgChan <- message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,3 +154,12 @@ func (tc *TCPClient) Write(data []byte) (n int, err error) {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tc *TCPClient) WriteString(data string) (n int, err error) {
|
||||||
|
n, err = tc.Writer.Write([]byte(data))
|
||||||
|
if err == nil {
|
||||||
|
err = tc.Writer.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
17
TCPServer.go
17
TCPServer.go
@ -92,20 +92,19 @@ func (s *TCPServer) handleConnection() {
|
|||||||
|
|
||||||
message = strings.TrimSpace(message)
|
message = strings.TrimSpace(message)
|
||||||
|
|
||||||
s.Log.Infof("Message reçu du client: %s\n", message)
|
// if message is by then disconnect
|
||||||
|
if message == "bye" {
|
||||||
switch message {
|
|
||||||
case "bye":
|
|
||||||
s.Mutex.Lock()
|
s.Mutex.Lock()
|
||||||
delete(s.Clients, s.Conn)
|
delete(s.Clients, s.Conn)
|
||||||
s.Mutex.Unlock()
|
s.Mutex.Unlock()
|
||||||
s.Log.Infof("client %s disconnected", s.Conn.RemoteAddr().String())
|
s.Log.Infof("client %s disconnected", s.Conn.RemoteAddr().String())
|
||||||
return
|
|
||||||
case "SH/DX 30":
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
s.Write("cannot identify command\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.Contains(message, "DX") && message != "SH/DX 30" {
|
||||||
|
// send DX spot to the client
|
||||||
|
s.CmdChan <- message
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +38,11 @@ type Config struct {
|
|||||||
Host string `yaml:"host"`
|
Host string `yaml:"host"`
|
||||||
Port string `yaml:"port"`
|
Port string `yaml:"port"`
|
||||||
} `yaml:"telnet"`
|
} `yaml:"telnet"`
|
||||||
|
|
||||||
|
HTTPServer struct {
|
||||||
|
Host string `yaml:"host"`
|
||||||
|
Port string `yaml:"port"`
|
||||||
|
} `yaml:"httpserver"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfig(configPath string) error {
|
func NewConfig(configPath string) error {
|
||||||
|
@ -6,9 +6,9 @@ sqlite:
|
|||||||
cluster:
|
cluster:
|
||||||
server: dxc.k0xm.net
|
server: dxc.k0xm.net
|
||||||
port: 7300
|
port: 7300
|
||||||
login: xv9q-5
|
login: xv9q-2
|
||||||
skimmer: true
|
skimmer: true
|
||||||
ft8: false
|
ft8: true
|
||||||
flex:
|
flex:
|
||||||
ip: 10.10.10.120
|
ip: 10.10.10.120
|
||||||
spot_life: 600
|
spot_life: 600
|
||||||
@ -17,3 +17,6 @@ clublog:
|
|||||||
telnet:
|
telnet:
|
||||||
host: 0.0.0.0
|
host: 0.0.0.0
|
||||||
port: 7301
|
port: 7301
|
||||||
|
httpserver:
|
||||||
|
host: 0.0.0.0
|
||||||
|
port: 3000
|
110
database.go
110
database.go
@ -20,7 +20,12 @@ type Contact struct {
|
|||||||
Country string
|
Country string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContactsRepository struct {
|
type Spotter struct {
|
||||||
|
Spotter string
|
||||||
|
NumberofSpots string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Log4OMContactsRepository struct {
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
Log *log.Logger
|
Log *log.Logger
|
||||||
}
|
}
|
||||||
@ -30,12 +35,12 @@ type FlexDXClusterRepository struct {
|
|||||||
Log *log.Logger
|
Log *log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContactsRepository(filePath string, log *log.Logger) *ContactsRepository {
|
func NewLog4OMContactsRepository(filePath string, log *log.Logger) *Log4OMContactsRepository {
|
||||||
db, err := sql.Open("sqlite3", filePath)
|
db, err := sql.Open("sqlite3", filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Cannot open db", err)
|
fmt.Println("Cannot open db", err)
|
||||||
}
|
}
|
||||||
return &ContactsRepository{
|
return &Log4OMContactsRepository{
|
||||||
db: db,
|
db: db,
|
||||||
Log: log}
|
Log: log}
|
||||||
}
|
}
|
||||||
@ -84,13 +89,15 @@ func NewFlexDXDatabase(filePath string, log *log.Logger) *FlexDXClusterRepositor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ContactsRepository) ListByCountry(countryID string) ([]*Contact, error) {
|
func (r *Log4OMContactsRepository) ListByCountry(countryID string) ([]*Contact, error) {
|
||||||
rows, err := r.db.Query("SELECT callsign, band, mode, dxcc, stationcallsign, country FROM log WHERE dxcc = ?", countryID)
|
rows, err := r.db.Query("SELECT callsign, band, mode, dxcc, stationcallsign, country FROM log WHERE dxcc = ?", countryID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("could not query database", err)
|
log.Error("could not query database", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
contacts := []*Contact{}
|
contacts := []*Contact{}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
c := Contact{}
|
c := Contact{}
|
||||||
@ -103,7 +110,7 @@ func (r *ContactsRepository) ListByCountry(countryID string) ([]*Contact, error)
|
|||||||
return contacts, nil
|
return contacts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ContactsRepository) ListByCountryMode(countryID string, mode string) ([]*Contact, error) {
|
func (r *Log4OMContactsRepository) ListByCountryMode(countryID string, mode string) ([]*Contact, error) {
|
||||||
|
|
||||||
modeUSB := "USB"
|
modeUSB := "USB"
|
||||||
modeLSB := "LSB"
|
modeLSB := "LSB"
|
||||||
@ -116,6 +123,8 @@ func (r *ContactsRepository) ListByCountryMode(countryID string, mode string) ([
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
contacts := []*Contact{}
|
contacts := []*Contact{}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
c := Contact{}
|
c := Contact{}
|
||||||
@ -135,6 +144,8 @@ func (r *ContactsRepository) ListByCountryMode(countryID string, mode string) ([
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
contacts := []*Contact{}
|
contacts := []*Contact{}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
c := Contact{}
|
c := Contact{}
|
||||||
@ -149,13 +160,15 @@ func (r *ContactsRepository) ListByCountryMode(countryID string, mode string) ([
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ContactsRepository) ListByCountryBand(countryID string, band string) ([]*Contact, error) {
|
func (r *Log4OMContactsRepository) ListByCountryBand(countryID string, band string) ([]*Contact, error) {
|
||||||
rows, err := r.db.Query("SELECT callsign, band, mode, dxcc, stationcallsign, country FROM log WHERE dxcc = ? AND band = ?", countryID, band)
|
rows, err := r.db.Query("SELECT callsign, band, mode, dxcc, stationcallsign, country FROM log WHERE dxcc = ? AND band = ?", countryID, band)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
contacts := []*Contact{}
|
contacts := []*Contact{}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
c := Contact{}
|
c := Contact{}
|
||||||
@ -168,13 +181,15 @@ func (r *ContactsRepository) ListByCountryBand(countryID string, band string) ([
|
|||||||
return contacts, nil
|
return contacts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ContactsRepository) ListByCallSign(callSign string, band string, mode string) ([]*Contact, error) {
|
func (r *Log4OMContactsRepository) ListByCallSign(callSign string, band string, mode string) ([]*Contact, error) {
|
||||||
rows, err := r.db.Query("SELECT callsign, band, mode, dxcc, stationcallsign, country FROM log WHERE callsign = ? AND band = ? AND mode = ?", callSign, band, mode)
|
rows, err := r.db.Query("SELECT callsign, band, mode, dxcc, stationcallsign, country FROM log WHERE callsign = ? AND band = ? AND mode = ?", callSign, band, mode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
contacts := []*Contact{}
|
contacts := []*Contact{}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
c := Contact{}
|
c := Contact{}
|
||||||
@ -187,13 +202,76 @@ func (r *ContactsRepository) ListByCallSign(callSign string, band string, mode s
|
|||||||
return contacts, nil
|
return contacts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *FlexDXClusterRepository) GetAllSpots(limit string) []FlexSpot {
|
||||||
|
|
||||||
|
Spots := []FlexSpot{}
|
||||||
|
|
||||||
|
var query string
|
||||||
|
|
||||||
|
if limit == "0" {
|
||||||
|
query = "SELECT * from spots ORDER BY id DESC"
|
||||||
|
} else {
|
||||||
|
query = fmt.Sprintf("SELECT * from spots ORDER BY id DESC LIMIT %s", limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := r.db.Query(query)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
r.Log.Error(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
s := FlexSpot{}
|
||||||
|
for rows.Next() {
|
||||||
|
if err := rows.Scan(&s.ID, &s.CommandNumber, &s.FlexSpotNumber, &s.DX, &s.FrequencyMhz, &s.FrequencyHz, &s.Band, &s.Mode, &s.SpotterCallsign, &s.FlexMode, &s.Source, &s.UTCTime, &s.TimeStamp, &s.LifeTime, &s.Priority,
|
||||||
|
&s.Comment, &s.Color, &s.BackgroundColor); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
Spots = append(Spots, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Spots
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FlexDXClusterRepository) GetSpotters() []Spotter {
|
||||||
|
|
||||||
|
sList := []Spotter{}
|
||||||
|
|
||||||
|
rows, err := r.db.Query("select spotter, count(*) as occurences from spots group by spotter order by occurences desc, spotter limit 3")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
r.Log.Error(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
s := Spotter{}
|
||||||
|
for rows.Next() {
|
||||||
|
if err := rows.Scan(&s.Spotter, &s.NumberofSpots); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sList = append(sList, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sList
|
||||||
|
}
|
||||||
|
|
||||||
func (r *FlexDXClusterRepository) FindDXSameBand(spot FlexSpot) (*FlexSpot, error) {
|
func (r *FlexDXClusterRepository) FindDXSameBand(spot FlexSpot) (*FlexSpot, error) {
|
||||||
rows, err := r.db.Query("SELECT * from spots WHERE dx = ? AND band = ?", spot.DX, spot.Band)
|
rows, err := r.db.Query("SELECT * from spots WHERE dx = ? AND band = ?", spot.DX, spot.Band)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
r.Log.Error(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
s := FlexSpot{}
|
s := FlexSpot{}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
if err := rows.Scan(&s.ID, &s.CommandNumber, &s.FlexSpotNumber, &s.DX, &s.FrequencyMhz, &s.FrequencyHz, &s.Band, &s.Mode, &s.SpotterCallsign, &s.FlexMode, &s.Source, &s.UTCTime, &s.TimeStamp, &s.LifeTime, &s.Priority,
|
if err := rows.Scan(&s.ID, &s.CommandNumber, &s.FlexSpotNumber, &s.DX, &s.FrequencyMhz, &s.FrequencyHz, &s.Band, &s.Mode, &s.SpotterCallsign, &s.FlexMode, &s.Source, &s.UTCTime, &s.TimeStamp, &s.LifeTime, &s.Priority,
|
||||||
@ -219,8 +297,14 @@ func (r *FlexDXClusterRepository) CreateSpot(spot FlexSpot) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FlexDXClusterRepository) UpdateSpotSameBand(spot FlexSpot) {
|
func (r *FlexDXClusterRepository) UpdateSpotSameBand(spot FlexSpot) error {
|
||||||
return
|
_, err := r.db.Exec(`UPDATE spots SET commandNumber = ?, DX = ?, freqMhz = ?, freqHz = ?, band = ?, mode = ?, spotter = ?, flexMode = ?, source = ?, time = ?, timestamp = ?, lifeTime = ?, priority = ?, comment = ?, color = ?, backgroundColor = ? WHERE DX = ? AND band = ?`,
|
||||||
|
spot.CommandNumber, spot.DX, spot.FrequencyMhz, spot.FrequencyHz, spot.Band, spot.Mode, spot.SpotterCallsign, spot.FlexMode, spot.Source, spot.UTCTime, spot.TimeStamp, spot.LifeTime, spot.Priority, spot.Comment, spot.Color, spot.BackgroundColor, spot.DX, spot.Band)
|
||||||
|
if err != nil {
|
||||||
|
r.Log.Errorf("could not update database: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FlexDXClusterRepository) FindSpotByCommandNumber(commandNumber string) (*FlexSpot, error) {
|
func (r *FlexDXClusterRepository) FindSpotByCommandNumber(commandNumber string) (*FlexSpot, error) {
|
||||||
@ -230,6 +314,8 @@ func (r *FlexDXClusterRepository) FindSpotByCommandNumber(commandNumber string)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
s := FlexSpot{}
|
s := FlexSpot{}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
if err := rows.Scan(&s.ID, &s.CommandNumber, &s.FlexSpotNumber, &s.DX, &s.FrequencyMhz, &s.FrequencyHz, &s.Band, &s.Mode, &s.SpotterCallsign, &s.FlexMode, &s.Source, &s.UTCTime, &s.TimeStamp, &s.LifeTime, &s.Priority,
|
if err := rows.Scan(&s.ID, &s.CommandNumber, &s.FlexSpotNumber, &s.DX, &s.FrequencyMhz, &s.FrequencyHz, &s.Band, &s.Mode, &s.SpotterCallsign, &s.FlexMode, &s.Source, &s.UTCTime, &s.TimeStamp, &s.LifeTime, &s.Priority,
|
||||||
@ -248,6 +334,8 @@ func (r *FlexDXClusterRepository) FindSpotByFlexSpotNumber(spotNumber string) (*
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
s := FlexSpot{}
|
s := FlexSpot{}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
if err := rows.Scan(&s.ID, &s.CommandNumber, &s.FlexSpotNumber, &s.DX, &s.FrequencyMhz, &s.FrequencyHz, &s.Band, &s.Mode, &s.SpotterCallsign, &s.FlexMode, &s.Source, &s.UTCTime, &s.TimeStamp, &s.LifeTime, &s.Priority,
|
if err := rows.Scan(&s.ID, &s.CommandNumber, &s.FlexSpotNumber, &s.DX, &s.FrequencyMhz, &s.FrequencyHz, &s.Band, &s.Mode, &s.SpotterCallsign, &s.FlexMode, &s.Source, &s.UTCTime, &s.TimeStamp, &s.LifeTime, &s.Priority,
|
||||||
@ -266,6 +354,8 @@ func (r *FlexDXClusterRepository) UpdateFlexSpotNumberByID(flexSpotNumber string
|
|||||||
r.Log.Errorf("could not update database: %s", err)
|
r.Log.Errorf("could not update database: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
s := FlexSpot{}
|
s := FlexSpot{}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
if err := rows.Scan(&s.ID, &s.CommandNumber, &s.FlexSpotNumber, &s.DX, &s.FrequencyMhz, &s.FrequencyHz, &s.Band, &s.Mode, &s.SpotterCallsign, &s.FlexMode, &s.Source, &s.UTCTime, &s.TimeStamp, &s.LifeTime, &s.Priority,
|
if err := rows.Scan(&s.ID, &s.CommandNumber, &s.FlexSpotNumber, &s.DX, &s.FrequencyMhz, &s.FrequencyHz, &s.Band, &s.Mode, &s.SpotterCallsign, &s.FlexMode, &s.Source, &s.UTCTime, &s.TimeStamp, &s.LifeTime, &s.Priority,
|
||||||
|
BIN
flex.sqlite
BIN
flex.sqlite
Binary file not shown.
39
flexradio.go
39
flexradio.go
@ -78,7 +78,7 @@ func (fc *FlexClient) StartFlexClient() {
|
|||||||
fc.Log.Error("could not connect to flex radio, exiting...", err)
|
fc.Log.Error("could not connect to flex radio, exiting...", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fc.Log.Infof("connected to %s:%s", fc.Address, fc.Port)
|
fc.Log.Infof("connected to flex radio at %s:%s", fc.Address, fc.Port)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for message := range fc.SpotChan {
|
for message := range fc.SpotChan {
|
||||||
@ -103,6 +103,8 @@ func (fc *FlexClient) StartFlexClient() {
|
|||||||
clrSpotAllCmd := fmt.Sprintf("C%v|spot clear", CommandNumber)
|
clrSpotAllCmd := fmt.Sprintf("C%v|spot clear", CommandNumber)
|
||||||
fc.Write(clrSpotAllCmd)
|
fc.Write(clrSpotAllCmd)
|
||||||
CommandNumber++
|
CommandNumber++
|
||||||
|
|
||||||
|
fc.Log.Info("Subscribed to spot on FlexRadio and Deleted all spots from panadapter")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fc *FlexClient) SendSpottoFlex(spot TelnetSpot) {
|
func (fc *FlexClient) SendSpottoFlex(spot TelnetSpot) {
|
||||||
@ -151,8 +153,14 @@ func (fc *FlexClient) SendSpottoFlex(spot TelnetSpot) {
|
|||||||
flexSpot.Priority = "5"
|
flexSpot.Priority = "5"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if spot.DX == Cfg.SQLite.Callsign {
|
||||||
|
flexSpot.Color = "#ff0000"
|
||||||
|
flexSpot.Priority = "2"
|
||||||
|
flexSpot.BackgroundColor = "#000000"
|
||||||
|
}
|
||||||
|
|
||||||
flexSpot.Comment = strings.ReplaceAll(flexSpot.Comment, " ", "\u00A0")
|
flexSpot.Comment = strings.ReplaceAll(flexSpot.Comment, " ", "\u00A0")
|
||||||
flexSpot.Comment = flexSpot.Comment + "\u00A0" + "[" + flexSpot.Mode + "]"
|
flexSpot.Comment = flexSpot.Comment + "\u00A0" + "[" + flexSpot.Mode + "] [" + flexSpot.SpotterCallsign + "]"
|
||||||
|
|
||||||
srcFlexSpot, err := fc.Repo.FindDXSameBand(flexSpot)
|
srcFlexSpot, err := fc.Repo.FindDXSameBand(flexSpot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -163,29 +171,29 @@ func (fc *FlexClient) SendSpottoFlex(spot TelnetSpot) {
|
|||||||
fc.Repo.CreateSpot(flexSpot)
|
fc.Repo.CreateSpot(flexSpot)
|
||||||
stringSpot := fmt.Sprintf("C%v|spot add rx_freq=%v callsign=%s mode=%s source=%s spotter_callsign=%s timestamp=%v lifetime_seconds=%s comment=%s color=%s background_color=%s priority=%s", flexSpot.CommandNumber, flexSpot.FrequencyMhz,
|
stringSpot := fmt.Sprintf("C%v|spot add rx_freq=%v callsign=%s mode=%s source=%s spotter_callsign=%s timestamp=%v lifetime_seconds=%s comment=%s color=%s background_color=%s priority=%s", flexSpot.CommandNumber, flexSpot.FrequencyMhz,
|
||||||
flexSpot.DX, flexSpot.Mode, flexSpot.Source, flexSpot.SpotterCallsign, flexSpot.TimeStamp, flexSpot.LifeTime, flexSpot.Comment, flexSpot.Color, flexSpot.BackgroundColor, flexSpot.Priority)
|
flexSpot.DX, flexSpot.Mode, flexSpot.Source, flexSpot.SpotterCallsign, flexSpot.TimeStamp, flexSpot.LifeTime, flexSpot.Comment, flexSpot.Color, flexSpot.BackgroundColor, flexSpot.Priority)
|
||||||
|
CommandNumber++
|
||||||
fc.SendSpot(stringSpot)
|
fc.SendSpot(stringSpot)
|
||||||
|
|
||||||
} else if srcFlexSpot.DX != "" && srcFlexSpot.Band == flexSpot.Band && srcFlexSpot.FrequencyMhz != flexSpot.FrequencyMhz {
|
} else if srcFlexSpot.DX != "" && srcFlexSpot.Band == flexSpot.Band && srcFlexSpot.FrequencyMhz != flexSpot.FrequencyMhz {
|
||||||
fc.Repo.UpdateSpotSameBand(flexSpot)
|
fc.Repo.UpdateSpotSameBand(flexSpot)
|
||||||
stringSpot := fmt.Sprintf("C%v|spot set %v rx_freq=%v callsign=%s mode=%s source=%s spotter_callsign=%s timestamp=%v lifetime_seconds=%s comment=%s color=%s background_color=%s priority=%s", flexSpot.CommandNumber, srcFlexSpot.CommandNumber, flexSpot.FrequencyMhz,
|
stringSpot := fmt.Sprintf("C%v|spot set %v rx_freq=%v callsign=%s mode=%s source=%s spotter_callsign=%s timestamp=%v lifetime_seconds=%s comment=%s color=%s background_color=%s priority=%s", flexSpot.CommandNumber, srcFlexSpot.FlexSpotNumber, flexSpot.FrequencyMhz,
|
||||||
flexSpot.DX, flexSpot.Mode, flexSpot.Source, flexSpot.SpotterCallsign, flexSpot.TimeStamp, flexSpot.LifeTime, flexSpot.Comment, flexSpot.Color, flexSpot.BackgroundColor, flexSpot.Priority)
|
flexSpot.DX, flexSpot.Mode, flexSpot.Source, flexSpot.SpotterCallsign, flexSpot.TimeStamp, flexSpot.LifeTime, flexSpot.Comment, flexSpot.Color, flexSpot.BackgroundColor, flexSpot.Priority)
|
||||||
CommandNumber++
|
CommandNumber++
|
||||||
stringDelete := fmt.Sprintf("C%v|spot remove %v", CommandNumber, srcFlexSpot.FlexSpotNumber)
|
fc.SendSpot(stringSpot)
|
||||||
fc.DeleteAndSendSpot(stringSpot, stringDelete)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
} else if srcFlexSpot.DX != "" && srcFlexSpot.Band != flexSpot.Band {
|
||||||
|
fc.Repo.CreateSpot(flexSpot)
|
||||||
|
stringSpot := fmt.Sprintf("C%v|spot add rx_freq=%v callsign=%s mode=%s source=%s spotter_callsign=%s timestamp=%v lifetime_seconds=%s comment=%s color=%s background_color=%s priority=%s", flexSpot.CommandNumber, flexSpot.FrequencyMhz,
|
||||||
|
flexSpot.DX, flexSpot.Mode, flexSpot.Source, flexSpot.SpotterCallsign, flexSpot.TimeStamp, flexSpot.LifeTime, flexSpot.Comment, flexSpot.Color, flexSpot.BackgroundColor, flexSpot.Priority)
|
||||||
CommandNumber++
|
CommandNumber++
|
||||||
|
fc.SendSpot(stringSpot)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fc *FlexClient) SendSpot(stringSpot string) {
|
func (fc *FlexClient) SendSpot(stringSpot string) {
|
||||||
fc.Write(stringSpot)
|
fc.Write(stringSpot)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fc *FlexClient) DeleteAndSendSpot(stringSpot string, deleteSpot string) {
|
|
||||||
fc.Write(deleteSpot)
|
|
||||||
fc.Write(stringSpot)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fc *FlexClient) ReadLine() {
|
func (fc *FlexClient) ReadLine() {
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@ -195,10 +203,8 @@ func (fc *FlexClient) ReadLine() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// msgRaw := strings.TrimSpace(message)
|
// fc.Log.Info(message)
|
||||||
// fc.Log.Info(msgRaw)
|
|
||||||
|
|
||||||
// Response when spot is added
|
|
||||||
regRespSpot := *regexp.MustCompile(`R(\d+)\|0\|(\d+)\n`)
|
regRespSpot := *regexp.MustCompile(`R(\d+)\|0\|(\d+)\n`)
|
||||||
respSpot := regRespSpot.FindStringSubmatch(message)
|
respSpot := regRespSpot.FindStringSubmatch(message)
|
||||||
|
|
||||||
@ -232,8 +238,9 @@ func (fc *FlexClient) ReadLine() {
|
|||||||
respDelete := regSpotDeleted.FindStringSubmatch(message)
|
respDelete := regSpotDeleted.FindStringSubmatch(message)
|
||||||
|
|
||||||
if len(respDelete) > 0 {
|
if len(respDelete) > 0 {
|
||||||
|
spot, _ := fc.Repo.FindSpotByFlexSpotNumber(respDelete[1])
|
||||||
fc.Repo.DeleteSpotByFlexSpotNumber(respDelete[1])
|
fc.Repo.DeleteSpotByFlexSpotNumber(respDelete[1])
|
||||||
fc.Log.Infof("deleted spot %v from database", respDelete[1])
|
fc.Log.Infof("Spot: DX: %s - Spotter: %s - Freq: %s - Band: %s - FlexID: %v deleted from database", spot.DX, spot.SpotterCallsign, spot.FrequencyHz, spot.Band, respDelete[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
131789
flexradio.log
131789
flexradio.log
File diff suppressed because it is too large
Load Diff
1
go.mod
1
go.mod
@ -9,6 +9,7 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/gorilla/mux v1.8.1 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||||
|
2
go.sum
2
go.sum
@ -1,6 +1,8 @@
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||||
|
@ -15,6 +15,7 @@ func NewLog() *log.Logger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
w := io.MultiWriter(os.Stdout, f)
|
w := io.MultiWriter(os.Stdout, f)
|
||||||
|
// w := io.Writer(os.Stdout)
|
||||||
|
|
||||||
l := &log.Logger{
|
l := &log.Logger{
|
||||||
Out: w,
|
Out: w,
|
||||||
|
35
main.go
35
main.go
@ -3,6 +3,9 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"git.rouggy.com/rouggy/FlexDXCluster/logger"
|
"git.rouggy.com/rouggy/FlexDXCluster/logger"
|
||||||
)
|
)
|
||||||
@ -48,16 +51,42 @@ func main() {
|
|||||||
|
|
||||||
DeleteDatabase("./flex.sqlite", log)
|
DeleteDatabase("./flex.sqlite", log)
|
||||||
|
|
||||||
repo := NewFlexDXDatabase("flex.sqlite", log)
|
fRepo := NewFlexDXDatabase("flex.sqlite", log)
|
||||||
|
defer fRepo.db.Close()
|
||||||
|
|
||||||
|
cRepo := NewLog4OMContactsRepository(Cfg.SQLite.SQLitePath, log)
|
||||||
|
defer cRepo.db.Close()
|
||||||
|
|
||||||
TCPServer := NewTCPServer(Cfg.Telnet.Host, Cfg.Telnet.Port, log)
|
TCPServer := NewTCPServer(Cfg.Telnet.Host, Cfg.Telnet.Port, log)
|
||||||
FlexClient := NewFlexClient(*repo, *TCPServer, log)
|
FlexClient := NewFlexClient(*fRepo, *TCPServer, log)
|
||||||
TCPClient := NewTCPClient(*Cfg, TCPServer, FlexClient, log)
|
TCPClient := NewTCPClient(*Cfg, TCPServer, FlexClient, log)
|
||||||
|
HTTPServer := NewHTTPServer(*cRepo, *fRepo, log)
|
||||||
|
|
||||||
|
sigCh := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
|
||||||
|
|
||||||
go FlexClient.StartFlexClient()
|
go FlexClient.StartFlexClient()
|
||||||
go TCPClient.StartClient()
|
go TCPClient.StartClient()
|
||||||
go TCPServer.StartServer()
|
go TCPServer.StartServer()
|
||||||
|
|
||||||
select {}
|
go HTTPServer.StartHTTPServer()
|
||||||
|
|
||||||
|
for sig := range sigCh {
|
||||||
|
log.Infof("received signal: %v, shutting down TCP Client.", sig)
|
||||||
|
|
||||||
|
TCPClient.Close()
|
||||||
|
|
||||||
|
if err := fRepo.db.Close(); err != nil {
|
||||||
|
log.Error("failed to close the database connection properly")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cRepo.db.Close(); err != nil {
|
||||||
|
log.Error("failed to close Log4OM database connection properly")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
3
spot.go
3
spot.go
@ -25,7 +25,6 @@ type TelnetSpot struct {
|
|||||||
NewBand bool
|
NewBand bool
|
||||||
NewMode bool
|
NewMode bool
|
||||||
CallsignWorked bool
|
CallsignWorked bool
|
||||||
SpotChan chan TelnetSpot
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProcessTelnetSpot(re *regexp.Regexp, spotRaw string, SpotChan chan TelnetSpot, log *log.Logger) {
|
func ProcessTelnetSpot(re *regexp.Regexp, spotRaw string, SpotChan chan TelnetSpot, log *log.Logger) {
|
||||||
@ -49,7 +48,7 @@ func ProcessTelnetSpot(re *regexp.Regexp, spotRaw string, SpotChan chan TelnetSp
|
|||||||
spot.NewMode = false
|
spot.NewMode = false
|
||||||
spot.NewDXCC = false
|
spot.NewDXCC = false
|
||||||
|
|
||||||
contactRepo := NewContactsRepository(Cfg.SQLite.SQLitePath, log)
|
contactRepo := NewLog4OMContactsRepository(Cfg.SQLite.SQLitePath, log)
|
||||||
|
|
||||||
defer contactRepo.db.Close()
|
defer contactRepo.db.Close()
|
||||||
|
|
||||||
|
43
templates/home.html
Normal file
43
templates/home.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="theme-color" content="#18181B">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||||
|
<script src="https://unpkg.com/htmx.org@1.9.5" integrity="sha384-xcuj3WpfgjlKF+FXhSQFQ0ZNr39ln+hwjN3npfM9VBnUskLolQAcN80McRIVOPuO" crossorigin="anonymous"></script>
|
||||||
|
<title>FlexDXCluster DashBoard</title>
|
||||||
|
<style>
|
||||||
|
.card {
|
||||||
|
display:inline-block;
|
||||||
|
margin-right:25px!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-deck {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container" style="width:1000px; margin: 0 auto;">
|
||||||
|
<div class="card-deck">
|
||||||
|
<div class="card shadow p-3 mb-5 bg-body rounded" style="width: 18rem;">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Spots</h5>
|
||||||
|
<h6 class="card-subtitle mb-2 text-muted">Current number of spots</h6>
|
||||||
|
<div class="card-text fs-1 fw-bold text-center" id="spotCount" hx-get="/spotscount" hx-trigger="every 1s" hx-swap="innerHTML"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card shadow p-3 mb-5 bg-body rounded" style="width: 18rem;">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Top Spotters</h5>
|
||||||
|
<div id="spotters" hx-get="/spotters" hx-trigger="every 1s" hx-swap="innerHTML"><p class="card-text"></p></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="spot" style="width:1000px; margin:0 auto;" hx-get="/spots" hx-trigger="every 1s" hx-swap="innerHTML"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
31
templates/spot.html
Normal file
31
templates/spot.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{{define "spot"}}
|
||||||
|
<table class="table table-sm table-bordered shadow p-3 mb-5 bg-body rounded">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th scope="col">DX</th>
|
||||||
|
<th scope="col">Spotter</th>
|
||||||
|
<th scope="col">Freq</th>
|
||||||
|
<th scope="col">Band</th>
|
||||||
|
<th scope="col">Mode</th>
|
||||||
|
<th scope="col">UTC Time</th>
|
||||||
|
<th scope="col">Comment</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{ range .}}
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td class="align-middle">{{ .DX }}</td>
|
||||||
|
<td class="align-middle">{{ .SpotterCallsign }}</td>
|
||||||
|
<td class="align-middle">{{ .FrequencyMhz }}</td>
|
||||||
|
<td class="align-middle">{{ .Band }}</td>
|
||||||
|
<td class="align-middle">{{ .Mode }}</td>
|
||||||
|
<td class="align-middle">{{ .UTCTime }}</td>
|
||||||
|
<td class="align-middle">{{ .Comment }}</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
|
5
templates/spotCount.html
Normal file
5
templates/spotCount.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{{ define "spotCount" }}
|
||||||
|
|
||||||
|
{{ . }}
|
||||||
|
|
||||||
|
{{ end }}
|
9
templates/spotters.html
Normal file
9
templates/spotters.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{{ define "spotters" }}
|
||||||
|
|
||||||
|
{{ range.}}
|
||||||
|
|
||||||
|
<span class="fw-bold">{{ .Spotter }}</span>: <span>{{ .NumberofSpots }} spots</span><br>
|
||||||
|
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ end }}
|
Loading…
Reference in New Issue
Block a user