Compare commits

..

No commits in common. "72d044b6e8cda3d1c39a1572b2a3b2ceae0c068e" and "5d35eec1d828d7224c27436a90bc59aa2aaa0c4a" have entirely different histories.

3 changed files with 72 additions and 238 deletions

View File

@ -1,7 +1,6 @@
# 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
@ -11,8 +10,7 @@ application is built using Flask and serves an HTML page that interacts with the
## 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
@ -62,14 +60,11 @@ 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,270 +2,117 @@
<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>Invoice Email Sender</title>
<link href="https://fonts.googleapis.com/css2?family=Lora:wght@400;700&display=swap" rel="stylesheet">
<title>YAML Editor</title>
<link href="https://fonts.googleapis.com/css2?family=Roboto: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: var(--font-family);
background-color: var(--light-color);
color: var(--secondary-color);
font-family: 'Roboto', sans-serif;
background-color: #f8f9fa;
color: #212529;
margin: 0;
padding: 20px;
line-height: 1.6;
}
.container {
max-width: 850px;
max-width: 800px;
margin: auto;
padding: 20px;
background: #fff;
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
border-radius: 10px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
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: var(--primary-color);
background-color: #007bff;
color: #fff;
border: none;
padding: 10px 20px;
margin: 10px 5px;
border-radius: var(--border-radius);
border-radius: 5px;
cursor: pointer;
transition: background-color var(--transition-speed);
transition: background-color 0.3s;
font-weight: 500;
font-size: 1rem;
}
.button:hover {
background-color: var(--primary-dark);
background-color: #0056b3;
}
input[type="text"], input[type="password"], textarea {
width: calc(100% - 20px);
textarea {
width: 100%;
padding: 10px;
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
border: 1px solid #ccc;
border-radius: 5px;
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">
<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>
<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>
<script>
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");
function displayStatus(message) {
document.getElementById('statusMessage').textContent = message;
}
// 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 => {
async function loadData() {
try {
displayStatus('Loading data...');
const response = await fetch('/data');
if (!response.ok) {
throw new Error('Network response was not ok: ' + response.statusText);
displayStatus(`HTTP error! Status: ${response.status}`);
return;
}
return response.json();
})
.then(data => {
if (!data.email || typeof data.email_template !== 'string') {
throw new Error('Incorrect data structure from server');
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('');
}
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 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;
function saveData() {
const content = document.getElementById('yamlContent').value;
displayStatus('Saving data...');
fetch('/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: emailData,
email_template: emailTemplateData
}),
body: JSON.stringify({text: content}),
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok: ' + response.statusText);
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
return response.text();
})
.then(data => {
if (data.success) {
alert("Configuration Saved Successfully");
updateStatusBar("Configuration Saved Successfully");
} else {
throw new Error("Failed to save configuration: " + data.message);
}
alert(data);
displayStatus('');
})
.catch(error => {
console.error('Error saving configuration:', error);
updateStatusBar(`Error saving configuration: ${error}`, true);
console.error('Error:', error);
displayStatus('Failed to save data.');
});
}
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
}
}
loadData();
</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 = 'config.yaml' # Ensure this is the correct path to your YAML file
DATA_FILE = 'data.yaml'
def read_yaml():
@ -23,20 +23,17 @@ def read_yaml():
Reads and returns content from the YAML file.
Returns:
dict: A dictionary with the key 'email' and 'email_template' containing their respective contents.
dict: A dictionary with the key 'text' containing file content.
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 {'email': {}, 'email_template': ''}, None
return {'text': ''}, None
with file_path.open() as file:
data = yaml.safe_load(file) or {}
return {
'email': data.get('email', {}),
'email_template': data.get('email_template', '')
}, None
return {'text': data.get('text', '')}, None
except yaml.YAMLError as e:
logging.error(f"YAML Error: {e}", exc_info=True)
return None, f"YAML parsing error: {e}"
@ -48,13 +45,12 @@ def read_yaml():
return None, f"Unexpected error: {e}"
def write_yaml(email_data, email_template_data):
def write_yaml(content):
"""
Writes content to the YAML file.
Args:
email_data (dict): The data for the 'email' section.
email_template_data (str): The data for the 'email_template' section.
content (str): The text content to be saved in the file.
Returns:
bool: True if the operation was successful, False otherwise.
@ -63,10 +59,7 @@ def write_yaml(email_data, email_template_data):
file_path = Path(DATA_FILE)
try:
with file_path.open('w') as file:
yaml.dump({
'email': email_data,
'email_template': email_template_data
}, file)
yaml.dump({'text': content}, file)
return True, None
except Exception as e:
logging.error(f"Error writing YAML file: {e}", exc_info=True)
@ -82,20 +75,19 @@ def handle_data():
Returns:
JSON response for GET requests.
JSON response for POST requests indicating success or failure.
Text message for POST requests.
"""
if request.method == 'GET':
data, error = read_yaml()
if error:
return jsonify({'success': False, 'message': error}), 500
return error, 500
return jsonify(data), 200
elif request.method == 'POST':
email_data = request.json.get('email', {})
email_template_data = request.json.get('email_template', '')
success, error = write_yaml(email_data, email_template_data)
content = request.json.get('text', '')
success, error = write_yaml(content)
if error:
return jsonify({'success': False, 'message': error}), 500
return jsonify({'success': True, 'message': "File saved successfully"}), 200
return error, 500
return "File saved successfully", 200
@app.route('/')