Browse Source

Added DB and table creation. Started working on getting the functionality working.

Adam Day 2 năm trước cách đây
mục cha
commit
0ea3df8c34

+ 174 - 22
app.py

@@ -3,7 +3,6 @@ from urllib.parse import urlparse
 import webbrowser
 from emailtool.emailer import Emailer
 from flask import Flask, render_template, request, redirect, url_for, session, send_file
-import configparser
 from gevent.pywsgi import WSGIServer
 import socket
 import logging
@@ -35,17 +34,68 @@ class IlsUser(Model):
         database = db
 
 
-config = configparser.ConfigParser()
-config.read('settings.ini')
-application_settings = config['application']
-smtp_settings = config['smtp']
-http_settings = config['http']
-domain_settings = config['domain']
+class Settings(Model):
+    name = CharField()
+    value = CharField()
+
+    class Meta:
+        database = db
+
+
+class Schedule(Model):
+    days = CharField()
+
+    class Meta:
+        database = db
+
+
+class Log(Model):
+    date = DateTimeField(default=datetime.datetime.now)
+    username = CharField()
+    action = CharField()
+
+    class Meta:
+        database = db
+
+
+class PasswordResetLog(Model):
+    date = DateTimeField(default=datetime.datetime.now)
+    username = CharField()
+
+    class Meta:
+        database = db
+
+
+settings = Settings.select().execute()
+
+debug_setting = Settings.get(Settings.name == 'Debug Mode')
+debug = debug_setting.value
+
+http_port_setting = Settings.get(Settings.name == 'HTTP Port')
+http_port = http_port_setting.value
+
+smtp_host_setting = Settings.get(Settings.name == 'SMTP Host')
+smtp_host = smtp_host_setting.value
+
+smtp_port_setting = Settings.get(Settings.name == 'SMTP Port')
+smtp_port = smtp_port_setting.value
+
+smtp_username_setting = Settings.get(Settings.name == 'SMTP Username')
+smtp_username = smtp_username_setting.value
+
+smtp_password_setting = Settings.get(Settings.name == 'SMTP Password')
+smtp_password = smtp_password_setting.value
+
+domain_name_setting = Settings.get(Settings.name == 'Domain Name')
+domain_name = domain_name_setting.value
+
+password_reset_url_setting = Settings.get(Settings.name == 'Password Reset URL')
+password_reset_url = password_reset_url_setting.value
 
 log = logging.getLogger('werkzeug')
 log.setLevel(logging.INFO)
 
-if application_settings['debug'].lower() == 'true':
+if debug.lower() == 'true':
     debug = True
 else:
     debug = False
@@ -53,10 +103,10 @@ else:
 
 def send_email(to, subject, body):
     # Get settings from smtp_settings
-    host = smtp_settings['host']
-    port = smtp_settings['port']
-    username = smtp_settings['username']
-    password = smtp_settings['password']
+    host = smtp_host
+    port = smtp_port
+    username = smtp_username
+    password = smtp_password
 
     # Create an instance of the Emailer class
     emailer = Emailer(host, port, username, password)
@@ -119,10 +169,30 @@ if db.table_exists('user') is False:
 if db.table_exists('ilsuser') is False:
     db.create_tables([IlsUser])
 
+if db.table_exists('settings') is False:
+    db.create_tables([Settings])
+    Settings.create(name='Debug Mode', value=False).save()
+    Settings.create(name='HTTP Port', value=5055).save()
+    Settings.create(name='SMTP Host', value="").save()
+    Settings.create(name='SMTP Port', value="587").save()
+    Settings.create(name='SMTP Username', value="").save()
+    Settings.create(name='SMTP Password', value="").save()
+    Settings.create(name='Domain Name', value="lynx").save()
+    Settings.create(name='Password Reset URL', value="https://terminal.idaho-lynx.org").save()
+
+if db.table_exists('log') is False:
+    db.create_tables([Log])
+
+if db.table_exists('password_reset_log') is False:
+    db.create_tables([PasswordResetLog])
+
+if db.table_exists('schedule') is False:
+    db.create_tables([Schedule])
+
 db.close()
 
 app = Flask(__name__)
-app.secret_key = 'super secret key'
+app.secret_key = os.urandom(24)
 
 
 @app.before_request
@@ -142,7 +212,7 @@ def index():
     # send_email('aday@twinfallspubliclibrary.org', 'TEST', 'This is a test email')
     error = None
     reset = False
-    reset_url = is_valid_url(domain_settings['reset_url'])
+    reset_url = is_valid_url(password_reset_url)
     reset_url_error = False
 
     if reset_url is False:
@@ -161,6 +231,7 @@ def index():
         if user:
             # Reset login datetime
             user.reset_datetime = datetime.datetime.now()
+            PasswordResetLog.create(username=user.username, date_created=datetime.datetime.now()).save()
             user.save()
 
             # Open the reset URL in a new tab if the URL is valid
@@ -172,7 +243,7 @@ def index():
             error = 'Invalid username'
 
     context = {
-        'domain': domain_settings['name'],
+        'domain': domain_name,
         'error': error,
         'reset': reset,
         'reset_url': reset_url,
@@ -216,6 +287,7 @@ def admin_users():
                 User.create(username=username, password=encrypt_password(password),
                             date_created=datetime.datetime.now(), logged_in=False).save()
                 message = 'User created successfully'
+                Log.create(username=session['username'], action='Created admin user: %s' % username, ).save()
             else:
                 message = 'Passwords do not match'
 
@@ -243,7 +315,9 @@ def admin_users_delete(id):
         # Unset session variable if the user is deleting their own account
         if user.username == session['username']:
             session.pop('username', None)
+        username = user.username
         user.delete_instance()
+        Log.create(username=session['username'], action='Removed admin user: %s' % username, ).save()
 
     return redirect(url_for('admin_users'))
 
@@ -270,7 +344,8 @@ def admin_ils_users():
         else:
 
             IlsUser.create(username=username, email=email, reset_datetime=datetime.datetime.now()).save()
-            message = 'User created successfully'
+            message = 'ILS User: %s created successfully' % username
+            Log.create(username=session['username'], action='Created ILS User: %s' % username,).save()
 
     # Get all admin users from the DB
     users = IlsUser.select().execute()
@@ -291,8 +366,9 @@ def admin_ils_users_delete(id):
 
     # Get the user from the DB
     user = IlsUser.get(IlsUser.id == id)
+    username = user.username
     user.delete_instance()
-
+    Log.create(username=session['username'], action='Removed ILS user: %s' % username, ).save()
     return redirect(url_for('admin_ils_users'))
 
 
@@ -309,7 +385,7 @@ def admin_ils_users_csv_download():
         users = IlsUser.select().execute()
         for user in users:
             writer.writerow([user.username, user.email])
-
+    Log.create(username=session['username'], action='Downloaded ILS user CSV file.').save()
     # return the CSV file to the user
     return send_file('users.csv', as_attachment=True)
 
@@ -362,12 +438,87 @@ def admin_ils_users_csv_import():
     return render_template('csv.html', context=context)
 
 
+@app.route('/admin/settings', methods=['GET', 'POST'])
+def settings():
+    # Check to see if user is logged in
+    if not requires_auth():
+        return redirect(url_for('login'))
+
+    message = None
+
+    # Process form submission
+    if request.method == 'POST':
+        # Assign form values to variables
+        id = request.form.get('id')
+        value = request.form.get('value')
+
+        # Check if the setting exists
+        try:
+            setting = Settings.get(Settings.id == id)
+        except Exception as e:
+            print(e)
+            setting = None
+
+        if setting:
+            # Update the setting
+            old_value = setting.value
+            setting.value = value
+            setting.save()
+            Log.create(username=session['username'], action='Changed %s setting from "%s" to "%s"' % (setting.name,
+                                                                                                      old_value,
+                                                                                                      value)).save()
+            message = '%s updated successfully' % setting.name
+
+    # Get settings from DB
+    all_settings = Settings.select().execute()
+
+    context = {
+        'settings': all_settings,
+        'message': message,
+    }
+
+    return render_template('settings.html', context=context)
+
+
+@app.route('/admin/system/log')
+def system_log():
+    # Check to see if user is logged in
+    if not requires_auth():
+        return redirect(url_for('login'))
+
+    # Get all logs from the DB
+    logs = Log.select().order_by(Log.id.desc()).execute()
+
+    context = {
+        'logs': logs,
+    }
+
+    return render_template('system_log.html', context=context)
+
+
+@app.route('/admin/system/log/password/resets')
+def password_reset_log():
+    # Check to see if user is logged in
+    if not requires_auth():
+        return redirect(url_for('login'))
+
+    # Get all logs from the DB
+    logs = PasswordResetLog.select().order_by(PasswordResetLog.id.desc()).execute()
+
+    context = {
+        'logs': logs,
+    }
+
+    return render_template('password_reset_log.html', context=context)
+
+
 @app.route('/logout')
 def logout():
     if 'username' in session:
         username = session['username']
         user = User.get(User.username == username)
         user.logged_in = False
+        Log.create(username=session['username'], action='Logged out').save()
         user.save()
         session.pop('username', None)
     return redirect(url_for('login'))
@@ -390,6 +541,7 @@ def login():
             # Login user
             session['username'] = request.form.get('username')
             user.logged_in = True
+            Log.create(username=session['username'], action='Logged in').save()
             user.save()
 
             return redirect(url_for('admin'))
@@ -413,20 +565,20 @@ def login():
 
 if __name__ == "__main__":
     print("------------------------- Start up -----------------------------")
-    print("Starting HTTP Service on port %s..." % http_settings['port'])
+    print("Starting HTTP Service on port %s..." % http_port)
 
     if debug is True:
         print("Debug mode is enabled.")
-        http_server = WSGIServer(('0.0.0.0', int(http_settings['port'])), app)
+        http_server = WSGIServer(('0.0.0.0', int(http_port)), app)
     else:
-        http_server = WSGIServer(('0.0.0.0', int(http_settings['port'])), app, log=log, error_log=log)
+        http_server = WSGIServer(('0.0.0.0', int(http_port)), app, log=log, error_log=log)
     print("HTTP Service Started.")
     print("--------------------- Application Details ---------------------")
     print("Application started at %s" % datetime.datetime.now())
     print("System IP Address: %s" % get_ip_address())
     print("System Hostname: %s" % get_hostname())
     print("Access the Dashboard using a web browser using any of the following:")
-    print("http://%s:%s or http://%s:%s" % (get_hostname(), http_settings['port'], get_ip_address(), http_settings['port']))
+    print("http://%s:%s or http://%s:%s" % (get_hostname(), http_port, get_ip_address(), http_port))
     print("---------------------------------------------------------------")
     print("To stop the application close this window.")
     print("---------------------------------------------------------------")

+ 0 - 3
ils_password_tool.ini

@@ -1,3 +0,0 @@
-[uwsgi]
-http = :5055
-module = app:app

+ 0 - 16
settings.ini

@@ -1,16 +0,0 @@
-[application]
-debug = true
-
-[http]
-port = 5055
-
-[smtp]
-host = smtp.gmail.com
-port = 587
-username = tfplweb@twinfallspubliclibrary.org
-password = uYP&>m68
-
-[domain]
-name = lynx
-password_check_interval = [60, 80, 85, 88, 89, 90]
-reset_url = https://terminal.idaho-lynx.org

+ 2 - 1
templates/admin_ils_users.html

@@ -9,7 +9,8 @@
 {% endif %}
 <div class="row">
     <div class="col">
-        <a href="{{ url_for('admin_ils_users_csv_import') }}" class="btn btn-dark float-end"><i class="ri-file-excel-line"></i> CSV Import</a>
+        <a href="{{ url_for('admin_ils_users_csv_download') }}" class="btn btn-dark float-end"><i class="ri-file-download-fill"></i> CSV Export</a>
+        <a href="{{ url_for('admin_ils_users_csv_import') }}" class="btn btn-dark float-end me-2"><i class="ri-file-excel-line"></i> CSV Import</a>
         <a href="#!" data-bs-toggle="modal" data-bs-target="#add-user" class="btn btn-primary float-end me-2"><i class="ri-user-add-line"></i> Add ILS User</a>
         <h3><i class="ri-shield-user-fill"></i> ILS Users</h3>
         <p class="lead">ILS users accounts establish with the ILS system.</p>

+ 11 - 5
templates/auth_layout.html

@@ -18,19 +18,25 @@
             <div class="col-2 border" style="min-height:50vh;">
                 <ul class="nav flex-column">
                     <li class="nav-item">
-                        <a class="nav-link bg-light text-dark border-bottom" href="/admin"><i class="ri-dashboard-fill"></i> Admin Dashboard</a>
+                        <a class="nav-link bg-light text-dark border-bottom" href="{{ url_for('admin') }}"><i class="ri-dashboard-fill"></i> Admin Dashboard</a>
                     </li>
                     <li class="nav-item">
-                        <a class="nav-link bg-light text-dark border-bottom" href="/admin/users"><i class="ri-shield-user-line"></i> Admin Users</a>
+                        <a class="nav-link bg-light text-dark border-bottom" href="{{ url_for('admin_users') }}"><i class="ri-shield-user-line"></i> Admin Users</a>
                     </li>
                     <li class="nav-item">
-                        <a class="nav-link bg-light text-dark border-bottom" href="/admin/users/ils"><i class="ri-shield-user-fill"></i> ILS Users</a>
+                        <a class="nav-link bg-light text-dark border-bottom" href="{{ url_for('admin_ils_users') }}"><i class="ri-shield-user-fill"></i> ILS Users</a>
                     </li>
                     <li class="nav-item">
-                        <a class="nav-link bg-light text-dark border-bottom" href="#"><i class="ri-settings-5-line"></i> Settings</a>
+                        <a class="nav-link bg-light text-dark border-bottom" href="{{ url_for('settings') }}"><i class="ri-settings-5-line"></i> Settings</a>
                     </li>
                     <li class="nav-item">
-                        <a class="nav-link bg-light text-dark border-bottom" href="/logout"><i class="ri-logout-box-line"></i> Logout</a>
+                        <a class="nav-link bg-light text-dark border-bottom" href="{{ url_for('system_log') }}"><i class="ri-file-list-3-line"></i> System Log</a>
+                    </li>
+                    <li class="nav-item">
+                        <a class="nav-link bg-light text-dark border-bottom" href="{{ url_for('password_reset_log') }}"><i class="ri-file-list-3-line"></i> Password Reset Log</a>
+                    </li>
+                    <li class="nav-item">
+                        <a class="nav-link bg-light text-dark border-bottom" href="{{ url_for('logout') }}"><i class="ri-logout-box-line"></i> Logout</a>
                     </li>
                 </ul>
             </div>

+ 2 - 2
templates/csv.html

@@ -10,8 +10,8 @@
 
 <div class="row">
     <div class="col">
-        <a href="{{ url_for('admin_ils_users_csv_download') }}" class="btn btn-secondary float-end"><i class="ri-file-download-fill"></i> Download CSV Template</a>
-        <a href="{{ url_for('admin_ils_users') }}" class="btn btn-dark float-end me-2"><i class="ri-arrow-left-circle-fill"></i> Back</a>
+        <a href="{{ url_for('admin_ils_users') }}" class="btn btn-dark float-end"><i class="ri-arrow-left-s-line"></i> Back</a>
+        <a href="{{ url_for('admin_ils_users_csv_download') }}" class="btn btn-secondary float-end me-2"><i class="ri-file-download-fill"></i> Download CSV Template</a>
         <h3><i class="ri-file-excel-line"></i> CSV Import ILS Users</h3>
         <p class="lead">ILS users can be loaded using a comma delimited CSV file.</p>
         <p class="alert alert-warning">

+ 1 - 1
templates/login.html

@@ -10,7 +10,7 @@
                     </div>
                 {% endif %}
                 <div class="mb-3">
-                    <input type="text" class="form-control" id="username" name="username" placeholder="ILS user account">
+                    <input type="text" class="form-control" id="username" name="username" placeholder="Username">
                 </div>
                 <div class="mb-3">
                     <input type="password" class="form-control" id="password" name="password" placeholder="Password">

+ 30 - 0
templates/password_reset_log.html

@@ -0,0 +1,30 @@
+{% extends 'auth_layout.html' %}
+{% block content %}
+
+<div class="row">
+    <div class="col">
+        <h3><i class="ri-file-list-3-line"></i> Password Reset Log</h3>
+    </div>
+</div>
+<div class="row">
+    <div class="col">
+        <table class="table table-striped">
+            <thead>
+                <tr>
+                    <th scope="col">ILS User</th>
+                    <th scope="col">Reset Date/Time</th>
+                </tr>
+            </thead>
+            <tbody>
+                {% for log in context.logs %}
+                <tr>
+                    <td>{{ log.username }}</td>
+                    <td>{{ log.date }}</td>
+                </tr>
+                {% endfor %}
+            </tbody>
+        </table>
+    </div>
+</div>
+
+{% endblock %}

+ 66 - 0
templates/settings.html

@@ -0,0 +1,66 @@
+{% extends 'auth_layout.html' %}
+{% block content %}
+{% if context.message %}
+    <div class="row">
+        <div class="col text-center text-primary">
+            <i class="ri-settings-5-line"></i> {{ context.message }}
+        </div>
+    </div>
+{% endif %}
+<div class="row">
+    <div class="col">
+        <h3><i class="ri-shield-user-line"></i> Settings</h3>
+    </div>
+</div>
+<div class="row">
+    <div class="col">
+        <table class="table table-flush">
+            <thead>
+                <tr>
+                    <th scope="col">Name</th>
+                    <th scope="col">Value</th>
+                    <th scope="col">Actions</th>
+                </tr>
+            </thead>
+            <tbody>
+                {% for setting in context.settings %}
+                <tr>
+                    <td>{{ setting.name }}</td>
+                    <td>{{ setting.value }}</td>
+                    <td>
+                        <a href="#!" data-bs-toggle="modal" data-bs-target="#setting-{{ loop.index0 }}" class="btn btn-primary">Edit</a>
+                    </td>
+                </tr>
+                {% endfor %}
+            </tbody>
+        </table>
+    </div>
+</div>
+
+{% for setting in context.settings %}
+<div class="modal fade" id="setting-{{ loop.index0 }}" tabindex="-1">
+    <div class="modal-dialog modal-dialog-centered">
+        <div class="modal-content">
+            <form action="" method="post">
+                <div class="modal-header bg-light">
+                    <h1 class="modal-title fs-5">Edit {{ setting.name}}</h1>
+                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+                </div>
+                <div class="modal-body">
+                    <div class="mb-3">
+                        <label for="value" class="form-label">{{ setting.name}}</label>
+                        <input type="text" class="form-control" id="value" name="value" value="{{ setting.value }}" required>
+                        <input type="hidden" name="id" value="{{ setting.id }}" required>
+                    </div>
+                </div>
+                <div class="modal-footer bg-light">
+                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
+                    <input type="submit" class="btn btn-primary" value="Save">
+                </div>
+            </form>
+        </div>
+    </div>
+</div>
+{% endfor %}
+
+{% endblock %}

+ 32 - 0
templates/system_log.html

@@ -0,0 +1,32 @@
+{% extends 'auth_layout.html' %}
+{% block content %}
+
+<div class="row">
+    <div class="col">
+        <h3><i class="ri-file-list-3-line"></i> System Log</h3>
+    </div>
+</div>
+<div class="row">
+    <div class="col">
+        <table class="table table-striped">
+            <thead>
+                <tr>
+                    <th scope="col">Admin User</th>
+                    <th scope="col">Date/Time</th>
+                    <th scope="col">Action</th>
+                </tr>
+            </thead>
+            <tbody>
+                {% for log in context.logs %}
+                <tr>
+                    <td>{{ log.username }}</td>
+                    <td>{{ log.date }}</td>
+                    <td>{{ log.action }}</td>
+                </tr>
+                {% endfor %}
+            </tbody>
+        </table>
+    </div>
+</div>
+
+{% endblock %}