#144 Notification toast when updating settings (and other things)

This commit is contained in:
vexorian 2021-02-21 00:33:25 -04:00
parent d6b2bd1d5e
commit 87b6bb6d85
13 changed files with 579 additions and 10 deletions

View File

@ -21,6 +21,7 @@ const ChannelDB = require("./src/dao/channel-db");
const M3uService = require("./src/services/m3u-service");
const FillerDB = require("./src/dao/filler-db");
const TVGuideService = require("./src/tv-guide-service");
const EventService = require("./src/services/event-service");
const onShutdown = require("node-graceful-shutdown").onShutdown;
console.log(
@ -76,6 +77,7 @@ db.connect(process.env.DATABASE, ['channels', 'plex-servers', 'ffmpeg-settings',
fileCache = new FileCacheService( path.join(process.env.DATABASE, 'cache') );
cacheImageService = new CacheImageService(db, fileCache);
m3uService = new M3uService(channelDB, fileCache, channelCache)
eventService = new EventService();
initDB(db, channelDB)
@ -165,6 +167,8 @@ xmltvInterval.startInterval()
let hdhr = HDHR(db, channelDB)
let app = express()
eventService.setup(app);
app.use(fileUpload({
createParentPath: true
}));
@ -197,7 +201,7 @@ app.use('/favicon.svg', express.static(
) );
// API Routers
app.use(api.router(db, channelDB, fillerDB, xmltvInterval, guideService, m3uService ))
app.use(api.router(db, channelDB, fillerDB, xmltvInterval, guideService, m3uService, eventService ))
app.use('/api/cache/images', cacheImageService.apiRouters())
app.use(video.router( channelDB, fillerDB, db))
@ -242,8 +246,49 @@ function initDB(db, channelDB) {
}
function _wait(t) {
return new Promise((resolve) => {
setTimeout(resolve, t);
});
}
async function sendEventAfterTime() {
let t = (new Date()).getTime();
await _wait(20000);
eventService.push(
"lifecycle",
{
"message": `Server Started`,
"detail" : {
"time": t,
},
"level" : "success"
}
);
}
sendEventAfterTime();
onShutdown("log" , [], async() => {
let t = (new Date()).getTime();
eventService.push(
"lifecycle",
{
"message": `Initiated Server Shutdown`,
"detail" : {
"time": t,
},
"level" : "warning"
}
);
console.log("Received exit signal, attempting graceful shutdonw...");
await _wait(2000);
});
onShutdown("xmltv-writer" , [], async() => {
await xmltv.shutdown();

View File

@ -12,9 +12,20 @@ const Plex = require("./plex.js");
const timeSlotsService = require('./services/time-slots-service');
const randomSlotsService = require('./services/random-slots-service');
function safeString(object) {
let o = object;
for(let i = 1; i < arguments.length; i++) {
o = o[arguments[i]];
if (typeof(o) === 'undefined') {
return "missing";
}
}
return String(o);
}
module.exports = { router: api }
function api(db, channelDB, fillerDB, xmltvInterval, guideService, _m3uService ) {
const m3uService = _m3uService;
function api(db, channelDB, fillerDB, xmltvInterval, guideService, _m3uService, eventService ) {
let m3uService = _m3uService;
const router = express.Router()
const plexServerDB = new PlexServerDB(channelDB, channelCache, db);
@ -89,34 +100,113 @@ function api(db, channelDB, fillerDB, xmltvInterval, guideService, _m3uService
}
})
router.delete('/api/plex-servers', async (req, res) => {
let name = "unknown";
try {
let name = req.body.name;
name = req.body.name;
if (typeof(name) === 'undefined') {
return res.status(400).send("Missing name");
}
let report = await plexServerDB.deleteServer(name);
res.send(report)
eventService.push(
"settings-update",
{
"message": `Plex server ${name} removed.`,
"module" : "plex-server",
"detail" : {
"serverName" : name,
"action" : "delete"
},
"level" : "warn"
}
);
} catch(err) {
console.error(err);
res.status(500).send("error");
eventService.push(
"settings-update",
{
"message": "Error deleting plex server.",
"module" : "plex-server",
"detail" : {
"action": "delete",
"serverName" : name,
"error" : safeString(err, "message"),
},
"level" : "danger"
}
);
}
})
router.post('/api/plex-servers', async (req, res) => {
try {
await plexServerDB.updateServer(req.body);
res.status(204).send("Plex server updated.");;
eventService.push(
"settings-update",
{
"message": `Plex server ${req.body.name} updated.`,
"module" : "plex-server",
"detail" : {
"serverName" : req.body.name,
"action" : "update"
},
"level" : "info"
}
);
} catch (err) {
console.error("Could not add plex server.", err);
console.error("Could not update plex server.", err);
res.status(400).send("Could not add plex server.");
eventService.push(
"settings-update",
{
"message": "Error updating plex server.",
"module" : "plex-server",
"detail" : {
"action": "update",
"serverName" : safeString(req, "body", "name"),
"error" : safeString(err, "message"),
},
"level" : "danger"
}
);
}
})
router.put('/api/plex-servers', async (req, res) => {
try {
await plexServerDB.addServer(req.body);
res.status(201).send("Plex server added.");;
eventService.push(
"settings-update",
{
"message": `Plex server ${req.body.name} added.`,
"module" : "plex-server",
"detail" : {
"serverName" : req.body.name,
"action" : "add"
},
"level" : "info"
}
);
} catch (err) {
console.error("Could not add plex server.", err);
res.status(400).send("Could not add plex server.");
eventService.push(
"settings-update",
{
"message": "Error adding plex server.",
"module" : "plex-server",
"detail" : {
"action": "add",
"serverName" : safeString(req, "body", "name"),
"error" : safeString(err, "message"),
},
"level" : "danger"
}
);
}
})
@ -341,10 +431,34 @@ function api(db, channelDB, fillerDB, xmltvInterval, guideService, _m3uService
if (typeof(err) !== 'undefined') {
return res.status(400).send(err);
}
eventService.push(
"settings-update",
{
"message": "FFMPEG configuration updated.",
"module" : "ffmpeg",
"detail" : {
"action" : "update"
},
"level" : "info"
}
);
res.send(ffmpeg)
} catch(err) {
console.error(err);
res.status(500).send("error");
eventService.push(
"settings-update",
{
"message": "Error updating FFMPEG configuration.",
"module" : "ffmpeg",
"detail" : {
"action": "update",
"error" : safeString(err, "message"),
},
"level" : "danger"
}
);
}
})
router.post('/api/ffmpeg-settings', (req, res) => { // RESET
@ -353,10 +467,34 @@ function api(db, channelDB, fillerDB, xmltvInterval, guideService, _m3uService
ffmpeg.ffmpegPath = req.body.ffmpegPath;
db['ffmpeg-settings'].update({ _id: req.body._id }, ffmpeg)
ffmpeg = db['ffmpeg-settings'].find()[0]
eventService.push(
"settings-update",
{
"message": "FFMPEG configuration reset.",
"module" : "ffmpeg",
"detail" : {
"action" : "reset"
},
"level" : "warning"
}
);
res.send(ffmpeg)
} catch(err) {
console.error(err);
res.status(500).send("error");
eventService.push(
"settings-update",
{
"message": "Error reseting FFMPEG configuration.",
"module" : "ffmpeg",
"detail" : {
"action": "reset",
"error" : safeString(err, "message"),
},
"level" : "danger"
}
);
}
})
@ -384,9 +522,34 @@ function api(db, channelDB, fillerDB, xmltvInterval, guideService, _m3uService
db['plex-settings'].update({ _id: req.body._id }, req.body)
let plex = db['plex-settings'].find()[0]
res.send(plex)
eventService.push(
"settings-update",
{
"message": "Plex configuration updated.",
"module" : "plex",
"detail" : {
"action" : "update"
},
"level" : "info"
}
);
} catch(err) {
console.error(err);
res.status(500).send("error");
eventService.push(
"settings-update",
{
"message": "Error updating Plex configuration",
"module" : "plex",
"detail" : {
"action": "update",
"error" : safeString(err, "message"),
},
"level" : "danger"
}
);
}
})
@ -415,9 +578,35 @@ function api(db, channelDB, fillerDB, xmltvInterval, guideService, _m3uService
})
let plex = db['plex-settings'].find()[0]
res.send(plex)
eventService.push(
"settings-update",
{
"message": "Plex configuration reset.",
"module" : "plex",
"detail" : {
"action" : "reset"
},
"level" : "warning"
}
);
} catch(err) {
console.error(err);
res.status(500).send("error");
eventService.push(
"settings-update",
{
"message": "Error reseting Plex configuration",
"module" : "plex",
"detail" : {
"action": "reset",
"error" : safeString(err, "message"),
},
"level" : "danger"
}
);
}
})
@ -458,10 +647,35 @@ function api(db, channelDB, fillerDB, xmltvInterval, guideService, _m3uService
);
xmltv = db['xmltv-settings'].find()[0]
res.send(xmltv)
eventService.push(
"settings-update",
{
"message": "xmltv settings updated.",
"module" : "xmltv",
"detail" : {
"action" : "update"
},
"level" : "info"
}
);
updateXmltv()
} catch(err) {
console.error(err);
res.status(500).send("error");
eventService.push(
"settings-update",
{
"message": "Error updating xmltv configuration",
"module" : "xmltv",
"detail" : {
"action": "update",
"error" : safeString(err, "message"),
},
"level" : "danger"
}
);
}
})
@ -475,10 +689,35 @@ function api(db, channelDB, fillerDB, xmltvInterval, guideService, _m3uService
})
var xmltv = db['xmltv-settings'].find()[0]
res.send(xmltv)
eventService.push(
"settings-update",
{
"message": "xmltv settings reset.",
"module" : "xmltv",
"detail" : {
"action" : "reset"
},
"level" : "warning"
}
);
updateXmltv()
} catch(err) {
console.error(err);
res.status(500).send("error");
eventService.push(
"settings-update",
{
"message": "Error reseting xmltv configuration",
"module" : "xmltv",
"detail" : {
"action": "reset",
"error" : safeString(err, "message"),
},
"level" : "danger"
}
);
}
})
@ -537,9 +776,34 @@ function api(db, channelDB, fillerDB, xmltvInterval, guideService, _m3uService
db['hdhr-settings'].update({ _id: req.body._id }, req.body)
let hdhr = db['hdhr-settings'].find()[0]
res.send(hdhr)
eventService.push(
"settings-update",
{
"message": "HDHR configuration updated.",
"module" : "hdhr",
"detail" : {
"action" : "update"
},
"level" : "info"
}
);
} catch(err) {
console.error(err);
res.status(500).send("error");
eventService.push(
"settings-update",
{
"message": "Error updating HDHR configuration",
"module" : "hdhr",
"detail" : {
"action": "action",
"error" : safeString(err, "message"),
},
"level" : "danger"
}
);
}
})
@ -552,9 +816,34 @@ function api(db, channelDB, fillerDB, xmltvInterval, guideService, _m3uService
})
var hdhr = db['hdhr-settings'].find()[0]
res.send(hdhr)
eventService.push(
"settings-update",
{
"message": "HDHR configuration reset.",
"module" : "hdhr",
"detail" : {
"action" : "reset"
},
"level" : "warning"
}
);
} catch(err) {
console.error(err);
res.status(500).send("error");
eventService.push(
"settings-update",
{
"message": "Error reseting HDHR configuration",
"module" : "hdhr",
"detail" : {
"action": "reset",
"error" : safeString(err, "message"),
},
"level" : "danger"
}
);
}
})

View File

@ -0,0 +1,47 @@
const EventEmitter = require("events");
class EventsService {
constructor() {
this.stream = new EventEmitter();
let that = this;
let fun = () => {
that.push( "heartbeat", "{}");
setTimeout(fun, 5000)
};
fun();
}
setup(app) {
app.get("/api/events", (request, response) => {
console.log("Open event channel.");
response.writeHead(200, {
"Content-Type" : "text/event-stream",
"Cache-Control" : "no-cache",
"connection" : "keep-alive",
} );
let listener = (event,data) => {
//console.log( String(event) + " " + JSON.stringify(data) );
response.write("event: " + String(event) + "\ndata: "
+ JSON.stringify(data) + "\nretry: 5000\n\n" );
};
this.stream.on("push", listener );
response.on( "close", () => {
console.log("Remove event channel.");
this.stream.removeListener("push", listener);
} );
} );
}
push(event, data) {
if (typeof(data.message) !== 'undefined') {
console.log("Push event: " + data.message );
}
this.stream.emit("push", event, data );
}
}
module.exports = EventsService;

View File

@ -7,7 +7,7 @@ class TVGuideService
/****
*
**/
constructor(xmltv, db, cacheImageService) {
constructor(xmltv, db, cacheImageService, eventService) {
this.cached = null;
this.lastUpdate = 0;
this.updateTime = 0;
@ -19,6 +19,7 @@ class TVGuideService
this.xmltv = xmltv;
this.db = db;
this.cacheImageService = cacheImageService;
this.eventService = eventService;
}
async get() {
@ -44,6 +45,19 @@ class TVGuideService
this.currentUpdate = this.updateTime;
this.currentLimit = this.updateLimit;
this.currentChannels = this.updateChannels;
let t = "" + ( (new Date()) );
eventService.push(
"xmltv",
{
"message": `Started building tv-guide at = ${t}`,
"module" : "xmltv",
"detail" : {
"time": new Date(),
},
"level" : "info"
}
);
await this.buildIt();
}
await _wait(100);
@ -353,6 +367,19 @@ class TVGuideService
async refreshXML() {
let xmltvSettings = this.db['xmltv-settings'].find()[0];
await this.xmltv.WriteXMLTV(this.cached, xmltvSettings, async() => await this._throttle(), this.cacheImageService);
let t = "" + ( (new Date()) );
eventService.push(
"xmltv",
{
"message": `XMLTV updated at server time = ${t}`,
"module" : "xmltv",
"detail" : {
"time": new Date(),
},
"level" : "info"
}
);
}
async getStatus() {

View File

@ -19,6 +19,7 @@ app.directive('plexLibrary', require('./directives/plex-library'))
app.directive('programConfig', require('./directives/program-config'))
app.directive('flexConfig', require('./directives/flex-config'))
app.directive('timeSlotsTimeEditor', require('./directives/time-slots-time-editor'))
app.directive('toastNotifications', require('./directives/toast-notifications'))
app.directive('fillerConfig', require('./directives/filler-config'))
app.directive('deleteFiller', require('./directives/delete-filler'))
app.directive('frequencyTweak', require('./directives/frequency-tweak'))

View File

@ -0,0 +1,116 @@
module.exports = function ($timeout) {
return {
restrict: 'E',
templateUrl: 'templates/toast-notifications.html',
replace: true,
scope: {
},
link: function (scope, element, attrs) {
const FADE_IN_START = 100;
const FADE_IN_END = 1000;
const FADE_OUT_START = 10000;
const TOTAL_DURATION = 11000;
scope.toasts = [];
let eventSource = null;
let timerHandle = null;
let refreshHandle = null;
let setResetTimer = () => {
if (timerHandle != null) {
clearTimeout( timerHandle );
}
timerHandle = setTimeout( () => {
scope.setup();
} , 10000);
};
let updateAfter = (wait) => {
if (refreshHandle != null) {
$timeout.cancel( refreshHandle );
}
refreshHandle = $timeout( ()=> updater(), wait );
};
let updater = () => {
let wait = 10000;
let updatedToasts = [];
try {
let t = (new Date()).getTime();
for (let i = 0; i < scope.toasts.length; i++) {
let toast = scope.toasts[i];
let diff = t - toast.time;
if (diff < TOTAL_DURATION) {
if (diff < FADE_IN_START) {
toast.clazz = { "about-to-fade-in" : true }
wait = Math.min( wait, FADE_IN_START - diff );
} else if (diff < FADE_IN_END) {
toast.clazz = { "fade-in" : true }
wait = Math.min( wait, FADE_IN_END - diff );
} else if (diff < FADE_OUT_START) {
toast.clazz = {}
wait = Math.min( wait, FADE_OUT_START - diff );
} else {
toast.clazz = { "fade-out" : true }
wait = Math.min( wait, TOTAL_DURATION - diff );
}
toast.clazz[toast.deco] = true;
updatedToasts.push(toast);
}
}
} catch (err) {
console.error("error", err);
}
scope.toasts = updatedToasts;
updateAfter(wait);
};
let addToast = (toast) => {
toast.time = (new Date()).getTime();
toast.clazz= { "about-to-fade-in": true };
toast.clazz[toast.deco] = true;
scope.toasts.push(toast);
$timeout( () => updateAfter(0) );
};
let getDeco = (data) => {
return "bg-" + data.level;
}
scope.setup = () => {
if (eventSource != null) {
eventSource.close();
eventSource = null;
}
setResetTimer();
eventSource = new EventSource("api/events");
eventSource.addEventListener("heartbeat", () => {
setResetTimer();
} );
let normalEvent = (title) => {
return (event) => {
let data = JSON.parse(event.data);
addToast ( {
title : title,
text : data.message,
deco: getDeco(data)
} )
};
};
eventSource.addEventListener('settings-update', normalEvent("Settings Update") );
eventSource.addEventListener('xmltv', normalEvent("TV Guide") );
eventSource.addEventListener('lifecycle', normalEvent("Server") );
};
scope.setup();
}
};
}

View File

@ -43,6 +43,7 @@
</span>
<hr/>
<div ng-view></div>
<toast-notifications></toast-notifications>
</div>
</body>

View File

@ -357,6 +357,38 @@ div.programming-programs div.list-group-item {
background : rgba(255,255,255, 0.1);
}
.dizque-toast {
margin-top: 0.2rem;
padding: 0.5rem;
background: #FFFFFF;
border: 1px solid rgba(0,0,0,.1);
border-radius: .25rem;
color: #FFFFFF;
}
.dizque-toast.bg-warning {
color: black
}
.about-to-fade-in {
opacity: 0.00;
transition: opacity 1.00s ease-in-out;
-moz-transition: opacity 1.00s ease-in-out;
-webkit-transition: opacity 1.00s ease-in-out;
}
.fade-in {
opacity: 0.95;
transition: opacity 1.00s ease-in-out;
-moz-transition: opacity 1.00s ease-in-out;
-webkit-transition: opacity 1.00s ease-in-out;
}
.fade-out {
transition: opacity 1.00s ease-in-out;
-moz-transition: opacity 1.00s ease-in-out;
-webkit-transition: opacity 1.00s ease-in-out;
opacity: 0.0;
}
#dizquetv-logo {
width: 1em;

View File

@ -4,7 +4,7 @@
<button class="pull-right btn btn-sm btn-success" style="margin-left: 5px;" ng-click="updateSettings(settings)">
Update
</button>
<button class="pull-right btn btn-sm btn-info" ng-click="resetSettings(settings)">
<button class="pull-right btn btn-sm btn-warning" ng-click="resetSettings(settings)">
Reset Options
</button>
</h5>

View File

@ -3,7 +3,7 @@
<button class="pull-right btn btn-sm btn-success" style="margin-left: 5px;" ng-click="updateSettings(settings)">
Update
</button>
<button class="pull-right btn btn-sm btn-info" ng-click="resetSettings(settings)">
<button class="pull-right btn btn-sm btn-warning" ng-click="resetSettings(settings)">
Reset Options
</button>
</h5>

View File

@ -70,7 +70,7 @@
<button class="pull-right btn btn-sm btn-success" style="margin-left: 5px;" ng-click="updateSettings(settings)">
Update
</button>
<button class="pull-right btn btn-sm btn-info" ng-click="resetSettings(settings)">
<button class="pull-right btn btn-sm btn-warning" ng-click="resetSettings(settings)">
Reset Options
</button>
</h6>

View File

@ -0,0 +1,11 @@
<div style='position: fixed; top: 30px; right: 30px; width:400px; z-index: 1000000;'>
<div
ng-repeat="toast in toasts track by $index"
class="dizque-toast"
ng-class="toast.clazz"
>
<strong>{{ toast.title }}</strong>
<div>{{ toast.text }}</div>
</div>
</div>

View File

@ -4,7 +4,7 @@
<button class="pull-right btn btn-sm btn-success" style="margin-left: 5px;" ng-click="updateSettings(settings)">
Update
</button>
<button class="pull-right btn btn-sm btn-info" ng-click="resetSettings(settings)">
<button class="pull-right btn btn-sm btn-warning" ng-click="resetSettings(settings)">
Reset Options
</button>
</h5>