Compare commits

...

2 Commits

Author SHA1 Message Date
72d044b6e8
♻️ Refactor README.md for Flask YAML Editor
📚 Improved readability and coding standards adherence.
2023-12-22 23:39:07 +01:00
dc1b2ecb08
🎨 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
2023-12-22 23:37:44 +01:00
3 changed files with 238 additions and 72 deletions

View File

@ -1,6 +1,7 @@
# Flask YAML Editor
Flask YAML Editor is a web application that provides an interface for reading and updating YAML file content. This application is built using Flask and serves an HTML page that interacts with the Flask backend to manage YAML data.
Flask YAML Editor is a web application that provides an interface for reading and updating YAML file content. This
application is built using Flask and serves an HTML page that interacts with the Flask backend to manage YAML data.
## Features
@ -10,7 +11,8 @@ Flask YAML Editor is a web application that provides an interface for reading an
## Getting Started
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.
These instructions will get you a copy of the project up and running on your local machine for development and testing
purposes.
### Prerequisites
@ -60,11 +62,14 @@ pip install Flask PyYAML
## License
This project is licensed under a custom licence. This licence permits private and non-commercial use, testing, and use by the author's employer under certain conditions. It prohibits modification, distribution, and commercial use by others without a separate licence.
This project is licensed under a custom licence. This licence permits private and non-commercial use, testing, and use
by the author's employer under certain conditions. It prohibits modification, distribution, and commercial use by others
without a separate licence.
See the [LICENSE.md](LICENSE.md) file for more details.
For questions regarding the use, modification, or redistribution of this software, please contact Isaak Buslovich at isaak.buslovich@potsdam.de.
For questions regarding the use, modification, or redistribution of this software, please contact Isaak Buslovich at
isaak.buslovich@potsdam.de.
## Acknowledgments

View File

@ -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
View File

@ -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('/')