| @@ -8,3 +8,7 @@ craftbeerpi.db | |||||
| modules/plugins/* | modules/plugins/* | ||||
| !modules/plugins/__init__.py | !modules/plugins/__init__.py | ||||
| *.pyc | *.pyc | ||||
| *.js | |||||
| modules/ui/package.json | |||||
| modules/ui/.babelrc | |||||
| @@ -86,6 +86,9 @@ INSERT INTO config VALUES ('unit', 'C', 'select', 'Temperature Unit', '["C","F"] | |||||
| INSERT INTO config VALUES ('brewery_name', 'My Home Brewery', 'text', 'Your brewery name', NULL ); | INSERT INTO config VALUES ('brewery_name', 'My Home Brewery', 'text', 'Your brewery name', NULL ); | ||||
| INSERT INTO config VALUES ('buzzer', 16, 'select', 'Buzzer GPIO', '[16,17,18,19,20]'); | INSERT INTO config VALUES ('buzzer', 16, 'select', 'Buzzer GPIO', '[16,17,18,19,20]'); | ||||
| INSERT INTO config VALUES ('setup', 'YES', 'select', 'Show the Setup dialog', '["YES","NO"]'); | INSERT INTO config VALUES ('setup', 'YES', 'select', 'Show the Setup dialog', '["YES","NO"]'); | ||||
| INSERT INTO config VALUES ('brew_name', '', 'text', 'Brew Name', NULL); | |||||
| INSERT INTO config VALUES ('donation_notification', 'YES', 'select', 'Disable Donation Notification', '["YES","NO"]'); | |||||
| CREATE TABLE actor | CREATE TABLE actor | ||||
| ( | ( | ||||
| @@ -0,0 +1 @@ | |||||
| 3.0.1 | |||||
| @@ -63,3 +63,6 @@ cbpi.run_background_processes() | |||||
| app.logger.info("##########################################") | |||||
| app.logger.info("### STARTUP COMPLETE") | |||||
| app.logger.info("##########################################") | |||||
| @@ -15,7 +15,9 @@ from modules.core.db import DBModel | |||||
| app = Flask(__name__) | app = Flask(__name__) | ||||
| logging.basicConfig(filename='./logs/app.log',level=logging.INFO) | |||||
| 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['SECRET_KEY'] = 'craftbeerpi' | ||||
| app.config['UPLOAD_FOLDER'] = './upload' | app.config['UPLOAD_FOLDER'] = './upload' | ||||
| @@ -45,7 +47,7 @@ class ComplexEncoder(json.JSONEncoder): | |||||
| return None | return None | ||||
| app.json_encoder = ComplexEncoder | app.json_encoder = ComplexEncoder | ||||
| socketio = SocketIO(app, json=json) | |||||
| socketio = SocketIO(app, json=json, logging=False) | |||||
| cbpi = CraftBeerPi(app, socketio) | cbpi = CraftBeerPi(app, socketio) | ||||
| app.logger.info("##########################################") | app.logger.info("##########################################") | ||||
| @@ -7,6 +7,7 @@ from modules.core.step import StepBase | |||||
| from modules import cbpi | from modules import cbpi | ||||
| @cbpi.step | @cbpi.step | ||||
| class MashStep(StepBase): | class MashStep(StepBase): | ||||
| ''' | ''' | ||||
| @@ -69,6 +70,10 @@ class MashInStep(StepBase): | |||||
| kettle = StepProperty.Kettle("Kettle") | kettle = StepProperty.Kettle("Kettle") | ||||
| s = False | s = False | ||||
| @cbpi.action("Change Power") | |||||
| def change_power(self): | |||||
| self.actor_power(1, 50) | |||||
| def init(self): | def init(self): | ||||
| ''' | ''' | ||||
| Initialize Step. This method is called once at the beginning of the step | Initialize Step. This method is called once at the beginning of the step | ||||
| @@ -93,3 +98,125 @@ class MashInStep(StepBase): | |||||
| @cbpi.step | |||||
| class ChilStep(StepBase): | |||||
| timer = Property.Number("Timer in Minutes", configurable=True, default_value=0) | |||||
| @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") | |||||
| timer = Property.Number("Timer in Minutes", configurable=True, default_value=0) | |||||
| @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) | |||||
| kettle = StepProperty.Kettle("Kettle") | |||||
| timer = Property.Number("Timer in Minutes", configurable=True, default_value=90) | |||||
| hop_1 = Property.Number("Hop 1 Addition", configurable=True) | |||||
| hop_1_added = Property.Number("",default_value=None) | |||||
| hop_2 = Property.Number("Hop 2 Addition", configurable=True) | |||||
| 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) | |||||
| 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) >= int(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.next() | |||||
| @@ -10,21 +10,37 @@ from modules.core.props import Property | |||||
| @cbpi.sensor | @cbpi.sensor | ||||
| class DummyTempSensor(SensorActive): | class DummyTempSensor(SensorActive): | ||||
| temp = Property.Number("Temperature", configurable=True, default_value=5) | temp = Property.Number("Temperature", configurable=True, default_value=5) | ||||
| def get_unit(self): | 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" | return "°C" if self.get_config_parameter("unit", "C") == "C" else "°F" | ||||
| def stop(self): | def stop(self): | ||||
| ''' | |||||
| Stop the sensor. Is called when the sensor config is updated or the sensor is deleted | |||||
| :return: | |||||
| ''' | |||||
| pass | pass | ||||
| def execute(self): | def execute(self): | ||||
| ''' | |||||
| Active sensor has to handle his own loop | |||||
| :return: | |||||
| ''' | |||||
| while self.is_running(): | while self.is_running(): | ||||
| self.data_received(self.temp) | self.data_received(self.temp) | ||||
| socketio.sleep(5) | socketio.sleep(5) | ||||
| @classmethod | |||||
| def init_global(cls): | |||||
| ''' | |||||
| Called one at the startup for all sensors | |||||
| :return: | |||||
| ''' | |||||
| @@ -57,6 +57,11 @@ class GPIOPWM(ActorBase): | |||||
| self.p.start(int(self.power)) | self.p.start(int(self.power)) | ||||
| def set_power(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: | if power is not None: | ||||
| self.power = int(power) | self.power = int(power) | ||||
| self.p.ChangeDutyCycle(self.power) | self.p.ChangeDutyCycle(self.power) | ||||
| @@ -87,7 +92,13 @@ class RelayBoard(ActorBase): | |||||
| class Dummy(ActorBase): | class Dummy(ActorBase): | ||||
| def on(self, power=100): | def on(self, power=100): | ||||
| ''' | |||||
| Code to switch on the actor | |||||
| :param power: int value between 0 - 100 | |||||
| :return: | |||||
| ''' | |||||
| print "ON" | print "ON" | ||||
| def off(self): | def off(self): | ||||
| print "OFF" | print "OFF" | ||||
| @@ -43,8 +43,6 @@ class Buzzer(object): | |||||
| @cbpi.initalizer(order=0) | @cbpi.initalizer(order=0) | ||||
| def init(cbpi): | def init(cbpi): | ||||
| print "INIT BUZZER" | |||||
| cbpi.app.logger.info("INIT BUZZER") | |||||
| gpio = cbpi.get_config_parameter("buzzer", 16) | gpio = cbpi.get_config_parameter("buzzer", 16) | ||||
| cbpi.buzzer = Buzzer(gpio) | cbpi.buzzer = Buzzer(gpio) | ||||
| cbpi.beep() | cbpi.beep() | ||||
| @@ -25,6 +25,7 @@ class ConfigView(BaseView): | |||||
| if self.api.cache.get(self.cache_key) is not None: | if self.api.cache.get(self.cache_key) is not None: | ||||
| #self.pre_post_callback(self.api.cache.get(self.cache_key)[name]) | #self.pre_post_callback(self.api.cache.get(self.cache_key)[name]) | ||||
| print self.api.cache.get(self.cache_key)[name] | |||||
| self.api.cache.get(self.cache_key)[name].__dict__.update(**update_data) | 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__) | 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.post_put_callback(self.api.cache.get(self.cache_key)[name]) | ||||
| @@ -51,7 +52,6 @@ class ConfigView(BaseView): | |||||
| cls.post_init_callback(value) | cls.post_init_callback(value) | ||||
| cls.api.cache[cls.cache_key][value.name] = value | cls.api.cache[cls.cache_key][value.name] = value | ||||
| @cbpi.initalizer(order=1) | @cbpi.initalizer(order=1) | ||||
| def init(cbpi): | def init(cbpi): | ||||
| print "INITIALIZE CONFIG MODULE" | print "INITIALIZE CONFIG MODULE" | ||||
| @@ -1,3 +1,4 @@ | |||||
| import inspect | |||||
| import pprint | import pprint | ||||
| import sqlite3 | import sqlite3 | ||||
| @@ -28,6 +29,7 @@ class ActorAPI(object): | |||||
| self.app.logger.info("Init Actors") | self.app.logger.info("Init Actors") | ||||
| t = self.cache.get("actor_types") | t = self.cache.get("actor_types") | ||||
| for key, value in t.iteritems(): | for key, value in t.iteritems(): | ||||
| value.get("class").api = self | |||||
| value.get("class").init_global() | value.get("class").init_global() | ||||
| for key in self.cache.get("actors"): | for key in self.cache.get("actors"): | ||||
| @@ -73,12 +75,8 @@ class ActorAPI(object): | |||||
| if actor.state == 0: | if actor.state == 0: | ||||
| return | return | ||||
| actor.instance.off() | actor.instance.off() | ||||
| actor.state = 0 | actor.state = 0 | ||||
| self.emit("SWITCH_ACTOR", actor) | self.emit("SWITCH_ACTOR", actor) | ||||
| class SensorAPI(object): | class SensorAPI(object): | ||||
| @@ -231,7 +229,6 @@ class CraftBeerPi(ActorAPI, SensorAPI): | |||||
| self.emit("NOTIFY", msg) | self.emit("NOTIFY", msg) | ||||
| def beep(self): | def beep(self): | ||||
| if self.buzzer is not None: | if self.buzzer is not None: | ||||
| self.buzzer.beep() | self.buzzer.beep() | ||||
| @@ -286,6 +283,15 @@ class CraftBeerPi(ActorAPI, SensorAPI): | |||||
| def actor(self, cls): | def actor(self, cls): | ||||
| return self.__parseProps("actor_types", cls) | return self.__parseProps("actor_types", cls) | ||||
| def actor2(self, description="", power=True, **options): | |||||
| def decorator(f): | |||||
| print f() | |||||
| print options | |||||
| print description | |||||
| return f | |||||
| return decorator | |||||
| def sensor(self, cls): | def sensor(self, cls): | ||||
| return self.__parseProps("sensor_types", cls) | return self.__parseProps("sensor_types", cls) | ||||
| @@ -320,9 +326,13 @@ class CraftBeerPi(ActorAPI, SensorAPI): | |||||
| for m in members: | for m in members: | ||||
| if isinstance(tmpObj.__getattribute__(m), StepProperty.Number): | if isinstance(tmpObj.__getattribute__(m), StepProperty.Number): | ||||
| t = tmpObj.__getattribute__(m) | t = tmpObj.__getattribute__(m) | ||||
| self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "number", "configurable": t.configurable}) | |||||
| print t.__dict__ | |||||
| #self.cache[key][name]["properties"].append(t.__dict__) | |||||
| self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "number", "configurable": t.configurable, "default_value": t.default_value}) | |||||
| elif isinstance(tmpObj.__getattribute__(m), StepProperty.Text): | elif isinstance(tmpObj.__getattribute__(m), StepProperty.Text): | ||||
| t = tmpObj.__getattribute__(m) | t = tmpObj.__getattribute__(m) | ||||
| print t.__dict__ | |||||
| #self.cache[key][name]["properties"].append(t.__dict__) | |||||
| self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "text", "configurable": t.configurable}) | self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "text", "configurable": t.configurable}) | ||||
| elif isinstance(tmpObj.__getattribute__(m), StepProperty.Select): | elif isinstance(tmpObj.__getattribute__(m), StepProperty.Select): | ||||
| t = tmpObj.__getattribute__(m) | t = tmpObj.__getattribute__(m) | ||||
| @@ -420,7 +430,7 @@ class CraftBeerPi(ActorAPI, SensorAPI): | |||||
| self.app.logger.info("Invoke Init") | self.app.logger.info("Invoke Init") | ||||
| self.cache["init"] = sorted(self.cache["init"], key=lambda k: k['order']) | self.cache["init"] = sorted(self.cache["init"], key=lambda k: k['order']) | ||||
| for i in self.cache.get("init"): | for i in self.cache.get("init"): | ||||
| self.app.logger.info("-> %s " % i.get("function").__name__) | |||||
| self.app.logger.info("INITIALIZER - METHOD %s PAHT %s: " % (i.get("function").__name__, str(inspect.getmodule(i.get("function")).__file__) )) | |||||
| i.get("function")(self) | i.get("function")(self) | ||||
| @@ -42,13 +42,15 @@ class DBModel(object): | |||||
| def get_all(cls): | def get_all(cls): | ||||
| cur = get_db().cursor() | cur = get_db().cursor() | ||||
| if cls.__order_by__ is not None: | if cls.__order_by__ is not None: | ||||
| cur.execute("SELECT * FROM %s ORDER BY '%s'" % (cls.__table_name__,cls.__order_by__)) | |||||
| cur.execute("SELECT * FROM %s ORDER BY %s.'%s'" % (cls.__table_name__,cls.__table_name__,cls.__order_by__)) | |||||
| else: | else: | ||||
| cur.execute("SELECT * FROM %s" % cls.__table_name__) | cur.execute("SELECT * FROM %s" % cls.__table_name__) | ||||
| if cls.__as_array__ is True: | if cls.__as_array__ is True: | ||||
| result = [] | result = [] | ||||
| for r in cur.fetchall(): | for r in cur.fetchall(): | ||||
| result.append( cls(r)) | result.append( cls(r)) | ||||
| else: | else: | ||||
| result = {} | result = {} | ||||
| @@ -104,7 +106,7 @@ class DBModel(object): | |||||
| else: | else: | ||||
| data = data + (kwargs.get(f),) | data = data + (kwargs.get(f),) | ||||
| print query, data | |||||
| cur.execute(query, data) | cur.execute(query, data) | ||||
| get_db().commit() | get_db().commit() | ||||
| i = cur.lastrowid | i = cur.lastrowid | ||||
| @@ -10,6 +10,9 @@ class Base(object): | |||||
| def get_config_parameter(self, key, default_value): | def get_config_parameter(self, key, default_value): | ||||
| return self.api.get_config_parameter(key, default_value) | return self.api.get_config_parameter(key, default_value) | ||||
| def sleep(self, seconds): | |||||
| self.api.socketio.sleep(seconds) | |||||
| def init(self): | def init(self): | ||||
| print "INIT BASE" | print "INIT BASE" | ||||
| @@ -9,10 +9,11 @@ class Property(object): | |||||
| self.options = options | self.options = options | ||||
| class Number(PropertyType): | class Number(PropertyType): | ||||
| def __init__(self, label, configurable=False, default_value=0, unit=""): | |||||
| def __init__(self, label, configurable=False, default_value=None, unit=""): | |||||
| PropertyType.__init__(self) | PropertyType.__init__(self) | ||||
| self.label = label | self.label = label | ||||
| self.configurable = configurable | self.configurable = configurable | ||||
| self.default_value = default_value | |||||
| class Text(PropertyType): | class Text(PropertyType): | ||||
| def __init__(self, label, configurable=False, default_value=""): | def __init__(self, label, configurable=False, default_value=""): | ||||
| @@ -44,9 +44,8 @@ def init(cbpi): | |||||
| :param app: the flask app | :param app: the flask app | ||||
| :return: None | :return: None | ||||
| """ | """ | ||||
| print "INITIALIZE MESSAGE MODULE" | |||||
| 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) | |||||
| if cbpi.get_config_parameter("donation_notification", "YES") == "YES": | |||||
| 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') | ||||
| @@ -1,5 +1,5 @@ | |||||
| import time | import time | ||||
| from flask import json | |||||
| from flask import json, request | |||||
| from flask_classy import route | from flask_classy import route | ||||
| from modules import DBModel, cbpi, get_db | from modules import DBModel, cbpi, get_db | ||||
| @@ -21,9 +21,9 @@ class Step(DBModel): | |||||
| return r.get("order") | return r.get("order") | ||||
| @classmethod | @classmethod | ||||
| def get_by_state(cls, state): | |||||
| def get_by_state(cls, state, order=True): | |||||
| cur = get_db().cursor() | cur = get_db().cursor() | ||||
| cur.execute("SELECT * FROM %s WHERE state = ?" % cls.__table_name__, state) | |||||
| cur.execute("SELECT * FROM %s WHERE state = ? ORDER BY %s.'order'" % (cls.__table_name__,cls.__table_name__,), state) | |||||
| r = cur.fetchone() | r = cur.fetchone() | ||||
| if r is not None: | if r is not None: | ||||
| return cls(r) | return cls(r) | ||||
| @@ -54,6 +54,16 @@ class Step(DBModel): | |||||
| cur.execute("UPDATE %s SET stepstate = ? WHERE id =?" % cls.__table_name__, (json.dumps(state),id)) | cur.execute("UPDATE %s SET stepstate = ? WHERE id =?" % cls.__table_name__, (json.dumps(state),id)) | ||||
| get_db().commit() | 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): | class StepView(BaseView): | ||||
| model = Step | model = Step | ||||
| def pre_post_callback(self, data): | def pre_post_callback(self, data): | ||||
| @@ -61,6 +71,14 @@ class StepView(BaseView): | |||||
| data["order"] = 1 if order is None else order + 1 | data["order"] = 1 if order is None else order + 1 | ||||
| data["state"] = "I" | 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"]) | @route('/', methods=["DELETE"]) | ||||
| def deleteAll(self): | def deleteAll(self): | ||||
| self.model.delete_all() | self.model.delete_all() | ||||
| @@ -185,8 +203,10 @@ def init_after_startup(): | |||||
| if type_cfg is None: | if type_cfg is None: | ||||
| # step type not found. cant restart step | # step type not found. cant restart step | ||||
| return | return | ||||
| print "STEP SATE......", step.stepstate | |||||
| cfg = step.stepstate.copy() | cfg = step.stepstate.copy() | ||||
| cfg.update(dict(api=cbpi, id=step.id, timer_end=None, managed_fields=get_manged_fields_as_array(type_cfg))) | |||||
| cfg.update(dict(api=cbpi, id=step.id, managed_fields=get_manged_fields_as_array(type_cfg))) | |||||
| instance = type_cfg.get("class")(**cfg) | instance = type_cfg.get("class")(**cfg) | ||||
| instance.init() | instance.init() | ||||
| cbpi.cache["active_step"] = instance | cbpi.cache["active_step"] = instance | ||||
| @@ -214,9 +234,7 @@ def execute_step(): | |||||
| step = cbpi.cache.get("active_step") | step = cbpi.cache.get("active_step") | ||||
| if step is not None: | if step is not None: | ||||
| step.execute() | step.execute() | ||||
| if step.is_dirty(): | if step.is_dirty(): | ||||
| state = {} | state = {} | ||||
| for field in step.managed_fields: | for field in step.managed_fields: | ||||
| state[field] = step.__getattribute__(field) | state[field] = step.__getattribute__(field) | ||||
| @@ -9,6 +9,8 @@ def init(cbpi): | |||||
| cbpi.app.register_blueprint(react, url_prefix='/ui') | cbpi.app.register_blueprint(react, url_prefix='/ui') | ||||
| @react.route('/') | @react.route('/') | ||||
| def index(): | def index(): | ||||
| return react.send_static_file("index.html") | return react.send_static_file("index.html") | ||||
| @@ -0,0 +1,16 @@ | |||||
| .App { | |||||
| } | |||||
| .container { | |||||
| padding-right: 5px; | |||||
| padding-left: 5px; | |||||
| margin-right: auto; | |||||
| margin-left: auto; | |||||
| } | |||||
| .container-fluid { | |||||
| padding-right: 5px; | |||||
| padding-left: 5px; | |||||
| } | |||||
| @@ -0,0 +1,5 @@ | |||||
| body { | |||||
| margin: 0; | |||||
| padding: 0; | |||||
| font-family: sans-serif; | |||||
| } | |||||
| @@ -13,6 +13,8 @@ | |||||
| <style> | <style> | ||||
| .col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { | .col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { | ||||
| position: relative; | position: relative; | ||||
| min-height: 1px; | min-height: 1px; | ||||