From 53db127b14e900e6ee0259f0606d608cd4e5bfba Mon Sep 17 00:00:00 2001 From: root Date: Wed, 16 Feb 2022 18:56:53 +0100 Subject: [PATCH 01/12] Python3 fixes --- parts/server.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/parts/server.py b/parts/server.py index caa9e4a..2d37e50 100644 --- a/parts/server.py +++ b/parts/server.py @@ -75,7 +75,7 @@ def get_locations_in_container(containerID): r = db_engine.execute(text(s), id=containerID) l={} for row in r: - l[row[0]]= row[1]; + l[str(row[0])]= row[1]; r.close() return json.dumps(l) @@ -324,7 +324,8 @@ def connect(user, password, db, host='localhost', port=5432): con = sqlalchemy.create_engine(url, client_encoding='utf8') # We then bind the connection to MetaData() - meta = sqlalchemy.MetaData(bind=con, reflect=True) + meta = sqlalchemy.MetaData(bind=con) + meta.reflect(bind=con) return con, meta From 90b948a0573b6873bf5c1f2a3cadfc29aaf798b3 Mon Sep 17 00:00:00 2001 From: Parts Date: Wed, 23 Nov 2022 21:39:20 +0100 Subject: [PATCH 02/12] Updated readme --- readme.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index f219947..2c9d65c 100644 --- a/readme.md +++ b/readme.md @@ -5,14 +5,19 @@ ELABs custom inventory system for locating components. The system allows searching for parts in ELAB by part name or a description text and returns the location of the part - a drawer or other compartment name and a map illustration. Finding obscure, outdated ICs was never this easy! -##Requirements -Python, and the following packages: +##Requirements (Debian-based systems) + + +$ sudo apt install python3 python3-pip libpq-dev postgresql-13 nginx git + +Python packages: * flask * requests * sqlalchemy * werkzeug * pillow +* psycopg2 ##Work in progress -The system is currently being worked on. No touchy! \ No newline at end of file +The system is currently being worked on. No touchy! From 1c8bdc69f68ab26730d8dcaca661014848c467db Mon Sep 17 00:00:00 2001 From: willd Date: Mon, 28 Nov 2022 21:13:49 +0100 Subject: [PATCH 03/12] Preparatory work for user editor --- parts/static/userEditorScript.js | 118 +++++++++++++++++++++++++++++++ parts/templates/userEditor.html | 53 ++++++++++++++ 2 files changed, 171 insertions(+) create mode 100755 parts/static/userEditorScript.js create mode 100755 parts/templates/userEditor.html diff --git a/parts/static/userEditorScript.js b/parts/static/userEditorScript.js new file mode 100755 index 0000000..9e4d1d8 --- /dev/null +++ b/parts/static/userEditorScript.js @@ -0,0 +1,118 @@ +function new_user_entry() { + +} + +function init_User_edit(userID, name) { + $('#save-button').attr("onclick", "saveUser(" + userID + ")"); + $('#delete-button').attr("onclick", "deleteUser(" + userID + ")"); + $('#user-name-input').val(name); + $('#user-password-input').val(''); + overlay_in(); +} + + +function saveUser(userID) { + var name_v = $('#user-name-input').val(); + var password_v = $('#user-password-input').val(); + + if(name_v.length > 100) { + alert('Name too long (max 100 characters).') + return; + } + var data = new FormData(); + + data.append('password', password_v); + data.append('name', name_v) + + $.ajax({ + url: rootURL + 'alterUser/' + userID, + type: 'POST', + data: data, + cache: false, + contentType: false, + processData: false, + success: function(data) { + alert("k."); + }, + error: function() { + alert("Couldn't update the user information. Please retry."); + } + }); + + end_edit(); + $('#edit-button').click(function() { + init_edit(userID); + }); +} + + +function deleteUser(userID) { + if (userID < 0){ + alert('Congratulations! You found the secret UI bug easter egg! This button should not be here, yet I left it specifically because I wanted an easter egg and not because of any other reasons! Good for you!') + return; + } + if (!confirm('Delete the selected user? ')){ + return; + } + $.ajax({ + url: rootURL + 'deleteUser/' + userID, + type: 'GET', + cache: false, + contentType: false, + processData: false, + success: function() { + alert("User removed."); + overlay_out(); + location.reload(); + }, + fail: function() { + console.log('An error occurred while deleting the entry'); + }, + }); +} + +function end_edit(){ + //intentionally left blank +} + + +function show_user_info(userID) { + $.getJSON(rootURL + 'getpartinfo/' + partID, function(data) { + $('table#details tr#location td p').text(text_filter(data.location_descriptor)); // name is the location friendly name + $('#location-dropdown').val(data.location_id); + $('#container-dropdown').val(data.container_id); + $('table#details tr#partno td p').text(text_filter(data.partno)); + $('table#details tr#partno td input').val(text_filter(data.partno)); + $('table#details tr#description td p').text(text_filter(data.description)); + $('table#details tr#description td input').val(text_filter(data.description)); + container_onchange(); + if (data.datasheet != null) { + $('tr#datasheet-head').html($('DATASHEET: ')); + $('#datasheet-input').val(data.datasheet); + } + else + $('tr#datasheet-head td').text('DATASHEET: '); + $('#edit-button').click(function() { + init_edit(partID); + }); + $('#delete-button').click(function() { + delete_entry(partID); + }); + overlay_in(); + }).fail(function() { + console.log( "Fetching part info failed" ); + }); +} + +function placeMarker(locationID){ + //temporarily not used + var $img = $('#clickablemap'); + var currentClickPosX = event.pageX - $img.offset().left; + var currentClickPosY = event.pageY - $img.offset().top; + + var correctX = (($img.prop('naturalWidth') / $img.width()) * currentClickPosX).toFixed(0); + var correctY = (($img.prop('naturalHeight') / $img.height()) * currentClickPosY).toFixed(0); + + // $("#mapURL").html("elab.png?x=" + correctX + "&y=" + correctY); + $("#clickablemap").attr("src", "map/" + $("#mapfile-input").val() + "?x=" + correctX + "&y=" + correctY); +} diff --git a/parts/templates/userEditor.html b/parts/templates/userEditor.html new file mode 100755 index 0000000..2a9d652 --- /dev/null +++ b/parts/templates/userEditor.html @@ -0,0 +1,53 @@ + + + + + ELAB Part Search Engine - User editor + + + + + + + + +

USER EDITOR

+

Get in! Get out! Get your fresh users here! back to parts

+ + + + + + {% for user in users %} + + + + + {% endfor %} +
IDUsername
{{user['id']}}{{user['username']}}
+
+ +
+
+

User Details

+ + + + + + + + + + +
Adding a new user
Name and password for new user
Example: "noob"

+
+
+
+
+
+ + From 4f5df6c1feef997fb3fa27a13bdc717c7a4a1ee3 Mon Sep 17 00:00:00 2001 From: willd Date: Mon, 28 Nov 2022 21:16:34 +0100 Subject: [PATCH 04/12] User editor, baseURL change, remove octopart autofill --- parts/server.py | 76 +++++++++++++++++++++------- parts/static/locationEditorScript.js | 0 parts/static/script.js | 44 +++++++++++----- parts/static/style-m.css | 0 parts/static/style.css | 0 parts/templates/locationEditor.html | 6 +-- parts/templates/partsearch.html | 12 ++--- 7 files changed, 98 insertions(+), 40 deletions(-) mode change 100644 => 100755 parts/server.py mode change 100644 => 100755 parts/static/locationEditorScript.js mode change 100644 => 100755 parts/static/script.js mode change 100644 => 100755 parts/static/style-m.css mode change 100644 => 100755 parts/static/style.css mode change 100644 => 100755 parts/templates/locationEditor.html mode change 100644 => 100755 parts/templates/partsearch.html diff --git a/parts/server.py b/parts/server.py old mode 100644 new mode 100755 index 2d37e50..1b8229a --- a/parts/server.py +++ b/parts/server.py @@ -20,6 +20,7 @@ db_engine = {} db_metadata = {} parts = {} octopartURL ="" +baseURL = "/parts-dev" def getContainers(): query = "select id, name from containers order by UPPER(name);" @@ -65,11 +66,11 @@ def serveImage(img): img_io.seek(0) return send_file(img_io, mimetype='image/png') -@app.route('/parts', strict_slashes=True) +@app.route(baseURL, strict_slashes=True) def index(): - return render_template('partsearch.html', containers=getContainers()) + return render_template('partsearch.html', containers=getContainers(), baseURL=baseURL) -@app.route('/parts/getlocationsInContainer/') +@app.route(baseURL+'/getlocationsInContainer/') def get_locations_in_container(containerID): s = 'select id, name from locations where container_id = :id order by name;' r = db_engine.execute(text(s), id=containerID) @@ -79,7 +80,7 @@ def get_locations_in_container(containerID): r.close() return json.dumps(l) -@app.route('/parts/getlocationURL/') +@app.route(baseURL+'/getlocationURL/') def get_locationURL(locationID): s = 'select map from locations where id = :id;' r = db_engine.execute(text(s), id=locationID) @@ -89,7 +90,7 @@ def get_locationURL(locationID): r.close() return l[0][0] -@app.route('/parts/locationEditor') +@app.route(baseURL+'/locationEditor') def locationEditor(): query = 'select c.name as container, l.name as name, l.id, c.id as container_id from locations as l inner join containers as c on l.container_id = c.id order by container, name;' r = db_engine.execute(text(query)) @@ -97,9 +98,20 @@ def locationEditor(): for row in r: locations.append(dict(row)) r.close() - return render_template('locationEditor.html', locations=locations, containers=getContainers()) + return render_template('locationEditor.html', locations=locations, containers=getContainers(),baseURL=baseURL) +@app.route(baseURL+'/userEditor') +def userEditor(): + query = 'select c.name as container, l.name as name, l.id, c.id as container_id from locations as l inner join containers as c on l.container_id = c.id order by container, name;' + query = 'select id, username from users;' + + r = db_engine.execute(text(query)) + users = [] + for row in r: + users.append(dict(row)) + r.close() + return render_template('userEditor.html', users=users, containers=getContainers(),baseURL=baseURL) -@app.route('/parts/alterLocation/', methods=['POST']) +@app.route(baseURL+'/alterLocation/', methods=['POST']) @requires_auth def alterLocation(locationID): locationID = int(locationID) @@ -121,8 +133,30 @@ def alterLocation(locationID): r = db_engine.execute(s, name=request.form['name'],container=request.form['container'],locationID=locationID); r.close() return '{"status":"ok"}' +@app.route(baseURL+'/alterUser/', methods=['POST']) +@requires_auth +def alterUser(userID): + userID = int(userID) + s = '' + if userID < 0: + # New entry + s = 'insert into users (username, password) ' + s += 'values (:name, :password);' + s = text(s) + r = db_engine.execute(s,username=request.form['name'],password=request.form['password']); + r.close() + return '{"status":"ok"}' + else: + # Modify entry + s = 'update users ' + s += 'set username=:name, password=:password ' + s += 'where id=:userID;' + s = text(s) + r = db_engine.execute(s, name=request.form['name'],password=request.form['password'],userID=userID); + r.close() + return '{"status":"ok"}' -@app.route('/parts/getpartinfo/') +@app.route(baseURL+'/getpartinfo/') def get_part_info(partID): s = 'select p.id,partno,description,notes, c.name || l.name as location_descriptor, location_id, container_id, datasheet from parts as p inner join locations as l on p.location_id = l.id inner join containers as c on l.container_id = c.id where p.id = :id;' r = db_engine.execute(text(s), id=partID) @@ -132,7 +166,7 @@ def get_part_info(partID): r.close() return json.dumps(l[0]) -@app.route('/parts/query//') # TODO: maybe change AND to OR or maybe not +@app.route(baseURL+'/query//') # TODO: maybe change AND to OR or maybe not def query(filter_dummy, query): filter = request.args.to_dict() keywords = query.split() # Default splits with spaces @@ -171,7 +205,7 @@ def query(filter_dummy, query): r.close() return json.dumps(l) -@app.route('/parts/map/') +@app.route(baseURL+'/map/') def getMap(containerID): s = 'select map, overlay from containers where id = :id;' r = db_engine.execute(text(s), id=containerID) @@ -196,14 +230,14 @@ def getMap(containerID): return serveImage(mapimage) -@app.route('/parts/getfile/') +@app.route(baseURL+'/getfile/') def getfile(filename): if(re.match('^[\w\-_]+.[p|P][d|D][f|F]$', filename) == None): return 'No injections pls.' return send_from_directory('/srv/datasheets/', filename) -@app.route('/parts/alter/', methods=['POST']) +@app.route(baseURL+'/alter/', methods=['POST']) @requires_auth def alter(partID): partID = int(partID) @@ -276,7 +310,7 @@ def alter(partID): r.close() return '{"status":"ok", "part_id" : ' + str(new_id) + '}' -@app.route('/parts/delete/') +@app.route(baseURL+'/delete/') @requires_auth def delete(partID): if int(partID) < 0: @@ -285,7 +319,7 @@ def delete(partID): r = db_engine.execute(s, id=partID) return '{"status":"ok"}' -@app.route('/parts/deleteLocation/') +@app.route(baseURL+'/deleteLocation/') @requires_auth def deleteLocation(locationID): if int(locationID) < 0: @@ -293,8 +327,16 @@ def deleteLocation(locationID): s = text('delete from locations where id=:id;') r = db_engine.execute(s, id=locationID) return '{"status":"ok"}' +@app.route(baseURL+'/deleteUser/') +@requires_auth +def deleteUser(userID): + if int(userID) < 0: + abort(400) + s = text('delete from users where id=:id;') + r = db_engine.execute(s, id=userID) + return '{"status":"ok"}' -@app.route('/parts/fetchOctopartSnippet/') +@app.route(baseURL+'/fetchOctopartSnippet/') def fetchOctopartSnippet(searchTerm): if octopartURL == '': return '{"result":"octopart integration not enabled"}' @@ -334,7 +376,7 @@ if __name__ == '__main__': app.config['SESSION_TYPE'] = 'memcached' with open('admin.json') as f: postgres_credentials = json.load(f) - db_engine, db_metadata = connect(postgres_credentials['username'], postgres_credentials['password'], 'parts_v2') + db_engine, db_metadata = connect(postgres_credentials['username'], postgres_credentials['password'], 'parts_v3') parts = sqlalchemy.Table('parts', db_metadata) try: with open('octopartAPIkey.json') as f: @@ -350,4 +392,4 @@ if __name__ == '__main__': '''s = select([parts]).where(parts.c.notes != '') for row in db_engine.execute(s): print row''' - app.run('0.0.0.0') + app.run(host='127.0.0.1', port='5001', debug=True) diff --git a/parts/static/locationEditorScript.js b/parts/static/locationEditorScript.js old mode 100644 new mode 100755 diff --git a/parts/static/script.js b/parts/static/script.js old mode 100644 new mode 100755 index d58a40d..56ec5ab --- a/parts/static/script.js +++ b/parts/static/script.js @@ -213,6 +213,10 @@ function show_part_info(partID) { }).fail(function() { console.log( "Fetching part info failed" ); }); + + // update URL and history + var query = $('.search-bar').val(); + window.history.pushState('searching', '', 'parts?q=' + query + '&p=' + partID) } function perform_query() { @@ -245,6 +249,9 @@ function perform_query() { $('#no-results').animate({opacity:1},2000); console.log( "Query failed" ); }); + + // update URL and history + window.history.pushState('searching', '', 'parts?q=' + query) } function container_onchange() { @@ -260,20 +267,20 @@ function container_onchange() { } } -function octopartFetch(){ - $('#magic-autofill-button').attr('disabled', 'disabled'); - $.getJSON(rootURL + 'fetchOctopartSnippet/' + $('#partno-input').val()).done(function(json){ - $('#magic-autofill-button').removeAttr('disabled'); - if (json['result']=='ok'){ - $('#description-input').val(json['snippet']); - }else{ - $('#description-input').val(''); - $('#description-input').attr('placeholder', json['result']); - } - }).fail(function() { - $('#magic-autofill-button').removeAttr('disabled'); - }); -} +// function octopartFetch(){ +// $('#magic-autofill-button').attr('disabled', 'disabled'); +// $.getJSON(rootURL + 'fetchOctopartSnippet/' + $('#partno-input').val()).done(function(json){ +// $('#magic-autofill-button').removeAttr('disabled'); +// if (json['result']=='ok'){ +// $('#description-input').val(json['snippet']); +// }else{ +// $('#description-input').val(''); +// $('#description-input').attr('placeholder', json['result']); +// } +// }).fail(function() { +// $('#magic-autofill-button').removeAttr('disabled'); +// }); +// } $(document).ready(function() { $.ajaxSetup({ cache: false }); @@ -291,6 +298,15 @@ $(document).ready(function() { perform_query(); }); + //For linking to a search query or specific part + const search_params = new URLSearchParams(window.location.search); + if(search_params.has('q')){ + $('.search-bar').val(search_params.get('q')); perform_query(); + } + if(search_params.has('p')){ + show_part_info(search_params.get('p')); + } + // $('#partno-input').bind('blur',function() { // if($('#partno-input').val().length > 0){ // octopartFetch(); diff --git a/parts/static/style-m.css b/parts/static/style-m.css old mode 100644 new mode 100755 diff --git a/parts/static/style.css b/parts/static/style.css old mode 100644 new mode 100755 diff --git a/parts/templates/locationEditor.html b/parts/templates/locationEditor.html old mode 100644 new mode 100755 index a32dfac..688185c --- a/parts/templates/locationEditor.html +++ b/parts/templates/locationEditor.html @@ -8,10 +8,10 @@ integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"> - + - - + +

LOCATION EDITOR

diff --git a/parts/templates/partsearch.html b/parts/templates/partsearch.html old mode 100644 new mode 100755 index 618529b..5ee9c8c --- a/parts/templates/partsearch.html +++ b/parts/templates/partsearch.html @@ -9,13 +9,13 @@ integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"> - - - + + + - - + +

ELAB Part Search Engine

@@ -58,7 +58,7 @@

DESCRIPTION

- powered by OctoPartâ„¢ + DATASHEET: OR: From feb1aea5c3dab66b346830e18e437ba1c6b3b73b Mon Sep 17 00:00:00 2001 From: willd Date: Mon, 28 Nov 2022 21:21:56 +0100 Subject: [PATCH 05/12] Change to prod url --- parts/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parts/server.py b/parts/server.py index 1b8229a..4e6f03b 100755 --- a/parts/server.py +++ b/parts/server.py @@ -20,7 +20,7 @@ db_engine = {} db_metadata = {} parts = {} octopartURL ="" -baseURL = "/parts-dev" +baseURL = "/parts" def getContainers(): query = "select id, name from containers order by UPPER(name);" From 6897d0f8ac9836a29288113ba767874474f4635d Mon Sep 17 00:00:00 2001 From: willd Date: Mon, 28 Nov 2022 21:23:06 +0100 Subject: [PATCH 06/12] Flask run fix --- parts/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parts/server.py b/parts/server.py index 4e6f03b..fde1e58 100755 --- a/parts/server.py +++ b/parts/server.py @@ -392,4 +392,4 @@ if __name__ == '__main__': '''s = select([parts]).where(parts.c.notes != '') for row in db_engine.execute(s): print row''' - app.run(host='127.0.0.1', port='5001', debug=True) + app.run(host='127.0.0.1', port='5000', debug=False) From 21e7356a99e619b50adb470eef743b9965c84d7d Mon Sep 17 00:00:00 2001 From: root Date: Wed, 16 Feb 2022 18:56:53 +0100 Subject: [PATCH 07/12] Python3 fixes --- parts/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parts/server.py b/parts/server.py index 54c43e1..2d37e50 100644 --- a/parts/server.py +++ b/parts/server.py @@ -75,7 +75,7 @@ def get_locations_in_container(containerID): r = db_engine.execute(text(s), id=containerID) l={} for row in r: - l[row[0]]= row[1]; + l[str(row[0])]= row[1]; r.close() return json.dumps(l) From 2be1d78d39860785eaaec8a5030d399da25f9627 Mon Sep 17 00:00:00 2001 From: Parts Date: Wed, 23 Nov 2022 21:39:20 +0100 Subject: [PATCH 08/12] Updated readme --- readme.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 595a007..2ba8336 100644 --- a/readme.md +++ b/readme.md @@ -5,14 +5,19 @@ ELABs custom inventory system for locating components. The system allows searching for parts in ELAB by part name or a description text and returns the location of the part - a drawer or other compartment name and a map illustration. Finding obscure, outdated ICs was never this easy! -## Requirements -Python, and the following packages: +##Requirements (Debian-based systems) + + +$ sudo apt install python3 python3-pip libpq-dev postgresql-13 nginx git + +Python packages: * flask * requests * sqlalchemy * werkzeug * pillow +* psycopg2 ## Adding a part @@ -49,4 +54,4 @@ AND DONE! Congratulations! You have added a part to the database! Only 563945679 * The Notes field is for notes. ## Work in progress -The system is currently being worked on. No touchy! \ No newline at end of file +The system is currently being worked on. No touchy! From 8bb2a9d31855b88b8e1f25c842c69742345793f4 Mon Sep 17 00:00:00 2001 From: willd Date: Mon, 28 Nov 2022 21:13:49 +0100 Subject: [PATCH 09/12] Preparatory work for user editor --- parts/static/userEditorScript.js | 118 +++++++++++++++++++++++++++++++ parts/templates/userEditor.html | 53 ++++++++++++++ 2 files changed, 171 insertions(+) create mode 100755 parts/static/userEditorScript.js create mode 100755 parts/templates/userEditor.html diff --git a/parts/static/userEditorScript.js b/parts/static/userEditorScript.js new file mode 100755 index 0000000..9e4d1d8 --- /dev/null +++ b/parts/static/userEditorScript.js @@ -0,0 +1,118 @@ +function new_user_entry() { + +} + +function init_User_edit(userID, name) { + $('#save-button').attr("onclick", "saveUser(" + userID + ")"); + $('#delete-button').attr("onclick", "deleteUser(" + userID + ")"); + $('#user-name-input').val(name); + $('#user-password-input').val(''); + overlay_in(); +} + + +function saveUser(userID) { + var name_v = $('#user-name-input').val(); + var password_v = $('#user-password-input').val(); + + if(name_v.length > 100) { + alert('Name too long (max 100 characters).') + return; + } + var data = new FormData(); + + data.append('password', password_v); + data.append('name', name_v) + + $.ajax({ + url: rootURL + 'alterUser/' + userID, + type: 'POST', + data: data, + cache: false, + contentType: false, + processData: false, + success: function(data) { + alert("k."); + }, + error: function() { + alert("Couldn't update the user information. Please retry."); + } + }); + + end_edit(); + $('#edit-button').click(function() { + init_edit(userID); + }); +} + + +function deleteUser(userID) { + if (userID < 0){ + alert('Congratulations! You found the secret UI bug easter egg! This button should not be here, yet I left it specifically because I wanted an easter egg and not because of any other reasons! Good for you!') + return; + } + if (!confirm('Delete the selected user? ')){ + return; + } + $.ajax({ + url: rootURL + 'deleteUser/' + userID, + type: 'GET', + cache: false, + contentType: false, + processData: false, + success: function() { + alert("User removed."); + overlay_out(); + location.reload(); + }, + fail: function() { + console.log('An error occurred while deleting the entry'); + }, + }); +} + +function end_edit(){ + //intentionally left blank +} + + +function show_user_info(userID) { + $.getJSON(rootURL + 'getpartinfo/' + partID, function(data) { + $('table#details tr#location td p').text(text_filter(data.location_descriptor)); // name is the location friendly name + $('#location-dropdown').val(data.location_id); + $('#container-dropdown').val(data.container_id); + $('table#details tr#partno td p').text(text_filter(data.partno)); + $('table#details tr#partno td input').val(text_filter(data.partno)); + $('table#details tr#description td p').text(text_filter(data.description)); + $('table#details tr#description td input').val(text_filter(data.description)); + container_onchange(); + if (data.datasheet != null) { + $('tr#datasheet-head').html($('DATASHEET: ')); + $('#datasheet-input').val(data.datasheet); + } + else + $('tr#datasheet-head td').text('DATASHEET: '); + $('#edit-button').click(function() { + init_edit(partID); + }); + $('#delete-button').click(function() { + delete_entry(partID); + }); + overlay_in(); + }).fail(function() { + console.log( "Fetching part info failed" ); + }); +} + +function placeMarker(locationID){ + //temporarily not used + var $img = $('#clickablemap'); + var currentClickPosX = event.pageX - $img.offset().left; + var currentClickPosY = event.pageY - $img.offset().top; + + var correctX = (($img.prop('naturalWidth') / $img.width()) * currentClickPosX).toFixed(0); + var correctY = (($img.prop('naturalHeight') / $img.height()) * currentClickPosY).toFixed(0); + + // $("#mapURL").html("elab.png?x=" + correctX + "&y=" + correctY); + $("#clickablemap").attr("src", "map/" + $("#mapfile-input").val() + "?x=" + correctX + "&y=" + correctY); +} diff --git a/parts/templates/userEditor.html b/parts/templates/userEditor.html new file mode 100755 index 0000000..2a9d652 --- /dev/null +++ b/parts/templates/userEditor.html @@ -0,0 +1,53 @@ + + + + + ELAB Part Search Engine - User editor + + + + + + + + +

USER EDITOR

+

Get in! Get out! Get your fresh users here! back to parts

+ + + + + + {% for user in users %} + + + + + {% endfor %} +
IDUsername
{{user['id']}}{{user['username']}}
+
+ +
+
+

User Details

+ + + + + + + + + + +
Adding a new user
Name and password for new user
Example: "noob"

+
+
+
+
+
+ + From a549c16548aaf2162eb074d845a99d681c5ad608 Mon Sep 17 00:00:00 2001 From: willd Date: Mon, 28 Nov 2022 21:16:34 +0100 Subject: [PATCH 10/12] User editor, baseURL change, remove octopart autofill --- parts/server.py | 76 +++++++++++++++++++++------- parts/static/locationEditorScript.js | 0 parts/static/script.js | 12 ++++- parts/static/style-m.css | 0 parts/static/style.css | 0 parts/templates/locationEditor.html | 6 +-- parts/templates/partsearch.html | 10 ++-- 7 files changed, 78 insertions(+), 26 deletions(-) mode change 100644 => 100755 parts/server.py mode change 100644 => 100755 parts/static/locationEditorScript.js mode change 100644 => 100755 parts/static/script.js mode change 100644 => 100755 parts/static/style-m.css mode change 100644 => 100755 parts/static/style.css mode change 100644 => 100755 parts/templates/locationEditor.html mode change 100644 => 100755 parts/templates/partsearch.html diff --git a/parts/server.py b/parts/server.py old mode 100644 new mode 100755 index 2d37e50..1b8229a --- a/parts/server.py +++ b/parts/server.py @@ -20,6 +20,7 @@ db_engine = {} db_metadata = {} parts = {} octopartURL ="" +baseURL = "/parts-dev" def getContainers(): query = "select id, name from containers order by UPPER(name);" @@ -65,11 +66,11 @@ def serveImage(img): img_io.seek(0) return send_file(img_io, mimetype='image/png') -@app.route('/parts', strict_slashes=True) +@app.route(baseURL, strict_slashes=True) def index(): - return render_template('partsearch.html', containers=getContainers()) + return render_template('partsearch.html', containers=getContainers(), baseURL=baseURL) -@app.route('/parts/getlocationsInContainer/') +@app.route(baseURL+'/getlocationsInContainer/') def get_locations_in_container(containerID): s = 'select id, name from locations where container_id = :id order by name;' r = db_engine.execute(text(s), id=containerID) @@ -79,7 +80,7 @@ def get_locations_in_container(containerID): r.close() return json.dumps(l) -@app.route('/parts/getlocationURL/') +@app.route(baseURL+'/getlocationURL/') def get_locationURL(locationID): s = 'select map from locations where id = :id;' r = db_engine.execute(text(s), id=locationID) @@ -89,7 +90,7 @@ def get_locationURL(locationID): r.close() return l[0][0] -@app.route('/parts/locationEditor') +@app.route(baseURL+'/locationEditor') def locationEditor(): query = 'select c.name as container, l.name as name, l.id, c.id as container_id from locations as l inner join containers as c on l.container_id = c.id order by container, name;' r = db_engine.execute(text(query)) @@ -97,9 +98,20 @@ def locationEditor(): for row in r: locations.append(dict(row)) r.close() - return render_template('locationEditor.html', locations=locations, containers=getContainers()) + return render_template('locationEditor.html', locations=locations, containers=getContainers(),baseURL=baseURL) +@app.route(baseURL+'/userEditor') +def userEditor(): + query = 'select c.name as container, l.name as name, l.id, c.id as container_id from locations as l inner join containers as c on l.container_id = c.id order by container, name;' + query = 'select id, username from users;' + + r = db_engine.execute(text(query)) + users = [] + for row in r: + users.append(dict(row)) + r.close() + return render_template('userEditor.html', users=users, containers=getContainers(),baseURL=baseURL) -@app.route('/parts/alterLocation/', methods=['POST']) +@app.route(baseURL+'/alterLocation/', methods=['POST']) @requires_auth def alterLocation(locationID): locationID = int(locationID) @@ -121,8 +133,30 @@ def alterLocation(locationID): r = db_engine.execute(s, name=request.form['name'],container=request.form['container'],locationID=locationID); r.close() return '{"status":"ok"}' +@app.route(baseURL+'/alterUser/', methods=['POST']) +@requires_auth +def alterUser(userID): + userID = int(userID) + s = '' + if userID < 0: + # New entry + s = 'insert into users (username, password) ' + s += 'values (:name, :password);' + s = text(s) + r = db_engine.execute(s,username=request.form['name'],password=request.form['password']); + r.close() + return '{"status":"ok"}' + else: + # Modify entry + s = 'update users ' + s += 'set username=:name, password=:password ' + s += 'where id=:userID;' + s = text(s) + r = db_engine.execute(s, name=request.form['name'],password=request.form['password'],userID=userID); + r.close() + return '{"status":"ok"}' -@app.route('/parts/getpartinfo/') +@app.route(baseURL+'/getpartinfo/') def get_part_info(partID): s = 'select p.id,partno,description,notes, c.name || l.name as location_descriptor, location_id, container_id, datasheet from parts as p inner join locations as l on p.location_id = l.id inner join containers as c on l.container_id = c.id where p.id = :id;' r = db_engine.execute(text(s), id=partID) @@ -132,7 +166,7 @@ def get_part_info(partID): r.close() return json.dumps(l[0]) -@app.route('/parts/query//') # TODO: maybe change AND to OR or maybe not +@app.route(baseURL+'/query//') # TODO: maybe change AND to OR or maybe not def query(filter_dummy, query): filter = request.args.to_dict() keywords = query.split() # Default splits with spaces @@ -171,7 +205,7 @@ def query(filter_dummy, query): r.close() return json.dumps(l) -@app.route('/parts/map/') +@app.route(baseURL+'/map/') def getMap(containerID): s = 'select map, overlay from containers where id = :id;' r = db_engine.execute(text(s), id=containerID) @@ -196,14 +230,14 @@ def getMap(containerID): return serveImage(mapimage) -@app.route('/parts/getfile/') +@app.route(baseURL+'/getfile/') def getfile(filename): if(re.match('^[\w\-_]+.[p|P][d|D][f|F]$', filename) == None): return 'No injections pls.' return send_from_directory('/srv/datasheets/', filename) -@app.route('/parts/alter/', methods=['POST']) +@app.route(baseURL+'/alter/', methods=['POST']) @requires_auth def alter(partID): partID = int(partID) @@ -276,7 +310,7 @@ def alter(partID): r.close() return '{"status":"ok", "part_id" : ' + str(new_id) + '}' -@app.route('/parts/delete/') +@app.route(baseURL+'/delete/') @requires_auth def delete(partID): if int(partID) < 0: @@ -285,7 +319,7 @@ def delete(partID): r = db_engine.execute(s, id=partID) return '{"status":"ok"}' -@app.route('/parts/deleteLocation/') +@app.route(baseURL+'/deleteLocation/') @requires_auth def deleteLocation(locationID): if int(locationID) < 0: @@ -293,8 +327,16 @@ def deleteLocation(locationID): s = text('delete from locations where id=:id;') r = db_engine.execute(s, id=locationID) return '{"status":"ok"}' +@app.route(baseURL+'/deleteUser/') +@requires_auth +def deleteUser(userID): + if int(userID) < 0: + abort(400) + s = text('delete from users where id=:id;') + r = db_engine.execute(s, id=userID) + return '{"status":"ok"}' -@app.route('/parts/fetchOctopartSnippet/') +@app.route(baseURL+'/fetchOctopartSnippet/') def fetchOctopartSnippet(searchTerm): if octopartURL == '': return '{"result":"octopart integration not enabled"}' @@ -334,7 +376,7 @@ if __name__ == '__main__': app.config['SESSION_TYPE'] = 'memcached' with open('admin.json') as f: postgres_credentials = json.load(f) - db_engine, db_metadata = connect(postgres_credentials['username'], postgres_credentials['password'], 'parts_v2') + db_engine, db_metadata = connect(postgres_credentials['username'], postgres_credentials['password'], 'parts_v3') parts = sqlalchemy.Table('parts', db_metadata) try: with open('octopartAPIkey.json') as f: @@ -350,4 +392,4 @@ if __name__ == '__main__': '''s = select([parts]).where(parts.c.notes != '') for row in db_engine.execute(s): print row''' - app.run('0.0.0.0') + app.run(host='127.0.0.1', port='5001', debug=True) diff --git a/parts/static/locationEditorScript.js b/parts/static/locationEditorScript.js old mode 100644 new mode 100755 diff --git a/parts/static/script.js b/parts/static/script.js old mode 100644 new mode 100755 index 859bf02..4e7ab4d --- a/parts/static/script.js +++ b/parts/static/script.js @@ -213,6 +213,7 @@ function show_part_info(partID) { }).fail(function() { console.log( "Fetching part info failed" ); }); + // update URL and history var query = $('.search-bar').val(); window.history.pushState('searching', '', 'parts?q=' + query + '&p=' + partID) @@ -248,7 +249,7 @@ function perform_query() { $('#no-results').animate({opacity:1},2000); console.log( "Query failed" ); }); - + // update URL and history window.history.pushState('searching', '', 'parts?q=' + query) } @@ -306,6 +307,15 @@ $(document).ready(function() { show_part_info(search_params.get('p')); } + //For linking to a search query or specific part + const search_params = new URLSearchParams(window.location.search); + if(search_params.has('q')){ + $('.search-bar').val(search_params.get('q')); perform_query(); + } + if(search_params.has('p')){ + show_part_info(search_params.get('p')); + } + // $('#partno-input').bind('blur',function() { // if($('#partno-input').val().length > 0){ // octopartFetch(); diff --git a/parts/static/style-m.css b/parts/static/style-m.css old mode 100644 new mode 100755 diff --git a/parts/static/style.css b/parts/static/style.css old mode 100644 new mode 100755 diff --git a/parts/templates/locationEditor.html b/parts/templates/locationEditor.html old mode 100644 new mode 100755 index a32dfac..688185c --- a/parts/templates/locationEditor.html +++ b/parts/templates/locationEditor.html @@ -8,10 +8,10 @@ integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"> - + - - + +

LOCATION EDITOR

diff --git a/parts/templates/partsearch.html b/parts/templates/partsearch.html old mode 100644 new mode 100755 index 3265c71..5ee9c8c --- a/parts/templates/partsearch.html +++ b/parts/templates/partsearch.html @@ -9,13 +9,13 @@ integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"> - - - + + + - - + +

ELAB Part Search Engine

From f331be7a899eeaf120e0c4cdda60284b7d8ec71a Mon Sep 17 00:00:00 2001 From: willd Date: Mon, 28 Nov 2022 21:21:56 +0100 Subject: [PATCH 11/12] Change to prod url --- parts/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parts/server.py b/parts/server.py index 1b8229a..4e6f03b 100755 --- a/parts/server.py +++ b/parts/server.py @@ -20,7 +20,7 @@ db_engine = {} db_metadata = {} parts = {} octopartURL ="" -baseURL = "/parts-dev" +baseURL = "/parts" def getContainers(): query = "select id, name from containers order by UPPER(name);" From 961fb859f49156774a3f8a3cd0f5143ff795afb8 Mon Sep 17 00:00:00 2001 From: willd Date: Mon, 28 Nov 2022 21:23:06 +0100 Subject: [PATCH 12/12] Flask run fix --- parts/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parts/server.py b/parts/server.py index 4e6f03b..fde1e58 100755 --- a/parts/server.py +++ b/parts/server.py @@ -392,4 +392,4 @@ if __name__ == '__main__': '''s = select([parts]).where(parts.c.notes != '') for row in db_engine.execute(s): print row''' - app.run(host='127.0.0.1', port='5001', debug=True) + app.run(host='127.0.0.1', port='5000', debug=False)