- Several UI Adjustments - still work in progress - New API Method to add custom JavaScript to CraftBeerPi - Global Action Buttons added - Register new Web Modules for complete custom UItags/3.1_alpha
| @@ -1,5 +1,7 @@ | |||
| import time | |||
| from flask_classy import route | |||
| from flask_login import login_required | |||
| from modules.core.db import DBModel | |||
| from modules.core.core import cbpi | |||
| from modules.core.baseview import BaseView | |||
| @@ -21,6 +23,7 @@ class ActorView(BaseView): | |||
| def _post_put_callback(self, m): | |||
| self.api.actor.init_one(m.id) | |||
| @login_required | |||
| @route("<int:id>/switch/on", methods=["POST"]) | |||
| def on(self, id): | |||
| """ | |||
| @@ -42,6 +45,7 @@ class ActorView(BaseView): | |||
| self.api.actor.on(id) | |||
| return ('', 204) | |||
| @login_required | |||
| @route("<int:id>/switch/off", methods=["POST"]) | |||
| def off(self, id): | |||
| """ | |||
| @@ -63,6 +67,7 @@ class ActorView(BaseView): | |||
| self.api.actor.off(id) | |||
| return ('', 204) | |||
| @login_required | |||
| @route("<int:id>/power/<int:power>", methods=["POST"]) | |||
| def power(self, id, power): | |||
| """ | |||
| @@ -90,6 +95,7 @@ class ActorView(BaseView): | |||
| self.api.actor.power(id, power) | |||
| return ('', 204) | |||
| @login_required | |||
| @route("<int:id>/toggle", methods=["POST"]) | |||
| def toggle(self, id): | |||
| """ | |||
| @@ -108,10 +114,7 @@ class ActorView(BaseView): | |||
| 200: | |||
| description: Actor toggled | |||
| """ | |||
| if self.api.cache.get("actors").get(id).state == 0: | |||
| self.on(id) | |||
| else: | |||
| self.off(id) | |||
| cbpi.actor.toggle(id) | |||
| return ('', 204) | |||
| def toggleTimeJob(self, id, t): | |||
| @@ -121,6 +124,7 @@ class ActorView(BaseView): | |||
| self.api.cache.get("actors").get(int(id)).timer = None | |||
| self.toggle(int(id)) | |||
| @login_required | |||
| @route("/<id>/toggle/<int:t>", methods=["POST"]) | |||
| def toggleTime(self, id, t): | |||
| """ | |||
| @@ -145,9 +149,11 @@ class ActorView(BaseView): | |||
| 200: | |||
| description: Actor toggled | |||
| """ | |||
| t = self.api._socketio.start_background_task(target=self.toggleTimeJob, id=id, t=t) | |||
| self.api.actor.toggle_timeout(id, t) | |||
| #t = self.api._socketio.start_background_task(target=self.toggleTimeJob, id=id, t=t) | |||
| return ('', 204) | |||
| @login_required | |||
| @route('<int:id>/action/<method>', methods=["POST"]) | |||
| def action(self, id, method): | |||
| """ | |||
| @@ -172,7 +178,8 @@ class ActorView(BaseView): | |||
| 200: | |||
| description: Actor Action called | |||
| """ | |||
| cbpi.cache.get("actors").get(id).instance.__getattribute__(method)() | |||
| self.api.actor.action(id, method) | |||
| return ('', 204) | |||
| @@ -26,7 +26,6 @@ class Dummy(Actor): | |||
| @cbpi.addon.kettle.controller() | |||
| class MyController(KettleController): | |||
| def run(self): | |||
| while self.is_running(): | |||
| print "HALLO" | |||
| @@ -1,4 +1,8 @@ | |||
| # -*- coding: utf-8 -*- | |||
| import os | |||
| from os.path import join | |||
| from modules.core.basetypes import Actor, Sensor | |||
| from modules.core.core import cbpi | |||
| from modules.core.proptypes import Property | |||
| @@ -33,4 +37,11 @@ class Dummy(Sensor): | |||
| @cbpi.addon.core.action(key="clear", label="Clear all Logs") | |||
| def woohoo(cbpi): | |||
| print "COOL" | |||
| cbpi.notify(headline="HELLO WORLD",message="") | |||
| dir = "./logs" | |||
| test = os.listdir(dir) | |||
| for item in test: | |||
| if item.endswith(".log"): | |||
| os.remove(join(dir, item)) | |||
| cbpi.notify(headline="Logs Deleted",message="All Logs Cleared") | |||
| @@ -7,6 +7,10 @@ from modules.core.proptypes import Property | |||
| class Dummy(Step): | |||
| @cbpi.addon.step.action("WOHOO") | |||
| def myaction(self): | |||
| print "HALLO" | |||
| text = Property.Text(label="Text", configurable=True, description="WOHOOO") | |||
| def execute(self): | |||
| @@ -87,6 +87,9 @@ class StepAPI(BaseAPI): | |||
| return func | |||
| return real_decorator | |||
| class ActorAPI(BaseAPI): | |||
| key = "actor_types" | |||
| @@ -150,6 +153,8 @@ class CoreAPI(BaseAPI): | |||
| self.cbpi.cache["init"] = [] | |||
| self.cbpi.cache["js"] = {} | |||
| self.cbpi.cache["background"] = [] | |||
| self.cbpi.cache["web_menu"] =[] | |||
| def init(self): | |||
| self.cbpi.cache["init"] = sorted(self.cbpi.cache["init"], key=lambda k: k['order']) | |||
| @@ -173,6 +178,9 @@ class CoreAPI(BaseAPI): | |||
| def add_js(self, name, file): | |||
| self.cbpi.cache["js"][name] = file | |||
| def add_menu_link(self, name, path): | |||
| self.cbpi.cache["web_menu"].append(dict(name=name, path=path)) | |||
| def initializer(self, order=0, **options): | |||
| def decorator(f): | |||
| self.cbpi.cache.get("init").append({"function": f, "order": order}) | |||
| @@ -246,4 +246,5 @@ class Step(Base): | |||
| self.__dirty = True | |||
| super(Step, self).__setattr__(name, value) | |||
| else: | |||
| super(Step, self).__setattr__(name, value) | |||
| super(Step, self).__setattr__(name, value) | |||
| @@ -1,5 +1,7 @@ | |||
| from flask import request, json | |||
| from flask_classy import route, FlaskView | |||
| from flask_login import login_required | |||
| from modules.core.core import cbpi | |||
| @@ -9,6 +11,7 @@ class BaseView(FlaskView): | |||
| cache_key = None | |||
| api = cbpi | |||
| @login_required | |||
| @route('/<int:id>', methods=["GET"]) | |||
| def getOne(self, id): | |||
| if self.api.cache.get(self.cache_key) is not None: | |||
| @@ -16,6 +19,7 @@ class BaseView(FlaskView): | |||
| else: | |||
| return json.dumps(self.model.get_one(id)) | |||
| @login_required | |||
| @route('/', methods=["GET"]) | |||
| def getAll(self): | |||
| if self.api.cache.get(self.cache_key) is not None: | |||
| @@ -30,6 +34,7 @@ class BaseView(FlaskView): | |||
| def _post_post_callback(self, m): | |||
| pass | |||
| @login_required | |||
| @route('/', methods=["POST"]) | |||
| def post(self): | |||
| @@ -50,7 +55,7 @@ class BaseView(FlaskView): | |||
| def _post_put_callback(self, m): | |||
| pass | |||
| @login_required | |||
| @route('/<int:id>', methods=["PUT"]) | |||
| def put(self, id): | |||
| data = request.json | |||
| @@ -78,6 +83,7 @@ class BaseView(FlaskView): | |||
| def _post_delete_callback(self, id): | |||
| pass | |||
| @login_required | |||
| @route('/<int:id>', methods=["DELETE"]) | |||
| def delete(self, id): | |||
| if self.api.cache.get(self.cache_key) is not None: | |||
| @@ -7,6 +7,7 @@ from datetime import datetime | |||
| from functools import wraps, update_wrapper | |||
| from importlib import import_module | |||
| from time import localtime, strftime | |||
| import time | |||
| from flask import Flask, redirect, json, g, make_response | |||
| from flask_socketio import SocketIO | |||
| @@ -17,6 +18,7 @@ from modules.core.basetypes import Sensor, Actor | |||
| from modules.database.dbmodel import Kettle | |||
| class ComplexEncoder(json.JSONEncoder): | |||
| def default(self, obj): | |||
| try: | |||
| @@ -109,6 +111,12 @@ class ActorCore(object): | |||
| print e | |||
| return False | |||
| def toggle(self, id): | |||
| if self.cbpi.cache.get("actors").get(id).state == 0: | |||
| self.on(id) | |||
| else: | |||
| self.off(id) | |||
| def power(self, id, power): | |||
| try: | |||
| actor = self.cbpi.cache["actors"].get(int(id)) | |||
| @@ -121,6 +129,20 @@ class ActorCore(object): | |||
| print e | |||
| return False | |||
| def action(self, id, method): | |||
| self.cbpi.cache.get("actors").get(id).instance.__getattribute__(method)() | |||
| def toggle_timeout(self, id, seconds): | |||
| def toggle( id, seconds): | |||
| self.cbpi.cache.get("actors").get(int(id)).timer = int(time.time()) + int(seconds) | |||
| self.toggle(int(id)) | |||
| self.cbpi.sleep(seconds) | |||
| self.cbpi.cache.get("actors").get(int(id)).timer = None | |||
| self.toggle(int(id)) | |||
| job = self.cbpi._socketio.start_background_task(target=toggle, id=id, seconds=seconds) | |||
| def get_state(self, actor_id): | |||
| print actor_id | |||
| print self.cbpi | |||
| @@ -183,6 +205,10 @@ class SensorCore(object): | |||
| with open(filename, "a") as file: | |||
| file.write(msg) | |||
| def action(self, id, method): | |||
| self.cbpi.cache.get("sensors").get(id).instance.__getattribute__(method)() | |||
| class BrewingCore(object): | |||
| def __init__(self, cbpi): | |||
| @@ -207,6 +233,32 @@ class BrewingCore(object): | |||
| self.cbpi.emit("SET_KETTLE_TARGET_TEMP", id=id, temp=temp) | |||
| def toggle_automatic(self, id): | |||
| kettle = self.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 = self.get_controller(kettle.logic).get("class")(**cfg) | |||
| instance.init() | |||
| kettle.instance = instance | |||
| def run(instance): | |||
| instance.run() | |||
| t = self.cbpi._socketio.start_background_task(target=run, instance=instance) | |||
| kettle.state = not kettle.state | |||
| self.cbpi.ws_emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(id)) | |||
| self.cbpi.emit("KETTLE_CONTROLLER_STARTED", id=id) | |||
| else: | |||
| # Stop controller | |||
| kettle.instance.stop() | |||
| kettle.state = not kettle.state | |||
| self.cbpi.ws_emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(id)) | |||
| self.cbpi.emit("KETTLE_CONTROLLER_STOPPED", id=id) | |||
| class FermentationCore(object): | |||
| def __init__(self, cbpi): | |||
| self.cbpi = cbpi | |||
| @@ -313,7 +365,7 @@ class CraftBeerPI(object): | |||
| 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: | |||
| @@ -0,0 +1,44 @@ | |||
| import sqlite3 | |||
| import os | |||
| from modules.core.core import cbpi | |||
| from modules.core.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.addon.core.initializer(order=-9999) | |||
| def init(cbpi): | |||
| 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"): | |||
| print filename | |||
| if filename.endswith(".sql"): | |||
| d = {"version": int(filename[:filename.index('_')]), "file": filename} | |||
| result.append(d) | |||
| execute_file(current_version, d) | |||
| @@ -1,36 +0,0 @@ | |||
| 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 :-(' | |||
| @@ -214,39 +214,9 @@ class KettleView(BaseView): | |||
| 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) | |||
| self.api.brewing.toggle_automatic(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): | |||
| """ | |||
| @@ -0,0 +1,57 @@ | |||
| import flask_login | |||
| from flask import request | |||
| 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=['POST']) | |||
| def login(): | |||
| data = request.json | |||
| password = cbpi.get_config_parameter("password", None) | |||
| if password is None: | |||
| return ('',500) | |||
| if password == data.get("password",""): | |||
| user = User() | |||
| user.id = "craftbeerpi" | |||
| flask_login.login_user(user) | |||
| return ('',204) | |||
| else: | |||
| return ('',401) | |||
| @cbpi._app.route('/logout', methods=['POST']) | |||
| def logout(): | |||
| flask_login.logout_user() | |||
| return 'Logged out' | |||
| @cbpi._login_manager.user_loader | |||
| def user_loader(user): | |||
| print cbpi.get_config_parameter("password_security", "NO") | |||
| print user | |||
| if cbpi.get_config_parameter("password_security", "NO") == "YES": | |||
| if user != "craftbeerpi": | |||
| return | |||
| user = User() | |||
| user.id = user | |||
| return user | |||
| else: | |||
| user = User() | |||
| user.id = user | |||
| return user | |||
| @cbpi._login_manager.unauthorized_handler | |||
| def unauthorized_handler(): | |||
| return ('Please login',401) | |||
| @@ -34,7 +34,7 @@ class SensorView(BaseView): | |||
| 200: | |||
| description: Sensor Action called | |||
| """ | |||
| cbpi.cache.get("sensors").get(id).instance.__getattribute__(method)() | |||
| cbpi.sensor.action(id, method) | |||
| return ('', 204) | |||
| def _post_post_callback(self, m): | |||
| @@ -1,6 +1,7 @@ | |||
| import yaml | |||
| from flask import json, url_for, Response | |||
| from flask_classy import FlaskView, route | |||
| from flask_login import login_required, current_user | |||
| from git import Repo, Git | |||
| from modules.core.core import cbpi | |||
| import pprint | |||
| @@ -13,6 +14,7 @@ class SystemView(FlaskView): | |||
| from subprocess import call | |||
| call("halt") | |||
| @login_required | |||
| @route('/shutdown', methods=['POST']) | |||
| def shutdown(self): | |||
| """ | |||
| @@ -33,6 +35,7 @@ class SystemView(FlaskView): | |||
| from subprocess import call | |||
| call("reboot") | |||
| @login_required | |||
| @route('/reboot', methods=['POST']) | |||
| def reboot(self): | |||
| """ | |||
| @@ -47,6 +50,7 @@ class SystemView(FlaskView): | |||
| self.doReboot() | |||
| return ('', 204) | |||
| @login_required | |||
| @route('/tags/<name>', methods=['GET']) | |||
| def checkout_tag(self,name): | |||
| repo = Repo('./') | |||
| @@ -58,6 +62,7 @@ class SystemView(FlaskView): | |||
| cbpi.notify("Checkout successful", "Please restart the system") | |||
| return ('', 204) | |||
| @login_required | |||
| @route('/git/status', methods=['GET']) | |||
| def git_status(self): | |||
| """ | |||
| @@ -92,6 +97,7 @@ class SystemView(FlaskView): | |||
| return json.dumps({"tags": tags, "headcommit": str(repo.head.commit), "branchname": branch_name, | |||
| "master": {"changes": changes}}) | |||
| @login_required | |||
| @route('/check_update', methods=['GET']) | |||
| def check_update(self): | |||
| """ | |||
| @@ -114,6 +120,7 @@ class SystemView(FlaskView): | |||
| return json.dumps(changes) | |||
| @login_required | |||
| @route('/git/pull', methods=['POST']) | |||
| def update(self): | |||
| """ | |||
| @@ -131,6 +138,24 @@ class SystemView(FlaskView): | |||
| cbpi.notify("Pull successful", "The lasted updated was downloaded. Please restart the system") | |||
| return ('', 204) | |||
| @route('/connect', methods=['GET']) | |||
| def connect(self): | |||
| """ | |||
| Connect | |||
| --- | |||
| tags: | |||
| - system | |||
| responses: | |||
| 200: | |||
| description: CraftBeerPi System Cache | |||
| """ | |||
| if self.api.get_config_parameter("setup", "YES") == "YES": | |||
| return json.dumps(dict(setup=True, loggedin= current_user.is_authenticated )) | |||
| else: | |||
| return json.dumps(dict(setup=False, loggedin= current_user.is_authenticated)) | |||
| @login_required | |||
| @route('/dump', methods=['GET']) | |||
| def dump(self): | |||
| """ | |||
| @@ -1,4 +1,4 @@ | |||
| from flask import Blueprint | |||
| from flask import Blueprint,render_template | |||
| from modules.core.core import cbpi | |||
| @@ -11,12 +11,21 @@ def init(cbpi): | |||
| @react.route('/', methods=["GET"]) | |||
| def index(): | |||
| return react.send_static_file("index.html") | |||
| #return react.send_static_file("index.html") | |||
| js_files = [] | |||
| for key, value in cbpi.cache["js"].iteritems(): | |||
| js_files.append(value) | |||
| print js_files | |||
| return render_template('index.html', js_files=js_files) | |||
| @cbpi._app.errorhandler(404) | |||
| def page_not_found(e): | |||
| return render_template('404.html'), 404 | |||
| @@ -7,8 +7,7 @@ | |||
| } | |||
| .container-fluid { | |||
| padding-right: 5px; | |||
| padding-left: 5px; | |||
| } | |||
| @@ -1,5 +1,3 @@ | |||
| body { | |||
| margin: 0; | |||
| padding: 0; | |||
| font-family: sans-serif; | |||
| } | |||
| @@ -0,0 +1,29 @@ | |||
| <!doctype html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="utf-8"> | |||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |||
| <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> | |||
| <link rel="stylesheet" href="/ui/static/bootstrap.min.css"> | |||
| <link rel="stylesheet" href="/ui/static/css/font-awesome.min.css"> | |||
| <link rel="stylesheet" href="/ui/static/bootstrap.dark.css"> | |||
| <link href="https://fonts.googleapis.com/css?family=Dosis:200,500" rel="stylesheet"> | |||
| <style> | |||
| body, h1, h2, h3, h4, h5, h6 { | |||
| font-family: 'Dosis', sans-serif; | |||
| } | |||
| </style> | |||
| <title>CraftBeerPi 3.1</title> | |||
| </head> | |||
| <body> | |||
| <h1>CraftBeerPi - Page not Found!</h1> | |||
| </body> | |||
| </html> | |||
| @@ -0,0 +1,33 @@ | |||
| <!doctype html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="utf-8"> | |||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |||
| <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> | |||
| <link rel="stylesheet" href="static/bootstrap.min.css"> | |||
| <link rel="stylesheet" href="static/css/font-awesome.min.css"> | |||
| <link rel="stylesheet" href="static/bootstrap.dark.css"> | |||
| <link href="https://fonts.googleapis.com/css?family=Dosis:200,500" rel="stylesheet"> | |||
| <style> | |||
| body, h1, h2, h3, h4, h5, h6 { | |||
| font-family: 'Dosis', sans-serif; | |||
| } | |||
| </style> | |||
| <title>CraftBeerPi 3.1</title> | |||
| </head> | |||
| <body> | |||
| <div id="root" ></div> | |||
| {% for file in js_files %} | |||
| <script src="{{ file }}" type="text/javascript"></script> | |||
| {% endfor %} | |||
| <script src="static/bundle.js" type="text/javascript"></script> | |||
| </body> | |||
| </html> | |||
| @@ -6,9 +6,11 @@ cbpi = CraftBeerPI() | |||
| addon = cbpi.addon | |||
| from modules.core.db_migrate import * | |||
| from modules.buzzer import * | |||
| from modules.config import * | |||
| from modules.core.login import * | |||
| from modules.login import * | |||
| from modules.system import * | |||
| from modules.ui import * | |||
| from modules.step import * | |||
| @@ -0,0 +1 @@ | |||
| INSERT OR IGNORE INTO config VALUES ('password', 'beer', 'text', 'LoginPassword', NULL ); | |||