Handle settings

This commit is contained in:
nemunaire 2022-10-05 20:16:53 +02:00
parent 8f64a349ec
commit 25a4d7be2c
7 changed files with 210 additions and 58 deletions

1
.gitignore vendored
View File

@ -2,6 +2,7 @@ actions
gongs gongs
reveil reveil
routines routines
settings.json
tracks tracks
vendor vendor
ui/build ui/build

View File

@ -1,16 +1,38 @@
package api package api
import ( import (
"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"
) )
func declareSettingsRoutes(cfg *config.Config, router *gin.RouterGroup) { func declareSettingsRoutes(cfg *config.Config, router *gin.RouterGroup) {
router.GET("/settings", func(c *gin.Context) { router.GET("/settings", 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)
}) })
router.PUT("/settings", func(c *gin.Context) { router.PUT("/settings", func(c *gin.Context) {
var config reveil.Settings
err := c.ShouldBindJSON(&config)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
err = reveil.SaveSettings(cfg.SettingsFile, config)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
c.JSON(http.StatusOK, config)
}) })
} }

View File

@ -10,6 +10,7 @@ func (c *Config) declareFlags() {
flag.StringVar(&c.BaseURL, "baseurl", c.BaseURL, "URL prepended to each URL") flag.StringVar(&c.BaseURL, "baseurl", c.BaseURL, "URL prepended to each URL")
flag.StringVar(&c.Bind, "bind", c.Bind, "Bind port/socket") flag.StringVar(&c.Bind, "bind", c.Bind, "Bind port/socket")
flag.StringVar(&c.DevProxy, "dev", c.DevProxy, "Use ui directory instead of embedded assets") flag.StringVar(&c.DevProxy, "dev", c.DevProxy, "Use ui directory instead of embedded assets")
flag.StringVar(&c.SettingsFile, "settings-file", c.SettingsFile, "Path to the file containing the settings")
flag.StringVar(&c.TracksDir, "tracks-dir", c.TracksDir, "Path to the directory containing the tracks") flag.StringVar(&c.TracksDir, "tracks-dir", c.TracksDir, "Path to the directory containing the tracks")
flag.StringVar(&c.GongsDir, "gongs-dir", c.GongsDir, "Path to the directory containing the gongs") flag.StringVar(&c.GongsDir, "gongs-dir", c.GongsDir, "Path to the directory containing the gongs")
flag.StringVar(&c.ActionsDir, "actions-dir", c.ActionsDir, "Path to the directory containing the actions") flag.StringVar(&c.ActionsDir, "actions-dir", c.ActionsDir, "Path to the directory containing the actions")
@ -21,11 +22,12 @@ func (c *Config) declareFlags() {
func Consolidated() (cfg *Config, err error) { func Consolidated() (cfg *Config, err error) {
// Define defaults options // Define defaults options
cfg = &Config{ cfg = &Config{
Bind: "127.0.0.1:8080", Bind: "127.0.0.1:8080",
TracksDir: "./tracks/", SettingsFile: "./settings.json",
GongsDir: "./gongs/", TracksDir: "./tracks/",
ActionsDir: "./actions/", GongsDir: "./gongs/",
RoutinesDir: "./routines/", ActionsDir: "./actions/",
RoutinesDir: "./routines/",
} }
cfg.declareFlags() cfg.declareFlags()

View File

@ -6,14 +6,15 @@ import (
) )
type Config struct { type Config struct {
DevProxy string DevProxy string
Bind string Bind string
ExternalURL URL ExternalURL URL
BaseURL string BaseURL string
TracksDir string SettingsFile string
GongsDir string TracksDir string
ActionsDir string GongsDir string
RoutinesDir string ActionsDir string
RoutinesDir string
} }
// parseLine treats a config line and place the read value in the variable // parseLine treats a config line and place the read value in the variable

54
model/settings.go Normal file
View File

@ -0,0 +1,54 @@
package reveil
import (
"encoding/json"
"os"
"time"
)
// Settings represents the settings panel.
type Settings struct {
Language string `json:"language"`
GongInterval time.Duration `json:"gong_interval"`
WeatherDelay time.Duration `json:"weather_delay"`
WeatherAction string `json:"weather_action"`
}
// ExistsSettings checks if the settings file can by found at the given path.
func ExistsSettings(settingsPath string) bool {
_, err := os.Stat(settingsPath)
return !os.IsNotExist(err)
}
// ReadSettings parses the file at the given location.
func ReadSettings(path string) (*Settings, error) {
var s Settings
if fd, err := os.Open(path); err != nil {
return nil, err
} else {
defer fd.Close()
jdec := json.NewDecoder(fd)
if err := jdec.Decode(&s); err != nil {
return &s, err
}
return &s, nil
}
}
// SaveSettings saves settings at the given location.
func SaveSettings(path string, s interface{}) error {
if fd, err := os.Create(path); err != nil {
return err
} else {
defer fd.Close()
jenc := json.NewEncoder(fd)
if err := jenc.Encode(s); err != nil {
return err
}
return nil
}
}

38
ui/src/lib/settings.js Normal file
View File

@ -0,0 +1,38 @@
export class Settings {
constructor(res) {
if (res) {
this.update(res);
}
}
update({ language, gong_interval, weather_delay, weather_action }) {
this.language = language;
this.gong_interval = gong_interval;
this.weather_delay = weather_delay;
this.weather_action = weather_action;
}
async save() {
const res = await fetch(`api/settings`, {
method: 'PUT',
headers: {'Accept': 'application/json'},
body: JSON.stringify(this),
});
if (res.status == 200) {
const data = await res.json();
this.update(data);
return data;
} else {
throw new Error((await res.json()).errmsg);
}
}
}
export async function getSettings() {
const res = await fetch(`api/settings`, {headers: {'Accept': 'application/json'}})
if (res.status == 200) {
return new Settings(await res.json());
} else {
throw new Error((await res.json()).errmsg);
}
}

View File

@ -8,7 +8,17 @@
InputGroup, InputGroup,
InputGroupText, InputGroupText,
Label, Label,
Spinner,
} from 'sveltestrap'; } from 'sveltestrap';
import { actions } from '../../stores/actions';
import { getSettings } from '../../lib/settings';
let settingsP = getSettings();
$: settingsP.then((s) => settings = s);
let settings;
</script> </script>
<Container class="flex-fill d-flex flex-column py-2"> <Container class="flex-fill d-flex flex-column py-2">
@ -16,51 +26,75 @@
Paramètres Paramètres
</h2> </h2>
<Form> <Form>
<FormGroup> {#await settingsP}
<Label for="gongIntervals">Intervalle entre les gongs</Label> <div class="d-flex justify-content-center align-items-center gap-2">
<InputGroup> <Spinner color="primary" /> Chargement en cours&hellip;
</div>
{:then}
<FormGroup>
<Label for="gongIntervals">Intervalle entre les gongs</Label>
<InputGroup>
<Input
type="number"
id="gongIntervals"
placeholder="20"
bind:value={settings.gong_interval}
on:change={() => settings.save()}
/>
<InputGroupText>min</InputGroupText>
</InputGroup>
</FormGroup>
<FormGroup>
<Label for="weatherDelay">Annonce météo après</Label>
<InputGroup>
<Input
type="number"
id="weatherDelay"
placeholder="5"
bind:value={settings.weather_delay}
on:change={() => settings.save()}
/>
<InputGroupText>min</InputGroupText>
</InputGroup>
</FormGroup>
<FormGroup>
<Label for="weatherRituel">Rituel pour l'annonce météo</Label>
{#if $actions.list}
<Input
type="select"
id="weatherRituel"
bind:value={settings.weather_action}
on:change={() => settings.save()}
>
{#each $actions.list as action (action.id)}
<option value="{action.path}">{action.name}</option>
{/each}
</Input>
{:else}
{#await actions.refresh()}
<div class="d-flex justify-content-center align-items-center gap-2">
<Spinner color="primary" /> Chargement en cours&hellip;
</div>
{/await}
{/if}
</FormGroup>
<FormGroup>
<Label for="greetingLanguage">Langue de salutation</Label>
<Input <Input
type="number" type="select"
id="gongIntervals" id="greetingLanguage"
placeholder="20" bind:value={settings.lang}
/> on:change={() => settings.save()}
<InputGroupText>min</InputGroupText> >
</InputGroup> <option value="fr_FR">Français</option>
</FormGroup> <option value="en_US">Anglais</option>
<option value="es_ES">Espagnol</option>
<FormGroup> <option value="de_DE">Allemand</option>
<Label for="weatherDelay">Annonce météo après</Label> </Input>
<InputGroup> </FormGroup>
<Input {/await}
type="number"
id="weatherDelay"
placeholder="5"
/>
<InputGroupText>min</InputGroupText>
</InputGroup>
</FormGroup>
<FormGroup>
<Label for="weatherRituel">Rituel pour l'annonce météo</Label>
<Input
type="select"
id="weatherRituel"
>
<option value="1">test</option>
</Input>
</FormGroup>
<FormGroup>
<Label for="greetingLanguage">Langue de salutation</Label>
<Input
type="select"
id="greetingLanguage"
>
<option value="fr_FR">Français</option>
<option value="en_US">Anglais</option>
<option value="es_ES">Espagnol</option>
<option value="de_DE">Allemand</option>
</Input>
</FormGroup>
</Form> </Form>
</Container> </Container>