+
+{% endblock %}
diff --git a/src/opsoro/apps/lua_scripting/__init__.py b/src/opsoro/apps/lua_scripting/__init__.py
index 6706327..018d33d 100644
--- a/src/opsoro/apps/lua_scripting/__init__.py
+++ b/src/opsoro/apps/lua_scripting/__init__.py
@@ -1,26 +1,30 @@
-from flask import Blueprint, render_template, request, send_from_directory
-from werkzeug import secure_filename
-from functools import partial
-import os
import glob
+import os
import time
-import lupa
+from functools import partial
+
+from flask import Blueprint, render_template, request, send_from_directory
+from werkzeug import secure_filename
+
+from opsoro.robot import Robot
+
+# import lupa
from .scripthost import ScriptHost
-config = {'full_name': 'Lua Scripting',
- 'icon': 'fa-terminal',
- 'color': '#36c9ff',
- 'allowed_background': True,
- 'robot_state': 1}
+config = {
+ 'full_name': 'Lua Scripting',
+ 'author': 'OPSORO',
+ 'icon': 'fa-terminal',
+ 'color': 'orange',
+ 'difficulty': 7,
+ 'tags': ['lua', 'code', 'script'],
+ 'allowed_background': True,
+ 'multi_user': False,
+ 'connection': Robot.Connection.OFFLINE,
+ 'activation': Robot.Activation.AUTO
+}
config['formatted_name'] = config['full_name'].lower().replace(' ', '_')
-# robot_state:
-# 0: Manual start/stop
-# 1: Start robot automatically (alive feature according to preferences)
-# 2: Start robot automatically and enable alive feature
-# 3: Start robot automatically and disable alive feature
-
-clientconn = None
sh = None
script = ''
script_name = None
@@ -30,58 +34,37 @@
def add_console(message, color='#888888', icon=None):
- global clientconn
- if clientconn:
- clientconn.send_data('addConsole', {'message': message,
- 'color': color,
- 'icon': icon})
+ Users.send_app_data(config['formatted_name'], 'addConsole', {'message': message, 'color': color, 'icon': icon})
def send_started():
- global clientconn
- if clientconn:
- clientconn.send_data('scriptStarted', {})
+ Users.send_app_data(config['formatted_name'], 'scriptStarted', {})
def send_stopped():
- global clientconn
- if clientconn:
- clientconn.send_data('scriptStopped', {})
+ Users.send_app_data(config['formatted_name'], 'scriptStopped', {})
def init_ui():
- global clientconn
- if clientconn:
- clientconn.send_data('initUI', {})
+ Users.send_app_data(config['formatted_name'], 'initUI', {})
def ui_add_button(name, caption, icon, toggle=False):
- global clientconn
- if clientconn:
- clientconn.send_data('UIAddButton', {'name': name,
- 'caption': caption,
- 'icon': icon,
- 'toggle': toggle})
+ Users.send_app_data(config['formatted_name'], 'UIAddButton', {'name': name, 'caption': caption, 'icon': icon, 'toggle': toggle})
def ui_add_key(key):
- global clientconn
global sh
- if clientconn:
- valid_keys = ['up', 'down', 'left', 'right', 'space']
- valid_keys += list('abcdefghijklmnopqrstuvwxyz')
- if key in valid_keys:
- clientconn.send_data('UIAddKey', {'key': key})
- else:
- sh.generate_lua_error('Invalid key: %s' % key)
+ valid_keys = ['up', 'down', 'left', 'right', 'space']
+ valid_keys += list('abcdefghijklmnopqrstuvwxyz')
+ if key in valid_keys:
+ Users.send_app_data(config['formatted_name'], 'UIAddKey', {'key': key})
+ else:
+ sh.generate_lua_error('Invalid key: %s' % key)
def setup_pages(opsoroapp):
- luascripting_bp = Blueprint(
- config['formatted_name'],
- __name__,
- template_folder='templates',
- static_folder='static')
+ luascripting_bp = Blueprint(config['formatted_name'], __name__, template_folder='templates', static_folder='static')
@luascripting_bp.route('/', methods=['GET'])
@opsoroapp.app_view
@@ -103,7 +86,7 @@ def index():
data['actions'][action] = request.args.get('param', None)
if sh.is_running:
- data['script'] = script #sh._script
+ data['script'] = script # sh._script
else:
with open(get_path('static/boilerplate.lua'), 'r') as f:
data['script'] = f.read()
@@ -147,16 +130,6 @@ def stopscript():
return {'status': 'error',
'message': 'There is no active script to stop.'}
- @opsoroapp.app_socket_connected
- def s_connected(conn):
- global clientconn
- clientconn = conn
-
- @opsoroapp.app_socket_disconnected
- def s_disconnected(conn):
- global clientconn
- clientconn = None
-
@opsoroapp.app_socket_message('keyDown')
def s_key_down(conn, data):
global sh
diff --git a/src/opsoro/apps/lua_scripting/scripthost.py b/src/opsoro/apps/lua_scripting/scripthost.py
index 75d4e65..692935d 100644
--- a/src/opsoro/apps/lua_scripting/scripthost.py
+++ b/src/opsoro/apps/lua_scripting/scripthost.py
@@ -1,25 +1,27 @@
from __future__ import division
-import time
import sys
+import time
import traceback
+
import lupa
-from opsoro.hardware import Hardware
-from opsoro.expression import Expression
from opsoro.animate import Animate, AnimatePeriodic
+from opsoro.expression import Expression
+from opsoro.hardware import Hardware
+from opsoro.robot import Robot
from opsoro.sound import Sound
from opsoro.stoppable_thread import StoppableThread
def callback(fn):
"""
- Helper function to support callbacks in classes. Returns the first parameter
- if it is callable, returns a dummy function otherwise.
+ Helper function to support callbacks in classes. Returns the first parameter
+ if it is callable, returns a dummy function otherwise.
- Usage:
- callback(self.on_my_callback)()
- """
+ Usage:
+ callback(self.on_my_callback)()
+ """
def do_nothing(*args, **kwargs):
pass
@@ -56,9 +58,9 @@ def __del__(self):
def setup_runtime(self):
"""
- Creates a new lua runtime and initializes all globals. Used by
- start_script(), should not be called directly.
- """
+ Creates a new lua runtime and initializes all globals. Used by
+ start_script(), should not be called directly.
+ """
# Create new lua instance
self.runtime = lupa.LuaRuntime(unpack_returned_tuples=True)
@@ -75,6 +77,7 @@ def setup_runtime(self):
g["Sound"] = Sound
g["Expression"] = Expression
+ g["Robot"] = Robot
g["Hardware"] = LuaHardware(self.runtime)
g["Animate"] = LuaAnimate
g["AnimatePeriodic"] = LuaAnimatePeriodic
@@ -98,10 +101,10 @@ def setup_runtime(self):
def start_script(self, script):
"""
- Start a new script. This method will create a new runtime, pass the
- script to the runtime, and start a thread to continuously call the
- script's loop function. Can only be used if no other script is running.
- """
+ Start a new script. This method will create a new runtime, pass the
+ script to the runtime, and start a thread to continuously call the
+ script's loop function. Can only be used if no other script is running.
+ """
# Check if running
if self.is_running:
raise RuntimeError("A script is already running!")
@@ -119,31 +122,31 @@ def start_script(self, script):
def stop_script(self):
"""
- Attempts to stop the current script. Returns immediately if no script is
- running. If a script is running, this method will send a stop signal to
- to the script thread, and then block until the thread is stopped. Note
- that the thread's stopped condition is only checked during sleep() and
- at the end of loop() calls, this function will not stop infinite loops.
- """
+ Attempts to stop the current script. Returns immediately if no script is
+ running. If a script is running, this method will send a stop signal to
+ to the script thread, and then block until the thread is stopped. Note
+ that the thread's stopped condition is only checked during sleep() and
+ at the end of loop() calls, this function will not stop infinite loops.
+ """
if self.is_running and self.runtime_thread is not None:
self.runtime_thread.stop()
self.runtime_thread.join()
def generate_lua_error(self, message):
"""
- If a script is running, this method will generate an error inside the
- script. Useful to signal script errors (e.g. bad parameter) to the user.
- """
+ If a script is running, this method will generate an error inside the
+ script. Useful to signal script errors (e.g. bad parameter) to the user.
+ """
if self.is_running and self.runtime is not None:
g = self.runtime.globals()
g["error"](message)
def _report_error(self, e):
"""
- Helper function that prefixes the type of error to the exception, and
- then sends the error message to the application through the on_error
- callback.
- """
+ Helper function that prefixes the type of error to the exception, and
+ then sends the error message to the application through the on_error
+ callback.
+ """
if type(e) == lupa.LuaSyntaxError:
callback(self.on_error)("Syntax error: %s" % str(e))
elif type(e) == lupa.LuaError:
@@ -157,28 +160,28 @@ def _report_error(self, e):
def _sleep(self, time):
"""
- Lua API
- Sleep function that pauses the thread for a number of seconds. This
- sleep function will return immediately if the thread's stop flag is set.
- This means that loop function should come to an end instantaneously,
- after which the thread is ended.
- """
+ Lua API
+ Sleep function that pauses the thread for a number of seconds. This
+ sleep function will return immediately if the thread's stop flag is set.
+ This means that loop function should come to an end instantaneously,
+ after which the thread is ended.
+ """
if self.runtime_thread is not None:
self.runtime_thread.sleep(time)
def _rising_edge(self, identifier, status):
"""
- Lua API
- Helper function to detect a rising edge of a signal (e.g. button, key,
- capacitive touch pad, etc). Identifier is an arbitrary string that is
- used to distinguish between different signals. Internally, it's used as
- a key for the dictionary that keeps track of different signals.
-
- Usage:
- if rising_edge("mybutton", UI:is_key_pressed("up")) then
- -- Do something
- end
- """
+ Lua API
+ Helper function to detect a rising edge of a signal (e.g. button, key,
+ capacitive touch pad, etc). Identifier is an arbitrary string that is
+ used to distinguish between different signals. Internally, it's used as
+ a key for the dictionary that keeps track of different signals.
+
+ Usage:
+ if rising_edge("mybutton", UI:is_key_pressed("up")) then
+ -- Do something
+ end
+ """
last_status = False
if identifier in self._rising_dict:
last_status = self._rising_dict[identifier]
@@ -188,17 +191,17 @@ def _rising_edge(self, identifier, status):
def _falling_edge(self, identifier, status):
"""
- Lua API
- Helper function to detect a falling edge of a signal (e.g. button, key,
- capacitive touch pad, etc). Identifier is an arbitrary string that is
- used to distinguish between different signals. Internally, it's used as
- a key for the dictionary that keeps track of different signals.
-
- Usage:
- if falling_edge("mybutton", UI:is_key_pressed("up")) then
- -- Do something
- end
- """
+ Lua API
+ Helper function to detect a falling edge of a signal (e.g. button, key,
+ capacitive touch pad, etc). Identifier is an arbitrary string that is
+ used to distinguish between different signals. Internally, it's used as
+ a key for the dictionary that keeps track of different signals.
+
+ Usage:
+ if falling_edge("mybutton", UI:is_key_pressed("up")) then
+ -- Do something
+ end
+ """
last_status = False
if identifier in self._falling_dict:
last_status = self._falling_dict[identifier]
@@ -216,13 +219,13 @@ def _remove_lua_overlays(self):
def _run(self):
"""
- Called by the worker thread when the script is run. First attempts to
- call the script's setup function, then continuously calls the loop
- function. When the thread's stop flag is set, the loop breaks and the
- thread attempts to run the quit function. At any time, if the runtime
- encounters an error, the script is stopped, and the on_error and on_stop
- callbacks are triggered.
- """
+ Called by the worker thread when the script is run. First attempts to
+ call the script's setup function, then continuously calls the loop
+ function. When the thread's stop flag is set, the loop breaks and the
+ thread attempts to run the quit function. At any time, if the runtime
+ encounters an error, the script is stopped, and the on_error and on_stop
+ callbacks are triggered.
+ """
time.sleep(0.05) # delay
@@ -276,60 +279,60 @@ def __init__(self):
def init(self):
"""
- Lua API
- Requests the application to initialize the UI through the on_init
- callback. The request is typically passed on to the client via websocket.
- """
+ Lua API
+ Requests the application to initialize the UI through the on_init
+ callback. The request is typically passed on to the client via websocket.
+ """
callback(self.on_init)()
def add_button(self, name, caption, icon, toggle=False):
"""
- Lua API
- Adds a button to the client's UI. Request to client is sent through the
- on_add_button callback.
- """
+ Lua API
+ Adds a button to the client's UI. Request to client is sent through the
+ on_add_button callback.
+ """
callback(self.on_add_button)(name, caption, icon, toggle)
def add_key(self, key):
"""
- Lua API
- Adds a key listener to the client's UI.
- Request to client is sent through the on_add_key callback.
- """
+ Lua API
+ Adds a key listener to the client's UI.
+ Request to client is sent through the on_add_key callback.
+ """
callback(self.on_add_key)(key)
def set_key_status(self, key, status):
"""
- Used by the app to set the status of a key. Typically, key events are
- captured on the clientside using javascript and are transfered to the
- application using a websocket. The application is responsible for
- updating the key status in the ScriptUI class.
- """
+ Used by the app to set the status of a key. Typically, key events are
+ captured on the clientside using javascript and are transfered to the
+ application using a websocket. The application is responsible for
+ updating the key status in the ScriptUI class.
+ """
self._keys[key] = status
def set_button_status(self, button, status):
"""
- Used by the app to set the status of a button. Typically, button events
- are captured on the clientside using javascript and are transfered to
- the application using a websocket. The application is responsible for
- updating the button status in the ScriptUI class.
- """
+ Used by the app to set the status of a button. Typically, button events
+ are captured on the clientside using javascript and are transfered to
+ the application using a websocket. The application is responsible for
+ updating the button status in the ScriptUI class.
+ """
self._buttons[button] = status
def is_button_pressed(self, name):
"""
- Lua API
- Returns True if a button is pressed, False otherwise.
- """
+ Lua API
+ Returns True if a button is pressed, False otherwise.
+ """
if name in self._buttons:
return self._buttons[name]
return False
def is_key_pressed(self, key):
"""
- Lua API
- Returns True if a key is pressed, False otherwise.
- """
+ Lua API
+ Returns True if a key is pressed, False otherwise.
+ """
if key in self._keys:
return self._keys[key]
return False
@@ -355,23 +358,23 @@ def ignore_one(ign, *args, **kwargs):
return attr
def cap_get_filtered_data(self):
- return self.runtime.table_from(Hardware.cap_get_filtered_data())
+ return self.runtime.table_from(Hardware.Capacitive.get_filtered_data())
def cap_get_baseline_data(self):
- return self.runtime.table_from(Hardware.cap_get_baseline_data())
+ return self.runtime.table_from(Hardware.Capacitive.get_baseline_data())
def ana_read_all_channels(self):
- return self.runtime.table_from(Hardware.cap_get_baseline_data())
+ return self.runtime.table_from(Hardware.Capacitive.get_baseline_data())
def spi_command(self, cmd, params=None, returned=0, delay=0):
if params is not None:
params = list(params.values())
return self.runtime.table_from(
- Hardware.spi_command(cmd, params, returned, delay))
+ Hardware.SPI.command(cmd, params, returned, delay))
def servo_set_all(self, pos_list):
pos_list = list(pos_list.values())
- Hardware.servo_set_all(post_list)
+ Hardware.Servo.set_all(post_list)
class LuaAnimate(object):
@@ -402,8 +405,8 @@ def new(cls, times, values):
def __call__(self):
return self._a()
-### NEW NEW NEW and untested
-### install pyserial
+# NEW NEW NEW and untested
+# install pyserial
# TODO: add to scripthost
# import serial
# import serial.tools.list_ports
diff --git a/src/opsoro/apps/lua_scripting/static/ono-lua-highlighter.js b/src/opsoro/apps/lua_scripting/static/ono-lua-highlighter.js
index ce4faf0..c6f7576 100644
--- a/src/opsoro/apps/lua_scripting/static/ono-lua-highlighter.js
+++ b/src/opsoro/apps/lua_scripting/static/ono-lua-highlighter.js
@@ -13,14 +13,14 @@ var OnoLuaHighlightRules = function() {
"init|add_button|add_key|is_button_pressed|is_key_pressed|"+
// Hardware class methods
- "ping|reset|led_on|led_off|i2c_detect|i2c_read8|i2c_write8|i2c_read16|"+
- "i2C_write16|servo_init|servo_enable|servo_disable|servo_neutral|"+
- "servo_set|servo_set_all|cap_init|cap_set_threshold|"+
- "cap_get_filtered_data|cap_get_baseline_data|cap_get_touched|"+
- "cap_set_gpio_pinmode|cap_read_gpio|cap_write_gpio|neo_init|neo_enable|"+
- "neo_disable|neo_set_brightness|neo_show|neo_set_pixel|neo_set_range|"+
- "neo_set_all|neo_set_pixel_hsv|neo_set_range_hsv|neo_set_all_hsv|"+
- "ana_read_channel|ana_read_all_channels|"+
+ "ping|reset|led_on|led_off|I2C:detect|I2C:read8|I2C:write8|I2C:read16|"+
+ "I2C:write16|Servo:init|Servo:enable|Servo:disable|Servo:neutral|"+
+ "Servo:set|Servo:set_all|Capacitive:init|Capacitive:set_threshold|"+
+ "Capacitive:get_filtered_data|Capacitive:get_baseline_data|Capacitive:get_touched|"+
+ "Capacitive:set_gpio_pinmode|Capacitive:read_gpio|Capacitive:write_gpio|Neopixel:init|Neopixel:enable|"+
+ "Neopixel:disable|Neopixel:set_brightness|Neopixel:show|Neopixel:set_pixel|Neopixel:set_range|"+
+ "Neopixel:set_all|Neopixel:set_pixel_hsv|Neopixel:set_range_hsv|Neopixel:set_all_hsv|"+
+ "Analog:read_channel|Analog:read_all_channels|"+
// Sound class methods
"say_tts|play_file|"+
diff --git a/src/opsoro/apps/lua_scripting/templates/lua_scripting.html b/src/opsoro/apps/lua_scripting/templates/lua_scripting.html
index ddf51de..373fe85 100644
--- a/src/opsoro/apps/lua_scripting/templates/lua_scripting.html
+++ b/src/opsoro/apps/lua_scripting/templates/lua_scripting.html
@@ -1,50 +1,16 @@
{% extends "app_base.html" %}
-{% block head %}
+{% block app_head %}
{% endblock %}
{% block app_toolbar %}
- {% include "toolbar/_file_operations.html" %}
- {% include "toolbar/_script_operations.html" %}
{% include "toolbar/_expand_collapse.html" %}
+ {% include "toolbar/_script_operations.html" %}
+ {% include "toolbar/_file_operations.html" %}
{% endblock %}
{% block app_content %}
-
-
-
";
$("#ScriptUIButtons").append(li);
@@ -240,15 +191,15 @@
"z": 90
};
- if (msg.key == "space") {
+ if (data.key == "space") {
// Key is space bar
- div = "
";
- } else if (arrows.indexOf(msg.key) > -1) {
+ div = "
";
+ } else if (arrows.indexOf(data.key) > -1) {
// Key is an arrow
- div = "
";
- } else if (alphabet.indexOf(msg.key) > -1) {
+ div = "
";
+ } else if (alphabet.indexOf(data.key) > -1) {
// Key is a letter
- div = "
" + msg.key + "
";
+ div = "
" + data.key + "
";
} else {
return;
}
@@ -265,12 +216,6 @@
}
};
- conn.onclose = function () {
- console.log("SockJS disconnected.");
- conn = null;
- connReady = false;
- };
-
// Don't submit form on enter
$("input,select").keypress(function (evt) {
return evt.keyCode != 13;
@@ -302,7 +247,7 @@
boilerplate = data;
});
- self.init = function () {
+ self.newFileData = function () {
editor.setValue(boilerplate);
editor.gotoLine(1, 0, false);
@@ -431,22 +376,16 @@
});
self.scriptUI = function () {
- $("#ScriptUIModal").foundation("reveal", "open");
+ $("#ScriptUIModal").foundation("open");
};
- if (action_data.openfile) {
- self.loadFileData(action_data.openfile || "");
- } else {
- self.init();
- }
-
};
// This makes Knockout get to work
var model = new Model();
ko.applyBindings(model);
model.fileIsModified(false);
- config_file_operations("scripts", model.fileExtension(), model.saveFileData, model.loadFileData, model.init);
+ config_file_operations("", model.fileExtension(), model.saveFileData, model.loadFileData, model.newFileData);
});
@@ -455,9 +394,9 @@
{% block app_modals %}
-
+
-
+
diff --git a/src/opsoro/apps/opa/README b/src/opsoro/apps/opa/README
new file mode 100644
index 0000000..318395c
--- /dev/null
+++ b/src/opsoro/apps/opa/README
@@ -0,0 +1,11 @@
+Folder info:
+ scripts:
+ contains files the app can save/edit/use
+ static:
+ contains specific app files; javascript/css/yaml-settings/images/...
+ templates:
+ contains the apps main html file and also other html files the app uses
+
+
+__init__.py:
+ Contains the server-side functionality of the app
diff --git a/src/opsoro/apps/opa/__init__.py b/src/opsoro/apps/opa/__init__.py
new file mode 100644
index 0000000..4990b62
--- /dev/null
+++ b/src/opsoro/apps/opa/__init__.py
@@ -0,0 +1,227 @@
+from __future__ import with_statement
+
+import json, datetime
+import json,datetime
+
+import time
+from threading import Thread, current_thread
+
+from flask import Blueprint, render_template, request, redirect, url_for, flash, send_from_directory, jsonify
+
+from opsoro.console_msg import *
+from opsoro.hardware import Hardware
+from opsoro.robot import Robot
+from opsoro.expression import Expression
+from opsoro.stoppable_thread import StoppableThread
+from opsoro.sound import Sound
+from opsoro.module.eye import Eye
+from opsoro.preferences import Preferences
+from functools import partial
+
+import os
+
+constrain = lambda n, minn, maxn: max(min(maxn, n), minn)
+get_path = partial(os.path.join, os.path.abspath(os.path.dirname(__file__)))
+
+clientconn = None
+
+config = {
+ 'full_name': 'personal assistant',
+ 'icon': 'fa-child',
+ 'color': 'green',
+ 'difficulty': 1,
+ 'tags': ['template', 'developer'],
+ 'allowed_background': False,
+ 'connection': Robot.Connection.OFFLINE,
+ 'activation': Robot.Activation.AUTO
+}
+config['formatted_name'] = 'opa'
+
+def setup_pages(server):
+ app_bp = Blueprint(config['formatted_name'], __name__, template_folder='templates', static_folder='static')
+ # Public function declarations
+ app_bp.add_url_rule('/demo', 'demo', server.app_api(demo), methods=['GET', 'POST'])
+
+
+ @app_bp.route('/')
+ @server.app_view
+ def index():
+ data = {
+ 'actions': {},
+ 'data': [],
+ 'activity': [],
+ }
+
+ action = request.args.get('action', None)
+ if action != None:
+ data['actions'][action] = request.args.get('param', None)
+ Robot.sleep()
+ Robot.blink(1)
+
+ #json data ophalen uit json-files
+ json_commands = read_json_file('Commands.json')
+ activity_data = read_json_file('Activity.json')
+
+ #json data doorsturen naar template
+ print_info(activity_data['Activity'][-10:])
+ data['commands'] = json_commands
+ data['activity'] = activity_data['Activity'][-10:]
+
+ return server.render_template(config['formatted_name'] + '.html', **data)
+
+ #IFTTT Maker Webhook web request opvangen
+ @app_bp.route('/action', methods=['POST'])
+ def action():
+ Robot.wake()
+ json_dict = request.data
+ data = json.loads(json_dict)
+ print_info(data)
+
+ #De data die verkregen is via de webhook laten uitspreken
+ speak(data)
+
+ #Opslaan van de activity
+ save_activity(data)
+ Robot.sleep()
+ return jsonify(data)
+
+ #Naam veranderen van persoon die de robot gebruikt WIP
+ @app_bp.route('/name', methods=['POST'])
+ def change_name():
+ data = {}
+ if request.method == 'POST':
+ Preferences.set('general', 'robot_name', request.form.get('robotName', type=str, default=None))
+ return redirect('/apps/opa/')
+
+ #ophalen applets via GET
+ @app_bp.route('/getapplets',methods=['GET'])
+ def getapplets():
+ json_data = read_json_file('Applets.json')
+ return jsonify(json_data)
+
+ #ophalen commands via GET
+ @app_bp.route('/getcommands',methods=['GET'])
+ def getcommands():
+ json_data = read_json_file('Commands.json')
+ return jsonify(json_data)
+
+
+
+ @server.app_socket_message('command')
+ def socket_message(conn, data):
+ print_info(data)
+ #global command_queue
+ print_info("Message received")
+ #command_queue = data['data']
+ #command_queue.remove('placeholder')
+ #print_info(command_queue)
+ response = {
+ 'data': "Message received"
+ }
+ conn.send_data("Message",response)
+
+ @server.app_socket_connected
+ def socket_connected(conn):
+ global clientconn
+ clientconn = conn
+ print_info("Connected")
+
+ @server.app_socket_disconnected
+ def socket_connected(conn):
+ global clientconn
+ clientconn = None
+ print_info("Disconnected")
+
+
+ #Activity opslaan in de json file
+ def save_activity(data):
+ data['date'] = str(datetime.date.today())
+ data['time'] = str(datetime.datetime.now().strftime("%H:%M:%S"))
+ print_info(data)
+ filename = os.path.join(app_bp.static_folder+'/json/', 'Activity.json')
+ with open(filename, 'r') as blog_file:
+ json_data = json.load(blog_file)
+ json_data['Activity'].append(data)
+ with open(filename, 'w') as write_file:
+ write_file.write(json.dumps(json_data))
+
+ #Data lezen van json file
+ def read_json_file(filename):
+ try:
+ filename = os.path.join(app_bp.static_folder+'/json/',filename)
+ with open(filename) as json_file:
+ json_read = json.load(json_file)
+ return json_read
+ except:
+ print_error('Failed to read json file')
+ return null
+
+
+ server.register_app_blueprint(app_bp)
+
+#Uitvoeren van spraak
+def speak(data):
+ if data['service'] != "Alarm":
+ if data['say'] == "True":
+ play_data = data['play']
+ Sound.play_file("smb_1-up.wav")
+ Sound.wait_for_sound()
+ play(play_data)
+ else: print_info('No need to play')
+ else:
+ print_info("Alarm")
+ alarm()
+ print_info("Exit speak action..")
+ return
+
+
+def play(play_data):
+ Sound.say_tts(play_data['1'])
+ Sound.wait_for_sound()
+ Sound.say_tts(play_data['2'])
+ Sound.wait_for_sound()
+ Sound.say_tts(play_data['3'])
+
+#Alarm laten afspelen WIP
+def alarm():
+ onetoten = range(0,3)
+ for i in onetoten:
+ Sound.play_file("1_kamelenrace.wav")
+ Sound.wait_for_sound(print_info)
+ print_info("Alarm stopped...")
+ return
+
+def CommandLoop():
+ print_info('Start Command loop')
+ global command_queue
+ time.sleep(1)
+ while not command_webservice.stopped():
+ if len(command_queue) > 0:
+ print_info(command_queue.pop(0))
+ #do something
+ response = {
+ 'data': "Remove"
+ }
+ clientconn.send_data("Message",response)
+ command_webservice.sleep(5)
+
+def demo():
+ # publicly accessible function
+ if 1 > 0:
+ return {'status': 'success'}
+ else:
+ return {'status': 'error', 'message': 'This is a demo error!'}
+
+# Default functions for setting up, starting and stopping an app
+def setup(server):
+ global command_queue
+ command_queue = []
+
+def start(server):
+ global command_webservice
+ command_webservice = StoppableThread(target=CommandLoop)
+
+def stop(server):
+ print_info('Stop Command loop')
+ global command_webservice
+ command_webservice.stop()
diff --git a/src/opsoro/apps/opa/static/app.css b/src/opsoro/apps/opa/static/app.css
new file mode 100644
index 0000000..475c6ab
--- /dev/null
+++ b/src/opsoro/apps/opa/static/app.css
@@ -0,0 +1,143 @@
+.flex-container{
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+}
+#cmdqueue{
+ display: flex;
+ flex-wrap: wrap;
+ position: relative;
+ min-width: 170px;
+ min-height: 170px;
+}
+.applet{
+ margin: 10px;
+ border-radius: 15px;
+ overflow: hidden;
+}
+.applet-content{
+ color: white;
+ display: flex;
+ flex-wrap: wrap;
+ width: 250px;
+ margin: 15px;
+ flex-direction: column;
+}
+.applet-content .logo{
+ width: 36px;
+ height: 36px;
+ margin: 0;
+ position: absolute;
+}
+.applet-content h3{
+ margin-left: 50px;
+}
+.applet-footer{
+ text-align: center;
+ height: 30px;
+ line-height: 30px;
+ font-size: 12px;
+ color: white;
+ margin-top: auto;
+ opacity: 0.4;
+}
+
+.command{
+ z-index: 2;
+ margin: 10px;
+ width: 230px;
+ border-radius: 15px;
+ background-color: blue;
+ overflow: hidden;
+}
+.command a{
+ display: flex;
+ flex-wrap: wrap;
+ flex-direction: column;
+ height: 100%;
+}
+.command-content{
+ margin: 15px;
+ max-width: 200px;
+ color: white;
+}
+.command-footer{
+ text-align: center;
+ font-size: 12px;
+ color: white;
+ height: 30px;
+ line-height: 30px;
+ width: 100%;
+ margin-top: auto;
+}
+
+.flex-container > .highlight{
+ background-color: green;
+ opacity: 0.5;
+}
+
+#cmdqueue-placeholder{
+ position: absolute;
+ border: 4px dashed #f8f8f8;
+ text-align: center;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border-radius: 15px;
+ width: calc(100% - 20px);
+ height: calc(100% - 20px);
+ margin: 10px;
+}
+.text-muted{
+ opacity: 0.8;
+}
+.hidden{
+ display: none !important;
+}
+.visible{
+ display: block;
+}
+
+.activity {
+ width:400px;
+ margin-bottom: 2em
+}
+
+.activities {
+ display: flex;
+ flex-direction: column-reverse;
+ align-items: center;
+}
+
+.activity_content {
+ background-color:#2e294e;
+ height: 100px;
+ border-radius: 15px;
+ padding: 2em;
+ color:#ffffff;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+}
+.activity_header {
+ display: flex;
+ flex-direction: row;
+}
+
+#protocolcontrol>ul>li {
+ list-style-type: none;
+ display: inline-block;
+}
+#protocolcontrol {
+ display: flex;
+ justify-content: center;
+}
+.no-margin {
+ margin: 0;
+}
+
+@media screen and (max-width: 550px) {
+ .activity {
+ width: 280px;
+ }
+}
\ No newline at end of file
diff --git a/src/opsoro/apps/opa/static/app.js b/src/opsoro/apps/opa/static/app.js
new file mode 100644
index 0000000..1600e71
--- /dev/null
+++ b/src/opsoro/apps/opa/static/app.js
@@ -0,0 +1,279 @@
+$(document).ready(function() {
+
+
+ var commandData;
+ $.ajax({
+ url: "/apps/opa/getcommands",
+ cache: false
+ }).done(function(data){
+ commandData = data.Commands
+ console.log(commandData);
+ });
+
+ //Websockets
+ conn = null;
+ connReady = false;
+ conn = new SockJS('http://' + window.location.host + '/appsockjs');
+
+ conn.onopen = function() {
+ console.log("SockJS connected.");
+ $.ajax({
+ url: "/appsockjstoken",
+ cache: false
+ }).done(function(data) {
+ conn.send(JSON.stringify({
+ action: "authenticate",
+ token: data
+ }));
+
+ connReady = true;
+ console.log("SockJS authenticated.");
+ });
+ };
+ conn.onmessage = function(e) {
+ var data = JSON.parse(e.data)
+ if(data['data'] == "Remove"){
+ $('#cmdqueue').find('.command:first').remove();
+ }
+ };
+
+ conn.onclose = function() {
+ console.log("SockJS disconnected.");
+ conn = null;
+ connReady = false;
+ };
+
+ //JQuery UI
+ $('#cmdqueue').sortable({
+ revert: true,
+ placeholder: "highlight command",
+ cancel: ".disabled",
+ stop: function ( event, ui){
+ var data = $(this).sortable('toArray', { attribute: 'command-id' });
+ conn.send(JSON.stringify({
+ action: "command",
+ data: data
+ }));
+ }
+ });
+ $('#sortable').disableSelection();
+ $('.draggable').draggable({
+ connectToSortable: "#cmdqueue",
+ helper: "clone",
+ revert: "invalid",
+ drag: function (event,ui){
+ $(this).removeClass('bounceIn')
+ },
+ stop: function (event,ui){
+ $(this).addClass('bounceIn')
+ }
+ });
+ $('#cmdqueue').droppable({
+ drop: function( event, ui ) {
+ $('#cmdqueue-placeholder').find('p').addClass("hidden");
+ }
+ });
+
+ $('#filters').accordion({
+ collapsible: true
+ });
+ $('#cmdqueue').on('click', '.command', function() {
+ this.parentNode.removeChild(this);
+ });
+
+
+ //Knockout JS
+ var Model = function() {
+ var self = this;
+
+ // File operations toolbar item
+ self.fileIsLocked = ko.observable(false);
+ self.fileIsModified = ko.observable(false);
+ self.fileName = ko.observable("");
+ self.fileStatus = ko.observable("");
+ self.fileExtension = ko.observable(".ext");
+
+ // Script operations toolbar item
+ self.isRunning = ko.observable(false);
+ self.isUI = ko.observable(false);
+
+ // Lock/Unlock toolbar item
+ self.toggleLocked = function() {
+ if (self.fileIsLocked()) {
+ self.unlockFile();
+ } else {
+ self.lockFile();
+ }
+ };
+ self.lockFile = function() {
+ self.fileIsLocked(true);
+ self.fileStatus("Locked")
+ };
+ self.unlockFile = function() {
+ self.fileIsLocked(false);
+ self.fileStatus("Editing")
+ };
+
+
+
+ // Popup window
+ /*
+ self.popupTextInput = ko.observable("Hi! This text can be changed. Click on the button to change me!");
+ self.showPopup = function() {
+ $("#popup_window").foundation('open');
+
+ };
+ self.closePopup = function() {
+ $("#popup_window").foundation('close');
+ };
+ self.popupButtonHandler = function() {
+ self.closePopup();
+ };
+
+ self.init = function() {
+ // Clear data, new file, ...
+ self.fileName("Untitled");
+ self.unlockFile();
+ self.fileIsModified(false);
+ };
+
+ self.loadFileData = function(data) {
+ if (data == undefined) {
+ return;
+ }
+
+ // Load data, parse if needed
+ var dataobj = JSON.parse(data);
+
+
+ self.fileIsModified(false);
+ self.lockFile();
+ };
+
+ self.saveFileData = function() {
+ // Convert data
+ file_data = {};
+
+ var data = ko.toJSON(file_data, null, 2);
+ self.fileIsModified(false);
+ return data;
+ };
+ */
+ };
+
+ function Applet(Applet_name, Applet_url, Applet_color, Applet_categorie, Applet_logo) {
+ this.Applet_name = Applet_name;
+ this.Applet_url = Applet_url;
+ this.Applet_color = Applet_color;
+ this.Applet_categorie = Applet_categorie;
+ this.Applet_logo = Applet_logo;
+ }
+
+
+ /*var listOfApplets = [
+ new Applet("Test", "fkdsjfsdkf", "#000000", "News", "dlkjfskldjfsd"),
+ new Applet("Test", "fkdsjfsdkf", "#000000", "News", "dlkjfskldjfsd"),
+ new Applet("Test", "fkdsjfsdkf", "#000000", "News", "dlkjfskldjfsd"),
+ new Applet("Test", "fkdsjfsdkf", "#000000", "News", "dlkjfskldjfsd"),
+ ];*/
+
+ var listOfApplets = [
+
+ ];
+
+ $.ajax({
+ url: "/apps/opa/getapplets",
+ cache: false
+ }).done(function(data){
+
+ $(data['Applets']).each(function(index, item){
+
+ listOfApplets.push(new Applet(item.Applet_name, item.Applet_url, item.Applet_color, item.Applet_categorie, item.Applet_logo));
+
+ });
+
+ $(".applet").removeClass("hidden");
+
+ console.log(listOfApplets);
+ function protocol(id, name) {
+ this.id = id;
+ this.name = name;
+ this.selected = ko.observable(false);
+ }
+
+ var listOfCategories = [
+ new protocol(1, 'Social'),
+ new protocol(2, 'News'),
+ new protocol(3, 'Education'),
+ new protocol(4, 'Location'),
+ new protocol(5, 'Tools'),
+ ];
+
+
+ var viewModel = {
+ protocoldocs: ko.observableArray(listOfApplets),
+ protocol: ko.observableArray(listOfCategories),
+ selectedProtocol: ko.observableArray(),
+ addprotocol: function (protocol, elem) {
+ var $checkBox = $(elem.srcElement);
+ var isChecked = $checkBox.is(':checked');
+ //If it is checked and not in the array, add it
+ if (isChecked && viewModel.selectedProtocol.indexOf(protocol) < 0) {
+ viewModel.selectedProtocol.push(protocol);
+ }
+ //If it is in the array and not checked remove it
+ else if (!isChecked && viewModel.selectedProtocol.indexOf(protocol) >= 0) {
+ viewModel.selectedProtocol.remove(protocol);
+ }
+ //Need to return to to allow the Checkbox to process checked/unchecked
+ return true;
+ }
+ }
+
+ viewModel.filteredProtocols = ko.computed(function () {
+ var selectedProtocols = ko.utils.arrayFilter(viewModel.protocol(), function (p) {
+ return p.selected();
+ });
+ if (selectedProtocols.length == 0) { //if none selected return all
+ console.log("selected is null");
+ console.log(selectedProtocols.length);
+ console.log(viewModel.protocoldocs());
+ return viewModel.protocoldocs();
+ }
+ else {
+ return ko.utils.arrayFilter(viewModel.protocoldocs(), function (item) {
+ return ko.utils.arrayFilter(selectedProtocols, function (p) {
+ if(p.name == 'All'){
+ return viewModel.protocoldocs();
+ }
+ return p.name == item.Applet_categorie
+ }).length > 0;
+ });
+
+ }
+ })
+
+ ko.applyBindings(viewModel);
+ });
+
+ /*
+ $.get("/apps/opa/getapplets", function(data, status){
+ var i =0;
+ $(data['Applets']).each(function(index, item){
+
+ listOfApplets.push(new Applet(item.Applet_name, item.Applet_url, item.Applet_color, item.Applet_categorie, item.Applet_logo));
+
+ });
+ });
+*/
+
+
+ //var newDropped = false;
+
+ //ko.applyBindings(viewModel, $("#protocoldocs")[0]);
+ // This makes Knockout get to work
+ // ko.applyBindings(model);
+
+ // Configurate toolbar handlers
+ //config_file_operations("", model.fileExtension(), model.saveFileData, model.loadFileData, model.init);
+});
diff --git a/src/opsoro/apps/opa/static/css/animate.min.css b/src/opsoro/apps/opa/static/css/animate.min.css
new file mode 100644
index 0000000..e7dd655
--- /dev/null
+++ b/src/opsoro/apps/opa/static/css/animate.min.css
@@ -0,0 +1,11 @@
+@charset "UTF-8";
+
+/*!
+ * animate.css -http://daneden.me/animate
+ * Version - 3.5.2
+ * Licensed under the MIT license - http://opensource.org/licenses/MIT
+ *
+ * Copyright (c) 2017 Daniel Eden
+ */
+
+.animated{animation-duration:1s;animation-fill-mode:both}.animated.infinite{animation-iteration-count:infinite}.animated.hinge{animation-duration:2s}.animated.bounceIn,.animated.bounceOut,.animated.flipOutX,.animated.flipOutY{animation-duration:.75s}@keyframes bounce{0%,20%,53%,80%,to{animation-timing-function:cubic-bezier(.215,.61,.355,1);transform:translateZ(0)}40%,43%{animation-timing-function:cubic-bezier(.755,.05,.855,.06);transform:translate3d(0,-30px,0)}70%{animation-timing-function:cubic-bezier(.755,.05,.855,.06);transform:translate3d(0,-15px,0)}90%{transform:translate3d(0,-4px,0)}}.bounce{animation-name:bounce;transform-origin:center bottom}@keyframes flash{0%,50%,to{opacity:1}25%,75%{opacity:0}}.flash{animation-name:flash}@keyframes pulse{0%{transform:scaleX(1)}50%{transform:scale3d(1.05,1.05,1.05)}to{transform:scaleX(1)}}.pulse{animation-name:pulse}@keyframes rubberBand{0%{transform:scaleX(1)}30%{transform:scale3d(1.25,.75,1)}40%{transform:scale3d(.75,1.25,1)}50%{transform:scale3d(1.15,.85,1)}65%{transform:scale3d(.95,1.05,1)}75%{transform:scale3d(1.05,.95,1)}to{transform:scaleX(1)}}.rubberBand{animation-name:rubberBand}@keyframes shake{0%,to{transform:translateZ(0)}10%,30%,50%,70%,90%{transform:translate3d(-10px,0,0)}20%,40%,60%,80%{transform:translate3d(10px,0,0)}}.shake{animation-name:shake}@keyframes headShake{0%{transform:translateX(0)}6.5%{transform:translateX(-6px) rotateY(-9deg)}18.5%{transform:translateX(5px) rotateY(7deg)}31.5%{transform:translateX(-3px) rotateY(-5deg)}43.5%{transform:translateX(2px) rotateY(3deg)}50%{transform:translateX(0)}}.headShake{animation-timing-function:ease-in-out;animation-name:headShake}@keyframes swing{20%{transform:rotate(15deg)}40%{transform:rotate(-10deg)}60%{transform:rotate(5deg)}80%{transform:rotate(-5deg)}to{transform:rotate(0deg)}}.swing{transform-origin:top center;animation-name:swing}@keyframes tada{0%{transform:scaleX(1)}10%,20%{transform:scale3d(.9,.9,.9) rotate(-3deg)}30%,50%,70%,90%{transform:scale3d(1.1,1.1,1.1) rotate(3deg)}40%,60%,80%{transform:scale3d(1.1,1.1,1.1) rotate(-3deg)}to{transform:scaleX(1)}}.tada{animation-name:tada}@keyframes wobble{0%{transform:none}15%{transform:translate3d(-25%,0,0) rotate(-5deg)}30%{transform:translate3d(20%,0,0) rotate(3deg)}45%{transform:translate3d(-15%,0,0) rotate(-3deg)}60%{transform:translate3d(10%,0,0) rotate(2deg)}75%{transform:translate3d(-5%,0,0) rotate(-1deg)}to{transform:none}}.wobble{animation-name:wobble}@keyframes jello{0%,11.1%,to{transform:none}22.2%{transform:skewX(-12.5deg) skewY(-12.5deg)}33.3%{transform:skewX(6.25deg) skewY(6.25deg)}44.4%{transform:skewX(-3.125deg) skewY(-3.125deg)}55.5%{transform:skewX(1.5625deg) skewY(1.5625deg)}66.6%{transform:skewX(-.78125deg) skewY(-.78125deg)}77.7%{transform:skewX(.390625deg) skewY(.390625deg)}88.8%{transform:skewX(-.1953125deg) skewY(-.1953125deg)}}.jello{animation-name:jello;transform-origin:center}@keyframes bounceIn{0%,20%,40%,60%,80%,to{animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;transform:scale3d(.3,.3,.3)}20%{transform:scale3d(1.1,1.1,1.1)}40%{transform:scale3d(.9,.9,.9)}60%{opacity:1;transform:scale3d(1.03,1.03,1.03)}80%{transform:scale3d(.97,.97,.97)}to{opacity:1;transform:scaleX(1)}}.bounceIn{animation-name:bounceIn}@keyframes bounceInDown{0%,60%,75%,90%,to{animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;transform:translate3d(0,-3000px,0)}60%{opacity:1;transform:translate3d(0,25px,0)}75%{transform:translate3d(0,-10px,0)}90%{transform:translate3d(0,5px,0)}to{transform:none}}.bounceInDown{animation-name:bounceInDown}@keyframes bounceInLeft{0%,60%,75%,90%,to{animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;transform:translate3d(-3000px,0,0)}60%{opacity:1;transform:translate3d(25px,0,0)}75%{transform:translate3d(-10px,0,0)}90%{transform:translate3d(5px,0,0)}to{transform:none}}.bounceInLeft{animation-name:bounceInLeft}@keyframes bounceInRight{0%,60%,75%,90%,to{animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;transform:translate3d(3000px,0,0)}60%{opacity:1;transform:translate3d(-25px,0,0)}75%{transform:translate3d(10px,0,0)}90%{transform:translate3d(-5px,0,0)}to{transform:none}}.bounceInRight{animation-name:bounceInRight}@keyframes bounceInUp{0%,60%,75%,90%,to{animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;transform:translate3d(0,3000px,0)}60%{opacity:1;transform:translate3d(0,-20px,0)}75%{transform:translate3d(0,10px,0)}90%{transform:translate3d(0,-5px,0)}to{transform:translateZ(0)}}.bounceInUp{animation-name:bounceInUp}@keyframes bounceOut{20%{transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;transform:scale3d(1.1,1.1,1.1)}to{opacity:0;transform:scale3d(.3,.3,.3)}}.bounceOut{animation-name:bounceOut}@keyframes bounceOutDown{20%{transform:translate3d(0,10px,0)}40%,45%{opacity:1;transform:translate3d(0,-20px,0)}to{opacity:0;transform:translate3d(0,2000px,0)}}.bounceOutDown{animation-name:bounceOutDown}@keyframes bounceOutLeft{20%{opacity:1;transform:translate3d(20px,0,0)}to{opacity:0;transform:translate3d(-2000px,0,0)}}.bounceOutLeft{animation-name:bounceOutLeft}@keyframes bounceOutRight{20%{opacity:1;transform:translate3d(-20px,0,0)}to{opacity:0;transform:translate3d(2000px,0,0)}}.bounceOutRight{animation-name:bounceOutRight}@keyframes bounceOutUp{20%{transform:translate3d(0,-10px,0)}40%,45%{opacity:1;transform:translate3d(0,20px,0)}to{opacity:0;transform:translate3d(0,-2000px,0)}}.bounceOutUp{animation-name:bounceOutUp}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.fadeIn{animation-name:fadeIn}@keyframes fadeInDown{0%{opacity:0;transform:translate3d(0,-100%,0)}to{opacity:1;transform:none}}.fadeInDown{animation-name:fadeInDown}@keyframes fadeInDownBig{0%{opacity:0;transform:translate3d(0,-2000px,0)}to{opacity:1;transform:none}}.fadeInDownBig{animation-name:fadeInDownBig}@keyframes fadeInLeft{0%{opacity:0;transform:translate3d(-100%,0,0)}to{opacity:1;transform:none}}.fadeInLeft{animation-name:fadeInLeft}@keyframes fadeInLeftBig{0%{opacity:0;transform:translate3d(-2000px,0,0)}to{opacity:1;transform:none}}.fadeInLeftBig{animation-name:fadeInLeftBig}@keyframes fadeInRight{0%{opacity:0;transform:translate3d(100%,0,0)}to{opacity:1;transform:none}}.fadeInRight{animation-name:fadeInRight}@keyframes fadeInRightBig{0%{opacity:0;transform:translate3d(2000px,0,0)}to{opacity:1;transform:none}}.fadeInRightBig{animation-name:fadeInRightBig}@keyframes fadeInUp{0%{opacity:0;transform:translate3d(0,100%,0)}to{opacity:1;transform:none}}.fadeInUp{animation-name:fadeInUp}@keyframes fadeInUpBig{0%{opacity:0;transform:translate3d(0,2000px,0)}to{opacity:1;transform:none}}.fadeInUpBig{animation-name:fadeInUpBig}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.fadeOut{animation-name:fadeOut}@keyframes fadeOutDown{0%{opacity:1}to{opacity:0;transform:translate3d(0,100%,0)}}.fadeOutDown{animation-name:fadeOutDown}@keyframes fadeOutDownBig{0%{opacity:1}to{opacity:0;transform:translate3d(0,2000px,0)}}.fadeOutDownBig{animation-name:fadeOutDownBig}@keyframes fadeOutLeft{0%{opacity:1}to{opacity:0;transform:translate3d(-100%,0,0)}}.fadeOutLeft{animation-name:fadeOutLeft}@keyframes fadeOutLeftBig{0%{opacity:1}to{opacity:0;transform:translate3d(-2000px,0,0)}}.fadeOutLeftBig{animation-name:fadeOutLeftBig}@keyframes fadeOutRight{0%{opacity:1}to{opacity:0;transform:translate3d(100%,0,0)}}.fadeOutRight{animation-name:fadeOutRight}@keyframes fadeOutRightBig{0%{opacity:1}to{opacity:0;transform:translate3d(2000px,0,0)}}.fadeOutRightBig{animation-name:fadeOutRightBig}@keyframes fadeOutUp{0%{opacity:1}to{opacity:0;transform:translate3d(0,-100%,0)}}.fadeOutUp{animation-name:fadeOutUp}@keyframes fadeOutUpBig{0%{opacity:1}to{opacity:0;transform:translate3d(0,-2000px,0)}}.fadeOutUpBig{animation-name:fadeOutUpBig}@keyframes flip{0%{transform:perspective(400px) rotateY(-1turn);animation-timing-function:ease-out}40%{transform:perspective(400px) translateZ(150px) rotateY(-190deg);animation-timing-function:ease-out}50%{transform:perspective(400px) translateZ(150px) rotateY(-170deg);animation-timing-function:ease-in}80%{transform:perspective(400px) scale3d(.95,.95,.95);animation-timing-function:ease-in}to{transform:perspective(400px);animation-timing-function:ease-in}}.animated.flip{-webkit-backface-visibility:visible;backface-visibility:visible;animation-name:flip}@keyframes flipInX{0%{transform:perspective(400px) rotateX(90deg);animation-timing-function:ease-in;opacity:0}40%{transform:perspective(400px) rotateX(-20deg);animation-timing-function:ease-in}60%{transform:perspective(400px) rotateX(10deg);opacity:1}80%{transform:perspective(400px) rotateX(-5deg)}to{transform:perspective(400px)}}.flipInX{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;animation-name:flipInX}@keyframes flipInY{0%{transform:perspective(400px) rotateY(90deg);animation-timing-function:ease-in;opacity:0}40%{transform:perspective(400px) rotateY(-20deg);animation-timing-function:ease-in}60%{transform:perspective(400px) rotateY(10deg);opacity:1}80%{transform:perspective(400px) rotateY(-5deg)}to{transform:perspective(400px)}}.flipInY{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;animation-name:flipInY}@keyframes flipOutX{0%{transform:perspective(400px)}30%{transform:perspective(400px) rotateX(-20deg);opacity:1}to{transform:perspective(400px) rotateX(90deg);opacity:0}}.flipOutX{animation-name:flipOutX;-webkit-backface-visibility:visible!important;backface-visibility:visible!important}@keyframes flipOutY{0%{transform:perspective(400px)}30%{transform:perspective(400px) rotateY(-15deg);opacity:1}to{transform:perspective(400px) rotateY(90deg);opacity:0}}.flipOutY{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;animation-name:flipOutY}@keyframes lightSpeedIn{0%{transform:translate3d(100%,0,0) skewX(-30deg);opacity:0}60%{transform:skewX(20deg);opacity:1}80%{transform:skewX(-5deg);opacity:1}to{transform:none;opacity:1}}.lightSpeedIn{animation-name:lightSpeedIn;animation-timing-function:ease-out}@keyframes lightSpeedOut{0%{opacity:1}to{transform:translate3d(100%,0,0) skewX(30deg);opacity:0}}.lightSpeedOut{animation-name:lightSpeedOut;animation-timing-function:ease-in}@keyframes rotateIn{0%{transform-origin:center;transform:rotate(-200deg);opacity:0}to{transform-origin:center;transform:none;opacity:1}}.rotateIn{animation-name:rotateIn}@keyframes rotateInDownLeft{0%{transform-origin:left bottom;transform:rotate(-45deg);opacity:0}to{transform-origin:left bottom;transform:none;opacity:1}}.rotateInDownLeft{animation-name:rotateInDownLeft}@keyframes rotateInDownRight{0%{transform-origin:right bottom;transform:rotate(45deg);opacity:0}to{transform-origin:right bottom;transform:none;opacity:1}}.rotateInDownRight{animation-name:rotateInDownRight}@keyframes rotateInUpLeft{0%{transform-origin:left bottom;transform:rotate(45deg);opacity:0}to{transform-origin:left bottom;transform:none;opacity:1}}.rotateInUpLeft{animation-name:rotateInUpLeft}@keyframes rotateInUpRight{0%{transform-origin:right bottom;transform:rotate(-90deg);opacity:0}to{transform-origin:right bottom;transform:none;opacity:1}}.rotateInUpRight{animation-name:rotateInUpRight}@keyframes rotateOut{0%{transform-origin:center;opacity:1}to{transform-origin:center;transform:rotate(200deg);opacity:0}}.rotateOut{animation-name:rotateOut}@keyframes rotateOutDownLeft{0%{transform-origin:left bottom;opacity:1}to{transform-origin:left bottom;transform:rotate(45deg);opacity:0}}.rotateOutDownLeft{animation-name:rotateOutDownLeft}@keyframes rotateOutDownRight{0%{transform-origin:right bottom;opacity:1}to{transform-origin:right bottom;transform:rotate(-45deg);opacity:0}}.rotateOutDownRight{animation-name:rotateOutDownRight}@keyframes rotateOutUpLeft{0%{transform-origin:left bottom;opacity:1}to{transform-origin:left bottom;transform:rotate(-45deg);opacity:0}}.rotateOutUpLeft{animation-name:rotateOutUpLeft}@keyframes rotateOutUpRight{0%{transform-origin:right bottom;opacity:1}to{transform-origin:right bottom;transform:rotate(90deg);opacity:0}}.rotateOutUpRight{animation-name:rotateOutUpRight}@keyframes hinge{0%{transform-origin:top left;animation-timing-function:ease-in-out}20%,60%{transform:rotate(80deg);transform-origin:top left;animation-timing-function:ease-in-out}40%,80%{transform:rotate(60deg);transform-origin:top left;animation-timing-function:ease-in-out;opacity:1}to{transform:translate3d(0,700px,0);opacity:0}}.hinge{animation-name:hinge}@keyframes jackInTheBox{0%{opacity:0;transform:scale(.1) rotate(30deg);transform-origin:center bottom}50%{transform:rotate(-10deg)}70%{transform:rotate(3deg)}to{opacity:1;transform:scale(1)}}.jackInTheBox{animation-name:jackInTheBox}@keyframes rollIn{0%{opacity:0;transform:translate3d(-100%,0,0) rotate(-120deg)}to{opacity:1;transform:none}}.rollIn{animation-name:rollIn}@keyframes rollOut{0%{opacity:1}to{opacity:0;transform:translate3d(100%,0,0) rotate(120deg)}}.rollOut{animation-name:rollOut}@keyframes zoomIn{0%{opacity:0;transform:scale3d(.3,.3,.3)}50%{opacity:1}}.zoomIn{animation-name:zoomIn}@keyframes zoomInDown{0%{opacity:0;transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;transform:scale3d(.475,.475,.475) translate3d(0,60px,0);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInDown{animation-name:zoomInDown}@keyframes zoomInLeft{0%{opacity:0;transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;transform:scale3d(.475,.475,.475) translate3d(10px,0,0);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInLeft{animation-name:zoomInLeft}@keyframes zoomInRight{0%{opacity:0;transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInRight{animation-name:zoomInRight}@keyframes zoomInUp{0%{opacity:0;transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInUp{animation-name:zoomInUp}@keyframes zoomOut{0%{opacity:1}50%{opacity:0;transform:scale3d(.3,.3,.3)}to{opacity:0}}.zoomOut{animation-name:zoomOut}@keyframes zoomOutDown{40%{opacity:1;transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}to{opacity:0;transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);transform-origin:center bottom;animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomOutDown{animation-name:zoomOutDown}@keyframes zoomOutLeft{40%{opacity:1;transform:scale3d(.475,.475,.475) translate3d(42px,0,0)}to{opacity:0;transform:scale(.1) translate3d(-2000px,0,0);transform-origin:left center}}.zoomOutLeft{animation-name:zoomOutLeft}@keyframes zoomOutRight{40%{opacity:1;transform:scale3d(.475,.475,.475) translate3d(-42px,0,0)}to{opacity:0;transform:scale(.1) translate3d(2000px,0,0);transform-origin:right center}}.zoomOutRight{animation-name:zoomOutRight}@keyframes zoomOutUp{40%{opacity:1;transform:scale3d(.475,.475,.475) translate3d(0,60px,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}to{opacity:0;transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);transform-origin:center bottom;animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomOutUp{animation-name:zoomOutUp}@keyframes slideInDown{0%{transform:translate3d(0,-100%,0);visibility:visible}to{transform:translateZ(0)}}.slideInDown{animation-name:slideInDown}@keyframes slideInLeft{0%{transform:translate3d(-100%,0,0);visibility:visible}to{transform:translateZ(0)}}.slideInLeft{animation-name:slideInLeft}@keyframes slideInRight{0%{transform:translate3d(100%,0,0);visibility:visible}to{transform:translateZ(0)}}.slideInRight{animation-name:slideInRight}@keyframes slideInUp{0%{transform:translate3d(0,100%,0);visibility:visible}to{transform:translateZ(0)}}.slideInUp{animation-name:slideInUp}@keyframes slideOutDown{0%{transform:translateZ(0)}to{visibility:hidden;transform:translate3d(0,100%,0)}}.slideOutDown{animation-name:slideOutDown}@keyframes slideOutLeft{0%{transform:translateZ(0)}to{visibility:hidden;transform:translate3d(-100%,0,0)}}.slideOutLeft{animation-name:slideOutLeft}@keyframes slideOutRight{0%{transform:translateZ(0)}to{visibility:hidden;transform:translate3d(100%,0,0)}}.slideOutRight{animation-name:slideOutRight}@keyframes slideOutUp{0%{transform:translateZ(0)}to{visibility:hidden;transform:translate3d(0,-100%,0)}}.slideOutUp{animation-name:slideOutUp}
\ No newline at end of file
diff --git a/src/opsoro/apps/opa/static/data/.fuse_hidden00000e1000000003 b/src/opsoro/apps/opa/static/data/.fuse_hidden00000e1000000003
new file mode 100644
index 0000000..202cdb8
--- /dev/null
+++ b/src/opsoro/apps/opa/static/data/.fuse_hidden00000e1000000003
@@ -0,0 +1,47 @@
+{ "Applets":[
+ {
+ "Applet_name":"Weather Applet",
+ "Applet_url":"https://ifttt.com/applets/DbSc6XqZ/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/"
+ },
+ {
+ "Applet_name":"Alarm",
+ "Applet_url":"https://ifttt.com/applets/yxcUavkK/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/"
+ },
+ {
+ "Applet_name":"Coming home",
+ "Applet_url":"https://ifttt.com/applets/UcdTPWAV/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/"
+ },
+ {
+ "Applet_name":"Event",
+ "Applet_url":"https://ifttt.com/applets/wjLHMbBx/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/"
+ },
+ {
+ "Applet_name":"ISS",
+ "Applet_url":"https://ifttt.com/applets/Ld8aRTUD/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/"
+ },
+ {
+ "Applet_name":"Leaving work",
+ "Applet_url":"https://ifttt.com/applets/Wcw42HM5/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/"
+ },
+ {
+ "Applet_name":"Youtube",
+ "Applet_url":"https://ifttt.com/applets/CTjyYCQV/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/"
+ },
+ {
+ "Applet_name":"The New York Times",
+ "Applet_url":"https://ifttt.com/applets/LtNGA4Xm/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/"
+ },
+ {
+ "Applet_name":"SMS",
+ "Applet_url":"https://ifttt.com/applets/vsKwNSpG/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/"
+ },
+ {
+ "Applet_name":"Christmas",
+ "Applet_url":"https://ifttt.com/applets/VVktLwKe/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/"
+ },
+ {
+ "Applet_name":"Facebook status",
+ "Applet_url":"https://ifttt.com/applets/VVktLwKe/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/"
+ }
+]
+}
\ No newline at end of file
diff --git a/src/opsoro/apps/opa/static/images/Thumbs.db b/src/opsoro/apps/opa/static/images/Thumbs.db
new file mode 100755
index 0000000..3ae7e02
Binary files /dev/null and b/src/opsoro/apps/opa/static/images/Thumbs.db differ
diff --git a/src/opsoro/apps/opa/static/images/screenshot-opsoro-step2.png b/src/opsoro/apps/opa/static/images/screenshot-opsoro-step2.png
new file mode 100755
index 0000000..bce7778
Binary files /dev/null and b/src/opsoro/apps/opa/static/images/screenshot-opsoro-step2.png differ
diff --git a/src/opsoro/apps/opa/static/images/screenshot-opsoro-step3.png b/src/opsoro/apps/opa/static/images/screenshot-opsoro-step3.png
new file mode 100755
index 0000000..12737b8
Binary files /dev/null and b/src/opsoro/apps/opa/static/images/screenshot-opsoro-step3.png differ
diff --git a/src/opsoro/apps/opa/static/images/screenshot-opsoro-step4.png b/src/opsoro/apps/opa/static/images/screenshot-opsoro-step4.png
new file mode 100755
index 0000000..d73f3a3
Binary files /dev/null and b/src/opsoro/apps/opa/static/images/screenshot-opsoro-step4.png differ
diff --git a/src/opsoro/apps/opa/static/images/screenshot-opsoro-step5.png b/src/opsoro/apps/opa/static/images/screenshot-opsoro-step5.png
new file mode 100755
index 0000000..ee0747b
Binary files /dev/null and b/src/opsoro/apps/opa/static/images/screenshot-opsoro-step5.png differ
diff --git a/src/opsoro/apps/opa/static/json/Activity.json b/src/opsoro/apps/opa/static/json/Activity.json
new file mode 100644
index 0000000..47a6ebe
--- /dev/null
+++ b/src/opsoro/apps/opa/static/json/Activity.json
@@ -0,0 +1 @@
+{"Activity": [{"play": {"play2": "You are tagged in a photo of {{From}}", "play1": "New facebook notification "}, "service": "Google Calendar", "color": "#2c6efc", "say": "True", "time": "14:15:14", "date": "2017-06-07"}, {"play": {"play2": "You are tagged in a photo of {{From}}", "play1": "New facebook notification "}, "service": "Facebook", "color": "#3b579d", "say": "True", "time": "14:18:49", "date": "2017-06-07"}, {"play": {"play2": "You are tagged in a photo of {{From}}", "play1": "New facebook notification "}, "service": "Alarm", "color": "#000000", "say": "True", "time": "17:50:20", "date": "2017-06-07"}, {"play": {"1": "New facebook notification ", "3": "", "2": "You are tagged in a photo"}, "service": "Facebook", "color": "#3b579d", "say": "True", "time": "18:25:05", "date": "2017-06-07"}, {"play": {"1": "New facebook notification ", "3": "", "2": "You are tagged in a photo"}, "service": "Facebook", "color": "#3b579d", "say": "True", "time": "18:26:56", "date": "2017-06-07"}, {"play": {"1": "New facebook notification ", "3": "", "2": "You are tagged in a photo"}, "service": "Facebook", "color": "#3b579d", "say": "True", "time": "19:22:17", "date": "2017-06-07"}, {"play": {"1": "One of your parents just arrived at home", "3": "", "2": " June 07, 2017 at 10:45PM"}, "service": "Location", "color": "#0099ff", "say": "True", "time": "22:46:02", "date": "2017-06-07"}, {"play": {"1": "New facebook notification ", "3": "", "2": "You are tagged in a photo"}, "service": "Facebook", "color": "#3b579d", "say": "True", "time": "14:46:36", "date": "2017-06-08"}, {"play": {"1": "New facebook notification ", "3": "", "2": "You are tagged in a photo"}, "service": "Facebook", "color": "#3b579d", "say": "True", "time": "14:47:32", "date": "2017-06-08"}, {"play": {"1": "It is June 08, 2017 at 03:00PM", "3": "The wind speed is 23 Kilometers per hour", "2": "The weather of the moment is Partly Cloudy and 23 degrees Celsius"}, "service": "Weather", "color": "#000000", "say": "True", "time": "15:00:30", "date": "2017-06-08"}, {"play": {"1": "New notification from Twitter", "3": " Sander V", "2": "You have a new follower"}, "service": "Twitter", "color": "#00abec", "say": "True", "time": "15:22:53", "date": "2017-06-08"}, {"play": {"1": "New track saved to playlist", "3": " Girls Keep Drinking by Compact Disk Dummies", "2": "New track"}, "service": "Spotify", "color": "#1ED760", "say": "True", "time": "15:29:26", "date": "2017-06-08"}, {"play": {"1": "New track saved to playlist", "3": " Jewels by Warhola", "2": "New track"}, "service": "Spotify", "color": "#1ED760", "say": "True", "time": "15:30:04", "date": "2017-06-08"}, {"play": {"1": "New track saved to playlist", "3": " Slide by Calvin Harris, Frank Ocean, Migos", "2": "New track"}, "service": "Spotify", "color": "#1ED760", "say": "True", "time": "15:37:25", "date": "2017-06-08"}, {"play": {"1": "New track saved to playlist", "3": " Slide by Calvin Harris, Frank Ocean, Migos", "2": "New track"}, "service": "Spotify", "color": "#1ED760", "say": "True", "time": "15:37:29", "date": "2017-06-08"}, {"play": {"1": "New track saved to playlist", "3": " Weak by AJR", "2": "New track"}, "service": "Spotify", "color": "#1ED760", "say": "True", "time": "15:47:47", "date": "2017-06-08"}, {"play": {"1": "New track saved to playlist DAMN! That's old", "3": " Green Light by Lorde", "2": "New track"}, "service": "Spotify", "color": "#1ED760", "say": "True", "time": "15:49:37", "date": "2017-06-08"}, {"play": {"1": "BREAKING NEWS", "3": "Read more on Fox News", "2": " Fox News Breaking News Alert, COMEY COMES OUT SWINGING: Comey rips Trump, blasts 'lies' about performance"}, "service": "Fox News", "color": "#003366", "say": "True", "time": "16:27:46", "date": "2017-06-08"}, {"play": {"1": "BREAKING NEWS", "3": "Read more on Fox News", "2": " Fox News Breaking News Alert, COMEY'S CLOSE CALL: Comey says he considered calling for a special counsel for Clinton server probe."}, "service": "Fox News", "color": "#003366", "say": "True", "time": "18:38:49", "date": "2017-06-08"}, {"play": {"1": "BREAKING NEWS", "3": "Read more on Fox News", "2": " Fox News Breaking News Alert, COMEY SHOW ENDS: James Comey's public appearance before the Senate Intelligence Committee has ended, and the former FBI director will now meet with lawmakers in private."}, "service": "Fox News", "color": "#003366", "say": "True", "time": "18:53:28", "date": "2017-06-08"}, {"play": {"1": "New track saved to playlist DAMN! That's old", "3": " Permission To Love by Hayden James", "2": "New track"}, "service": "Spotify", "color": "#1ED760", "say": "True", "time": "20:15:54", "date": "2017-06-08"}, {"play": {"1": "BREAKING NEWS", "3": "Read more on Fox News", "2": " Fox News Breaking News Alert, CHURCH BUS CRASH: At least 17 hurt in accident outside Atlanta"}, "service": "Fox News", "color": "#003366", "say": "True", "time": "23:08:24", "date": "2017-06-08"}, {"play": {"1": "One of your parents is on their way home", "3": "", "2": "At June 09, 2017 at 10:34AM they left work"}, "service": "Location", "color": "#0099ff", "say": "True", "time": "11:37:33", "date": "2017-06-09"}, {"play": {"1": "New facebook notification ", "3": "", "2": "You are tagged in a photo"}, "service": "Facebook", "color": "#3b579d", "say": "True", "time": "14:05:34", "date": "2017-06-09"}, {"play": {"1": "New facebook notification ", "3": "", "2": "You are tagged in a photo"}, "service": "Facebook", "color": "#3b579d", "say": "True", "time": "13:34:33", "date": "2017-06-09"}, {"play": {"1": "New facebook notification ", "3": "", "2": "You are tagged in a photo"}, "service": "Facebook", "color": "#3b579d", "say": "True", "time": "13:36:49", "date": "2017-06-09"}]}
\ No newline at end of file
diff --git a/src/opsoro/apps/opa/static/json/Applets.json b/src/opsoro/apps/opa/static/json/Applets.json
new file mode 100644
index 0000000..448b0ee
--- /dev/null
+++ b/src/opsoro/apps/opa/static/json/Applets.json
@@ -0,0 +1,108 @@
+{ "Applets":[
+ {
+ "Applet_name":"Weather Applet",
+ "Applet_url":"https://ifttt.com/applets/DbSc6XqZ/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/",
+ "Applet_color":"#1d9a59",
+ "Applet_categorie":"News",
+ "Applet_logo":"https://applets.imgix.net/https%3A%2F%2Fassets.ifttt.com%2Fimages%2Fchannels%2F7%2Ficons%2Fon_color_large.png%3Fversion%3D0?ixlib=rails-2.1.3&w=100&h=100&auto=compress&s=a779437e46041cd85963c1e169aea000"
+ },
+ {
+ "Applet_name":"Alarm",
+ "Applet_url":"https://ifttt.com/applets/yxcUavkK/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/",
+ "Applet_color":"#333333",
+ "Applet_categorie":"Tools",
+ "Applet_logo":"https://applets.imgix.net/https%3A%2F%2Fassets.ifttt.com%2Fimages%2Fchannels%2F3%2Ficons%2Fon_color_large.png%3Fversion%3D0?ixlib=rails-2.1.3&w=100&h=100&auto=compress&s=3e9ab3d7d45a360b93f96f7131ae7169"
+ },
+ {
+ "Applet_name":"Coming home",
+ "Applet_url":"https://ifttt.com/applets/UcdTPWAV/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/",
+ "Applet_color":"#0099ff",
+ "Applet_categorie":"Location",
+ "Applet_logo":"https://applets.imgix.net/https%3A%2F%2Fassets.ifttt.com%2Fimages%2Fchannels%2F941030000%2Ficons%2Fon_color_large.png%3Fversion%3D0?ixlib=rails-2.1.3&w=100&h=100&auto=compress&s=c1e46d4cb4a148c6a7fd55ccf2c5a21b"
+ },
+ {
+ "Applet_name":"Event",
+ "Applet_url":"https://ifttt.com/applets/wjLHMbBx/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/",
+ "Applet_color":"#2c6efc",
+ "Applet_categorie":"Location",
+ "Applet_logo":"https://applets.imgix.net/https%3A%2F%2Fassets.ifttt.com%2Fimages%2Fchannels%2F36%2Ficons%2Fon_color_large.png%3Fversion%3D0?ixlib=rails-2.1.3&w=100&h=100&auto=compress&s=f21aba281510145b7032da7abaa0ef74"
+ },
+ {
+ "Applet_name":"ISS",
+ "Applet_url":"https://ifttt.com/applets/Ld8aRTUD/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/",
+ "Applet_color":"#2e294e",
+ "Applet_categorie":"Education",
+ "Applet_logo":"https://applets.imgix.net/https%3A%2F%2Fassets.ifttt.com%2Fimages%2Fchannels%2F1829340444%2Ficons%2Fon_color_large.png%3Fversion%3D0?ixlib=rails-2.1.3&w=100&h=100&auto=compress&s=e631efb18ee2b3e9c757c0a1f96c27c0"
+ },
+ {
+ "Applet_name":"Leaving work",
+ "Applet_url":"https://ifttt.com/applets/Wcw42HM5/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/",
+ "Applet_color":"#0099ff",
+ "Applet_categorie":"Location",
+ "Applet_logo":"https://applets.imgix.net/https%3A%2F%2Fassets.ifttt.com%2Fimages%2Fchannels%2F941030000%2Ficons%2Fon_color_large.png%3Fversion%3D0?ixlib=rails-2.1.3&w=100&h=100&auto=compress&s=c1e46d4cb4a148c6a7fd55ccf2c5a21b"
+ },
+ {
+ "Applet_name":"Youtube",
+ "Applet_url":"https://ifttt.com/applets/CTjyYCQV/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/",
+ "Applet_color":"#e52d27",
+ "Applet_categorie":"Social",
+ "Applet_logo":"https://applets.imgix.net/https%3A%2F%2Fassets.ifttt.com%2Fimages%2Fchannels%2F32%2Ficons%2Fon_color_large.png%3Fversion%3D0?ixlib=rails-2.1.3&w=100&h=100&auto=compress&s=25656b78c56a17d5f6056231bcd54a88"
+ },
+ {
+ "Applet_name":"The New York Times",
+ "Applet_url":"https://ifttt.com/applets/LtNGA4Xm/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/",
+ "Applet_color":"#333333",
+ "Applet_categorie":"News",
+ "Applet_logo":"https://applets.imgix.net/https%3A%2F%2Fassets.ifttt.com%2Fimages%2Fchannels%2F89%2Ficons%2Fon_color_large.png%3Fversion%3D0?ixlib=rails-2.1.3&w=100&h=100&auto=compress&s=2a64565a7e995c4437385c0fd1cd2449"
+ },
+ {
+ "Applet_name":"SMS",
+ "Applet_url":"https://ifttt.com/applets/vsKwNSpG/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/",
+ "Applet_color":"#1d9a59",
+ "Applet_categorie":"Tools",
+ "Applet_logo":"https://applets.imgix.net/https%3A%2F%2Fassets.ifttt.com%2Fimages%2Fchannels%2F1322033008%2Ficons%2Fon_color_large.png%3Fversion%3D0?ixlib=rails-2.1.3&w=100&h=100&auto=compress&s=60ac0ffb1b7842577b10ee4ba99a4d0d"
+ },
+ {
+ "Applet_name":"Christmas",
+ "Applet_url":"https://ifttt.com/applets/VVktLwKe/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/",
+ "Applet_color":"#e52d27",
+ "Applet_categorie":"Tools",
+ "Applet_logo":"https://applets.imgix.net/https%3A%2F%2Fassets.ifttt.com%2Fimages%2Fchannels%2F696562578%2Ficons%2Fon_color_large.png%3Fversion%3D0?ixlib=rails-2.1.3&w=100&h=100&auto=compress&s=71cb85615aa8ebb9c9f1096e02b02e33"
+ },
+ {
+ "Applet_name":"Facebook status",
+ "Applet_url":"https://ifttt.com/applets/DvEUeRmd/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/",
+ "Applet_color":"#3b579d",
+ "Applet_categorie":"Social",
+ "Applet_logo":"https://applets.imgix.net/https%3A%2F%2Fassets.ifttt.com%2Fimages%2Fchannels%2F10%2Ficons%2Fon_color_large.png%3Fversion%3D0?ixlib=rails-2.1.3&w=100&h=100&auto=compress&s=198287ecad0ec524d2d392508ca7ccd9"
+ },
+ {
+ "Applet_name":"New follower Twitter",
+ "Applet_url":"https://ifttt.com/applets/UKvHk432/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/",
+ "Applet_color":"#00abec",
+ "Applet_categorie":"Social",
+ "Applet_logo":"https://applets.imgix.net/https%3A%2F%2Fassets.ifttt.com%2Fimages%2Fchannels%2F2%2Ficons%2Fon_color_large.png%3Fversion%3D0?ixlib=rails-2.1.3&w=100&h=100&auto=compress&s=7053b15a8babcda62c7252482714819a"
+ },
+ {
+ "Applet_name":"New track in playlist",
+ "Applet_url":"https://ifttt.com/applets/eZdLvymM/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/",
+ "Applet_color":"#1ED760",
+ "Applet_categorie":"Social",
+ "Applet_logo":"https://applets.imgix.net/https%3A%2F%2Fassets.ifttt.com%2Fimages%2Fchannels%2F51464135%2Ficons%2Fon_color_large.png%3Fversion%3D0?ixlib=rails-2.1.3&w=100&h=100&auto=compress&s=7a11ebf35fb5b43224582137708af9b0"
+ },
+ {
+ "Applet_name":"Mentioned in Tweet",
+ "Applet_url":"https://ifttt.com/applets/Fxuk37Cn/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/",
+ "Applet_color":"#00abec",
+ "Applet_categorie":"Social",
+ "Applet_logo":"https://applets.imgix.net/https%3A%2F%2Fassets.ifttt.com%2Fimages%2Fchannels%2F2%2Ficons%2Fon_color_large.png%3Fversion%3D0?ixlib=rails-2.1.3&w=100&h=100&auto=compress&s=7053b15a8babcda62c7252482714819a"
+ },
+ {
+ "Applet_name":"Breaking News",
+ "Applet_url":"https://ifttt.com/applets/gyuvVb5p/embed?redirect_uri=http://opa.eu.ngrok.io/apps/opa/",
+ "Applet_color":"#003366",
+ "Applet_categorie":"News",
+ "Applet_logo":"https://applets.imgix.net/https%3A%2F%2Fassets.ifttt.com%2Fimages%2Fchannels%2F789804492%2Ficons%2Fon_color_large.png%3Fversion%3D0?ixlib=rails-2.1.3&w=100&h=100&auto=compress&s=d8980de80e654aa336f8cf3febdbdb58"
+ }
+]
+}
\ No newline at end of file
diff --git a/src/opsoro/apps/opa/static/json/Commands.json b/src/opsoro/apps/opa/static/json/Commands.json
new file mode 100644
index 0000000..d66cf98
--- /dev/null
+++ b/src/opsoro/apps/opa/static/json/Commands.json
@@ -0,0 +1,22 @@
+{ "Commands":[
+ {
+ "Command_id": "1",
+ "Command_name":"Status",
+ "Command_color":"#3b579d",
+ "Command_description": "Status update:",
+ "Command_uses": "Facebook",
+ "Command_type": "ifttt",
+ "Command_eventname": "test",
+ "Command_customizeable": true
+ },
+ {
+ "Command_id": "2",
+ "Command_name":"Robot Command",
+ "Command_color":"#fff222",
+ "Command_description": "Robot command: do this",
+ "Command_uses": "Opsoro",
+ "Command_type": "robot",
+ "Command_eventname": "",
+ "Command_customizeable": false
+ }
+]}
\ No newline at end of file
diff --git a/src/opsoro/apps/opa/templates/opa.html b/src/opsoro/apps/opa/templates/opa.html
new file mode 100644
index 0000000..5398071
--- /dev/null
+++ b/src/opsoro/apps/opa/templates/opa.html
@@ -0,0 +1,325 @@
+{% extends "app_base.html" %}
+{% block app_head %}
+
+
+
+
+
+
+{% endblock %}
+
+
+{% block sidebar_left %}{% endblock %}
+{% block sidebar_right %}{% endblock %}
+
+{% block app_content %}
+