Compare commits
100 Commits
Author | SHA1 | Date | |
---|---|---|---|
e2b01f6585 | |||
37b41e2d4b | |||
ed8648fcf0 | |||
114430ed6d | |||
9a90ef7f30 | |||
78006a9da8 | |||
edffa5ddf6 | |||
77c6b1adc6 | |||
0ee5b8a90d | |||
b65344a766 | |||
8ed5ef9b35 | |||
dc565088ea | |||
f2587de80f | |||
a83d94b562 | |||
0795fc616e | |||
3818e3926a | |||
6c28be7f72 | |||
e716097f9b | |||
79192bd277 | |||
5b8e676e71 | |||
a3b00fe38d | |||
86c81396e2 | |||
e131a6fdce | |||
0265f4cb7b | |||
7152c7f377 | |||
4c1a2c7b01 | |||
b6d37527d7 | |||
c8cff15d28 | |||
bef1c4a3b6 | |||
8c09ac3fbe | |||
d4efcf2986 | |||
3387b78a42 | |||
f4e546a3f1 | |||
2026afdb8e | |||
a4faf89474 | |||
fd8dcca8e8 | |||
e52bb4ff5a | |||
98466d797a | |||
313aa152ed | |||
4bf61270d6 | |||
f3399f39c6 | |||
7277799e5d | |||
85ece4a101 | |||
0a954669be | |||
47e16d4610 | |||
a77422a561 | |||
e60059ac13 | |||
c7d5e61ba6 | |||
c4d5fb3a96 | |||
947c2a2403 | |||
145bbd4bf1 | |||
c45c67d59f | |||
4e2ffd3e60 | |||
c31b1d3188 | |||
714fd7636c | |||
a8f36c833f | |||
0085d40b4d | |||
b055cee040 | |||
7cacf2dfb7 | |||
ac8069f184 | |||
890d2a3565 | |||
054e575257 | |||
cb9e04fc1d | |||
7866f2b0b8 | |||
5eea245c7c | |||
980a99c1c4 | |||
f933c32ace | |||
12a48d025e | |||
271fea3e8a | |||
4f615662f3 | |||
23fb78b037 | |||
7343424b18 | |||
92a1643598 | |||
006e2722c6 | |||
67a0277b42 | |||
505bfd81e0 | |||
a910029353 | |||
8bfd5489b3 | |||
e3cbfd3d07 | |||
b94aea0f33 | |||
b0c9467ab7 | |||
5e91f7227f | |||
02a4cb15a2 | |||
34aa7477a5 | |||
6b914029c9 | |||
086bacbedd | |||
4c5ebe5988 | |||
c2fd3d679b | |||
ebffb7b168 | |||
3bc3b1c96d | |||
732df88b04 | |||
dd375b79b6 | |||
a55084dfd8 | |||
c8c6282216 | |||
e99efdb43f | |||
700e3ad178 | |||
25e5362d01 | |||
5f2a515437 | |||
8d8fa1f855 | |||
424eeeaa56 |
@ -62,7 +62,7 @@ func declareAlarmRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for k, srv := range settings.Federation {
|
for k, srv := range settings.Federation {
|
||||||
err = player.FederatedStop(srv)
|
err = srv.WakeStop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Unable to do federated wakeup on %s: %s", k, err.Error())
|
log.Printf("Unable to do federated wakeup on %s: %s", k, err.Error())
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"git.nemunai.re/nemunaire/reveil/config"
|
"git.nemunai.re/nemunaire/reveil/config"
|
||||||
|
"git.nemunai.re/nemunaire/reveil/model"
|
||||||
"git.nemunai.re/nemunaire/reveil/player"
|
"git.nemunai.re/nemunaire/reveil/player"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -47,4 +50,127 @@ func declareFederationRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
|||||||
|
|
||||||
c.JSON(http.StatusOK, true)
|
c.JSON(http.StatusOK, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.GET("/federation", func(c *gin.Context) {
|
||||||
|
settings, err := reveil.ReadSettings(cfg.SettingsFile)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, settings.Federation)
|
||||||
|
})
|
||||||
|
|
||||||
|
federationsRoutes := router.Group("/federation/:fid")
|
||||||
|
federationsRoutes.Use(func(c *gin.Context) {
|
||||||
|
settings, err := reveil.ReadSettings(cfg.SettingsFile)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f, ok := settings.Federation[string(c.Param("fid"))]
|
||||||
|
if !ok {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Action not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("federation", &f)
|
||||||
|
c.Next()
|
||||||
|
})
|
||||||
|
federationsRoutes.GET("", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, c.MustGet("federation"))
|
||||||
|
})
|
||||||
|
federationsRoutes.POST("sync", func(c *gin.Context) {
|
||||||
|
srv := c.MustGet("federation").(*reveil.FederationServer)
|
||||||
|
|
||||||
|
// Retrieve music list on remote
|
||||||
|
remoteMusics, err := srv.GetMusics()
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to retrieve remote tracks lists: %s", err.Error())})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve local music list
|
||||||
|
localMusics, err := reveil.LoadTracks(cfg)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to retrieve local tracks: %s", err.Error())})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute diff
|
||||||
|
var newMusics []reveil.Track
|
||||||
|
var oldMusics []reveil.Track
|
||||||
|
var musicsToEnable []reveil.Track
|
||||||
|
|
||||||
|
for _, rTrack := range remoteMusics {
|
||||||
|
found := false
|
||||||
|
for _, lTrack := range localMusics {
|
||||||
|
if bytes.Compare(lTrack.Id, rTrack.Id) == 0 || lTrack.Name == rTrack.Name {
|
||||||
|
if lTrack.Enabled != rTrack.Enabled {
|
||||||
|
if lTrack.Enabled {
|
||||||
|
musicsToEnable = append(musicsToEnable, rTrack)
|
||||||
|
} else {
|
||||||
|
oldMusics = append(oldMusics, *lTrack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found && rTrack.Enabled {
|
||||||
|
oldMusics = append(oldMusics, rTrack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, lTrack := range localMusics {
|
||||||
|
found := false
|
||||||
|
for _, rTrack := range remoteMusics {
|
||||||
|
if bytes.Compare(lTrack.Id, rTrack.Id) == 0 || lTrack.Name == rTrack.Name {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found && lTrack.Enabled {
|
||||||
|
newMusics = append(newMusics, *lTrack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable unexistant musics on local
|
||||||
|
for _, t := range oldMusics {
|
||||||
|
t.Enabled = false
|
||||||
|
err = srv.UpdateTrack(&t)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs when disabling remote tracks (unexistant on local): %s: %s", t.Id.ToString(), err.Error())})
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable existant musics on remote
|
||||||
|
for _, t := range musicsToEnable {
|
||||||
|
t.Enabled = true
|
||||||
|
err = srv.UpdateTrack(&t)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send new musics
|
||||||
|
for _, t := range newMusics {
|
||||||
|
err = srv.SendTrack(&t)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, true)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
45
app.go
45
app.go
@ -16,11 +16,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
db *reveil.LevelDBStorage
|
db *reveil.LevelDBStorage
|
||||||
router *gin.Engine
|
router *gin.Engine
|
||||||
srv *http.Server
|
srv *http.Server
|
||||||
nextAlarm *time.Timer
|
nextAlarm *time.Timer
|
||||||
|
nextPreAlarm *time.Timer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApp(cfg *config.Config) *App {
|
func NewApp(cfg *config.Config) *App {
|
||||||
@ -76,11 +77,45 @@ func (app *App) Start() {
|
|||||||
func (app *App) ResetTimer() {
|
func (app *App) ResetTimer() {
|
||||||
if app.nextAlarm != nil {
|
if app.nextAlarm != nil {
|
||||||
app.nextAlarm.Stop()
|
app.nextAlarm.Stop()
|
||||||
|
app.nextPreAlarm = nil
|
||||||
app.nextAlarm = nil
|
app.nextAlarm = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
settings, _ := reveil.ReadSettings(app.cfg.SettingsFile)
|
||||||
|
|
||||||
if na, routines, federated, err := reveil.GetNextAlarm(app.cfg, app.db); err == nil && na != nil {
|
if na, routines, federated, err := reveil.GetNextAlarm(app.cfg, app.db); err == nil && na != nil {
|
||||||
|
if settings != nil && settings.PreAlarmAction != "" {
|
||||||
|
app.nextPreAlarm = time.AfterFunc(time.Until(*na)-settings.PreAlarmActionDelay*time.Minute, func() {
|
||||||
|
app.nextPreAlarm = nil
|
||||||
|
|
||||||
|
settings, err := reveil.ReadSettings(app.cfg.SettingsFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to read settings:", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
action, err := reveil.LoadAction(app.cfg, settings.PreAlarmAction)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to load pre-alarm action:", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, err := action.Launch(settings)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err := cmd.Wait()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("%q: %s", action.Name, err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
log.Println("Next pre-alarm programmed for", time.Time(*na).Add(settings.PreAlarmActionDelay*-1*time.Minute))
|
||||||
|
}
|
||||||
app.nextAlarm = time.AfterFunc(time.Until(*na), func() {
|
app.nextAlarm = time.AfterFunc(time.Until(*na), func() {
|
||||||
|
app.nextPreAlarm = nil
|
||||||
app.nextAlarm = nil
|
app.nextAlarm = nil
|
||||||
reveil.RemoveOldAlarmsSingle(app.db)
|
reveil.RemoveOldAlarmsSingle(app.db)
|
||||||
|
|
||||||
|
137
model/federation.go
Normal file
137
model/federation.go
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
package reveil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FederationServer struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
Delay uint `json:"delay"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *FederationServer) WakeUp(seed int64) error {
|
||||||
|
req := map[string]interface{}{"seed": seed}
|
||||||
|
req_enc, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := http.Post(srv.URL+"/api/federation/wakeup", "application/json", bytes.NewBuffer(req_enc))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
res.Body.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *FederationServer) WakeStop() error {
|
||||||
|
res, err := http.Post(srv.URL+"/api/federation/wakeok", "application/json", nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
res.Body.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *FederationServer) GetMusics() ([]Track, error) {
|
||||||
|
res, err := http.Get(srv.URL + "/api/tracks")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
var tracks []Track
|
||||||
|
err = json.NewDecoder(res.Body).Decode(&tracks)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tracks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *FederationServer) UpdateTrack(t *Track) error {
|
||||||
|
req_enc, err := json.Marshal(t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("PUT", srv.URL+"/api/tracks/"+t.Id.ToString(), bytes.NewBuffer(req_enc))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
if res.StatusCode == http.StatusOK {
|
||||||
|
var track Track
|
||||||
|
err = json.NewDecoder(res.Body).Decode(&track)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var errmsg map[string]string
|
||||||
|
err = json.NewDecoder(res.Body).Decode(&errmsg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("%s", errmsg["errmsg"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *FederationServer) SendTrack(track *Track) error {
|
||||||
|
// Retrieve file
|
||||||
|
fd, err := track.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
w := multipart.NewWriter(&b)
|
||||||
|
|
||||||
|
var fw io.Writer
|
||||||
|
// Add an image file
|
||||||
|
if fw, err = w.CreateFormFile("trackfile", fd.Name()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err = io.Copy(fw, fd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
//
|
||||||
|
req, err := http.NewRequest("POST", srv.URL+"/api/tracks", &b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", w.FormDataContentType())
|
||||||
|
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("bad status: %s", res.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -6,19 +6,17 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FederationSettings struct {
|
|
||||||
URL string `json:"url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Settings represents the settings panel.
|
// Settings represents the settings panel.
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
Language string `json:"language"`
|
Language string `json:"language"`
|
||||||
GongInterval time.Duration `json:"gong_interval"`
|
GongInterval time.Duration `json:"gong_interval"`
|
||||||
WeatherDelay time.Duration `json:"weather_delay"`
|
WeatherDelay time.Duration `json:"weather_delay"`
|
||||||
WeatherAction string `json:"weather_action"`
|
WeatherAction string `json:"weather_action"`
|
||||||
MaxRunTime time.Duration `json:"max_run_time"`
|
PreAlarmActionDelay time.Duration `json:"pre_alarm_delay"`
|
||||||
MaxVolume uint16 `json:"max_volume"`
|
PreAlarmAction string `json:"pre_alarm_action"`
|
||||||
Federation map[string]FederationSettings `json:"federation"`
|
MaxRunTime time.Duration `json:"max_run_time"`
|
||||||
|
MaxVolume uint16 `json:"max_volume"`
|
||||||
|
Federation map[string]FederationServer `json:"federation"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExistsSettings checks if the settings file can by found at the given path.
|
// ExistsSettings checks if the settings file can by found at the given path.
|
||||||
|
@ -1,35 +1,29 @@
|
|||||||
package player
|
package player
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"log"
|
||||||
"encoding/json"
|
"time"
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"git.nemunai.re/nemunaire/reveil/model"
|
"git.nemunai.re/nemunaire/reveil/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func FederatedWakeUp(srv reveil.FederationSettings, seed int64) error {
|
func FederatedWakeUp(k string, srv reveil.FederationServer, seed int64) {
|
||||||
req := map[string]interface{}{"seed": seed}
|
if srv.Delay == 0 {
|
||||||
req_enc, err := json.Marshal(req)
|
err := srv.WakeUp(seed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
log.Printf("Unable to do federated wakeup on %s: %s", k, err.Error())
|
||||||
|
} else {
|
||||||
|
log.Printf("Federated wakeup on %s: launched!", k)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
go func() {
|
||||||
|
time.Sleep(time.Duration(srv.Delay) * time.Millisecond)
|
||||||
|
err := srv.WakeUp(seed)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to do federated wakeup on %s: %s", k, err.Error())
|
||||||
|
} else {
|
||||||
|
log.Printf("Federated wakeup on %s: launched!", k)
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := http.Post(srv.URL+"/api/federation/wakeup", "application/json", bytes.NewBuffer(req_enc))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
res.Body.Close()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func FederatedStop(srv reveil.FederationSettings) error {
|
|
||||||
res, err := http.Post(srv.URL+"/api/federation/wakeok", "application/json", nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
res.Body.Close()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
@ -58,12 +58,7 @@ func WakeUp(cfg *config.Config, routine []reveil.Identifier, federated bool) (er
|
|||||||
}
|
}
|
||||||
|
|
||||||
for k, srv := range settings.Federation {
|
for k, srv := range settings.Federation {
|
||||||
err = FederatedWakeUp(srv, seed)
|
FederatedWakeUp(k, srv, seed)
|
||||||
if err != nil {
|
|
||||||
log.Printf("Unable to do federated wakeup on %s: %s", k, err.Error())
|
|
||||||
} else {
|
|
||||||
log.Printf("Federated wakeup on %s: launched!", k)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1777
ui/package-lock.json
generated
1777
ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -15,18 +15,18 @@
|
|||||||
"@sveltejs/adapter-static": "^3.0.0",
|
"@sveltejs/adapter-static": "^3.0.0",
|
||||||
"@sveltejs/kit": "^2.0.0",
|
"@sveltejs/kit": "^2.0.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||||
"@typescript-eslint/parser": "^7.0.0",
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
"bootstrap": "^5.1.3",
|
"bootstrap": "^5.1.3",
|
||||||
"bootstrap-icons": "^1.8.0",
|
"bootstrap-icons": "^1.8.0",
|
||||||
"bootswatch": "^5.1.3",
|
"bootswatch": "^5.1.3",
|
||||||
"eslint": "^8.0.0",
|
"eslint": "^9.0.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint-config-prettier": "^9.0.0",
|
||||||
"eslint-plugin-svelte": "^2.33.0",
|
"eslint-plugin-svelte": "^2.33.0",
|
||||||
"prettier": "^3.1.1",
|
"prettier": "^3.1.1",
|
||||||
"prettier-plugin-svelte": "^3.1.2",
|
"prettier-plugin-svelte": "^3.1.2",
|
||||||
"svelte": "^4.0.0",
|
"svelte": "^5.0.0",
|
||||||
"svelte-check": "^3.4.3",
|
"svelte-check": "^4.0.0",
|
||||||
"svelte-preprocess": "^6.0.0",
|
"svelte-preprocess": "^6.0.0",
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.3.1",
|
||||||
"typescript": "^5.0.0"
|
"typescript": "^5.0.0"
|
||||||
@ -35,7 +35,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dayjs": "^1.11.5",
|
"dayjs": "^1.11.5",
|
||||||
"sass": "^1.49.7",
|
"sass": "^1.49.7",
|
||||||
"sass-loader": "^14.0.0",
|
"sass-loader": "^16.0.0",
|
||||||
"@sveltestrap/sveltestrap": "^6.0.0",
|
"@sveltestrap/sveltestrap": "^6.0.0",
|
||||||
"vite": "^5.0.0"
|
"vite": "^5.0.0"
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,14 @@
|
|||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
Button,
|
||||||
Col,
|
Col,
|
||||||
|
Icon,
|
||||||
Input,
|
Input,
|
||||||
|
InputGroup,
|
||||||
|
InputGroupText,
|
||||||
Row,
|
Row,
|
||||||
|
Spinner,
|
||||||
} from '@sveltestrap/sveltestrap';
|
} from '@sveltestrap/sveltestrap';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
@ -23,11 +28,26 @@
|
|||||||
delete value[bak];
|
delete value[bak];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const syncInProgress = { };
|
||||||
|
async function syncMusic(srv) {
|
||||||
|
syncInProgress[srv] = true;
|
||||||
|
|
||||||
|
const res = await fetch(`api/federation/${srv}/sync`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Accept': 'application/json'}
|
||||||
|
});
|
||||||
|
if (res.status != 200) {
|
||||||
|
throw new Error((await res.json()).errmsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
syncInProgress[srv] = false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if value}
|
{#if value}
|
||||||
{#each Object.keys(value) as key}
|
{#each Object.keys(value) as key}
|
||||||
<Row>
|
<Row class="mb-3">
|
||||||
<Col>
|
<Col>
|
||||||
<Input
|
<Input
|
||||||
type="string"
|
type="string"
|
||||||
@ -37,12 +57,50 @@
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col>
|
<Col>
|
||||||
<Input
|
<InputGroup>
|
||||||
type="string"
|
<Input
|
||||||
placeholder="https://reveil.fr/"
|
type="string"
|
||||||
bind:value={value[key].url}
|
placeholder="https://reveil.fr/"
|
||||||
on:change={() => dispatch("input")}
|
bind:value={value[key].url}
|
||||||
/>
|
on:change={() => dispatch("input")}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
href={value[key].url}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<Icon name="globe" />
|
||||||
|
</Button>
|
||||||
|
</InputGroup>
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<InputGroup>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="60"
|
||||||
|
bind:value={value[key].delay}
|
||||||
|
on:change={() => dispatch("input")}
|
||||||
|
/>
|
||||||
|
<InputGroupText>ms</InputGroupText>
|
||||||
|
</InputGroup>
|
||||||
|
</Col>
|
||||||
|
<Col xs="auto">
|
||||||
|
<Button
|
||||||
|
color="info"
|
||||||
|
disabled={syncInProgress[key]}
|
||||||
|
title="Synchroniser les musiques"
|
||||||
|
type="button"
|
||||||
|
on:click={() => syncMusic(key)}
|
||||||
|
>
|
||||||
|
{#if syncInProgress[key]}
|
||||||
|
<Spinner size="sm" />
|
||||||
|
{:else}
|
||||||
|
<Icon name="music-note-list" />
|
||||||
|
{/if}
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{/each}
|
{/each}
|
||||||
@ -65,4 +123,12 @@
|
|||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="60"
|
||||||
|
disabled
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -5,11 +5,13 @@ export class Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update({ language, gong_interval, weather_delay, weather_action, max_run_time, max_volume, federation }) {
|
update({ language, gong_interval, weather_delay, weather_action, pre_alarm_delay, pre_alarm_action, max_run_time, max_volume, federation }) {
|
||||||
this.language = language;
|
this.language = language;
|
||||||
this.gong_interval = gong_interval;
|
this.gong_interval = gong_interval;
|
||||||
this.weather_delay = weather_delay;
|
this.weather_delay = weather_delay;
|
||||||
this.weather_action = weather_action;
|
this.weather_action = weather_action;
|
||||||
|
this.pre_alarm_delay = pre_alarm_delay;
|
||||||
|
this.pre_alarm_action = pre_alarm_action;
|
||||||
this.max_run_time = max_run_time;
|
this.max_run_time = max_run_time;
|
||||||
this.max_volume = max_volume;
|
this.max_volume = max_volume;
|
||||||
this.federation = federation;
|
this.federation = federation;
|
||||||
|
@ -2,6 +2,18 @@ import { writable } from 'svelte/store';
|
|||||||
|
|
||||||
import { getTracks } from '$lib/track'
|
import { getTracks } from '$lib/track'
|
||||||
|
|
||||||
|
function cmpTracks(a, b) {
|
||||||
|
if (a.enabled && !b.enabled) return -1;
|
||||||
|
if (!a.enabled && b.enabled) return 1;
|
||||||
|
|
||||||
|
if (a.path.toLowerCase() > b.path.toLowerCase())
|
||||||
|
return 1;
|
||||||
|
if (a.path.toLowerCase() < b.path.toLowerCase())
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
function createTracksStore() {
|
function createTracksStore() {
|
||||||
const { subscribe, set, update } = writable({list: null});
|
const { subscribe, set, update } = writable({list: null});
|
||||||
|
|
||||||
@ -14,6 +26,7 @@ function createTracksStore() {
|
|||||||
|
|
||||||
refresh: async () => {
|
refresh: async () => {
|
||||||
const list = await getTracks();
|
const list = await getTracks();
|
||||||
|
list.sort(cmpTracks);
|
||||||
update((m) => Object.assign(m, {list}));
|
update((m) => Object.assign(m, {list}));
|
||||||
return list;
|
return list;
|
||||||
},
|
},
|
||||||
|
@ -89,6 +89,40 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<Label for="preAlarmDelay">Lancement action pré-alarme</Label>
|
||||||
|
<InputGroup>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
id="preAlarmDelay"
|
||||||
|
placeholder="5"
|
||||||
|
bind:value={settings.pre_alarm_delay}
|
||||||
|
on:input={submitSettings}
|
||||||
|
/>
|
||||||
|
<InputGroupText>min</InputGroupText>
|
||||||
|
</InputGroup>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<Label for="preAlarmRituel">Action pour l'action pré-alarme</Label>
|
||||||
|
{#if $actions.list}
|
||||||
|
<Input
|
||||||
|
type="select"
|
||||||
|
id="preAlarmRituel"
|
||||||
|
bind:value={settings.pre_alarm_action}
|
||||||
|
on:input={submitSettings}
|
||||||
|
>
|
||||||
|
{#each $actions.list as action (action.id)}
|
||||||
|
<option value="{action.path}">{action.name}</option>
|
||||||
|
{/each}
|
||||||
|
</Input>
|
||||||
|
{:else}
|
||||||
|
<div class="d-flex justify-content-center align-items-center gap-2">
|
||||||
|
<Spinner color="primary" /> Chargement en cours…
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<Label for="greetingLanguage">Langue de salutation</Label>
|
<Label for="greetingLanguage">Langue de salutation</Label>
|
||||||
<Input
|
<Input
|
||||||
|
Loading…
Reference in New Issue
Block a user