vue.js migration commit - WIP

This commit is contained in:
Fabio Manganiello 2019-05-15 09:31:04 +02:00
parent 1ad86428c8
commit 8b478ede45
12 changed files with 13821 additions and 437 deletions

View file

@ -3,23 +3,22 @@
.. license: MIT
"""
import importlib
import logging
import sys
import threading
from threading import Thread
from platypush.bus import Bus
from platypush.config import Config
from platypush.context import get_backend, get_plugin
from platypush.utils import get_message_class_by_type, set_timeout, clear_timeout
from platypush.context import get_backend
from platypush.utils import set_timeout, clear_timeout, \
get_redis_queue_name_by_message, set_thread_name
from platypush.event import EventGenerator
from platypush.message import Message
from platypush.message.event import Event, StopEvent
from platypush.message.request import Request
from platypush.message.response import Response
from platypush.utils import get_redis_queue_name_by_message, set_thread_name
class Backend(Thread, EventGenerator):
@ -64,11 +63,10 @@ class Backend(Thread, EventGenerator):
else None
if 'logging' in kwargs:
self.logger.setLevel(getattr(logging, kwargs['logging'].upper()))
self.logger.setLevel(getattr(logging, kwargs.get('logging').upper()))
Thread.__init__(self)
def on_message(self, msg):
"""
Callback when a message is received on the backend.
@ -76,7 +74,8 @@ class Backend(Thread, EventGenerator):
It should be called by the derived classes whenever
a new message should be processed.
:param msg: Received message. It can be either a key-value dictionary, a platypush.message.Message object, or a string/byte UTF-8 encoded string
:param msg: Received message. It can be either a key-value dictionary, a platypush.message.Message object,
or a string/byte UTF-8 encoded string
"""
msg = Message.build(msg)

View file

@ -25,7 +25,9 @@ class HttpBackend(Backend):
}' \\
http://localhost:8008/execute
* To interact with your system (and control plugins and backends) through the Platypush web panel, by default available on your web root document. Any plugin that you have configured and available as a panel plugin will appear on the web panel as well as a tab.
* To interact with your system (and control plugins and backends) through the Platypush web panel,
by default available on your web root document. Any plugin that you have configured and available as a panel
plugin will appear on the web panel as well as a tab.
* To display a fullscreen dashboard with your configured widgets, by default available under ``/dashboard``
@ -69,9 +71,9 @@ class HttpBackend(Backend):
def __init__(self, port=_DEFAULT_HTTP_PORT,
websocket_port=_DEFAULT_WEBSOCKET_PORT,
disable_websocket=False, dashboard={}, resource_dirs={},
disable_websocket=False, dashboard=None, resource_dirs=None,
ssl_cert=None, ssl_key=None, ssl_cafile=None, ssl_capath=None,
maps={}, run_externally=False, uwsgi_args=None, **kwargs):
maps=None, run_externally=False, uwsgi_args=None, **kwargs):
"""
:param port: Listen port for the web server (default: 8008)
:type port: int
@ -88,10 +90,12 @@ class HttpBackend(Backend):
:param ssl_key: Set it to the path of your key file if you want to enable HTTPS (default: None)
:type ssl_key: str
:param ssl_cafile: Set it to the path of your certificate authority file if you want to enable HTTPS (default: None)
:param ssl_cafile: Set it to the path of your certificate authority file if you want to enable HTTPS
(default: None)
:type ssl_cafile: str
:param ssl_capath: Set it to the path of your certificate authority directory if you want to enable HTTPS (default: None)
:param ssl_capath: Set it to the path of your certificate authority directory if you want to enable HTTPS
(default: None)
:type ssl_capath: str
:param resource_dirs: Static resources directories that will be
@ -100,7 +104,8 @@ class HttpBackend(Backend):
the value is the absolute path to expose.
:type resource_dirs: dict[str, str]
:param dashboard: Set it if you want to use the dashboard service. It will contain the configuration for the widgets to be used (look under ``platypush/backend/http/templates/widgets/`` for the available widgets).
:param dashboard: Set it if you want to use the dashboard service. It will contain the configuration for the
widgets to be used (look under ``platypush/backend/http/templates/widgets/`` for the available widgets).
Example configuration::
@ -119,14 +124,15 @@ class HttpBackend(Backend):
-
widget: image-carousel # Image carousel
columns: 6
images_path: ~/Dropbox/Photos/carousel # Absolute path (valid as long as it's a subdirectory of one of the available `resource_dirs`)
# Absolute path (valid as long as it's a subdirectory of one of the available `resource_dirs`)
images_path: ~/Dropbox/Photos/carousel
refresh_seconds: 15
-
widget: rss-news # RSS feeds widget
# Requires backend.http.poll to be enabled with some RSS sources and write them to sqlite db
columns: 6
limit: 25
db: "sqlite:////home/blacklight/.local/share/platypush/feeds/rss.db"
db: "sqlite:////home/user/.local/share/platypush/feeds/rss.db"
:type dashboard: dict
@ -155,13 +161,18 @@ class HttpBackend(Backend):
self.port = port
self.websocket_port = websocket_port
self.dashboard = dashboard
self.maps = maps
self.dashboard = dashboard or {}
self.maps = maps or {}
self.server_proc = None
self.disable_websocket = disable_websocket
self.websocket_thread = None
self.resource_dirs = { name: os.path.abspath(
os.path.expanduser(d)) for name, d in resource_dirs.items() }
if resource_dirs:
self.resource_dirs = {name: os.path.abspath(
os.path.expanduser(d)) for name, d in resource_dirs.items()}
else:
self.resource_dirs = {}
self.active_websockets = set()
self.run_externally = run_externally
self.uwsgi_args = uwsgi_args or []
@ -175,11 +186,9 @@ class HttpBackend(Backend):
self.uwsgi_args = [str(_) for _ in self.uwsgi_args] + \
['--module', 'platypush.backend.http.uwsgi', '--enable-threads']
def send_message(self, msg):
def send_message(self, msg, **kwargs):
self.logger.warning('Use cURL or any HTTP client to query the HTTP backend')
def on_stop(self):
""" On backend stop """
self.logger.info('Received STOP event on HttpBackend')
@ -196,23 +205,22 @@ class HttpBackend(Backend):
""" Notify all the connected web clients (over websocket) of a new event """
import websockets
async def send_event(websocket):
async def send_event(ws):
try:
await websocket.send(str(event))
await ws.send(str(event))
except Exception as e:
self.logger.warning('Error on websocket send_event: {}'.format(e))
loop = get_or_create_event_loop()
websockets = self.active_websockets.copy()
for websocket in websockets:
wss = self.active_websockets.copy()
for websocket in wss:
try:
loop.run_until_complete(send_event(websocket))
except websockets.exceptions.ConnectionClosed:
self.logger.info('Client connection lost')
self.active_websockets.remove(websocket)
def websocket(self):
""" Websocket main server """
import websockets
@ -222,7 +230,7 @@ class HttpBackend(Backend):
address = websocket.remote_address[0] if websocket.remote_address \
else '<unknown client>'
self.logger.info('New websocket connection from {}'.format(address))
self.logger.info('New websocket connection from {} on path {}'.format(address, path))
self.active_websockets.add(websocket)
try:
@ -258,7 +266,6 @@ class HttpBackend(Backend):
return proc
def run(self):
super().run()
@ -274,8 +281,11 @@ class HttpBackend(Backend):
self.server_proc.join()
elif self.uwsgi_args:
uwsgi_cmd = ['uwsgi'] + self.uwsgi_args
self.logger.info
self.logger.info('Starting uWSGI with arguments {}'.format(uwsgi_cmd))
self.server_proc = subprocess.Popen(uwsgi_cmd)
else:
raise EnvironmentError('The web server is configured to be launched externally but ' +
'no uwsgi_args were provide')
# vim:sw=4:ts=4:et:

View file

@ -17,10 +17,12 @@ __routes__ = [
index,
]
@index.route('/')
def index():
""" Route to the main web panel """
if not authentication_ok(request): return authenticate()
if not authentication_ok(request):
return authenticate()
# These plugins have their own template file but won't be shown as a tab in
# the web panel. This is usually the case for plugins that only include JS
@ -34,7 +36,7 @@ def index():
hidden_plugins = {}
for plugin, conf in configured_plugins.items():
template_file = os.path.join('plugins', plugin + '.html')
template_file = os.path.join('panel', plugin, 'index.html')
if os.path.isfile(os.path.join(template_folder, template_file)):
if plugin in _hidden_plugins:
hidden_plugins[plugin] = conf

File diff suppressed because it is too large Load diff

View file

@ -1,372 +1,32 @@
$(document).ready(function() {
var websocket,
pendingConnection = false,
openedWebsocket,
dateTimeInterval,
websocketTimeoutId,
modalFadingInTimeoutId,
websocketReconnectMsecs = 30000,
eventListeners = [];
var initWebsocket = function() {
try {
url_prefix = window.has_ssl ? 'wss://' : 'ws://';
websocket = new WebSocket(url_prefix + window.location.hostname + ':' + window.websocket_port);
} catch (err) {
websocket = undefined;
return;
}
pendingConnection = true;
var onWebsocketTimeout = function(self) {
return function() {
console.log('Websocket reconnection timed out, retrying');
pendingConnection = false;
self.close();
self.onclose();
};
};
websocketTimeoutId = setTimeout(
onWebsocketTimeout(websocket), websocketReconnectMsecs);
websocket.onmessage = function(event) {
console.debug(event);
for (var listener of eventListeners) {
data = event.data;
if (typeof event.data === 'string') {
data = JSON.parse(data);
}
listener(data);
}
};
websocket.onopen = function(event) {
if (openedWebsocket) {
console.log("There's already an opened websocket connection, closing the newly opened one");
this.onclose = function() {};
this.close();
}
console.log('Websocket connection successful');
openedWebsocket = this;
if (pendingConnection) {
pendingConnection = false;
}
if (websocketTimeoutId) {
clearInterval(websocketTimeoutId);
websocketTimeoutId = undefined;
}
};
websocket.onerror = function(event) {
console.error(event);
};
websocket.onclose = function(event) {
if (event) {
console.log('Websocket closed - code: ' + event.code + ' - reason: ' + event.reason);
}
openedWebsocket = undefined;
if (!pendingConnection) {
pendingConnection = true;
initWebsocket();
}
};
};
var initDateTime = function() {
var getDateString = function(t) {
var s = '';
switch (t.getDay()) {
case 1: s += 'Mon '; break; case 2: s += 'Tue '; break; case 3: s += 'Wed '; break;
case 4: s += 'Thu '; break; case 5: s += 'Fri '; break; case 6: s += 'Sat '; break;
case 7: s += 'Sun '; break;
}
s += (t.getDate() < 10 ? '0' : '') + t.getDate() + ' ';
switch (t.getMonth()) {
case 0: s += 'Jan '; break; case 1: s += 'Feb '; break; case 2: s += 'Mar '; break;
case 3: s += 'Apr '; break; case 4: s += 'May '; break; case 5: s += 'Jun '; break;
case 6: s += 'Jul '; break; case 7: s += 'Ago '; break; case 8: s += 'Sep '; break;
case 9: s += 'Oct '; break; case 10: s += 'Nov '; break; case 11: s += 'Dec '; break;
}
s += t.getFullYear();
return s;
};
var setDateTime = function() {
var $dateTime = $('#date-time');
var $date = $dateTime.find('.date');
var $time = $dateTime.find('.time');
var now = new Date();
$date.text(getDateString(now));
$time.text((now.getHours() < 10 ? '0' : '') + now.getHours() + ':' +
(now.getMinutes() < 10 ? '0' : '') + now.getMinutes());
};
if (dateTimeInterval) {
clearInterval(dateTimeInterval);
}
setDateTime();
dateTimeInterval = setInterval(setDateTime, 1000);
};
var registerEventListener = function(listener) {
eventListeners.push(listener);
};
var initElements = function() {
var $tabItems = $('main').find('.plugin-tab-item');
var $tabContents = $('main').find('.plugin-tab-content');
// Set the active tag on the first plugin tab
$tabContents.removeClass('active');
$tabContents.first().addClass('active');
$tabItems.removeClass('active');
$tabItems.first().addClass('active');
$tabItems.on('click', function() {
var pluginId = $(this).attr('href').substr(1);
$tabContents.removeClass('active');
$tabContents.filter(function(i, content) {
return $(content).attr('id') === pluginId
}).addClass('active');
// Declaration of the main vue app
var app;
function ready(callback){
if (document.readyState!='loading') callback();
else if (document.addEventListener) document.addEventListener('DOMContentLoaded', callback);
else document.attachEvent('onreadystatechange', function(){
if (document.readyState=='complete') callback();
});
};
}
var initModalOpenBindings = function() {
$('body').on('mouseup touchend', '[data-modal]', function(event) {
var $source = $(this);
var $modal = $($source.data('modal'));
$modal.height($(document).height() + 2);
ready(function() {
app = new Vue({
el: '#app',
delimiters: ['[[',']]'],
data: {
config: {foo:"bar"}
},
var $container = $modal.find('.modal-container');
var top = 40 + $(window).scrollTop();
$container.css('margin-top', top + 'px');
created: function() {
},
modalFadingInTimeoutId = setTimeout(function() {
modalFadingInTimeoutId = undefined;
}, 100);
mounted: function() {
},
$modal.fadeIn();
updated: function() {
},
destroyed: function() {
},
});
};
var initModalCloseBindings = function() {
$('body').on('mouseup touchend', '[data-dismiss-modal]', function(event) {
var $source = $(this);
var $modal = $($source.data('dismiss-modal'));
$modal.fadeOut();
});
$('body').on('mouseup touchend', function(event) {
var $source = $(event.target);
if (!$source.parents('.modal').length
&& !$source.data('modal')
&& !$source.data('dismiss-modal')) {
if (!modalFadingInTimeoutId) {
$('.modal').filter(':visible').fadeOut();
}
}
});
};
var initPanelOpenBindings = function() {
$('body').on('mouseup touchend', '[data-panel]', function(event) {
var $source = $(this);
var $panel = $($source.data('panel'));
setTimeout(() => {
$panel.show();
}, 200);
});
};
var initPanelCloseBindings = function() {
$('body').on('mouseup touchend', function(event) {
var $source = $(event.target);
if ($source.data('panel') || $source.parents('[data-panel]').length) {
var $panel = $source.data('panel') ? $($source.data('panel')) :
$($source.parents('[data-panel]').data('panel'));
$panel.toggle();
return;
}
if (!$source.parents('.panel').length
&& !$source.data('panel')) {
$('.panel').filter(':visible').hide();
}
});
};
var initModals = function() {
initModalOpenBindings();
initModalCloseBindings();
};
var initPanels = function() {
initPanelOpenBindings();
initPanelCloseBindings();
};
var init = function() {
initWebsocket();
initElements();
initDateTime();
initModals();
initPanels();
};
window.registerEventListener = registerEventListener;
init();
});
function execute(request, onSuccess, onError, onComplete) {
request['target'] = 'localhost';
return $.ajax({
type: 'POST',
url: '/execute',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify(request),
complete: function() {
if (onComplete) {
onComplete();
}
},
error: function(xhr, status, error) {
if (onError) {
onError(xhr, status, error);
}
},
success: function(response, status, xhr) {
if ('errors' in response && response.errors.length) {
if (onError) {
onError(xhr, '500', response.errors);
}
}
if (onSuccess) {
onSuccess(response, status, xhr);
}
},
beforeSend: function(xhr) {
if (window.token) {
xhr.setRequestHeader('X-Token', window.token);
}
},
});
}
function run(request) {
request['type'] = 'request';
return new Promise((resolve, reject) => {
execute(request,
onSuccess = (response) => {
resolve(response);
},
onError = (xhr, status, error) => {
reject(xhr, status, error);
}
);
});
}
function createNotification(options) {
var $notificationContainer = $('#notification-container');
var $notification = $('<div></div>').addClass('notification');
var $title = $('<div></div>').addClass('notification-title');
var timeout = 'timeout' in options ? options.timeout : 10000;
if ('title' in options) {
$title.text(options.title);
}
var $body = $('<div></div>').addClass('notification-body');
var $imgDiv = $('<div></div>').addClass('notification-image').addClass('three columns');
var $img = $('<i></i>').addClass('fa fa-bell').addClass('three columns').addClass('notification-image-item');
var $text = $('<div></div>').addClass('notification-text').addClass('nine columns')
if ('image' in options) {
$img = $('<img></img>').attr('src', options.image);
} else if ('icon' in options) {
$img.removeClass('fa-bell').addClass('fa-' + options.icon);
}
if ('text' in options) {
$text.text(options.text);
} else if ('html' in options) {
$text.html(options.html);
}
$img.appendTo($imgDiv);
$imgDiv.appendTo($body);
$text.appendTo($body);
var clickHandler;
var removeTimeoutId;
var removeNotification = function() {
$notification.fadeOut(300, function() {
$notification.remove();
});
};
var setRemoveTimeout = function() {
if (timeout) {
removeTimeoutId = setTimeout(removeNotification, timeout);
}
};
setRemoveTimeout();
if ('onclick' in options) {
clickHandler = options.onclick;
} else {
clickHandler = removeNotification;
}
$notification.on('click', clickHandler);
$notification.hover(
// on hover start
function() {
if (removeTimeoutId) {
clearTimeout(removeTimeoutId);
removeTimeoutId = undefined;
}
},
// on hover end
function() {
if (timeout) {
setRemoveTimeout();
}
}
);
$title.appendTo($notification);
$body.appendTo($notification);
$notification.prependTo($notificationContainer);
$notification.fadeIn();
}
function showError(errorMessage) {
createNotification({
'icon': 'exclamation',
'text': errorMessage,
});
}

View file

@ -0,0 +1,372 @@
$(document).ready(function() {
var websocket,
pendingConnection = false,
openedWebsocket,
dateTimeInterval,
websocketTimeoutId,
modalFadingInTimeoutId,
websocketReconnectMsecs = 30000,
eventListeners = [];
var initWebsocket = function() {
try {
url_prefix = window.has_ssl ? 'wss://' : 'ws://';
websocket = new WebSocket(url_prefix + window.location.hostname + ':' + window.websocket_port);
} catch (err) {
websocket = undefined;
return;
}
pendingConnection = true;
var onWebsocketTimeout = function(self) {
return function() {
console.log('Websocket reconnection timed out, retrying');
pendingConnection = false;
self.close();
self.onclose();
};
};
websocketTimeoutId = setTimeout(
onWebsocketTimeout(websocket), websocketReconnectMsecs);
websocket.onmessage = function(event) {
console.debug(event);
for (var listener of eventListeners) {
data = event.data;
if (typeof event.data === 'string') {
data = JSON.parse(data);
}
listener(data);
}
};
websocket.onopen = function(event) {
if (openedWebsocket) {
console.log("There's already an opened websocket connection, closing the newly opened one");
this.onclose = function() {};
this.close();
}
console.log('Websocket connection successful');
openedWebsocket = this;
if (pendingConnection) {
pendingConnection = false;
}
if (websocketTimeoutId) {
clearInterval(websocketTimeoutId);
websocketTimeoutId = undefined;
}
};
websocket.onerror = function(event) {
console.error(event);
};
websocket.onclose = function(event) {
if (event) {
console.log('Websocket closed - code: ' + event.code + ' - reason: ' + event.reason);
}
openedWebsocket = undefined;
if (!pendingConnection) {
pendingConnection = true;
initWebsocket();
}
};
};
var initDateTime = function() {
var getDateString = function(t) {
var s = '';
switch (t.getDay()) {
case 1: s += 'Mon '; break; case 2: s += 'Tue '; break; case 3: s += 'Wed '; break;
case 4: s += 'Thu '; break; case 5: s += 'Fri '; break; case 6: s += 'Sat '; break;
case 7: s += 'Sun '; break;
}
s += (t.getDate() < 10 ? '0' : '') + t.getDate() + ' ';
switch (t.getMonth()) {
case 0: s += 'Jan '; break; case 1: s += 'Feb '; break; case 2: s += 'Mar '; break;
case 3: s += 'Apr '; break; case 4: s += 'May '; break; case 5: s += 'Jun '; break;
case 6: s += 'Jul '; break; case 7: s += 'Ago '; break; case 8: s += 'Sep '; break;
case 9: s += 'Oct '; break; case 10: s += 'Nov '; break; case 11: s += 'Dec '; break;
}
s += t.getFullYear();
return s;
};
var setDateTime = function() {
var $dateTime = $('#date-time');
var $date = $dateTime.find('.date');
var $time = $dateTime.find('.time');
var now = new Date();
$date.text(getDateString(now));
$time.text((now.getHours() < 10 ? '0' : '') + now.getHours() + ':' +
(now.getMinutes() < 10 ? '0' : '') + now.getMinutes());
};
if (dateTimeInterval) {
clearInterval(dateTimeInterval);
}
setDateTime();
dateTimeInterval = setInterval(setDateTime, 1000);
};
var registerEventListener = function(listener) {
eventListeners.push(listener);
};
var initElements = function() {
var $tabItems = $('main').find('.plugin-tab-item');
var $tabContents = $('main').find('.plugin-tab-content');
// Set the active tag on the first plugin tab
$tabContents.removeClass('active');
$tabContents.first().addClass('active');
$tabItems.removeClass('active');
$tabItems.first().addClass('active');
$tabItems.on('click', function() {
var pluginId = $(this).attr('href').substr(1);
$tabContents.removeClass('active');
$tabContents.filter(function(i, content) {
return $(content).attr('id') === pluginId
}).addClass('active');
});
};
var initModalOpenBindings = function() {
$('body').on('mouseup touchend', '[data-modal]', function(event) {
var $source = $(this);
var $modal = $($source.data('modal'));
$modal.height($(document).height() + 2);
var $container = $modal.find('.modal-container');
var top = 40 + $(window).scrollTop();
$container.css('margin-top', top + 'px');
modalFadingInTimeoutId = setTimeout(function() {
modalFadingInTimeoutId = undefined;
}, 100);
$modal.fadeIn();
});
};
var initModalCloseBindings = function() {
$('body').on('mouseup touchend', '[data-dismiss-modal]', function(event) {
var $source = $(this);
var $modal = $($source.data('dismiss-modal'));
$modal.fadeOut();
});
$('body').on('mouseup touchend', function(event) {
var $source = $(event.target);
if (!$source.parents('.modal').length
&& !$source.data('modal')
&& !$source.data('dismiss-modal')) {
if (!modalFadingInTimeoutId) {
$('.modal').filter(':visible').fadeOut();
}
}
});
};
var initPanelOpenBindings = function() {
$('body').on('mouseup touchend', '[data-panel]', function(event) {
var $source = $(this);
var $panel = $($source.data('panel'));
setTimeout(() => {
$panel.show();
}, 200);
});
};
var initPanelCloseBindings = function() {
$('body').on('mouseup touchend', function(event) {
var $source = $(event.target);
if ($source.data('panel') || $source.parents('[data-panel]').length) {
var $panel = $source.data('panel') ? $($source.data('panel')) :
$($source.parents('[data-panel]').data('panel'));
$panel.toggle();
return;
}
if (!$source.parents('.panel').length
&& !$source.data('panel')) {
$('.panel').filter(':visible').hide();
}
});
};
var initModals = function() {
initModalOpenBindings();
initModalCloseBindings();
};
var initPanels = function() {
initPanelOpenBindings();
initPanelCloseBindings();
};
var init = function() {
initWebsocket();
initElements();
initDateTime();
initModals();
initPanels();
};
window.registerEventListener = registerEventListener;
init();
});
function execute(request, onSuccess, onError, onComplete) {
request['target'] = 'localhost';
return $.ajax({
type: 'POST',
url: '/execute',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify(request),
complete: function() {
if (onComplete) {
onComplete();
}
},
error: function(xhr, status, error) {
if (onError) {
onError(xhr, status, error);
}
},
success: function(response, status, xhr) {
if ('errors' in response && response.errors.length) {
if (onError) {
onError(xhr, '500', response.errors);
}
}
if (onSuccess) {
onSuccess(response, status, xhr);
}
},
beforeSend: function(xhr) {
if (window.token) {
xhr.setRequestHeader('X-Token', window.token);
}
},
});
}
function run(request) {
request['type'] = 'request';
return new Promise((resolve, reject) => {
execute(request,
onSuccess = (response) => {
resolve(response);
},
onError = (xhr, status, error) => {
reject(xhr, status, error);
}
);
});
}
function createNotification(options) {
var $notificationContainer = $('#notification-container');
var $notification = $('<div></div>').addClass('notification');
var $title = $('<div></div>').addClass('notification-title');
var timeout = 'timeout' in options ? options.timeout : 10000;
if ('title' in options) {
$title.text(options.title);
}
var $body = $('<div></div>').addClass('notification-body');
var $imgDiv = $('<div></div>').addClass('notification-image').addClass('three columns');
var $img = $('<i></i>').addClass('fa fa-bell').addClass('three columns').addClass('notification-image-item');
var $text = $('<div></div>').addClass('notification-text').addClass('nine columns')
if ('image' in options) {
$img = $('<img></img>').attr('src', options.image);
} else if ('icon' in options) {
$img.removeClass('fa-bell').addClass('fa-' + options.icon);
}
if ('text' in options) {
$text.text(options.text);
} else if ('html' in options) {
$text.html(options.html);
}
$img.appendTo($imgDiv);
$imgDiv.appendTo($body);
$text.appendTo($body);
var clickHandler;
var removeTimeoutId;
var removeNotification = function() {
$notification.fadeOut(300, function() {
$notification.remove();
});
};
var setRemoveTimeout = function() {
if (timeout) {
removeTimeoutId = setTimeout(removeNotification, timeout);
}
};
setRemoveTimeout();
if ('onclick' in options) {
clickHandler = options.onclick;
} else {
clickHandler = removeNotification;
}
$notification.on('click', clickHandler);
$notification.hover(
// on hover start
function() {
if (removeTimeoutId) {
clearTimeout(removeTimeoutId);
removeTimeoutId = undefined;
}
},
// on hover end
function() {
if (timeout) {
setRemoveTimeout();
}
}
);
$title.appendTo($notification);
$body.appendTo($notification);
$notification.prependTo($notificationContainer);
$notification.fadeIn();
}
function showError(errorMessage) {
createNotification({
'icon': 'exclamation',
'text': errorMessage,
});
}

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -3,26 +3,36 @@
<title>Platypush Web Console</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/skeleton.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/skeleton-tabs.css') }}">
<!-- <link rel="stylesheet" href="{{ url_for('static', filename='css/skeleton-tabs.css') }}"> -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/normalize.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='font-awesome/css/font-awesome.min.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/application.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/toggles.css') }}">
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css">
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery-3.3.1.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery-ui-1.12.1.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/skeleton-tabs.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/vue.js') }}"></script>
<!--<script type="text/javascript" src="{{ url_for('static', filename='js/vue.min.js') }}"></script>-->
<!-- <script type="text/javascript" src="{{ url_for('static', filename='js/jquery-3.3.1.min.js') }}"></script> -->
<!-- <script type="text/javascript" src="{{ url_for('static', filename='js/jquery-ui-1.12.1.min.js') }}"></script> -->
<!-- <script type="text/javascript" src="{{ url_for('static', filename='js/skeleton-tabs.js') }}"></script> -->
<script type="text/javascript" src="{{ url_for('static', filename='js/application.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/pushbullet.js') }}"></script>
<!-- <script type="text/javascript" src="{{ url_for('static', filename='js/pushbullet.js') }}"></script> -->
<script type="text/javascript">
window.websocket_port = {% print(websocket_port) %}
window.has_ssl = {% print('true' if has_ssl else 'false') %};
if (!window.config) {
window.config = {};
}
window.config = { ...window.config,
websocket_port: {% print(websocket_port) %},
has_ssl: {% print('true' if has_ssl else 'false') %},
plugins: JSON.parse('{% print(utils.to_json(plugins))|safe %}'),
};
{% if token %}
window.token = '{% print(token) %}'
window.config.token = '{% print(token) %}';
{% else %}
window.token = undefined
window.config.token = undefined;
{% endif %}
</script>
</head>
@ -42,37 +52,24 @@
</div>
</header>
<main>
<ul class="tab-nav">
<nav>
{% for plugin in plugins.keys()|sort() %}
<li>
<a class="button plugin-tab-item" href="#{% print plugin %}">
<a href="#{% print plugin %}">
{% print plugin %}
</a>
</li>
{% endfor %}
</ul>
</nav>
<div class="tab-content">
<main>
<div id="app">
{% for plugin in plugins.keys()|sort() %}
{% with configuration=plugins[plugin], utils=utils %}
<div class="tab-pane plugin-tab-content" id="{% print plugin %}">
{% include 'plugins/' + plugin + '.html' %}
<div class="tab-pane plugin-tab-content" id="{% print plugin %}-container">
{% include 'panel/' + plugin + '/index.html' %}
</div>
{% endwith %}
{% endfor %}
</div>
<div id="notification-container"></div>
<div id="hidden-plugins-container">
{% for plugin in hidden_plugins.keys()|sort() %}
{% set configuration = plugins[plugin] %}
<div class="plugin" id="{% print plugin %}">
{% include 'plugins/' + plugin + '.html' %}
</div>
{% endfor %}
</div>
</main>
<body>
</body>

View file

@ -0,0 +1,78 @@
<!doctype html>
<head>
<title>Platypush Web Console</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/skeleton.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/skeleton-tabs.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/normalize.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='font-awesome/css/font-awesome.min.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/application.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/toggles.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/jquery-ui.css') }}">
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery-3.3.1.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery-ui-1.12.1.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/skeleton-tabs.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/application.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/pushbullet.js') }}"></script>
<script type="text/javascript">
window.websocket_port = {% print(websocket_port) %};
window.has_ssl = {% print('true' if has_ssl else 'false') %};
{% if token %}
window.token = '{% print(token) %}'
{% else %}
window.token = undefined
{% endif %}
</script>
</head>
<body>
<header>
<div class="row">
<div class="logo nine columns">
<span class="logo-1">Platypush</span>
<span class="logo-2">Web Panel</span>
</div>
<div id="date-time" class="three columns">
<div class="date"></div>
<div class="time"></div>
</div>
</div>
</header>
<main>
<ul class="tab-nav">
{% for plugin in plugins.keys()|sort() %}
<li>
<a class="button plugin-tab-item" href="#{% print plugin %}">
{% print plugin %}
</a>
</li>
{% endfor %}
</ul>
<div class="tab-content">
{% for plugin in plugins.keys()|sort() %}
{% with configuration=plugins[plugin], utils=utils %}
<div class="tab-pane plugin-tab-content" id="{% print plugin %}">
{% include 'plugins/' + plugin + '.html' %}
</div>
{% endwith %}
{% endfor %}
</div>
<div id="notification-container"></div>
<div id="hidden-plugins-container">
{% for plugin in hidden_plugins.keys()|sort() %}
{% set configuration = plugins[plugin] %}
<div class="plugin" id="{% print plugin %}">
{% include 'plugins/' + plugin + '.html' %}
</div>
{% endfor %}
</div>
</main>
</body>

View file

@ -0,0 +1,2 @@
IT WORKED!

View file

@ -83,6 +83,9 @@ class HttpUtils(object):
@classmethod
def to_json(cls, data):
if isinstance(data, type({}.keys())):
# Convert dict_keys to list before serializing
data = list(data)
return json.dumps(data)
@classmethod