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 .. license: MIT
""" """
import importlib
import logging import logging
import sys
import threading import threading
from threading import Thread from threading import Thread
from platypush.bus import Bus from platypush.bus import Bus
from platypush.config import Config from platypush.config import Config
from platypush.context import get_backend, get_plugin from platypush.context import get_backend
from platypush.utils import get_message_class_by_type, set_timeout, clear_timeout from platypush.utils import set_timeout, clear_timeout, \
get_redis_queue_name_by_message, set_thread_name
from platypush.event import EventGenerator from platypush.event import EventGenerator
from platypush.message import Message from platypush.message import Message
from platypush.message.event import Event, StopEvent from platypush.message.event import Event, StopEvent
from platypush.message.request import Request from platypush.message.request import Request
from platypush.message.response import Response from platypush.message.response import Response
from platypush.utils import get_redis_queue_name_by_message, set_thread_name
class Backend(Thread, EventGenerator): class Backend(Thread, EventGenerator):
@ -64,11 +63,10 @@ class Backend(Thread, EventGenerator):
else None else None
if 'logging' in kwargs: if 'logging' in kwargs:
self.logger.setLevel(getattr(logging, kwargs['logging'].upper())) self.logger.setLevel(getattr(logging, kwargs.get('logging').upper()))
Thread.__init__(self) Thread.__init__(self)
def on_message(self, msg): def on_message(self, msg):
""" """
Callback when a message is received on the backend. 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 It should be called by the derived classes whenever
a new message should be processed. 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) msg = Message.build(msg)

View File

@ -25,7 +25,9 @@ class HttpBackend(Backend):
}' \\ }' \\
http://localhost:8008/execute 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`` * 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, def __init__(self, port=_DEFAULT_HTTP_PORT,
websocket_port=_DEFAULT_WEBSOCKET_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, 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) :param port: Listen port for the web server (default: 8008)
:type port: int :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) :param ssl_key: Set it to the path of your key file if you want to enable HTTPS (default: None)
:type ssl_key: str :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 :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 :type ssl_capath: str
:param resource_dirs: Static resources directories that will be :param resource_dirs: Static resources directories that will be
@ -100,7 +104,8 @@ class HttpBackend(Backend):
the value is the absolute path to expose. the value is the absolute path to expose.
:type resource_dirs: dict[str, str] :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:: Example configuration::
@ -119,14 +124,15 @@ class HttpBackend(Backend):
- -
widget: image-carousel # Image carousel widget: image-carousel # Image carousel
columns: 6 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 refresh_seconds: 15
- -
widget: rss-news # RSS feeds widget widget: rss-news # RSS feeds widget
# Requires backend.http.poll to be enabled with some RSS sources and write them to sqlite db # Requires backend.http.poll to be enabled with some RSS sources and write them to sqlite db
columns: 6 columns: 6
limit: 25 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 :type dashboard: dict
@ -155,13 +161,18 @@ class HttpBackend(Backend):
self.port = port self.port = port
self.websocket_port = websocket_port self.websocket_port = websocket_port
self.dashboard = dashboard self.dashboard = dashboard or {}
self.maps = maps self.maps = maps or {}
self.server_proc = None self.server_proc = None
self.disable_websocket = disable_websocket self.disable_websocket = disable_websocket
self.websocket_thread = None 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.active_websockets = set()
self.run_externally = run_externally self.run_externally = run_externally
self.uwsgi_args = uwsgi_args or [] self.uwsgi_args = uwsgi_args or []
@ -175,11 +186,9 @@ class HttpBackend(Backend):
self.uwsgi_args = [str(_) for _ in self.uwsgi_args] + \ self.uwsgi_args = [str(_) for _ in self.uwsgi_args] + \
['--module', 'platypush.backend.http.uwsgi', '--enable-threads'] ['--module', 'platypush.backend.http.uwsgi', '--enable-threads']
def send_message(self, msg, **kwargs):
def send_message(self, msg):
self.logger.warning('Use cURL or any HTTP client to query the HTTP backend') self.logger.warning('Use cURL or any HTTP client to query the HTTP backend')
def on_stop(self): def on_stop(self):
""" On backend stop """ """ On backend stop """
self.logger.info('Received STOP event on HttpBackend') 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 """ """ Notify all the connected web clients (over websocket) of a new event """
import websockets import websockets
async def send_event(websocket): async def send_event(ws):
try: try:
await websocket.send(str(event)) await ws.send(str(event))
except Exception as e: except Exception as e:
self.logger.warning('Error on websocket send_event: {}'.format(e)) self.logger.warning('Error on websocket send_event: {}'.format(e))
loop = get_or_create_event_loop() loop = get_or_create_event_loop()
websockets = self.active_websockets.copy() wss = self.active_websockets.copy()
for websocket in websockets: for websocket in wss:
try: try:
loop.run_until_complete(send_event(websocket)) loop.run_until_complete(send_event(websocket))
except websockets.exceptions.ConnectionClosed: except websockets.exceptions.ConnectionClosed:
self.logger.info('Client connection lost') self.logger.info('Client connection lost')
self.active_websockets.remove(websocket) self.active_websockets.remove(websocket)
def websocket(self): def websocket(self):
""" Websocket main server """ """ Websocket main server """
import websockets import websockets
@ -222,7 +230,7 @@ class HttpBackend(Backend):
address = websocket.remote_address[0] if websocket.remote_address \ address = websocket.remote_address[0] if websocket.remote_address \
else '<unknown client>' 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) self.active_websockets.add(websocket)
try: try:
@ -258,7 +266,6 @@ class HttpBackend(Backend):
return proc return proc
def run(self): def run(self):
super().run() super().run()
@ -274,8 +281,11 @@ class HttpBackend(Backend):
self.server_proc.join() self.server_proc.join()
elif self.uwsgi_args: elif self.uwsgi_args:
uwsgi_cmd = ['uwsgi'] + 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) 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: # vim:sw=4:ts=4:et:

View File

@ -17,10 +17,12 @@ __routes__ = [
index, index,
] ]
@index.route('/') @index.route('/')
def index(): def index():
""" Route to the main web panel """ """ 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 # 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 # the web panel. This is usually the case for plugins that only include JS
@ -34,7 +36,7 @@ def index():
hidden_plugins = {} hidden_plugins = {}
for plugin, conf in configured_plugins.items(): 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 os.path.isfile(os.path.join(template_folder, template_file)):
if plugin in _hidden_plugins: if plugin in _hidden_plugins:
hidden_plugins[plugin] = conf hidden_plugins[plugin] = conf
@ -43,10 +45,10 @@ def index():
http_conf = Config.get('backend.http') http_conf = Config.get('backend.http')
return render_template('index.html', plugins=enabled_plugins, return render_template('index.html', plugins=enabled_plugins,
hidden_plugins=hidden_plugins, utils=HttpUtils, hidden_plugins=hidden_plugins, utils=HttpUtils,
token=Config.get('token'), token=Config.get('token'),
websocket_port=get_websocket_port(), websocket_port=get_websocket_port(),
has_ssl=http_conf.get('ssl_cert') is not None) has_ssl=http_conf.get('ssl_cert') is not None)
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et:

File diff suppressed because it is too large Load Diff

View File

@ -1,372 +1,32 @@
$(document).ready(function() { // Declaration of the main vue app
var websocket, var app;
pendingConnection = false,
openedWebsocket,
dateTimeInterval,
websocketTimeoutId,
modalFadingInTimeoutId,
websocketReconnectMsecs = 30000,
eventListeners = [];
var initWebsocket = function() { function ready(callback){
try { if (document.readyState!='loading') callback();
url_prefix = window.has_ssl ? 'wss://' : 'ws://'; else if (document.addEventListener) document.addEventListener('DOMContentLoaded', callback);
websocket = new WebSocket(url_prefix + window.location.hostname + ':' + window.websocket_port); else document.attachEvent('onreadystatechange', function(){
} catch (err) { if (document.readyState=='complete') callback();
websocket = undefined; });
return; }
}
pendingConnection = true; ready(function() {
app = new Vue({
el: '#app',
delimiters: ['[[',']]'],
data: {
config: {foo:"bar"}
},
var onWebsocketTimeout = function(self) { created: function() {
return function() { },
console.log('Websocket reconnection timed out, retrying');
pendingConnection = false;
self.close();
self.onclose();
};
};
websocketTimeoutId = setTimeout( mounted: function() {
onWebsocketTimeout(websocket), websocketReconnectMsecs); },
websocket.onmessage = function(event) { updated: function() {
console.debug(event); },
for (var listener of eventListeners) {
data = event.data;
if (typeof event.data === 'string') {
data = JSON.parse(data);
}
listener(data); destroyed: function() {
} },
}; });
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,
});
}

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> <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.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='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='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/application.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/toggles.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/vue.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/vue.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/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/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"> <script type="text/javascript">
window.websocket_port = {% print(websocket_port) %} if (!window.config) {
window.has_ssl = {% print('true' if has_ssl else 'false') %}; 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 %} {% if token %}
window.token = '{% print(token) %}' window.config.token = '{% print(token) %}';
{% else %} {% else %}
window.token = undefined window.config.token = undefined;
{% endif %} {% endif %}
</script> </script>
</head> </head>
@ -42,37 +52,24 @@
</div> </div>
</header> </header>
<main> <nav>
<ul class="tab-nav"> {% for plugin in plugins.keys()|sort() %}
{% for plugin in plugins.keys()|sort() %} <a href="#{% print plugin %}">
<li> {% print plugin %}
<a class="button plugin-tab-item" href="#{% print plugin %}"> </a>
{% print plugin %} {% endfor %}
</a> </nav>
</li>
{% endfor %}
</ul>
<div class="tab-content"> <main>
<div id="app">
{% for plugin in plugins.keys()|sort() %} {% for plugin in plugins.keys()|sort() %}
{% with configuration=plugins[plugin], utils=utils %} {% with configuration=plugins[plugin], utils=utils %}
<div class="tab-pane plugin-tab-content" id="{% print plugin %}"> <div class="tab-pane plugin-tab-content" id="{% print plugin %}-container">
{% include 'plugins/' + plugin + '.html' %} {% include 'panel/' + plugin + '/index.html' %}
</div> </div>
{% endwith %} {% endwith %}
{% endfor %} {% endfor %}
</div> </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> </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 @classmethod
def to_json(cls, data): def to_json(cls, data):
if isinstance(data, type({}.keys())):
# Convert dict_keys to list before serializing
data = list(data)
return json.dumps(data) return json.dumps(data)
@classmethod @classmethod