Compare commits
No commits in common. "72d044b6e8cda3d1c39a1572b2a3b2ceae0c068e" and "5d35eec1d828d7224c27436a90bc59aa2aaa0c4a" have entirely different histories.
72d044b6e8
...
5d35eec1d8
13
README.md
13
README.md
@ -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
|
||||
|
||||
|
||||
263
index.html
263
index.html
@ -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");
|
||||
}
|
||||
|
||||
// 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 displayStatus(message) {
|
||||
document.getElementById('statusMessage').textContent = message;
|
||||
}
|
||||
|
||||
function loadConfig() {
|
||||
fetch('/data')
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok: ' + response.statusText);
|
||||
}
|
||||
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);
|
||||
});
|
||||
async function loadData() {
|
||||
try {
|
||||
displayStatus('Loading data...');
|
||||
const response = await fetch('/data');
|
||||
if (!response.ok) {
|
||||
displayStatus(`HTTP error! Status: ${response.status}`);
|
||||
return;
|
||||
}
|
||||
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('');
|
||||
}
|
||||
}
|
||||
|
||||
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
34
main.py
@ -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('/')
|
||||
|
||||
Loading…
Reference in New Issue
Block a user