admin: Handle dashboard later publication through evdist

This commit is contained in:
nemunaire 2024-03-14 17:44:21 +01:00
parent 5592fabefa
commit 79afaa8fb2
4 changed files with 157 additions and 9 deletions

View File

@ -7,7 +7,9 @@ import (
"net/http"
"os"
"path"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
)
@ -15,6 +17,7 @@ import (
var DashboardDir string
func declarePublicRoutes(router *gin.RouterGroup) {
router.GET("/public/", listPublic)
router.GET("/public/:sid", getPublic)
router.DELETE("/public/:sid", deletePublic)
router.PUT("/public/:sid", savePublic)
@ -32,6 +35,11 @@ type FICPublicDisplay struct {
HideEvents bool `json:"hideEvents"`
HideCountdown bool `json:"hideCountdown"`
HideCarousel bool `json:"hideCarousel"`
PropagationTime *time.Time `json:"propagationTime,omitempty"`
}
func InitDashboardPresets(dir string) error {
return nil
}
func readPublic(path string) (FICPublicDisplay, error) {
@ -65,14 +73,62 @@ func savePublicTo(path string, s FICPublicDisplay) error {
}
}
type DashboardFiles struct {
Presets []string `json:"presets"`
Nexts []*NextDashboardFile `json:"nexts"`
}
type NextDashboardFile struct {
Name string `json:"name"`
Screen int `json:"screen"`
Date time.Time `json:"date"`
}
func listPublic(c *gin.Context) {
files, err := os.ReadDir(DashboardDir)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
var ret DashboardFiles
for _, file := range files {
if strings.HasPrefix(file.Name(), "preset-") {
ret.Presets = append(ret.Presets, strings.TrimSuffix(strings.TrimPrefix(file.Name(), "preset-"), ".json"))
continue
}
if !strings.HasPrefix(file.Name(), "public") || len(file.Name()) < 18 {
continue
}
ts, err := strconv.ParseInt(file.Name()[8:18], 10, 64)
if err == nil {
s, _ := strconv.Atoi(file.Name()[6:7])
ret.Nexts = append(ret.Nexts, &NextDashboardFile{
Name: file.Name()[6:18],
Screen: s,
Date: time.Unix(ts, 0),
})
}
}
c.JSON(http.StatusOK, ret)
}
func getPublic(c *gin.Context) {
if strings.Contains(c.Params.ByName("sid"), "/") {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "sid cannot contains /"})
return
}
if _, err := os.Stat(path.Join(DashboardDir, fmt.Sprintf("public%s.json", c.Params.ByName("sid")))); !os.IsNotExist(err) {
p, err := readPublic(path.Join(DashboardDir, fmt.Sprintf("public%s.json", c.Params.ByName("sid"))))
filename := fmt.Sprintf("public%s.json", c.Params.ByName("sid"))
if strings.HasPrefix(c.Params.ByName("sid"), "preset-") {
filename = fmt.Sprintf("%s.json", c.Params.ByName("sid"))
}
if _, err := os.Stat(path.Join(DashboardDir, filename)); !os.IsNotExist(err) {
p, err := readPublic(path.Join(DashboardDir, filename))
if err != nil {
log.Println("Unable to readPublic in getPublic:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during scene retrieval."})
@ -92,10 +148,23 @@ func deletePublic(c *gin.Context) {
return
}
if err := savePublicTo(path.Join(DashboardDir, fmt.Sprintf("public%s.json", c.Params.ByName("sid"))), FICPublicDisplay{}); err != nil {
log.Println("Unable to deletePublic:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during scene deletion."})
return
filename := fmt.Sprintf("public%s.json", c.Params.ByName("sid"))
if strings.HasPrefix(c.Params.ByName("sid"), "preset-") {
filename = fmt.Sprintf("%s.json", c.Params.ByName("sid"))
}
if len(filename) == 12 {
if err := savePublicTo(path.Join(DashboardDir, filename), FICPublicDisplay{}); err != nil {
log.Println("Unable to deletePublic:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during scene deletion."})
return
}
} else {
if err := os.Remove(path.Join(DashboardDir, filename)); err != nil {
log.Println("Unable to deletePublic:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during scene deletion."})
return
}
}
c.JSON(http.StatusOK, FICPublicDisplay{Scenes: []FICPublicScene{}, Side: []FICPublicScene{}})
@ -114,7 +183,20 @@ func savePublic(c *gin.Context) {
return
}
if err := savePublicTo(path.Join(DashboardDir, fmt.Sprintf("public%s.json", c.Params.ByName("sid"))), scenes); err != nil {
filename := fmt.Sprintf("public%s.json", c.Params.ByName("sid"))
if c.Request.URL.Query().Has("t") {
t, err := time.Parse(time.RFC3339, c.Request.URL.Query().Get("t"))
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
filename = fmt.Sprintf("public%s-%d.json", c.Params.ByName("sid"), t.Unix())
} else if c.Request.URL.Query().Has("p") {
filename = fmt.Sprintf("preset-%s.json", c.Request.URL.Query().Get("p"))
}
if err := savePublicTo(path.Join(DashboardDir, filename), scenes); err != nil {
log.Println("Unable to savePublicTo:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during scene saving."})
return

View File

@ -247,6 +247,11 @@ func main() {
api.ApplySettings(config)
}
// Initialize dashboard presets
if err = api.InitDashboardPresets(api.DashboardDir); err != nil {
log.Println("Unable to initialize dashboards presets:", err)
}
// Database connection
log.Println("Opening database...")
if err = fic.DBInit(*dsn); err != nil {

View File

@ -962,9 +962,12 @@ angular.module("FICApp")
})
.controller("PublicController", function($scope, $rootScope, $routeParams, $location, Scene, Theme, Teams, Exercice) {
$scope.propagationtime = null;
$scope.presetName = "";
$scope.screens = [0,1,2,3,4,5,6,7,8,9];
$scope.screenid = $routeParams.screenId;
$scope.display = Scene.get({ screenId: $routeParams.screenId });
$scope.listScenes = Scene.get();
$scope.themes = Theme.query();
$scope.teams = Teams.get();
@ -1221,6 +1224,32 @@ angular.module("FICApp")
}
}
$scope.loadFile = function(fname) {
$scope.display = Scene.get({ screenId: fname });
$scope.someUpdt = true;
};
$scope.deleteFile = function(fname) {
Scene.delete({ screenId: fname });
$scope.listScenes = Scene.get();
};
$scope.latePropagation = function() {
$scope.someUpdt = false;
var prms = Scene.update({ screenId: $scope.screenid, t: $scope.propagationTime }, $scope.display);
prms.$promise.then(function() {
$scope.addToast('success', 'Scene successfully planned!');
}, function(response) {
$scope.addToast('danger', 'An error occurs when planning scene:', response.data.errmsg);
});
};
$scope.savePreset = function() {
$scope.someUpdt = false;
var prms = Scene.update({ screenId: $scope.screenid, p: $scope.presetName }, $scope.display);
prms.$promise.then(function() {
$scope.addToast('success', 'Preset successfully saved!');
}, function(response) {
$scope.addToast('danger', 'An error occurs when saving preset:', response.data.errmsg);
});
};
$scope.saveScenes = function() {
$scope.someUpdt = false;
var prms = Scene.update({ screenId: $scope.screenid }, $scope.display);

View File

@ -4,7 +4,7 @@
<select class="custom-select" style="width: auto" id="screenid" ng-model="screenid" ng-options="k as 'Écran ' + v for (k, v) in screens" ng-change="chScreen()"></select>
<div class="float-right dropdown">
<button type="button" class="btn btn-secondary dropdown-toggle mr-sm-2" type="button" data-toggle="dropdown">
Charger scène
Scène prédéfinie
</button>
<div class="dropdown-menu">
<a class="dropdown-item" ng-click="presetScene('registration')">Enregistrement équipes</a>
@ -19,12 +19,44 @@
<a class="dropdown-item" ng-click="presetScene('freehintquarter')">Free Hint Quarter</a>
<a class="dropdown-item" ng-click="presetScene('happyhour')">Happy Hour</a>
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item" ng-repeat="preset in listScenes.presets" ng-click="loadFile('preset-' + preset)">
{{ preset }}
<button class="btn btn-sm btn-outline-danger" ng-click="deleteFile('preset-' + preset); $event.preventDefault();"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></button>
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" ng-click="clearScene()">Scène vide</a>
</div>
</div>
<button type="button" ng-click="addSide()" class="float-right btn btn-primary mr-sm-2"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> élm. côté</button>
<button type="button" ng-click="addScene()" class="float-right btn btn-primary mr-sm-2"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> élm. central</button>
<button type="submit" class="float-right btn mr-sm-2" ng-class="{'btn-success': someUpdt,'btn-secondary': !someUpdt}"><span class="glyphicon glyphicon-save" aria-hidden="true"></span> Publier cette scène</button>
<div class="float-right btn-group mr-sm-2">
<button type="submit" class="btn" ng-class="{'btn-success': someUpdt,'btn-secondary': !someUpdt}"><span class="glyphicon glyphicon-blackboard" aria-hidden="true"></span> Publier cette scène</button>
<button type="button" class="btn dropdown-toggle dropdown-toggle-split" ng-class="{'btn-success': someUpdt,'btn-secondary': !someUpdt}" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu">
<div class="px-3 py-1">
<div class="form-group">
<label for="public-propagation-time">Publier à :</label>
<input type="datetime-local" id="public-propagation-time" class="form-control" ng-model="propagationTime">
</div>
<button type="button" ng-disabled="!propagationTime" ng-click="latePropagation()" class="btn btn-primary w-100"><span class="glyphicon glyphicon-time" aria-hidden="true"></span> Publier plus tard</button>
</div>
<div class="dropdown-divider"></div>
<div class="px-3 py-1">
<div class="form-group">
<label for="public-preset-name">Sauvegarder comme :</label>
<input id="public-preset-name" class="form-control" placeholder="Scène de départ" ng-model="presetName">
</div>
<button type="button" ng-disabled="!presetName" ng-click="savePreset()" class="btn btn-primary w-100"><span class="glyphicon glyphicon-download-alt" aria-hidden="true"></span> Sauvegarder</button>
</div>
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item" ng-repeat="next in listScenes.nexts" ng-if="next.screen == screenid" ng-click="loadFile(next.name)">
{{ next.date | date:'dd/MM/yy HH:mm:ss' }}
<button class="btn btn-sm btn-outline-danger" ng-click="deleteFile(next.name); $event.preventDefault();"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></button>
</a>
</div>
</div>
</h2>
<div class="row">