Compare commits

..

No commits in common. "master" and "v0.4.3" have entirely different histories.

21 changed files with 954 additions and 1550 deletions

View File

@ -1,14 +1,11 @@
package api package api
import ( import (
"fmt"
"log"
"net/http" "net/http"
"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"
) )
@ -23,7 +20,7 @@ func declareAlarmRoutes(cfg *config.Config, router *gin.RouterGroup) {
router.POST("/alarm/run", func(c *gin.Context) { router.POST("/alarm/run", func(c *gin.Context) {
if player.CommonPlayer == nil { if player.CommonPlayer == nil {
err := player.WakeUp(cfg, nil, true) err := player.WakeUp(cfg, nil)
if err != nil { if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return return
@ -54,21 +51,6 @@ func declareAlarmRoutes(cfg *config.Config, router *gin.RouterGroup) {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return return
} }
settings, err := reveil.ReadSettings(cfg.SettingsFile)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Errorf("Unable to read settings: %s", err.Error())})
return
}
for k, srv := range settings.Federation {
err = srv.WakeStop()
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)
}
}
} }
c.JSON(http.StatusOK, true) c.JSON(http.StatusOK, true)

View File

@ -13,7 +13,7 @@ import (
func declareAlarmsRoutes(cfg *config.Config, db *reveil.LevelDBStorage, resetTimer func(), router *gin.RouterGroup) { func declareAlarmsRoutes(cfg *config.Config, db *reveil.LevelDBStorage, resetTimer func(), router *gin.RouterGroup) {
router.GET("/alarms/next", func(c *gin.Context) { router.GET("/alarms/next", func(c *gin.Context) {
alarm, _, _, err := reveil.GetNextAlarm(cfg, db) alarm, _, err := reveil.GetNextAlarm(cfg, db)
if err != nil { if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return return

View File

@ -1,176 +0,0 @@
package api
import (
"bytes"
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
"git.nemunai.re/nemunaire/reveil/config"
"git.nemunai.re/nemunaire/reveil/model"
"git.nemunai.re/nemunaire/reveil/player"
)
func declareFederationRoutes(cfg *config.Config, router *gin.RouterGroup) {
router.POST("/federation/wakeup", func(c *gin.Context) {
var s map[string]interface{}
c.ShouldBind(s)
if player.CommonPlayer == nil {
var seed int64
if tmp, ok := s["seed"].(int64); ok {
seed = tmp
} else {
seed := time.Now().Unix()
seed -= seed % 172800
}
err := player.WakeUpFromFederation(cfg, seed, nil)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
} else {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Player already running"})
return
}
c.JSON(http.StatusOK, true)
})
router.POST("/federation/wakeok", func(c *gin.Context) {
if player.CommonPlayer != nil {
err := player.CommonPlayer.Stop()
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
}
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)
})
}

View File

@ -13,7 +13,6 @@ func DeclareRoutes(router *gin.Engine, cfg *config.Config, db *reveil.LevelDBSto
declareActionsRoutes(cfg, apiRoutes) declareActionsRoutes(cfg, apiRoutes)
declareAlarmRoutes(cfg, apiRoutes) declareAlarmRoutes(cfg, apiRoutes)
declareAlarmsRoutes(cfg, db, resetTimer, apiRoutes) declareAlarmsRoutes(cfg, db, resetTimer, apiRoutes)
declareFederationRoutes(cfg, apiRoutes)
declareGongsRoutes(cfg, apiRoutes) declareGongsRoutes(cfg, apiRoutes)
declareHistoryRoutes(cfg, apiRoutes) declareHistoryRoutes(cfg, apiRoutes)
declareQuotesRoutes(cfg, apiRoutes) declareQuotesRoutes(cfg, apiRoutes)

39
app.go
View File

@ -21,7 +21,6 @@ type App struct {
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 {
@ -77,52 +76,18 @@ 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, 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)
// Rearm timer for the next time // Rearm timer for the next time
app.ResetTimer() app.ResetTimer()
err := player.WakeUp(app.cfg, routines, federated) err := player.WakeUp(app.cfg, routines)
if err != nil { if err != nil {
log.Println(err.Error()) log.Println(err.Error())
return return

View File

@ -40,27 +40,25 @@ func (h *Hour) UnmarshalJSON(src []byte) error {
return nil return nil
} }
func GetNextAlarm(cfg *config.Config, db *LevelDBStorage) (*time.Time, []Identifier, bool, error) { func GetNextAlarm(cfg *config.Config, db *LevelDBStorage) (*time.Time, []Identifier, error) {
alarmsRepeated, err := GetAlarmsRepeated(db) alarmsRepeated, err := GetAlarmsRepeated(db)
if err != nil { if err != nil {
return nil, nil, false, err return nil, nil, err
} }
var closestAlarm *time.Time var closestAlarm *time.Time
var closestAlarmRoutines []Identifier var closestAlarmRoutines []Identifier
var closestAlarmFederated bool
for _, alarm := range alarmsRepeated { for _, alarm := range alarmsRepeated {
next := alarm.GetNextOccurence(cfg, db) next := alarm.GetNextOccurence(cfg, db)
if next != nil && (closestAlarm == nil || closestAlarm.After(*next)) { if next != nil && (closestAlarm == nil || closestAlarm.After(*next)) {
closestAlarm = next closestAlarm = next
closestAlarmRoutines = alarm.FollowingRoutines closestAlarmRoutines = alarm.FollowingRoutines
closestAlarmFederated = alarm.EnableFederation
} }
} }
alarmsSingle, err := GetAlarmsSingle(db) alarmsSingle, err := GetAlarmsSingle(db)
if err != nil { if err != nil {
return nil, nil, false, err return nil, nil, err
} }
now := time.Now() now := time.Now()
@ -68,11 +66,10 @@ func GetNextAlarm(cfg *config.Config, db *LevelDBStorage) (*time.Time, []Identif
if closestAlarm == nil || (closestAlarm.After(alarm.Time) && alarm.Time.After(now)) { if closestAlarm == nil || (closestAlarm.After(alarm.Time) && alarm.Time.After(now)) {
closestAlarm = &alarm.Time closestAlarm = &alarm.Time
closestAlarmRoutines = alarm.FollowingRoutines closestAlarmRoutines = alarm.FollowingRoutines
closestAlarmFederated = alarm.EnableFederation
} }
} }
return closestAlarm, closestAlarmRoutines, closestAlarmFederated, nil return closestAlarm, closestAlarmRoutines, nil
} }
func GetNextException(cfg *config.Config, db *LevelDBStorage) (*time.Time, error) { func GetNextException(cfg *config.Config, db *LevelDBStorage) (*time.Time, error) {
@ -93,7 +90,7 @@ func GetNextException(cfg *config.Config, db *LevelDBStorage) (*time.Time, error
} }
func DropNextAlarm(cfg *config.Config, db *LevelDBStorage) error { func DropNextAlarm(cfg *config.Config, db *LevelDBStorage) error {
timenext, _, _, err := GetNextAlarm(cfg, db) timenext, _, err := GetNextAlarm(cfg, db)
if err != nil { if err != nil {
return err return err
} }
@ -155,7 +152,6 @@ type AlarmRepeated struct {
Disabled bool `json:"disabled,omitempty"` Disabled bool `json:"disabled,omitempty"`
Excepts Exceptions `json:"excepts,omitempty"` Excepts Exceptions `json:"excepts,omitempty"`
NextTime *time.Time `json:"next_time,omitempty"` NextTime *time.Time `json:"next_time,omitempty"`
EnableFederation bool `json:"enable_federation,omitempty"`
} }
func (a *AlarmRepeated) FillExcepts(cfg *config.Config, db *LevelDBStorage) error { func (a *AlarmRepeated) FillExcepts(cfg *config.Config, db *LevelDBStorage) error {
@ -277,7 +273,6 @@ type AlarmSingle struct {
Time time.Time `json:"time"` Time time.Time `json:"time"`
FollowingRoutines []Identifier `json:"routines"` FollowingRoutines []Identifier `json:"routines"`
Comment string `json:"comment,omitempty"` Comment string `json:"comment,omitempty"`
EnableFederation bool `json:"enable_federation,omitempty"`
} }
func GetAlarmSingle(db *LevelDBStorage, id Identifier) (alarm *AlarmSingle, err error) { func GetAlarmSingle(db *LevelDBStorage, id Identifier) (alarm *AlarmSingle, err error) {

View File

@ -1,137 +0,0 @@
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
}

View File

@ -12,11 +12,8 @@ type Settings struct {
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"`
PreAlarmActionDelay time.Duration `json:"pre_alarm_delay"`
PreAlarmAction string `json:"pre_alarm_action"`
MaxRunTime time.Duration `json:"max_run_time"` MaxRunTime time.Duration `json:"max_run_time"`
MaxVolume uint16 `json:"max_volume"` 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.

View File

@ -1,29 +0,0 @@
package player
import (
"log"
"time"
"git.nemunai.re/nemunaire/reveil/model"
)
func FederatedWakeUp(k string, srv reveil.FederationServer, seed int64) {
if srv.Delay == 0 {
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)
}
} 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)
}
}()
}
}

View File

@ -43,29 +43,13 @@ type Player struct {
playedItem int playedItem int
} }
func WakeUp(cfg *config.Config, routine []reveil.Identifier, federated bool) (err error) { func WakeUp(cfg *config.Config, routine []reveil.Identifier) (err error) {
if CommonPlayer != nil { if CommonPlayer != nil {
return fmt.Errorf("Unable to start the player: a player is already running") return fmt.Errorf("Unable to start the player: a player is already running")
} }
seed := time.Now().Unix() seed := time.Now().Unix()
seed -= seed % 172800 seed -= seed % 172800
if federated {
settings, err := reveil.ReadSettings(cfg.SettingsFile)
if err != nil {
return fmt.Errorf("Unable to read settings: %w", err)
}
for k, srv := range settings.Federation {
FederatedWakeUp(k, srv, seed)
}
}
return WakeUpFromFederation(cfg, seed, routine)
}
func WakeUpFromFederation(cfg *config.Config, seed int64, routine []reveil.Identifier) (err error) {
rand.Seed(seed) rand.Seed(seed)
CommonPlayer, err = NewPlayer(cfg, routine) CommonPlayer, err = NewPlayer(cfg, routine)

View File

@ -24,7 +24,7 @@ func DeclareNoJSRoutes(router *gin.Engine, cfg *config.Config, db *reveil.LevelD
router.SetHTMLTemplate(templ) router.SetHTMLTemplate(templ)
router.GET("/nojs.html", func(c *gin.Context) { router.GET("/nojs.html", func(c *gin.Context) {
alarm, _, _, err := reveil.GetNextAlarm(cfg, db) alarm, _, err := reveil.GetNextAlarm(cfg, db)
if err != nil { if err != nil {
c.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{"errmsg": err.Error()}) c.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{"errmsg": err.Error()})
return return
@ -122,7 +122,7 @@ func DeclareNoJSRoutes(router *gin.Engine, cfg *config.Config, db *reveil.LevelD
case "start": case "start":
if player.CommonPlayer == nil { if player.CommonPlayer == nil {
err := player.WakeUp(cfg, nil, true) err := player.WakeUp(cfg, nil)
if err != nil { if err != nil {
c.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{"errmsg": err.Error()}) c.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{"errmsg": err.Error()})
return return

1809
ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -15,19 +15,19 @@
"@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": "^8.0.0", "@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^8.0.0", "@typescript-eslint/parser": "^7.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": "^9.0.0", "eslint": "^8.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": "^5.0.0", "svelte": "^4.0.0",
"svelte-check": "^4.0.0", "svelte-check": "^3.4.3",
"svelte-preprocess": "^6.0.0", "svelte-preprocess": "^5.0.3",
"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": "^16.0.0", "sass-loader": "^14.0.0",
"@sveltestrap/sveltestrap": "^6.0.0", "@sveltestrap/sveltestrap": "^6.0.0",
"vite": "^5.0.0" "vite": "^5.0.0"
} }

View File

@ -5,7 +5,7 @@ export class AlarmRepeated {
} }
} }
update({ id, weekday, time, routines, disabled, ignore_exceptions, comment, excepts, next_time, enable_federation }) { update({ id, weekday, time, routines, disabled, ignore_exceptions, comment, excepts, next_time }) {
this.id = id; this.id = id;
this.weekday = weekday; this.weekday = weekday;
this.time = time; this.time = time;
@ -13,7 +13,6 @@ export class AlarmRepeated {
this.ignore_exceptions = ignore_exceptions; this.ignore_exceptions = ignore_exceptions;
this.comment = comment; this.comment = comment;
this.disabled = disabled == true; this.disabled = disabled == true;
this.enable_federation = enable_federation == true;
if (excepts !== undefined) if (excepts !== undefined)
this.excepts = excepts; this.excepts = excepts;
if (next_time !== undefined) if (next_time !== undefined)

View File

@ -5,12 +5,11 @@ export class AlarmSingle {
} }
} }
update({ id, time, routines, comment, enable_federation }) { update({ id, time, routines, comment }) {
this.id = id; this.id = id;
this.time = new Date(time); this.time = new Date(time);
this.routines = routines == null ? [] : routines; this.routines = routines == null ? [] : routines;
this.comment = comment; this.comment = comment;
this.enable_federation = enable_federation == true;
if (this.routines.length < 1) { if (this.routines.length < 1) {
this.routines.push(""); this.routines.push("");

View File

@ -1,134 +0,0 @@
<script>
import { createEventDispatcher } from 'svelte';
import {
Button,
Col,
Icon,
Input,
InputGroup,
InputGroupText,
Row,
Spinner,
} from '@sveltestrap/sveltestrap';
const dispatch = createEventDispatcher();
export let id = "";
export let value = { };
function changeKey(bak, to) {
if (bak === null && to.target.value) {
if (!value) value = { };
value[to.target.value] = { url:"" };
to.target.value = "";
value = value;
} else {
value[to.target.value] = 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>
{#if value}
{#each Object.keys(value) as key}
<Row class="mb-3">
<Col>
<Input
type="string"
value={key}
on:change={() => dispatch("input")}
on:input={(e) => changeKey(key, e)}
/>
</Col>
<Col>
<InputGroup>
<Input
type="string"
placeholder="https://reveil.fr/"
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>
</Row>
{/each}
{/if}
<Row>
<Col>
<Input
type="string"
{id}
placeholder="name"
value=""
on:input={(e) => changeKey(null, e)}
/>
</Col>
<Col>
<Input
type="string"
placeholder="https://reveil.fr/"
disabled
value=""
/>
</Col>
<Col>
<Input
type="number"
placeholder="60"
disabled
value=""
/>
</Col>
</Row>

View File

@ -5,16 +5,13 @@ export class Settings {
} }
} }
update({ language, gong_interval, weather_delay, weather_action, pre_alarm_delay, pre_alarm_action, max_run_time, max_volume, federation }) { update({ language, gong_interval, weather_delay, weather_action, max_run_time, max_volume }) {
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;
} }
async save() { async save() {

View File

@ -2,18 +2,6 @@ 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});
@ -26,7 +14,6 @@ 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;
}, },

View File

@ -95,10 +95,6 @@
<ListGroupItem> <ListGroupItem>
<strong>Heure du réveil</strong> <DateFormat date={alarm.time} timeStyle="long" /> <strong>Heure du réveil</strong> <DateFormat date={alarm.time} timeStyle="long" />
</ListGroupItem> </ListGroupItem>
<ListGroupItem class="d-flex">
<strong>Fédération activée&nbsp;?</strong>
<Input type="switch" class="ms-2" on:change={() => {obj.enable_federation = !obj.enable_federation; obj.save();}} checked={obj.enable_federation} /> {obj.enable_federation?"oui":"non"}
</ListGroupItem>
</ListGroup> </ListGroup>
{/await} {/await}
{:else if $page.params["kind"] == "repeated"} {:else if $page.params["kind"] == "repeated"}
@ -130,10 +126,6 @@
<strong>Ignorer les exceptions&nbsp;?</strong> <strong>Ignorer les exceptions&nbsp;?</strong>
<Input type="switch" class="ms-2" on:change={() => {obj.ignore_exceptions = !obj.ignore_exceptions; obj.save();}} checked={obj.ignore_exceptions} /> {obj.ignore_exceptions?"oui":"non"} <Input type="switch" class="ms-2" on:change={() => {obj.ignore_exceptions = !obj.ignore_exceptions; obj.save();}} checked={obj.ignore_exceptions} /> {obj.ignore_exceptions?"oui":"non"}
</ListGroupItem> </ListGroupItem>
<ListGroupItem class="d-flex">
<strong>Fédération activée&nbsp;?</strong>
<Input type="switch" class="ms-2" on:change={() => {obj.enable_federation = !obj.enable_federation; obj.save();}} checked={obj.enable_federation} /> {obj.enable_federation?"oui":"non"}
</ListGroupItem>
{#if alarm.next_time} {#if alarm.next_time}
<ListGroupItem> <ListGroupItem>
<strong>Prochaine occurrence</strong> <DateFormat date={new Date(obj.next_time)} dateStyle="long" /> <strong>Prochaine occurrence</strong> <DateFormat date={new Date(obj.next_time)} dateStyle="long" />

View File

@ -154,11 +154,6 @@
<Input id="exceptionEnd" type="date" required bind:value={obj.end} /> <Input id="exceptionEnd" type="date" required bind:value={obj.end} />
</FormGroup> </FormGroup>
{/if} {/if}
{#if $page.params["kind"] != "exceptions"}
<FormGroup>
<Input id="enable_federation" type="checkbox" label="Activer la fédération" bind:checked={obj.enable_federation} />
</FormGroup>
{/if}
<FormGroup> <FormGroup>
<Label for="comment">Commentaire</Label> <Label for="comment">Commentaire</Label>

View File

@ -15,7 +15,6 @@
import { actions } from '$lib/stores/actions'; import { actions } from '$lib/stores/actions';
import { getSettings } from '$lib/settings'; import { getSettings } from '$lib/settings';
import FederationSettings from '$lib/components/FederationSettings.svelte';
let settingsP = getSettings(); let settingsP = getSettings();
@ -89,40 +88,6 @@
{/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&hellip;
</div>
{/if}
</FormGroup>
<FormGroup> <FormGroup>
<Label for="greetingLanguage">Langue de salutation</Label> <Label for="greetingLanguage">Langue de salutation</Label>
<Input <Input
@ -165,15 +130,6 @@
/> />
</InputGroup> </InputGroup>
</FormGroup> </FormGroup>
<FormGroup>
<Label for="federation">Federation</Label>
<FederationSettings
id="federation"
bind:value={settings.federation}
on:input={submitSettings}
/>
</FormGroup>
{/await} {/await}
</Form> </Form>
</Container> </Container>