diff --git a/modules/__init__.py b/modules/__init__.py index bf9d008..e69de29 100755 --- a/modules/__init__.py +++ b/modules/__init__.py @@ -1,72 +0,0 @@ -import json -import pprint -import sys, os -from flask import Flask, render_template, redirect -from flask_socketio import SocketIO, emit - -import logging -# Define the WSGI application object - -from app_config import * -import pprint - -from modules.core.db import get_db - - -@app.route('/') -def index(): - return redirect('ui') - - -# Define the database object which is imported -# by modules and controllers - - -import modules.steps -import modules.config -import modules.logs -import modules.sensors -import modules.actor -import modules.notification -import modules.fermenter -from modules.addon.endpoints import initPlugins -import modules.ui -import modules.system -import modules.buzzer -import modules.stats -import modules.kettle -import modules.recipe_import -import modules.core.db_mirgrate - -from app_config import cbpi -# Build the database: -# This will create the database file using SQLAlchemy - - -pp = pprint.PrettyPrinter(indent=6) - - -def init_db(): - print "INIT DB" - with app.app_context(): - db = get_db() - - try: - with app.open_resource('../config/schema.sql', mode='r') as f: - db.cursor().executescript(f.read()) - - db.commit() - except Exception as e: - pass - -init_db() -initPlugins() -cbpi.run_init() - -cbpi.run_background_processes() - - - -app.logger.info("##########################################") -app.logger.info("### STARTUP COMPLETE") -app.logger.info("##########################################") \ No newline at end of file diff --git a/modules/action/__init__.py b/modules/action/__init__.py new file mode 100644 index 0000000..715fdaa --- /dev/null +++ b/modules/action/__init__.py @@ -0,0 +1,31 @@ +import json +from flask_classy import FlaskView, route +from modules.core.core import cbpi + +class ActionView(FlaskView): + + @route('/', methods=['POST']) + def action(self, action): + """ + Call global action button + --- + tags: + - action + responses: + 200: + description: action invoked + """ + print self.cbpi.cache["actions"] + self.cbpi.cache["actions"][action]["function"](self.cbpi) + + return ('',204) + +@cbpi.addon.core.initializer() +def init(cbpi): + """ + Initializer for the message module + :param app: the flask app + :return: None + """ + ActionView.cbpi = cbpi + ActionView.register(cbpi._app, route_base='/api/action') diff --git a/modules/actor/__init__.py b/modules/actor/__init__.py old mode 100644 new mode 100755 index c966d27..3b07572 --- a/modules/actor/__init__.py +++ b/modules/actor/__init__.py @@ -1,74 +1,183 @@ -import time -from flask_classy import route -from modules import DBModel, cbpi -from modules.core.baseview import BaseView - -class Actor(DBModel): - __fields__ = ["name","type", "config", "hide"] - __table_name__ = "actor" - __json_fields__ = ["config"] - -class ActorView(BaseView): - model = Actor - cache_key = "actors" - - @classmethod - def post_init_callback(self, obj): - obj.state = 0 - obj.power = 100 - - def _post_post_callback(self, m): - self.api.init_actor(m.id) - - def _post_put_callback(self, m): - - self.api.init_actor(m.id) - - @route("/switch/on", methods=["POST"]) - def on(self, id): - self.api.switch_actor_on(id) - return ('', 204) - - @route("/switch/off", methods=["POST"]) - def off(self, id): - self.api.switch_actor_off(id) - return ('', 204) - - @route("/power/", methods=["POST"]) - def power(self, id, power): - self.api.actor_power(id, power) - return ('', 204) - - @route("/toggle", methods=["POST"]) - def toggle(self, id): - - if self.api.cache.get("actors").get(id).state == 0: - self.on(id) - else: - self.off(id) - return ('', 204) - - def toggleTimeJob(self, id, t): - self.api.cache.get("actors").get(int(id)).timer = int(time.time()) + int(t) - self.toggle(int(id)) - self.api.socketio.sleep(t) - self.api.cache.get("actors").get(int(id)).timer = None - self.toggle(int(id)) - - @route("//toggle/", methods=["POST"]) - def toggleTime(self, id, t): - t = self.api.socketio.start_background_task(target=self.toggleTimeJob, id=id, t=t) - return ('', 204) - - @route('/action/', methods=["POST"]) - def action(self, id, method): - - cbpi.cache.get("actors").get(id).instance.__getattribute__(method)() - return ('', 204) - - -@cbpi.initalizer(order=1000) -def init(cbpi): - ActorView.register(cbpi.app, route_base='/api/actor') - ActorView.init_cache() - cbpi.init_actors() \ No newline at end of file +import time +from flask_classy import route +from modules.core.db import DBModel +from modules.core.core import cbpi +from modules.core.baseview import BaseView +from modules.database.dbmodel import Actor + + +class ActorView(BaseView): + model = Actor + cache_key = "actors" + + @classmethod + def post_init_callback(self, obj): + obj.state = 0 + obj.power = 100 + + def _post_post_callback(self, m): + self.api.actor.init_one(m.id) + + def _post_put_callback(self, m): + self.api.actor.init_one(m.id) + + @route("/switch/on", methods=["POST"]) + def on(self, id): + """ + Switch actor on + --- + tags: + - actor + parameters: + - in: path + name: id + schema: + type: integer + required: true + description: Numeric ID of the actor + responses: + 200: + description: Actor switched on + """ + self.api.actor.on(id) + return ('', 204) + + @route("/switch/off", methods=["POST"]) + def off(self, id): + """ + Switch actor off + --- + tags: + - actor + parameters: + - in: path + name: id + schema: + type: integer + required: true + description: Numeric ID of the actor + responses: + 200: + description: Actor switched off + """ + self.api.actor.off(id) + return ('', 204) + + @route("/power/", methods=["POST"]) + def power(self, id, power): + """ + Set Actor Power + --- + tags: + - actor + parameters: + - in: path + name: id + schema: + type: integer + required: true + description: Numeric ID of the actor + - in: path + name: power + schema: + type: integer + required: true + description: Power value between 0 - 100 + responses: + 200: + description: Actor power set + """ + self.api.actor.power(id, power) + return ('', 204) + + @route("/toggle", methods=["POST"]) + def toggle(self, id): + """ + Toggle Actor + --- + tags: + - actor + parameters: + - in: path + name: id + schema: + type: integer + required: true + description: Numeric ID of the actor + responses: + 200: + description: Actor toggled + """ + if self.api.cache.get("actors").get(id).state == 0: + self.on(id) + else: + self.off(id) + return ('', 204) + + def toggleTimeJob(self, id, t): + self.api.cache.get("actors").get(int(id)).timer = int(time.time()) + int(t) + self.toggle(int(id)) + self.api._socketio.sleep(t) + self.api.cache.get("actors").get(int(id)).timer = None + self.toggle(int(id)) + + @route("//toggle/", methods=["POST"]) + def toggleTime(self, id, t): + """ + Toggle Actor for a defined time + --- + tags: + - actor + parameters: + - in: path + name: id + schema: + type: integer + required: true + description: Numeric ID of the actor + - in: path + name: time + schema: + type: integer + required: true + description: time in seconds + responses: + 200: + description: Actor toggled + """ + t = self.api._socketio.start_background_task(target=self.toggleTimeJob, id=id, t=t) + return ('', 204) + + @route('/action/', methods=["POST"]) + def action(self, id, method): + """ + Actor Action + --- + tags: + - actor + parameters: + - in: path + name: id + schema: + type: integer + required: true + description: Numeric ID of the actor + - in: path + name: method + schema: + type: string + required: true + description: action method name + responses: + 200: + description: Actor Action called + """ + cbpi.cache.get("actors").get(id).instance.__getattribute__(method)() + return ('', 204) + + +@cbpi.addon.core.initializer(order=1000) +def init(cbpi): + ActorView.register(cbpi._app, route_base='/api/actor') + ActorView.init_cache() + #cbpi.init_actors() diff --git a/modules/addon/__init__.py b/modules/addon/__init__.py deleted file mode 100644 index b89697f..0000000 --- a/modules/addon/__init__.py +++ /dev/null @@ -1 +0,0 @@ -import endpoints \ No newline at end of file diff --git a/modules/addon/endpoints.py b/modules/addon/endpoints.py deleted file mode 100644 index 83397b9..0000000 --- a/modules/addon/endpoints.py +++ /dev/null @@ -1,199 +0,0 @@ -import json - -import sys -from flask import Blueprint, request, send_from_directory -from importlib import import_module -from modules import socketio, cbpi - - -from git import Repo -import os -import requests -import yaml -import shutil - -blueprint = Blueprint('addon', __name__) - - -modules = {} - -def merge(source, destination): - """ - Helper method to merge two dicts - :param source: - :param destination: - :return: - """ - for key, value in source.items(): - if isinstance(value, dict): - # get node or create one - node = destination.setdefault(key, {}) - merge(value, node) - else: - destination[key] = value - - return destination - -@blueprint.route('/', methods=['GET']) -def getPlugins(): - """ - Endpoint for all plugins - :return: - """ - result = [] - for filename in os.listdir("./modules/plugins"): - if filename.endswith(".DS_Store") or filename.endswith(".py") or filename.endswith(".pyc"): - continue - result.append(filename) - - return json.dumps(result) - -@blueprint.route('/', methods=['GET']) -def getFile(name): - """ - Returns plugin code - :param name: plugin name - :return: the plugin code from __init__.py - """ - return send_from_directory('./plugins/'+name, "__init__.py") - -@blueprint.route('/', methods=['PUT']) -def createPlugin(name): - - """ - Create a new plugin file - :param name: the plugin name - :return: empty http response 204 - """ - if not os.path.exists("./modules/plugins/"+name): - os.makedirs("./modules/plugins/"+name) - with open("./modules/plugins/" + name + "/__init__.py", "wb") as fo: - fo.write("") - cbpi.emit_message("PLUGIN %s CREATED" % (name)) - return ('', 204) - else: - cbpi.emit_message("Failed to create plugin %s. Name arlready in use" % (name)) - return ('', 500) - - - - -@blueprint.route('/', methods=['POST']) -def saveFile(name): - - """ - save plugin code. code is provides via http body - :param name: the plugin name - :return: empty http reponse - """ - with open("./modules/plugins/"+name+"/__init__.py", "wb") as fo: - fo.write(request.get_data()) - cbpi.emit_message("PLUGIN %s SAVED" % (name)) - - return ('', 204) - -@blueprint.route('/', methods=['DELETE']) -def deletePlugin(name): - - """ - Delete plugin - :param name: plugin name - :return: HTTP 204 if ok - HTTP 500 if plugin not exists - """ - if os.path.isdir("./modules/plugins/"+name) is False: - return ('Dir Not found', 500) - shutil.rmtree("./modules/plugins/"+name) - cbpi.notify("Plugin deleted", "Plugin %s deleted successfully" % name) - return ('', 204) - -@blueprint.route('//reload/', methods=['POST']) -def reload(name): - """ - hot reload plugnin - :param name: - :return: - """ - try: - if name in cache["modules"]: - reload(cache["modules"][name]) - cbpi.emit_message("REALOD OF PLUGIN %s SUCCESSFUL" % (name)) - return ('', 204) - else: - cache["modules"][name] = import_module("modules.plugins.%s" % (name)) - return ('', 204) - except Exception as e: - cbpi.emit_message("REALOD OF PLUGIN %s FAILED" % (name)) - return json.dumps(e.message) - - -@blueprint.route('/list', methods=['GET']) -def plugins(): - """ - Read the central plugin yaml to get a list of all official plugins - :return: - """ - response = requests.get("https://raw.githubusercontent.com/Manuel83/craftbeerpi-plugins/master/plugins.yaml") - cbpi.cache["plugins"] = merge(yaml.load(response.text), cbpi.cache["plugins"]) - for key, value in cbpi.cache["plugins"].iteritems(): - value["installed"] = os.path.isdir("./modules/plugins/%s/" % (key)) - - return json.dumps(cbpi.cache["plugins"]) - - -@blueprint.route('//download', methods=['POST']) -def download_addon(name): - - plugin = cbpi.cache["plugins"].get(name) - plugin["loading"] = True - if plugin is None: - return ('', 404) - try: - Repo.clone_from(plugin.get("repo_url"), "./modules/plugins/%s/" % (name)) - cbpi.notify("Download successful", "Plugin %s downloaded successfully" % name) - finally: - plugin["loading"] = False - - return ('', 204) - -@blueprint.route('//update', methods=['POST']) -def update_addon(name): - repo = Repo("./modules/plugins/%s/" % (name)) - o = repo.remotes.origin - info = o.pull() - cbpi.notify("Plugin Updated", "Plugin %s updated successfully. Please restart the system" % name) - return ('', 204) - - -def loadCorePlugins(): - for filename in os.listdir("./modules/base_plugins"): - - - if os.path.isdir("./modules/base_plugins/"+filename) is False: - continue - try: - modules[filename] = import_module("modules.base_plugins.%s" % (filename)) - except Exception as e: - - - cbpi.notify("Failed to load plugin %s " % filename, str(e), type="danger", timeout=None) - cbpi.app.logger.error(e) - -def loadPlugins(): - for filename in os.listdir("./modules/plugins"): - if os.path.isdir("./modules/plugins/" + filename) is False: - continue - try: - modules[filename] = import_module("modules.plugins.%s" % (filename)) - except Exception as e: - cbpi.notify("Failed to load plugin %s " % filename, str(e), type="danger", timeout=None) - cbpi.app.logger.error(e) - -#@cbpi.initalizer(order=1) -def initPlugins(): - loadCorePlugins() - loadPlugins() - -@cbpi.initalizer(order=2) -def init(cbpi): - - cbpi.app.register_blueprint(blueprint, url_prefix='/api/editor') diff --git a/modules/app_config.py b/modules/app_config.py deleted file mode 100644 index 96a5e0c..0000000 --- a/modules/app_config.py +++ /dev/null @@ -1,55 +0,0 @@ - -import json -import sys, os -from flask import Flask, render_template, redirect, json, g - - -from flask_socketio import SocketIO, emit - -import logging - - - -from modules.core.core import CraftBeerPi, ActorBase, SensorBase -from modules.core.db import DBModel - -app = Flask(__name__) - -FORMAT = '%(asctime)-15s - %(levelname)s - %(message)s' - -logging.basicConfig(filename='./logs/app.log',level=logging.INFO, format=FORMAT) -app.config['SECRET_KEY'] = 'craftbeerpi' -app.config['UPLOAD_FOLDER'] = './upload' - - -@app.teardown_appcontext -def close_connection(exception): - db = getattr(g, '_database', None) - if db is not None: - db.close() - - -class ComplexEncoder(json.JSONEncoder): - def default(self, obj): - try: - if isinstance(obj, DBModel): - return obj.__dict__ - elif isinstance(obj, ActorBase): - return obj.state() - elif isinstance(obj, SensorBase): - return obj.get_value() - elif hasattr(obj, "callback"): - return obj() - else: - return None - except TypeError as e: - pass - return None - -app.json_encoder = ComplexEncoder -socketio = SocketIO(app, json=json, logging=False) -cbpi = CraftBeerPi(app, socketio) - -app.logger.info("##########################################") -app.logger.info("### NEW STARTUP Version 3.0") -app.logger.info("##########################################") \ No newline at end of file diff --git a/modules/base_plugins/actor.py b/modules/base_plugins/actor.py new file mode 100644 index 0000000..b8e6ba3 --- /dev/null +++ b/modules/base_plugins/actor.py @@ -0,0 +1,51 @@ +from modules.core.baseapi import Buzzer +from modules.core.basetypes import Actor, KettleController, FermenterController +from modules.core.core import cbpi + +@cbpi.addon.actor.type("Dummy Actor") +class Dummy(Actor): + + + @cbpi.addon.actor.action("WOHOO") + def myaction(self): + print "HALLO!!!" + + def on(self, power=100): + ''' + Code to switch on the actor + :param power: int value between 0 - 100 + :return: + ''' + print "ON" + + def off(self): + print "OFF" + + + +@cbpi.addon.kettle.controller() +class MyController(KettleController): + + + def run(self): + while self.is_running(): + print "HALLO" + self.sleep(1) + +@cbpi.addon.fermenter.controller() +class MyController2(FermenterController): + + + def run(self): + while self.is_running(): + print "HALLO" + self.sleep(1) + +@cbpi.addon.core.initializer(order=200) +def init(cbpi): + + class MyBuzzer(Buzzer): + def beep(self): + print "BEEEEEEP" + + cbpi.buzzer = MyBuzzer() diff --git a/modules/base_plugins/brew_steps/__init__.py b/modules/base_plugins/brew_steps/__init__.py deleted file mode 100644 index f46da69..0000000 --- a/modules/base_plugins/brew_steps/__init__.py +++ /dev/null @@ -1,223 +0,0 @@ -# -*- coding: utf-8 -*- -import time - - -from modules.core.props import Property, StepProperty -from modules.core.step import StepBase -from modules import cbpi - - - -@cbpi.step -class MashStep(StepBase): - ''' - Just put the decorator @cbpi.step on top of a method - ''' - # Properties - temp = Property.Number("Temperature", configurable=True, description="Target Temperature of Mash Step") - kettle = StepProperty.Kettle("Kettle", description="Kettle in which the mashing takes place") - timer = Property.Number("Timer in Minutes", configurable=True, description="Timer is started when the target temperature is reached") - - def init(self): - ''' - Initialize Step. This method is called once at the beginning of the step - :return: - ''' - # set target tep - self.set_target_temp(self.temp, self.kettle) - - @cbpi.action("Start Timer Now") - def start(self): - ''' - Custom Action which can be execute form the brewing dashboard. - All method with decorator @cbpi.action("YOUR CUSTOM NAME") will be available in the user interface - :return: - ''' - if self.is_timer_finished() is None: - self.start_timer(int(self.timer) * 60) - - def reset(self): - self.stop_timer() - self.set_target_temp(self.temp, self.kettle) - - def finish(self): - self.set_target_temp(0, self.kettle) - - def execute(self): - ''' - This method is execute in an interval - :return: - ''' - - # Check if Target Temp is reached - if self.get_kettle_temp(self.kettle) >= float(self.temp): - # Check if Timer is Running - if self.is_timer_finished() is None: - self.start_timer(int(self.timer) * 60) - - # Check if timer finished and go to next step - if self.is_timer_finished() == True: - self.notify("Mash Step Completed!", "Starting the next step", timeout=None) - self.next() - - -@cbpi.step -class MashInStep(StepBase): - ''' - Just put the decorator @cbpi.step on top of a method - ''' - # Properties - temp = Property.Number("Temperature", configurable=True, description="Target Temperature of Mash Step") - kettle = StepProperty.Kettle("Kettle", description="Kettle in which the mashing takes place") - s = False - - @cbpi.action("Change Power") - def change_power(self): - self.actor_power(1, 50) - - def init(self): - ''' - Initialize Step. This method is called once at the beginning of the step - :return: - ''' - # set target tep - self.s = False - self.set_target_temp(self.temp, self.kettle) - - - - def execute(self): - ''' - This method is execute in an interval - :return: - ''' - - # Check if Target Temp is reached - if self.get_kettle_temp(self.kettle) >= float(self.temp) and self.s is False: - self.s = True - self.notify("Step Temp Reached!", "Please press the next button to continue", timeout=None) - - - -@cbpi.step -class ChilStep(StepBase): - - timer = Property.Number("Timer in Minutes", configurable=True, default_value=0, description="Timer is started immediately") - - @cbpi.action("Stat Timer") - def start(self): - if self.is_timer_finished() is None: - self.start_timer(int(self.timer) * 60) - - def reset(self): - self.stop_timer() - - - def finish(self): - pass - - def execute(self): - if self.is_timer_finished() is None: - self.start_timer(int(self.timer) * 60) - - if self.is_timer_finished() == True: - self.next() - -@cbpi.step -class PumpStep(StepBase): - - pump = StepProperty.Actor("Pump", description="Pump actor gets toogled") - timer = Property.Number("Timer in Minutes", configurable=True, default_value=0, description="Timer is started immediately") - - @cbpi.action("Stat Timer") - def start(self): - if self.is_timer_finished() is None: - self.start_timer(int(self.timer) * 60) - - def reset(self): - self.stop_timer() - - - def finish(self): - self.actor_off(int(self.pump)) - - def init(self): - self.actor_on(int(self.pump)) - - def execute(self): - if self.is_timer_finished() is None: - self.start_timer(int(self.timer) * 60) - - if self.is_timer_finished() == True: - self.next() - -@cbpi.step -class BoilStep(StepBase): - ''' - Just put the decorator @cbpi.step on top of a method - ''' - # Properties - temp = Property.Number("Temperature", configurable=True, default_value=100, description="Target temperature for boiling") - kettle = StepProperty.Kettle("Kettle", description="Kettle in which the boiling step takes place") - timer = Property.Number("Timer in Minutes", configurable=True, default_value=90, description="Timer is started when target temperature is reached") - hop_1 = Property.Number("Hop 1 Addition", configurable=True, description="Fist Hop alert") - hop_1_added = Property.Number("",default_value=None) - hop_2 = Property.Number("Hop 2 Addition", configurable=True, description="Second Hop alert") - hop_2_added = Property.Number("", default_value=None) - hop_3 = Property.Number("Hop 3 Addition", configurable=True) - hop_3_added = Property.Number("", default_value=None, description="Second Hop alert") - - def init(self): - ''' - Initialize Step. This method is called once at the beginning of the step - :return: - ''' - # set target tep - self.set_target_temp(self.temp, self.kettle) - - - - - @cbpi.action("Start Timer Now") - def start(self): - ''' - Custom Action which can be execute form the brewing dashboard. - All method with decorator @cbpi.action("YOUR CUSTOM NAME") will be available in the user interface - :return: - ''' - if self.is_timer_finished() is None: - self.start_timer(int(self.timer) * 60) - - def reset(self): - self.stop_timer() - self.set_target_temp(self.temp, self.kettle) - - def finish(self): - self.set_target_temp(0, self.kettle) - - - def check_hop_timer(self, number, value): - - if self.__getattribute__("hop_%s_added" % number) is not True and time.time() > ( - self.timer_end - (int(self.timer) * 60 - int(value) * 60)): - self.__setattr__("hop_%s_added" % number, True) - self.notify("Hop Alert", "Please add Hop %s" % number, timeout=None) - - def execute(self): - ''' - This method is execute in an interval - :return: - ''' - # Check if Target Temp is reached - if self.get_kettle_temp(self.kettle) >= float(self.temp): - # Check if Timer is Running - if self.is_timer_finished() is None: - self.start_timer(int(self.timer) * 60) - else: - self.check_hop_timer(1, self.hop_1) - self.check_hop_timer(2, self.hop_2) - self.check_hop_timer(3, self.hop_3) - # Check if timer finished and go to next step - if self.is_timer_finished() == True: - self.notify("Boil Step Completed!", "Starting the next step", timeout=None) - self.next() diff --git a/modules/base_plugins/dummy_temp/__init__.py b/modules/base_plugins/dummy_temp/__init__.py deleted file mode 100644 index 2ed3f47..0000000 --- a/modules/base_plugins/dummy_temp/__init__.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- -import subprocess -import time - -from modules import cbpi, socketio -from modules.core.hardware import SensorActive -from modules import cbpi -from modules.core.props import Property - - -@cbpi.sensor -class DummyTempSensor(SensorActive): - - temp = Property.Number("Temperature", configurable=True, default_value=5, description="Dummy Temperature as decimal value") - - @cbpi.action("My Custom Action") - def my_action(self): - print "HELLO WORLD" - pass - - def get_unit(self): - ''' - :return: Unit of the sensor as string. Should not be longer than 3 characters - ''' - return "°C" if self.get_config_parameter("unit", "C") == "C" else "°F" - - def stop(self): - SensorActive.stop(self) - - def execute(self): - ''' - Active sensor has to handle his own loop - :return: - ''' - while self.is_running() is True: - self.data_received(self.temp) - self.sleep(5) - - @classmethod - def init_global(cls): - ''' - Called one at the startup for all sensors - :return: - ''' - - - - - - - - - diff --git a/modules/base_plugins/fermenter_hysteresis/__init__.py b/modules/base_plugins/fermenter_hysteresis/__init__.py deleted file mode 100644 index ef24d25..0000000 --- a/modules/base_plugins/fermenter_hysteresis/__init__.py +++ /dev/null @@ -1,38 +0,0 @@ -from modules import cbpi -from modules.core.controller import KettleController, FermenterController -from modules.core.props import Property - - -@cbpi.fermentation_controller -class Hysteresis(FermenterController): - - heater_offset_min = Property.Number("Heater Offset ON", True, 0, description="Offset as decimal number when the heater is switched on. Should be greater then 'Heater Offset OFF'. For example a value of 2 switches on the heater if the current temperature is 2 degrees below the target temperature") - heater_offset_max = Property.Number("Heater Offset OFF", True, 0, description="Offset as decimal number when the heater is switched off. Should be smaller then 'Heater Offset ON'. For example a value of 1 switches off the heater if the current temperature is 1 degree below the target temperature") - cooler_offset_min = Property.Number("Cooler Offset ON", True, 0, description="Offset as decimal number when the cooler is switched on. Should be greater then 'Cooler Offset OFF'. For example a value of 2 switches on the cooler if the current temperature is 2 degrees above the target temperature") - cooler_offset_max = Property.Number("Cooler Offset OFF", True, 0, description="Offset as decimal number when the cooler is switched off. Should be less then 'Cooler Offset ON'. For example a value of 1 switches off the cooler if the current temperature is 1 degree above the target temperature") - - def stop(self): - super(FermenterController, self).stop() - - self.heater_off() - self.cooler_off() - - def run(self): - while self.is_running(): - - target_temp = self.get_target_temp() - temp = self.get_temp() - - if temp + float(self.heater_offset_min) <= target_temp: - self.heater_on(100) - - if temp + float(self.heater_offset_max) >= target_temp: - self.heater_off() - - if temp >= target_temp + float(self.cooler_offset_min): - self.cooler_on(100) - - if temp <= target_temp + float(self.cooler_offset_max): - self.cooler_off() - - self.sleep(1) diff --git a/modules/base_plugins/gpio_actor/__init__.py b/modules/base_plugins/gpio_actor/__init__.py deleted file mode 100644 index e346ef9..0000000 --- a/modules/base_plugins/gpio_actor/__init__.py +++ /dev/null @@ -1,107 +0,0 @@ -# -*- coding: utf-8 -*- -import time - -from modules import cbpi -from modules.core.hardware import ActorBase, SensorPassive, SensorActive -from modules.core.props import Property - -try: - import RPi.GPIO as GPIO - - GPIO.setmode(GPIO.BCM) -except Exception as e: - print e - pass - - - -@cbpi.actor -class GPIOSimple(ActorBase): - - gpio = Property.Select("GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27], description="GPIO to which the actor is connected") - - def init(self): - GPIO.setup(int(self.gpio), GPIO.OUT) - GPIO.output(int(self.gpio), 0) - - def on(self, power=0): - print "GPIO ON %s" % str(self.gpio) - GPIO.output(int(self.gpio), 1) - - def off(self): - print "GPIO OFF" - GPIO.output(int(self.gpio), 0) - -@cbpi.actor -class GPIOPWM(ActorBase): - - gpio = Property.Select("GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27], description="GPIO to which the actor is connected") - duty_cylce = Property.Number("Duty Cycle", configurable=True) - - p = None - power = 100 - - def init(self): - GPIO.setup(int(self.gpio), GPIO.OUT) - GPIO.output(int(self.gpio), 0) - - - def on(self, power=None): - if power is not None: - self.power = int(power) - - if self.duty_cylce is None: - duty_cylce = 50 - - self.p = GPIO.PWM(int(self.gpio), int(self.duty_cylce)) - self.p.start(int(self.power)) - - def set_power(self, power): - ''' - Optional: Set the power of your actor - :param power: int value between 0 - 100 - :return: - ''' - if power is not None: - self.power = int(power) - self.p.ChangeDutyCycle(self.power) - - def off(self): - print "GPIO OFF" - self.p.stop() - - -@cbpi.actor -class RelayBoard(ActorBase): - - gpio = Property.Select("GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27], description="GPIO to which the actor is connected") - - def init(self): - GPIO.setup(int(self.gpio), GPIO.OUT) - GPIO.output(int(self.gpio), 1) - - def on(self, power=0): - - GPIO.output(int(self.gpio), 0) - - def off(self): - - GPIO.output(int(self.gpio), 1) - -@cbpi.actor -class Dummy(ActorBase): - - - def on(self, power=100): - ''' - Code to switch on the actor - :param power: int value between 0 - 100 - :return: - ''' - print "ON" - - def off(self): - print "OFF" - - - diff --git a/modules/base_plugins/hysteresis/__init__.py b/modules/base_plugins/hysteresis/__init__.py deleted file mode 100644 index ebc7419..0000000 --- a/modules/base_plugins/hysteresis/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -from modules import cbpi -from modules.core.controller import KettleController -from modules.core.props import Property - - -@cbpi.controller -class Hysteresis(KettleController): - - # Custom Properties - - on = Property.Number("Offset On", True, 0, description="Offset below target temp when heater should switched on. Should be bigger then Offset Off") - off = Property.Number("Offset Off", True, 0, description="Offset below target temp when heater should switched off. Should be smaller then Offset Off") - - def stop(self): - ''' - Invoked when the automatic is stopped. - Normally you switch off the actors and clean up everything - :return: None - ''' - super(KettleController, self).stop() - self.heater_off() - - - - - def run(self): - ''' - Each controller is exectuted in its own thread. The run method is the entry point - :return: - ''' - while self.is_running(): - - if self.get_temp() < self.get_target_temp() - float(self.on): - self.heater_on(100) - elif self.get_temp() >= self.get_target_temp() - float(self.off): - self.heater_off() - else: - self.heater_off() - self.sleep(1) - diff --git a/modules/base_plugins/one_wire/__init__.py b/modules/base_plugins/one_wire/__init__.py deleted file mode 100644 index 537f0c6..0000000 --- a/modules/base_plugins/one_wire/__init__.py +++ /dev/null @@ -1,118 +0,0 @@ -# -*- coding: utf-8 -*- -import os -from subprocess import Popen, PIPE, call - -from modules import cbpi, app -from modules.core.hardware import SensorPassive -import json -import os, re, threading, time -from flask import Blueprint, render_template, request -from modules.core.props import Property - -blueprint = Blueprint('one_wire', __name__) - -temp = 22 - -def getSensors(): - try: - arr = [] - for dirname in os.listdir('/sys/bus/w1/devices'): - if (dirname.startswith("28") or dirname.startswith("10")): - cbpi.app.logger.info("Device %s Found (Family: 28/10, Thermometer on GPIO4 (w1))" % dirname) - arr.append(dirname) - return arr - except: - return [] - - - - -class myThread (threading.Thread): - - value = 0 - - - def __init__(self, sensor_name): - threading.Thread.__init__(self) - self.value = 0 - self.sensor_name = sensor_name - self.runnig = True - - def shutdown(self): - pass - - def stop(self): - self.runnig = False - - def run(self): - - while self.runnig: - try: - app.logger.info("READ TEMP") - ## Test Mode - if self.sensor_name is None: - return - with open('/sys/bus/w1/devices/w1_bus_master1/%s/w1_slave' % self.sensor_name, 'r') as content_file: - content = content_file.read() - if (content.split('\n')[0].split(' ')[11] == "YES"): - temp = float(content.split("=")[-1]) / 1000 # temp in Celcius - self.value = temp - except: - pass - - time.sleep(4) - - - -@cbpi.sensor -class ONE_WIRE_SENSOR(SensorPassive): - - sensor_name = Property.Select("Sensor", getSensors(), description="The OneWire sensor address.") - offset = Property.Number("Offset", True, 0, description="Offset which is added to the received sensor data. Positive and negative values are both allowed.") - - def init(self): - - self.t = myThread(self.sensor_name) - - def shudown(): - shudown.cb.shutdown() - shudown.cb = self.t - - self.t.start() - - def stop(self): - try: - self.t.stop() - except: - pass - - def read(self): - if self.get_config_parameter("unit", "C") == "C": - self.data_received(round(self.t.value + self.offset_value(), 2)) - else: - self.data_received(round(9.0 / 5.0 * self.t.value + 32 + self.offset_value(), 2)) - - @cbpi.try_catch(0) - def offset_value(self): - return float(self.offset) - - @classmethod - def init_global(self): - try: - call(["modprobe", "w1-gpio"]) - call(["modprobe", "w1-therm"]) - except Exception as e: - pass - - -@blueprint.route('/', methods=['GET']) -def set_temp(t): - global temp - temp = t - return ('', 204) - - -@cbpi.initalizer() -def init(cbpi): - - cbpi.app.register_blueprint(blueprint, url_prefix='/api/one_wire') diff --git a/modules/base_plugins/sensor.py b/modules/base_plugins/sensor.py new file mode 100644 index 0000000..652aa03 --- /dev/null +++ b/modules/base_plugins/sensor.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +from modules.core.basetypes import Actor, Sensor +from modules.core.core import cbpi +from modules.core.proptypes import Property +import random +print "INit SENSOR" +@cbpi.addon.sensor.type("Dummy Sensor") +class Dummy(Sensor): + + text = Property.Text(label="Text", required=True, description="This is a parameter", configurable=True) + p = Property.Select(label="hallo",options=[1,2,3]) + + def init(self): + + if self.api.get_config_parameter("unit","C") == "C": + self.unit = "°C" + else: + self.unit = "°F" + + @cbpi.addon.sensor.action("WOHOO") + def myaction(self): + print self.text + print "SENSOR ACTION HALLO!!!" + + def execute(self): + while True: + try: + self.update_value(int(self.text)) + except: + pass + self.api.sleep(1) + +@cbpi.addon.core.action(key="clear", label="Clear all Logs") +def woohoo(cbpi): + print "COOL" + cbpi.notify(headline="HELLO WORLD",message="") diff --git a/modules/base_plugins/steps.py b/modules/base_plugins/steps.py new file mode 100644 index 0000000..d536b14 --- /dev/null +++ b/modules/base_plugins/steps.py @@ -0,0 +1,14 @@ +from modules.core.basetypes import Step +from modules.core.core import cbpi +from modules.core.proptypes import Property + + +@cbpi.addon.step.type("Dummy Step") +class Dummy(Step): + + + text = Property.Text(label="Text", configurable=True, description="WOHOOO") + + def execute(self): + #print self.text + pass \ No newline at end of file diff --git a/modules/buzzer/__init__.py b/modules/buzzer/__init__.py old mode 100644 new mode 100755 index 5120587..7e443c9 --- a/modules/buzzer/__init__.py +++ b/modules/buzzer/__init__.py @@ -1,50 +1,53 @@ -import time -from thread import start_new_thread -from modules import cbpi - -try: - import RPi.GPIO as GPIO -except Exception as e: - pass - -class Buzzer(object): - - sound = ["H", 0.1, "L", 0.1, "H", 0.1, "L", 0.1, "H", 0.1, "L"] - def __init__(self, gpio): - try: - cbpi.app.logger.info("INIT BUZZER NOW GPIO%s" % gpio) - self.gpio = int(gpio) - GPIO.setmode(GPIO.BCM) - GPIO.setup(self.gpio, GPIO.OUT) - self.state = True - cbpi.app.logger.info("BUZZER SETUP OK") - except Exception as e: - cbpi.app.logger.info("BUZZER EXCEPTION %s" % str(e)) - self.state = False - - def beep(self): - if self.state is False: - cbpi.app.logger.error("BUZZER not working") - return - - def play(sound): - try: - for i in sound: - if (isinstance(i, str)): - if i == "H": - GPIO.output(int(self.gpio), GPIO.HIGH) - else: - GPIO.output(int(self.gpio), GPIO.LOW) - else: - time.sleep(i) - except Exception as e: - pass - - start_new_thread(play, (self.sound,)) - -@cbpi.initalizer(order=1) -def init(cbpi): - gpio = cbpi.get_config_parameter("buzzer", 16) - cbpi.buzzer = Buzzer(gpio) - cbpi.beep() - cbpi.app.logger.info("INIT OK") +import time +from thread import start_new_thread + +from modules.core.baseapi import Buzzer +from modules.core.core import cbpi + +try: + import RPi.GPIO as GPIO +except Exception as e: + pass + +class GPIOBuzzer(Buzzer): + + sound = ["H", 0.1, "L", 0.1, "H", 0.1, "L", 0.1, "H", 0.1, "L"] + + + def __init__(self, gpio): + try: + cbpi._app.logger.info("INIT BUZZER NOW GPIO%s" % gpio) + self.gpio = int(gpio) + GPIO.setmode(GPIO.BCM) + GPIO.setup(self.gpio, GPIO.OUT) + self.state = True + cbpi._app.logger.info("BUZZER SETUP OK") + except Exception as e: + cbpi._app.logger.info("BUZZER EXCEPTION %s" % str(e)) + self.state = False + + def beep(self): + if self.state is False: + cbpi._app.logger.error("BUZZER not working") + return + + def play(sound): + try: + for i in sound: + if (isinstance(i, str)): + if i == "H": + GPIO.output(int(self.gpio), GPIO.HIGH) + else: + GPIO.output(int(self.gpio), GPIO.LOW) + else: + time.sleep(i) + except Exception as e: + pass + + start_new_thread(play, (self.sound,)) + +@cbpi.addon.core.initializer(order=1) +def init(cbpi): + gpio = cbpi.get_config_parameter("buzzer", 16) + cbpi.buzzer = GPIOBuzzer(gpio) + diff --git a/modules/config/__init__.py b/modules/config/__init__.py old mode 100644 new mode 100755 index 1adf054..253e188 --- a/modules/config/__init__.py +++ b/modules/config/__init__.py @@ -1,57 +1,89 @@ -import time - -from flask import json, request -from flask_classy import route -from modules import DBModel, cbpi, get_db -from modules.core.baseview import BaseView - -class Config(DBModel): - __fields__ = ["type", "value", "description", "options"] - __table_name__ = "config" - __json_fields__ = ["options"] - __priamry_key__ = "name" - - -class ConfigView(BaseView): - model = Config - cache_key = "config" - - @route('/', methods=["PUT"]) - def put(self, name): - - data = request.json - data["name"] = name - update_data = {"name": data["name"], "value": data["value"]} - - if self.api.cache.get(self.cache_key) is not None: - self.api.cache.get(self.cache_key)[name].__dict__.update(**update_data) - m = self.model.update(**self.api.cache.get(self.cache_key)[name].__dict__) - self._post_put_callback(self.api.cache.get(self.cache_key)[name]) - return json.dumps(self.api.cache.get(self.cache_key)[name].__dict__) - - @route('/', methods=["GET"]) - def getOne(self, id): - return ('NOT SUPPORTED', 400) - - @route('/', methods=["DELETE"]) - def delete(self, id): - return ('NOT SUPPORTED', 400) - - @route('/', methods=["POST"]) - def post(self): - return ('NOT SUPPORTED', 400) - - @classmethod - def init_cache(cls): - - with cls.api.app.app_context(): - cls.api.cache[cls.cache_key] = {} - for key, value in cls.model.get_all().iteritems(): - cls.post_init_callback(value) - cls.api.cache[cls.cache_key][value.name] = value - -@cbpi.initalizer(order=0) -def init(cbpi): - - ConfigView.register(cbpi.app, route_base='/api/config') - ConfigView.init_cache() +import time + +from flask import json, request +from flask_classy import route +from modules.core.core import cbpi +from modules.core.db import DBModel +from modules.core.baseview import BaseView +from modules.database.dbmodel import Config + + +class ConfigView(BaseView): + model = Config + cache_key = "config" + + @route('/', methods=["PUT"]) + def put(self, name): + """ + Set new config value + --- + tags: + - config + responses: + 204: + description: New config value set + """ + data = request.json + data["name"] = name + update_data = {"name": data["name"], "value": data["value"]} + + if self.api.cache.get(self.cache_key) is not None: + self.api.cache.get(self.cache_key)[name].__dict__.update(**update_data) + m = self.model.update(**self.api.cache.get(self.cache_key)[name].__dict__) + self._post_put_callback(self.api.cache.get(self.cache_key)[name]) + + self.api.emit("CONFIG_UPDATE", name=name, data=data["value"]) + return json.dumps(self.api.cache.get(self.cache_key)[name].__dict__) + + @route('/', methods=["GET"]) + def getOne(self, id): + """ + Get config parameter + --- + tags: + - config + responses: + 400: + description: Get one config parameter via web api is not supported + """ + return ('NOT SUPPORTED', 400) + + @route('/', methods=["DELETE"]) + def delete(self, id): + """ + Delete config parameter + --- + tags: + - config + responses: + 400: + description: Deleting config parameter via web api is not supported + """ + return ('NOT SUPPORTED', 400) + + @route('/', methods=["POST"]) + def post(self): + """ + Get config parameter + --- + tags: + - config + responses: + 400: + description: Adding new config parameter via web api is not supported + """ + return ('NOT SUPPORTED', 400) + + @classmethod + def init_cache(cls): + + with cls.api._app.app_context(): + cls.api.cache[cls.cache_key] = {} + for key, value in cls.model.get_all().iteritems(): + cls.post_init_callback(value) + cls.api.cache[cls.cache_key][value.name] = value + +@cbpi.addon.core.initializer(order=0) +def init(cbpi): + ConfigView.register(cbpi._app, route_base='/api/config') + ConfigView.init_cache() diff --git a/modules/core/__init__.py b/modules/core/__init__.py old mode 100644 new mode 100755 diff --git a/modules/core/baseapi.py b/modules/core/baseapi.py new file mode 100755 index 0000000..a68188f --- /dev/null +++ b/modules/core/baseapi.py @@ -0,0 +1,216 @@ +from proptypes import * + +class BaseAPI(object): + + def __init__(self, cbpi): + self.cbpi = cbpi + self.cbpi.cache[self.key] = {} + + + def init(self): + for name, value in self.cbpi.cache[self.key].iteritems(): + + value["class"].init_global() + + def parseProps(self, key, cls, **options): + + name = cls.__name__ + tmpObj = cls() + + try: + doc = tmpObj.__doc__.strip() + except: + doc = "" + + self.cbpi.cache.get(key)[name] = {"name": name, "class": cls, "description":doc, "properties": [], "actions": []} + self.cbpi.cache.get(key)[name].update(options) + members = [attr for attr in dir(tmpObj) if not callable(getattr(tmpObj, attr)) and not attr.startswith("__")] + for m in members: + t = tmpObj.__getattribute__(m) + + if isinstance(t, Property.Number): + self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "number", "configurable": t.configurable, "description": t.description, "default_value": t.default_value}) + elif isinstance(t, Property.Text): + self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "text", "required": t.required, "configurable": t.configurable, "description": t.description, "default_value": t.default_value}) + elif isinstance(tmpObj.__getattribute__(m), Property.Select): + self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "select", "configurable": True, "options": t.options, "description": t.description}) + elif isinstance(tmpObj.__getattribute__(m), Property.Actor): + self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "actor", "configurable": True, "description": t.description}) + elif isinstance(tmpObj.__getattribute__(m), Property.Sensor): + self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "sensor", "configurable": True, "description": t.description}) + elif isinstance(tmpObj.__getattribute__(m), Property.Kettle): + self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "kettle", "configurable": True, "description": t.description}) + + for method_name, method in cls.__dict__.iteritems(): + if hasattr(method, "action"): + label = method.__getattribute__("label") + self.cbpi.cache.get(key)[name]["actions"].append({"method": method_name, "label": label}) + + return cls + + +class SensorAPI(BaseAPI): + + key = "sensor_types" + + def type(self, description="Step", **options): + def decorator(f): + BaseAPI.parseProps(self, self.key,f, description=description) + return f + return decorator + + def action(self, label): + def real_decorator(func): + func.action = True + func.label = label + return func + return real_decorator + + +class StepAPI(BaseAPI): + + key = "step_types" + + def init(self): + pass + + def type(self, description="Step", **options): + def decorator(f): + BaseAPI.parseProps(self, self.key,f, description=description) + return f + return decorator + + def action(self, label): + def real_decorator(func): + func.action = True + func.label = label + return func + return real_decorator + +class ActorAPI(BaseAPI): + + key = "actor_types" + + def type(self, description="", **options): + def decorator(f): + BaseAPI.parseProps(self, self.key, f, description=description) + return f + return decorator + + def action(self, label): + def real_decorator(func): + func.action = True + func.label = label + return func + return real_decorator + + + +class KettleAPI(BaseAPI): + + key = "controller_types" + + def controller(self, description="", **options): + def decorator(f): + BaseAPI.parseProps(self, self.key,f,description=description) + return f + return decorator + + def action(self, label): + def real_decorator(func): + func.action = True + func.label = label + return func + return real_decorator + +class FermenterAPI(BaseAPI): + + key = "fermentation_controller_types" + + def controller(self, description="Step", **options): + def decorator(f): + BaseAPI.parseProps(self, self.key,f,description=description) + return f + return decorator + + def action(self, label): + def real_decorator(func): + func.action = True + func.label = label + return func + return real_decorator + +class CoreAPI(BaseAPI): + + key = "core" + + def __init__(self, cbpi): + self.cbpi = cbpi + self.cbpi.cache["actions"] = {} + self.cbpi.cache["init"] = [] + self.cbpi.cache["js"] = {} + self.cbpi.cache["background"] = [] + def init(self): + + self.cbpi.cache["init"] = sorted(self.cbpi.cache["init"], key=lambda k: k['order']) + for value in self.cbpi.cache.get("init"): + + print value + value["function"](self.cbpi) + + def job(interval, method): + while True: + try: + method(self.cbpi) + except Exception as e: + print e + self.cbpi._socketio.sleep(interval) + + for value in self.cbpi.cache.get("background"): + t = self.cbpi._socketio.start_background_task(target=job, interval=value.get("interval"), method=value.get("function")) + + + def add_js(self, name, file): + self.cbpi.cache["js"][name] = file + + def initializer(self, order=0, **options): + def decorator(f): + self.cbpi.cache.get("init").append({"function": f, "order": order}) + return f + return decorator + + + def action(self, key, label, **options): + def decorator(f): + self.cbpi.cache.get("actions")[key] = {"label": label, "function": f} + return f + return decorator + + + def backgroundjob(self, key, interval, **options): + def decorator(f): + self.cbpi.cache.get("background").append({"function": f, "key": key, "interval": interval}) + return f + return decorator + + def listen(self, name, method=None, async=False): + + if method is not None: + if self.cbpi.eventbus.get(name) is None: + self.cbpi.eventbus[name] = [] + self.cbpi.eventbus[name].append({"function": method, "async": async}) + else: + def real_decorator(function): + if self.cbpi.eventbus.get(name) is None: + self.cbpi.eventbus[name] = [] + self.cbpi.eventbus[name].append({"function": function, "async": async}) + def wrapper(*args, **kwargs): + return function(*args, **kwargs) + return wrapper + return real_decorator + + +class Buzzer(object): + + def beep(): + pass \ No newline at end of file diff --git a/modules/core/controller.py b/modules/core/basetypes.py old mode 100644 new mode 100755 similarity index 52% rename from modules/core/controller.py rename to modules/core/basetypes.py index aef2a7e..0c41eb9 --- a/modules/core/controller.py +++ b/modules/core/basetypes.py @@ -1,168 +1,249 @@ -from modules import cbpi - - -class ActorController(object): - - @cbpi.try_catch(None) - def actor_on(self, power=100, id=None): - - if id is None: - id = self.heater - self.api.switch_actor_on(int(id), power=power) - - @cbpi.try_catch(None) - def actor_off(self, id=None): - if id is None: - id = self.heater - - self.api.switch_actor_off(int(id)) - - @cbpi.try_catch(None) - def actor_power(self, power, id=None): - if id is None: - id = self.heater - self.api.actor_power(int(id), power) - - -class SensorController(object): - - @cbpi.try_catch(None) - def get_sensor_value(self, id=None): - - if id is None: - id = self.sensor - - return cbpi.get_sensor_value(id) - - - -class ControllerBase(object): - __dirty = False - __running = False - - @staticmethod - def init_global(): - print "GLOBAL CONTROLLER INIT" - - def notify(self, headline, message, type="success", timeout=5000): - self.api.notify(headline, message, type, timeout) - - def is_running(self): - return self.__running - - def init(self): - self.__running = True - - - def sleep(self, seconds): - self.api.socketio.sleep(seconds) - - def stop(self): - self.__running = False - - - def __init__(self, *args, **kwds): - for a in kwds: - super(ControllerBase, self).__setattr__(a, kwds.get(a)) - self.api = kwds.get("api") - self.heater = kwds.get("heater") - self.sensor = kwds.get("sensor") - - - def run(self): - pass - -class KettleController(ControllerBase, ActorController, SensorController): - - @staticmethod - def chart(kettle): - result = [] - result.append({"name": "Temp", "data_type": "sensor", "data_id": kettle.sensor}) - result.append({"name": "Target Temp", "data_type": "kettle", "data_id": kettle.id}) - - return result - - def __init__(self, *args, **kwds): - ControllerBase.__init__(self, *args, **kwds) - self.kettle_id = kwds.get("kettle_id") - - @cbpi.try_catch(None) - def heater_on(self, power=100): - k = self.api.cache.get("kettle").get(self.kettle_id) - if k.heater is not None: - self.actor_on(power, int(k.heater)) - - - - - @cbpi.try_catch(None) - def heater_off(self): - k = self.api.cache.get("kettle").get(self.kettle_id) - if k.heater is not None: - self.actor_off(int(k.heater)) - - @cbpi.try_catch(None) - def get_temp(self, id=None): - if id is None: - id = self.kettle_id - return self.get_sensor_value(int(self.api.cache.get("kettle").get(id).sensor)) - - @cbpi.try_catch(None) - def get_target_temp(self, id=None): - if id is None: - id = self.kettle_id - return self.api.cache.get("kettle").get(id).target_temp - -class FermenterController(ControllerBase, ActorController, SensorController): - - @staticmethod - def chart(fermenter): - result = [] - result.append({"name": "Temp", "data_type": "sensor", "data_id": fermenter.sensor}) - result.append({"name": "Target Temp", "data_type": "fermenter", "data_id": fermenter.id}) - return result - - def __init__(self, *args, **kwds): - ControllerBase.__init__(self, *args, **kwds) - self.fermenter_id = kwds.get("fermenter_id") - self.cooler = kwds.get("cooler") - - - - @cbpi.try_catch(None) - def get_target_temp(self, id=None): - if id is None: - id = self.fermenter_id - return self.api.cache.get("fermenter").get(id).target_temp - - @cbpi.try_catch(None) - def heater_on(self, power=100): - f = self.api.cache.get("fermenter").get(self.fermenter_id) - if f.heater is not None: - self.actor_on(int(f.heater)) - - @cbpi.try_catch(None) - def heater_off(self): - f = self.api.cache.get("fermenter").get(self.fermenter_id) - if f.heater is not None: - self.actor_off(int(f.heater)) - - @cbpi.try_catch(None) - def cooler_on(self, power=100): - f = self.api.cache.get("fermenter").get(self.fermenter_id) - if f.cooler is not None: - self.actor_on(power, int(f.cooler)) - - @cbpi.try_catch(None) - def cooler_off(self): - f = self.api.cache.get("fermenter").get(self.fermenter_id) - if f.cooler is not None: - self.actor_off(int(f.cooler)) - - @cbpi.try_catch(None) - def get_temp(self, id=None): - - if id is None: - id = self.fermenter_id - return self.get_sensor_value(int(self.api.cache.get("fermenter").get(id).sensor)) - + +class Base(object): + + def __init__(self, *args, **kwds): + for a in kwds: + + super(Base, self).__setattr__(a, kwds.get(a)) + self.api = kwds.get("cbpi") + self.id = kwds.get("id") + self.value = None + self.__dirty = False + +class Actor(Base): + + @classmethod + def init_global(cls): + print "GLOBAL INIT ACTOR" + pass + + def init(self): + pass + + def shutdown(self): + pass + + def on(self, power=100): + print "SWITCH ON" + pass + + def off(self): + print "SWITCH OFF" + pass + + def power(self, power): + print "SET POWER", power + pass + + def state(self): + pass + + +class Sensor(Base): + + unit = "" + + @classmethod + def init_global(cls): + pass + + def init(self): + pass + + def get_unit(self): + pass + + def stop(self): + pass + + def update_value(self, value): + self.value = value + self.cbpi.sensor.write_log(self.id, value) + self.cbpi.emit("SENSOR_UPDATE", id=self.id, value=value) + self.cbpi.ws_emit("SENSOR_UPDATE", self.cbpi.cache["sensors"][self.id]) + + def execute(self): + print "EXECUTE" + pass + + + + +class ControllerBase(object): + __dirty = False + __running = False + + @staticmethod + def init_global(): + print "GLOBAL CONTROLLER INIT" + + def notify(self, headline, message, type="success", timeout=5000): + self.api.notify(headline, message, type, timeout) + + def is_running(self): + return self.__running + + def init(self): + self.__running = True + + def sleep(self, seconds): + self.api.sleep(seconds) + + def stop(self): + self.__running = False + + def get_sensor_value(self, id): + return self.api.sensor.get_sensor_value(id) + + def __init__(self, *args, **kwds): + for a in kwds: + super(ControllerBase, self).__setattr__(a, kwds.get(a)) + self.api = kwds.get("api") + self.heater = kwds.get("heater") + self.sensor = kwds.get("sensor") + + def actor_on(self,id, power=100): + self.api.actor.on(id, power=power) + + def actor_off(self, id): + self.api.actor.off(id) + + def actor_power(self, power, id=None): + self.api.actor.power(id, power) + + def run(self): + pass + +class KettleController(ControllerBase): + + @staticmethod + def chart(kettle): + result = [] + result.append({"name": "Temp", "data_type": "sensor", "data_id": kettle.sensor}) + result.append({"name": "Target Temp", "data_type": "kettle", "data_id": kettle.id}) + return result + + def __init__(self, *args, **kwds): + ControllerBase.__init__(self, *args, **kwds) + self.kettle_id = kwds.get("kettle_id") + + def heater_on(self, power=100): + k = self.api.cache.get("kettle").get(self.kettle_id) + if k.heater is not None: + self.actor_on(k.heater, power) + + def heater_off(self): + k = self.api.cache.get("kettle").get(self.kettle_id) + if k.heater is not None: + self.actor_off(k.heater) + + def get_temp(self, id=None): + if id is None: + id = self.kettle_id + return self.get_sensor_value(int(self.api.cache.get("kettle").get(id).sensor)) + + def get_target_temp(self, id=None): + if id is None: + id = self.kettle_id + return self.api.cache.get("kettle").get(id).target_temp + +class FermenterController(ControllerBase): + + @staticmethod + def chart(fermenter): + result = [] + result.append({"name": "Temp", "data_type": "sensor", "data_id": fermenter.sensor}) + result.append({"name": "Target Temp", "data_type": "fermenter", "data_id": fermenter.id}) + return result + + def __init__(self, *args, **kwds): + ControllerBase.__init__(self, *args, **kwds) + self.fermenter_id = kwds.get("fermenter_id") + self.cooler = kwds.get("cooler") + + def get_target_temp(self, id=None): + if id is None: + id = self.fermenter_id + return self.api.cache.get("fermenter").get(id).target_temp + + def heater_on(self, power=100): + f = self.api.cache.get("fermenter").get(self.fermenter_id) + if f.heater is not None: + self.actor_on(int(f.heater)) + + def heater_off(self): + f = self.api.cache.get("fermenter").get(self.fermenter_id) + if f.heater is not None: + self.actor_off(int(f.heater)) + + def cooler_on(self, power=100): + f = self.api.cache.get("fermenter").get(self.fermenter_id) + if f.cooler is not None: + self.actor_on(power, int(f.cooler)) + + def cooler_off(self): + f = self.api.cache.get("fermenter").get(self.fermenter_id) + if f.cooler is not None: + self.actor_off(int(f.cooler)) + + def get_temp(self, id=None): + if id is None: + id = self.fermenter_id + return self.get_sensor_value(int(self.api.cache.get("fermenter").get(id).sensor)) + + + +class Step(Base): + + + @classmethod + def init_global(cls): + pass + + __dirty = False + managed_fields = [] + n = False + + def next(self): + self.n = True + + def init(self): + pass + + def finish(self): + pass + + def reset(self): + pass + + def execute(self): + print "-------------" + print "Step Info" + print "Kettle ID: %s" % self.kettle_id + print "ID: %s" % self.id + + def __init__(self, *args, **kwds): + + for a in kwds: + super(Step, self).__setattr__(a, kwds.get(a)) + + self.api = kwds.get("api") + self.id = kwds.get("id") + self.name = kwds.get("name") + self.kettle_id = kwds.get("kettleid") + self.value = None + self.__dirty = False + + def is_dirty(self): + return self.__dirty + + def reset_dirty(self): + self.__dirty = False + + def __setattr__(self, name, value): + + if name != "_StepBase__dirty" and name in self.managed_fields: + self.__dirty = True + super(Step, self).__setattr__(name, value) + else: + super(Step, self).__setattr__(name, value) \ No newline at end of file diff --git a/modules/core/baseview.py b/modules/core/baseview.py old mode 100644 new mode 100755 index 9bdf8a3..3c24cf5 --- a/modules/core/baseview.py +++ b/modules/core/baseview.py @@ -1,109 +1,110 @@ -from flask import request, json -from flask_classy import route, FlaskView -from modules import cbpi - - -class BaseView(FlaskView): - - as_array = False - cache_key = None - api = cbpi - - @route('/', methods=["GET"]) - def getOne(self, id): - - if self.api.cache.get(self.cache_key) is not None: - return json.dumps(self.api.cache.get(self.cache_key).get(id)) - else: - return json.dumps(self.model.get_one(id)) - - @route('/', methods=["GET"]) - def getAll(self): - if self.api.cache.get(self.cache_key) is not None: - return json.dumps(self.api.cache.get(self.cache_key)) - else: - return json.dumps(self.model.get_all()) - - def _pre_post_callback(self, data): - pass - - - def _post_post_callback(self, m): - pass - - @route('/', methods=["POST"]) - def post(self): - data = request.json - self._pre_post_callback(data) - m = self.model.insert(**data) - if self.api.cache.get(self.cache_key) is not None: - self.api.cache.get(self.cache_key)[m.id] = m - - self._post_post_callback(m) - - return json.dumps(m) - - def _pre_put_callback(self, m): - pass - - def _post_put_callback(self, m): - pass - - - @route('/', methods=["PUT"]) - def put(self, id): - data = request.json - data["id"] = id - try: - del data["instance"] - except: - pass - if self.api.cache.get(self.cache_key) is not None: - self._pre_put_callback(self.api.cache.get(self.cache_key)[id]) - self.api.cache.get(self.cache_key)[id].__dict__.update(**data) - m = self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__) - self._post_put_callback(self.api.cache.get(self.cache_key)[id]) - return json.dumps(self.api.cache.get(self.cache_key)[id]) - else: - m = self.model.update(**data) - - self._post_put_callback(m) - return json.dumps(m) - - - def _pre_delete_callback(self, m): - pass - - def _post_delete_callback(self, id): - pass - - @route('/', methods=["DELETE"]) - def delete(self, id): - if self.api.cache.get(self.cache_key) is not None: - self._pre_delete_callback(self.api.cache.get(self.cache_key)[id]) - del self.api.cache.get(self.cache_key)[id] - m = self.model.delete(id) - - def _post_delete_callback(self, id): - pass - return ('',204) - - @classmethod - def post_init_callback(cls, obj): - pass - - @classmethod - def init_cache(cls): - with cls.api.app.app_context(): - - if cls.model.__as_array__ is True: - cls.api.cache[cls.cache_key] = [] - - for value in cls.model.get_all(): - cls.post_init_callback(value) - cls.api.cache[cls.cache_key].append(value) - else: - cls.api.cache[cls.cache_key] = {} - for key, value in cls.model.get_all().iteritems(): - cls.post_init_callback(value) - cls.api.cache[cls.cache_key][key] = value \ No newline at end of file +from flask import request, json +from flask_classy import route, FlaskView +from modules.core.core import cbpi + + +class BaseView(FlaskView): + + as_array = False + cache_key = None + api = cbpi + + @route('/', methods=["GET"]) + def getOne(self, id): + if self.api.cache.get(self.cache_key) is not None: + return json.dumps(self.api.cache.get(self.cache_key).get(id)) + else: + return json.dumps(self.model.get_one(id)) + + @route('/', methods=["GET"]) + def getAll(self): + if self.api.cache.get(self.cache_key) is not None: + return json.dumps(self.api.cache.get(self.cache_key)) + else: + return json.dumps(self.model.get_all()) + + def _pre_post_callback(self, data): + pass + + + def _post_post_callback(self, m): + pass + + @route('/', methods=["POST"]) + def post(self): + + data = request.json + self.api._app.logger.info("INSERT Model %s", self.model.__name__) + self._pre_post_callback(data) + m = self.model.insert(**data) + if self.api.cache.get(self.cache_key) is not None: + self.api.cache.get(self.cache_key)[m.id] = m + + self._post_post_callback(m) + + return json.dumps(m) + + def _pre_put_callback(self, m): + pass + + def _post_put_callback(self, m): + pass + + + @route('/', methods=["PUT"]) + def put(self, id): + data = request.json + data["id"] = id + try: + del data["instance"] + except: + pass + if self.api.cache.get(self.cache_key) is not None: + self._pre_put_callback(self.api.cache.get(self.cache_key)[id]) + self.api.cache.get(self.cache_key)[id].__dict__.update(**data) + m = self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__) + self._post_put_callback(self.api.cache.get(self.cache_key)[id]) + return json.dumps(self.api.cache.get(self.cache_key)[id]) + else: + m = self.model.update(**data) + + self._post_put_callback(m) + return json.dumps(m) + + + def _pre_delete_callback(self, m): + pass + + def _post_delete_callback(self, id): + pass + + @route('/', methods=["DELETE"]) + def delete(self, id): + if self.api.cache.get(self.cache_key) is not None: + self._pre_delete_callback(self.api.cache.get(self.cache_key)[id]) + del self.api.cache.get(self.cache_key)[id] + m = self.model.delete(id) + + def _post_delete_callback(self, id): + pass + return ('',204) + + @classmethod + def post_init_callback(cls, obj): + pass + + @classmethod + def init_cache(cls): + with cls.api._app.app_context(): + + if cls.model.__as_array__ is True: + cls.api.cache[cls.cache_key] = [] + + for value in cls.model.get_all(): + cls.post_init_callback(value) + cls.api.cache[cls.cache_key].append(value) + else: + cls.api.cache[cls.cache_key] = {} + for key, value in cls.model.get_all().iteritems(): + cls.post_init_callback(value) + cls.api.cache[cls.cache_key][key] = value diff --git a/modules/core/core.py b/modules/core/core.py old mode 100644 new mode 100755 index c4b529d..9c1b6bc --- a/modules/core/core.py +++ b/modules/core/core.py @@ -1,494 +1,337 @@ -import inspect -import pprint - -import sqlite3 -from flask import make_response, g -import datetime -from datetime import datetime -from flask.views import MethodView -from flask_classy import FlaskView, route - -from time import localtime, strftime -from functools import wraps, update_wrapper - - -from props import * - -from hardware import * - -import time -import uuid - - -class NotificationAPI(object): - pass - -class ActorAPI(object): - - def init_actors(self): - self.app.logger.info("Init Actors") - t = self.cache.get("actor_types") - for key, value in t.iteritems(): - value.get("class").api = self - value.get("class").init_global() - - for key in self.cache.get("actors"): - self.init_actor(key) - - def init_actor(self, id): - try: - value = self.cache.get("actors").get(int(id)) - cfg = value.config.copy() - cfg.update(dict(api=self, id=id, name=value.name)) - cfg.update(dict(api=self, id=id, name=value.name)) - clazz = self.cache.get("actor_types").get(value.type).get("class") - value.instance = clazz(**cfg) - value.instance.init() - value.state = 0 - value.power = 100 - except Exception as e: - self.notify("Actor Error", "Failed to setup actor %s. Please check the configuraiton" % value.name, - type="danger", timeout=None) - self.app.logger.error("Initializing of Actor %s failed" % id) - - def switch_actor_on(self, id, power=None): - actor = self.cache.get("actors").get(id) - - if actor.state == 1: - return - - actor.instance.on(power=power) - actor.state = 1 - if power is not None: - - actor.power = power - self.emit("SWITCH_ACTOR", actor) - - def actor_power(self, id, power=100): - actor = self.cache.get("actors").get(id) - actor.instance.set_power(power=power) - actor.power = power - self.emit("SWITCH_ACTOR", actor) - - def switch_actor_off(self, id): - actor = self.cache.get("actors").get(id) - - if actor.state == 0: - return - actor.instance.off() - actor.state = 0 - self.emit("SWITCH_ACTOR", actor) - -class SensorAPI(object): - - def init_sensors(self): - ''' - Initialize all sensors - :return: - ''' - - self.app.logger.info("Init Sensors") - - t = self.cache.get("sensor_types") - for key, value in t.iteritems(): - value.get("class").init_global() - - for key in self.cache.get("sensors"): - self.init_sensor(key) - - def stop_sensor(self, id): - - try: - self.cache.get("sensors").get(id).instance.stop() - except Exception as e: - - self.app.logger.info("Stop Sensor Error") - pass - - - def init_sensor(self, id): - ''' - initialize sensor by id - :param id: - :return: - ''' - - def start_active_sensor(instance): - ''' - start active sensors as background job - :param instance: - :return: - ''' - instance.execute() - - try: - if id in self.cache.get("sensor_instances"): - self.cache.get("sensor_instances").get(id).stop() - value = self.cache.get("sensors").get(id) - - cfg = value.config.copy() - cfg.update(dict(api=self, id=id, name=value.name)) - clazz = self.cache.get("sensor_types").get(value.type).get("class") - value.instance = clazz(**cfg) - value.instance.init() - if isinstance(value.instance, SensorPassive): - # Passive Sensors - value.mode = "P" - else: - # Active Sensors - value.mode = "A" - t = self.socketio.start_background_task(target=start_active_sensor, instance=value.instance) - - except Exception as e: - - self.notify("Sensor Error", "Failed to setup Sensor %s. Please check the configuraiton" % value.name, type="danger", timeout=None) - self.app.logger.error("Initializing of Sensor %s failed" % id) - - def receive_sensor_value(self, id, value): - self.emit("SENSOR_UPDATE", self.cache.get("sensors")[id]) - self.save_to_file(id, value) - - def save_to_file(self, id, value, prefix="sensor"): - filename = "./logs/%s_%s.log" % (prefix, str(id)) - formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) - msg = str(formatted_time) + "," +str(value) + "\n" - - with open(filename, "a") as file: - file.write(msg) - - def log_action(self, text): - filename = "./logs/action.log" - formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) - with open(filename, "a") as file: - text = text.encode("utf-8") - file.write("%s,%s\n" % (formatted_time, text)) - - def shutdown_sensor(self, id): - self.cache.get("sensors")[id].stop() - - - def get_sensor_value(self, id): - try: - id = int(id) - return float(self.cache.get("sensors")[id].instance.last_value) - except Exception as e: - - return None - -class CacheAPI(object): - - def get_sensor(self, id): - try: - return self.cache["sensors"][id] - except: - return None - - def get_actor(self, id): - try: - return self.cache["actors"][id] - except: - return None - -class CraftBeerPi(ActorAPI, SensorAPI): - - cache = { - "init": {}, - "config": {}, - "actor_types": {}, - "sensor_types": {}, - "sensors": {}, - "sensor_instances": {}, - "init": [], - "background":[], - "step_types": {}, - "controller_types": {}, - "messages": [], - "plugins": {}, - "fermentation_controller_types": {}, - "fermenter_task": {} - } - buzzer = None - eventbus = {} - - - # constructor - def __init__(self, app, socketio): - self.app = app - self.socketio = socketio - - - def emit(self, key, data): - self.socketio.emit(key, data, namespace='/brew') - - def notify(self, headline, message, type="success", timeout=5000): - self.beep() - msg = {"id": str(uuid.uuid1()), "type": type, "headline": headline, "message": message, "timeout": timeout} - self.emit_message(msg) - - def beep(self): - if self.buzzer is not None: - self.buzzer.beep() - - - def add_cache_callback(self, key, method): - method.callback = True - self.cache[key] = method - - def get_config_parameter(self, key, default): - cfg = self.cache.get("config").get(key) - - if cfg is None: - return default - else: - return cfg.value - - def set_config_parameter(self, name, value): - from modules.config import Config - with self.app.app_context(): - update_data = {"name": name, "value": value} - self.cache.get("config")[name].__dict__.update(**update_data) - c = Config.update(**update_data) - self.emit("UPDATE_CONFIG", c) - - - def add_config_parameter(self, name, value, type, description, options=None): - from modules.config import Config - with self.app.app_context(): - c = Config.insert(**{"name":name, "value": value, "type": type, "description": description, "options": options}) - if self.cache.get("config") is not None: - self.cache.get("config")[c.name] = c - - def clear_cache(self, key, is_array=False): - if is_array: - self.cache[key] = [] - else: - self.cache[key] = {} - - # helper method for parsing props - def __parseProps(self, key, cls): - name = cls.__name__ - self.cache[key][name] = {"name": name, "class": cls, "properties": [], "actions": []} - tmpObj = cls() - members = [attr for attr in dir(tmpObj) if not callable(getattr(tmpObj, attr)) and not attr.startswith("__")] - for m in members: - if isinstance(tmpObj.__getattribute__(m), Property.Number): - t = tmpObj.__getattribute__(m) - self.cache[key][name]["properties"].append( - {"name": m, "label": t.label, "type": "number", "configurable": t.configurable, "description": t.description, "default_value": t.default_value}) - elif isinstance(tmpObj.__getattribute__(m), Property.Text): - t = tmpObj.__getattribute__(m) - self.cache[key][name]["properties"].append( - {"name": m, "label": t.label, "type": "text", "configurable": t.configurable, "default_value": t.default_value, "description": t.description}) - elif isinstance(tmpObj.__getattribute__(m), Property.Select): - t = tmpObj.__getattribute__(m) - self.cache[key][name]["properties"].append( - {"name": m, "label": t.label, "type": "select", "configurable": True, "options": t.options, "description": t.description}) - elif isinstance(tmpObj.__getattribute__(m), Property.Actor): - t = tmpObj.__getattribute__(m) - self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "actor", "configurable": t.configurable, "description": t.description}) - elif isinstance(tmpObj.__getattribute__(m), Property.Sensor): - t = tmpObj.__getattribute__(m) - self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "sensor", "configurable": t.configurable, "description": t.description}) - elif isinstance(tmpObj.__getattribute__(m), Property.Kettle): - t = tmpObj.__getattribute__(m) - self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "kettle", "configurable": t.configurable, "description": t.description}) - - for name, method in cls.__dict__.iteritems(): - if hasattr(method, "action"): - label = method.__getattribute__("label") - self.cache[key][cls.__name__]["actions"].append({"method": name, "label": label}) - - - return cls - - - def actor(self, cls): - return self.__parseProps("actor_types", cls) - - - - def actor2(self, description="", power=True, **options): - - def decorator(f): - print f() - print f - print options - print description - return f - return decorator - - def sensor(self, cls): - return self.__parseProps("sensor_types", cls) - - def controller(self, cls): - return self.__parseProps("controller_types", cls) - - def fermentation_controller(self, cls): - return self.__parseProps("fermentation_controller_types", cls) - - def get_controller(self, name): - return self.cache["controller_types"].get(name) - - def get_fermentation_controller(self, name): - return self.cache["fermentation_controller_types"].get(name) - - - # Step action - def action(self,label): - def real_decorator(func): - func.action = True - func.label = label - return func - return real_decorator - - # step decorator - def step(self, cls): - - key = "step_types" - name = cls.__name__ - self.cache[key][name] = {"name": name, "class": cls, "properties": [], "actions": []} - - tmpObj = cls() - members = [attr for attr in dir(tmpObj) if not callable(getattr(tmpObj, attr)) and not attr.startswith("__")] - for m in members: - if isinstance(tmpObj.__getattribute__(m), StepProperty.Number): - t = tmpObj.__getattribute__(m) - self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "number", "configurable": t.configurable, "default_value": t.default_value, "description": t.description}) - elif isinstance(tmpObj.__getattribute__(m), StepProperty.Text): - t = tmpObj.__getattribute__(m) - self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "text", "configurable": t.configurable, "default_value": t.default_value, "description": t.description}) - elif isinstance(tmpObj.__getattribute__(m), StepProperty.Select): - t = tmpObj.__getattribute__(m) - self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "select", "configurable": True, "options": t.options, "description": t.description}) - elif isinstance(tmpObj.__getattribute__(m), StepProperty.Actor): - t = tmpObj.__getattribute__(m) - self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "actor", "configurable": t.configurable, "description": t.description}) - elif isinstance(tmpObj.__getattribute__(m), StepProperty.Sensor): - t = tmpObj.__getattribute__(m) - self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "sensor", "configurable": t.configurable, "description": t.description}) - elif isinstance(tmpObj.__getattribute__(m), StepProperty.Kettle): - t = tmpObj.__getattribute__(m) - self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "kettle", "configurable": t.configurable, "description": t.description}) - - for name, method in cls.__dict__.iteritems(): - if hasattr(method, "action"): - label = method.__getattribute__("label") - self.cache[key][cls.__name__]["actions"].append({"method": name, "label": label}) - - return cls - - - # Event Bus - def event(self, name, async=False): - - def real_decorator(function): - if self.eventbus.get(name) is None: - self.eventbus[name] = [] - self.eventbus[name].append({"function": function, "async": async}) - def wrapper(*args, **kwargs): - return function(*args, **kwargs) - return wrapper - return real_decorator - - def emit_message(self, message): - self.emit_event(name="MESSAGE", message=message) - - def emit_event(self, name, **kwargs): - for i in self.eventbus.get(name, []): - if i["async"] is False: - i["function"](**kwargs) - else: - t = self.socketio.start_background_task(target=i["function"], **kwargs) - - # initializer decorator - def initalizer(self, order=0): - def real_decorator(function): - self.cache["init"].append({"function": function, "order": order}) - def wrapper(*args, **kwargs): - return function(*args, **kwargs) - return wrapper - return real_decorator - - - - def try_catch(self, errorResult="ERROR"): - def real_decorator(function): - def wrapper(*args, **kwargs): - try: - return function(*args, **kwargs) - except: - self.app.logger.error("Exception in function %s. Return default %s" % (function.__name__, errorResult)) - return errorResult - return wrapper - - return real_decorator - - def nocache(self, view): - @wraps(view) - def no_cache(*args, **kwargs): - response = make_response(view(*args, **kwargs)) - response.headers['Last-Modified'] = datetime.now() - response.headers[ - 'Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0' - response.headers['Pragma'] = 'no-cache' - response.headers['Expires'] = '-1' - return response - - return update_wrapper(no_cache, view) - - def init_kettle(self, id): - try: - value = self.cache.get("kettle").get(id) - value["state"] = False - except: - self.notify("Kettle Setup Faild", "Please check %s configuration" % value.name, type="danger", timeout=None) - self.app.logger.error("Initializing of Kettle %s failed" % id) - - - def run_init(self): - ''' - call all initialziers after startup - :return: - ''' - self.app.logger.info("Invoke Init") - self.cache["init"] = sorted(self.cache["init"], key=lambda k: k['order']) - for i in self.cache.get("init"): - self.app.logger.info("INITIALIZER - METHOD %s PAHT %s: " % (i.get("function").__name__, str(inspect.getmodule(i.get("function")).__file__) )) - i.get("function")(self) - - - - def backgroundtask(self, key, interval, config_parameter=None): - - ''' - Background Task Decorator - :param key: - :param interval: - :param config_parameter: - :return: - ''' - def real_decorator(function): - self.cache["background"].append({"function": function, "key": key, "interval": interval, "config_parameter": config_parameter}) - def wrapper(*args, **kwargs): - return function(*args, **kwargs) - return wrapper - return real_decorator - - def run_background_processes(self): - ''' - call all background task after startup - :return: - ''' - self.app.logger.info("Start Background") - - def job(interval, method): - while True: - try: - method(self) - except Exception as e: - self.app.logger.error("Exception" + method.__name__ + ": " + str(e)) - self.socketio.sleep(interval) - - - for value in self.cache.get("background"): - t = self.socketio.start_background_task(target=job, interval=value.get("interval"), method=value.get("function")) +import json +import logging +import os +import sqlite3 +import uuid +from datetime import datetime +from functools import wraps, update_wrapper +from importlib import import_module +from time import localtime, strftime + +from flask import Flask, redirect, json, g, make_response +from flask_socketio import SocketIO + +from baseapi import * +from db import DBModel +from modules.core.basetypes import Sensor, Actor +from modules.database.dbmodel import Kettle + + +class ComplexEncoder(json.JSONEncoder): + def default(self, obj): + try: + + if isinstance(obj, DBModel): + return obj.__dict__ + elif isinstance(obj, Actor): + return {"state": obj.value} + elif isinstance(obj, Sensor): + return {"value": obj.value, "unit": obj.unit} + elif hasattr(obj, "callback"): + return obj() + else: + return None + + return None + except TypeError as e: + pass + return None + + +class Addon(object): + def __init__(self, cbpi): + self.step = StepAPI(cbpi) + self.actor = ActorAPI(cbpi) + self.sensor = SensorAPI(cbpi) + self.kettle = KettleAPI(cbpi) + self.fermenter = FermenterAPI(cbpi) + self.core = CoreAPI(cbpi) + + def init(self): + self.core.init() + self.step.init() + self.actor.init() + self.sensor.init() + # self.kettle.init() + # self.fermenter.init() + + +class ActorCore(object): + key = "actor_types" + + def __init__(self, cbpi): + self.cbpi = cbpi + self.cbpi.cache["actors"] = {} + self.cbpi.cache[self.key] = {} + + def init(self): + for key, value in self.cbpi.cache["actors"].iteritems(): + self.init_one(key) + + def init_one(self, id): + try: + actor = self.cbpi.cache["actors"][id] + clazz = self.cbpi.cache[self.key].get(actor.type)["class"] + cfg = actor.config.copy() + cfg.update(dict(cbpi=self.cbpi, id=id)) + self.cbpi.cache["actors"][id].instance = clazz(**cfg) + self.cbpi.emit("INIT_ACTOR", id=id) + except Exception as e: + print e + self.cbpi._app.logger.error(e) + + def stop_one(self, id): + self.cbpi.cache["actors"][id]["instance"].stop() + self.cbpi.emit("STOP_ACTOR", id=id) + + def on(self, id, power=100): + try: + actor = self.cbpi.cache["actors"].get(int(id)) + actor.instance.on() + actor.state = 1 + actor.power = power + self.cbpi.ws_emit("SWITCH_ACTOR", actor) + self.cbpi.emit("SWITCH_ACTOR_ON", id=id, power=power) + return True + except Exception as e: + print e + return False + + def off(self, id): + try: + actor = self.cbpi.cache["actors"].get(int(id)) + actor.instance.off() + actor.state = 0 + self.cbpi.ws_emit("SWITCH_ACTOR", actor) + self.cbpi.emit("SWITCH_ACTOR_OFF", id=id) + return True + except Exception as e: + print e + return False + + def power(self, id, power): + try: + actor = self.cbpi.cache["actors"].get(int(id)) + actor.instance.power(power) + actor.power = power + self.cbpi.ws_emit("SWITCH_ACTOR", actor) + self.cbpi.emit("SWITCH_ACTOR_POWER_CHANGE", id=id, power=power) + return True + except Exception as e: + print e + return False + + def get_state(self, actor_id): + print actor_id + print self.cbpi + + +class SensorCore(object): + key = "sensor_types" + + def __init__(self, cbpi): + self.cbpi = cbpi + self.cbpi.cache["sensors"] = {} + self.cbpi.cache["sensor_instances"] = {} + self.cbpi.cache["sensor_types"] = {} + + def init(self): + for key, value in self.cbpi.cache["sensors"].iteritems(): + self.init_one(key) + + def init_one(self, id): + try: + sensor = self.cbpi.cache["sensors"][id] + clazz = self.cbpi.cache[self.key].get(sensor.type)["class"] + cfg = sensor.config.copy() + cfg.update(dict(cbpi=self.cbpi, id=id)) + self.cbpi.cache["sensors"][id].instance = clazz(**cfg) + self.cbpi.cache["sensors"][id].instance.init() + print self.cbpi.cache["sensors"][id].instance + self.cbpi.emit("INIT_SENSOR", id=id) + + def job(obj): + obj.execute() + + t = self.cbpi._socketio.start_background_task(target=job, obj=self.cbpi.cache["sensors"][id].instance) + self.cbpi.emit("INIT_SENSOR", id=id) + + except Exception as e: + print "ERROR" + self.cbpi._app.logger.error(e) + + def stop_one(self, id): + print "OBJ", self.cbpi.cache["sensors"][id] + self.cbpi.cache["sensors"][id].instance.stop() + self.cbpi.emit("STOP_SENSOR", id=id) + + def get_value(self, sensorid): + try: + return self.cbpi.cache["sensors"][sensorid].instance.value + except: + return None + + def get_state(self, actor_id): + print actor_id + print self.cbpi + + def write_log(self, id, value, prefix="sensor"): + filename = "./logs/%s_%s.log" % (prefix, str(id)) + formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) + msg = str(formatted_time) + "," + str(value) + "\n" + + with open(filename, "a") as file: + file.write(msg) + + +class BrewingCore(object): + def __init__(self, cbpi): + self.cbpi = cbpi + self.cbpi.cache["step_types"] = {} + self.cbpi.cache["controller_types"] = {} + + def log_action(self, text): + filename = "./logs/action.log" + formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) + with open(filename, "a") as file: + text = text.encode("utf-8") + file.write("%s,%s\n" % (formatted_time, text)) + + def get_controller(self, name): + return self.cbpi.cache["controller_types"].get(name) + + def set_target_temp(self, id, temp): + self.cbpi.cache.get("kettle")[id].target_temp = float(temp) + Kettle.update(**self.cbpi.cache.get("kettle")[id].__dict__) + self.cbpi.ws_emit("UPDATE_KETTLE_TARGET_TEMP", {"id": id, "target_temp": temp}) + self.cbpi.emit("SET_KETTLE_TARGET_TEMP", id=id, temp=temp) + + +class FermentationCore(object): + def __init__(self, cbpi): + self.cbpi = cbpi + self.cbpi.cache["fermenter"] = {} + self.cbpi.cache["fermentation_controller_types"] = {} + + def get_controller(self, name): + return self.cbpi.cache["fermentation_controller_types"].get(name) + + +class CraftBeerPI(object): + cache = {} + eventbus = {} + + def __init__(self): + FORMAT = '%(asctime)-15s - %(levelname)s - %(message)s' + logging.basicConfig(filename='./logs/app.log', level=logging.INFO, format=FORMAT) + self.cache["messages"] = [] + self.modules = {} + self.cache["users"] = {'manuel': {'pw': 'secret'}} + self.addon = Addon(self) + self.actor = ActorCore(self) + self.sensor = SensorCore(self) + self.brewing = BrewingCore(self) + self.fermentation = FermentationCore(self) + self._app = Flask(__name__) + self._app.secret_key = 'Cr4ftB33rP1' + self._app.json_encoder = ComplexEncoder + self._socketio = SocketIO(self._app, json=json, logging=False) + + @self._app.route('/') + def index(): + return redirect('ui') + + def run(self): + self.__init_db() + self.loadPlugins() + self.addon.init() + self.sensor.init() + self.actor.init() + self.beep() + self._socketio.run(self._app, host='0.0.0.0', port=5000) + + def beep(self): + self.buzzer.beep() + + def sleep(self, seconds): + self._socketio.sleep(seconds) + + def notify(self, headline, message, type="success", timeout=5000): + msg = {"id": str(uuid.uuid1()), "type": type, "headline": headline, "message": message, "timeout": timeout} + self.ws_emit("NOTIFY", msg) + + def ws_emit(self, key, data): + self._socketio.emit(key, data, namespace='/brew') + + def __init_db(self, ): + print "INIT DB" + with self._app.app_context(): + db = self.get_db() + try: + with self._app.open_resource('../../config/schema.sql', mode='r') as f: + db.cursor().executescript(f.read()) + db.commit() + except Exception as e: + print e + pass + + def nocache(self, view): + @wraps(view) + def no_cache(*args, **kwargs): + response = make_response(view(*args, **kwargs)) + response.headers['Last-Modified'] = datetime.now() + response.headers[ + 'Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0' + response.headers['Pragma'] = 'no-cache' + response.headers['Expires'] = '-1' + return response + + return update_wrapper(no_cache, view) + + def get_db(self): + db = getattr(g, '_database', None) + if db is None: + def dict_factory(cursor, row): + d = {} + for idx, col in enumerate(cursor.description): + d[col[0]] = row[idx] + return d + + db = g._database = sqlite3.connect('craftbeerpi.db') + db.row_factory = dict_factory + return db + + def add_cache_callback(self, key, method): + method.callback = True + self.cache[key] = method + + def get_config_parameter(self, key, default): + cfg = self.cache["config"].get(key) + if cfg is None: + return default + else: + return cfg.value + + def emit(self, key, **kwargs): + print key, kwargs + if self.eventbus.get(key) is not None: + for value in self.eventbus[key]: + if value["async"] is False: + value["function"](**kwargs) + else: + t = self.cbpi._socketio.start_background_task(target=value["function"], **kwargs) + + def loadPlugins(self): + for filename in os.listdir("./modules/plugins"): + print filename + if os.path.isdir("./modules/plugins/" + filename) is False: + continue + try: + self.modules[filename] = import_module("modules.plugins.%s" % (filename)) + except Exception as e: + print e + self.notify("Failed to load plugin %s " % filename, str(e), type="danger", timeout=None) + + +cbpi = CraftBeerPI() +addon = cbpi.addon diff --git a/modules/core/db.py b/modules/core/db.py old mode 100644 new mode 100755 index ef111ba..298571c --- a/modules/core/db.py +++ b/modules/core/db.py @@ -1,134 +1,133 @@ -import sqlite3 - -from flask import json, g - - -def get_db(): - db = getattr(g, '_database', None) - if db is None: - def dict_factory(cursor, row): - d = {} - for idx, col in enumerate(cursor.description): - d[col[0]] = row[idx] - return d - db = g._database = sqlite3.connect('craftbeerpi.db') - db.row_factory = dict_factory - return db - -class DBModel(object): - - __priamry_key__ = "id" - __as_array__ = False - __order_by__ = None - __json_fields__ = [] - - def __init__(self, args): - - self.__setattr__(self.__priamry_key__, args.get(self.__priamry_key__)) - for f in self.__fields__: - if f in self.__json_fields__: - if args.get(f) is not None: - - if isinstance(args.get(f) , dict) or isinstance(args.get(f) , list) : - self.__setattr__(f, args.get(f)) - else: - self.__setattr__(f, json.loads(args.get(f))) - else: - self.__setattr__(f, None) - else: - self.__setattr__(f, args.get(f)) - - @classmethod - def get_all(cls): - cur = get_db().cursor() - if cls.__order_by__ is not None: - - cur.execute("SELECT * FROM %s ORDER BY %s.'%s'" % (cls.__table_name__,cls.__table_name__,cls.__order_by__)) - else: - cur.execute("SELECT * FROM %s" % cls.__table_name__) - - if cls.__as_array__ is True: - result = [] - for r in cur.fetchall(): - - result.append( cls(r)) - else: - result = {} - for r in cur.fetchall(): - result[r.get(cls.__priamry_key__)] = cls(r) - return result - - @classmethod - def get_one(cls, id): - cur = get_db().cursor() - cur.execute("SELECT * FROM %s WHERE %s = ?" % (cls.__table_name__, cls.__priamry_key__), (id,)) - r = cur.fetchone() - if r is not None: - return cls(r) - else: - return None - - @classmethod - def delete(cls, id): - cur = get_db().cursor() - cur.execute("DELETE FROM %s WHERE %s = ? " % (cls.__table_name__, cls.__priamry_key__), (id,)) - get_db().commit() - - @classmethod - def insert(cls, **kwargs): - cur = get_db().cursor() - - - if cls.__priamry_key__ is not None and kwargs.has_key(cls.__priamry_key__): - query = "INSERT INTO %s (%s, %s) VALUES (?, %s)" % ( - cls.__table_name__, - cls.__priamry_key__, - ', '.join("'%s'" % str(x) for x in cls.__fields__), - ', '.join(['?'] * len(cls.__fields__))) - data = () - data = data + (kwargs.get(cls.__priamry_key__),) - for f in cls.__fields__: - if f in cls.__json_fields__: - data = data + (json.dumps(kwargs.get(f)),) - else: - data = data + (kwargs.get(f),) - else: - - query = 'INSERT INTO %s (%s) VALUES (%s)' % ( - cls.__table_name__, - ', '.join("'%s'" % str(x) for x in cls.__fields__), - ', '.join(['?'] * len(cls.__fields__))) - - data = () - for f in cls.__fields__: - if f in cls.__json_fields__: - data = data + (json.dumps(kwargs.get(f)),) - else: - data = data + (kwargs.get(f),) - - - cur.execute(query, data) - get_db().commit() - i = cur.lastrowid - kwargs["id"] = i - - return cls(kwargs) - - @classmethod - def update(cls, **kwargs): - cur = get_db().cursor() - query = 'UPDATE %s SET %s WHERE %s = ?' % ( - cls.__table_name__, - ', '.join("'%s' = ?" % str(x) for x in cls.__fields__),cls.__priamry_key__) - - data = () - for f in cls.__fields__: - if f in cls.__json_fields__: - data = data + (json.dumps(kwargs.get(f)),) - else: - data = data + (kwargs.get(f),) - - data = data + (kwargs.get(cls.__priamry_key__),) - cur.execute(query, data) - get_db().commit() - return cls(kwargs) \ No newline at end of file +import sqlite3 +from flask import json, g + + +def get_db(): + db = getattr(g, '_database', None) + if db is None: + def dict_factory(cursor, row): + d = {} + for idx, col in enumerate(cursor.description): + d[col[0]] = row[idx] + return d + db = g._database = sqlite3.connect('craftbeerpi.db') + db.row_factory = dict_factory + return db + +class DBModel(object): + + __priamry_key__ = "id" + __as_array__ = False + __order_by__ = None + __json_fields__ = [] + + def __init__(self, args): + + self.__setattr__(self.__priamry_key__, args.get(self.__priamry_key__)) + for f in self.__fields__: + if f in self.__json_fields__: + if args.get(f) is not None: + + if isinstance(args.get(f) , dict) or isinstance(args.get(f) , list) : + self.__setattr__(f, args.get(f)) + else: + self.__setattr__(f, json.loads(args.get(f))) + else: + self.__setattr__(f, None) + else: + self.__setattr__(f, args.get(f)) + + @classmethod + def get_all(cls): + cur = get_db().cursor() + if cls.__order_by__ is not None: + + cur.execute("SELECT * FROM %s ORDER BY %s.'%s'" % (cls.__table_name__,cls.__table_name__,cls.__order_by__)) + else: + cur.execute("SELECT * FROM %s" % cls.__table_name__) + + if cls.__as_array__ is True: + result = [] + for r in cur.fetchall(): + + result.append( cls(r)) + else: + result = {} + for r in cur.fetchall(): + result[r.get(cls.__priamry_key__)] = cls(r) + return result + + @classmethod + def get_one(cls, id): + cur = get_db().cursor() + cur.execute("SELECT * FROM %s WHERE %s = ?" % (cls.__table_name__, cls.__priamry_key__), (id,)) + r = cur.fetchone() + if r is not None: + return cls(r) + else: + return None + + @classmethod + def delete(cls, id): + cur = get_db().cursor() + cur.execute("DELETE FROM %s WHERE %s = ? " % (cls.__table_name__, cls.__priamry_key__), (id,)) + get_db().commit() + + @classmethod + def insert(cls, **kwargs): + cur = get_db().cursor() + + + if cls.__priamry_key__ is not None and kwargs.has_key(cls.__priamry_key__): + query = "INSERT INTO %s (%s, %s) VALUES (?, %s)" % ( + cls.__table_name__, + cls.__priamry_key__, + ', '.join("'%s'" % str(x) for x in cls.__fields__), + ', '.join(['?'] * len(cls.__fields__))) + data = () + data = data + (kwargs.get(cls.__priamry_key__),) + for f in cls.__fields__: + if f in cls.__json_fields__: + data = data + (json.dumps(kwargs.get(f)),) + else: + data = data + (kwargs.get(f),) + else: + + query = 'INSERT INTO %s (%s) VALUES (%s)' % ( + cls.__table_name__, + ', '.join("'%s'" % str(x) for x in cls.__fields__), + ', '.join(['?'] * len(cls.__fields__))) + + data = () + for f in cls.__fields__: + if f in cls.__json_fields__: + data = data + (json.dumps(kwargs.get(f)),) + else: + data = data + (kwargs.get(f),) + + + cur.execute(query, data) + get_db().commit() + i = cur.lastrowid + kwargs["id"] = i + + return cls(kwargs) + + @classmethod + def update(cls, **kwargs): + cur = get_db().cursor() + query = 'UPDATE %s SET %s WHERE %s = ?' % ( + cls.__table_name__, + ', '.join("'%s' = ?" % str(x) for x in cls.__fields__),cls.__priamry_key__) + + data = () + for f in cls.__fields__: + if f in cls.__json_fields__: + data = data + (json.dumps(kwargs.get(f)),) + else: + data = data + (kwargs.get(f),) + + data = data + (kwargs.get(cls.__priamry_key__),) + cur.execute(query, data) + get_db().commit() + return cls(kwargs) diff --git a/modules/core/db_mirgrate.py b/modules/core/db_mirgrate.py deleted file mode 100644 index bdb850e..0000000 --- a/modules/core/db_mirgrate.py +++ /dev/null @@ -1,47 +0,0 @@ -import sqlite3 -import os -from modules import cbpi -from db import get_db - -def execute_file(curernt_version, data): - if curernt_version >= data["version"]: - cbpi.app.logger.info("SKIP DB FILE: %s" % data["file"]) - return - try: - with sqlite3.connect("craftbeerpi.db") as conn: - with open('./update/%s' % data["file"], 'r') as f: - d = f.read() - sqlCommands = d.split(";") - cur = conn.cursor() - for s in sqlCommands: - cur.execute(s) - cur.execute("INSERT INTO schema_info (version,filename) values (?,?)", (data["version"], data["file"])) - conn.commit() - - except sqlite3.OperationalError as err: - print "EXCEPT" - print err - -@cbpi.initalizer(order=-9999) -def init(app=None): - - with cbpi.app.app_context(): - conn = get_db() - cur = conn.cursor() - current_version = None - try: - cur.execute("SELECT max(version) as m FROM schema_info") - m = cur.fetchone() - current_version = m["m"] - except: - pass - result = [] - for filename in os.listdir("./update"): - if filename.endswith(".sql"): - d = {"version": int(filename[:filename.index('_')]), "file": filename} - result.append(d) - execute_file(current_version, d) - - - - diff --git a/modules/core/hardware.py b/modules/core/hardware.py deleted file mode 100644 index e329f9a..0000000 --- a/modules/core/hardware.py +++ /dev/null @@ -1,108 +0,0 @@ -# -*- coding: utf-8 -*- - -class Base(object): - __dirty = False - - @classmethod - def init_global(cls): - pass - - def get_config_parameter(self, key, default_value): - return self.api.get_config_parameter(key, default_value) - - def sleep(self, seconds): - self.api.socketio.sleep(seconds) - - def init(self): - pass - - def stop(self): - pass - - def update(self, **kwds): - pass - - def __init__(self, *args, **kwds): - for a in kwds: - super(Base, self).__setattr__(a, kwds.get(a)) - self.api = kwds.get("api") - self.id = kwds.get("id") - self.value = None - self.__dirty = False - - def __setattr__(self, name, value): - - if name != "_Base__dirty": - self.__dirty = True - super(Base, self).__setattr__(name, value) - else: - super(Base, self).__setattr__(name, value) - - -class SensorBase(Base): - - last_value = 0 - - def init(self): - print "INIT Base SENSOR" - - def stop(self): - print "STOP SENSOR" - - def data_received(self, data): - - - self.last_value = data - self.api.receive_sensor_value(self.id, data) - - def get_unit(self): - if self.get_config_parameter("unit", "C") == "C": - return "°C" - else: - return "°F" - - def get_value(self): - - return {"value": self.last_value, "unit": self.get_unit()} - -class SensorActive(SensorBase): - - __running = False - - def is_running(self): - - return self.__running - - def init(self): - self.__running = True - - def stop(self): - self.__running = False - - - def execute(self): - pass - - -class SensorPassive(SensorBase): - def init(self): - print "INIT PASSIV SENSOR" - pass - - def read(self): - return 0 - - -class ActorBase(Base): - - def state(self): - return 1 - - def set_power(self, power): - pass - - def on(self, power=0): - pass - - def off(self): - pass diff --git a/modules/core/login.py b/modules/core/login.py new file mode 100755 index 0000000..ca0937f --- /dev/null +++ b/modules/core/login.py @@ -0,0 +1,36 @@ +import flask_login +from modules.core.core import cbpi, addon + +class User(flask_login.UserMixin): + pass + +@addon.core.initializer(order=0) +def log(cbpi): + + + cbpi._login_manager = flask_login.LoginManager() + cbpi._login_manager.init_app(cbpi._app) + @cbpi._app.route('/login', methods=['GET', 'POST']) + def login(): + user = User() + user.id = "manuel" + flask_login.login_user(user) + return "OK" + + @cbpi._app.route('/logout') + def logout(): + flask_login.logout_user() + return 'Logged out' + + @cbpi._login_manager.user_loader + def user_loader(email): + if email not in cbpi.cache["users"]: + return + + user = User() + user.id = email + return user + + @cbpi._login_manager.unauthorized_handler + def unauthorized_handler(): + return 'Unauthorized :-(' diff --git a/modules/core/props.py b/modules/core/proptypes.py old mode 100644 new mode 100755 similarity index 65% rename from modules/core/props.py rename to modules/core/proptypes.py index 1de5325..912b2f5 --- a/modules/core/props.py +++ b/modules/core/proptypes.py @@ -1,68 +1,48 @@ -class PropertyType(object): - pass - -class Property(object): - class Select(PropertyType): - def __init__(self, label, options, description=""): - PropertyType.__init__(self) - self.label = label - self.options = options - self.description = description - - class Number(PropertyType): - def __init__(self, label, configurable=False, default_value=None, unit="", description=""): - PropertyType.__init__(self) - self.label = label - self.configurable = configurable - self.default_value = default_value - self.description = description - - class Text(PropertyType): - def __init__(self, label, configurable=False, default_value="", description=""): - PropertyType.__init__(self) - self.label = label - self.configurable = configurable - self.default_value = default_value - self.description = description - - class Actor(PropertyType): - def __init__(self, label, description=""): - PropertyType.__init__(self) - self.label = label - self.configurable = True - self.description = description - - class Sensor(PropertyType): - def __init__(self, label, description=""): - PropertyType.__init__(self) - self.label = label - self.configurable = True - self.description = description - - class Kettle(PropertyType): - def __init__(self, label, description=""): - PropertyType.__init__(self) - self.label = label - self.configurable = True - self.description = description - - -class StepProperty(Property): - class Actor(PropertyType): - def __init__(self, label, description=""): - PropertyType.__init__(self) - self.label = label - self.configurable = True - self.description = description - class Sensor(PropertyType): - def __init__(self, label, description=""): - PropertyType.__init__(self) - self.label = label - self.configurable = True - self.description = description - class Kettle(PropertyType): - def __init__(self, label, description=""): - PropertyType.__init__(self) - self.label = label - self.configurable = True - self.description = description +class PropertyType(object): + pass + +class Property(object): + class Select(PropertyType): + def __init__(self, label, options, description=""): + PropertyType.__init__(self) + self.label = label + self.options = options + self.description = description + + class Number(PropertyType): + def __init__(self, label, configurable=False, default_value=None, unit="", description=""): + PropertyType.__init__(self) + self.label = label + self.configurable = configurable + self.default_value = default_value + self.description = description + + class Text(PropertyType): + def __init__(self, label, configurable=False, required=False, default_value="", description=""): + PropertyType.__init__(self) + self.label = label + self.required = required + self.configurable = configurable + self.default_value = default_value + self.description = description + + class Actor(PropertyType): + def __init__(self, label, description=""): + PropertyType.__init__(self) + self.label = label + self.configurable = True + self.description = description + + class Sensor(PropertyType): + def __init__(self, label, description=""): + PropertyType.__init__(self) + self.label = label + self.configurable = True + self.description = description + + class Kettle(PropertyType): + def __init__(self, label, description=""): + PropertyType.__init__(self) + self.label = label + self.configurable = True + self.description = description diff --git a/modules/core/step.py b/modules/core/step.py old mode 100644 new mode 100755 index 0084a37..e69de29 --- a/modules/core/step.py +++ b/modules/core/step.py @@ -1,146 +0,0 @@ -from modules import cbpi -from modules.core.props import StepProperty, Property -import time - - -class NotificationAPI(object): - def notify(self, headline, message, type="success", timeout=5000): - self.api.notify(headline, message, type, timeout) - -class ActorAPI(NotificationAPI): - - @cbpi.try_catch(None) - def actor_on(self, id, power=100): - self.api.switch_actor_on(int(id), power=power) - - @cbpi.try_catch(None) - def actor_off(self, id): - self.api.switch_actor_off(int(id)) - - @cbpi.try_catch(None) - def actor_power(self, id, power): - self.api.actor_power(int(id), power) - -class SensorAPI(NotificationAPI): - - @cbpi.try_catch(None) - def get_sensor_value(self, id): - return cbpi.get_sensor_value(id) - -class KettleAPI(NotificationAPI): - - @cbpi.try_catch(None) - def get_kettle_temp(self, id=None): - id = int(id) - if id is None: - id = self.kettle_id - return cbpi.get_sensor_value(int(self.api.cache.get("kettle").get(id).sensor)) - - @cbpi.try_catch(None) - def get_target_temp(self, id=None): - id = int(id) - if id is None: - id = self.kettle_id - return self.api.cache.get("kettle").get(id).target_temp - - def set_target_temp(self, temp, id=None): - temp = float(temp) - - try: - if id is None: - self.api.emit_event("SET_TARGET_TEMP", id=self.kettle_id, temp=temp) - else: - self.api.emit_event("SET_TARGET_TEMP", id=id, temp=temp) - except Exception as e: - - self.notify("Faild to set Target Temp", "", type="warning") - -class Timer(object): - timer_end = Property.Number("TIMER_END", configurable=False) - - def start_timer(self, timer): - - if self.timer_end is not None: - return - self.timer_end = int(time.time()) + timer - - - def stop_timer(self): - if self.timer_end is not None: - self.timer_end = None - - def is_timer_running(self): - if self.timer_end is not None: - return True - else: - return False - - def timer_remaining(self): - if self.timer_end is not None: - return self.timer_end - int(time.time()) - else: - return None - - def is_timer_finished(self): - if self.timer_end is None: - return None - if self.timer_end <= int(time.time()): - return True - else: - return False - - -class StepBase(Timer, ActorAPI, SensorAPI, KettleAPI): - - __dirty = False - managed_fields = [] - n = False - - def next(self): - self.n = True - - def init(self): - pass - - def finish(self): - pass - - def reset(self): - pass - - def execute(self): - print "-------------" - print "Step Info" - print "Kettle ID: %s" % self.kettle_id - print "ID: %s" % self.id - - - def __init__(self, *args, **kwds): - - for a in kwds: - - super(StepBase, self).__setattr__(a, kwds.get(a)) - - - self.api = kwds.get("api") - self.id = kwds.get("id") - self.name = kwds.get("name") - self.kettle_id = kwds.get("kettleid") - self.value = None - self.__dirty = False - - - def is_dirty(self): - return self.__dirty - - def reset_dirty(self): - self.__dirty = False - - def __setattr__(self, name, value): - - if name != "_StepBase__dirty" and name in self.managed_fields: - self.__dirty = True - super(StepBase, self).__setattr__(name, value) - else: - super(StepBase, self).__setattr__(name, value) - diff --git a/modules/database/__init__.py b/modules/database/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/modules/database/dbmodel.py b/modules/database/dbmodel.py new file mode 100644 index 0000000..82c94bb --- /dev/null +++ b/modules/database/dbmodel.py @@ -0,0 +1,134 @@ +from modules.core.db import DBModel, get_db + + +class Kettle(DBModel): + __fields__ = ["name","sensor", "heater", "automatic", "logic", "config", "agitator", "target_temp"] + __table_name__ = "kettle" + __json_fields__ = ["config"] + +class Sensor(DBModel): + __fields__ = ["name","type", "config", "hide"] + __table_name__ = "sensor" + __json_fields__ = ["config"] + +class Config(DBModel): + __fields__ = ["type", "value", "description", "options"] + __table_name__ = "config" + __json_fields__ = ["options"] + __priamry_key__ = "name" + +class Actor(DBModel): + __fields__ = ["name","type", "config", "hide"] + __table_name__ = "actor" + __json_fields__ = ["config"] + +class Step(DBModel): + __fields__ = ["name","type", "stepstate", "state", "start", "end", "order", "config"] + __table_name__ = "step" + __json_fields__ = ["config", "stepstate"] + __order_by__ = "order" + __as_array__ = True + + @classmethod + def get_max_order(cls): + cur = get_db().cursor() + cur.execute("SELECT max(step.'order') as 'order' FROM %s" % cls.__table_name__) + r = cur.fetchone() + return r.get("order") + + @classmethod + def get_by_state(cls, state, order=True): + cur = get_db().cursor() + cur.execute("SELECT * FROM %s WHERE state = ? ORDER BY %s.'order'" % (cls.__table_name__,cls.__table_name__,), state) + r = cur.fetchone() + if r is not None: + return cls(r) + else: + return None + + @classmethod + def delete_all(cls): + cur = get_db().cursor() + cur.execute("DELETE FROM %s" % cls.__table_name__) + get_db().commit() + + @classmethod + def reset_all_steps(cls): + cur = get_db().cursor() + cur.execute("UPDATE %s SET state = 'I', stepstate = NULL , start = NULL, end = NULL " % cls.__table_name__) + get_db().commit() + + @classmethod + def update_state(cls, id, state): + cur = get_db().cursor() + cur.execute("UPDATE %s SET state = ? WHERE id =?" % cls.__table_name__, (state, id)) + get_db().commit() + + @classmethod + def update_step_state(cls, id, state): + cur = get_db().cursor() + cur.execute("UPDATE %s SET stepstate = ? WHERE id =?" % cls.__table_name__, (json.dumps(state),id)) + get_db().commit() + + @classmethod + def sort(cls, new_order): + cur = get_db().cursor() + + for e in new_order: + + cur.execute("UPDATE %s SET '%s' = ? WHERE id = ?" % (cls.__table_name__, "order"), (e[1], e[0])) + get_db().commit() + + +class Fermenter(DBModel): + __fields__ = ["name", "brewname", "sensor", "sensor2", "sensor3", "heater", "cooler", "logic", "config", "target_temp"] + __table_name__ = "fermenter" + __json_fields__ = ["config"] + +class FermenterStep(DBModel): + __fields__ = ["name", "days", "hours", "minutes", "temp", "direction", "order", "state", "start", "end", "timer_start", "fermenter_id"] + __table_name__ = "fermenter_step" + + @classmethod + def get_by_fermenter_id(cls, id): + cur = get_db().cursor() + cur.execute("SELECT * FROM %s WHERE fermenter_id = ?" % cls.__table_name__,(id,)) + result = [] + for r in cur.fetchall(): + result.append(cls(r)) + return result + + @classmethod + def get_max_order(cls,id): + cur = get_db().cursor() + cur.execute("SELECT max(fermenter_step.'order') as 'order' FROM %s WHERE fermenter_id = ?" % cls.__table_name__, (id,)) + r = cur.fetchone() + return r.get("order") + + @classmethod + def update_state(cls, id, state): + cur = get_db().cursor() + cur.execute("UPDATE %s SET state = ? WHERE id =?" % cls.__table_name__, (state, id)) + get_db().commit() + + @classmethod + def update_timer(cls, id, timer): + cur = get_db().cursor() + cur.execute("UPDATE %s SET timer_start = ? WHERE id =?" % cls.__table_name__, (timer, id)) + get_db().commit() + + @classmethod + def get_by_state(cls, state): + cur = get_db().cursor() + cur.execute("SELECT * FROM %s WHERE state = ?" % cls.__table_name__, state) + r = cur.fetchone() + if r is not None: + return cls(r) + else: + return None + + @classmethod + def reset_all_steps(cls,id): + cur = get_db().cursor() + cur.execute("UPDATE %s SET state = 'I', start = NULL, end = NULL, timer_start = NULL WHERE fermenter_id = ?" % cls.__table_name__, (id,)) + get_db().commit() \ No newline at end of file diff --git a/modules/fermenter/__init__.py b/modules/fermenter/__init__.py old mode 100755 new mode 100644 index e2dcf91..b6ce4c9 --- a/modules/fermenter/__init__.py +++ b/modules/fermenter/__init__.py @@ -2,63 +2,12 @@ import time from flask import request from flask_classy import route -from modules import DBModel, cbpi, get_db +from modules.core.core import cbpi +from modules.core.db import get_db, DBModel from modules.core.baseview import BaseView +from modules.database.dbmodel import Fermenter, FermenterStep -class Fermenter(DBModel): - __fields__ = ["name", "brewname", "sensor", "sensor2", "sensor3", "heater", "cooler", "logic", "config", "target_temp"] - __table_name__ = "fermenter" - __json_fields__ = ["config"] - -class FermenterStep(DBModel): - __fields__ = ["name", "days", "hours", "minutes", "temp", "direction", "order", "state", "start", "end", "timer_start", "fermenter_id"] - __table_name__ = "fermenter_step" - - @classmethod - def get_by_fermenter_id(cls, id): - cur = get_db().cursor() - cur.execute("SELECT * FROM %s WHERE fermenter_id = ?" % cls.__table_name__,(id,)) - result = [] - for r in cur.fetchall(): - result.append(cls(r)) - return result - - @classmethod - def get_max_order(cls,id): - cur = get_db().cursor() - cur.execute("SELECT max(fermenter_step.'order') as 'order' FROM %s WHERE fermenter_id = ?" % cls.__table_name__, (id,)) - r = cur.fetchone() - return r.get("order") - - @classmethod - def update_state(cls, id, state): - cur = get_db().cursor() - cur.execute("UPDATE %s SET state = ? WHERE id =?" % cls.__table_name__, (state, id)) - get_db().commit() - - @classmethod - def update_timer(cls, id, timer): - cur = get_db().cursor() - cur.execute("UPDATE %s SET timer_start = ? WHERE id =?" % cls.__table_name__, (timer, id)) - get_db().commit() - - @classmethod - def get_by_state(cls, state): - cur = get_db().cursor() - cur.execute("SELECT * FROM %s WHERE state = ?" % cls.__table_name__, state) - r = cur.fetchone() - if r is not None: - return cls(r) - else: - return None - - @classmethod - def reset_all_steps(cls,id): - cur = get_db().cursor() - cur.execute("UPDATE %s SET state = 'I', start = NULL, end = NULL, timer_start = NULL WHERE fermenter_id = ?" % cls.__table_name__, (id,)) - get_db().commit() - class FermenterView(BaseView): model = Fermenter cache_key = "fermenter" @@ -86,7 +35,7 @@ class FermenterView(BaseView): temp = float(temp) cbpi.cache.get(self.cache_key)[id].target_temp = float(temp) self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__) - cbpi.emit("UPDATE_FERMENTER_TARGET_TEMP", {"id": id, "target_temp": temp}) + cbpi.ws_emit("UPDATE_FERMENTER_TARGET_TEMP", {"id": id, "target_temp": temp}) return ('', 204) @route('//brewname', methods=['POST']) @@ -95,7 +44,7 @@ class FermenterView(BaseView): brewname = data.get("brewname") cbpi.cache.get(self.cache_key)[id].brewname = brewname self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__) - cbpi.emit("UPDATE_FERMENTER_BREWNAME", {"id": id, "brewname": brewname}) + cbpi.ws_emit("UPDATE_FERMENTER_BREWNAME", {"id": id, "brewname": brewname}) return ('', 204) @classmethod @@ -119,7 +68,7 @@ class FermenterView(BaseView): cbpi.cache.get(self.cache_key)[id].steps.append(f) - cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) + cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) return ('', 204) @@ -141,7 +90,7 @@ class FermenterView(BaseView): FermenterStep.update(**s.__dict__) break - cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) + cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) return ('', 204) @route('//step/', methods=["DELETE"]) @@ -152,7 +101,7 @@ class FermenterView(BaseView): del cbpi.cache.get(self.cache_key)[id].steps[idx] FermenterStep.delete(s.id) break - cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) + cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) return ('', 204) @route('//start', methods=['POST']) @@ -179,7 +128,8 @@ class FermenterView(BaseView): if inactive is not None: fermenter = self.get_fermenter(inactive.fermenter_id) - current_temp = cbpi.get_sensor_value(int(fermenter.sensor)) + + current_temp = cbpi.sensor.get_value(int(fermenter.sensor)) inactive.state = 'A' inactive.start = time.time() @@ -190,7 +140,7 @@ class FermenterView(BaseView): cbpi.cache["fermenter_task"][id] = inactive - cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) + cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) return ('', 204) @route('//reset', methods=["POST"]) @@ -202,7 +152,7 @@ class FermenterView(BaseView): if id in cbpi.cache["fermenter_task"]: del cbpi.cache["fermenter_task"][id] - cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) + cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) return ('', 204) @route('//automatic', methods=['POST']) @@ -216,21 +166,23 @@ class FermenterView(BaseView): cfg = fermenter.config.copy() cfg.update( dict(api=cbpi, fermenter_id=fermenter.id, heater=fermenter.heater, sensor=fermenter.sensor)) - instance = cbpi.get_fermentation_controller(fermenter.logic).get("class")(**cfg) + instance = cbpi.fermentation.get_controller(fermenter.logic).get("class")(**cfg) instance.init() fermenter.instance = instance def run(instance): instance.run() - t = cbpi.socketio.start_background_task(target=run, instance=instance) + t = cbpi._socketio.start_background_task(target=run, instance=instance) fermenter.state = not fermenter.state - cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id)) + cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id)) + cbpi.emit("FERMENTER_CONTROLLER_STARTED", id=id) else: # Stop controller fermenter.instance.stop() fermenter.state = not fermenter.state - cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id)) + cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id)) + cbpi.emit("FERMENTER_CONTROLLER_STOPPED", id=id) except Exception as e: print e @@ -255,7 +207,7 @@ class FermenterView(BaseView): step.timer_start = target_time - cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) + cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) def check_step(self): for key, value in cbpi.cache["fermenter_task"].iteritems(): @@ -282,35 +234,33 @@ class FermenterView(BaseView): pass -@cbpi.backgroundtask(key="read_target_temps_fermenter", interval=5) -def read_target_temps(api): +@cbpi.addon.core.backgroundjob(key="read_target_temps_fermenter", interval=5) +def read_target_temps(cbpi): """ background process that reads all passive sensors in interval of 1 second :return: None """ - result = {} for key, value in cbpi.cache.get("fermenter").iteritems(): - cbpi.save_to_file(key, value.target_temp, prefix="fermenter") + cbpi.sensor.write_log(key, value.target_temp, prefix="fermenter") + instance = FermenterView() -@cbpi.backgroundtask(key="fermentation_task", interval=1) -def execute_fermentation_step(api): - with cbpi.app.app_context(): +@cbpi.addon.core.backgroundjob(key="fermentation_task", interval=1) +def execute_fermentation_step(cbpi): + with cbpi._app.app_context(): instance.check_step() def init_active_steps(): - ''' - active_steps = FermenterStep.query.filter_by(state='A') - for a in active_steps: - db.session.expunge(a) - cbpi.cache["fermenter_task"][a.fermenter_id] = a - ''' - -@cbpi.initalizer(order=1) + pass + + + +@cbpi.addon.core.initializer(order=1) def init(cbpi): - FermenterView.register(cbpi.app, route_base='/api/fermenter') + cbpi.cache["fermenter_task"] = {} + FermenterView.register(cbpi._app, route_base='/api/fermenter') FermenterView.init_cache() diff --git a/modules/kettle/__init__.py b/modules/kettle/__init__.py old mode 100644 new mode 100755 index c8bff56..8588dda --- a/modules/kettle/__init__.py +++ b/modules/kettle/__init__.py @@ -1,96 +1,264 @@ -from flask import request -from flask_classy import FlaskView, route -from modules import cbpi, socketio -from modules.core.baseview import BaseView -from modules.core.db import DBModel - -class Kettle(DBModel): - __fields__ = ["name","sensor", "heater", "automatic", "logic", "config", "agitator", "target_temp"] - __table_name__ = "kettle" - __json_fields__ = ["config"] - - -class Kettle2View(BaseView): - model = Kettle - cache_key = "kettle" - - @classmethod - def _pre_post_callback(self, data): - data["target_temp"] = 0 - - @classmethod - def post_init_callback(cls, obj): - obj.state = False - - - def _post_post_callback(self, m): - m.state = False - - def _pre_put_callback(self, m): - try: - m.instance.stop() - except: - pass - - def _post_put_callback(self, m): - m.state = False - - @route('//targettemp/', methods=['POST']) - def postTargetTemp(self, id, temp): - id = int(id) - temp = float(temp) - cbpi.cache.get("kettle")[id].target_temp = float(temp) - self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__) - cbpi.emit("UPDATE_KETTLE_TARGET_TEMP", {"id": id, "target_temp": temp}) - return ('', 204) - - @route('//automatic', methods=['POST']) - def toggle(self, id): - kettle = cbpi.cache.get("kettle")[id] - - if kettle.state is False: - # Start controller - if kettle.logic is not None: - cfg = kettle.config.copy() - cfg.update(dict(api=cbpi, kettle_id=kettle.id, heater=kettle.heater, sensor=kettle.sensor)) - instance = cbpi.get_controller(kettle.logic).get("class")(**cfg) - instance.init() - kettle.instance = instance - def run(instance): - instance.run() - t = self.api.socketio.start_background_task(target=run, instance=instance) - kettle.state = not kettle.state - cbpi.emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(id)) - else: - # Stop controller - kettle.instance.stop() - kettle.state = not kettle.state - cbpi.emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(id)) - return ('', 204) - -@cbpi.event("SET_TARGET_TEMP") -def set_target_temp(id, temp): - ''' - Change Taget Temp Event - :param id: kettle id - :param temp: target temp to set - :return: None - ''' - - Kettle2View().postTargetTemp(id,temp) - -@cbpi.backgroundtask(key="read_target_temps", interval=5) -def read_target_temps(api): - """ - background process that reads all passive sensors in interval of 1 second - :return: None - """ - result = {} - for key, value in cbpi.cache.get("kettle").iteritems(): - cbpi.save_to_file(key, value.target_temp, prefix="kettle") - -@cbpi.initalizer() -def init(cbpi): - Kettle2View.api = cbpi - Kettle2View.register(cbpi.app,route_base='/api/kettle') - Kettle2View.init_cache() +from flask import request +from flask_classy import FlaskView, route +from modules.core.core import cbpi +from modules.core.baseview import BaseView +from modules.core.db import DBModel +from modules.database.dbmodel import Kettle + +class KettleView(BaseView): + model = Kettle + cache_key = "kettle" + + @route('/', methods=["GET"]) + def getAll(self): + """ + Get all Kettles + --- + tags: + - kettle + responses: + 200: + description: List auf all Kettles + """ + return super(KettleView, self).getAll() + + @route('/', methods=["POST"]) + def post(self): + """ + Create a new kettle + --- + tags: + - kettle + parameters: + - in: body + name: body + schema: + id: Kettle + required: + - name + properties: + name: + type: string + description: name for user + sensor: + type: string + description: name for user + heater: + type: string + description: name for user + automatic: + type: string + description: name for user + logic: + type: string + description: name for user + config: + type: string + description: name for user + agitator: + type: string + description: name for user + target_temp: + type: string + description: name for user + responses: + 200: + description: User created + """ + return super(KettleView, self).post() + + + @route('/', methods=["PUT"]) + def put(self, id): + """ + Update a kettle + --- + tags: + - kettle + parameters: + - in: path + name: id + schema: + type: integer + required: true + description: Numeric ID of the Kettle + - in: body + name: body + schema: + id: Kettle + required: + - name + properties: + name: + type: string + description: name for user + sensor: + type: string + description: name for user + heater: + type: string + description: name for user + automatic: + type: string + description: name for user + logic: + type: string + description: name for user + config: + type: string + description: name for user + agitator: + type: string + description: name for user + target_temp: + type: string + description: name for user + responses: + 200: + description: User created + """ + return super(KettleView, self).put(id) + + @route('/', methods=["DELETE"]) + def delete(self, id): + """ + Delete a kettle + --- + tags: + - kettle + parameters: + - in: path + name: id + schema: + type: integer + required: true + description: Numeric ID of the Kettle + + responses: + 200: + description: User created + """ + return super(KettleView, self).delete(id) + + @classmethod + def _pre_post_callback(self, data): + data["target_temp"] = 0 + + @classmethod + def post_init_callback(cls, obj): + obj.state = False + + + def _post_post_callback(self, m): + m.state = False + + def _pre_put_callback(self, m): + try: + m.instance.stop() + except: + pass + + def _post_put_callback(self, m): + m.state = False + + @route('//targettemp/', methods=['POST']) + def postTargetTemp(self, id, temp): + + """ + Set Target Temp + --- + tags: + - kettle + parameters: + - required: true + type: string + description: ID of pet to return + in: path + name: id + - required: true + type: string + description: Temperature you like to set + in: path + name: temp + responses: + 201: + description: User created + """ + + id = int(id) + temp = float(temp) + cbpi.brewing.set_target_temp(id, temp) + return ('', 204) + + @route('//automatic', methods=['POST']) + def toggle(self, id): + + """ + Set Target Temp + --- + tags: + - kettle + parameters: + - required: true + type: string + description: ID of pet to return + in: path + name: id + - required: true + type: string + description: Temperature you like to set + in: path + name: temp + responses: + 201: + description: User created + """ + + kettle = cbpi.cache.get("kettle")[id] + if kettle.state is False: + # Start controller + if kettle.logic is not None: + cfg = kettle.config.copy() + cfg.update(dict(api=cbpi, kettle_id=kettle.id, heater=kettle.heater, sensor=kettle.sensor)) + instance = cbpi.brewing.get_controller(kettle.logic).get("class")(**cfg) + instance.init() + kettle.instance = instance + def run(instance): + instance.run() + t = self.api._socketio.start_background_task(target=run, instance=instance) + kettle.state = not kettle.state + cbpi.ws_emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(id)) + cbpi.emit("KETTLE_CONTROLLER_STARTED", id=id) + else: + # Stop controller + kettle.instance.stop() + kettle.state = not kettle.state + cbpi.ws_emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(id)) + cbpi.emit("KETTLE_CONTROLLER_STOPPED", id=id) + return ('', 204) + +#@cbpi.event("SET_TARGET_TEMP") +def set_target_temp(id, temp): + ''' + Change Taget Temp Event + :param id: kettle id + :param temp: target temp to set + :return: None + ''' + KettleView().postTargetTemp(id, temp) + +@cbpi.addon.core.backgroundjob(key="read_target_temps", interval=5) +def read_target_temps(api): + """ + background process that reads all passive sensors in interval of 1 second + :return: None + """ + result = {} + for key, value in cbpi.cache.get("kettle").iteritems(): + cbpi.sensor.write_log(key, value.target_temp, prefix="kettle") + +@cbpi.addon.core.initializer() +def init(cbpi): + KettleView.api = cbpi + KettleView.register(cbpi._app, route_base='/api/kettle') + KettleView.init_cache() diff --git a/modules/logs/__init__.py b/modules/logs/__init__.py old mode 100644 new mode 100755 index b89697f..d3dfc4e --- a/modules/logs/__init__.py +++ b/modules/logs/__init__.py @@ -1 +1,175 @@ -import endpoints \ No newline at end of file +import datetime +import os +from flask import Blueprint, request, send_from_directory, json +from flask_classy import FlaskView, route +from modules.core.core import cbpi + + +class LogView(FlaskView): + + @route('/', methods=['GET']) + def get_all_logfiles(self): + """ + Get a list of all Log Files + --- + tags: + - logs + responses: + 200: + description: List of all log files + """ + result = [] + for filename in os.listdir("./logs"): + if filename.endswith(".log"): + result.append(filename) + return json.dumps(result) + + @route('/actions') + def actions(self): + """ + Get a list of all brewing actions + --- + tags: + - logs + responses: + 200: + description: List of all log files + """ + filename = "./logs/action.log" + if os.path.isfile(filename) == False: + return + import csv + array = [] + with open(filename, 'rb') as f: + reader = csv.reader(f) + for row in reader: + try: + array.append([int((datetime.datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S") - datetime.datetime(1970, 1, 1)).total_seconds()) * 1000, row[1]]) + except: + pass + return json.dumps(array) + + @route('/', methods=["DELETE"]) + def clearlog(self, file): + """ + Delete a log file by name + --- + tags: + - logs + parameters: + - in: path + name: file + schema: + type: string + required: true + description: File name + responses: + 204: + description: Log deleted + """ + if not self.check_filename(file): + return ('File Not Found', 404) + + filename = "./logs/%s" % file + if os.path.isfile(filename) == True: + os.remove(filename) + cbpi.notify("log deleted succesfully", "") + return ('', 204) + else: + cbpi.notify("Failed to delete log", "", type="danger") + return ('', 404) + + + def read_log_as_json(self, type, id): + filename = "./logs/%s_%s.log" % (type, id) + if os.path.isfile(filename) == False: + return + + import csv + array = [] + with open(filename, 'rb') as f: + reader = csv.reader(f) + for row in reader: + try: + array.append([int((datetime.datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S") - datetime.datetime(1970, 1, 1)).total_seconds()) * 1000, float(row[1])]) + except: + pass + return array + + def convert_chart_data_to_json(self, chart_data): + return {"name": chart_data["name"], "data": self.read_log_as_json(chart_data["data_type"], chart_data["data_id"])} + + @route('//', methods=["POST"]) + def get_logs_as_json(self, t, id): + + """ + Get Log as json + --- + tags: + - logs + parameters: + - in: path + name: id + schema: + type: string + required: true + description: id of the file + responses: + 200: + description: Log File Data + """ + + data = request.json + result = [] + if t == "s": + name = cbpi.cache.get("sensors").get(id).name + result.append({"name": name, "data": self.read_log_as_json("sensor", id)}) + + if t == "k": + kettle = cbpi.cache.get("kettle").get(id) + result = map(self.convert_chart_data_to_json, cbpi.brewing.get_controller(kettle.logic).get("class").chart(kettle)) + + if t == "f": + fermenter = cbpi.cache.get("fermenter").get(id) + result = map(self.convert_chart_data_to_json, cbpi.fermentation.get_controller(fermenter.logic).get("class").chart(fermenter)) + + return json.dumps(result) + + @route('/download/') + @cbpi.nocache + def download(self, file): + + """ + Download a log file by name + --- + tags: + - logs + parameters: + - in: path + name: file + schema: + type: string + required: true + description: filename + responses: + 200: + description: Log file downloaded + """ + if not self.check_filename(file): + return ('File Not Found', 404) + return send_from_directory('../../logs', file, as_attachment=True, attachment_filename=file) + + def check_filename(self, name): + import re + pattern = re.compile('^([A-Za-z0-9-_])+.log$') + + return True if pattern.match(name) else False + +@cbpi.addon.core.initializer() +def init(cbpi): + """ + Initializer for the message module + :param app: the flask app + :return: None + """ + LogView.register(cbpi._app, route_base='/api/logs') diff --git a/modules/logs/endpoints.py b/modules/logs/endpoints.py deleted file mode 100644 index 5f8b29e..0000000 --- a/modules/logs/endpoints.py +++ /dev/null @@ -1,109 +0,0 @@ -import datetime -import os -from flask import Blueprint, request, send_from_directory, json -from flask_classy import FlaskView, route -from modules import cbpi - - -class LogView(FlaskView): - - @route('/', methods=['GET']) - def get_all_logfiles(self): - result = [] - for filename in os.listdir("./logs"): - if filename.endswith(".log"): - result.append(filename) - return json.dumps(result) - - @route('/actions') - def actions(self): - filename = "./logs/action.log" - if os.path.isfile(filename) == False: - return - import csv - array = [] - with open(filename, 'rb') as f: - reader = csv.reader(f) - for row in reader: - try: - array.append([int((datetime.datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S") - datetime.datetime(1970, 1, 1)).total_seconds()) * 1000, row[1]]) - except: - pass - return json.dumps(array) - - @route('/', methods=["DELETE"]) - def clearlog(self, file): - """ - Overload delete method to shutdown sensor before delete - :param id: sensor id - :return: HTTP 204 - """ - if not self.check_filename(file): - return ('File Not Found', 404) - - filename = "./logs/%s" % file - if os.path.isfile(filename) == True: - os.remove(filename) - cbpi.notify("log deleted succesfully", "") - else: - cbpi.notify("Failed to delete log", "", type="danger") - return ('', 204) - - def read_log_as_json(self, type, id): - filename = "./logs/%s_%s.log" % (type, id) - if os.path.isfile(filename) == False: - return - - import csv - array = [] - with open(filename, 'rb') as f: - reader = csv.reader(f) - for row in reader: - try: - array.append([int((datetime.datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S") - datetime.datetime(1970, 1, 1)).total_seconds()) * 1000, float(row[1])]) - except: - pass - return array - - def convert_chart_data_to_json(self, chart_data): - return {"name": chart_data["name"], "data": self.read_log_as_json(chart_data["data_type"], chart_data["data_id"])} - - @route('//', methods=["POST"]) - def get_logs_as_json(self, t, id): - data = request.json - result = [] - if t == "s": - name = cbpi.cache.get("sensors").get(id).name - result.append({"name": name, "data": self.read_log_as_json("sensor", id)}) - - if t == "k": - kettle = cbpi.cache.get("kettle").get(id) - result = map(self.convert_chart_data_to_json, cbpi.get_controller(kettle.logic).get("class").chart(kettle)) - - if t == "f": - fermenter = cbpi.cache.get("fermenter").get(id) - result = map(self.convert_chart_data_to_json, cbpi.get_fermentation_controller(fermenter.logic).get("class").chart(fermenter)) - - return json.dumps(result) - - @route('/download/') - @cbpi.nocache - def download(self, file): - if not self.check_filename(file): - return ('File Not Found', 404) - return send_from_directory('../logs', file, as_attachment=True, attachment_filename=file) - - def check_filename(self, name): - import re - pattern = re.compile('^([A-Za-z0-9-_])+.log$') - - return True if pattern.match(name) else False - -@cbpi.initalizer() -def init(app): - """ - Initializer for the message module - :param app: the flask app - :return: None - """ - LogView.register(cbpi.app, route_base='/api/logs') diff --git a/modules/notification/__init__.py b/modules/notification/__init__.py index 2f440cb..1003ed5 100644 --- a/modules/notification/__init__.py +++ b/modules/notification/__init__.py @@ -1,30 +1,46 @@ import json from flask_classy import FlaskView, route -from modules import cbpi +from modules.core.core import cbpi + class NotificationView(FlaskView): @route('/', methods=['GET']) def getMessages(self): """ - Get all messages - :return: current messages + Get all Messages + --- + tags: + - notification + responses: + 200: + description: All messages """ - return json.dumps(cbpi.cache["messages"]) @route('/', methods=['DELETE']) def dismiss(self, id): """ - Delete message from cache by id - :param id: message id to be deleted - :return: empty response HTTP 204 + Delete Message + --- + tags: + - notification + parameters: + - in: path + name: id + schema: + type: integer + required: true + description: Numeric ID of the message + responses: + 200: + description: Message deleted """ for idx, m in enumerate(cbpi.cache.get("messages", [])): if (m.get("id") == id): cbpi.cache["messages"].pop(idx) return ('', 204) -@cbpi.event("MESSAGE", async=True) +#@cbpi.event("MESSAGE", async=True) def messageEvent(message, **kwargs): """ React on message event. add the message to the cache and push the message to the clients @@ -36,7 +52,7 @@ def messageEvent(message, **kwargs): cbpi.cache["messages"].append(message) cbpi.emit("NOTIFY", message) -@cbpi.initalizer(order=2) +@cbpi.addon.core.initializer(order=2) def init(cbpi): """ Initializer for the message module @@ -47,4 +63,4 @@ def init(cbpi): msg = {"id": len(cbpi.cache["messages"]), "type": "info", "headline": "Support CraftBeerPi with your donation", "message": "You will find the PayPay Donation button in the system menu" , "read": False} cbpi.cache["messages"].append(msg) - NotificationView.register(cbpi.app, route_base='/api/notification') + NotificationView.register(cbpi._app, route_base='/api/notification') diff --git a/modules/plugin/__init__.py b/modules/plugin/__init__.py new file mode 100755 index 0000000..f4ac1bf --- /dev/null +++ b/modules/plugin/__init__.py @@ -0,0 +1,138 @@ +import sys +from flask import request, send_from_directory, json +from importlib import import_module +from modules.core.core import cbpi +from git import Repo +import os +import requests +import yaml +import shutil +from flask_classy import FlaskView, route + +modules = {} + +class PluginView(FlaskView): + + + def merge(self, source, destination): + """ + Helper method to merge two dicts + :param source: + :param destination: + :return: + """ + for key, value in source.items(): + if isinstance(value, dict): + # get node or create one + node = destination.setdefault(key, {}) + self.merge(value, node) + else: + destination[key] = value + + return destination + + + @route('/', methods=['GET']) + def get(self): + """ + Get Plugin List + --- + tags: + - plugin + responses: + 200: + description: List of all plugins + """ + response = requests.get("https://raw.githubusercontent.com/Manuel83/craftbeerpi-plugins/master/plugins.yaml") + self.api.cache["plugins"] = self.merge(yaml.load(response.text), self.api.cache["plugins"]) + for key, value in cbpi.cache["plugins"].iteritems(): + value["installed"] = os.path.isdir("./plugins/%s/" % (key)) + return json.dumps(cbpi.cache["plugins"]) + + + @route('/', methods=['DELETE']) + def delete(self,name): + """ + Delete Plugin + --- + tags: + - plugin + parameters: + - in: path + name: name + schema: + type: string + required: true + description: Plugin name + responses: + 200: + description: Plugin deleted + """ + if os.path.isdir("./plugins/"+name) is False: + return ('Dir not found', 500) + shutil.rmtree("./plugins/"+name) + cbpi.notify("Plugin deleted", "Plugin %s deleted successfully" % name) + return ('', 204) + + @route('//download', methods=['POST']) + def download(self, name): + """ + Download Plugin + --- + tags: + - plugin + parameters: + - in: path + name: name + schema: + type: string + required: true + description: Plugin name + responses: + 200: + description: Plugin downloaded + """ + plugin = self.api.cache["plugins"].get(name) + plugin["loading"] = True + if plugin is None: + return ('', 404) + try: + Repo.clone_from(plugin.get("repo_url"), "./modules/plugins/%s/" % (name)) + self.api.notify("Download successful", "Plugin %s downloaded successfully" % name) + finally: + plugin["loading"] = False + return ('', 204) + + @route('//update', methods=['POST']) + def update(self, name): + """ + Pull Plugin Update + --- + tags: + - plugin + parameters: + - in: path + name: name + schema: + type: string + required: true + description: Plugin name + responses: + 200: + description: Plugin updated + """ + repo = Repo("./modules/plugins/%s/" % (name)) + o = repo.remotes.origin + info = o.pull() + self.api.notify("Plugin Updated", "Plugin %s updated successfully. Please restart the system" % name) + return ('', 204) + + + + + +@cbpi.addon.core.initializer() +def init(cbpi): + cbpi.cache["plugins"] = {} + PluginView.api = cbpi + PluginView.register(cbpi._app, route_base='/api/plugin') \ No newline at end of file diff --git a/modules/recipe_import/beerxml.py b/modules/recipe_import/beerxml.py index e9f17e0..79101a8 100644 --- a/modules/recipe_import/beerxml.py +++ b/modules/recipe_import/beerxml.py @@ -2,18 +2,28 @@ from flask import json, request from flask_classy import FlaskView, route from git import Repo, Git import sqlite3 -from modules.app_config import cbpi +from modules.core.core import cbpi from werkzeug.utils import secure_filename import pprint import time import os -from modules.steps import Step,StepView +from modules.step import Step,StepView import xml.etree.ElementTree class BeerXMLImport(FlaskView): BEER_XML_FILE = "./upload/beer.xml" @route('/', methods=['GET']) def get(self): + + """ + Get BeerXML + --- + tags: + - beerxml + responses: + 200: + description: BeerXML file stored in CraftBeerPI + """ if not os.path.exists(self.BEER_XML_FILE): self.api.notify(headline="File Not Found", message="Please upload a Beer.xml File", type="danger") @@ -31,6 +41,16 @@ class BeerXMLImport(FlaskView): @route('/upload', methods=['POST']) def upload_file(self): + """ + Upload BeerXML File + --- + tags: + - beerxml + + responses: + 200: + description: BeerXML File Uploaded + """ try: if request.method == 'POST': file = request.files['file'] @@ -46,6 +66,23 @@ class BeerXMLImport(FlaskView): @route('/', methods=['POST']) def load(self, id): + """ + Load Recipe from BeerXML + --- + tags: + - beerxml + parameters: + - in: path + name: id + schema: + type: integer + required: true + description: Recipe ID from BeerXML + + responses: + 200: + description: Recipe loaed + """ steps = self.getSteps(id) name = self.getRecipeName(id) @@ -103,8 +140,8 @@ class BeerXMLImport(FlaskView): return steps -@cbpi.initalizer() +@cbpi.addon.core.initializer() def init(cbpi): BeerXMLImport.api = cbpi - BeerXMLImport.register(cbpi.app, route_base='/api/beerxml') + BeerXMLImport.register(cbpi._app, route_base='/api/beerxml') diff --git a/modules/recipe_import/kbh.py b/modules/recipe_import/kbh.py index d6c23cb..8cdfd4b 100644 --- a/modules/recipe_import/kbh.py +++ b/modules/recipe_import/kbh.py @@ -2,18 +2,28 @@ from flask import json, request from flask_classy import FlaskView, route from git import Repo, Git import sqlite3 -from modules.app_config import cbpi +from modules.core.core import cbpi from werkzeug.utils import secure_filename import pprint import time import os -from modules.steps import Step, StepView +from modules.step import Step, StepView class KBH(FlaskView): @route('/', methods=['GET']) def get(self): + + """ + Get all recipes from uploaded kleinerbrauhelfer database + --- + tags: + - kleinerbrauhelfer + responses: + 200: + description: Recipes from kleinerbrauhelfer database + """ conn = None try: if not os.path.exists(self.api.app.config['UPLOAD_FOLDER'] + '/kbh.db'): @@ -41,6 +51,16 @@ class KBH(FlaskView): @route('/upload', methods=['POST']) def upload_file(self): + """ + Upload KleinerBrauhelfer Database File + --- + tags: + - kleinerbrauhelfer + + responses: + 200: + description: File uploaed + """ try: if request.method == 'POST': file = request.files['file'] @@ -57,6 +77,23 @@ class KBH(FlaskView): @route('/', methods=['POST']) def load(self, id): + """ + Load Recipe from Kleinerbrauhelfer Database + --- + tags: + - kleinerbrauhelfer + parameters: + - in: path + name: id + schema: + type: integer + required: true + description: Numeric ID of recipe + + responses: + 200: + description: Recipe loaded + """ mashstep_type = cbpi.get_config_parameter("step_mash", "MashStep") mashinstep_type = cbpi.get_config_parameter("step_mashin", "MashInStep") chilstep_type = cbpi.get_config_parameter("step_chil", "ChilStep") @@ -102,8 +139,8 @@ class KBH(FlaskView): -@cbpi.initalizer() +@cbpi.addon.core.initializer() def init(cbpi): KBH.api = cbpi - KBH.register(cbpi.app, route_base='/api/kbh') + KBH.register(cbpi._app, route_base='/api/kbh') diff --git a/modules/recipe_import/restapi.py b/modules/recipe_import/restapi.py index 1e9a84c..b1e71b5 100644 --- a/modules/recipe_import/restapi.py +++ b/modules/recipe_import/restapi.py @@ -2,12 +2,12 @@ from flask import json, request from flask_classy import FlaskView, route from git import Repo, Git import sqlite3 -from modules.app_config import cbpi +from modules.core.core import cbpi from werkzeug.utils import secure_filename import pprint import time import os -from modules.steps import Step,StepView +from modules.step import Step,StepView import xml.etree.ElementTree @@ -56,7 +56,7 @@ class RESTImport(FlaskView): return ('', 204) -@cbpi.initalizer() +@cbpi.addon.core.initializer() def init(cbpi): RESTImport.api = cbpi - RESTImport.register(cbpi.app, route_base='/api/recipe/import/v1') + RESTImport.register(cbpi._app, route_base='/api/recipe/import/v1') diff --git a/modules/sensor/__init__.py b/modules/sensor/__init__.py new file mode 100644 index 0000000..1378ba0 --- /dev/null +++ b/modules/sensor/__init__.py @@ -0,0 +1,67 @@ +import time +from flask_classy import route +from modules.core.core import cbpi +from modules.core.db import DBModel +from modules.core.baseview import BaseView +from modules.database.dbmodel import Sensor + + +class SensorView(BaseView): + model = Sensor + cache_key = "sensors" + + @route('/action/', methods=["POST"]) + def action(self, id, method): + """ + Sensor Action + --- + tags: + - sensor + parameters: + - in: path + name: id + schema: + type: integer + required: true + description: Numeric ID of the sensor + - in: path + name: method + schema: + type: string + required: true + description: action method name + responses: + 200: + description: Sensor Action called + """ + cbpi.cache.get("sensors").get(id).instance.__getattribute__(method)() + return ('', 204) + + def _post_post_callback(self, m): + + cbpi.sensor.init_one(m.id) + + def _post_put_callback(self, m): + cbpi.sensor.stop_one(m.id) + cbpi.sensor.init_one(m.id) + + def _pre_delete_callback(self, m): + cbpi.sensor.stop_one(m.id) + +@cbpi.addon.core.initializer(order=1000) +def init(cbpi): + + SensorView.register(cbpi._app, route_base='/api/sensor') + SensorView.init_cache() + + +#@cbpi.backgroundtask(key="read_passiv_sensor", interval=5) +def read_passive_sensor(api): + """ + background process that reads all passive sensors in interval of 1 second + :return: None + + """ + for key, value in cbpi.cache.get("sensors").iteritems(): + if value.mode == "P": + value.instance.read() diff --git a/modules/sensors/__init__.py b/modules/sensors/__init__.py deleted file mode 100755 index dfbe744..0000000 --- a/modules/sensors/__init__.py +++ /dev/null @@ -1,48 +0,0 @@ -import time -from flask_classy import route -from modules import DBModel, cbpi -from modules.core.baseview import BaseView - -class Sensor(DBModel): - __fields__ = ["name","type", "config", "hide"] - __table_name__ = "sensor" - __json_fields__ = ["config"] - -class SensorView(BaseView): - model = Sensor - cache_key = "sensors" - - @route('/action/', methods=["POST"]) - def action(self, id, method): - - cbpi.cache.get("sensors").get(id).instance.__getattribute__(method)() - return ('', 204) - - def _post_post_callback(self, m): - cbpi.init_sensor(m.id) - - def _post_put_callback(self, m): - cbpi.stop_sensor(m.id) - cbpi.init_sensor(m.id) - - def _pre_delete_callback(self, m): - cbpi.stop_sensor(m.id) - -@cbpi.initalizer(order=1000) -def init(cbpi): - - SensorView.register(cbpi.app, route_base='/api/sensor') - SensorView.init_cache() - cbpi.init_sensors() - - -@cbpi.backgroundtask(key="read_passiv_sensor", interval=5) -def read_passive_sensor(api): - """ - background process that reads all passive sensors in interval of 1 second - :return: None - - """ - for key, value in cbpi.cache.get("sensors").iteritems(): - if value.mode == "P": - value.instance.read() diff --git a/modules/stats/__init__.py b/modules/stats/__init__.py old mode 100644 new mode 100755 index 993cf26..6142ae6 --- a/modules/stats/__init__.py +++ b/modules/stats/__init__.py @@ -1,36 +1,35 @@ -from modules import cbpi - - -def getserial(): - cpuserial = "0000000000000000" - try: - f = open('/proc/cpuinfo','r') - for line in f: - if line[0:6]=='Serial': - cpuserial = line[10:26] - f.close() - except: - cpuserial = "0000000000000000" - return cpuserial - - -@cbpi.initalizer(order=9999) -def sendStats(cbpi): - - try: - serial = getserial() - - info = { - "id": serial, - "version": "3.0", - "kettle": len(cbpi.cache.get("kettle")), - "hardware": len(cbpi.cache.get("actors")), - "thermometer": "CBP3.0", - "hardware_control": "CBP3.0" - } - - import requests - r = requests.post('http://statistics.craftbeerpi.com', json=info) - - except Exception as e: - pass \ No newline at end of file +from modules import cbpi + + +def getserial(): + cpuserial = "0000000000000000" + try: + f = open('/proc/cpuinfo','r') + for line in f: + if line[0:6]=='Serial': + cpuserial = line[10:26] + f.close() + except: + cpuserial = "0000000000000000" + return cpuserial + + +@cbpi.initalizer(order=9999) +def sendStats(cbpi): + + try: + serial = getserial() + info = { + "id": serial, + "version": "3.1", + "kettle": len(cbpi.cache.get("kettle")), + "hardware": len(cbpi.cache.get("actors")), + "thermometer": "CBP3.0", + "hardware_control": "CBP3.0" + } + + import requests + #r = requests.post('http://statistics.craftbeerpi.com', json=info) + + except Exception as e: + pass diff --git a/modules/steps/__init__.py b/modules/step/__init__.py similarity index 60% rename from modules/steps/__init__.py rename to modules/step/__init__.py index 05d65fc..880cd41 100755 --- a/modules/steps/__init__.py +++ b/modules/step/__init__.py @@ -1,239 +1,241 @@ -import time -from flask import json, request -from flask_classy import route - -from modules import DBModel, cbpi, get_db -from modules.core.baseview import BaseView - - -class Step(DBModel): - __fields__ = ["name","type", "stepstate", "state", "start", "end", "order", "config"] - __table_name__ = "step" - __json_fields__ = ["config", "stepstate"] - __order_by__ = "order" - __as_array__ = True - - @classmethod - def get_max_order(cls): - cur = get_db().cursor() - cur.execute("SELECT max(step.'order') as 'order' FROM %s" % cls.__table_name__) - r = cur.fetchone() - return r.get("order") - - @classmethod - def get_by_state(cls, state, order=True): - cur = get_db().cursor() - cur.execute("SELECT * FROM %s WHERE state = ? ORDER BY %s.'order'" % (cls.__table_name__,cls.__table_name__,), state) - r = cur.fetchone() - if r is not None: - return cls(r) - else: - return None - - @classmethod - def delete_all(cls): - cur = get_db().cursor() - cur.execute("DELETE FROM %s" % cls.__table_name__) - get_db().commit() - - @classmethod - def reset_all_steps(cls): - cur = get_db().cursor() - cur.execute("UPDATE %s SET state = 'I', stepstate = NULL , start = NULL, end = NULL " % cls.__table_name__) - get_db().commit() - - @classmethod - def update_state(cls, id, state): - cur = get_db().cursor() - cur.execute("UPDATE %s SET state = ? WHERE id =?" % cls.__table_name__, (state, id)) - get_db().commit() - - @classmethod - def update_step_state(cls, id, state): - cur = get_db().cursor() - cur.execute("UPDATE %s SET stepstate = ? WHERE id =?" % cls.__table_name__, (json.dumps(state),id)) - get_db().commit() - - @classmethod - def sort(cls, new_order): - cur = get_db().cursor() - - for e in new_order: - - cur.execute("UPDATE %s SET '%s' = ? WHERE id = ?" % (cls.__table_name__, "order"), (e[1], e[0])) - get_db().commit() - - -class StepView(BaseView): - model = Step - def _pre_post_callback(self, data): - order = self.model.get_max_order() - data["order"] = 1 if order is None else order + 1 - data["state"] = "I" - - @route('/sort', methods=["POST"]) - def sort_steps(self): - Step.sort(request.json) - cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all()) - return ('', 204) - - @route('/', methods=["DELETE"]) - def deleteAll(self): - self.model.delete_all() - cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all()) - return ('', 204) - - @route('/action/', methods=["POST"]) - def action(self, method): - cbpi.cache["active_step"].__getattribute__(method)() - return ('', 204) - - @route('/reset', methods=["POST"]) - def reset(self): - self.model.reset_all_steps() - self.stop_step() - cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all()) - return ('', 204) - - def stop_step(self): - ''' - stop active step - :return: - ''' - step = cbpi.cache.get("active_step") - cbpi.cache["active_step"] = None - - if step is not None: - step.finish() - - @route('/reset/current', methods=['POST']) - def resetCurrentStep(self): - ''' - Reset current step - :return: - ''' - step = cbpi.cache.get("active_step") - - if step is not None: - step.reset() - if step.is_dirty(): - - state = {} - for field in step.managed_fields: - state[field] = step.__getattribute__(field) - Step.update_step_state(step.id, state) - step.reset_dirty() - cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all()) - return ('', 204) - - def init_step(self, step): - cbpi.log_action("Start Step %s" % step.name) - type_cfg = cbpi.cache.get("step_types").get(step.type) - if type_cfg is None: - # if type not found - return - - # copy config to stepstate - # init step - cfg = step.config.copy() - cfg.update(dict(name=step.name, api=cbpi, id=step.id, timer_end=None, managed_fields=get_manged_fields_as_array(type_cfg))) - instance = type_cfg.get("class")(**cfg) - instance.init() - # set step instance to ache - cbpi.cache["active_step"] = instance - - @route('/next', methods=['POST']) - @route('/start', methods=['POST']) - def start(self): - active = Step.get_by_state("A") - inactive = Step.get_by_state('I') - - if (active is not None): - active.state = 'D' - active.end = int(time.time()) - self.stop_step() - Step.update(**active.__dict__) - - if (inactive is not None): - self.init_step(inactive) - inactive.state = 'A' - inactive.stepstate = inactive.config - inactive.start = int(time.time()) - Step.update(**inactive.__dict__) - else: - cbpi.log_action("Brewing Finished") - cbpi.notify("Brewing Finished", "You are done!", timeout=None) - - cbpi.emit("UPDATE_ALL_STEPS", Step.get_all()) - return ('', 204) - -def get_manged_fields_as_array(type_cfg): - - result = [] - for f in type_cfg.get("properties"): - - result.append(f.get("name")) - - return result - -@cbpi.try_catch(None) -def init_after_startup(): - ''' - Restart after startup. Check is a step is in state A and reinitialize - :return: None - ''' - - step = Step.get_by_state('A') - # We have an active step - if step is not None: - - # get the type - - - type_cfg = cbpi.cache.get("step_types").get(step.type) - - if type_cfg is None: - # step type not found. cant restart step - return - - cfg = step.stepstate.copy() - cfg.update(dict(api=cbpi, id=step.id, managed_fields=get_manged_fields_as_array(type_cfg))) - instance = type_cfg.get("class")(**cfg) - instance.init() - cbpi.cache["active_step"] = instance - -@cbpi.initalizer(order=2000) -def init(cbpi): - - StepView.register(cbpi.app, route_base='/api/step') - - def get_all(): - with cbpi.app.app_context(): - return Step.get_all() - - with cbpi.app.app_context(): - init_after_startup() - cbpi.add_cache_callback("steps", get_all) - -@cbpi.backgroundtask(key="step_task", interval=0.1) -def execute_step(api): - ''' - Background job which executes the step - :return: - ''' - with cbpi.app.app_context(): - step = cbpi.cache.get("active_step") - if step is not None: - step.execute() - if step.is_dirty(): - state = {} - for field in step.managed_fields: - state[field] = step.__getattribute__(field) - Step.update_step_state(step.id, state) - step.reset_dirty() - cbpi.emit("UPDATE_ALL_STEPS", Step.get_all()) - - if step.n is True: - - StepView().start() - cbpi.emit("UPDATE_ALL_STEPS", Step.get_all()) +import time +from flask import json, request +from flask_classy import route + +from modules.core.db import DBModel +from modules.core.baseview import BaseView +from modules.core.core import cbpi +from modules.database.dbmodel import Step + + +class StepView(BaseView): + model = Step + def _pre_post_callback(self, data): + order = self.model.get_max_order() + data["order"] = 1 if order is None else order + 1 + data["state"] = "I" + + @route('/sort', methods=["POST"]) + def sort_steps(self): + """ + Sort all steps + --- + tags: + - steps + responses: + 204: + description: Steps sorted. Update delivered via web socket + """ + Step.sort(request.json) + cbpi.ws_emit("UPDATE_ALL_STEPS", self.model.get_all()) + return ('', 204) + + @route('/', methods=["DELETE"]) + def deleteAll(self): + """ + Delete all Steps + --- + tags: + - steps + responses: + 204: + description: All steps deleted + """ + self.model.delete_all() + self.api.emit("ALL_BREWING_STEPS_DELETED") + cbpi.ws_emit("UPDATE_ALL_STEPS", self.model.get_all()) + return ('', 204) + + @route('/action/', methods=["POST"]) + def action(self, method): + """ + Call Step Action + --- + tags: + - steps + responses: + 204: + description: Step action called + """ + self.api.emit("BREWING_STEP_ACTION_INVOKED", method=method) + cbpi.cache["active_step"].__getattribute__(method)() + return ('', 204) + + @route('/reset', methods=["POST"]) + def reset(self): + + """ + Reset All Steps + --- + tags: + - steps + responses: + 200: + description: Steps reseted + """ + self.model.reset_all_steps() + self.stop_step() + self.api.emit("ALL_BREWING_STEPS_RESET") + cbpi.ws_emit("UPDATE_ALL_STEPS", self.model.get_all()) + return ('', 204) + + def stop_step(self): + ''' + stop active step + :return: + ''' + step = cbpi.cache.get("active_step") + cbpi.cache["active_step"] = None + self.api.emit("BREWING_STEPS_STOP") + if step is not None: + step.finish() + + @route('/reset/current', methods=['POST']) + def resetCurrentStep(self): + """ + Reset current Steps + --- + tags: + - steps + responses: + 200: + description: Current Steps reseted + """ + step = cbpi.cache.get("active_step") + + if step is not None: + step.reset() + if step.is_dirty(): + + state = {} + for field in step.managed_fields: + state[field] = step.__getattribute__(field) + Step.update_step_state(step.id, state) + step.reset_dirty() + self.api.emit("BREWING_STEPS_RESET_CURRENT") + cbpi.ws_emit("UPDATE_ALL_STEPS", self.model.get_all()) + return ('', 204) + + def init_step(self, step): + + cbpi.brewing.log_action("Start Step %s" % step.name) + type_cfg = cbpi.cache.get("step_types").get(step.type) + if type_cfg is None: + # if type not found + return + + # copy config to stepstate + # init step + cfg = step.config.copy() + cfg.update(dict(name=step.name, api=cbpi, id=step.id, timer_end=None, managed_fields=get_manged_fields_as_array(type_cfg))) + instance = type_cfg.get("class")(**cfg) + instance.init() + # set step instance to ache + cbpi.cache["active_step"] = instance + + @route('/next', methods=['POST']) + @route('/start', methods=['POST']) + def start(self): + + """ + Next Step + --- + tags: + - steps + responses: + 200: + description: Next Step + """ + active = Step.get_by_state("A") + inactive = Step.get_by_state('I') + + if (active is not None): + active.state = 'D' + active.end = int(time.time()) + self.stop_step() + Step.update(**active.__dict__) + self.api.emit("BREWING_STEP_DONE") + + if (inactive is not None): + self.init_step(inactive) + inactive.state = 'A' + inactive.stepstate = inactive.config + inactive.start = int(time.time()) + Step.update(**inactive.__dict__) + self.api.emit("BREWING_STEP_STARTED") + else: + cbpi.brewing.log_action("Brewing Finished") + self.api.emit("BREWING_FINISHED") + cbpi.notify("Brewing Finished", "You are done!", timeout=None) + + + cbpi.ws_emit("UPDATE_ALL_STEPS", Step.get_all()) + + return ('', 204) + +def get_manged_fields_as_array(type_cfg): + + result = [] + for f in type_cfg.get("properties"): + + result.append(f.get("name")) + + return result + + +def init_after_startup(): + ''' + Restart after startup. Check is a step is in state A and reinitialize + :return: None + ''' + step = Step.get_by_state('A') + # We have an active step + if step is not None: + # get the type + type_cfg = cbpi.cache.get("step_types").get(step.type) + + if type_cfg is None: + # step type not found. cant restart step + return + cfg = step.stepstate.copy() + cfg.update(dict(api=cbpi, id=step.id, managed_fields=get_manged_fields_as_array(type_cfg))) + instance = type_cfg.get("class")(**cfg) + instance.init() + cbpi.cache["active_step"] = instance + +@cbpi.addon.core.initializer(order=2000) +def init(cbpi): + + StepView.register(cbpi._app, route_base='/api/step') + + def get_all(): + with cbpi._app.app_context(): + return Step.get_all() + + with cbpi._app.app_context(): + init_after_startup() + + cbpi.add_cache_callback("steps", get_all) + +@cbpi.addon.core.backgroundjob(key="step_task", interval=0.1) +def execute_step(api): + ''' + Background job which executes the step + :return: + ''' + with cbpi._app.app_context(): + step = cbpi.cache.get("active_step") + if step is not None: + step.execute() + if step.is_dirty(): + state = {} + for field in step.managed_fields: + state[field] = step.__getattribute__(field) + Step.update_step_state(step.id, state) + step.reset_dirty() + cbpi.ws_emit("UPDATE_ALL_STEPS", Step.get_all()) + + if step.n is True: + + StepView().start() + cbpi.ws_emit("UPDATE_ALL_STEPS", Step.get_all()) diff --git a/modules/system/__init__.py b/modules/system/__init__.py index b89697f..38ee784 100755 --- a/modules/system/__init__.py +++ b/modules/system/__init__.py @@ -1 +1,152 @@ -import endpoints \ No newline at end of file +import yaml +from flask import json, url_for, Response +from flask_classy import FlaskView, route +from git import Repo, Git +from modules.core.core import cbpi +import pprint +import time + + +class SystemView(FlaskView): + def doShutdown(self): + time.sleep(5) + from subprocess import call + call("halt") + + @route('/shutdown', methods=['POST']) + def shutdown(self): + """ + System Shutdown + --- + tags: + - system + responses: + 200: + description: Shutdown triggered + """ + self.doShutdown() + + return ('', 204) + + def doReboot(self): + time.sleep(5) + from subprocess import call + call("reboot") + + @route('/reboot', methods=['POST']) + def reboot(self): + """ + System Reboot + --- + tags: + - system + responses: + 200: + description: Reboot triggered + """ + self.doReboot() + return ('', 204) + + @route('/tags/', methods=['GET']) + def checkout_tag(self,name): + repo = Repo('./') + repo.git.reset('--hard') + o = repo.remotes.origin + o.fetch() + g = Git('./') + g.checkout(name) + cbpi.notify("Checkout successful", "Please restart the system") + return ('', 204) + + @route('/git/status', methods=['GET']) + def git_status(self): + """ + Check for GIT status + --- + tags: + - system + responses: + 200: + description: Git Status + """ + repo = Repo('./') + o = repo.remotes.origin + o.fetch() + # Tags + tags = [] + for t in repo.tags: + tags.append({"name": t.name, "commit": str(t.commit), "date": t.commit.committed_date, + "committer": t.commit.committer.name, "message": t.commit.message}) + try: + branch_name = repo.active_branch.name + # test1 + except: + branch_name = None + + changes = [] + commits_behind = repo.iter_commits('master..origin/master') + + for c in list(commits_behind): + changes.append({"committer": c.committer.name, "message": c.message}) + + return json.dumps({"tags": tags, "headcommit": str(repo.head.commit), "branchname": branch_name, + "master": {"changes": changes}}) + + @route('/check_update', methods=['GET']) + def check_update(self): + """ + Check for GIT update + --- + tags: + - system + responses: + 200: + description: Git Changes + """ + repo = Repo('./') + o = repo.remotes.origin + o.fetch() + changes = [] + commits_behind = repo.iter_commits('master..origin/master') + + for c in list(commits_behind): + changes.append({"committer": c.committer.name, "message": c.message}) + + return json.dumps(changes) + + @route('/git/pull', methods=['POST']) + def update(self): + """ + System Update + --- + tags: + - system + responses: + 200: + description: Git Pull Triggered + """ + repo = Repo('./') + o = repo.remotes.origin + info = o.pull() + cbpi.notify("Pull successful", "The lasted updated was downloaded. Please restart the system") + return ('', 204) + + @route('/dump', methods=['GET']) + def dump(self): + """ + Dump Cache + --- + tags: + - system + responses: + 200: + description: CraftBeerPi System Cache + """ + return json.dumps(cbpi.cache) + + +@cbpi.addon.core.initializer() +def init(cbpi): + + SystemView.api = cbpi + SystemView.register(cbpi._app, route_base='/api/system') diff --git a/modules/system/endpoints.py b/modules/system/endpoints.py deleted file mode 100755 index bfb23b3..0000000 --- a/modules/system/endpoints.py +++ /dev/null @@ -1,145 +0,0 @@ -import yaml -from flask import json, url_for, Response -from flask_classy import FlaskView, route -from git import Repo, Git - -from modules.app_config import cbpi - -import pprint -import time - - -class SystemView(FlaskView): - def doShutdown(self): - time.sleep(5) - from subprocess import call - call("halt") - - @route('/shutdown', methods=['POST']) - def shutdown(self): - """ - Shutdown hook - :return: HTTP 204 - """ - self.doShutdown() - - return ('', 204) - - def doReboot(self): - time.sleep(5) - from subprocess import call - call("reboot") - - @route('/reboot', methods=['POST']) - def reboot(self): - """ - Reboot hook - :return: HTTP 204 - """ - self.doReboot() - - return ('', 204) - - @route('/tags/', methods=['GET']) - def checkout_tag(self,name): - repo = Repo('./') - repo.git.reset('--hard') - o = repo.remotes.origin - o.fetch() - g = Git('./') - g.checkout(name) - cbpi.notify("Checkout successful", "Please restart the system") - return ('', 204) - - @route('/git/status', methods=['GET']) - def git_status(self): - repo = Repo('./') - o = repo.remotes.origin - o.fetch() - # Tags - tags = [] - for t in repo.tags: - tags.append({"name": t.name, "commit": str(t.commit), "date": t.commit.committed_date, - "committer": t.commit.committer.name, "message": t.commit.message}) - try: - branch_name = repo.active_branch.name - # test1 - except: - branch_name = None - - changes = [] - commits_behind = repo.iter_commits('master..origin/master') - - for c in list(commits_behind): - changes.append({"committer": c.committer.name, "message": c.message}) - - return json.dumps({"tags": tags, "headcommit": str(repo.head.commit), "branchname": branch_name, - "master": {"changes": changes}}) - - @route('/check_update', methods=['GET']) - def check_update(self): - - repo = Repo('./') - o = repo.remotes.origin - o.fetch() - changes = [] - commits_behind = repo.iter_commits('master..origin/master') - - for c in list(commits_behind): - changes.append({"committer": c.committer.name, "message": c.message}) - - return json.dumps(changes) - - @route('/git/pull', methods=['POST']) - def update(self): - repo = Repo('./') - o = repo.remotes.origin - info = o.pull() - cbpi.notify("Pull successful", "The lasted updated was downloaded. Please restart the system") - return ('', 204) - - @route('/dump', methods=['GET']) - def dump(self): - return json.dumps(cbpi.cache) - - @route('/endpoints', methods=['GET']) - def endpoints(self): - import urllib - output = [] - vf = self.api.app.view_functions - - for f in self.api.app.view_functions: - print f - endpoints = {} - re = { - "swagger": "2.0", - "host": "", - "info": { - "description":"", - "version": "", - "title": "CraftBeerPi" - }, - "schemes": ["http"], - "paths": endpoints} - for rule in self.api.app.url_map.iter_rules(): - r = rule - endpoints[rule.rule] = {} - if "HEAD" in r.methods: r.methods.remove("HEAD") - if "OPTIONS" in r.methods: r.methods.remove("OPTIONS") - for m in rule.methods: - endpoints[rule.rule][m] = dict(summary="", description="", consumes=["application/json"],produces=["application/json"]) - - with open("config/version.yaml", 'r') as stream: - - y = yaml.load(stream) - pprint.pprint(y) - pprint.pprint(re) - return Response(yaml.dump(re), mimetype='text/yaml') - - - -@cbpi.initalizer() -def init(cbpi): - - SystemView.api = cbpi - SystemView.register(cbpi.app, route_base='/api/system') diff --git a/modules/ui/__init__.py b/modules/ui/__init__.py index b89697f..ca5a866 100755 --- a/modules/ui/__init__.py +++ b/modules/ui/__init__.py @@ -1 +1,23 @@ -import endpoints \ No newline at end of file +from flask import Blueprint + +from modules.core.core import cbpi + +react = Blueprint('ui', __name__, template_folder='templates', static_folder='static') + +@cbpi.addon.core.initializer(order=10) +def init(cbpi): + cbpi._app.register_blueprint(react, url_prefix='/ui') + + +@react.route('/', methods=["GET"]) +def index(): + return react.send_static_file("index.html") + + + + + + + + + diff --git a/modules/ui/endpoints.py b/modules/ui/endpoints.py deleted file mode 100644 index e891201..0000000 --- a/modules/ui/endpoints.py +++ /dev/null @@ -1,25 +0,0 @@ -from flask import Blueprint - -from modules import cbpi - -react = Blueprint('react', __name__, template_folder='templates', static_folder='static') - -@cbpi.initalizer(order=10) -def init(cbpi): - cbpi.app.register_blueprint(react, url_prefix='/ui') - - - - -@react.route('/', methods=["GET"]) -def index(): - return react.send_static_file("index.html") - - - - - - - - - diff --git a/modules/ui/static/bundle.js b/modules/ui/static/bundle.js index 5870f05..3f8533f 100644 --- a/modules/ui/static/bundle.js +++ b/modules/ui/static/bundle.js @@ -1,27 +1,28 @@ -!function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var o=n(1),i=r(o),a=n(18),s=r(a),l=n(383),u=r(l);n(935),s.default.render(i.default.createElement(u.default,null),document.getElementById("root"))},function(e,t,n){"use strict";e.exports=n(71)},function(e,t){function n(){throw new Error("setTimeout has not been defined")}function r(){throw new Error("clearTimeout has not been defined")}function o(e){if(c===setTimeout)return setTimeout(e,0);if((c===n||!c)&&setTimeout)return c=setTimeout,setTimeout(e,0);try{return c(e,0)}catch(t){try{return c.call(null,e,0)}catch(t){return c.call(this,e,0)}}}function i(e){if(d===clearTimeout)return clearTimeout(e);if((d===r||!d)&&clearTimeout)return d=clearTimeout,clearTimeout(e);try{return d(e)}catch(t){try{return d.call(null,e)}catch(t){return d.call(this,e)}}}function a(){m&&p&&(m=!1,p.length?h=p.concat(h):v=-1,h.length&&s())}function s(){if(!m){var e=o(a);m=!0;for(var t=h.length;t;){for(p=h,h=[];++v1)for(var n=1;n=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}},function(e,t,n){var r,o;/*! +!function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var o=n(1),i=r(o),a=n(18),s=r(a),l=n(394),u=r(l);n(981),s.default.render(i.default.createElement(u.default,null),document.getElementById("root"))},function(e,t,n){"use strict";e.exports=n(75)},function(e,t){function n(){throw new Error("setTimeout has not been defined")}function r(){throw new Error("clearTimeout has not been defined")}function o(e){if(c===setTimeout)return setTimeout(e,0);if((c===n||!c)&&setTimeout)return c=setTimeout,setTimeout(e,0);try{return c(e,0)}catch(t){try{return c.call(null,e,0)}catch(t){return c.call(this,e,0)}}}function i(e){if(d===clearTimeout)return clearTimeout(e);if((d===r||!d)&&clearTimeout)return d=clearTimeout,clearTimeout(e);try{return d(e)}catch(t){try{return d.call(null,e)}catch(t){return d.call(this,e)}}}function a(){m&&p&&(m=!1,p.length?h=p.concat(h):v=-1,h.length&&s())}function s(){if(!m){var e=o(a);m=!0;for(var t=h.length;t;){for(p=h,h=[];++v1)for(var n=1;n=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}},function(e,t,n){var r,o;/*! Copyright (c) 2016 Jed Watson. Licensed under the MIT License (MIT), see http://jedwatson.github.io/classnames */ -!function(){"use strict";function n(){for(var e=[],t=0;t1?t-1:0),r=1;r1?t-1:0),r=1;r2?r-2:0),i=2;i2?r-2:0);for(var o=2;o1?t-1:0),r=1;r1?t-1:0),r=1;r2?n-2:0),o=2;o2?r-2:0);for(var o=2;o0&&void 0!==arguments[0]?arguments[0]:l(),t=arguments[1];switch(t.type){case"UPDATE_CONFIG":return i({},e,{list:i({},e.list,o({},t.payload.name,t.payload))});case"LOAD_STATE":return i({},e,{list:t.payload.config});default:return e}});t.default=u},function(e,t){"use strict";var n=!("undefined"==typeof window||!window.document||!window.document.createElement),r={canUseDOM:n,canUseWorkers:"undefined"!=typeof Worker,canUseEventListeners:n&&!(!window.addEventListener&&!window.attachEvent),canUseViewport:n&&!!window.screen,isInWorker:!n};e.exports=r},function(e,t){"use strict";function n(){for(var e=arguments.length,t=Array(e),n=0;n=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}Object.defineProperty(t,"__esModule",{value:!0}),t.CheckboxField=t.SelectField=t.SliderField=t.NumberField=t.TextField=void 0;var u=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:void 0;if(void 0===e){var t=this.props.form;e={};for(var n in t)switch(t[n].type){case"checkbox":e[t[n].name]=!1;break;default:e[t[n].name]=""}}else this.props.configfield&&!e.hasOwnProperty("config")&&(e.config={});this.setState({showModal:!0,data:e})}},{key:"hide",value:function(){this.setState({showModal:!1})}},{key:"_clear_config",value:function(e){var t={};for(var n in e)t[e[n].name]=e[n].default_value;return t}},{key:"_handle",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];if(n)this.setState({data:(0,m.default)(this.state.data,{config:o({},e.name,{$set:t.target.value})})});else if(e.clear_config){var r,i=""==t.target.value?{}:this._clear_config(this.props.types[t.target.value].properties);this.setState({data:(0,m.default)(this.state.data,(r={},o(r,e.name,{$set:t.target.value}),o(r,"config",{$set:i}),r))})}else this.setState({data:(0,m.default)(this.state.data,o({},e.name,{$set:t.target.value}))})}},{key:"_handleSlider",value:function(e,t){arguments.length>2&&void 0!==arguments[2]&&arguments[2];this.setState({data:(0,m.default)(this.state.data,o({},e.name,{$set:t}))})}},{key:"_handleCheckBox",value:function(e,t){this.setState({data:(0,m.default)(this.state.data,o({},e.name,{$set:!this.state.data[e.name]}))})}},{key:"render_field",value:function(e,t){var n=this,r=arguments.length>2&&void 0!==arguments[2]&&arguments[2];if(!e.condition||e.condition(this.state.data))switch(e.type){case"text":return f.default.createElement(g,{key:t,onChange:function(t){n._handle(e,t,r)},value:this.state.data[e.name],label:e.label});case"actor":return f.default.createElement(E,{onChange:function(t){n._handle(e,t,r)},value:this.state.data[e.name],key:t,label:e.label,options:this.props.actors});case"sensor":return f.default.createElement(E,{onChange:function(t){n._handle(e,t,r)},value:this.state.data[e.name],key:t,label:e.label,options:this.props.sensors});case"kettle":return f.default.createElement(E,{onChange:function(t){n._handle(e,t,r)},value:this.state.data[e.name],key:t,label:e.label,options:this.props.kettle});case"number":return f.default.createElement(b,{key:t,onChange:function(t){n._handle(e,t,r)},value:this.state.data[e.name],description:e.description,label:e.label});case"select":return f.default.createElement(E,{onChange:function(t){n._handle(e,t,r)},value:this.state.data[e.name],key:t,label:e.label,options:e.options});case"checkbox":return f.default.createElement(_,{checked:this.state.data[e.name]||!1,onChange:function(t){n._handleCheckBox(e,t)},value:this.state.data[e.name],key:t,label:e.label});case"slider":return f.default.createElement(x,{key:t,marks:e.marks,min:e.min,max:e.max,onChange:function(t){n._handleSlider(e,t)},value:this.state.data[e.name]});default:return}}},{key:"render_field_config",value:function(e,t){var n=this,r=arguments.length>2&&void 0!==arguments[2]&&arguments[2];if(e.configurable)switch(e.type){case"text":return f.default.createElement(g,{key:t,onChange:function(t){n._handle(e,t,r)},value:this.state.data.config[e.name],label:e.label,description:e.description});case"actor":return f.default.createElement(E,{onChange:function(t){n._handle(e,t,r)},value:this.state.data.config[e.name],key:t,label:e.label,options:this.props.actors,description:e.description});case"sensor":return f.default.createElement(E,{onChange:function(t){n._handle(e,t,r)},value:this.state.data.config[e.name],key:t,label:e.label,options:this.props.sensors,description:e.description});case"kettle":return f.default.createElement(E,{onChange:function(t){n._handle(e,t,r)},value:this.state.data.config[e.name],key:t,label:e.label,options:this.props.kettle,description:e.description});case"number":return f.default.createElement(b,{key:t,onChange:function(t){n._handle(e,t,r)},value:this.state.data.config[e.name],label:e.label,description:e.description});case"select":return f.default.createElement(E,{onChange:function(t){n._handle(e,t,r)},value:this.state.data.config[e.name],key:t,label:e.label,options:e.options,description:e.description});default:return}}},{key:"render_config",value:function(){var e=this;if(void 0!==this.state.data[this.props.configfield]&&""!==this.state.data[this.props.configfield]){var t=this.state.data[this.props.configfield];if(this.props.types.hasOwnProperty(t)){var n=this.props.types[t].properties||[];return n.map(function(t,n){return e.render_field_config(t,n,!0)})}return f.default.createElement("div",null,'Configuration Error. Type "',t,'" configured but not found')}}},{key:"_add",value:function(){this.props.btn_add(this.state.data)===!0&&this.hide()}},{key:"_save",value:function(){this.props.btn_save(this.state.data)===!0&&this.hide()}},{key:"_delete",value:function(){this.props.btn_delete(this.state.data)===!0&&this.hide()}},{key:"render_body",value:function(){var e=this,t=this.props.form;return this.props.configfield?f.default.createElement(p.Row,{className:"show-grid"},f.default.createElement(p.Col,{xs:12,sm:6,md:6},t.map(function(t,n){return e.render_field(t,n)})),f.default.createElement(p.Col,{xs:12,sm:6,md:6},this.render_config())):t.map(function(t,n){return e.render_field(t,n)})}},{key:"render",value:function(){var e=this.props,t=e.add,n=e.save,r=e.remove,o=e.title,i=e.bsSize,a=e.btn_save_label,s=e.btn_add_label,l=e.btn_delete_label;return f.default.createElement(p.Modal,{bsSize:i,animation:!1,show:this.state.showModal,onHide:this.hide.bind(this)},f.default.createElement(p.Modal.Header,null,f.default.createElement(p.Modal.Title,null,o||"Modal")),f.default.createElement(p.Modal.Body,null,this.render_body()),f.default.createElement(p.Modal.Footer,null,f.default.createElement(p.Button,{onClick:this.hide.bind(this)},"Close"),r?f.default.createElement(p.Button,{className:"btn-danger",onClick:this._delete.bind(this)},l):void 0,t?f.default.createElement(p.Button,{className:"btn-success",onClick:this._add.bind(this)},s):void 0,n?f.default.createElement(p.Button,{className:"btn-success",onClick:this._save.bind(this)},a):void 0))}}]),t}(d.Component);w.defaultProps={sensors:{},kettle:{},actors:{},bsSize:"small",btn_save_label:"Update",btn_add_label:"Add",btn_delete_label:"Delete"},t.default=w},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t,n){var r=0;return p.default.Children.map(e,function(e){return p.default.isValidElement(e)?t.call(n,e,r++):e})}function i(e,t,n){var r=0;p.default.Children.forEach(e,function(e){p.default.isValidElement(e)&&t.call(n,e,r++)})}function a(e){var t=0;return p.default.Children.forEach(e,function(e){p.default.isValidElement(e)&&++t}),t}function s(e,t,n){var r=0,o=[];return p.default.Children.forEach(e,function(e){p.default.isValidElement(e)&&t.call(n,e,r++)&&o.push(e)}),o}function l(e,t,n){var r=0,o=void 0;return p.default.Children.forEach(e,function(e){o||p.default.isValidElement(e)&&t.call(n,e,r++)&&(o=e)}),o}function u(e,t,n){var r=0,o=!0;return p.default.Children.forEach(e,function(e){o&&p.default.isValidElement(e)&&(t.call(n,e,r++)||(o=!1))}),o}function c(e,t,n){var r=0,o=!1;return p.default.Children.forEach(e,function(e){o||p.default.isValidElement(e)&&t.call(n,e,r++)&&(o=!0)}),o}function d(e){var t=[];return p.default.Children.forEach(e,function(e){p.default.isValidElement(e)&&t.push(e)}),t}t.__esModule=!0;var f=n(1),p=r(f);t.default={map:o,forEach:i,count:a,find:l,filter:s,every:u,some:c,toArray:d},e.exports=t.default},function(e,t,n){(function(t){"use strict";function r(e){var t=Function.prototype.toString,n=Object.prototype.hasOwnProperty,r=RegExp("^"+t.call(n).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");try{var o=t.call(e);return r.test(o)}catch(e){return!1}}function o(e){var t=u(e);if(t){var n=t.childIDs;c(e),n.forEach(o)}}function i(e,t,n){return"\n in "+(e||"Unknown")+(t?" (at "+t.fileName.replace(/^.*[\\\/]/,"")+":"+t.lineNumber+")":n?" (created by "+n+")":"")}function a(e){return null==e?"#empty":"string"==typeof e||"number"==typeof e?"#text":"string"==typeof e.type?e.type:e.type.displayName||e.type.name||"Unknown"}function s(e){var n,r=T.getDisplayName(e),o=T.getElement(e),a=T.getOwnerID(e);return a&&(n=T.getDisplayName(a)),"production"!==t.env.NODE_ENV?g(o,"ReactComponentTreeHook: Missing React element for debugID %s when building stack",e):void 0,i(r,o&&o._source,n)}var l,u,c,d,f,p,h,m=n(72),v=n(40),y=n(12),g=n(13),b="function"==typeof Array.from&&"function"==typeof Map&&r(Map)&&null!=Map.prototype&&"function"==typeof Map.prototype.keys&&r(Map.prototype.keys)&&"function"==typeof Set&&r(Set)&&null!=Set.prototype&&"function"==typeof Set.prototype.keys&&r(Set.prototype.keys);if(b){var x=new Map,E=new Set;l=function(e,t){x.set(e,t)},u=function(e){return x.get(e)},c=function(e){x.delete(e)},d=function(){return Array.from(x.keys())},f=function(e){E.add(e)},p=function(e){E.delete(e)},h=function(){return Array.from(E.keys())}}else{var _={},w={},C=function(e){return"."+e},O=function(e){return parseInt(e.substr(1),10)};l=function(e,t){var n=C(e);_[n]=t},u=function(e){var t=C(e);return _[t]},c=function(e){var t=C(e);delete _[t]},d=function(){return Object.keys(_).map(O)},f=function(e){var t=C(e);w[t]=!0},p=function(e){var t=C(e);delete w[t]},h=function(){return Object.keys(w).map(O)}}var k=[],T={onSetChildren:function(e,n){var r=u(e);r?void 0:"production"!==t.env.NODE_ENV?y(!1,"Item must have been set"):m("144"),r.childIDs=n;for(var o=0;o0&&void 0!==arguments[0]?arguments[0]:l(),t=arguments[1];switch(t.type){case"LOAD_STATE":return i({},e,{actors:t.payload.actors,config_type:t.payload.actor_types});case"UPDATE_ACTOR":case"ADD_ACTOR":return i({},e,{actors:i({},e.actors,o({},t.payload.id,t.payload))});case"DELETE_ACTOR":return delete e.actors[t.id],i({},e,{actors:i({},e.actors)});case"SWITCH_ACTOR":return i({},e,{actors:i({},e.actors,o({},t.payload.id,t.payload))});default:return e}});t.default=u},function(e,t,n){e.exports=!n(61)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(e,t){var n={}.hasOwnProperty;e.exports=function(e,t){return n.call(e,t)}},function(e,t,n){function r(e,t){var n=i(e,t);return o(n)?n:void 0}var o=n(604),i=n(634);e.exports=r},function(e,t,n){(function(t){"use strict";var r=n(15),o=n(12),i=function(e){var t=this;if(t.instancePool.length){var n=t.instancePool.pop();return t.call(n,e),n}return new t(e)},a=function(e,t){var n=this;if(n.instancePool.length){var r=n.instancePool.pop();return n.call(r,e,t),r}return new n(e,t)},s=function(e,t,n){var r=this;if(r.instancePool.length){var o=r.instancePool.pop();return r.call(o,e,t,n),o}return new r(e,t,n)},l=function(e,t,n,r){var o=this;if(o.instancePool.length){var i=o.instancePool.pop();return o.call(i,e,t,n,r),i}return new o(e,t,n,r)},u=function(e){var n=this;e instanceof n?void 0:"production"!==t.env.NODE_ENV?o(!1,"Trying to release an instance into a pool of a different type."):r("25"),e.destructor(),n.instancePool.length1){for(var x=Array(b),E=0;E1){for(var b=Array(g),x=0;x0&&void 0!==arguments[0]?arguments[0]:d(),t=arguments[1];switch(t.type){case"LOAD_STATE":return a({},e,{list:t.payload.fermenter,config_type:t.payload.fermentation_controller_types});case"DELETE_FERMENTER":return delete e.list[t.id],a({},e,{list:a({},e.list)});case"UPDATE_FERMENTER_TARGET_TEMP":return a({},e,{list:a({},e.list,i({},t.payload.id,(0,c.default)(e.list[t.payload.id],{target_temp:{$set:t.payload.target_temp}})))});case"UPDATE_FERMENTER_BREWNAME":return a({},e,{list:a({},e.list,i({},t.payload.id,(0,c.default)(e.list[t.payload.id],{brewname:{$set:t.payload.brewname}})))});case"ADD_FERMENTER_STEP":return a({},e,{list:a({},e.list,i({},t.payload.fermenter_id,a({},e.list[t.payload.fermenter_id],{steps:[].concat(o(e.list[t.payload.fermenter_id].steps),[t.payload])})))});case"DELETE_FERMENTER_STEP":return a({},e,{list:a({},e.list,i({},t.payload.fermenter_id,a({},e.list[t.payload.fermenter_id],{steps:e.list[t.payload.fermenter_id].steps.filter(function(e,n){return e.id!==t.payload.id})})))});case"UPDATE_FERMENTER_STEP":return a({},e,{list:a({},e.list,i({},t.payload.fermenter_id,a({},e.list[t.payload.fermenter_id],{steps:e.list[t.payload.fermenter_id].steps.map(function(e,n){return e.id!==t.payload.id?e:a({},e,t.payload)})})))});case"UPDATE_FERMENTER_STEPS":return a({},e,{list:a({},e.list,i({},t.payload.fermenter_id,a({},e.list[t.payload.fermenter_id],{steps:e.list[t.payload.fermenter_id].steps.map(function(e,n){return e.id!==t.payload.id?e:a({},e,t.payload)})})))});case"ADD_FERMENTER":case"UPDATE_FERMENTER":return a({},e,{list:a({},e.list,i({},t.payload.id,t.payload))});default:return e}});t.default=f},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}Object.defineProperty(t,"__esModule",{value:!0}),t.action=t.remove=t.save=t.add=void 0;var i=Object.assign||function(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:l(),t=arguments[1];switch(t.type){case"LOAD_STATE":return i({},e,{sensors:t.payload.sensors,config_type:t.payload.sensor_types});case"SENSOR_UPDATE":return i({},e,{sensors:i({},e.sensors,o({},t.payload.id,t.payload))});case"DELETE_SENSOR":return delete e.sensors[t.id],i({},e,{sensors:i({},e.sensors)});case"ADD_SENSOR":case"UPDATE_SENSOR":return i({},e,{sensors:i({},e.sensors,o({},t.payload.id,t.payload))});default:return e}});t.default=u},function(e,t,n){e.exports={default:n(447),__esModule:!0}},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(227),i=r(o);t.default=function(e,t,n){return t in e?(0,i.default)(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}},function(e,t,n){var r=n(79);e.exports=function(e){if(!r(e))throw TypeError(e+" is not an object!");return e}},function(e,t){e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,t,n){var r=n(47),o=n(82);e.exports=n(50)?function(e,t,n){return r.f(e,t,o(1,n))}:function(e,t,n){return e[t]=n,e}},function(e,t,n){var r=n(238),o=n(142);e.exports=Object.keys||function(e){return r(e,o)}},function(e,t){"use strict";function n(e){return e&&e.ownerDocument||document}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n,e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(t)do if(t===e)return!0;while(t=t.parentNode);return!1}Object.defineProperty(t,"__esModule",{value:!0});var i=n(42),a=r(i);t.default=function(){return a.default?function(e,t){return e.contains?e.contains(t):e.compareDocumentPosition?e===t||!!(16&e.compareDocumentPosition(t)):o(e,t)}:o}(),e.exports=t.default},function(e,t,n){(function(e){function r(e,n){var r="b"+t.packets[e.type]+e.data.data;return n(r)}function o(e,n,r){if(!n)return t.encodeBase64Packet(e,r);var o=e.data,i=new Uint8Array(o),a=new Uint8Array(1+o.byteLength);a[0]=g[e.type];for(var s=0;s1?{type:b[o],data:e.substring(1)}:{type:b[o]}:x}var i=new Uint8Array(e),o=i[0],a=f(e,1);return E&&"blob"===n&&(a=new E([a])),{type:b[o],data:a}},t.decodeBase64Packet=function(e,t){var n=b[e.charAt(0)];if(!u)return{type:n,data:{base64:!0,data:e.substr(1)}};var r=u.decode(e.substr(1));return"blob"===t&&E&&(r=new E([r])),{type:n,data:r}},t.encodePayload=function(e,n,r){function o(e){return e.length+":"+e}function i(e,r){t.encodePacket(e,!!a&&n,!0,function(e){r(null,o(e))})}"function"==typeof n&&(r=n,n=null);var a=d(e);return n&&a?E&&!y?t.encodePayloadAsBlob(e,r):t.encodePayloadAsArrayBuffer(e,r):e.length?void l(e,i,function(e,t){return r(t.join(""))}):r("0:")},t.decodePayload=function(e,n,r){if("string"!=typeof e)return t.decodePayloadAsBinary(e,n,r);"function"==typeof n&&(r=n,n=null);var o;if(""==e)return r(x,0,1);for(var i,a,s="",l=0,u=e.length;l0;){for(var s=new Uint8Array(o),l=0===s[0],u="",c=1;255!=s[c];c++){if(u.length>310){a=!0;break}u+=s[c]}if(a)return r(x,0,1);o=f(o,2+u.length),u=parseInt(u);var d=f(o,0,u);if(l)try{d=String.fromCharCode.apply(null,new Uint8Array(d))}catch(e){var p=new Uint8Array(d);d="";for(var c=0;c0&&void 0!==arguments[0]?arguments[0]:"Question",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"ARE YOU SURE?",n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(){return!0},r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){return!0};this.setState({showModal:!0,title:e,message:t,confirm:n,cancel:r})}},{key:"hide",value:function(){this.setState({showModal:!1})}},{key:"_confirm",value:function(){this.props.confirm()===!0&&this.hide()}},{key:"_cancel",value:function(){this.props.cancel()===!0&&this.hide()}},{key:"render",value:function(){var e=this.props,t=e.cancel,n=e.confirm,r=e.message,o=e.title,i=e.bsSize,a=e.btn_confirm_label,s=e.btn_cancel_label;return u.default.createElement(c.Modal,{bsSize:i,animation:!1,show:this.state.showModal,onHide:this.hide.bind(this)},u.default.createElement(c.Modal.Header,null,u.default.createElement(c.Modal.Title,null,o||"Modal")),u.default.createElement(c.Modal.Body,null,r),u.default.createElement(c.Modal.Footer,null,n?u.default.createElement(c.Button,{className:"btn-success",onClick:this._confirm.bind(this)},a):void 0,t?u.default.createElement(c.Button,{className:"btn-danger",onClick:this._cancel.bind(this)},s):void 0))}}]),t}(l.Component);d.defaultProps={bsSize:"small",btn_confirm_label:"Confirm",btn_cancel_label:"Cancel"},t.default=d},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t0&&void 0!==arguments[0]?arguments[0]:l(),t=arguments[1];switch(t.type){case"LOAD_STATE":return i({},e,{list:t.payload.steps,type:t.payload.step_types});case"UPDATE_ALL_STEPS":return i({},e,{list:t.payload});case"ADD_STEP":return i({},e,{list:[].concat(o(e.list),[t.payload])});case"REMOVE_STEP":return i({},e,{list:e.list.filter(function(e,n){return e.id!==t.payload})});case"UPDATE_STEP":return i({},e,{list:e.list.map(function(e,n){return e.id!==t.payload.id?e:i({},e,t.payload)})});default:return e}});t.default=u},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0}),t.checkout_tag=t.git_pull=t.get_git_status=t.reboot=t.shutdown=t.set_time=t.load_state=void 0;var o=Object.assign||function(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:s(),t=arguments[1];switch(t.type){case"UPDATE_GIT_STATUS":return o({},e,{head:t.payload.headcommit,tags:t.payload.tags,branchname:t.payload.branchname,loading:!1});case"GIT_LOADING_STARTED":return o({},e,{loading:!0});case"GIT_LOADING_FINISED":return o({},e,{loading:!1});case"LOAD_STATE":return o({},e,{ready:!0});default:return e}});t.default=u},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var s=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:l(),t=arguments[1];switch(t.type){case"UPDATE_CONFIG":return i({},e,{list:i({},e.list,o({},t.payload.name,t.payload))});case"LOAD_STATE":return i({},e,{list:t.payload.config});default:return e}});t.default=u},function(e,t){"use strict";var n=!("undefined"==typeof window||!window.document||!window.document.createElement),r={canUseDOM:n,canUseWorkers:"undefined"!=typeof Worker,canUseEventListeners:n&&!(!window.addEventListener&&!window.attachEvent),canUseViewport:n&&!!window.screen,isInWorker:!n};e.exports=r},function(e,t,n){(function(t){"use strict";var n=function(e,n,r,o,i,a,s,l){if("production"!==t.env.NODE_ENV&&void 0===n)throw new Error("invariant requires an error message argument");if(!e){var u;if(void 0===n)u=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var c=[r,o,i,a,s,l],d=0;u=new Error(n.replace(/%s/g,function(){return c[d++]})),u.name="Invariant Violation"}throw u.framesToPop=1,u}};e.exports=n}).call(t,n(2))},function(e,t){"use strict";function n(){for(var e=arguments.length,t=Array(e),n=0;n=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}Object.defineProperty(t,"__esModule",{value:!0}),t.CheckboxField=t.SelectField=t.SliderField=t.NumberField=t.TextField=void 0;var u=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:void 0;if(void 0===e){var t=this.props.form;e={};for(var n in t)switch(t[n].type){case"checkbox":e[t[n].name]=!1;break;default:e[t[n].name]=""}}else this.props.configfield&&!e.hasOwnProperty("config")&&(e.config={});this.setState({showModal:!0,data:e})}},{key:"hide",value:function(){this.setState({showModal:!1})}},{key:"_clear_config",value:function(e){var t={};for(var n in e)t[e[n].name]=e[n].default_value;return t}},{key:"_handle",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];if(n)this.setState({data:(0,m.default)(this.state.data,{config:o({},e.name,{$set:t.target.value})})});else if(e.clear_config){var r,i=""==t.target.value?{}:this._clear_config(this.props.types[t.target.value].properties);this.setState({data:(0,m.default)(this.state.data,(r={},o(r,e.name,{$set:t.target.value}),o(r,"config",{$set:i}),r))})}else this.setState({data:(0,m.default)(this.state.data,o({},e.name,{$set:t.target.value}))})}},{key:"_handleSlider",value:function(e,t){arguments.length>2&&void 0!==arguments[2]&&arguments[2];this.setState({data:(0,m.default)(this.state.data,o({},e.name,{$set:t}))})}},{key:"_handleCheckBox",value:function(e,t){this.setState({data:(0,m.default)(this.state.data,o({},e.name,{$set:!this.state.data[e.name]}))})}},{key:"render_field",value:function(e,t){var n=this,r=arguments.length>2&&void 0!==arguments[2]&&arguments[2];if(!e.condition||e.condition(this.state.data)){var o={key:t,value:this.state.data[e.name],label:e.label};switch(e.type){case"text":return f.default.createElement(g,c({onChange:function(t){n._handle(e,t,r)}},o));case"actor":return f.default.createElement(E,c({onChange:function(t){n._handle(e,t,r)}},o,{options:this.props.actors}));case"sensor":return f.default.createElement(E,c({onChange:function(t){n._handle(e,t,r)}},o,{options:this.props.sensors}));case"kettle":return f.default.createElement(E,c({onChange:function(t){n._handle(e,t,r)}},o,{options:this.props.kettle}));case"number":return f.default.createElement(b,c({onChange:function(t){n._handle(e,t,r)}},o));case"select":return f.default.createElement(E,c({onChange:function(t){n._handle(e,t,r)}},o,{options:e.options}));case"checkbox":return f.default.createElement(_,c({checked:this.state.data[e.name]||!1,onChange:function(t){n._handleCheckBox(e,t)}},o));case"slider":return f.default.createElement(x,c({marks:e.marks,min:e.min,max:e.max,onChange:function(t){n._handleSlider(e,t)}},o));default:return}}}},{key:"render_field_config",value:function(e,t){var n=this,r=arguments.length>2&&void 0!==arguments[2]&&arguments[2];if(e.configurable)switch(e.type){case"text":return f.default.createElement(g,{key:t,onChange:function(t){n._handle(e,t,r)},value:this.state.data.config[e.name],label:e.label,required:e.required,description:e.description});case"actor":return f.default.createElement(E,{onChange:function(t){n._handle(e,t,r)},value:this.state.data.config[e.name],key:t,label:e.label,options:this.props.actors,description:e.description});case"sensor":return f.default.createElement(E,{onChange:function(t){n._handle(e,t,r)},value:this.state.data.config[e.name],key:t,label:e.label,options:this.props.sensors,description:e.description});case"kettle":return f.default.createElement(E,{onChange:function(t){n._handle(e,t,r)},value:this.state.data.config[e.name],key:t,label:e.label,options:this.props.kettle,description:e.description});case"number":return f.default.createElement(b,{key:t,onChange:function(t){n._handle(e,t,r)},value:this.state.data.config[e.name],label:e.label,description:e.description});case"select":return f.default.createElement(E,{onChange:function(t){n._handle(e,t,r)},value:this.state.data.config[e.name],key:t,label:e.label,options:e.options,description:e.description});default:return}}},{key:"render_config",value:function(){var e=this;if(void 0!==this.state.data[this.props.configfield]&&""!==this.state.data[this.props.configfield]){var t=this.state.data[this.props.configfield];if(this.props.types.hasOwnProperty(t)){var n=this.props.types[t].properties||[];return n.map(function(t,n){return e.render_field_config(t,n,!0)})}return f.default.createElement("div",null,'Configuration Error. Type "',t,'" configured but not found')}}},{key:"_add",value:function(){this.props.btn_add(this.state.data)===!0&&this.hide()}},{key:"_save",value:function(){this.props.btn_save(this.state.data)===!0&&this.hide()}},{key:"_delete",value:function(){this.props.btn_delete(this.state.data)===!0&&this.hide()}},{key:"render_body",value:function(){var e=this,t=this.props.form;return this.props.configfield?f.default.createElement(p.Row,{className:"show-grid"},f.default.createElement(p.Col,{xs:12,sm:6,md:6},t.map(function(t,n){return e.render_field(t,n)})),f.default.createElement(p.Col,{xs:12,sm:6,md:6},this.render_config())):t.map(function(t,n){return e.render_field(t,n)})}},{key:"render",value:function(){var e=this.props,t=e.add,n=e.save,r=e.remove,o=e.title,i=e.bsSize,a=e.btn_save_label,s=e.btn_add_label,l=e.btn_delete_label;return f.default.createElement(p.Modal,{bsSize:i,animation:!1,show:this.state.showModal,onHide:this.hide.bind(this)},f.default.createElement(p.Modal.Header,null,f.default.createElement(p.Modal.Title,null,o||"Modal")),f.default.createElement(p.Modal.Body,null,this.render_body()),f.default.createElement(p.Modal.Footer,null,f.default.createElement(p.Button,{onClick:this.hide.bind(this)},"Close"),r?f.default.createElement(p.Button,{className:"btn-danger",onClick:this._delete.bind(this)},l):void 0,t?f.default.createElement(p.Button,{className:"btn-success",onClick:this._add.bind(this)},s):void 0,n?f.default.createElement(p.Button,{className:"btn-success",onClick:this._save.bind(this)},a):void 0))}}]),t}(d.Component);w.defaultProps={sensors:{},kettle:{},actors:{},bsSize:"small",btn_save_label:"Update",btn_add_label:"Add",btn_delete_label:"Delete"},t.default=w},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t,n){var r=0;return p.default.Children.map(e,function(e){return p.default.isValidElement(e)?t.call(n,e,r++):e})}function i(e,t,n){var r=0;p.default.Children.forEach(e,function(e){p.default.isValidElement(e)&&t.call(n,e,r++)})}function a(e){var t=0;return p.default.Children.forEach(e,function(e){p.default.isValidElement(e)&&++t}),t}function s(e,t,n){var r=0,o=[];return p.default.Children.forEach(e,function(e){p.default.isValidElement(e)&&t.call(n,e,r++)&&o.push(e)}),o}function l(e,t,n){var r=0,o=void 0;return p.default.Children.forEach(e,function(e){o||p.default.isValidElement(e)&&t.call(n,e,r++)&&(o=e)}),o}function u(e,t,n){var r=0,o=!0;return p.default.Children.forEach(e,function(e){o&&p.default.isValidElement(e)&&(t.call(n,e,r++)||(o=!1))}),o}function c(e,t,n){var r=0,o=!1;return p.default.Children.forEach(e,function(e){o||p.default.isValidElement(e)&&t.call(n,e,r++)&&(o=!0)}),o}function d(e){var t=[];return p.default.Children.forEach(e,function(e){p.default.isValidElement(e)&&t.push(e)}),t}t.__esModule=!0;var f=n(1),p=r(f);t.default={map:o,forEach:i,count:a,find:l,filter:s,every:u,some:c,toArray:d},e.exports=t.default},function(e,t,n){(function(e){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}function s(){return{fields:n(881).default,widgets:n(900).default,definitions:{},formContext:{}}}function l(e,t){function n(e){return e.MergedWidget||!function(){var t=e.defaultProps&&e.defaultProps.options||{};e.MergedWidget=function(n){var r=n.options,o=void 0===r?{}:r,i=a(n,["options"]);return F.default.createElement(e,L({options:L({},t,o)},i))}}(),e.MergedWidget}var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},o=e.type;if("function"==typeof t)return n(t);if("string"!=typeof t)throw new Error("Unsupported widget definition: "+("undefined"==typeof t?"undefined":I(t)));if(r.hasOwnProperty(t)){var i=r[t];return l(e,i,r)}if(!V.hasOwnProperty(o))throw new Error('No widget for type "'+o+'"');if(V[o].hasOwnProperty(t)){var s=r[V[o][t]];return l(e,s,r)}throw new Error('No widget "'+t+'" for type "'+o+'"')}function u(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=t;if(f(r)&&f(e.default))r=p(r,e.default);else if("default"in e)r=e.default;else{if("$ref"in e){var o=O(e.$ref,n);return u(o,r,n)}E(e)&&(r=e.items.map(function(e){return u(e,void 0,n)}))}switch("undefined"==typeof r&&(r=e.default),e.type){case"object":return Object.keys(e.properties||{}).reduce(function(t,o){return t[o]=u(e.properties[o],(r||{})[o],n),t},{});case"array":if(e.minItems){if(b(e,n))return[];var i=r?r.length:0;if(e.minItems>i){var a=r||[],s=new Array(e.minItems-i).fill(u(e.items,e.items.defaults,n));return a.concat(s)}}}return r}function c(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(!f(e))throw new Error("Invalid schema: "+e);var r=C(e,n),o=u(r,e.default,n);return"undefined"==typeof t?o:f(t)?p(o,t):t||o}function d(e){return Object.keys(e).filter(function(e){return 0===e.indexOf("ui:")}).reduce(function(t,n){var r=e[n];return"ui:widget"===n&&f(r)?(console.warn("Setting options via ui:widget object is deprecated, use ui:options instead"),L({},t,r.options||{},{widget:r.component})):"ui:options"===n&&f(r)?L({},t,r):L({},t,i({},n.substring(3),r))},{})}function f(e){return"object"===("undefined"==typeof e?"undefined":I(e))&&null!==e&&!Array.isArray(e)}function p(e,t){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=Object.assign({},e);return Object.keys(t).reduce(function(r,o){var i=e[o],a=t[o];return e.hasOwnProperty(o)&&f(a)?r[o]=p(i,a,n):n&&Array.isArray(i)&&Array.isArray(a)?r[o]=i.concat(a):r[o]=a,r},r)}function h(e){if(""!==e){if(/\.$/.test(e))return e;if(/\.0$/.test(e))return e;var t=Number(e),n="number"==typeof t&&!Number.isNaN(t);return/\.\d*0$/.test(e)?e:n?t:e}}function m(e,t){if(!Array.isArray(t))return e;var n=function(e){return e.reduce(function(e,t){return e[t]=!0,e},{})},r=function(e){return e.length>1?"properties '"+e.join("', '")+"'":"property '"+e[0]+"'"},i=n(e),a=n(t),s=t.filter(function(e){return"*"!==e&&!i[e]});if(s.length)throw new Error("uiSchema order list contains extraneous "+r(s));var l=e.filter(function(e){return!a[e]}),u=t.indexOf("*");if(u===-1){if(l.length)throw new Error("uiSchema order list does not contain "+r(l));return t}if(u!==t.lastIndexOf("*"))throw new Error("uiSchema order list contains more than one wildcard item");var c=[].concat(o(t));return c.splice.apply(c,[u,1].concat(o(l))),c}function v(e){return Array.isArray(e.enum)&&1===e.enum.length||e.hasOwnProperty("const")}function y(e){if(Array.isArray(e.enum)&&1===e.enum.length)return e.enum[0];if(e.hasOwnProperty("const"))return e.const;throw new Error("schema cannot be inferred as a constant")}function g(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=C(e,t),r=n.oneOf||n.anyOf;return!!Array.isArray(n.enum)||!!Array.isArray(r)&&r.every(function(e){return v(e)})}function b(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return!(!e.uniqueItems||!e.items)&&g(e.items,t)}function x(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if("files"===t["ui:widget"])return!0;if(e.items){var r=C(e.items,n);return"string"===r.type&&"data-url"===r.format}return!1}function E(e){return Array.isArray(e.items)&&e.items.length>0&&e.items.every(function(e){return f(e)})}function _(e){return e.additionalItems===!0&&console.warn("additionalItems=true is currently not supported"),f(e.additionalItems)}function w(e){if(e.enum)return e.enum.map(function(t,n){var r=e.enumNames&&e.enumNames[n]||String(t);return{label:r,value:t}});var t=e.oneOf||e.anyOf;return t.map(function(e,t){var n=y(e),r=e.title||String(n);return{label:r,value:n}})}function O(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=/^#\/definitions\/(.*)$/.exec(e);if(n&&n[1]){var r=n[1].split("/"),o=t,i=!0,a=!1,s=void 0;try{for(var l,u=r[Symbol.iterator]();!(i=(l=u.next()).done);i=!0){var c=l.value;if(c=c.replace(/~1/g,"/").replace(/~0/g,"~"),!o.hasOwnProperty(c))throw new Error("Could not find a definition for "+e+".");o=o[c]}}catch(e){a=!0,s=e}finally{try{!i&&u.return&&u.return()}finally{if(a)throw s}}return o}throw new Error("Could not find a definition for "+e+".")}function C(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(!e.hasOwnProperty("$ref"))return e;var n=O(e.$ref,t),r=(e.$ref,a(e,["$ref"]));return L({},n,r)}function k(e){return"[object Arguments]"===Object.prototype.toString.call(e)}function S(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:[];if(e===t)return!0;if("function"==typeof e||"function"==typeof t)return!0;if("object"!==("undefined"==typeof e?"undefined":I(e))||"object"!==("undefined"==typeof t?"undefined":I(t)))return!1;if(null===e||null===t)return!1;if(e instanceof Date&&t instanceof Date)return e.getTime()===t.getTime();if(e instanceof RegExp&&t instanceof RegExp)return e.source===t.source&&e.global===t.global&&e.multiline===t.multiline&&e.lastIndex===t.lastIndex&&e.ignoreCase===t.ignoreCase;if(k(e)||k(t)){if(!k(e)||!k(t))return!1;var o=Array.prototype.slice;return S(o.call(e),o.call(t),n,r)}if(e.constructor!==t.constructor)return!1;var i=Object.keys(e),a=Object.keys(t);if(0===i.length&&0===a.length)return!0;if(i.length!==a.length)return!1;for(var s=n.length;s--;)if(n[s]===e)return r[s]===t;n.push(e),r.push(t),i.sort(),a.sort();for(var l=i.length-1;l>=0;l--)if(i[l]!==a[l])return!1;for(var u=void 0,c=i.length-1;c>=0;c--)if(u=i[c],!S(e[u],t[u],n,r))return!1;return n.pop(),r.pop(),!0}function T(e,t,n){var r=e.props,o=e.state;return!S(r,t)||!S(o,n)}function N(e,t,n){var r={$id:t||"root"};if("$ref"in e){var o=C(e,n);return N(o,t,n)}if("items"in e&&!e.items.$ref)return N(e.items,t,n);if("object"!==e.type)return r;for(var i in e.properties||{}){var a=e.properties[i],s=r.$id+"_"+i;r[i]=N(a,s,n)}return r}function P(e){var t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];if(!e)return{year:-1,month:-1,day:-1,hour:t?-1:0,minute:t?-1:0,second:t?-1:0};var n=new Date(e);if(Number.isNaN(n.getTime()))throw new Error("Unable to parse date "+e);return{year:n.getUTCFullYear(),month:n.getUTCMonth()+1,day:n.getUTCDate(),hour:t?n.getUTCHours():0,minute:t?n.getUTCMinutes():0,second:t?n.getUTCSeconds():0}}function M(e){var t=e.year,n=e.month,r=e.day,o=e.hour,i=void 0===o?0:o,a=e.minute,s=void 0===a?0:a,l=e.second,u=void 0===l?0:l,c=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],d=Date.UTC(t,n-1,r,i,s,u),f=new Date(d).toJSON();return c?f:f.slice(0,10)}function A(e,t){for(var n=String(e);n.length=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}function i(e){var t=e.value,n=e.readonly,r=e.disabled,i=e.autofocus,s=e.onBlur,u=e.onFocus,c=e.options,d=(e.schema,e.formContext,e.registry,o(e,["value","readonly","disabled","autofocus","onBlur","onFocus","options","schema","formContext","registry"]));d.type=c.inputType||d.type||"text";var f=function(t){var n=t.target.value;return e.onChange(""===n?c.emptyValue:n)};return l.default.createElement("input",a({className:"form-control",readOnly:n,disabled:r,autoFocus:i,value:null==t?"":t},d,{onChange:f,onBlur:s&&function(e){return s(d.id,e.target.value)},onFocus:u&&function(e){return u(d.id,e.target.value)}}))}Object.defineProperty(t,"__esModule",{value:!0});var a=Object.assign||function(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:l(),t=arguments[1];switch(t.type){case"LOAD_STATE":return i({},e,{actors:t.payload.actors,config_type:t.payload.actor_types});case"UPDATE_ACTOR":case"ADD_ACTOR":return i({},e,{actors:i({},e.actors,o({},t.payload.id,t.payload))});case"DELETE_ACTOR":return delete e.actors[t.id],i({},e,{actors:i({},e.actors)});case"SWITCH_ACTOR":return i({},e,{actors:i({},e.actors,o({},t.payload.id,t.payload))});default:return e}});t.default=u},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0}),t.action=t.login=t.checkout_tag=t.git_pull=t.get_git_status=t.reboot=t.shutdown=t.set_time=t.load_state=void 0;var o=Object.assign||function(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:s(),t=arguments[1];switch(t.type){case"UPDATE_GIT_STATUS":return o({},e,{head:t.payload.headcommit,tags:t.payload.tags,branchname:t.payload.branchname,loading:!1});case"GIT_LOADING_STARTED":return o({},e,{loading:!0});case"GIT_LOADING_FINISED":return o({},e,{loading:!1});case"LOAD_STATE":return o({},e,{ready:!0,actions:t.payload.actions});case"LOGGED_IN_SUCCESS":return o({},e,{loggedin:!0});default:return e}});t.default=u},function(e,t,n){e.exports=!n(64)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(e,t){var n={}.hasOwnProperty;e.exports=function(e,t){return n.call(e,t)}},function(e,t,n){function r(e,t){var n=i(e,t);return o(n)?n:void 0}var o=n(618),i=n(648);e.exports=r},function(e,t,n){(function(t){"use strict";var r=n(16),o=n(12),i=function(e){var t=this;if(t.instancePool.length){var n=t.instancePool.pop();return t.call(n,e),n}return new t(e)},a=function(e,t){var n=this;if(n.instancePool.length){var r=n.instancePool.pop();return n.call(r,e,t),r}return new n(e,t)},s=function(e,t,n){var r=this;if(r.instancePool.length){var o=r.instancePool.pop();return r.call(o,e,t,n),o}return new r(e,t,n)},l=function(e,t,n,r){var o=this;if(o.instancePool.length){var i=o.instancePool.pop();return o.call(i,e,t,n,r),i}return new o(e,t,n,r)},u=function(e){var n=this;e instanceof n?void 0:"production"!==t.env.NODE_ENV?o(!1,"Trying to release an instance into a pool of a different type."):r("25"),e.destructor(),n.instancePool.length1){for(var x=Array(b),E=0;E1){for(var b=Array(g),x=0;x0&&void 0!==arguments[0]?arguments[0]:d(),t=arguments[1];switch(t.type){case"LOAD_STATE":return a({},e,{list:t.payload.fermenter,config_type:t.payload.fermentation_controller_types});case"DELETE_FERMENTER":return delete e.list[t.id],a({},e,{list:a({},e.list)});case"UPDATE_FERMENTER_TARGET_TEMP":return a({},e,{list:a({},e.list,i({},t.payload.id,(0,c.default)(e.list[t.payload.id],{target_temp:{$set:t.payload.target_temp}})))});case"UPDATE_FERMENTER_BREWNAME":return a({},e,{list:a({},e.list,i({},t.payload.id,(0,c.default)(e.list[t.payload.id],{ +brewname:{$set:t.payload.brewname}})))});case"ADD_FERMENTER_STEP":return a({},e,{list:a({},e.list,i({},t.payload.fermenter_id,a({},e.list[t.payload.fermenter_id],{steps:[].concat(o(e.list[t.payload.fermenter_id].steps),[t.payload])})))});case"DELETE_FERMENTER_STEP":return a({},e,{list:a({},e.list,i({},t.payload.fermenter_id,a({},e.list[t.payload.fermenter_id],{steps:e.list[t.payload.fermenter_id].steps.filter(function(e,n){return e.id!==t.payload.id})})))});case"UPDATE_FERMENTER_STEP":return a({},e,{list:a({},e.list,i({},t.payload.fermenter_id,a({},e.list[t.payload.fermenter_id],{steps:e.list[t.payload.fermenter_id].steps.map(function(e,n){return e.id!==t.payload.id?e:a({},e,t.payload)})})))});case"UPDATE_FERMENTER_STEPS":return a({},e,{list:a({},e.list,i({},t.payload.fermenter_id,a({},e.list[t.payload.fermenter_id],{steps:e.list[t.payload.fermenter_id].steps.map(function(e,n){return e.id!==t.payload.id?e:a({},e,t.payload)})})))});case"ADD_FERMENTER":case"UPDATE_FERMENTER":return a({},e,{list:a({},e.list,i({},t.payload.id,t.payload))});default:return e}});t.default=f},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}Object.defineProperty(t,"__esModule",{value:!0}),t.action=t.remove=t.save=t.add=void 0;var i=Object.assign||function(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:l(),t=arguments[1];switch(t.type){case"LOAD_STATE":return i({},e,{sensors:t.payload.sensors,config_type:t.payload.sensor_types});case"SENSOR_UPDATE":return i({},e,{sensors:i({},e.sensors,o({},t.payload.id,t.payload))});case"DELETE_SENSOR":return delete e.sensors[t.id],i({},e,{sensors:i({},e.sensors)});case"ADD_SENSOR":case"UPDATE_SENSOR":return i({},e,{sensors:i({},e.sensors,o({},t.payload.id,t.payload))});default:return e}});t.default=u},function(e,t,n){e.exports={default:n(459),__esModule:!0}},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(230),i=r(o);t.default=function(e,t,n){return t in e?(0,i.default)(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(444),i=r(o),a=n(443),s=r(a),l="function"==typeof s.default&&"symbol"==typeof i.default?function(e){return typeof e}:function(e){return e&&"function"==typeof s.default&&e.constructor===s.default&&e!==s.default.prototype?"symbol":typeof e};t.default="function"==typeof s.default&&"symbol"===l(i.default)?function(e){return"undefined"==typeof e?"undefined":l(e)}:function(e){return e&&"function"==typeof s.default&&e.constructor===s.default&&e!==s.default.prototype?"symbol":"undefined"==typeof e?"undefined":l(e)}},function(e,t,n){var r=n(82);e.exports=function(e){if(!r(e))throw TypeError(e+" is not an object!");return e}},function(e,t){e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,t,n){var r=n(47),o=n(86);e.exports=n(52)?function(e,t,n){return r.f(e,t,o(1,n))}:function(e,t,n){return e[t]=n,e}},function(e,t){"use strict";function n(e){return e&&e.ownerDocument||document}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n,e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(t)do if(t===e)return!0;while(t=t.parentNode);return!1}Object.defineProperty(t,"__esModule",{value:!0});var i=n(41),a=r(i);t.default=function(){return a.default?function(e,t){return e.contains?e.contains(t):e.compareDocumentPosition?e===t||!!(16&e.compareDocumentPosition(t)):o(e,t)}:o}(),e.exports=t.default},function(e,t,n){(function(e){function r(e,n){var r="b"+t.packets[e.type]+e.data.data;return n(r)}function o(e,n,r){if(!n)return t.encodeBase64Packet(e,r);var o=e.data,i=new Uint8Array(o),a=new Uint8Array(1+o.byteLength);a[0]=g[e.type];for(var s=0;s1?{type:b[o],data:e.substring(1)}:{type:b[o]}:x}var i=new Uint8Array(e),o=i[0],a=f(e,1);return E&&"blob"===n&&(a=new E([a])),{type:b[o],data:a}},t.decodeBase64Packet=function(e,t){var n=b[e.charAt(0)];if(!u)return{type:n,data:{base64:!0,data:e.substr(1)}};var r=u.decode(e.substr(1));return"blob"===t&&E&&(r=new E([r])),{type:n,data:r}},t.encodePayload=function(e,n,r){function o(e){return e.length+":"+e}function i(e,r){t.encodePacket(e,!!a&&n,!0,function(e){r(null,o(e))})}"function"==typeof n&&(r=n,n=null);var a=d(e);return n&&a?E&&!y?t.encodePayloadAsBlob(e,r):t.encodePayloadAsArrayBuffer(e,r):e.length?void l(e,i,function(e,t){return r(t.join(""))}):r("0:")},t.decodePayload=function(e,n,r){if("string"!=typeof e)return t.decodePayloadAsBinary(e,n,r);"function"==typeof n&&(r=n,n=null);var o;if(""==e)return r(x,0,1);for(var i,a,s="",l=0,u=e.length;l0;){for(var s=new Uint8Array(o),l=0===s[0],u="",c=1;255!=s[c];c++){if(u.length>310){a=!0;break}u+=s[c]}if(a)return r(x,0,1);o=f(o,2+u.length),u=parseInt(u);var d=f(o,0,u);if(l)try{d=String.fromCharCode.apply(null,new Uint8Array(d))}catch(e){var p=new Uint8Array(d);d="";for(var c=0;c0&&void 0!==arguments[0]?arguments[0]:"Question",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"ARE YOU SURE?",n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(){return!0},r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){return!0};this.setState({showModal:!0,title:e,message:t,confirm:n,cancel:r})}},{key:"hide",value:function(){this.setState({showModal:!1})}},{key:"_confirm",value:function(){this.props.confirm()===!0&&this.hide()}},{key:"_cancel",value:function(){this.props.cancel()===!0&&this.hide()}},{key:"render",value:function(){var e=this.props,t=e.cancel,n=e.confirm,r=e.message,o=e.title,i=e.bsSize,a=e.btn_confirm_label,s=e.btn_cancel_label;return u.default.createElement(c.Modal,{bsSize:i,animation:!1,show:this.state.showModal,onHide:this.hide.bind(this)},u.default.createElement(c.Modal.Header,null,u.default.createElement(c.Modal.Title,null,o||"Modal")),u.default.createElement(c.Modal.Body,null,r),u.default.createElement(c.Modal.Footer,null,n?u.default.createElement(c.Button,{className:"btn-success",onClick:this._confirm.bind(this)},a):void 0,t?u.default.createElement(c.Button,{className:"btn-danger",onClick:this._cancel.bind(this)},s):void 0))}}]),t}(l.Component);d.defaultProps={bsSize:"small",btn_confirm_label:"Confirm",btn_cancel_label:"Cancel"},t.default=d},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t0&&void 0!==arguments[0]?arguments[0]:l(),t=arguments[1];switch(t.type){case"LOAD_STATE":return i({},e,{list:t.payload.steps,type:t.payload.step_types});case"UPDATE_ALL_STEPS":return i({},e,{list:t.payload});case"ADD_STEP":return i({},e,{list:[].concat(o(e.list),[t.payload])});case"REMOVE_STEP":return i({},e,{list:e.list.filter(function(e,n){return e.id!==t.payload})});case"UPDATE_STEP":return i({},e,{list:e.list.map(function(e,n){return e.id!==t.payload.id?e:i({},e,t.payload)})});default:return e}});t.default=u},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var s=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:c(),t=arguments[1];switch(t.type){case"LOAD_STATE":return i({},e,{list:t.payload.kettle,config_type:t.payload.controller_types});case"DELETE_KETTLE":return delete e.list[t.id],i({},e,{list:i({},e.list)});case"UPDATE_KETTLE_TARGET_TEMP":return i({},e,{list:i({},e.list,o({},t.payload.id,(0,u.default)(e.list[t.payload.id],{target_temp:{$set:t.payload.target_temp}})))});case"ADD_KETTLE":case"UPDATE_KETTLE":return i({},e,{list:i({},e.list,o({},t.payload.id,t.payload))});default:return e}});t.default=d},function(e,t){e.exports=function(e,t){var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}},function(e,t,n){var r=n(141);e.exports=function(e){return Object(r(e))}},function(e,t){var n=0,r=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++n+r).toString(36))}},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=n(42),i=r(o),a=function(){};i.default&&(a=function(){return document.addEventListener?function(e,t,n,r){return e.addEventListener(t,n,r||!1)}:document.attachEvent?function(e,t,n){return e.attachEvent("on"+t,function(t){t=t||window.event,t.target=t.target||t.srcElement,t.currentTarget=e,n.call(e,t)})}:void 0}()),t.default=a,e.exports=t.default},function(e,t){"use strict";function n(e){return e===e.window?e:9===e.nodeType&&(e.defaultView||e.parentWindow)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n,e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t,n){var r="",o="",i=t;if("string"==typeof t){if(void 0===n)return e.style[(0,a.default)(t)]||(0,c.default)(e).getPropertyValue((0,l.default)(t));(i={})[t]=n}Object.keys(i).forEach(function(t){var n=i[t];n||0===n?(0,m.default)(t)?o+=t+"("+n+") ":r+=(0,l.default)(t)+": "+n+";":(0,f.default)(e,(0,l.default)(t))}),o&&(r+=p.transform+": "+o+";"),e.style.cssText+=";"+r}Object.defineProperty(t,"__esModule",{value:!0}),t.default=o;var i=n(253),a=r(i),s=n(516),l=r(s),u=n(511),c=r(u),d=n(512),f=r(d),p=n(157),h=n(513),m=r(h);e.exports=t.default},function(e,t,n){(function(r){function o(){return"undefined"!=typeof document&&"WebkitAppearance"in document.documentElement.style||window.console&&(console.firebug||console.exception&&console.table)||navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31}function i(){var e=arguments,n=this.useColors;if(e[0]=(n?"%c":"")+this.namespace+(n?" %c":" ")+e[0]+(n?"%c ":" ")+"+"+t.humanize(this.diff),!n)return e;var r="color: "+this.color;e=[e[0],r,"color: inherit"].concat(Array.prototype.slice.call(e,1));var o=0,i=0;return e[0].replace(/%[a-z%]/g,function(e){"%%"!==e&&(o++,"%c"===e&&(i=o))}),e.splice(i,0,r),e}function a(){return"object"==typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}function s(e){try{null==e?t.storage.removeItem("debug"):t.storage.debug=e}catch(e){}}function l(){try{return t.storage.debug}catch(e){}if("undefined"!=typeof r&&"env"in r)return r.env.DEBUG}function u(){try{return window.localStorage}catch(e){}}t=e.exports=n(524),t.log=a,t.formatArgs=i,t.save=s,t.load=l,t.useColors=o,t.storage="undefined"!=typeof chrome&&"undefined"!=typeof chrome.storage?chrome.storage.local:u(),t.colors=["lightseagreen","forestgreen","goldenrod","dodgerblue","darkorchid","crimson"],t.formatters.j=function(e){try{return JSON.stringify(e)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}},t.enable(l())}).call(t,n(2))},function(e,t,n){(function(t){"use strict";var n={};"production"!==t.env.NODE_ENV&&Object.freeze(n),e.exports=n}).call(t,n(2))},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},r=t.isBrowser="object"===("undefined"==typeof window?"undefined":n(window))&&"object"===("undefined"==typeof document?"undefined":n(document))&&9===document.nodeType;t.default=r},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var i=Object.assign||function(e){for(var t=1;t` components. To apply a ref to the component use the callback signature:\n\n https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute"):void 0:o=(0,q.default)(e.ref,o),(0,w.cloneElement)(e,(0,l.default)({},t,{ref:o,bsClass:(0,G.prefix)(t,"toggle"),onClick:(0,q.default)(e.props.onClick,this.handleClick),onKeyDown:(0,q.default)(e.props.onKeyDown,this.handleKeyDown)}))},t.prototype.renderMenu=function(e,t){var n=this,o=t.id,i=t.onClose,s=t.onSelect,u=t.rootCloseEvent,c=(0,a.default)(t,["id","onClose","onSelect","rootCloseEvent"]),d=function(e){n.menu=e};return"string"==typeof e.ref?"production"!==r.env.NODE_ENV?(0,B.default)(!1,"String refs are not supported on `` components. To apply a ref to the component use the callback signature:\n\n https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute"):void 0:d=(0,q.default)(e.ref,d),(0,w.cloneElement)(e,(0,l.default)({},c,{ref:d,labelledBy:o,bsClass:(0,G.prefix)(c,"menu"),onClose:(0,q.default)(e.props.onClose,i,this.handleClose),onSelect:(0,q.default)(e.props.onSelect,s,function(e,t){return n.handleClose(t,{source:"select"})}),rootCloseEvent:u}))},t.prototype.render=function(){var e,t=this,n=this.props,r=n.componentClass,o=n.id,i=n.dropup,s=n.disabled,u=n.pullRight,c=n.open,d=n.onClose,f=n.onSelect,p=n.role,h=n.bsClass,m=n.className,y=n.rootCloseEvent,g=n.children,b=(0,a.default)(n,["componentClass","id","dropup","disabled","pullRight","open","onClose","onSelect","role","bsClass","className","rootCloseEvent","children"]);delete b.onToggle;var x=(e={},e[h]=!0,e.open=c,e.disabled=s,e);return i&&(x[h]=!1,x.dropup=!0),C.default.createElement(r,(0,l.default)({},b,{className:(0,v.default)(m,x)}),$.default.map(g,function(e){switch(e.props.bsRole){case Z:return t.renderToggle(e,{id:o,disabled:s,open:c,role:p,bsClass:h});case J:return t.renderMenu(e,{id:o,open:c,pullRight:u,bsClass:h,onClose:d,onSelect:f,rootCloseEvent:y});default:return e}}))},t}(C.default.Component);te.propTypes=Q,te.defaultProps=ee,(0,G.bsClass)("dropdown",te);var ne=(0,I.default)(te,{open:"onToggle"});ne.Toggle=W.default,ne.Menu=H.default,t.default=ne,e.exports=t.default}).call(t,n(2))},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(4),i=r(o),a=n(5),s=r(a),l=n(7),u=r(l),c=n(6),d=r(c),f=n(9),p=r(f),h=n(1),m=r(h),v=n(3),y=r(v),g=n(337),b=r(g),x={in:y.default.bool,mountOnEnter:y.default.bool,unmountOnExit:y.default.bool,transitionAppear:y.default.bool,timeout:y.default.number,onEnter:y.default.func,onEntering:y.default.func,onEntered:y.default.func,onExit:y.default.func,onExiting:y.default.func,onExited:y.default.func},E={in:!1,timeout:300,mountOnEnter:!1,unmountOnExit:!1,transitionAppear:!1},_=function(e){function t(){return(0,s.default)(this,t),(0,u.default)(this,e.apply(this,arguments))}return(0,d.default)(t,e),t.prototype.render=function(){return m.default.createElement(b.default,(0,i.default)({},this.props,{className:(0,p.default)(this.props.className,"fade"),enteredClassName:"in",enteringClassName:"in"}))},t}(m.default.Component);_.propTypes=x,_.defaultProps=E,t.default=_,e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){var n=t.propTypes,r={},o={};return(0,a.default)(e).forEach(function(e){var t=e[0],i=e[1];n[t]?r[t]=i:o[t]=i}),[r,o]}t.__esModule=!0;var i=n(228),a=r(i);t.default=o,e.exports=t.default},function(e,t,n){(function(t){"use strict";function r(){if(l)for(var e in u){var n=u[e],r=l.indexOf(e);if(r>-1?void 0:"production"!==t.env.NODE_ENV?s(!1,"EventPluginRegistry: Cannot inject event plugins that do not exist in the plugin ordering, `%s`.",e):a("96",e),!c.plugins[r]){n.extractEvents?void 0:"production"!==t.env.NODE_ENV?s(!1,"EventPluginRegistry: Event plugins must implement an `extractEvents` method, but `%s` does not.",e):a("97",e),c.plugins[r]=n;var i=n.eventTypes;for(var d in i)o(i[d],n,d)?void 0:"production"!==t.env.NODE_ENV?s(!1,"EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.",d,e):a("98",d,e)}}}function o(e,n,r){c.eventNameDispatchConfigs.hasOwnProperty(r)?"production"!==t.env.NODE_ENV?s(!1,"EventPluginHub: More than one plugin attempted to publish the same event name, `%s`.",r):a("99",r):void 0,c.eventNameDispatchConfigs[r]=e;var o=e.phasedRegistrationNames;if(o){for(var l in o)if(o.hasOwnProperty(l)){var u=o[l];i(u,n,r)}return!0}return!!e.registrationName&&(i(e.registrationName,n,r),!0)}function i(e,n,r){if(c.registrationNameModules[e]?"production"!==t.env.NODE_ENV?s(!1,"EventPluginHub: More than one plugin attempted to publish the same registration name, `%s`.",e):a("100",e):void 0,c.registrationNameModules[e]=n,c.registrationNameDependencies[e]=n.eventTypes[r].dependencies,"production"!==t.env.NODE_ENV){var o=e.toLowerCase();c.possibleRegistrationNames[o]=e,"onDoubleClick"===e&&(c.possibleRegistrationNames.ondblclick=e)}}var a=n(15),s=n(12),l=null,u={},c={plugins:[],eventNameDispatchConfigs:{},registrationNameModules:{},registrationNameDependencies:{},possibleRegistrationNames:"production"!==t.env.NODE_ENV?{}:null,injectEventPluginOrder:function(e){l?"production"!==t.env.NODE_ENV?s(!1,"EventPluginRegistry: Cannot inject event plugin ordering more than once. You are likely trying to load more than one copy of React."):a("101"):void 0,l=Array.prototype.slice.call(e),r()},injectEventPluginsByName:function(e){var n=!1;for(var o in e)if(e.hasOwnProperty(o)){var i=e[o];u.hasOwnProperty(o)&&u[o]===i||(u[o]?"production"!==t.env.NODE_ENV?s(!1,"EventPluginRegistry: Cannot inject two different event plugins using the same name, `%s`.",o):a("102",o):void 0,u[o]=i,n=!0)}n&&r()},getPluginModuleForEvent:function(e){var t=e.dispatchConfig;if(t.registrationName)return c.registrationNameModules[t.registrationName]||null;if(void 0!==t.phasedRegistrationNames){var n=t.phasedRegistrationNames;for(var r in n)if(n.hasOwnProperty(r)){var o=c.registrationNameModules[n[r]];if(o)return o}}return null},_resetEventPlugins:function(){l=null;for(var e in u)u.hasOwnProperty(e)&&delete u[e];c.plugins.length=0;var n=c.eventNameDispatchConfigs;for(var r in n)n.hasOwnProperty(r)&&delete n[r];var o=c.registrationNameModules;for(var i in o)o.hasOwnProperty(i)&&delete o[i];if("production"!==t.env.NODE_ENV){var a=c.possibleRegistrationNames;for(var s in a)a.hasOwnProperty(s)&&delete a[s]}}};e.exports=c}).call(t,n(2))},function(e,t,n){"use strict";function r(e){return Object.prototype.hasOwnProperty.call(e,m)||(e[m]=p++,d[e[m]]={}),d[e[m]]}var o,i=n(19),a=n(120),s=n(815),l=n(323),u=n(850),c=n(199),d={},f=!1,p=0,h={topAbort:"abort",topAnimationEnd:u("animationend")||"animationend",topAnimationIteration:u("animationiteration")||"animationiteration",topAnimationStart:u("animationstart")||"animationstart",topBlur:"blur",topCanPlay:"canplay",topCanPlayThrough:"canplaythrough",topChange:"change",topClick:"click",topCompositionEnd:"compositionend",topCompositionStart:"compositionstart",topCompositionUpdate:"compositionupdate",topContextMenu:"contextmenu",topCopy:"copy",topCut:"cut",topDoubleClick:"dblclick",topDrag:"drag",topDragEnd:"dragend",topDragEnter:"dragenter",topDragExit:"dragexit",topDragLeave:"dragleave",topDragOver:"dragover",topDragStart:"dragstart",topDrop:"drop",topDurationChange:"durationchange",topEmptied:"emptied",topEncrypted:"encrypted",topEnded:"ended",topError:"error",topFocus:"focus",topInput:"input",topKeyDown:"keydown",topKeyPress:"keypress",topKeyUp:"keyup",topLoadedData:"loadeddata",topLoadedMetadata:"loadedmetadata",topLoadStart:"loadstart",topMouseDown:"mousedown",topMouseMove:"mousemove",topMouseOut:"mouseout",topMouseOver:"mouseover",topMouseUp:"mouseup",topPaste:"paste",topPause:"pause",topPlay:"play",topPlaying:"playing",topProgress:"progress",topRateChange:"ratechange",topScroll:"scroll",topSeeked:"seeked",topSeeking:"seeking",topSelectionChange:"selectionchange",topStalled:"stalled",topSuspend:"suspend",topTextInput:"textInput",topTimeUpdate:"timeupdate",topTouchCancel:"touchcancel",topTouchEnd:"touchend",topTouchMove:"touchmove",topTouchStart:"touchstart",topTransitionEnd:u("transitionend")||"transitionend",topVolumeChange:"volumechange",topWaiting:"waiting",topWheel:"wheel"},m="_reactListenersID"+String(Math.random()).slice(2),v=i({},s,{ReactEventListener:null,injection:{injectReactEventListener:function(e){e.setHandleTopLevel(v.handleTopLevel),v.ReactEventListener=e}},setEnabled:function(e){v.ReactEventListener&&v.ReactEventListener.setEnabled(e)},isEnabled:function(){return!(!v.ReactEventListener||!v.ReactEventListener.isEnabled())},listenTo:function(e,t){for(var n=t,o=r(n),i=a.registrationNameDependencies[e],s=0;s]/;e.exports=r},function(e,t,n){"use strict";var r,o=n(22),i=n(188),a=/^[ \r\n\t\f]/,s=/<(!--|link|noscript|meta|script|style)[ \r\n\t\f\/>]/,l=n(195),u=l(function(e,t){if(e.namespaceURI!==i.svg||"innerHTML"in e)e.innerHTML=t;else{r=r||document.createElement("div"),r.innerHTML=""+t+"";for(var n=r.firstChild;n.firstChild;)e.appendChild(n.firstChild)}});if(o.canUseDOM){var c=document.createElement("div"); -c.innerHTML=" ",""===c.innerHTML&&(u=function(e,t){if(e.parentNode&&e.parentNode.replaceChild(e,e),a.test(t)||"<"===t[0]&&s.test(t)){e.innerHTML=String.fromCharCode(65279)+t;var n=e.firstChild;1===n.data.length?e.removeChild(n):n.deleteData(0,1)}else e.innerHTML=t}),c=null}e.exports=u},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(){function e(){for(var e=arguments.length,t=Array(e),r=0;r>",s=i||n;if(null==t[n])return new Error("The "+o+" `"+s+"` is required to make "+("`"+a+"` accessible for users of assistive ")+"technologies such as screen readers.");for(var l=arguments.length,u=Array(l>5?l-5:0),c=5;c>",l=a||r;if(null==n[r])return t?new Error("Required "+i+" `"+l+"` was not specified "+("in `"+s+"`.")):null;for(var u=arguments.length,c=Array(u>6?u-6:0),d=6;d=r.length)for(var o=n-r.length;o--+1;)r.push(void 0);return r.splice(n,0,r.splice(t,1)[0]),r}function r(e){for(var t=arguments.length,n=Array(t>1?t-1:0),r=1;rt?t:n}function a(e){return"px"===e.substr(-2)?parseFloat(e):0}function s(e){var t=window.getComputedStyle(e);return{top:a(t.marginTop),right:a(t.marginRight),bottom:a(t.marginBottom),left:a(t.marginLeft)}}function l(e,t){var n=t.displayName||t.name;return n?e+"("+n+")":e}Object.defineProperty(t,"__esModule",{value:!0}),t.arrayMove=n,t.omit=r,t.closest=o,t.limit=i,t.getElementMargin=s,t.provideDisplayName=l;t.events={start:["touchstart","mousedown"],move:["touchmove","mousemove"],end:["touchend","touchcancel","mouseup"]},t.vendorPrefix=function(){if("undefined"==typeof window||"undefined"==typeof document)return"";var e=window.getComputedStyle(document.documentElement,"")||["-moz-hidden-iframe"],t=(Array.prototype.slice.call(e).join("").match(/-(moz|webkit|ms)-/)||""===e.OLink&&["","o"])[1];switch(t){case"ms":return"ms";default:return t&&t.length?t[0].toUpperCase()+t.substr(1):""}}()},function(e,t,n){(function(t){"use strict";var n=!1;if("production"!==t.env.NODE_ENV)try{Object.defineProperty({},"x",{get:function(){}}),n=!0}catch(e){}e.exports=n}).call(t,n(2))},function(e,t,n){(function(r){function o(){return"undefined"!=typeof document&&"WebkitAppearance"in document.documentElement.style||window.console&&(console.firebug||console.exception&&console.table)||navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31}function i(){var e=arguments,n=this.useColors;if(e[0]=(n?"%c":"")+this.namespace+(n?" %c":" ")+e[0]+(n?"%c ":" ")+"+"+t.humanize(this.diff),!n)return e;var r="color: "+this.color;e=[e[0],r,"color: inherit"].concat(Array.prototype.slice.call(e,1));var o=0,i=0;return e[0].replace(/%[a-z%]/g,function(e){"%%"!==e&&(o++,"%c"===e&&(i=o))}),e.splice(i,0,r),e}function a(){return"object"==typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}function s(e){try{null==e?t.storage.removeItem("debug"):t.storage.debug=e}catch(e){}}function l(){try{return t.storage.debug}catch(e){}if("undefined"!=typeof r&&"env"in r)return r.env.DEBUG}function u(){try{return window.localStorage}catch(e){}}t=e.exports=n(925),t.log=a,t.formatArgs=i,t.save=s,t.load=l,t.useColors=o,t.storage="undefined"!=typeof chrome&&"undefined"!=typeof chrome.storage?chrome.storage.local:u(),t.colors=["lightseagreen","forestgreen","goldenrod","dodgerblue","darkorchid","crimson"],t.formatters.j=function(e){try{return JSON.stringify(e)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}},t.enable(l())}).call(t,n(2))},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t,n,r,o){n&&(e._notifying=!0,n.call.apply(n,[e,r].concat(o)),e._notifying=!1),e._values[t]=r,e.unmounted||e.forceUpdate()}t.__esModule=!0;var i=n(940),a=r(i),s={shouldComponentUpdate:function(){return!this._notifying}};t.default=(0,a.default)(s,o),e.exports=t.default},function(e,t,n){(function(t){"use strict";function r(e,t){!i.isUndefined(e)&&i.isUndefined(e["Content-Type"])&&(e["Content-Type"]=t)}function o(){var e;return"undefined"!=typeof XMLHttpRequest?e=n(211):"undefined"!=typeof t&&(e=n(211)),e}var i=n(30),a=n(380),s={"Content-Type":"application/x-www-form-urlencoded"},l={adapter:o(),transformRequest:[function(e,t){return a(t,"Content-Type"),i.isFormData(e)||i.isArrayBuffer(e)||i.isBuffer(e)||i.isStream(e)||i.isFile(e)||i.isBlob(e)?e:i.isArrayBufferView(e)?e.buffer:i.isURLSearchParams(e)?(r(t,"application/x-www-form-urlencoded;charset=utf-8"),e.toString()):i.isObject(e)?(r(t,"application/json;charset=utf-8"),JSON.stringify(e)):e}],transformResponse:[function(e){if("string"==typeof e)try{e=JSON.parse(e)}catch(e){}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,validateStatus:function(e){return e>=200&&e<300}};l.headers={common:{Accept:"application/json, text/plain, */*"}},i.forEach(["delete","get","head"],function(e){l.headers[e]={}}),i.forEach(["post","put","patch"],function(e){l.headers[e]=i.merge(s)}),e.exports=l}).call(t,n(2))},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=n(11),i=n(26),a=r(i),s=n(49),l=function(e,t){return{title:"Actor Modal",bsSize:"large",form:[{label:"Name",name:"name",type:"text"},{label:"Hide on Dashboard",name:"hide",type:"checkbox"},{label:"Type",clear_config:!0,name:"type",type:"select",options:Object.keys(e.actor.config_type)}],types:e.actor.config_type,configfield:"type",actors:e.actor.actors,sensors:e.sensor.sensors,kettle:e.kettle.list}},u=function(e,t){return{btn_save:function(t){return e((0,s.save)(t)),!0},btn_add:function(t){return e((0,s.add)(t)),!0},btn_delete:function(t){return e((0,s.remove)(t)),!0}}},c=(0,o.connect)(l,u,null,{withRef:!0})(a.default);t.default=c},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=n(1),i=r(o),a=n(11),s=n(26),l=r(s),u=n(49),c={0:i.default.createElement("strong",null,"0%"),25:"25%",50:"50%",75:"75%",100:{style:{color:"red"},label:i.default.createElement("strong",null,"100%")}},d=function(e,t){return{title:t.actor_name?"Power for "+t.actor_name:"Power for Actor",form:[{label:"Power",name:"power",type:"slider",min:0,max:100,marks:c}]}},f=function(e,t){return{btn_save:function(n){return e((0,u.set_power)(t.actor_id,n.power)),!0},btn_add:function(e){return!0},btn_delete:function(e){return!0}}};t.default=(0,a.connect)(d,f,null,{withRef:!0})(l.default)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var s=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:s(),t=arguments[1];switch(t.type){case"UPDATE_FILES":return o({},e,{files:t.payload});case"SENSOR_DATA_LOADED":return o({},e,{chart_config:o({},e.chart_config,{series:t.payload})});case"UPDATE_ACTIONS":return o({},e,{chart_config:o({},e.chart_config,{xAxis:o({},e.chart_config.xAxis,{plotLines:t.payload})})});default:return e}});t.default=u},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=n(11),i=n(26),a=r(i),s=n(57),l=function(e,t){return{title:"Sensor Modal",bsSize:"large",form:[{label:"Name",name:"name",type:"text"},{label:"Hide on Dashboard",name:"hide",type:"checkbox"},{label:"Type",clear_config:!0,name:"type",type:"select",options:Object.keys(e.sensor.config_type)}],types:e.sensor.config_type,configfield:"type",actors:e.actor.actors,sensors:e.sensor.sensors,kettle:e.kettle.list}},u=function(e,t,n){return{btn_save:function(t){return e((0,s.save)(t)),!0},btn_add:function(t){return e((0,s.add)(t)),!0},btn_delete:function(t){return e((0,s.remove)(t)),!0}}},c=(0,o.connect)(l,u,null,{withRef:!0})(a.default);t.default=c},function(e,t){var n={}.toString;e.exports=function(e){return n.call(e).slice(8,-1)}},function(e,t,n){var r=n(450);e.exports=function(e,t,n){if(r(e),void 0===t)return e;switch(n){case 1:return function(n){return e.call(t,n)};case 2:return function(n,r){return e.call(t,n,r)};case 3:return function(n,r,o){return e.call(t,n,r,o)}}return function(){return e.apply(t,arguments)}}},function(e,t){e.exports=function(e){if(void 0==e)throw TypeError("Can't call method on "+e);return e}},function(e,t){e.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(e,t){e.exports=!0},function(e,t,n){var r=n(60),o=n(466),i=n(142),a=n(148)("IE_PROTO"),s=function(){},l="prototype",u=function(){var e,t=n(232)("iframe"),r=i.length,o="<",a=">";for(t.style.display="none",n(456).appendChild(t),t.src="javascript:",e=t.contentWindow.document,e.open(),e.write(o+"script"+a+"document.F=Object"+o+"/script"+a),e.close(),u=e.F;r--;)delete u[l][i[r]];return u()};e.exports=Object.create||function(e,t){var n;return null!==e?(s[l]=r(e),n=new s,s[l]=null,n[a]=e):n=u(),void 0===t?n:o(n,t)}},function(e,t,n){var r=n(81),o=n(82),i=n(41),a=n(151),s=n(51),l=n(233),u=Object.getOwnPropertyDescriptor;t.f=n(50)?u:function(e,t){if(e=i(e),t=a(t,!0),l)try{return u(e,t)}catch(e){}if(s(e,t))return o(!r.f.call(e,t),e[t])}},function(e,t){t.f=Object.getOwnPropertySymbols},function(e,t,n){var r=n(47).f,o=n(51),i=n(37)("toStringTag");e.exports=function(e,t,n){e&&!o(e=n?e:e.prototype,i)&&r(e,i,{configurable:!0,value:t})}},function(e,t,n){var r=n(149)("keys"),o=n(102);e.exports=function(e){return r[e]||(r[e]=o(e))}},function(e,t,n){var r=n(46),o="__core-js_shared__",i=r[o]||(r[o]={});e.exports=function(e){return i[e]||(i[e]={})}},function(e,t){var n=Math.ceil,r=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?r:n)(e)}},function(e,t,n){var r=n(79);e.exports=function(e,t){if(!r(e))return e;var n,o;if(t&&"function"==typeof(n=e.toString)&&!r(o=n.call(e)))return o;if("function"==typeof(n=e.valueOf)&&!r(o=n.call(e)))return o;if(!t&&"function"==typeof(n=e.toString)&&!r(o=n.call(e)))return o;throw TypeError("Can't convert object to primitive value")}},function(e,t,n){var r=n(46),o=n(25),i=n(143),a=n(153),s=n(47).f;e.exports=function(e){var t=o.Symbol||(o.Symbol=i?{}:r.Symbol||{});"_"==e.charAt(0)||e in t||s(t,e,{value:a.f(e)})}},function(e,t,n){t.f=n(37)},function(e,t){e.exports=function(){var e=[];return e.toString=function(){for(var e=[],t=0;t2&&void 0!==arguments[2]?arguments[2]:{},r="";if(!t)return r;var i=n.indent,a=void 0===i?0:i,l=t.fallbacks;if(a++,l)if(Array.isArray(l))for(var u=0;u0&&void 0!==arguments[0]?arguments[0]:c(),t=arguments[1];switch(t.type){case"LOAD_STATE":return i({},e,{list:t.payload.kettle,config_type:t.payload.controller_types});case"DELETE_KETTLE":return delete e.list[t.id],i({},e,{list:i({},e.list)});case"UPDATE_KETTLE_TARGET_TEMP":return i({},e,{list:i({},e.list,o({},t.payload.id,(0,u.default)(e.list[t.payload.id],{target_temp:{$set:t.payload.target_temp}})))});case"ADD_KETTLE":case"UPDATE_KETTLE":return i({},e,{list:i({},e.list,o({},t.payload.id,t.payload))});default:return e}});t.default=d},function(e,t){e.exports=function(e,t){var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}},function(e,t,n){var r=n(144);e.exports=function(e){return Object(r(e))}},function(e,t){var n=0,r=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++n+r).toString(36))}},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=n(41),i=r(o),a=function(){};i.default&&(a=function(){return document.addEventListener?function(e,t,n,r){return e.addEventListener(t,n,r||!1)}:document.attachEvent?function(e,t,n){return e.attachEvent("on"+t,function(t){t=t||window.event,t.target=t.target||t.srcElement,t.currentTarget=e,n.call(e,t)})}:void 0}()),t.default=a,e.exports=t.default},function(e,t){"use strict";function n(e){return e===e.window?e:9===e.nodeType&&(e.defaultView||e.parentWindow)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n,e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t,n){var r="",o="",i=t;if("string"==typeof t){if(void 0===n)return e.style[(0,a.default)(t)]||(0,c.default)(e).getPropertyValue((0,l.default)(t));(i={})[t]=n}Object.keys(i).forEach(function(t){var n=i[t];n||0===n?(0,m.default)(t)?o+=t+"("+n+") ":r+=(0,l.default)(t)+": "+n+";":(0,f.default)(e,(0,l.default)(t))}),o&&(r+=p.transform+": "+o+";"),e.style.cssText+=";"+r}Object.defineProperty(t,"__esModule",{value:!0}),t.default=o;var i=n(255),a=r(i),s=n(528),l=r(s),u=n(523),c=r(u),d=n(524),f=r(d),p=n(160),h=n(525),m=r(h);e.exports=t.default},function(e,t,n){(function(r){function o(){return"undefined"!=typeof document&&"WebkitAppearance"in document.documentElement.style||window.console&&(console.firebug||console.exception&&console.table)||navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31}function i(){var e=arguments,n=this.useColors;if(e[0]=(n?"%c":"")+this.namespace+(n?" %c":" ")+e[0]+(n?"%c ":" ")+"+"+t.humanize(this.diff),!n)return e;var r="color: "+this.color;e=[e[0],r,"color: inherit"].concat(Array.prototype.slice.call(e,1));var o=0,i=0;return e[0].replace(/%[a-z%]/g,function(e){"%%"!==e&&(o++,"%c"===e&&(i=o))}),e.splice(i,0,r),e}function a(){return"object"==typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}function s(e){try{null==e?t.storage.removeItem("debug"):t.storage.debug=e}catch(e){}}function l(){try{return t.storage.debug}catch(e){}if("undefined"!=typeof r&&"env"in r)return r.env.DEBUG}function u(){try{return window.localStorage}catch(e){}}t=e.exports=n(536),t.log=a,t.formatArgs=i,t.save=s,t.load=l,t.useColors=o,t.storage="undefined"!=typeof chrome&&"undefined"!=typeof chrome.storage?chrome.storage.local:u(),t.colors=["lightseagreen","forestgreen","goldenrod","dodgerblue","darkorchid","crimson"],t.formatters.j=function(e){try{return JSON.stringify(e)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}},t.enable(l())}).call(t,n(2))},function(e,t,n){(function(t){"use strict";var n={};"production"!==t.env.NODE_ENV&&Object.freeze(n),e.exports=n}).call(t,n(2))},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0,t.locationsAreEqual=t.createLocation=void 0;var o=Object.assign||function(e){for(var t=1;t` components. To apply a ref to the component use the callback signature:\n\n https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute"):void 0:o=(0,Y.default)(e.ref,o),(0,w.cloneElement)(e,(0,l.default)({},t,{ref:o,bsClass:(0,q.prefix)(t,"toggle"),onClick:(0,Y.default)(e.props.onClick,this.handleClick),onKeyDown:(0,Y.default)(e.props.onKeyDown,this.handleKeyDown)}))},t.prototype.renderMenu=function(e,t){var n=this,o=t.id,i=t.onClose,s=t.onSelect,u=t.rootCloseEvent,c=(0,a.default)(t,["id","onClose","onSelect","rootCloseEvent"]),d=function(e){n.menu=e};return"string"==typeof e.ref?"production"!==r.env.NODE_ENV?(0,B.default)(!1,"String refs are not supported on `` components. To apply a ref to the component use the callback signature:\n\n https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute"):void 0:d=(0,Y.default)(e.ref,d),(0,w.cloneElement)(e,(0,l.default)({},c,{ref:d,labelledBy:o,bsClass:(0,q.prefix)(c,"menu"),onClose:(0,Y.default)(e.props.onClose,i,this.handleClose),onSelect:(0,Y.default)(e.props.onSelect,s,function(e,t){return n.handleClose(t,{source:"select"})}),rootCloseEvent:u}))},t.prototype.render=function(){var e,t=this,n=this.props,r=n.componentClass,o=n.id,i=n.dropup,s=n.disabled,u=n.pullRight,c=n.open,d=n.onClose,f=n.onSelect,p=n.role,h=n.bsClass,m=n.className,y=n.rootCloseEvent,g=n.children,b=(0,a.default)(n,["componentClass","id","dropup","disabled","pullRight","open","onClose","onSelect","role","bsClass","className","rootCloseEvent","children"]);delete b.onToggle;var x=(e={},e[h]=!0,e.open=c,e.disabled=s,e);return i&&(x[h]=!1,x.dropup=!0),O.default.createElement(r,(0,l.default)({},b,{className:(0,v.default)(m,x)}),$.default.map(g,function(e){switch(e.props.bsRole){case Z:return t.renderToggle(e,{id:o,disabled:s,open:c,role:p,bsClass:h});case J:return t.renderMenu(e,{id:o,open:c,pullRight:u,bsClass:h,onClose:d,onSelect:f,rootCloseEvent:y});default:return e}}))},t}(O.default.Component);te.propTypes=Q,te.defaultProps=ee,(0,q.bsClass)("dropdown",te);var ne=(0,I.default)(te,{open:"onToggle"});ne.Toggle=W.default,ne.Menu=H.default,t.default=ne,e.exports=t.default}).call(t,n(2))},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(4),i=r(o),a=n(5),s=r(a),l=n(7),u=r(l),c=n(6),d=r(c),f=n(9),p=r(f),h=n(1),m=r(h),v=n(3),y=r(v),g=n(345),b=r(g),x={in:y.default.bool,mountOnEnter:y.default.bool,unmountOnExit:y.default.bool,transitionAppear:y.default.bool,timeout:y.default.number,onEnter:y.default.func,onEntering:y.default.func,onEntered:y.default.func,onExit:y.default.func,onExiting:y.default.func,onExited:y.default.func},E={in:!1,timeout:300,mountOnEnter:!1,unmountOnExit:!1,transitionAppear:!1},_=function(e){function t(){return(0,s.default)(this,t),(0,u.default)(this,e.apply(this,arguments))}return(0,d.default)(t,e),t.prototype.render=function(){return m.default.createElement(b.default,(0,i.default)({},this.props,{className:(0,p.default)(this.props.className,"fade"),enteredClassName:"in",enteringClassName:"in"}))},t}(m.default.Component);_.propTypes=x,_.defaultProps=E,t.default=_,e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){var n=t.propTypes,r={},o={};return(0,a.default)(e).forEach(function(e){var t=e[0],i=e[1];n[t]?r[t]=i:o[t]=i}),[r,o]}t.__esModule=!0;var i=n(231),a=r(i);t.default=o,e.exports=t.default},function(e,t,n){(function(t){"use strict";function r(){if(l)for(var e in u){var n=u[e],r=l.indexOf(e);if(r>-1?void 0:"production"!==t.env.NODE_ENV?s(!1,"EventPluginRegistry: Cannot inject event plugins that do not exist in the plugin ordering, `%s`.",e):a("96",e),!c.plugins[r]){n.extractEvents?void 0:"production"!==t.env.NODE_ENV?s(!1,"EventPluginRegistry: Event plugins must implement an `extractEvents` method, but `%s` does not.",e):a("97",e),c.plugins[r]=n;var i=n.eventTypes;for(var d in i)o(i[d],n,d)?void 0:"production"!==t.env.NODE_ENV?s(!1,"EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.",d,e):a("98",d,e)}}}function o(e,n,r){c.eventNameDispatchConfigs.hasOwnProperty(r)?"production"!==t.env.NODE_ENV?s(!1,"EventPluginHub: More than one plugin attempted to publish the same event name, `%s`.",r):a("99",r):void 0,c.eventNameDispatchConfigs[r]=e;var o=e.phasedRegistrationNames;if(o){for(var l in o)if(o.hasOwnProperty(l)){var u=o[l];i(u,n,r)}return!0}return!!e.registrationName&&(i(e.registrationName,n,r),!0)}function i(e,n,r){if(c.registrationNameModules[e]?"production"!==t.env.NODE_ENV?s(!1,"EventPluginHub: More than one plugin attempted to publish the same registration name, `%s`.",e):a("100",e):void 0,c.registrationNameModules[e]=n,c.registrationNameDependencies[e]=n.eventTypes[r].dependencies,"production"!==t.env.NODE_ENV){var o=e.toLowerCase();c.possibleRegistrationNames[o]=e,"onDoubleClick"===e&&(c.possibleRegistrationNames.ondblclick=e)}}var a=n(16),s=n(12),l=null,u={},c={plugins:[],eventNameDispatchConfigs:{},registrationNameModules:{},registrationNameDependencies:{},possibleRegistrationNames:"production"!==t.env.NODE_ENV?{}:null,injectEventPluginOrder:function(e){l?"production"!==t.env.NODE_ENV?s(!1,"EventPluginRegistry: Cannot inject event plugin ordering more than once. You are likely trying to load more than one copy of React."):a("101"):void 0,l=Array.prototype.slice.call(e),r()},injectEventPluginsByName:function(e){var n=!1;for(var o in e)if(e.hasOwnProperty(o)){var i=e[o];u.hasOwnProperty(o)&&u[o]===i||(u[o]?"production"!==t.env.NODE_ENV?s(!1,"EventPluginRegistry: Cannot inject two different event plugins using the same name, `%s`.",o):a("102",o):void 0,u[o]=i,n=!0)}n&&r()},getPluginModuleForEvent:function(e){var t=e.dispatchConfig;if(t.registrationName)return c.registrationNameModules[t.registrationName]||null;if(void 0!==t.phasedRegistrationNames){var n=t.phasedRegistrationNames;for(var r in n)if(n.hasOwnProperty(r)){var o=c.registrationNameModules[n[r]];if(o)return o}}return null},_resetEventPlugins:function(){l=null;for(var e in u)u.hasOwnProperty(e)&&delete u[e];c.plugins.length=0;var n=c.eventNameDispatchConfigs;for(var r in n)n.hasOwnProperty(r)&&delete n[r];var o=c.registrationNameModules;for(var i in o)o.hasOwnProperty(i)&&delete o[i];if("production"!==t.env.NODE_ENV){var a=c.possibleRegistrationNames;for(var s in a)a.hasOwnProperty(s)&&delete a[s]}}};e.exports=c}).call(t,n(2))},function(e,t,n){"use strict";function r(e){return Object.prototype.hasOwnProperty.call(e,m)||(e[m]=p++,d[e[m]]={}),d[e[m]]}var o,i=n(19),a=n(123),s=n(831),l=n(330),u=n(866),c=n(200),d={},f=!1,p=0,h={topAbort:"abort",topAnimationEnd:u("animationend")||"animationend",topAnimationIteration:u("animationiteration")||"animationiteration",topAnimationStart:u("animationstart")||"animationstart",topBlur:"blur",topCanPlay:"canplay",topCanPlayThrough:"canplaythrough",topChange:"change",topClick:"click",topCompositionEnd:"compositionend",topCompositionStart:"compositionstart",topCompositionUpdate:"compositionupdate",topContextMenu:"contextmenu",topCopy:"copy",topCut:"cut",topDoubleClick:"dblclick",topDrag:"drag",topDragEnd:"dragend",topDragEnter:"dragenter",topDragExit:"dragexit",topDragLeave:"dragleave",topDragOver:"dragover",topDragStart:"dragstart",topDrop:"drop",topDurationChange:"durationchange",topEmptied:"emptied",topEncrypted:"encrypted",topEnded:"ended",topError:"error",topFocus:"focus",topInput:"input",topKeyDown:"keydown",topKeyPress:"keypress",topKeyUp:"keyup",topLoadedData:"loadeddata",topLoadedMetadata:"loadedmetadata",topLoadStart:"loadstart",topMouseDown:"mousedown",topMouseMove:"mousemove",topMouseOut:"mouseout",topMouseOver:"mouseover",topMouseUp:"mouseup",topPaste:"paste",topPause:"pause",topPlay:"play",topPlaying:"playing",topProgress:"progress",topRateChange:"ratechange",topScroll:"scroll",topSeeked:"seeked",topSeeking:"seeking",topSelectionChange:"selectionchange",topStalled:"stalled",topSuspend:"suspend",topTextInput:"textInput",topTimeUpdate:"timeupdate",topTouchCancel:"touchcancel",topTouchEnd:"touchend",topTouchMove:"touchmove",topTouchStart:"touchstart",topTransitionEnd:u("transitionend")||"transitionend",topVolumeChange:"volumechange",topWaiting:"waiting",topWheel:"wheel"},m="_reactListenersID"+String(Math.random()).slice(2),v=i({},s,{ReactEventListener:null,injection:{injectReactEventListener:function(e){e.setHandleTopLevel(v.handleTopLevel),v.ReactEventListener=e}},setEnabled:function(e){v.ReactEventListener&&v.ReactEventListener.setEnabled(e)},isEnabled:function(){return!(!v.ReactEventListener||!v.ReactEventListener.isEnabled())},listenTo:function(e,t){for(var n=t,o=r(n),i=a.registrationNameDependencies[e],s=0;s]/;e.exports=r},function(e,t,n){"use strict";var r,o=n(22),i=n(189),a=/^[ \r\n\t\f]/,s=/<(!--|link|noscript|meta|script|style)[ \r\n\t\f\/>]/,l=n(196),u=l(function(e,t){ +if(e.namespaceURI!==i.svg||"innerHTML"in e)e.innerHTML=t;else{r=r||document.createElement("div"),r.innerHTML=""+t+"";for(var n=r.firstChild;n.firstChild;)e.appendChild(n.firstChild)}});if(o.canUseDOM){var c=document.createElement("div");c.innerHTML=" ",""===c.innerHTML&&(u=function(e,t){if(e.parentNode&&e.parentNode.replaceChild(e,e),a.test(t)||"<"===t[0]&&s.test(t)){e.innerHTML=String.fromCharCode(65279)+t;var n=e.firstChild;1===n.data.length?e.removeChild(n):n.deleteData(0,1)}else e.innerHTML=t}),c=null}e.exports=u},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(){function e(){for(var e=arguments.length,t=Array(e),r=0;r>",s=i||n;if(null==t[n])return new Error("The "+o+" `"+s+"` is required to make "+("`"+a+"` accessible for users of assistive ")+"technologies such as screen readers.");for(var l=arguments.length,u=Array(l>5?l-5:0),c=5;c>",l=a||r;if(null==n[r])return t?new Error("Required "+i+" `"+l+"` was not specified "+("in `"+s+"`.")):null;for(var u=arguments.length,c=Array(u>6?u-6:0),d=6;d=r.length)for(var o=n-r.length;o--+1;)r.push(void 0);return r.splice(n,0,r.splice(t,1)[0]),r}function r(e){for(var t=arguments.length,n=Array(t>1?t-1:0),r=1;rt?t:n}function a(e){return"px"===e.substr(-2)?parseFloat(e):0}function s(e){var t=window.getComputedStyle(e);return{top:a(t.marginTop),right:a(t.marginRight),bottom:a(t.marginBottom),left:a(t.marginLeft)}}function l(e,t){var n=t.displayName||t.name;return n?e+"("+n+")":e}Object.defineProperty(t,"__esModule",{value:!0}),t.arrayMove=n,t.omit=r,t.closest=o,t.limit=i,t.getElementMargin=s,t.provideDisplayName=l;t.events={start:["touchstart","mousedown"],move:["touchmove","mousemove"],end:["touchend","touchcancel","mouseup"]},t.vendorPrefix=function(){if("undefined"==typeof window||"undefined"==typeof document)return"";var e=window.getComputedStyle(document.documentElement,"")||["-moz-hidden-iframe"],t=(Array.prototype.slice.call(e).join("").match(/-(moz|webkit|ms)-/)||""===e.OLink&&["","o"])[1];switch(t){case"ms":return"ms";default:return t&&t.length?t[0].toUpperCase()+t.substr(1):""}}()},function(e,t,n){(function(t){"use strict";var n=!1;if("production"!==t.env.NODE_ENV)try{Object.defineProperty({},"x",{get:function(){}}),n=!0}catch(e){}e.exports=n}).call(t,n(2))},function(e,t,n){(function(r){function o(){return"undefined"!=typeof document&&"WebkitAppearance"in document.documentElement.style||window.console&&(console.firebug||console.exception&&console.table)||navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31}function i(){var e=arguments,n=this.useColors;if(e[0]=(n?"%c":"")+this.namespace+(n?" %c":" ")+e[0]+(n?"%c ":" ")+"+"+t.humanize(this.diff),!n)return e;var r="color: "+this.color;e=[e[0],r,"color: inherit"].concat(Array.prototype.slice.call(e,1));var o=0,i=0;return e[0].replace(/%[a-z%]/g,function(e){"%%"!==e&&(o++,"%c"===e&&(i=o))}),e.splice(i,0,r),e}function a(){return"object"==typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}function s(e){try{null==e?t.storage.removeItem("debug"):t.storage.debug=e}catch(e){}}function l(){try{return t.storage.debug}catch(e){}if("undefined"!=typeof r&&"env"in r)return r.env.DEBUG}function u(){try{return window.localStorage}catch(e){}}t=e.exports=n(971),t.log=a,t.formatArgs=i,t.save=s,t.load=l,t.useColors=o,t.storage="undefined"!=typeof chrome&&"undefined"!=typeof chrome.storage?chrome.storage.local:u(),t.colors=["lightseagreen","forestgreen","goldenrod","dodgerblue","darkorchid","crimson"],t.formatters.j=function(e){try{return JSON.stringify(e)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}},t.enable(l())}).call(t,n(2))},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t,n,r,o){n&&(e._notifying=!0,n.call.apply(n,[e,r].concat(o)),e._notifying=!1),e._values[t]=r,e.unmounted||e.forceUpdate()}t.__esModule=!0;var i=n(987),a=r(i),s={shouldComponentUpdate:function(){return!this._notifying}};t.default=(0,a.default)(s,o),e.exports=t.default},function(e,t,n){(function(t){"use strict";function r(e,t){!i.isUndefined(e)&&i.isUndefined(e["Content-Type"])&&(e["Content-Type"]=t)}function o(){var e;return"undefined"!=typeof XMLHttpRequest?e=n(214):"undefined"!=typeof t&&(e=n(214)),e}var i=n(32),a=n(391),s={"Content-Type":"application/x-www-form-urlencoded"},l={adapter:o(),transformRequest:[function(e,t){return a(t,"Content-Type"),i.isFormData(e)||i.isArrayBuffer(e)||i.isBuffer(e)||i.isStream(e)||i.isFile(e)||i.isBlob(e)?e:i.isArrayBufferView(e)?e.buffer:i.isURLSearchParams(e)?(r(t,"application/x-www-form-urlencoded;charset=utf-8"),e.toString()):i.isObject(e)?(r(t,"application/json;charset=utf-8"),JSON.stringify(e)):e}],transformResponse:[function(e){if("string"==typeof e)try{e=JSON.parse(e)}catch(e){}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,validateStatus:function(e){return e>=200&&e<300}};l.headers={common:{Accept:"application/json, text/plain, */*"}},i.forEach(["delete","get","head"],function(e){l.headers[e]={}}),i.forEach(["post","put","patch"],function(e){l.headers[e]=i.merge(s)}),e.exports=l}).call(t,n(2))},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=n(11),i=n(27),a=r(i),s=n(50),l=function(e,t){return{title:"Actor Modal",bsSize:"large",form:[{label:"Name",name:"name",type:"text"},{label:"Hide on Dashboard",name:"hide",type:"checkbox"},{label:"Type",clear_config:!0,name:"type",type:"select",options:Object.keys(e.actor.config_type)}],types:e.actor.config_type,configfield:"type",actors:e.actor.actors,sensors:e.sensor.sensors,kettle:e.kettle.list}},u=function(e,t){return{btn_save:function(t){return e((0,s.save)(t)),!0},btn_add:function(t){return e((0,s.add)(t)),!0},btn_delete:function(t){return e((0,s.remove)(t)),!0}}},c=(0,o.connect)(l,u,null,{withRef:!0})(a.default);t.default=c},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=n(1),i=r(o),a=n(11),s=n(27),l=r(s),u=n(50),c={0:i.default.createElement("strong",null,"0%"),25:"25%",50:"50%",75:"75%",100:{style:{color:"red"},label:i.default.createElement("strong",null,"100%")}},d=function(e,t){return{title:t.actor_name?"Power for "+t.actor_name:"Power for Actor",form:[{label:"Power",name:"power",type:"slider",min:0,max:100,marks:c}]}},f=function(e,t){return{btn_save:function(n){return e((0,u.set_power)(t.actor_id,n.power)),!0},btn_add:function(e){return!0},btn_delete:function(e){return!0}}};t.default=(0,a.connect)(d,f,null,{withRef:!0})(l.default)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var s=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:s(),t=arguments[1];switch(t.type){case"UPDATE_FILES":return o({},e,{files:t.payload});case"REMOVE_FILE":return o({},e,{files:e.files.filter(function(e,n){return e!==t.file})});case"SENSOR_DATA_LOADED":return o({},e,{chart_config:o({},e.chart_config,{series:t.payload})});case"UPDATE_ACTIONS":return o({},e,{chart_config:o({},e.chart_config,{xAxis:o({},e.chart_config.xAxis,{plotLines:t.payload})})});default:return e}});t.default=u},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=n(11),i=n(27),a=r(i),s=n(59),l=function(e,t){return{title:"Sensor Modal",bsSize:"large",form:[{label:"Name",name:"name",type:"text"},{label:"Type",clear_config:!0,name:"type",type:"select",options:Object.keys(e.sensor.config_type)},{label:"Hide on Dashboard",name:"hide",type:"checkbox"}],types:e.sensor.config_type,configfield:"type",actors:e.actor.actors,sensors:e.sensor.sensors,kettle:e.kettle.list}},u=function(e,t,n){return{btn_save:function(t){return e((0,s.save)(t)),!0},btn_add:function(t){return e((0,s.add)(t)),!0},btn_delete:function(t){return e((0,s.remove)(t)),!0}}},c=(0,o.connect)(l,u,null,{withRef:!0})(a.default);t.default=c},function(e,t){var n={}.toString;e.exports=function(e){return n.call(e).slice(8,-1)}},function(e,t,n){var r=n(462);e.exports=function(e,t,n){if(r(e),void 0===t)return e;switch(n){case 1:return function(n){return e.call(t,n)};case 2:return function(n,r){return e.call(t,n,r)};case 3:return function(n,r,o){return e.call(t,n,r,o)}}return function(){return e.apply(t,arguments)}}},function(e,t){e.exports=function(e){if(void 0==e)throw TypeError("Can't call method on "+e);return e}},function(e,t){e.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(e,t){e.exports=!0},function(e,t,n){var r=n(63),o=n(477),i=n(145),a=n(151)("IE_PROTO"),s=function(){},l="prototype",u=function(){var e,t=n(235)("iframe"),r=i.length,o="<",a=">";for(t.style.display="none",n(468).appendChild(t),t.src="javascript:",e=t.contentWindow.document,e.open(),e.write(o+"script"+a+"document.F=Object"+o+"/script"+a),e.close(),u=e.F;r--;)delete u[l][i[r]];return u()};e.exports=Object.create||function(e,t){var n;return null!==e?(s[l]=r(e),n=new s,s[l]=null,n[a]=e):n=u(),void 0===t?n:o(n,t)}},function(e,t,n){var r=n(85),o=n(86),i=n(48),a=n(154),s=n(53),l=n(236),u=Object.getOwnPropertyDescriptor;t.f=n(52)?u:function(e,t){if(e=i(e),t=a(t,!0),l)try{return u(e,t)}catch(e){}if(s(e,t))return o(!r.f.call(e,t),e[t])}},function(e,t){t.f=Object.getOwnPropertySymbols},function(e,t,n){var r=n(47).f,o=n(53),i=n(37)("toStringTag");e.exports=function(e,t,n){e&&!o(e=n?e:e.prototype,i)&&r(e,i,{configurable:!0,value:t})}},function(e,t,n){var r=n(152)("keys"),o=n(104);e.exports=function(e){return r[e]||(r[e]=o(e))}},function(e,t,n){var r=n(46),o="__core-js_shared__",i=r[o]||(r[o]={});e.exports=function(e){return i[e]||(i[e]={})}},function(e,t){var n=Math.ceil,r=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?r:n)(e)}},function(e,t,n){var r=n(82);e.exports=function(e,t){if(!r(e))return e;var n,o;if(t&&"function"==typeof(n=e.toString)&&!r(o=n.call(e)))return o;if("function"==typeof(n=e.valueOf)&&!r(o=n.call(e)))return o;if(!t&&"function"==typeof(n=e.toString)&&!r(o=n.call(e)))return o;throw TypeError("Can't convert object to primitive value")}},function(e,t,n){var r=n(46),o=n(26),i=n(146),a=n(156),s=n(47).f;e.exports=function(e){var t=o.Symbol||(o.Symbol=i?{}:r.Symbol||{});"_"==e.charAt(0)||e in t||s(t,e,{value:a.f(e)})}},function(e,t,n){t.f=n(37)},function(e,t){e.exports=function(){var e=[];return e.toString=function(){for(var e=[],t=0;t2&&void 0!==arguments[2]?arguments[2]:{},r="";if(!t)return r;var i=n.indent,a=void 0===i?0:i,l=t.fallbacks;if(a++,l)if(Array.isArray(l))for(var u=0;u-1&&e%1==0&&e-1&&e%1==0&&e<=r}var r=9007199254740991;e.exports=n},function(e,t,n){function r(e){if(!a(e)||o(e)!=s)return!1;var t=i(e);if(null===t)return!0;var n=d.call(t,"constructor")&&t.constructor;return"function"==typeof n&&n instanceof n&&c.call(n)==f}var o=n(67),i=n(630),a=n(68),s="[object Object]",l=Function.prototype,u=Object.prototype,c=l.toString,d=u.hasOwnProperty,f=c.call(Object);e.exports=r},function(e,t){t.encode=function(e){var t="";for(var n in e)e.hasOwnProperty(n)&&(t.length&&(t+="&"),t+=encodeURIComponent(n)+"="+encodeURIComponent(e[n]));return t},t.decode=function(e){for(var t={},n=e.split("&"),r=0,o=n.length;rr}function a(e){return e.touches.length>1||"touchend"===e.type.toLowerCase()&&e.touches.length>0}function s(e,t){var n=t.marks,r=t.step,o=t.min,i=Object.keys(n).map(parseFloat);if(null!==r){var a=Math.round((e-o)/r)*r+o;i.push(a)}var s=i.map(function(t){return Math.abs(e-t)});return i[s.indexOf(Math.min.apply(Math,(0,v.default)(s)))]}function l(e){var t=e.toString(),n=0;return t.indexOf(".")>=0&&(n=t.length-t.indexOf(".")-1),n}function u(e,t){return e?t.clientY:t.pageX}function c(e,t){return e?t.touches[0].clientY:t.touches[0].pageX}function d(e,t){var n=t.getBoundingClientRect();return e?n.top+.5*n.height:n.left+.5*n.width}function f(e,t){var n=t.max,r=t.min;return e<=r?r:e>=n?n:e}function p(e,t){var n=t.step,r=s(e,t);return null===n?r:parseFloat(r.toFixed(l(n)))}function h(e){e.stopPropagation(),e.preventDefault()}Object.defineProperty(t,"__esModule",{value:!0});var m=n(229),v=r(m);t.isEventFromHandle=o,t.isValueOutOfRange=i,t.isNotTouchEvent=a,t.getClosestPoint=s,t.getPrecision=l,t.getMousePosition=u,t.getTouchPosition=c,t.getHandleCenterPosition=d,t.ensureValueInRange=f,t.ensureValuePrecision=p,t.pauseEvent=h;var y=n(18)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t,n){var r=l.default.unstable_batchedUpdates?function(e){l.default.unstable_batchedUpdates(n,e)}:n;return(0,a.default)(e,t,r)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=o;var i=n(363),a=r(i),s=n(18),l=r(s);e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e){e.offsetHeight}function i(e,t){var n=t["offset"+(0,T.default)(e)],r=S[e];return n+parseInt((0,b.default)(t,r[0]),10)+parseInt((0,b.default)(t,r[1]),10)}t.__esModule=!0;var a=n(4),s=r(a),l=n(8),u=r(l),c=n(5),d=r(c),f=n(7),p=r(f),h=n(6),m=r(h),v=n(9),y=r(v),g=n(105),b=r(g),x=n(1),E=r(x),_=n(3),w=r(_),C=n(337),O=r(C),k=n(309),T=r(k),N=n(23),P=r(N),S={height:["marginTop","marginBottom"],width:["marginLeft","marginRight"]},M={in:w.default.bool,mountOnEnter:w.default.bool,unmountOnExit:w.default.bool,transitionAppear:w.default.bool,timeout:w.default.number,onEnter:w.default.func,onEntering:w.default.func,onEntered:w.default.func,onExit:w.default.func,onExiting:w.default.func,onExited:w.default.func,dimension:w.default.oneOfType([w.default.oneOf(["height","width"]),w.default.func]),getDimensionValue:w.default.func,role:w.default.string},A={in:!1,timeout:300,mountOnEnter:!1,unmountOnExit:!1,transitionAppear:!1,dimension:"height",getDimensionValue:i},D=function(e){function t(n,r){(0,d.default)(this,t);var o=(0,p.default)(this,e.call(this,n,r));return o.handleEnter=o.handleEnter.bind(o),o.handleEntering=o.handleEntering.bind(o),o.handleEntered=o.handleEntered.bind(o),o.handleExit=o.handleExit.bind(o),o.handleExiting=o.handleExiting.bind(o),o}return(0,m.default)(t,e),t.prototype.handleEnter=function(e){var t=this._dimension();e.style[t]="0"},t.prototype.handleEntering=function(e){var t=this._dimension();e.style[t]=this._getScrollDimensionValue(e,t)},t.prototype.handleEntered=function(e){var t=this._dimension();e.style[t]=null},t.prototype.handleExit=function(e){var t=this._dimension();e.style[t]=this.props.getDimensionValue(t,e)+"px",o(e)},t.prototype.handleExiting=function(e){var t=this._dimension();e.style[t]="0"},t.prototype._dimension=function(){return"function"==typeof this.props.dimension?this.props.dimension():this.props.dimension},t.prototype._getScrollDimensionValue=function(e,t){return e["scroll"+(0,T.default)(t)]+"px"},t.prototype.render=function(){var e=this.props,t=e.onEnter,n=e.onEntering,r=e.onEntered,o=e.onExit,i=e.onExiting,a=e.className,l=(0,u.default)(e,["onEnter","onEntering","onEntered","onExit","onExiting","className"]);delete l.dimension,delete l.getDimensionValue;var c=(0,P.default)(this.handleEnter,t),d=(0,P.default)(this.handleEntering,n),f=(0,P.default)(this.handleEntered,r),p=(0,P.default)(this.handleExit,o),h=(0,P.default)(this.handleExiting,i),m={width:"width"===this._dimension()};return E.default.createElement(O.default,(0,s.default)({},l,{"aria-expanded":l.role?l.in:null,className:(0,y.default)(a,m),exitedClassName:"collapse",exitingClassName:"collapsing",enteredClassName:"collapse in",enteringClassName:"collapsing",onEnter:c,onEntering:d,onEntered:f,onExit:p,onExiting:h}))},t}(E.default.Component);D.propTypes=M,D.defaultProps=A,t.default=D,e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(4),i=r(o),a=n(8),s=r(a),l=n(5),u=r(l),c=n(7),d=r(c),f=n(6),p=r(f),h=n(9),m=r(h),v=n(1),y=r(v),g=n(3),b=r(g),x=n(10),E={glyph:b.default.string.isRequired},_=function(e){function t(){return(0,u.default)(this,t),(0,d.default)(this,e.apply(this,arguments))}return(0,p.default)(t,e),t.prototype.render=function(){var e,t=this.props,n=t.glyph,r=t.className,o=(0,s.default)(t,["glyph","className"]),a=(0,x.splitBsProps)(o),l=a[0],u=a[1],c=(0,i.default)({},(0,x.getClassSet)(l),(e={},e[(0,x.prefix)(l,n)]=!0,e));return y.default.createElement("span",(0,i.default)({},u,{className:(0,m.default)(r,c)}))},t}(y.default.Component);_.propTypes=E,t.default=(0,x.bsClass)("glyphicon",_),e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(4),i=r(o),a=n(8),s=r(a),l=n(5),u=r(l),c=n(7),d=r(c),f=n(6),p=r(f),h=n(9),m=r(h),v=n(1),y=r(v),g=n(16),b=r(g),x=n(740),E=r(x),_=n(741),w=r(_),C=n(742),O=r(C),k=n(743),T=r(k),N=n(744),P=r(N),S=n(745),M=r(S),A=n(10),D={componentClass:b.default},R={componentClass:"div"},j=function(e){function t(){return(0,u.default)(this,t),(0,d.default)(this,e.apply(this,arguments))}return(0,p.default)(t,e),t.prototype.render=function(){var e=this.props,t=e.componentClass,n=e.className,r=(0,s.default)(e,["componentClass","className"]),o=(0,A.splitBsProps)(r),a=o[0],l=o[1],u=(0,A.getClassSet)(a);return y.default.createElement(t,(0,i.default)({},l,{className:(0,m.default)(n,u)}))},t}(y.default.Component);j.propTypes=D,j.defaultProps=R,j.Heading=w.default,j.Body=E.default,j.Left=O.default,j.Right=M.default,j.List=T.default,j.ListItem=P.default,t.default=(0,A.bsClass)("media",j),e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(8),i=r(o),a=n(5),s=r(a),l=n(7),u=r(l),c=n(6),d=r(c),f=n(1),p=r(f),h=n(3),m=r(h),v=n(132),y=r(v),g="tab",b="pane",x=m.default.oneOfType([m.default.string,m.default.number]),E={id:function(e){var t=null;if(!e.generateChildId){for(var n=arguments.length,r=Array(n>1?n-1:0),o=1;o0&&r.length<20?n+" (keys: "+r.join(", ")+")":n}function i(e,n){var r=l.get(e);if(!r){if("production"!==t.env.NODE_ENV){var o=e.constructor;"production"!==t.env.NODE_ENV?f(!n,"%s(...): Can only update a mounted or mounting component. This usually means you called %s() on an unmounted component. This is a no-op. Please check the code for the %s component.",n,n,o&&(o.displayName||o.name)||"ReactClass"):void 0}return null}return"production"!==t.env.NODE_ENV&&("production"!==t.env.NODE_ENV?f(null==s.current,"%s(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.",n):void 0),r}var a=n(15),s=n(40),l=n(94),u=n(33),c=n(39),d=n(12),f=n(13),p={isMounted:function(e){if("production"!==t.env.NODE_ENV){var n=s.current;null!==n&&("production"!==t.env.NODE_ENV?f(n._warnedAboutRefsInRender,"%s is accessing isMounted inside its render() function. render() should be a pure function of props and state. It should never access something that requires stale data from the previous render, such as refs. Move this logic to componentDidMount and componentDidUpdate instead.",n.getName()||"A component"):void 0,n._warnedAboutRefsInRender=!0)}var r=l.get(e);return!!r&&!!r._renderedComponent},enqueueCallback:function(e,t,n){p.validateCallback(t,n);var o=i(e);return o?(o._pendingCallbacks?o._pendingCallbacks.push(t):o._pendingCallbacks=[t],void r(o)):null},enqueueCallbackInternal:function(e,t){e._pendingCallbacks?e._pendingCallbacks.push(t):e._pendingCallbacks=[t],r(e)},enqueueForceUpdate:function(e){var t=i(e,"forceUpdate");t&&(t._pendingForceUpdate=!0,r(t))},enqueueReplaceState:function(e,t,n){var o=i(e,"replaceState");o&&(o._pendingStateQueue=[t],o._pendingReplaceState=!0,void 0!==n&&null!==n&&(p.validateCallback(n,"replaceState"),o._pendingCallbacks?o._pendingCallbacks.push(n):o._pendingCallbacks=[n]),r(o))},enqueueSetState:function(e,n){"production"!==t.env.NODE_ENV&&(u.debugTool.onSetState(),"production"!==t.env.NODE_ENV?f(null!=n,"setState(...): You passed an undefined or null state object; instead, use forceUpdate()."):void 0);var o=i(e,"setState");if(o){var a=o._pendingStateQueue||(o._pendingStateQueue=[]);a.push(n),r(o)}},enqueueElementInternal:function(e,t,n){e._pendingElement=t,e._context=n,r(e)},validateCallback:function(e,n){e&&"function"!=typeof e?"production"!==t.env.NODE_ENV?d(!1,"%s(...): Expected the last optional `callback` argument to be a function. Instead received: %s.",n,o(e)):a("122",n,o(e)):void 0}};e.exports=p}).call(t,n(2))},function(e,t){"use strict";var n=function(e){return"undefined"!=typeof MSApp&&MSApp.execUnsafeLocalFunction?function(t,n,r,o){MSApp.execUnsafeLocalFunction(function(){return e(t,n,r,o)})}:e};e.exports=n},function(e,t){"use strict";function n(e){var t,n=e.keyCode;return"charCode"in e?(t=e.charCode,0===t&&13===n&&(t=13)):t=n,t>=32||13===t?t:0}e.exports=n},function(e,t){"use strict";function n(e){var t=this,n=t.nativeEvent;if(n.getModifierState)return n.getModifierState(e);var r=o[e];return!!r&&!!n[r]}function r(e){return n}var o={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};e.exports=r},function(e,t){"use strict";function n(e){var t=e.target||e.srcElement||window;return t.correspondingUseElement&&(t=t.correspondingUseElement),3===t.nodeType?t.parentNode:t}e.exports=n},function(e,t,n){"use strict";/** +for(o=97;o<123;o++)n[String.fromCharCode(o)]=o-32;for(var o=48;o<58;o++)n[o-48]=o;for(o=1;o<13;o++)n["f"+o]=o+111;for(o=0;o<10;o++)n["numpad "+o]=o+96;var i=t.names=t.title={};for(o in n)i[n[o]]=o;for(var a in r)n[a]=r[a]},function(e,t,n){var r=n(54),o=n(42),i=r(o,"Map");e.exports=i},function(e,t,n){function r(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t-1&&e%1==0&&e-1&&e%1==0&&e<=r}var r=9007199254740991;e.exports=n},function(e,t,n){function r(e){if(!a(e)||o(e)!=s)return!1;var t=i(e);if(null===t)return!0;var n=d.call(t,"constructor")&&t.constructor;return"function"==typeof n&&n instanceof n&&c.call(n)==f}var o=n(70),i=n(644),a=n(71),s="[object Object]",l=Function.prototype,u=Object.prototype,c=l.toString,d=u.hasOwnProperty,f=c.call(Object);e.exports=r},function(e,t){t.encode=function(e){var t="";for(var n in e)e.hasOwnProperty(n)&&(t.length&&(t+="&"),t+=encodeURIComponent(n)+"="+encodeURIComponent(e[n]));return t},t.decode=function(e){for(var t={},n=e.split("&"),r=0,o=n.length;rr}function a(e){return e.touches.length>1||"touchend"===e.type.toLowerCase()&&e.touches.length>0}function s(e,t){var n=t.marks,r=t.step,o=t.min,i=Object.keys(n).map(parseFloat);if(null!==r){var a=Math.round((e-o)/r)*r+o;i.push(a)}var s=i.map(function(t){return Math.abs(e-t)});return i[s.indexOf(Math.min.apply(Math,(0,v.default)(s)))]}function l(e){var t=e.toString(),n=0;return t.indexOf(".")>=0&&(n=t.length-t.indexOf(".")-1),n}function u(e,t){return e?t.clientY:t.pageX}function c(e,t){return e?t.touches[0].clientY:t.touches[0].pageX}function d(e,t){var n=t.getBoundingClientRect();return e?n.top+.5*n.height:n.left+.5*n.width}function f(e,t){var n=t.max,r=t.min;return e<=r?r:e>=n?n:e}function p(e,t){var n=t.step,r=s(e,t);return null===n?r:parseFloat(r.toFixed(l(n)))}function h(e){e.stopPropagation(),e.preventDefault()}Object.defineProperty(t,"__esModule",{value:!0});var m=n(232),v=r(m);t.isEventFromHandle=o,t.isValueOutOfRange=i,t.isNotTouchEvent=a,t.getClosestPoint=s,t.getPrecision=l,t.getMousePosition=u,t.getTouchPosition=c,t.getHandleCenterPosition=d,t.ensureValueInRange=f,t.ensureValuePrecision=p,t.pauseEvent=h;var y=n(18)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t,n){var r=l.default.unstable_batchedUpdates?function(e){l.default.unstable_batchedUpdates(n,e)}:n;return(0,a.default)(e,t,r)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=o;var i=n(374),a=r(i),s=n(18),l=r(s);e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e){e.offsetHeight}function i(e,t){var n=t["offset"+(0,S.default)(e)],r=P[e];return n+parseInt((0,b.default)(t,r[0]),10)+parseInt((0,b.default)(t,r[1]),10)}t.__esModule=!0;var a=n(4),s=r(a),l=n(8),u=r(l),c=n(5),d=r(c),f=n(7),p=r(f),h=n(6),m=r(h),v=n(9),y=r(v),g=n(107),b=r(g),x=n(1),E=r(x),_=n(3),w=r(_),O=n(345),C=r(O),k=n(316),S=r(k),T=n(24),N=r(T),P={height:["marginTop","marginBottom"],width:["marginLeft","marginRight"]},M={in:w.default.bool,mountOnEnter:w.default.bool,unmountOnExit:w.default.bool,transitionAppear:w.default.bool,timeout:w.default.number,onEnter:w.default.func,onEntering:w.default.func,onEntered:w.default.func,onExit:w.default.func,onExiting:w.default.func,onExited:w.default.func,dimension:w.default.oneOfType([w.default.oneOf(["height","width"]),w.default.func]),getDimensionValue:w.default.func,role:w.default.string},A={in:!1,timeout:300,mountOnEnter:!1,unmountOnExit:!1,transitionAppear:!1,dimension:"height",getDimensionValue:i},D=function(e){function t(n,r){(0,d.default)(this,t);var o=(0,p.default)(this,e.call(this,n,r));return o.handleEnter=o.handleEnter.bind(o),o.handleEntering=o.handleEntering.bind(o),o.handleEntered=o.handleEntered.bind(o),o.handleExit=o.handleExit.bind(o),o.handleExiting=o.handleExiting.bind(o),o}return(0,m.default)(t,e),t.prototype.handleEnter=function(e){var t=this._dimension();e.style[t]="0"},t.prototype.handleEntering=function(e){var t=this._dimension();e.style[t]=this._getScrollDimensionValue(e,t)},t.prototype.handleEntered=function(e){var t=this._dimension();e.style[t]=null},t.prototype.handleExit=function(e){var t=this._dimension();e.style[t]=this.props.getDimensionValue(t,e)+"px",o(e)},t.prototype.handleExiting=function(e){var t=this._dimension();e.style[t]="0"},t.prototype._dimension=function(){return"function"==typeof this.props.dimension?this.props.dimension():this.props.dimension},t.prototype._getScrollDimensionValue=function(e,t){return e["scroll"+(0,S.default)(t)]+"px"},t.prototype.render=function(){var e=this.props,t=e.onEnter,n=e.onEntering,r=e.onEntered,o=e.onExit,i=e.onExiting,a=e.className,l=(0,u.default)(e,["onEnter","onEntering","onEntered","onExit","onExiting","className"]);delete l.dimension,delete l.getDimensionValue;var c=(0,N.default)(this.handleEnter,t),d=(0,N.default)(this.handleEntering,n),f=(0,N.default)(this.handleEntered,r),p=(0,N.default)(this.handleExit,o),h=(0,N.default)(this.handleExiting,i),m={width:"width"===this._dimension()};return E.default.createElement(C.default,(0,s.default)({},l,{"aria-expanded":l.role?l.in:null,className:(0,y.default)(a,m),exitedClassName:"collapse",exitingClassName:"collapsing",enteredClassName:"collapse in",enteringClassName:"collapsing",onEnter:c,onEntering:d,onEntered:f,onExit:p,onExiting:h}))},t}(E.default.Component);D.propTypes=M,D.defaultProps=A,t.default=D,e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(4),i=r(o),a=n(8),s=r(a),l=n(5),u=r(l),c=n(7),d=r(c),f=n(6),p=r(f),h=n(9),m=r(h),v=n(1),y=r(v),g=n(3),b=r(g),x=n(10),E={glyph:b.default.string.isRequired},_=function(e){function t(){return(0,u.default)(this,t),(0,d.default)(this,e.apply(this,arguments))}return(0,p.default)(t,e),t.prototype.render=function(){var e,t=this.props,n=t.glyph,r=t.className,o=(0,s.default)(t,["glyph","className"]),a=(0,x.splitBsProps)(o),l=a[0],u=a[1],c=(0,i.default)({},(0,x.getClassSet)(l),(e={},e[(0,x.prefix)(l,n)]=!0,e));return y.default.createElement("span",(0,i.default)({},u,{className:(0,m.default)(r,c)}))},t}(y.default.Component);_.propTypes=E,t.default=(0,x.bsClass)("glyphicon",_),e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(4),i=r(o),a=n(8),s=r(a),l=n(5),u=r(l),c=n(7),d=r(c),f=n(6),p=r(f),h=n(9),m=r(h),v=n(1),y=r(v),g=n(17),b=r(g),x=n(756),E=r(x),_=n(757),w=r(_),O=n(758),C=r(O),k=n(759),S=r(k),T=n(760),N=r(T),P=n(761),M=r(P),A=n(10),D={componentClass:b.default},R={componentClass:"div"},j=function(e){function t(){return(0,u.default)(this,t),(0,d.default)(this,e.apply(this,arguments))}return(0,p.default)(t,e),t.prototype.render=function(){var e=this.props,t=e.componentClass,n=e.className,r=(0,s.default)(e,["componentClass","className"]),o=(0,A.splitBsProps)(r),a=o[0],l=o[1],u=(0,A.getClassSet)(a);return y.default.createElement(t,(0,i.default)({},l,{className:(0,m.default)(n,u)}))},t}(y.default.Component);j.propTypes=D,j.defaultProps=R,j.Heading=w.default,j.Body=E.default,j.Left=C.default,j.Right=M.default,j.List=S.default,j.ListItem=N.default,t.default=(0,A.bsClass)("media",j),e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(8),i=r(o),a=n(5),s=r(a),l=n(7),u=r(l),c=n(6),d=r(c),f=n(1),p=r(f),h=n(3),m=r(h),v=n(135),y=r(v),g="tab",b="pane",x=m.default.oneOfType([m.default.string,m.default.number]),E={id:function(e){var t=null;if(!e.generateChildId){for(var n=arguments.length,r=Array(n>1?n-1:0),o=1;o0&&r.length<20?n+" (keys: "+r.join(", ")+")":n}function i(e,n){var r=l.get(e);if(!r){if("production"!==t.env.NODE_ENV){var o=e.constructor;"production"!==t.env.NODE_ENV?f(!n,"%s(...): Can only update a mounted or mounting component. This usually means you called %s() on an unmounted component. This is a no-op. Please check the code for the %s component.",n,n,o&&(o.displayName||o.name)||"ReactClass"):void 0}return null}return"production"!==t.env.NODE_ENV&&("production"!==t.env.NODE_ENV?f(null==s.current,"%s(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.",n):void 0),r}var a=n(16),s=n(40),l=n(98),u=n(34),c=n(39),d=n(12),f=n(13),p={isMounted:function(e){if("production"!==t.env.NODE_ENV){var n=s.current;null!==n&&("production"!==t.env.NODE_ENV?f(n._warnedAboutRefsInRender,"%s is accessing isMounted inside its render() function. render() should be a pure function of props and state. It should never access something that requires stale data from the previous render, such as refs. Move this logic to componentDidMount and componentDidUpdate instead.",n.getName()||"A component"):void 0,n._warnedAboutRefsInRender=!0)}var r=l.get(e);return!!r&&!!r._renderedComponent},enqueueCallback:function(e,t,n){p.validateCallback(t,n);var o=i(e);return o?(o._pendingCallbacks?o._pendingCallbacks.push(t):o._pendingCallbacks=[t],void r(o)):null},enqueueCallbackInternal:function(e,t){e._pendingCallbacks?e._pendingCallbacks.push(t):e._pendingCallbacks=[t],r(e)},enqueueForceUpdate:function(e){var t=i(e,"forceUpdate");t&&(t._pendingForceUpdate=!0,r(t))},enqueueReplaceState:function(e,t,n){var o=i(e,"replaceState");o&&(o._pendingStateQueue=[t],o._pendingReplaceState=!0,void 0!==n&&null!==n&&(p.validateCallback(n,"replaceState"),o._pendingCallbacks?o._pendingCallbacks.push(n):o._pendingCallbacks=[n]),r(o))},enqueueSetState:function(e,n){"production"!==t.env.NODE_ENV&&(u.debugTool.onSetState(),"production"!==t.env.NODE_ENV?f(null!=n,"setState(...): You passed an undefined or null state object; instead, use forceUpdate()."):void 0);var o=i(e,"setState");if(o){var a=o._pendingStateQueue||(o._pendingStateQueue=[]);a.push(n),r(o)}},enqueueElementInternal:function(e,t,n){e._pendingElement=t,e._context=n,r(e)},validateCallback:function(e,n){e&&"function"!=typeof e?"production"!==t.env.NODE_ENV?d(!1,"%s(...): Expected the last optional `callback` argument to be a function. Instead received: %s.",n,o(e)):a("122",n,o(e)):void 0}};e.exports=p}).call(t,n(2))},function(e,t){"use strict";var n=function(e){return"undefined"!=typeof MSApp&&MSApp.execUnsafeLocalFunction?function(t,n,r,o){MSApp.execUnsafeLocalFunction(function(){return e(t,n,r,o)})}:e};e.exports=n},function(e,t){"use strict";function n(e){var t,n=e.keyCode;return"charCode"in e?(t=e.charCode,0===t&&13===n&&(t=13)):t=n,t>=32||13===t?t:0}e.exports=n},function(e,t){"use strict";function n(e){var t=this,n=t.nativeEvent;if(n.getModifierState)return n.getModifierState(e);var r=o[e];return!!r&&!!n[r]}function r(e){return n}var o={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};e.exports=r},function(e,t){"use strict";function n(e){var t=e.target||e.srcElement||window;return t.correspondingUseElement&&(t=t.correspondingUseElement),3===t.nodeType?t.parentNode:t}e.exports=n},function(e,t,n){"use strict";/** * Checks if an event is supported in the current execution environment. * * NOTE: This will not work correctly for non-generic events such as `change`, @@ -35,54 +36,57 @@ for(o=97;o<123;o++)n[String.fromCharCode(o)]=o-32;for(var o=48;o<58;o++)n[o-48]= * @internal * @license Modernizr 3.0.0pre (Custom Build) | MIT */ -function r(e,t){if(!i.canUseDOM||t&&!("addEventListener"in document))return!1;var n="on"+e,r=n in document;if(!r){var a=document.createElement("div");a.setAttribute(n,"return;"),r="function"==typeof a[n]}return!r&&o&&"wheel"===e&&(r=document.implementation.hasFeature("Events.wheel","3.0")),r}var o,i=n(22);i.canUseDOM&&(o=document.implementation&&document.implementation.hasFeature&&document.implementation.hasFeature("","")!==!0),e.exports=r},function(e,t){"use strict";function n(e,t){var n=null===e||e===!1,r=null===t||t===!1;if(n||r)return n===r;var o=typeof e,i=typeof t;return"string"===o||"number"===o?"string"===i||"number"===i:"object"===i&&e.type===t.type&&e.key===t.key}e.exports=n},function(e,t,n){(function(t){"use strict";var r=n(19),o=n(31),i=n(13),a=o;if("production"!==t.env.NODE_ENV){var s=["address","applet","area","article","aside","base","basefont","bgsound","blockquote","body","br","button","caption","center","col","colgroup","dd","details","dir","div","dl","dt","embed","fieldset","figcaption","figure","footer","form","frame","frameset","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","iframe","img","input","isindex","li","link","listing","main","marquee","menu","menuitem","meta","nav","noembed","noframes","noscript","object","ol","p","param","plaintext","pre","script","section","select","source","style","summary","table","tbody","td","template","textarea","tfoot","th","thead","title","tr","track","ul","wbr","xmp"],l=["applet","caption","html","table","td","th","marquee","object","template","foreignObject","desc","title"],u=l.concat(["button"]),c=["dd","dt","li","option","optgroup","p","rp","rt"],d={current:null,formTag:null,aTagInScope:null,buttonTagInScope:null,nobrTagInScope:null,pTagInButtonScope:null,listItemTagAutoclosing:null,dlItemTagAutoclosing:null},f=function(e,t,n){var o=r({},e||d),i={tag:t,instance:n};return l.indexOf(t)!==-1&&(o.aTagInScope=null,o.buttonTagInScope=null,o.nobrTagInScope=null),u.indexOf(t)!==-1&&(o.pTagInButtonScope=null),s.indexOf(t)!==-1&&"address"!==t&&"div"!==t&&"p"!==t&&(o.listItemTagAutoclosing=null,o.dlItemTagAutoclosing=null),o.current=i,"form"===t&&(o.formTag=i),"a"===t&&(o.aTagInScope=i),"button"===t&&(o.buttonTagInScope=i),"nobr"===t&&(o.nobrTagInScope=i),"p"===t&&(o.pTagInButtonScope=i),"li"===t&&(o.listItemTagAutoclosing=i),"dd"!==t&&"dt"!==t||(o.dlItemTagAutoclosing=i),o},p=function(e,t){switch(t){case"select":return"option"===e||"optgroup"===e||"#text"===e;case"optgroup":return"option"===e||"#text"===e;case"option":return"#text"===e;case"tr":return"th"===e||"td"===e||"style"===e||"script"===e||"template"===e;case"tbody":case"thead":case"tfoot":return"tr"===e||"style"===e||"script"===e||"template"===e;case"colgroup":return"col"===e||"template"===e;case"table":return"caption"===e||"colgroup"===e||"tbody"===e||"tfoot"===e||"thead"===e||"style"===e||"script"===e||"template"===e;case"head":return"base"===e||"basefont"===e||"bgsound"===e||"link"===e||"meta"===e||"title"===e||"noscript"===e||"noframes"===e||"style"===e||"script"===e||"template"===e;case"html":return"head"===e||"body"===e;case"#document":return"html"===e}switch(e){case"h1":case"h2":case"h3":case"h4":case"h5":case"h6":return"h1"!==t&&"h2"!==t&&"h3"!==t&&"h4"!==t&&"h5"!==t&&"h6"!==t;case"rp":case"rt":return c.indexOf(t)===-1;case"body":case"caption":case"col":case"colgroup":case"frame":case"head":case"html":case"tbody":case"td":case"tfoot":case"th":case"thead":case"tr":return null==t}return!0},h=function(e,t){switch(e){case"address":case"article":case"aside":case"blockquote":case"center":case"details":case"dialog":case"dir":case"div":case"dl":case"fieldset":case"figcaption":case"figure":case"footer":case"header":case"hgroup":case"main":case"menu":case"nav":case"ol":case"p":case"section":case"summary":case"ul":case"pre":case"listing":case"table":case"hr":case"xmp":case"h1":case"h2":case"h3":case"h4":case"h5":case"h6":return t.pTagInButtonScope;case"form":return t.formTag||t.pTagInButtonScope;case"li":return t.listItemTagAutoclosing;case"dd":case"dt":return t.dlItemTagAutoclosing;case"button":return t.buttonTagInScope;case"a":return t.aTagInScope;case"nobr":return t.nobrTagInScope}return null},m=function(e){if(!e)return[];var t=[];do t.push(e);while(e=e._currentElement._owner);return t.reverse(),t},v={};a=function(e,n,r,o){o=o||d;var a=o.current,s=a&&a.tag;null!=n&&("production"!==t.env.NODE_ENV?i(null==e,"validateDOMNesting: when childText is passed, childTag should be null"):void 0,e="#text");var l=p(e,s)?null:a,u=l?null:h(e,o),c=l||u;if(c){var f,y=c.tag,g=c.instance,b=r&&r._currentElement._owner,x=g&&g._currentElement._owner,E=m(b),_=m(x),w=Math.min(E.length,_.length),C=-1;for(f=0;f "),P=!!l+"|"+e+"|"+y+"|"+N;if(v[P])return;v[P]=!0;var S=e,M="";if("#text"===e?/\S/.test(n)?S="Text nodes":(S="Whitespace text nodes",M=" Make sure you don't have any extra whitespace between tags on each line of your source code."):S="<"+e+">",l){var A="";"table"===y&&"tr"===e&&(A+=" Add a to your code to match the DOM tree generated by the browser."),"production"!==t.env.NODE_ENV?i(!1,"validateDOMNesting(...): %s cannot appear as a child of <%s>.%s See %s.%s",S,y,M,N,A):void 0}else"production"!==t.env.NODE_ENV?i(!1,"validateDOMNesting(...): %s cannot appear as a descendant of <%s>. See %s.",S,y,N):void 0}},a.updatedAncestorInfo=f,a.isTagValidInContext=function(e,t){t=t||d;var n=t.current,r=n&&n.tag;return p(e,r)&&!h(e,t)}}e.exports=a}).call(t,n(2))},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){return e="function"==typeof e?e():e,a.default.findDOMNode(e)||t}Object.defineProperty(t,"__esModule",{value:!0}),t.default=o;var i=n(18),a=r(i);e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t,n,r,o){var a=e[t],l="undefined"==typeof a?"undefined":i(a);return s.default.isValidElement(a)?new Error("Invalid "+r+" `"+o+"` of type ReactElement "+("supplied to `"+n+"`, expected a ReactComponent or a ")+"DOMElement. You can usually obtain a ReactComponent or DOMElement from a ReactElement by attaching a ref to it."):"object"===l&&"function"==typeof a.render||1===a.nodeType?null:new Error("Invalid "+r+" `"+o+"` of value `"+a+"` "+("supplied to `"+n+"`, expected a ReactComponent or a ")+"DOMElement.")}t.__esModule=!0;var i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e},a=n(1),s=r(a),l=n(128),u=r(l);t.default=(0,u.default)(o)},function(e,t){"use strict";function n(e){"undefined"!=typeof console&&"function"==typeof console.error&&console.error(e);try{throw new Error(e)}catch(e){}}t.__esModule=!0,t.default=n},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}t.__esModule=!0;var s=Object.assign||function(e){for(var t=1;t may have only one child element"),this.unlisten=r.listen(function(){e.setState({match:e.computeMatch(r.location.pathname)})})},t.prototype.componentWillReceiveProps=function(e){(0,u.default)(this.props.history===e.history,"You cannot change ")},t.prototype.componentWillUnmount=function(){this.unlisten()},t.prototype.render=function(){var e=this.props.children;return e?p.default.Children.only(e):null},t}(p.default.Component);v.propTypes={history:m.default.object.isRequired,children:m.default.node},v.contextTypes={router:m.default.object},v.childContextTypes={router:m.default.object.isRequired},t.default=v},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(688),i=r(o),a={},s=1e4,l=0,u=function(e,t){var n=""+t.end+t.strict,r=a[n]||(a[n]={});if(r[e])return r[e];var o=[],u=(0,i.default)(e,o,t),c={re:u,keys:o};return l1&&void 0!==arguments[1]?arguments[1]:{};"string"==typeof t&&(t={path:t});var n=t,r=n.path,o=void 0===r?"/":r,i=n.exact,a=void 0!==i&&i,s=n.strict,l=void 0!==s&&s,c=u(o,{end:a,strict:l}),d=c.re,f=c.keys,p=d.exec(e);if(!p)return null;var h=p[0],m=p.slice(1),v=e===h;return a&&!v?null:{path:o,url:"/"===o&&""===h?"/":h,isExact:v,params:f.reduce(function(e,t,n){return e[t.name]=m[n],e},{})}};t.default=c},function(e,t,n){(function(t){"use strict";var n=function(){};if("production"!==t.env.NODE_ENV){var r=function(e){for(var t=arguments.length,n=Array(t>1?t-1:0),r=1;r2?n-2:0),i=2;i=0&&b.splice(t,1)}function s(e){var t=document.createElement("style");return t.type="text/css",i(e,t),t}function l(e){var t=document.createElement("link");return t.rel="stylesheet",i(e,t),t}function u(e,t){var n,r,o;if(t.singleton){var i=g++;n=y||(y=s(t)),r=c.bind(null,n,i,!1),o=c.bind(null,n,i,!0)}else e.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(n=l(t),r=f.bind(null,n),o=function(){a(n),n.href&&URL.revokeObjectURL(n.href)}):(n=s(t),r=d.bind(null,n),o=function(){a(n)});return r(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;r(e=t)}else o()}}function c(e,t,n,r){var o=n?"":r.css;if(e.styleSheet)e.styleSheet.cssText=x(t,o);else{var i=document.createTextNode(o),a=e.childNodes;a[t]&&e.removeChild(a[t]),a.length?e.insertBefore(i,a[t]):e.appendChild(i)}}function d(e,t){var n=t.css,r=t.media;if(r&&e.setAttribute("media",r),e.styleSheet)e.styleSheet.cssText=n;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(n))}}function f(e,t){var n=t.css,r=t.sourceMap;r&&(n+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(r))))+" */");var o=new Blob([n],{type:"text/css"}),i=e.href;e.href=URL.createObjectURL(o),i&&URL.revokeObjectURL(i)}var p={},h=function(e){var t;return function(){return"undefined"==typeof t&&(t=e.apply(this,arguments)),t}},m=h(function(){return/msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase())}),v=h(function(){return document.head||document.getElementsByTagName("head")[0]}),y=null,g=0,b=[];e.exports=function(e,t){t=t||{},"undefined"==typeof t.singleton&&(t.singleton=m()),"undefined"==typeof t.insertAt&&(t.insertAt="bottom");var n=o(e);return r(n,t),function(e){for(var i=[],a=0;a0&&void 0!==arguments[0]?arguments[0]:c(),t=arguments[1];switch(t.type){case"ADDON_UPDATE_FINISHED":return i({},e,o({},t.payload,(0,u.default)(e[t.payload],{loading:{$set:!1}})));case"ADDON_UPDATE_STARTED":case"ADDON_DOWNLOAD_STARTED":return i({},e,o({},t.payload,(0,u.default)(e[t.payload],{loading:{$set:!0}})));case"ADDON_DOWNLOADED":return i({},e,o({},t.payload,(0,u.default)(e[t.payload],{installed:{$set:!0},loading:{$set:!1}})));case"DELETE_ADDON":return i({},e,o({},t.payload,(0,u.default)(e[t.payload],{installed:{$set:!1}})));case"SET_ADDON_LIST":return i({},t.payload);default:return e}});t.default=d},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t0&&void 0!==arguments[0]?arguments[0]:s(),t=arguments[1];switch(t.type){case"UPDATE_BEER_XML_RECIPE":return[].concat(o(t.payload));default:return e}});t.default=l},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t0&&void 0!==arguments[0]?arguments[0]:s(),t=arguments[1];switch(t.type){case"UPDATE_KBH":return[].concat(o(t.payload));default:return e}});t.default=l},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t0&&void 0!==arguments[0]?arguments[0]:s(),t=arguments[1];switch(t.type){case"LOAD_STATE":return[].concat(o(t.payload.messages));case"NOTIFY":return[].concat(o(e),[t.payload]);case"DISMISS_NOTIFICATION":return e.filter(function(e,n){return e.id!==t.id});default:return e}});t.default=l},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=n(11),i=n(26),a=r(i),s=n(74),l=function(e,t){return{title:"Step Config",bsSize:"large",form:[{label:"Name",name:"name",type:"text"},{label:"Type",clear_config:!0,name:"type",type:"select",options:Object.keys(e.steps.type)}],types:e.steps.type,configfield:"type",actors:e.actor.actors,sensors:e.sensor.sensors,kettle:e.kettle.list}},u=function(e,t,n){return{btn_save:function(t){return e((0, -s.save)(t)),!0},btn_add:function(t){return e((0,s.add)(t)),!0},btn_delete:function(t){return e((0,s.remove)(t)),!0}}},c=(0,o.connect)(l,u,null,{withRef:!0})(a.default);t.default=c},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var s=function(){function e(e,t){for(var n=0;n0)return u.default.createElement("span",null,e," %")}},{key:"render",value:function(){var e=this,t=this.props,n=t.bsSize,r=t.url,o=t.title,i=t.onSuccess;return u.default.createElement(c.Modal,{bsSize:n,animation:!1,show:this.state.showModal,onHide:this.hide.bind(this)},u.default.createElement(c.Modal.Header,null,u.default.createElement(c.Modal.Title,null,o)),u.default.createElement(c.Modal.Body,null,u.default.createElement(f.default,{ref:"test",key:"ex1",url:r,onProgress:function(e,t,n){},onLoad:function(t,n){e.hide(),i()},onError:function(e,t){},onAbort:function(e,t){},formRenderer:this.customFormRenderer.bind(this),progressRenderer:this.customProgress.bind(this)})))}}]),t}(l.Component);p.defaultProps={bsSize:"small",title:"Upload",onSuccess:function(){},url:void 0},t.default=p},function(e,t,n){e.exports={default:n(439),__esModule:!0}},function(e,t,n){e.exports={default:n(440),__esModule:!0}},function(e,t,n){e.exports={default:n(442),__esModule:!0}},function(e,t,n){e.exports={default:n(443),__esModule:!0}},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(225),i=r(o);t.default=function(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);tl;)r(s,n=t[l++])&&(~i(u,n)||u.push(n));return u}},function(e,t,n){var r=n(36),o=n(25),i=n(61);e.exports=function(e,t){var n=(o.Object||{})[e]||Object[e],a={};a[e]=t(n),r(r.S+r.F*i(function(){n(1)}),"Object",a)}},function(e,t,n){var r=n(63),o=n(41),i=n(81).f;e.exports=function(e){return function(t){for(var n,a=o(t),s=r(a),l=s.length,u=0,c=[];l>u;)i.call(a,n=s[u++])&&c.push(e?[n,a[n]]:a[n]);return c}}},function(e,t,n){e.exports=n(62)},function(e,t,n){var r=n(150),o=Math.min;e.exports=function(e){return e>0?o(r(e),9007199254740991):0}},function(e,t,n){"use strict";var r=n(469)(!0);n(235)(String,"String",function(e){this._t=String(e),this._i=0},function(){var e,t=this._t,n=this._i;return n>=t.length?{value:void 0,done:!0}:(e=r(t,n),this._i+=e.length,{value:e,done:!1})})},function(e,t,n){(function(t){"use strict";function r(e){return e}function o(e,n,o){function d(e,n,r){for(var o in n)n.hasOwnProperty(o)&&"production"!==t.env.NODE_ENV&&l("function"==typeof n[o],"%s: %s type `%s` is invalid; it must be a function, usually from React.PropTypes.",e.displayName||"ReactClass",u[r],o)}function f(e,t){var n=_.hasOwnProperty(t)?_[t]:null;k.hasOwnProperty(t)&&s("OVERRIDE_BASE"===n,"ReactClassInterface: You are attempting to override `%s` from your class specification. Ensure that your method names do not overlap with React methods.",t),e&&s("DEFINE_MANY"===n||"DEFINE_MANY_MERGED"===n,"ReactClassInterface: You are attempting to define `%s` on your component more than once. This conflict may be due to a mixin.",t)}function p(e,r){if(r){s("function"!=typeof r,"ReactClass: You're attempting to use a component class or function as a mixin. Instead, just use a regular object."),s(!n(r),"ReactClass: You're attempting to use a component as a mixin. Instead, just use a regular object.");var o=e.prototype,i=o.__reactAutoBindPairs;r.hasOwnProperty(c)&&w.mixins(e,r.mixins);for(var a in r)if(r.hasOwnProperty(a)&&a!==c){var u=r[a],d=o.hasOwnProperty(a);if(f(d,a),w.hasOwnProperty(a))w[a](e,u);else{var p=_.hasOwnProperty(a),h="function"==typeof u,m=h&&!p&&!d&&r.autobind!==!1;if(m)i.push(a,u),o[a]=u;else if(d){var g=_[a];s(p&&("DEFINE_MANY_MERGED"===g||"DEFINE_MANY"===g),"ReactClass: Unexpected spec policy %s for key %s when mixing in component specs.",g,a),"DEFINE_MANY_MERGED"===g?o[a]=v(o[a],u):"DEFINE_MANY"===g&&(o[a]=y(o[a],u))}else o[a]=u,"production"!==t.env.NODE_ENV&&"function"==typeof u&&r.displayName&&(o[a].displayName=r.displayName+"_"+a)}}}else if("production"!==t.env.NODE_ENV){var b=typeof r,x="object"===b&&null!==r;"production"!==t.env.NODE_ENV&&l(x,"%s: You're attempting to include a mixin that is either null or not an object. Check the mixins included by the component, as well as any mixins they include themselves. Expected object but got %s.",e.displayName||"ReactClass",null===r?null:b)}}function h(e,t){if(t)for(var n in t){var r=t[n];if(t.hasOwnProperty(n)){var o=n in w;s(!o,'ReactClass: You are attempting to define a reserved property, `%s`, that shouldn\'t be on the "statics" key. Define it as an instance property instead; it will still be accessible on the constructor.',n);var i=n in e;s(!i,"ReactClass: You are attempting to define `%s` on your component more than once. This conflict may be due to a mixin.",n),e[n]=r}}}function m(e,t){s(e&&t&&"object"==typeof e&&"object"==typeof t,"mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.");for(var n in t)t.hasOwnProperty(n)&&(s(void 0===e[n],"mergeIntoWithNoDuplicateKeys(): Tried to merge two objects with the same key: `%s`. This conflict may be due to a mixin; in particular, this may be caused by two getInitialState() or getDefaultProps() methods returning objects with clashing keys.",n),e[n]=t[n]);return e}function v(e,t){return function(){var n=e.apply(this,arguments),r=t.apply(this,arguments);if(null==n)return r;if(null==r)return n;var o={};return m(o,n),m(o,r),o}}function y(e,t){return function(){e.apply(this,arguments),t.apply(this,arguments)}}function g(e,n){var r=n.bind(e);if("production"!==t.env.NODE_ENV){r.__reactBoundContext=e,r.__reactBoundMethod=n,r.__reactBoundArguments=null;var o=e.constructor.displayName,i=r.bind;r.bind=function(a){for(var s=arguments.length,u=Array(s>1?s-1:0),c=1;c0&&void 0!==arguments[0]?arguments[0]:(0,a.default)();try{return e.activeElement}catch(e){}}Object.defineProperty(t,"__esModule",{value:!0}),t.default=o;var i=n(64),a=r(i);e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){e.classList?e.classList.add(t):(0,a.default)(e)||(e.className=e.className+" "+t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=o;var i=n(249),a=r(i);e.exports=t.default},function(e,t){"use strict";function n(e,t){return e.classList?!!t&&e.classList.contains(t):(" "+e.className+" ").indexOf(" "+t+" ")!==-1}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n,e.exports=t.default},function(e,t){"use strict";e.exports=function(e,t){e.classList?e.classList.remove(t):e.className=e.className.replace(new RegExp("(^|\\s)"+t+"(?:\\s|$)","g"),"$1").replace(/\s+/g," ").replace(/^\s*|\s*$/g,"")}},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e){var t=(0,c.default)(e),n=(0,l.default)(t),r=t&&t.documentElement,o={top:0,left:0,height:0,width:0};if(t)return(0,a.default)(r,e)?(void 0!==e.getBoundingClientRect&&(o=e.getBoundingClientRect()),o={top:o.top+(n.pageYOffset||r.scrollTop)-(r.clientTop||0),left:o.left+(n.pageXOffset||r.scrollLeft)-(r.clientLeft||0),width:(null==o.width?e.offsetWidth:o.width)||0,height:(null==o.height?e.offsetHeight:o.height)||0}):o}Object.defineProperty(t,"__esModule",{value:!0}),t.default=o;var i=n(65),a=r(i),s=n(104),l=r(s),u=n(64),c=r(u);e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){var n=(0,a.default)(e);return void 0===t?n?"pageYOffset"in n?n.pageYOffset:n.document.documentElement.scrollTop:e.scrollTop:void(n?n.scrollTo("pageXOffset"in n?n.pageXOffset:n.document.documentElement.scrollLeft,t):e.scrollTop=t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=o;var i=n(104),a=r(i);e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e){return(0,a.default)(e.replace(s,"ms-"))}Object.defineProperty(t,"__esModule",{value:!0}),t.default=o;var i=n(514),a=r(i),s=/^-ms-/;e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){if((!a||e)&&i.default){var t=document.createElement("div");t.style.position="absolute",t.style.top="-9999px",t.style.width="50px",t.style.height="50px",t.style.overflow="scroll",document.body.appendChild(t),a=t.offsetWidth-t.clientWidth,document.body.removeChild(t)}return a};var o=n(42),i=r(o),a=void 0;e.exports=t.default},function(e,t,n){(function(e){function r(t){var n,r=!1,s=!1,l=!1!==t.jsonp;if(e.location){var u="https:"===location.protocol,c=location.port;c||(c=u?443:80),r=t.hostname!==location.hostname||c!==t.port,s=t.secure!==u}if(t.xdomain=r,t.xscheme=s,n=new o(t),"open"in n&&!t.forceJSONP)return new i(t);if(!l)throw new Error("JSONP disabled");return new a(t)}var o=n(159),i=n(522),a=n(521),s=n(523);t.polling=r,t.websocket=s}).call(t,function(){return this}())},function(e,t,n){function r(e){var t=e&&e.forceBase64;c&&!t||(this.supportsBinary=!1),o.call(this,e)}var o=n(158),i=n(176),a=n(66),s=n(100),l=n(360),u=n(106)("engine.io-client:polling");e.exports=r;var c=function(){var e=n(159),t=new e({xdomain:!1});return null!=t.responseType}();s(r,o),r.prototype.name="polling",r.prototype.doOpen=function(){this.poll()},r.prototype.pause=function(e){function t(){u("paused"),n.readyState="paused",e()}var n=this;if(this.readyState="pausing",this.polling||!this.writable){var r=0;this.polling&&(u("we are currently polling - waiting to pause"),r++,this.once("pollComplete",function(){u("pre-pause polling complete"),--r||t()})),this.writable||(u("we are currently writing - waiting to pause"),r++,this.once("drain",function(){u("pre-pause writing complete"),--r||t()}))}else t()},r.prototype.poll=function(){u("polling"),this.polling=!0,this.doPoll(),this.emit("poll")},r.prototype.onData=function(e){var t=this;u("polling got data %s",e);var n=function(e,n,r){return"opening"===t.readyState&&t.onOpen(),"close"===e.type?(t.onClose(),!1):void t.onPacket(e)};a.decodePayload(e,this.socket.binaryType,n),"closed"!==this.readyState&&(this.polling=!1,this.emit("pollComplete"),"open"===this.readyState?this.poll():u('ignoring poll - transport state "%s"',this.readyState))},r.prototype.doClose=function(){function e(){u("writing close packet"),t.write([{type:"close"}])}var t=this;"open"===this.readyState?(u("transport open - closing"),e()):(u("transport not open - deferring close"),this.once("open",e))},r.prototype.write=function(e){var t=this;this.writable=!1;var n=function(){t.writable=!0,t.emit("drain")};a.encodePayload(e,this.supportsBinary,function(e){t.doWrite(e,n)})},r.prototype.uri=function(){var e=this.query||{},t=this.secure?"https":"http",n="";!1!==this.timestampRequests&&(e[this.timestampParam]=l()),this.supportsBinary||e.sid||(e.b64=1),e=i.encode(e),this.port&&("https"===t&&443!==Number(this.port)||"http"===t&&80!==Number(this.port))&&(n=":"+this.port),e.length&&(e="?"+e);var r=this.hostname.indexOf(":")!==-1;return t+"://"+(r?"["+this.hostname+"]":this.hostname)+n+this.path+e}},function(e,t,n){(function(t){"use strict";var r=n(31),o={listen:function(e,t,n){return e.addEventListener?(e.addEventListener(t,n,!1),{remove:function(){e.removeEventListener(t,n,!1)}}):e.attachEvent?(e.attachEvent("on"+t,n),{remove:function(){e.detachEvent("on"+t,n)}}):void 0},capture:function(e,n,o){return e.addEventListener?(e.addEventListener(n,o,!0),{remove:function(){e.removeEventListener(n,o,!0)}}):("production"!==t.env.NODE_ENV&&console.error("Attempted to listen to events during the capture phase on a browser that does not support the capture phase. Your application will not receive some events."),{remove:r})},registerDefault:function(){}};e.exports=o}).call(t,n(2))},function(e,t){"use strict";function n(e){try{e.focus()}catch(e){}}e.exports=n},function(e,t){"use strict";function n(e){if(e=e||("undefined"!=typeof document?document:void 0),"undefined"==typeof e)return null;try{return e.activeElement||e.body}catch(t){return e.body}}e.exports=n},function(e,t,n){(function(t){function r(e){function n(e){if(!e)return!1;if(t.Buffer&&t.Buffer.isBuffer&&t.Buffer.isBuffer(e)||t.ArrayBuffer&&e instanceof ArrayBuffer||t.Blob&&e instanceof Blob||t.File&&e instanceof File)return!0;if(o(e)){for(var r=0;r=this.index)return void t.push(e);for(var r=0;rn)return void t.splice(r,0,e)}}},{key:"reset",value:function(){this.registry=[]}},{key:"remove",value:function(e){var t=this.registry.indexOf(e);this.registry.splice(t,1)}},{key:"toString",value:function(e){return this.registry.filter(function(e){return e.attached}).map(function(t){return t.toString(e)}).join("\n")}},{key:"index",get:function(){return 0===this.registry.length?0:this.registry[this.registry.length-1].options.index}}]),e}();t.default=o},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t,n){var r=n.jss,o=(0,c.default)(t);if(r){var i=r.plugins.onCreateRule(e,o,n);if(i)return i}return e&&"@"===e[0]&&(0,a.default)(!1,"[JSS] Unknown at-rule %s",e),new l.default(e,o,n)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=o;var i=n(17),a=r(i),s=n(574),l=r(s),u=n(578),c=r(u)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(e.Renderer)return e.Renderer;var t=e.virtual||!a.default;return t?c.default:l.default}Object.defineProperty(t,"__esModule",{value:!0}),t.default=o;var i=n(108),a=r(i),s=n(569),l=r(s),u=n(570),c=r(u)},function(e,t){"use strict";function n(e,t){e.renderable=t,e.rules&&t.cssRules&&e.rules.link(t.cssRules)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t){"use strict";function n(e){return Array.isArray(e)?Array.isArray(e[0])?n(e.map(r)):e.join(", "):e}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n;var r=function(e){return e.join(" ")}},function(e,t,n){function r(e){var t=this.__data__=new o(e);this.size=t.size}var o=n(110),i=n(667),a=n(668),s=n(669),l=n(670),u=n(671);r.prototype.clear=i,r.prototype.delete=a,r.prototype.get=s,r.prototype.has=l,r.prototype.set=u,e.exports=r},function(e,t){function n(e,t){for(var n=-1,r=null==e?0:e.length,o=Array(r);++nf))return!1;var h=c.get(e);if(h&&c.get(t))return h==t;var m=-1,v=!0,y=n&l?new o:void 0;for(c.set(e,t),c.set(t,e);++m1?(!n&&t&&(r.className+=" "+t),m.default.createElement("div",r)):m.default.Children.only(r.children)}}]),t}(h.Component);g.propTypes={children:y.default.any,className:y.default.string,visible:y.default.bool,hiddenClassName:y.default.string},t.default=g,e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(4),i=r(o),a=n(8),s=r(a),l=n(5),u=r(l),c=n(7),d=r(c),f=n(6),p=r(f),h=n(9),m=r(h),v=n(1),y=r(v),g=n(3),b=r(g),x=n(38),E=r(x),_={active:b.default.bool,href:b.default.string,title:b.default.node,target:b.default.string},w={active:!1},C=function(e){function t(){return(0,u.default)(this,t),(0,d.default)(this,e.apply(this,arguments))}return(0,p.default)(t,e),t.prototype.render=function(){var e=this.props,t=e.active,n=e.href,r=e.title,o=e.target,a=e.className,l=(0,s.default)(e,["active","href","title","target","className"]),u={href:n,title:r,target:o};return y.default.createElement("li",{className:(0,m.default)(a,{active:t})},t?y.default.createElement("span",l):y.default.createElement(E.default,(0,i.default)({},l,u)))},t}(y.default.Component);C.propTypes=_,C.defaultProps=w,t.default=C,e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(4),i=r(o),a=n(8),s=r(a),l=n(5),u=r(l),c=n(7),d=r(c),f=n(6),p=r(f),h=n(9),m=r(h),v=n(1),y=r(v),g=n(3),b=r(g),x=n(126),E=r(x),_=n(91),w=r(_),C=n(10),O={vertical:b.default.bool,justified:b.default.bool,block:(0,E.default)(b.default.bool,function(e){var t=e.block,n=e.vertical;return t&&!n?new Error("`block` requires `vertical` to be set to have any effect"):null})},k={block:!1,justified:!1,vertical:!1},T=function(e){function t(){return(0,u.default)(this,t),(0,d.default)(this,e.apply(this,arguments))}return(0,p.default)(t,e),t.prototype.render=function(){var e,t=this.props,n=t.block,r=t.justified,o=t.vertical,a=t.className,l=(0,s.default)(t,["block","justified","vertical","className"]),u=(0,C.splitBsProps)(l),c=u[0],d=u[1],f=(0,i.default)({},(0,C.getClassSet)(c),(e={},e[(0,C.prefix)(c)]=!o,e[(0,C.prefix)(c,"vertical")]=o,e[(0,C.prefix)(c,"justified")]=r,e[(0,C.prefix)(w.default.defaultProps,"block")]=n,e));return y.default.createElement("div",(0,i.default)({},d,{className:(0,m.default)(a,f)}))},t}(y.default.Component);T.propTypes=O,T.defaultProps=k,t.default=(0,C.bsClass)("btn-group",T),e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(4),i=r(o),a=n(8),s=r(a),l=n(5),u=r(l),c=n(7),d=r(c),f=n(6),p=r(f),h=n(9),m=r(h),v=n(1),y=r(v),g=n(3),b=r(g),x=n(18),E=r(x),_=n(774),w=r(_),C={direction:b.default.oneOf(["prev","next"]),onAnimateOutEnd:b.default.func,active:b.default.bool,animateIn:b.default.bool,animateOut:b.default.bool,index:b.default.number},O={active:!1,animateIn:!1,animateOut:!1},k=function(e){function t(n,r){(0,u.default)(this,t);var o=(0,d.default)(this,e.call(this,n,r));return o.handleAnimateOutEnd=o.handleAnimateOutEnd.bind(o),o.state={direction:null},o.isUnmounted=!1,o}return(0,p.default)(t,e),t.prototype.componentWillReceiveProps=function(e){this.props.active!==e.active&&this.setState({direction:null})},t.prototype.componentDidUpdate=function(e){var t=this,n=this.props.active,r=e.active;!n&&r&&w.default.addEndEventListener(E.default.findDOMNode(this),this.handleAnimateOutEnd),n!==r&&setTimeout(function(){return t.startAnimation()},20)},t.prototype.componentWillUnmount=function(){this.isUnmounted=!0},t.prototype.handleAnimateOutEnd=function(){this.isUnmounted||this.props.onAnimateOutEnd&&this.props.onAnimateOutEnd(this.props.index)},t.prototype.startAnimation=function(){this.isUnmounted||this.setState({direction:"prev"===this.props.direction?"right":"left"})},t.prototype.render=function(){var e=this.props,t=e.direction,n=e.active,r=e.animateIn,o=e.animateOut,a=e.className,l=(0,s.default)(e,["direction","active","animateIn","animateOut","className"]);delete l.onAnimateOutEnd,delete l.index;var u={item:!0,active:n&&!r||o};return t&&n&&r&&(u[t]=!0),this.state.direction&&(r||o)&&(u[this.state.direction]=!0),y.default.createElement("div",(0,i.default)({},l,{className:(0,m.default)(a,u)}))},t}(y.default.Component);k.propTypes=C,k.defaultProps=O,t.default=k,e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(4),i=r(o),a=n(8),s=r(a),l=n(5),u=r(l),c=n(7),d=r(c),f=n(6),p=r(f),h=n(1),m=r(h),v=n(3),y=r(v),g=n(9),b=r(g),x=n(91),E=r(x),_=n(38),w=r(_),C=n(10),O={noCaret:y.default.bool,open:y.default.bool,title:y.default.string,useAnchor:y.default.bool},k={open:!1,useAnchor:!1,bsRole:"toggle"},T=function(e){function t(){return(0,u.default)(this,t),(0,d.default)(this,e.apply(this,arguments))}return(0,p.default)(t,e),t.prototype.render=function(){var e=this.props,t=e.noCaret,n=e.open,r=e.useAnchor,o=e.bsClass,a=e.className,l=e.children,u=(0,s.default)(e,["noCaret","open","useAnchor","bsClass","className","children"]);delete u.bsRole;var c=r?w.default:E.default,d=!t;return m.default.createElement(c,(0,i.default)({},u,{role:"button",className:(0,b.default)(a,o),"aria-haspopup":!0,"aria-expanded":n}),l||u.title,d&&" ",d&&m.default.createElement("span",{className:"caret"}))},t}(m.default.Component);T.propTypes=O,T.defaultProps=k,t.default=(0,C.bsClass)("dropdown-toggle",T),e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(4),i=r(o),a=n(8),s=r(a),l=n(5),u=r(l),c=n(7),d=r(c),f=n(6),p=r(f),h=n(9),m=r(h),v=n(1),y=r(v),g=n(3),b=r(g),x=n(16),E=r(x),_=n(10),w={fluid:b.default.bool,componentClass:E.default},C={componentClass:"div",fluid:!1},O=function(e){function t(){return(0,u.default)(this,t),(0,d.default)(this,e.apply(this,arguments))}return(0,p.default)(t,e),t.prototype.render=function(){var e=this.props,t=e.fluid,n=e.componentClass,r=e.className,o=(0,s.default)(e,["fluid","componentClass","className"]),a=(0,_.splitBsProps)(o),l=a[0],u=a[1],c=(0,_.prefix)(l,t&&"fluid");return y.default.createElement(n,(0,i.default)({},u,{className:(0,m.default)(r,c)}))},t}(y.default.Component);O.propTypes=w,O.defaultProps=C,t.default=(0,_.bsClass)("container",O),e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(58),i=r(o),a=n(4),s=r(a),l=n(8),u=r(l),c=n(5),d=r(c),f=n(7),p=r(f),h=n(6),m=r(h),v=n(9),y=r(v),g=n(1),b=r(g),x=n(3),E=r(x),_=n(10),w=n(24),C={active:E.default.any,disabled:E.default.any,header:E.default.node,listItem:E.default.bool,onClick:E.default.func,href:E.default.string,type:E.default.string},O={listItem:!1},k=function(e){function t(){return(0,d.default)(this,t),(0,p.default)(this,e.apply(this,arguments))}return(0,m.default)(t,e),t.prototype.renderHeader=function(e,t){return b.default.isValidElement(e)?(0,g.cloneElement)(e,{className:(0,y.default)(e.props.className,t)}):b.default.createElement("h4",{className:t},e)},t.prototype.render=function(){var e=this.props,t=e.active,n=e.disabled,r=e.className,o=e.header,i=e.listItem,a=e.children,l=(0,u.default)(e,["active","disabled","className","header","listItem","children"]),c=(0,_.splitBsProps)(l),d=c[0],f=c[1],p=(0,s.default)({},(0,_.getClassSet)(d),{active:t,disabled:n}),h=void 0;return f.href?h="a":f.onClick?(h="button",f.type=f.type||"button"):h=i?"li":"span",f.className=(0,y.default)(r,p),o?b.default.createElement(h,f,this.renderHeader(o,(0,_.prefix)(d,"heading")),b.default.createElement("p",{className:(0,_.prefix)(d,"text")},a)):b.default.createElement(h,f,a)},t}(b.default.Component);k.propTypes=C,k.defaultProps=O,t.default=(0,_.bsClass)("list-group-item",(0,_.bsStyles)((0,i.default)(w.State),k)),e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(4),i=r(o),a=n(8),s=r(a),l=n(5),u=r(l),c=n(7),d=r(c),f=n(6),p=r(f),h=n(9),m=r(h),v=n(1),y=r(v),g=n(16),b=r(g),x=n(10),E={componentClass:b.default},_={componentClass:"div"},w=function(e){function t(){return(0,u.default)(this,t),(0,d.default)(this,e.apply(this,arguments))}return(0,p.default)(t,e),t.prototype.render=function(){var e=this.props,t=e.componentClass,n=e.className,r=(0,s.default)(e,["componentClass","className"]),o=(0,x.splitBsProps)(r),a=o[0],l=o[1],u=(0,x.getClassSet)(a);return y.default.createElement(t,(0,i.default)({},l,{className:(0,m.default)(n,u)}))},t}(y.default.Component);w.propTypes=E,w.defaultProps=_,t.default=(0,x.bsClass)("modal-body",w),e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(4),i=r(o),a=n(8),s=r(a),l=n(5),u=r(l),c=n(7),d=r(c),f=n(6),p=r(f),h=n(9),m=r(h),v=n(1),y=r(v),g=n(16),b=r(g),x=n(10),E={componentClass:b.default},_={componentClass:"div"},w=function(e){function t(){return(0,u.default)(this,t),(0,d.default)(this,e.apply(this,arguments))}return(0,p.default)(t,e),t.prototype.render=function(){var e=this.props,t=e.componentClass,n=e.className,r=(0,s.default)(e,["componentClass","className"]),o=(0,x.splitBsProps)(r),a=o[0],l=o[1],u=(0,x.getClassSet)(a);return y.default.createElement(t,(0,i.default)({},l,{className:(0,m.default)(n,u)}))},t}(y.default.Component);w.propTypes=E,w.defaultProps=_,t.default=(0,x.bsClass)("modal-footer",w),e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(4),i=r(o),a=n(8),s=r(a),l=n(5),u=r(l),c=n(7),d=r(c),f=n(6),p=r(f),h=n(9),m=r(h),v=n(1),y=r(v),g=n(3),b=r(g),x=n(10),E=n(23),_=r(E),w={"aria-label":b.default.string,closeButton:b.default.bool,onHide:b.default.func},C={"aria-label":"Close",closeButton:!1},O={$bs_modal:b.default.shape({onHide:b.default.func})},k=function(e){function t(){return(0,u.default)(this,t),(0,d.default)(this,e.apply(this,arguments))}return(0,p.default)(t,e),t.prototype.render=function(){var e=this.props,t=e["aria-label"],n=e.closeButton,r=e.onHide,o=e.className,a=e.children,l=(0,s.default)(e,["aria-label","closeButton","onHide","className","children"]),u=this.context.$bs_modal,c=(0,x.splitBsProps)(l),d=c[0],f=c[1],p=(0,x.getClassSet)(d);return y.default.createElement("div",(0,i.default)({},f,{className:(0,m.default)(o,p)}),n&&y.default.createElement("button",{type:"button",className:"close","aria-label":t,onClick:(0,_.default)(u&&u.onHide,r)},y.default.createElement("span",{"aria-hidden":"true"},"×")),a)},t}(y.default.Component);k.propTypes=w,k.defaultProps=C,k.contextTypes=O,t.default=(0,x.bsClass)("modal-header",k),e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=n(4),i=r(o),a=n(8),s=r(a),l=n(5),u=r(l),c=n(7),d=r(c),f=n(6),p=r(f),h=n(9),m=r(h),v=n(1),y=r(v),g=n(16),b=r(g),x=n(10),E={componentClass:b.default},_={componentClass:"h4"},w=function(e){function t(){return(0,u.default)(this,t),(0,d.default)(this,e.apply(this,arguments))}return(0,p.default)(t,e),t.prototype.render=function(){var e=this.props,t=e.componentClass,n=e.className,r=(0,s.default)(e,["componentClass","className"]),o=(0,x.splitBsProps)(r),a=o[0],l=o[1],u=(0,x.getClassSet)(a);return y.default.createElement(t,(0,i.default)({},l,{className:(0,m.default)(n,u)}))},t}(y.default.Component);w.propTypes=E,w.defaultProps=_,t.default=(0,x.bsClass)("modal-title",w),e.exports=t.default},function(e,t,n){(function(r){"use strict";function o(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var i=n(4),a=o(i),s=n(8),l=o(s),u=n(5),c=o(u),d=n(7),f=o(d),p=n(6),h=o(p),m=n(9),v=o(m),y=n(166),g=o(y),b=n(1),x=o(b),E=n(3),_=o(E),w=n(18),C=o(w),O=n(126),k=o(O),T=n(17),N=o(T),P=n(10),S=n(23),M=o(S),A=n(27),D=o(A),R={activeKey:_.default.any,activeHref:_.default.string,stacked:_.default.bool,justified:(0,k.default)(_.default.bool,function(e){var t=e.justified,n=e.navbar;return t&&n?Error("justified navbar `Nav`s are not supported"):null}),onSelect:_.default.func,role:_.default.string,navbar:_.default.bool,pullRight:_.default.bool,pullLeft:_.default.bool},j={justified:!1,pullRight:!1,pullLeft:!1,stacked:!1},I={$bs_navbar:_.default.shape({bsClass:_.default.string,onSelect:_.default.func}),$bs_tabContainer:_.default.shape({activeKey:_.default.any,onSelect:_.default.func.isRequired,getTabId:_.default.func.isRequired,getPaneId:_.default.func.isRequired})},L=function(e){function t(){return(0,c.default)(this,t),(0,f.default)(this,e.apply(this,arguments))}return(0,h.default)(t,e),t.prototype.componentDidUpdate=function(){var e=this;if(this._needsRefocus){this._needsRefocus=!1;var t=this.props.children,n=this.getActiveProps(),r=n.activeKey,o=n.activeHref,i=D.default.find(t,function(t){return e.isActive(t,r,o)}),a=D.default.toArray(t),s=a.indexOf(i),l=C.default.findDOMNode(this).children,u=l&&l[s];u&&u.firstChild&&u.firstChild.focus()}},t.prototype.handleTabKeyDown=function(e,t){var n=void 0;switch(t.keyCode){case g.default.codes.left:case g.default.codes.up:n=this.getNextActiveChild(-1);break;case g.default.codes.right:case g.default.codes.down:n=this.getNextActiveChild(1);break;default:return}t.preventDefault(),e&&n&&null!=n.props.eventKey&&e(n.props.eventKey),this._needsRefocus=!0},t.prototype.getNextActiveChild=function(e){var t=this,n=this.props.children,r=n.filter(function(e){return null!=e.props.eventKey&&!e.props.disabled}),o=this.getActiveProps(),i=o.activeKey,a=o.activeHref,s=D.default.find(n,function(e){return t.isActive(e,i,a)}),l=r.indexOf(s);if(l===-1)return r[0];var u=l+e,c=r.length;return u>=c?u=0:u<0&&(u=c-1),r[u]},t.prototype.getActiveProps=function(){var e=this.context.$bs_tabContainer;return e?("production"!==r.env.NODE_ENV?(0,N.default)(null==this.props.activeKey&&!this.props.activeHref,"Specifying a `