Started dashboard implementation

This commit is contained in:
Lucas Mathews
2024-05-26 18:03:50 +02:00
parent 241ab8529b
commit b835653fb8
9 changed files with 320 additions and 40 deletions

27
api.yml
View File

@@ -134,7 +134,7 @@ paths:
description: OTP not valid description: OTP not valid
'404': '404':
description: client_id not found description: client_id not found
/Client/Client: /Client:
put: put:
tags: tags:
- client - client
@@ -213,6 +213,31 @@ paths:
description: Invalid Client ID supplied description: Invalid Client ID supplied
'404': '404':
description: Client not found description: Client not found
/Client/Accounts:
get:
tags:
- client
summary: Get all accounts for a client
description: Get all accounts for a client
operationId: manager.get_accounts
parameters:
- name: client_id
in: query
description: ID of client to return accounts for
required: true
schema:
type: string
responses:
'200':
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/ApiResponse'
'400':
description: Invalid input
'404':
description: No accounts found
/Account: /Account:
post: post:
tags: tags:

View File

@@ -4,7 +4,7 @@ port=81
url=http://127.0.0.1:81 url=http://127.0.0.1:81
[preferences] [preferences]
dark_theme=system dark_theme=dark
# Modes: system, light, dark # Modes: light, dark
theme=dark-blue theme=dark-blue
# Themes: blue, dark-blue, green # Themes: blue, dark-blue, green

View File

@@ -9,16 +9,10 @@ import json
def authenticate_client(client_id, client_password): def authenticate_client(client_id, client_password):
try: try:
# Send a POST request to the /Client/Login endpoint with the client_id and password
response = requests.post(CONFIG["server"]["url"] + "/Client/Login", params={'client_id': client_id, 'password': client_password}) response = requests.post(CONFIG["server"]["url"] + "/Client/Login", params={'client_id': client_id, 'password': client_password})
# Return the response from the API
return response return response
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
# If a RequestException is raised, print the exception message
print(f"RequestException: {e}") print(f"RequestException: {e}")
# Create a new Response object with a status code of 500 and the error message in the JSON body
response = Response() response = Response()
response.status_code = 500 response.status_code = 500
response._content = b'{"success": false, "message": "Could not connect to the server. Please try again later."}' response._content = b'{"success": false, "message": "Could not connect to the server. Please try again later."}'
@@ -26,21 +20,93 @@ def authenticate_client(client_id, client_password):
def logout_client(): def logout_client():
try: try:
# Load the session cookie from the file with open('application\\session_data.json', 'r') as f: # Open the session_data.json file in read mode
with open('application\\session_cookie.json', 'r') as f: session_data = json.load(f)
cookies = json.load(f) response = requests.post(CONFIG["server"]["url"] + "/Client/Logout", cookies=session_data['session_cookie'])
# Send a POST request to the /Client/Logout endpoint
response = requests.post(CONFIG["server"]["url"] + "/Client/Logout", cookies=cookies)
# Return the response from the API
return response return response
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
# If a RequestException is raised, print the exception message
print(f"RequestException: {e}") print(f"RequestException: {e}")
# Create a new Response object with a status code of 500 and the error message in the JSON body
response = Response() response = Response()
response.status_code = 500 response.status_code = 500
response._content = b'{"success": false, "message": "Could not connect to the server. Please try again later."}' response._content = b'{"success": false, "message": "Could not connect to the server. Please try again later."}'
return response return response
def get_client(client_id):
try:
with open('application\\session_data.json', 'r') as f: # Open the session_data.json file in read mode
session_data = json.load(f)
response = requests.get(CONFIG["server"]["url"] + "/Client", cookies=session_data['session_cookie'], params={'client_id': client_id})
return response.json()
except requests.exceptions.RequestException as e:
print(f"RequestException: {e}")
response = Response()
response.status_code = 500
response._content = b'{"success": false, "message": "Could not connect to the server. Please try again later."}'
return response.json()
def update_client(client_id, email=None, phone_number=None, address=None):
try:
with open('application\\session_data.json', 'r') as f:
session_data = json.load(f)
# Create a dictionary of parameters to update
params = {'client_id': client_id}
if email is not None:
params['email'] = email
if phone_number is not None:
params['phone_number'] = phone_number
if address is not None:
params['address'] = address
response = requests.put(
CONFIG["server"]["url"] + "/Client",
cookies=session_data['session_cookie'],
params=params
)
response.raise_for_status() # Raise an exception if the request failed
return response.json()
except requests.exceptions.RequestException as e:
print(f"RequestException: {e}")
return {'success': False, 'message': "Could not connect to the server. Please try again later."}
def get_accounts(client_id):
try:
with open('application\\session_data.json', 'r') as f:
session_data = json.load(f)
response = requests.get(
CONFIG["server"]["url"] + "/Client/Accounts",
cookies=session_data['session_cookie'],
params={'client_id': client_id}
)
response.raise_for_status() # Raise an exception if the request failed
accounts = response.json()
if isinstance(accounts, str): # If the response is a string, convert it to a list of dictionaries
accounts = json.loads(accounts)
return accounts
except requests.exceptions.RequestException as e:
print(f"RequestException: {e}")
return {'success': False, 'message': "Could not connect to the server. Please try again later."}
def format_balance(balance): # Formats the balance as a currency string with comma seperator
return f"{balance:,.2f}"
def get_transactions(account_id):
try:
with open('application\\session_data.json', 'r') as f:
session_data = json.load(f)
response = requests.get(
CONFIG["server"]["url"] + "/Account/Transactions",
cookies=session_data['session_cookie'],
params={'account_id': account_id}
)
response.raise_for_status() # Raise an exception if the request failed
transactions = response.json()
if isinstance(transactions, str): # If the response is a string, convert it to a list of dictionaries
transactions = json.loads(transactions)
return transactions
except requests.exceptions.RequestException as e:
print(f"RequestException: {e}")
return {'success': False, 'message': "Could not connect to the server. Please try again later."}

View File

@@ -2,11 +2,26 @@
# Banking System Dashboard Page # Banking System Dashboard Page
import tkinter as tk import tkinter as tk
from tkinter import messagebox from tkinter import messagebox, ttk
import customtkinter import customtkinter
import json
from config import CONFIG
from connection import logout_client from connection import logout_client, get_client, update_client, get_accounts, format_balance
email_entry = None
phone_entry = None
address_entry = None
frame = None
#################
### Functions ###
#################
def logout(): def logout():
response = logout_client() # Call the logout_client function response = logout_client() # Call the logout_client function
@@ -17,21 +32,174 @@ def logout():
else: else:
messagebox.showerror("Logout failed", json_response['message']) messagebox.showerror("Logout failed", json_response['message'])
# Create the main window def display_client_info():
global frame # Declare frame as global inside the function
if frame is not None:
for widget in frame.winfo_children(): # Destroy all widgets in the frame
widget.destroy()
else:
frame = customtkinter.CTkFrame(root)
frame.pack(anchor='w', side='left', padx=20, pady=20)
with open('application\\session_data.json', 'r') as f:
session_data = json.load(f)
client_id = session_data['client_id']
client_info = get_client(client_id)
if 'success' in client_info and client_info['success']:
client = client_info['data']
fields = [('Name', 'name'),
('Client ID', 'client_id'),
('Email', 'email'),
('Phone', 'phone_number'),
('Address', 'address'),
('Account Opened', 'opening_timestamp')]
for i, (display_name, key) in enumerate(fields):
value = client.get(key, 'N/A') # Use 'N/A' as the default value if the key is not found
label_key = customtkinter.CTkLabel(frame, text=f"{display_name}: ", font=("Helvetica", 14))
label_value = customtkinter.CTkLabel(frame, text=value, font=("Helvetica", 14))
label_key.grid(row=i, column=0, sticky='e')
label_value.grid(row=i, column=1, sticky='w')
else:
error_label = customtkinter.CTkLabel(root, text="Error: Could not retrieve client information", font=("Helvetica", 14))
error_label.pack(pady=20)
edit_button = customtkinter.CTkButton(frame, text="Edit Details", command=edit_details)
edit_button.grid(row=len(fields), column=0, columnspan=2)
def edit_details():
global edit_window, email_entry, phone_entry, address_entry # Declare the variables as global inside the function
edit_window = customtkinter.CTkToplevel(root)
edit_window.title("Edit Details")
edit_window.geometry("300x200")
edit_window.attributes('-topmost', True)
email_label = customtkinter.CTkLabel(edit_window, text="Email: ")
email_entry = customtkinter.CTkEntry(edit_window)
email_label.pack()
email_entry.pack()
phone_label = customtkinter.CTkLabel(edit_window, text="Phone: ")
phone_entry = customtkinter.CTkEntry(edit_window)
phone_label.pack()
phone_entry.pack()
address_label = customtkinter.CTkLabel(edit_window, text="Address: ")
address_entry = customtkinter.CTkEntry(edit_window)
address_label.pack()
address_entry.pack()
save_button = customtkinter.CTkButton(edit_window, text="Save", command=save_details)
save_button.pack()
edit_window.lift()
def save_details():
global edit_window, email_entry, phone_entry, address_entry
new_email = email_entry.get() if email_entry.get() != '' else None
new_phone = phone_entry.get() if phone_entry.get() != '' else None
new_address = address_entry.get() if address_entry.get() != '' else None
# Get the client_id from the session data
with open('application\\session_data.json', 'r') as f:
session_data = json.load(f)
client_id = session_data['client_id']
# Display a confirmation dialog box
if not messagebox.askyesno("Confirmation", "Are you sure you want to update the details?"):
return # If the user clicked 'No', exit the function
# Update the client details
result = update_client(client_id, new_email, new_phone, new_address)
# If the request was successful, update the client info displayed in the main window
if result['success']:
display_client_info()
# Destroy the window after updating the client info
edit_window.destroy()
def populate_table():
# Get the client_id from the session data
with open('application\\session_data.json', 'r') as f:
session_data = json.load(f)
client_id = session_data['client_id']
# Get the accounts for the client
response = get_accounts(client_id)
accounts = response['data'] if 'data' in response else []
# Check if accounts is a list
if not isinstance(accounts, list):
print(f"Error: Expected a list of accounts, but got {type(accounts)}")
return
# Populate the table with the accounts
for account in accounts:
formatted_balance = format_balance(account['balance'])
table.insert('', 'end', values=(account['description'], account['account_id'], formatted_balance, account['account_type']))
def on_account_double_click(event):
# Get the selected account
selected_account = table.item(table.selection())
# Open a new window
account_window = tk.Toplevel(root)
# Display the account details
display_account_details(account_window, selected_account)
##############
### Layout ###
##############
root = customtkinter.CTk() root = customtkinter.CTk()
# Set the window title, icon, and size
root.title("Luxbank Dashboard") root.title("Luxbank Dashboard")
root.iconbitmap("application/luxbank.ico") root.iconbitmap("application/luxbank.ico")
root.geometry("800x600") root.geometry("800x350")
# Create a label with a welcome message # Check if dark mode is enabled
if CONFIG["preferences"]["dark_theme"] == "dark":
# Set the style for dark mode
customtkinter.set_appearance_mode("dark")
else:
# Set the style for light mode
customtkinter.set_appearance_mode("light")
# Create a label for the title
welcome_label = customtkinter.CTkLabel(root, text="Welcome to the Luxbank Dashboard!", font=("Helvetica", 24)) welcome_label = customtkinter.CTkLabel(root, text="Welcome to the Luxbank Dashboard!", font=("Helvetica", 24))
welcome_label.pack(pady=20) welcome_label.pack(pady=20)
# Create a Logout button display_client_info()
logout_button = customtkinter.CTkButton(root, text="Logout", command=logout) logout_button = customtkinter.CTkButton(root, text="Logout", command=logout)
logout_button.pack(pady=15) logout_button.pack(pady=15)
# Start the main loop # Create a frame for the table
table_frame = ttk.Frame(root)
table_frame.pack(side='right', fill='both', expand=True)
# Create the table
table = ttk.Treeview(table_frame, columns=('Description', 'Account ID', 'Balance', 'Account Type'), show='headings')
table.heading('Description', text='Description')
table.heading('Account ID', text='Account ID')
table.heading('Balance', text='Balance')
table.heading('Account Type', text='Account Type')
# Set the column widths
table.column('Description', width=200)
table.column('Account ID', width=100)
table.column('Balance', width=100)
table.column('Account Type', width=100)
table.pack(fill='both', expand=True)
populate_table()
# Create a scrollbar for the table
scrollbar = ttk.Scrollbar(table_frame, orient='vertical', command=table.yview)
scrollbar.pack(side='right', fill='y')
table.configure(yscrollcommand=scrollbar.set)
table.bind("<Double-1>", on_account_double_click)
root.mainloop() root.mainloop()

View File

@@ -5,7 +5,9 @@ from tkinter import messagebox
import customtkinter import customtkinter
import os import os
import json import json
from connection import * import requests
from connection import authenticate_client
from config import CONFIG
################# #################
### Functions ### ### Functions ###
@@ -18,18 +20,19 @@ def login():
response = authenticate_client(client_id, client_password) # Authenticate the client response = authenticate_client(client_id, client_password) # Authenticate the client
json_response = response.json() # Convert the response content to JSON json_response = response.json() # Convert the response content to JSON
if json_response["success"] == True: # If the authentication is successful, open the dashboard if json_response["success"] == True: # If the authentication is successful, open the dashboard
# Save the session cookie to a file session_data = {
with open('application\\session_cookie.json', 'w') as f: 'session_cookie': response.cookies.get_dict(),
json.dump(response.cookies.get_dict(), f) 'client_id': client_id
}
with open('application\\session_data.json', 'w') as f: # Save the session data to a file
json.dump(session_data, f)
root.destroy() root.destroy()
os.system("python application\\dashboard.py") os.system("python application\\dashboard.py")
else: else:
messagebox.showerror("Login failed", json_response["message"]) # If the authentication fails, show an error message messagebox.showerror("Login failed", json_response["message"])
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
# If a RequestException is raised, show an error message that includes the exception message
messagebox.showerror("Login failed", "Could not connect to the server. Please try again later. Error: " + str(e)) messagebox.showerror("Login failed", "Could not connect to the server. Please try again later. Error: " + str(e))
############## ##############
### Layout ### ### Layout ###
############## ##############
@@ -54,6 +57,8 @@ entry_password.pack(pady=10)
login_button= customtkinter.CTkButton(root, text="Login", command=login) login_button= customtkinter.CTkButton(root, text="Login", command=login)
login_button.pack(pady=15) login_button.pack(pady=15)
root.bind('<Return>', lambda event=None: login())
########### ###########
### Run ### ### Run ###
########### ###########

View File

@@ -1 +1 @@
{"session": "rZS5tQOS4nXGJu-WXEg6_Ls5q8njy3GNiZ3s8N3YHEA"} {"session": "ECxyI0rswhPqvD9bW2KiRsWLmRsVY8bhxDbK40X1_Ok"}

View File

@@ -0,0 +1 @@
{"session_cookie": {"session": "xC02SzGKn_a4_R2fhKuj8qNWFk1MIx9zjatqzyzpRBM"}, "client_id": "31d90aad"}

View File

@@ -13,6 +13,8 @@ import time # For OTP generation
from functools import wraps # For decorators / user login from functools import wraps # For decorators / user login
from database import * # Importing the database connection from database import * # Importing the database connection
from emailer import send_email # Importing the emailer function from emailer import send_email # Importing the emailer function
from flask import session as flask_session
from database import session
otps = {} # Temporary dictionary to store OTPs and their creation time otps = {} # Temporary dictionary to store OTPs and their creation time
@@ -47,10 +49,12 @@ def format_response(success: bool, message: str = '', data: dict = None): # Form
response['data'] = data response['data'] = data
return jsonify(response) return jsonify(response)
def get_current_client(): # Returns the current client and if they are an administrator def get_current_client():
client = flask_session['client_id'] client = flask_session['client_id']
is_admin = session.query(Client).filter_by(client_id=client).one_or_none().administrator client_obj = session.query(Client).filter_by(client_id=client).one_or_none()
return client, is_admin if client_obj is None:
return None, None
return client_obj.client_id, client_obj.administrator
def verify_otp(client_id:str, otp:int): # Verifies a one time password for a client def verify_otp(client_id:str, otp:int): # Verifies a one time password for a client
if client_id in otps and otps[client_id][0] == otp: if client_id in otps and otps[client_id][0] == otp:
@@ -186,6 +190,17 @@ def change_password(client_id:str, password:str, new_password:str, otp:int): # C
return format_response(False, "Invalid password."), 400 return format_response(False, "Invalid password."), 400
return format_response(False, "Client not found."), 404 return format_response(False, "Client not found."), 404
@login_required
def get_accounts(client_id: str):
current_client_id, is_admin = get_current_client()
if current_client_id is None:
# return an appropriate response or raise an exception
raise Exception("No current client found")
if not is_admin and client_id != current_client_id:
return format_response(False, "You can only view your own client information."), 403
accounts = session.query(Account).filter(Account.client_id == client_id)
return format_response(True, "", [account.to_dict() for account in accounts]), 200
############### ###############
### Account ### ### Account ###
############### ###############

Binary file not shown.