🎨 ⚡ Enhance UI and Refactor Backend for Email Configurations
- ✏️ Update HTML title to "Invoice Email Sender" - 💄 Change font from 'Roboto' to 'Lora' and revise color scheme - 🌈 Refine button and input styles with new colors, borders, and transitions - ✨ Implement tab functionality with CSS rules, HTML structure, and JS listeners - 📑 Revamp status bar style and content - 🔨 Refactor main.py: - 🗃️ Adapt read_yaml and write_yaml for enhanced 'email' and 'email_template' structure - 🔧 Modify handle_data for structure changes
This commit is contained in:
parent
5d35eec1d8
commit
dc1b2ecb08
257
index.html
257
index.html
@ -2,117 +2,270 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>YAML Editor</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
|
||||
<title>Invoice Email Sender</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Lora:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
/* color scheme */
|
||||
--primary-color: #007bff;
|
||||
--primary-dark: #0056b3;
|
||||
--secondary-color: #333;
|
||||
--light-color: #f4f7fa;
|
||||
--lighter-color: #f8f9fa;
|
||||
--error-color: #ee5555;
|
||||
|
||||
/* Font and border styling */
|
||||
--font-family: 'Lora', serif;
|
||||
--border-radius: 10px;
|
||||
--border-color: #ccc;
|
||||
--border-focus-color: #007bff;
|
||||
|
||||
/* Shadow and transitions */
|
||||
--box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
|
||||
--transition-speed: 0.3s;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
color: #212529;
|
||||
font-family: var(--font-family);
|
||||
background-color: var(--light-color);
|
||||
color: var(--secondary-color);
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
max-width: 850px;
|
||||
margin: auto;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--box-shadow);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav-tab {
|
||||
display: inline-block;
|
||||
margin-right: 15px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
color: var(--primary-color);
|
||||
transition: color var(--transition-speed);
|
||||
}
|
||||
|
||||
.nav-tab:hover, .nav-tab.active {
|
||||
color: var(--primary-dark);
|
||||
border-bottom: 3px solid var(--primary-color);
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none; /* Hide all tab content by default */
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
padding: 20px;
|
||||
text-align: left;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.button {
|
||||
background-color: #007bff;
|
||||
background: var(--primary-color);
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
margin: 10px 5px;
|
||||
border-radius: 5px;
|
||||
border-radius: var(--border-radius);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
transition: background-color var(--transition-speed);
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: #0056b3;
|
||||
background-color: var(--primary-dark);
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
input[type="text"], input[type="password"], textarea {
|
||||
width: calc(100% - 20px);
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
margin-top: 10px;
|
||||
display: block;
|
||||
margin-bottom: 20px;
|
||||
line-height: 1.4;
|
||||
background: var(--lighter-color);
|
||||
}
|
||||
|
||||
input[type="text"]:focus, input[type="password"]:focus, textarea:focus {
|
||||
border-color: var(--border-focus-color);
|
||||
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
color: var(--secondary-color);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
padding: 10px;
|
||||
margin-top: 20px;
|
||||
text-align: left;
|
||||
font-size: .9rem;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript"
|
||||
src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.2.7.nomodule.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container" id="appContainer">
|
||||
<label for="yamlContent"></label><textarea id="yamlContent" rows="10"></textarea><br>
|
||||
<button class="button" onclick="loadData()">Reload Data</button>
|
||||
<button class="button" onclick="saveData()">Save Data</button>
|
||||
<div id="statusMessage"></div>
|
||||
<div>
|
||||
<div class="nav-tab active" onclick="changeTab(event, 'config')">Configuration</div>
|
||||
<div class="nav-tab" onclick="changeTab(event, 'csv')">CSV Management</div>
|
||||
<div class="nav-tab" onclick="changeTab(event, 'email')">Email Operations</div>
|
||||
</div>
|
||||
|
||||
<div id="config" class="tab-content active">
|
||||
<label for="user">User:</label>
|
||||
<input type="text" id="user" name="user">
|
||||
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password">
|
||||
|
||||
<label for="ccEmail">CC Email:</label>
|
||||
<input type="text" id="ccEmail" name="ccEmail">
|
||||
|
||||
<label for="bccEmail">BCC Email:</label>
|
||||
<input type="text" id="bccEmail" name="bccEmail">
|
||||
|
||||
<label for="emailTemplate">Email Template:</label>
|
||||
<textarea id="emailTemplate" rows="6"></textarea>
|
||||
|
||||
<button class="button" id="loadConfigBtn">Load Config</button>
|
||||
<button class="button" id="saveConfigBtn">Save Config</button>
|
||||
</div>
|
||||
|
||||
<div id="csv" class="tab-content">
|
||||
<!-- CSV Management Content -->
|
||||
<label for="csvPath">CSV Path:</label>
|
||||
<input type="text" id="csvPath" name="csvPath">
|
||||
<button class="button">Load CSV</button>
|
||||
</div>
|
||||
|
||||
<div id="email" class="tab-content">
|
||||
<!-- Email Operations Content -->
|
||||
<button class="button">Send Emails</button>
|
||||
<textarea rows="10">Log output here...</textarea>
|
||||
</div>
|
||||
|
||||
<div class="status-bar" id="statusMessage">Status: Ready</div>
|
||||
</div>
|
||||
<script>
|
||||
function displayStatus(message) {
|
||||
document.getElementById('statusMessage').textContent = message;
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
// Event listeners for buttons
|
||||
document.getElementById('loadConfigBtn').addEventListener('click', loadConfig);
|
||||
document.getElementById('saveConfigBtn').addEventListener('click', saveConfig);
|
||||
});
|
||||
|
||||
function changeTab(event, tabId) {
|
||||
const tabContents = document.getElementsByClassName("tab-content");
|
||||
const tabs = document.getElementsByClassName("nav-tab");
|
||||
|
||||
// Hide all tab contents
|
||||
for (let i = 0; i < tabContents.length; i++) {
|
||||
tabContents[i].style.display = "none"; // Hide
|
||||
tabContents[i].classList.remove("active");
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
try {
|
||||
displayStatus('Loading data...');
|
||||
const response = await fetch('/data');
|
||||
// Remove 'active' class from all tabs
|
||||
for (let i = 0; i < tabs.length; i++) {
|
||||
tabs[i].classList.remove("active");
|
||||
}
|
||||
|
||||
// Show the clicked tab content and add 'active' class
|
||||
document.getElementById(tabId).style.display = "block";
|
||||
document.getElementById(tabId).classList.add("active");
|
||||
event.currentTarget.classList.add("active");
|
||||
}
|
||||
|
||||
function loadConfig() {
|
||||
fetch('/data')
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
displayStatus(`HTTP error! Status: ${response.status}`);
|
||||
return;
|
||||
throw new Error('Network response was not ok: ' + response.statusText);
|
||||
}
|
||||
const data = await response.json();
|
||||
if (typeof data !== 'object' || data === null || !('text' in data)) {
|
||||
displayStatus('Invalid format in response data');
|
||||
return;
|
||||
}
|
||||
document.getElementById('yamlContent').value = data.text || '';
|
||||
} catch (error) {
|
||||
console.error('Error loading data:', error);
|
||||
displayStatus(`Failed to load data: ${error.message}`);
|
||||
} finally {
|
||||
displayStatus('');
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (!data.email || typeof data.email_template !== 'string') {
|
||||
throw new Error('Incorrect data structure from server');
|
||||
}
|
||||
document.getElementById('user').value = data.email.user || '';
|
||||
document.getElementById('password').value = data.email.password || '';
|
||||
document.getElementById('ccEmail').value = data.email.cc_email || '';
|
||||
document.getElementById('bccEmail').value = data.email.bcc_email || '';
|
||||
document.getElementById('emailTemplate').textContent = data.email_template || '';
|
||||
updateStatusBar("Configuration Loaded Successfully");
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading configuration:', error);
|
||||
updateStatusBar(`Error loading configuration: ${error}`, true);
|
||||
});
|
||||
}
|
||||
|
||||
function saveData() {
|
||||
const content = document.getElementById('yamlContent').value;
|
||||
displayStatus('Saving data...');
|
||||
function saveConfig() {
|
||||
const emailData = {
|
||||
user: document.getElementById('user').value,
|
||||
password: document.getElementById('password').value,
|
||||
cc_email: document.getElementById('ccEmail').value,
|
||||
bcc_email: document.getElementById('bccEmail').value
|
||||
};
|
||||
|
||||
const emailTemplateData = document.getElementById('emailTemplate').value;
|
||||
|
||||
fetch('/data', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({text: content}),
|
||||
body: JSON.stringify({
|
||||
email: emailData,
|
||||
email_template: emailTemplateData
|
||||
}),
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
throw new Error('Network response was not ok: ' + response.statusText);
|
||||
}
|
||||
return response.text();
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
alert(data);
|
||||
displayStatus('');
|
||||
if (data.success) {
|
||||
alert("Configuration Saved Successfully");
|
||||
updateStatusBar("Configuration Saved Successfully");
|
||||
} else {
|
||||
throw new Error("Failed to save configuration: " + data.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
displayStatus('Failed to save data.');
|
||||
console.error('Error saving configuration:', error);
|
||||
updateStatusBar(`Error saving configuration: ${error}`, true);
|
||||
});
|
||||
}
|
||||
|
||||
loadData();
|
||||
function updateStatusBar(message, isError = false) {
|
||||
const statusBar = document.getElementById('statusMessage');
|
||||
statusBar.textContent = message;
|
||||
if (isError) {
|
||||
statusBar.style.color = 'var(--error-color)';
|
||||
} else {
|
||||
statusBar.style.color = 'initial'; // or any other color for normal messages
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
34
main.py
34
main.py
@ -15,7 +15,7 @@ import logging
|
||||
app = Flask(__name__, static_folder='.', static_url_path='')
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
DATA_FILE = 'data.yaml'
|
||||
DATA_FILE = 'config.yaml' # Ensure this is the correct path to your YAML file
|
||||
|
||||
|
||||
def read_yaml():
|
||||
@ -23,17 +23,20 @@ def read_yaml():
|
||||
Reads and returns content from the YAML file.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary with the key 'text' containing file content.
|
||||
dict: A dictionary with the key 'email' and 'email_template' containing their respective contents.
|
||||
None: If an error occurs, along with an error message.
|
||||
"""
|
||||
file_path = Path(DATA_FILE)
|
||||
try:
|
||||
if not file_path.is_file():
|
||||
logging.info("YAML file not found. Returning empty data.")
|
||||
return {'text': ''}, None
|
||||
return {'email': {}, 'email_template': ''}, None
|
||||
with file_path.open() as file:
|
||||
data = yaml.safe_load(file) or {}
|
||||
return {'text': data.get('text', '')}, None
|
||||
return {
|
||||
'email': data.get('email', {}),
|
||||
'email_template': data.get('email_template', '')
|
||||
}, None
|
||||
except yaml.YAMLError as e:
|
||||
logging.error(f"YAML Error: {e}", exc_info=True)
|
||||
return None, f"YAML parsing error: {e}"
|
||||
@ -45,12 +48,13 @@ def read_yaml():
|
||||
return None, f"Unexpected error: {e}"
|
||||
|
||||
|
||||
def write_yaml(content):
|
||||
def write_yaml(email_data, email_template_data):
|
||||
"""
|
||||
Writes content to the YAML file.
|
||||
|
||||
Args:
|
||||
content (str): The text content to be saved in the file.
|
||||
email_data (dict): The data for the 'email' section.
|
||||
email_template_data (str): The data for the 'email_template' section.
|
||||
|
||||
Returns:
|
||||
bool: True if the operation was successful, False otherwise.
|
||||
@ -59,7 +63,10 @@ def write_yaml(content):
|
||||
file_path = Path(DATA_FILE)
|
||||
try:
|
||||
with file_path.open('w') as file:
|
||||
yaml.dump({'text': content}, file)
|
||||
yaml.dump({
|
||||
'email': email_data,
|
||||
'email_template': email_template_data
|
||||
}, file)
|
||||
return True, None
|
||||
except Exception as e:
|
||||
logging.error(f"Error writing YAML file: {e}", exc_info=True)
|
||||
@ -75,19 +82,20 @@ def handle_data():
|
||||
|
||||
Returns:
|
||||
JSON response for GET requests.
|
||||
Text message for POST requests.
|
||||
JSON response for POST requests indicating success or failure.
|
||||
"""
|
||||
if request.method == 'GET':
|
||||
data, error = read_yaml()
|
||||
if error:
|
||||
return error, 500
|
||||
return jsonify({'success': False, 'message': error}), 500
|
||||
return jsonify(data), 200
|
||||
elif request.method == 'POST':
|
||||
content = request.json.get('text', '')
|
||||
success, error = write_yaml(content)
|
||||
email_data = request.json.get('email', {})
|
||||
email_template_data = request.json.get('email_template', '')
|
||||
success, error = write_yaml(email_data, email_template_data)
|
||||
if error:
|
||||
return error, 500
|
||||
return "File saved successfully", 200
|
||||
return jsonify({'success': False, 'message': error}), 500
|
||||
return jsonify({'success': True, 'message': "File saved successfully"}), 200
|
||||
|
||||
|
||||
@app.route('/')
|
||||
|
||||
Loading…
Reference in New Issue
Block a user