diff --git a/Berksfile b/Berksfile new file mode 100644 index 0000000..34fea21 --- /dev/null +++ b/Berksfile @@ -0,0 +1,3 @@ +source 'https://supermarket.chef.io' + +metadata diff --git a/README.md b/README.md new file mode 100644 index 0000000..bca0e40 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# aar + +TODO: Enter the cookbook description here. + diff --git a/attributes/default.rb b/attributes/default.rb new file mode 100644 index 0000000..6e99a1c --- /dev/null +++ b/attributes/default.rb @@ -0,0 +1,18 @@ +default['aar']['required-packages'] = [ 'unzip', + 'curl', + 'apache2', + 'mysql-server', + 'libapache2-mod-wsgi', + 'python-mysqldb', + 'python-pip' ] + +# Usernames and passwords; should refactor to databag or vault +default['aar']['web-user'] = 'www-data' +default['aar']['web-group'] = 'www-data' +default['aar']['mysql-host'] = 'localhost' +default['aar']['mysql-root-user'] = 'root' +default['aar']['mysql-root-password'] = '' # Using default MySQL root password; should be changed and refactored +default['aar']['app-db'] = 'AARdb' +default['aar']['app-db-user'] = 'aarapp' +default['aar']['app-db-password'] = 'Also5uper5ecret' +default['aar']['app-db-secret'] = 'Really5uper5ecret' diff --git a/chefignore b/chefignore new file mode 100644 index 0000000..a976917 --- /dev/null +++ b/chefignore @@ -0,0 +1,102 @@ +# Put files/directories that should be ignored in this file when uploading +# to a chef-server or supermarket. +# Lines that start with '# ' are comments. + +# OS generated files # +###################### +.DS_Store +Icon? +nohup.out +ehthumbs.db +Thumbs.db + +# SASS # +######## +.sass-cache + +# EDITORS # +########### +\#* +.#* +*~ +*.sw[a-z] +*.bak +REVISION +TAGS* +tmtags +*_flymake.* +*_flymake +*.tmproj +.project +.settings +mkmf.log + +## COMPILED ## +############## +a.out +*.o +*.pyc +*.so +*.com +*.class +*.dll +*.exe +*/rdoc/ + +# Testing # +########### +.watchr +.rspec +spec/* +spec/fixtures/* +test/* +features/* +examples/* +Guardfile +Procfile +.kitchen* +.rubocop.yml +spec/* +Rakefile +.travis.yml +.foodcritic +.codeclimate.yml + +# SCM # +####### +.git +*/.git +.gitignore +.gitmodules +.gitconfig +.gitattributes +.svn +*/.bzr/* +*/.hg/* +*/.svn/* + +# Berkshelf # +############# +Berksfile +Berksfile.lock +cookbooks/* +tmp + +# Cookbooks # +############# +CONTRIBUTING* +CHANGELOG* +TESTING* +MAINTAINERS.toml + +# Strainer # +############ +Colanderfile +Strainerfile +.colander +.strainer + +# Vagrant # +########### +.vagrant +Vagrantfile diff --git a/files/default/AAR/__init__.py b/files/default/AAR/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/files/default/AAR/awesomeapp.py b/files/default/AAR/awesomeapp.py new file mode 100755 index 0000000..0b54e6a --- /dev/null +++ b/files/default/AAR/awesomeapp.py @@ -0,0 +1,216 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +#### copyright, Opscode Inc. #### +# Apache License +# Version 2.0, January 2004 +# http://www.apache.org/licenses/ +################################# + +from functools import wraps +from hashlib import sha1 +from pprint import pprint +from flask import Flask, request, flash, Response, session, g, jsonify, redirect, url_for, abort, render_template +import MySQLdb +from AAR_config import SECRET_KEY, CONNECTION_ARGS, DB_VALUES + +app = Flask(__name__) +app.config['SECRET_KEY'] = SECRET_KEY + +def logged_in(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if session.get('logged_in') is not None: + return f(*args, **kwargs) + else: + flash('Please log in first...', 'error') + return redirect(url_for('login')) + + return decorated_function + +@app.route('/logout') +def logout(): + session.pop('username', None) + session.pop('role', None) + session.pop('logged_in', None) + return redirect(url_for('login')) + +def populate_db(ip): + db = MySQLdb.connect(**CONNECTION_ARGS) + cur = db.cursor() + for x in DB_VALUES: + cur.execute("insert into jobs (j_ip,cid, make, appliance, appointment, job_status, description) values (%s,%s,%s,%s,%s,%s,%s)", (ip,x[0],x[1],x[2],x[3],x[4],x[5])) + db.commit() + cur.close() + +@app.route('/resetdb', methods=['POST']) +def resetdb(): + IPadd = request.environ['REMOTE_ADDR'] + db = MySQLdb.connect(**CONNECTION_ARGS) + cur = db.cursor() + cur.execute("select j_ip from jobs where j_ip = %s limit 1", IPadd) + if cur.fetchone() > 0: + rows = cur.execute("delete from jobs where j_ip = %s", (IPadd)) + db.commit() + cur.close() + + populate_db(IPadd) + return jsonify({'IP': IPadd,'rows_deleted': rows}) + else: + return jsonify({'no_action': True}) + +@app.route('/', methods=['GET', 'POST']) +def login(): + login_error = '''I'm sorry. I can't find that combination of credentials in my database. Perhaps you mis-typed your password?''' + error = None + IPadd = request.environ['REMOTE_ADDR'] + db = MySQLdb.connect(**CONNECTION_ARGS) + cur = db.cursor() + + if request.method == 'POST': + uname = request.form['username'] + upswd = request.form['password'] + upw = sha1(upswd).hexdigest() + + rows = cur.execute("select uname, pw, role from users where uname = %s",(uname,)) + + if rows > 0: + users_name, pw_hash, role = cur.fetchone() + if rows == 0 or uname != users_name or upw != pw_hash: + error = login_error + else: + session['logged_in'] = True + session['username'] = uname + session['role'] = role + + cur.execute("select j_ip from jobs where j_ip = %s limit 1", IPadd) + if not cur.fetchone() > 0: + populate_db(IPadd) + + if session['role'] == 'admin': + return redirect(url_for('dispatcher')) + elif session['role'] == 'customer': + return redirect(url_for('repairRequest')) + cur.close() + return render_template('login.html', error=error) + +@app.route('/dispatcher', methods=['GET', 'POST']) +@logged_in +def dispatcher(): + error = None + db = MySQLdb.connect(**CONNECTION_ARGS) + cur = db.cursor() + IPadd = request.environ['REMOTE_ADDR'] + if session.get('role') != 'admin': + flash( 'please log in first' ) + return logout() + + + ##POST + if request.method == 'POST': + jid = request.form['job_id'] + field = request.form['field_value'] + new_value = request.form['new_value'] + set_job_status = "update jobs set job_status = %s where j_id=%s and j_ip=%s" + set_appointment = "update jobs set appointment = %s where j_id=%s and j_ip=%s" + + if new_value.upper() == 'NULL' or new_value.upper() == 'NONE': new_value = None + query = set_job_status if field == 'job_status' else set_appointment + + try: + affected_count = cur.execute(query, (new_value, jid, IPadd)) + db.commit() + if affected_count > 0: flash( 'your update was successful' ) + except MySQLdb.IntegrityError: + error = "failed to update job ID: %s" % (jid,) + finally: + cur.close() + + return redirect(url_for('dispatcher')) + + ##GET + else: + cur.execute("select j.j_id, c.cid, c.lname, j.make, j.appliance, j.job_status, j.appointment, j.description from jobs j, customer c where j.cid = c.cid and j.j_ip=%s", IPadd) + result = cur.fetchall() + + return render_template("dispatcher.html", + error = error, + title = "Dispatcher Interface", + user = session['username'], + result = result) + +@app.route('/repairRequest', methods=['GET', 'POST']) +@logged_in +def repairRequest(): + if session.get('role') != 'customer': + flash( 'please log in first' ) + return logout() + + session.permanent = False + error = None + user = session.get('username') + IPadd = request.environ['REMOTE_ADDR'] + db = MySQLdb.connect(**CONNECTION_ARGS) + cur = db.cursor() + rows = cur.execute("select c.fname, c.lname, u.cid from customer c, users u where u.uname = %s and u.cid = c.cid",(user,)) + if rows > 0: + fname, lname, cid = cur.fetchone() + else: + return logout() + + #POST + if request.method == 'POST': + make = request.form['make'].strip() + type = request.form['type'].strip() + description = request.form['description'].strip() + appointment = None + job_status = 'pending' + + rows = cur.execute("insert into jobs (j_ip,cid, make, appliance, appointment, job_status, description) values\ + (%s, %s, %s, %s, %s, %s, %s)", (IPadd, cid, make, type, appointment, job_status, description)) + db.commit() + if rows == 0: + error = "Your repair request failed." + + rows = cur.execute("select j_id, make, appliance, job_status, appointment from jobs where cid = %s and j_ip = %s", (cid, IPadd)) + result = cur.fetchall() + cur.close() + flash("Your request has been added to the database.") + + return render_template("repairRequest.html", + title = "Repair Request", + user = user, + fname = fname, + lname = lname, + result = result, + error = error) + + #GET + else: + cur.execute("select j_id, make, appliance, job_status, appointment from jobs where j_ip = %s and cid = %s", (IPadd, cid)) + result = cur.fetchall() + + return render_template("repairRequest.html", + title = "Repair Request", + user = user, + fname = fname, + lname = lname, + error = error, + result = result) + cur.close() + +##### Shut down the simple server from the browser address bar ##### +def shutdown_server(): + func = request.environ.get('werkzeug.server.shutdown') + if func is None: + raise RuntimeError('Not running with the Werkzeug Server') + func() + +@app.route('/shutdown', methods=['GET']) +def shutdown(): + shutdown_server() + return 'Server shutting down...' + +if __name__ == "__main__": + app.config['DEBUG'] = True + app.run(port=9229) diff --git a/files/default/AAR/awesomeapp.wsgi b/files/default/AAR/awesomeapp.wsgi new file mode 100755 index 0000000..c74cf4b --- /dev/null +++ b/files/default/AAR/awesomeapp.wsgi @@ -0,0 +1,7 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import sys +sys.path.insert(0, '/var/www/AAR') +from awesomeapp import app as application + diff --git a/files/default/AAR/robots.txt b/files/default/AAR/robots.txt new file mode 100755 index 0000000..77470cb --- /dev/null +++ b/files/default/AAR/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / \ No newline at end of file diff --git a/files/default/AAR/static/AAR.css b/files/default/AAR/static/AAR.css new file mode 100755 index 0000000..af496a3 --- /dev/null +++ b/files/default/AAR/static/AAR.css @@ -0,0 +1,174 @@ + body { + font-family: verdana, 'lucida grande', sans-serif; + } + + .aarhead { + font-size: 18pt; + font-weight: bold; + text-align: center + + } + #container { + width: 800px; + height: 100%; + border-left: 16px solid #59b6b2; + border-right: 16px solid #59b6b2; + border-radius: 16px; + margin-left: auto; + margin-right: auto; + } + #header { + width: 100%; + height: 164px; + background-color: #59b6b2; + } + + #navbar { + position: relative; + top: -30px; + float: right; + } + + #navbar a { + color: #fff; + font-weight: bold; + text-decoration: none; + } + + #navbar a:hover {color: #fcb614; text-decoration: overline underline;} + + #footer { + width: 100%; + height: 36px; + background-color: #59b6b2; + } + + #loginform { + float: right; + clear: both; + width: 340px; + margin: 16px 20px; + } + + #infodiv { + width: 340px; + float: right; + clear: right; + font-size: 10pt; + padding-right: 16px; + color: #666; + } + + #infodiv table { + width: 95%; + text-align: center; + font-size: 9pt; + float: right; + margin-bottom: 16px; + } + + + #request-form { + float: right; + clear: both; + font-size: 80%; + width: 480px; + margin-right: 16px; + } + + + fieldset { + padding: 1em; + } + + label { + float:left; + clear: left; + width:30%; + margin-right:0.5em; + padding-top:0.2em; + text-align:right; + } + + legend { + padding: 0.2em .5em; + font-weight: bold; + } + + .error { + position: relative; + background: url(images/bubble400px.png) no-repeat; + left: 310px; + top:110px; + width: 309px; + text-align: center; + padding: 0px 36px; + height: 400px; + } + + .request-dialog { + position: relative; + background: url(images/bubble.png) no-repeat; + width: 400px; + height: 140px; + left: 260px; + top: 230px; + text-align: center; + padding: 0px 60px; + } + + #request-dialog-inner { + position: relative; + left: 40px; + top: -10px; + } + .closeme { + position: relative; + left: 190px; + } + + .request-closeme { + position: relative; + margin-top: -16px; + left: 216px; + } + + .closeme, .request-closeme, .dispatch-closeme:hover {cursor: pointer;} + + .job_status { + text-decoration: underline; + } + .job_status:hover { + color: red !important; + cursor: pointer; + } + + .job_date { + text-decoration: underline; + } + .job_date:hover { + color: red !important; + cursor: pointer; + } + + #update {display: none} + + .flashes { + background-color: #f18a20; + color: #fff; + padding: 0px; + -moz-border-radius: 16px; + -webkit-border-radius: 16px; + border-radius: 16px; } + + #jobs-table-div {height: 300px; overflow: scroll} + #requested-jobs-table-div { + width: 476px; + float: right; + margin-right: 18px; + max-height: 140px; + overflow-y: scroll + } + + #requested-jobs-table th {border: none} + \ No newline at end of file diff --git a/files/default/AAR/static/images/aar_logo1.png b/files/default/AAR/static/images/aar_logo1.png new file mode 100755 index 0000000..d6fef8f Binary files /dev/null and b/files/default/AAR/static/images/aar_logo1.png differ diff --git a/files/default/AAR/static/images/bubble.png b/files/default/AAR/static/images/bubble.png new file mode 100755 index 0000000..df7617f Binary files /dev/null and b/files/default/AAR/static/images/bubble.png differ diff --git a/files/default/AAR/static/images/bubble400px.png b/files/default/AAR/static/images/bubble400px.png new file mode 100755 index 0000000..a4db8fa Binary files /dev/null and b/files/default/AAR/static/images/bubble400px.png differ diff --git a/files/default/AAR/static/images/close.png b/files/default/AAR/static/images/close.png new file mode 100755 index 0000000..df6c311 Binary files /dev/null and b/files/default/AAR/static/images/close.png differ diff --git a/files/default/AAR/static/images/fridge.png b/files/default/AAR/static/images/fridge.png new file mode 100755 index 0000000..4268218 Binary files /dev/null and b/files/default/AAR/static/images/fridge.png differ diff --git a/files/default/AAR/static/images/range.png b/files/default/AAR/static/images/range.png new file mode 100755 index 0000000..4c72a21 Binary files /dev/null and b/files/default/AAR/static/images/range.png differ diff --git a/files/default/AAR/static/js/AAR.js b/files/default/AAR/static/js/AAR.js new file mode 100755 index 0000000..92bf278 --- /dev/null +++ b/files/default/AAR/static/js/AAR.js @@ -0,0 +1,151 @@ +/*====================================== +* copyright, Opscode Inc. +* Apache License +* Version 2.0, January 2004 +* http://www.apache.org/licenses/ +*======================================= +*/ + +function formReset() { + document.getElementById("update-form").reset(); + $("#update").hide(); +} + +function validateDate(input) { + var sdate = input.split('-'); + if (!/^\d{4}$/.test(sdate[0]) || !/^\d{2}$/.test(sdate[1]) || !/^\d{2}$/.test(sdate[2])) { + alert('not valid date pattern, please use yyyy-mm-dd'); + return false; + } else { + var yearfield = sdate[0]; + var monthfield = sdate[1]; + var dayfield = sdate[2]; + var dayobj = new Date(yearfield, monthfield - 1, dayfield); + + if ((dayobj.getMonth() + 1 != monthfield) || (dayobj.getDate() != dayfield) || (dayobj.getFullYear() != yearfield)) { + alert("Invalid Day, Month, or Year range detected. Please correct and submit again."); + return false; + } else { + return true; + } + } +} + +function updateValidate() { + var valid = true; + var field = $("#update-form input[name='new_value']").val(); + if (field.toUpperCase() === 'NONE' || field.toUpperCase() === 'NULL' || validateDate(field)) { + valid = true; + } else { + valid = false; + } + + if (valid === false) { + alert("please enter a date in the form yyyy-mm-dd \n or the string 'null' or 'none'"); + return false; + } +} + +function validateForm() { + $(".request-dialog").remove(); + var valid = true; + $("#request-form input[type='text'], textarea").each(function() { + var $this = $(this); + if ($this.val() === null || $this.val() === '') { + valid = false; + } + }); + + if (valid === false) { + $("#request-form-div").append("
Please fill out all of the fields above before clicking the Submit button.
The sample data has not yet been generated. Please log in first ...
" + (data.rows_deleted - 28) + " new records, created from " + data.IP + ", were deleted, and the original sample data was restored.
t |
job ID | cust ID | Last Name | Make | Appliance | Job Status | Appointment | Description | +
---|---|---|---|---|---|---|---|
{{ cell }} | + {% elif loop.index == 7 %} +{{ cell }} | + {% else %} +{{ cell }} | + {% endif %} + {% endfor %} +
This is the log on page for Awesome Appliance. If you log on as a customer, you're directed to a page where you can request repairs. If you log on as an administrator, you're directed to a page where you can view requests and update data in the database.
+There are three users built in:
+username | password | role |
---|---|---|
ad1 | ad1pw | administrator |
cust1 | cpw1 | customer |
cust2 | cpw2 | customer |
Click here to reset the database to the default sample data:
+Error: {{ error }}
+{{ message }}
Welcome, {{fname}} {{lname}}
+Please tell us what kind of appliance needs repair and provide a brief description of the problem. We'll schedule an appointment as soon as possible.
+Here's a list of the jobs you've submitted:
+job ID | Make | Appliance | Job Status | Appointment | +
---|---|---|---|---|
{{ cell }} | + {% elif loop.index == 7 %} +{{ cell }} | + {% else %} +{{ cell }} | + {% endif %} + {% endfor %} +
{{ error }}
+{{ message }}
+