Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions internal/config/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package config

import (
"log/slog"
"os"
"sync/atomic"

"gopkg.in/yaml.v3"
)

var defaultConfig atomic.Pointer[Config]

func init() {
defaultConfig.Store(New())
}

func New() *Config {
return &Config{}
}

// LoadFromFile loads the configuration from a YAML file at the given path.
// It validates and serializes the configuration after loading.
// Returns the loaded Config or an error if loading, parsing, validation, or serialization fails.
func (conf *Config) LoadFromFile(path string) (*Config, error) {
yamlFile, err := os.ReadFile(path)

if err != nil {
return nil, err
}

err = yaml.Unmarshal(yamlFile, &conf)

if err != nil {
return nil, err
}

conf.Serialize()

validation_err := conf.Validate()

if validation_err != nil {
return nil, validation_err
}

slog.Info("Finished loading configuration from file")

return conf, nil
}

func SetConfig(c *Config){
defaultConfig.Store(c)
}

func Default() *Config {
return defaultConfig.Load()
}
36 changes: 36 additions & 0 deletions internal/config/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package config

type OpencastConfig struct {
URL string `yaml:"url"`
Username string `yaml:"username"`
Password string `yaml:"password"`
Agent string `yaml:"agent"`
}

type DisplayStateConfig struct {
Text string `yaml:"text" json:"text"`
Color string `yaml:"color" json:"color"`
Background string `yaml:"background" json:"background"`
Image string `yaml:"image" json:"image"`
Info string `yaml:"info" json:"info"`
Empty string `yaml:"none" json:"empty"`
}

type DisplayConfig struct {
Capturing DisplayStateConfig `yaml:"capturing" json:"capturing"`
Idle DisplayStateConfig `yaml:"idle" json:"idle"`
Unknown DisplayStateConfig `yaml:"unknown" json:"unknown"`
}

type MetricsConfig struct {
Enable bool `yaml:"prometheus"` // is currently still controlled through prometheus, TODO: change in future
Listen string `yaml:"listen"`
}

type Config struct {
Opencast OpencastConfig `yaml:"opencast"`
Display DisplayConfig `yaml:"display"`
Listen string `yaml:"listen"`
Timeout int `yaml:"timeout"`
Metrics MetricsConfig `yaml:"metrics"`
}
51 changes: 51 additions & 0 deletions internal/config/serialize.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package config

import (
"log/slog"
"strings"
)

// serialize cleans up the Opencast configuration by trimming trailing slashes from the URL.
func (oc_conf *OpencastConfig) serialize() {
oc_conf.URL = strings.Trim(oc_conf.URL, "/")
}

// serialize sets default values for Metrics configuration if not specified.
// If Metrics are enabled but Listen is empty, it defaults to "0.0.0.0:9100".
func (met_conf *MetricsConfig) serialize() {
if met_conf.Listen == "" && met_conf.Enable {
met_conf.Listen = "0.0.0.0:9100"
slog.Info("set metrics enpoit to default 0.0.0.0:9100")
}
}

// serialize performs serialization on Display configuration.
// Currently a no-op (empty implementation).
func (display_conf *DisplayConfig) serialize() {}

// serialize performs serialization on DisplayState configuration.
// Currently a no-op (empty implementation).
func (display_state_conf *DisplayStateConfig) serialize() {}

// Serialize performs serialization on all configuration sections.
// It sets default values for Listen (127.0.0.1:8080) and Timeout (500) if not specified.
func (conf *Config) Serialize() {
// Serialize opencast config
conf.Opencast.serialize()

// Serilaize Display config
conf.Display.serialize()

// Serialize metrics config
conf.Metrics.serialize()

if conf.Listen == "" {
conf.Listen = "127.0.0.1:8080"
slog.Info("set backend listen to default 127.0.0.1:8080")
}

if conf.Timeout <= 0 {
conf.Timeout = 500
slog.Info("set request timeout to a reasonable level")
}
}
58 changes: 58 additions & 0 deletions internal/config/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package config

import (
"errors"
)

// validate checks if the Opencast configuration has a valid URL.
// Returns an error if the URL is empty.
func (oc_conf *OpencastConfig) validate() error {
if oc_conf.URL == "" {
return errors.New("Opencast: No Opencast server URL in configuration")
}
return nil
}

// validate checks if the Display configuration is valid.
// Currently always returns nil (no validation implemented).
func (display_conf *DisplayConfig) validate() error {
return nil
}

// validate checks if the DisplayState configuration is valid.
// Currently always returns nil (no validation implemented).
func (disp_state_conf *DisplayStateConfig) validate() error {
return nil
}

// validate checks if the Metrics configuration is valid.
// Currently always returns nil (no validation implemented).
func (metrics_conf *MetricsConfig) validate() error {
return nil
}

// Validate performs validation on all configuration sections.
// It checks Opencast, Display, and Metrics configurations, as well as
// the main timeout and listen settings.
// Returns a combined error of all validation failures, or nil if all are valid.
func (conf *Config) Validate() error {
opencast_err := conf.Opencast.validate()

display_err := conf.Display.validate()

metrics_err := conf.Metrics.validate()

var timeout_err error
if conf.Timeout <= 0 {
timeout_err = errors.New("the timeout can´t be zero or negative")
}

var listen_err error
if conf.Listen == "" {
listen_err = errors.New("it most be configured, where the main backend should listen on")
}

err := errors.Join(opencast_err, display_err, metrics_err, timeout_err, listen_err)

return err
}
112 changes: 112 additions & 0 deletions internal/endpoints/calendar.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package endpoints

import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"time"

"github.com/gin-gonic/gin"
)

type Event struct {
Title string `json:"title"`
Start int `json:"start"`
End int `json:"end"`
}

type CalendarWorkflowProperties struct {
StraightToPublishing string `json:"straightToPublishing"`
}

type CalenderAgentConfig struct {
CaptureDeviceNames string `json:"capture.device.names"`
WorkflowDefinition string `json:"org.opencastproject.workflow.definition"`
WorkflowConfigStraightToPublishing string `json:"org.opencastproject.workflow.config.straightToPublishing"`
EventLocation string `json:"event.location"`
EventTitle string `json:"event.title"`
}

type CalendarRecording struct {
}

type CalendarData struct {
EventID string `json:"eventId"`
AgentID string `json:"agentId"`
StartDate int // Verwenden Sie time.Time statt string
EndDate int // Verwenden Sie time.Time statt string
Presenters []string `json:"presenters"`
WorkflowProperties CalendarWorkflowProperties `json:"workflowProperties"`
AgentConfig CalenderAgentConfig `json:"agentConfig"`
Recording CalendarRecording `json:"recording"`
}

type CalendarEntry struct {
Data CalendarData `json:"data"`
EpisodeDublinCore string `json:"episode-dublincore"`
}

func calendarEndpoint(c *gin.Context) {
client := &http.Client{Timeout: time.Duration(localConfig.Timeout * int(time.Millisecond))}
// Cutoff is set to 3 day from now; TODO: set back to 24 Hours
cutoff := time.Now().Add(time.Hour * 720).UnixMilli()
url := localConfig.Opencast.URL + "/recordings/calendar.json?agentid=" + localConfig.Opencast.Agent + "&cutoff=" + fmt.Sprint(cutoff) + "&timestamp=true"
req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Println(err)
c.JSON(http.StatusBadGateway, nil)
return
}
req.SetBasicAuth(localConfig.Opencast.Username, localConfig.Opencast.Password)
resp, err := client.Do(req)
if err != nil {
if os.IsTimeout(err) {
log.Println("Request timed out:", err)
c.JSON(http.StatusGatewayTimeout, gin.H{"error": "Request timed out"})
// stateCollector.WithLabelValues("gateway_timeout").Set(1)
} else {
log.Println(err)
c.JSON(http.StatusBadGateway, gin.H{"error": "Internal server error"})
// stateCollector.WithLabelValues("internal_server_error").Set(1)
}
return
}
if resp.StatusCode != 200 {
log.Println(resp)
c.JSON(resp.StatusCode, nil)
return
}

bodyText, err := io.ReadAll(resp.Body)
if err != nil {
log.Println(err)
c.JSON(http.StatusBadGateway, nil)
return
}
s := string([]byte(bodyText))

var allEvents []CalendarEntry
json_err := json.Unmarshal([]byte(s), &allEvents)
if json_err != nil {
log.Fatal(json_err)
}

var events []Event
for _, eventData := range allEvents {
start := eventData.Data.StartDate
end := eventData.Data.EndDate
title := eventData.Data.AgentConfig.EventTitle
e := Event{Title: title, Start: start, End: end}
events = append(events, e)
}

if len(allEvents) > 0 {
fmt.Println(events)
c.JSON(http.StatusOK, events)
} else {
c.JSON(http.StatusOK, "")
}
}
64 changes: 64 additions & 0 deletions internal/endpoints/network.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package endpoints

import (
"log"
"net"
"net/http"
"os"
"time"

"github.com/gin-gonic/gin"
)

type NetworkStatus struct {
Interfaces []NetInterface `json:"interfaces"`
Connected bool `json:"connected"`
Hostname string `json:"hostname"`
}

type NetInterface struct {
Name string `json:"name"`
Adress []string `json:"addr"`
MAC string `json:"mac_adress"`
Flags string `json:"flags"`
}

func networkEndpoint(c *gin.Context) {
var net_status NetworkStatus
net_interfaces, err := net.Interfaces()
if err != nil {
log.Fatalln("Network devices could not be loaded.")
return
}
for _, net_inter := range net_interfaces {
addrs, err := net_inter.Addrs()
var addrs_str []string
if err == nil {
for _, a := range addrs {
addrs_str = append(addrs_str, a.String())
}
}
inter := NetInterface{Name: net_inter.Name, MAC: net_inter.HardwareAddr.String(), Adress: addrs_str, Flags: net_inter.Flags.String()}
net_status.Interfaces = append(net_status.Interfaces, inter)
}
client := &http.Client{Timeout: time.Duration(localConfig.Timeout * int(time.Millisecond))}
url := localConfig.Opencast.URL
req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Println(err)
c.JSON(http.StatusBadGateway, nil)
return
}
// req.SetBasicAuth(localConfig.Opencast.Username, localConfig.Opencast.Password)
_, err = client.Do(req)
if err != nil {
net_status.Connected = false
} else {
net_status.Connected = true
}
net_status.Hostname, err = os.Hostname()
if err != nil {
c.JSON(http.StatusInternalServerError, nil)
}
c.JSON(http.StatusOK, net_status)
}
Loading
Loading