diff --git a/README.md b/README.md index a153a9e..8ffbf84 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,17 @@ flask init-db Database then lives in `database.sqlite`. To reset database, delete this file and run `init-db` again. + +## Migrate to v.1.0.0 + +(see https://github.com/mbarde/sloth-tools/issues/11) + +``` +export FLASK_APP=server.py +flask migrate-to-v1 +``` + + ## Run in dev mode ``` @@ -81,6 +92,7 @@ Enable: sudo systemctl enable homecontrol.service ``` + ## API Once running you can access Sloth Tools via the provided web application. But you can also directly interact with the backend by calling certain endpoints: @@ -99,6 +111,3 @@ For example you could use following script to blink node 42 everytime a motion i #!/bin/sh wget -O/dev/null 192.168.0.13:5000/toggle?id=42 -q ``` - - - diff --git a/db.py b/db.py index 3fa8d3d..06c4350 100644 --- a/db.py +++ b/db.py @@ -30,6 +30,37 @@ def init_db(): db.executescript(f.read().decode('utf8')) +@click.command('migrate-to-v1') +@with_appcontext +def migrate_to_v1(): + # version 1.0.0 introduces sorting feature for nodes, + # which depends on additional field `sort_order` + # in `node` table. + db = get_db() + + cursor = db.cursor() + cursor.execute('PRAGMA table_info(node);') + columns_info = cursor.fetchall() + columns_names = [c['name'] for c in columns_info] + + if 'sort_order' not in columns_names: + click.echo('Adding missing field `sort_order` to table `node`.') + cursor.execute('ALTER TABLE `node` ADD `sort_order` INTEGER NOT NULL;') + + cursor.execute('CREATE UNIQUE INDEX `idx_unique_sort_order` ON `node` (`sort_order`);') + + click.echo('Initializing new field `sort_order` with node ID.') + query = 'UPDATE node SET "sort_order" = id;' + cursor.execute(query) + db.commit() + + cursor.close() + + # we have to stop timer manually here + # otherwise process will run forever + current_app.eventTable.stopTimer() + + @click.command('init-db') @with_appcontext def init_db_command(): @@ -44,3 +75,4 @@ def init_db_command(): def init_app(app): app.teardown_appcontext(close_db) app.cli.add_command(init_db_command) + app.cli.add_command(migrate_to_v1) diff --git a/nodes.html b/nodes.html index 41e406d..c8e71a8 100644 --- a/nodes.html +++ b/nodes.html @@ -13,6 +13,16 @@ + {% if loop.index > 1 %} + + {% else %} + + {% endif %} + {% if loop.index < nodes | length %} + + {% else %} + + {% endif %}
{% endfor %} diff --git a/server.py b/server.py index 74eb580..13f7223 100644 --- a/server.py +++ b/server.py @@ -138,7 +138,8 @@ def toggle(): @app.route('/nodes') def nodes(): nodeService = CRUDService('node') - nodes = nodeService.read() + nodes = nodeService.readAll( + order_by='sort_order', order_mode='ASC') return render_template('./nodes.html', nodes=nodes) @app.route('/node/create', methods=['GET', 'POST']) @@ -150,6 +151,15 @@ def nodeCreate(): if jsonData is not None: node = jsonData + + # get currently highest sort_order value + allNodes = nodeService.readAll(order_by='sort_order', order_mode='DESC') + if len(allNodes) > 0: + # to make sure new node is appended at the end + node['sort_order'] = allNodes[0]['sort_order'] + 1 + else: + node['sort_order'] = 0 + if nodeService.create(node): return 'OK' @@ -196,6 +206,25 @@ def nodeDelete(id): nodeService.delete(id) return 'OK' + @app.route('/node/reorder//', methods=['POST']) + def nodeReorder(posFrom, posTo): + # flips sort_order positions of two nodes + nodeService = CRUDService('node') + + nodeFrom = dict(nodeService.readBy('sort_order', posFrom)[0]) + nodeTo = dict(nodeService.readBy('sort_order', posTo)[0]) + + nodeTo['sort_order'] = -1 + nodeService.update(nodeTo) + + nodeFrom['sort_order'] = posTo + nodeService.update(nodeFrom) + + nodeTo['sort_order'] = posFrom + nodeService.update(nodeTo) + + return 'OK' + # event API @app.route('/event/create/', methods=['GET', 'POST']) def eventCreate(nodeId): diff --git a/service.py b/service.py index 3ca95c1..0f009b3 100644 --- a/service.py +++ b/service.py @@ -51,9 +51,12 @@ def create(self, data): conn.commit() return True - def readAll(self): + def readAll(self, order_by=None, order_mode='ASC'): conn = db.get_db() sql = 'SELECT * FROM {0}'.format(self.tableName) + if order_by is not None: + sql += ' ORDER BY {0} {1};'.format( + order_by, order_mode) results = conn.execute(sql).fetchall() return results @@ -100,3 +103,9 @@ def delete(self, id): conn.execute(sql, (id,)) conn.commit() return True + + def countRows(self): + conn = db.get_db() + query = 'SELECT COUNT(*) FROM {0};'.format(self.tableName) + conn.execute(query) + return conn.fetchone()[0] diff --git a/setup.py b/setup.py index b4a4341..3811420 100644 --- a/setup.py +++ b/setup.py @@ -2,5 +2,5 @@ setup( name='sloth-tools', - version='0.9.0', + version='1.0.0', ) diff --git a/static/css/classic/custom.css b/static/css/classic/custom.css index c754898..05d092d 100644 --- a/static/css/classic/custom.css +++ b/static/css/classic/custom.css @@ -87,6 +87,10 @@ button.transparent { background: none; } +button.invisible { + visibility: hidden; +} + #jobs-container { background: rgba(0.2, 0.2, 0.2, 0.2); color: black; @@ -123,7 +127,7 @@ ul.nodes { ul.nodes li.node { margin-bottom: 15px; overflow: hidden; - width: calc(100% + 142px); + width: calc(100% + 229px); } ul.nodes li.node.disabled { @@ -215,14 +219,14 @@ li.event ul.weekdays li:hover { background: transparent; } to { - transform: translateX(-142px); + transform: translateX(-229px); background: #D5ECC9; } } @keyframes slideback { from { - transform: translateX(-142px); + transform: translateX(-229px); background: #D5ECC9; } to { diff --git a/static/js/main.js b/static/js/main.js index ae40742..54592ff 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -177,6 +177,16 @@ function deleteNode(clickEvent, nodeId, nodeTitle) { return false } +function reorderNodes(posFrom, posTo) { + var url = '/node/reorder/' + posFrom + '/' + posTo + var xhttp = new XMLHttpRequest() + xhttp.open('POST', url) + xhttp.addEventListener('load', function() { + refreshNodes() + }) + xhttp.send() +} + function onToolClicked(element, event) { event.preventDefault() event.stopPropagation()