@@ -1722,6 +1860,10 @@
`;
} else {
validationDiv.className = 'json-validation success';
+ if (statusElement) {
+ statusElement.textContent = 'VALID';
+ statusElement.className = 'json-status valid';
+ }
validationDiv.innerHTML = `
@@ -1735,6 +1877,10 @@
} catch (error) {
// JSON parsing failed
validationDiv.className = 'json-validation error';
+ if (statusElement) {
+ statusElement.textContent = 'INVALID';
+ statusElement.className = 'json-status error';
+ }
let errorMessage = '❌ Invalid JSON syntax';
let errorDetails = error.message;
@@ -1775,6 +1921,90 @@
}
}
+ function saveRawJson(configType) {
+ const textareaId = configType === 'main' ? 'main-config-json' : 'secrets-config-json';
+ const validationId = configType === 'main' ? 'main-config-validation' : 'secrets-config-validation';
+ const textarea = document.getElementById(textareaId);
+ const validationDiv = document.getElementById(validationId);
+ const jsonText = textarea.value;
+
+ // First validate the JSON
+ try {
+ JSON.parse(jsonText);
+ } catch (error) {
+ showMessage(`Invalid JSON format: ${error.message}`, 'error');
+ validateJson(textareaId, validationId);
+ return;
+ }
+
+ // Show confirmation dialog
+ const configName = configType === 'main' ? 'Main Configuration' : 'Secrets Configuration';
+ if (!confirm(`Are you sure you want to save changes to the ${configName}? This will overwrite the current configuration file.`)) {
+ return;
+ }
+
+ // Show loading state
+ const saveButton = event.target;
+ const originalText = saveButton.textContent;
+ saveButton.textContent = 'Saving...';
+ saveButton.disabled = true;
+
+ // Send to server
+ fetch("{{ url_for('save_raw_json_route') }}", {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ config_type: configType,
+ config_data: jsonText
+ })
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.status === 'success') {
+ showMessage(data.message, 'success');
+ // Update validation to show success
+ validationDiv.className = 'json-validation success';
+ validationDiv.innerHTML = `
+
✅ Configuration saved successfully!
+
+ ✓ JSON is valid
+ ✓ File saved successfully
+ ✓ Configuration updated
+
+ `;
+ validationDiv.style.display = 'block';
+ } else {
+ showMessage(data.message, 'error');
+ // Show error in validation area
+ validationDiv.className = 'json-validation error';
+ validationDiv.innerHTML = `
+
❌ Save failed
+
+ Error: ${data.message}
+
+ `;
+ validationDiv.style.display = 'block';
+ }
+ })
+ .catch(error => {
+ showMessage(`Error saving configuration: ${error}`, 'error');
+ // Show error in validation area
+ validationDiv.className = 'json-validation error';
+ validationDiv.innerHTML = `
+
❌ Save failed
+
+ Error: ${error}
+
+ `;
+ validationDiv.style.display = 'block';
+ })
+ .finally(() => {
+ // Restore button state
+ saveButton.textContent = originalText;
+ saveButton.disabled = false;
+ });
+ }
+
function saveSportsConfig() {
// Collect all sports configuration and save
const config = {
@@ -2541,6 +2771,51 @@
// Set default active tab
document.addEventListener("DOMContentLoaded", function() {
document.querySelector('.tab-link').click();
+
+ // Add real-time JSON validation for the raw JSON textareas
+ const mainConfigTextarea = document.getElementById('main-config-json');
+ const secretsConfigTextarea = document.getElementById('secrets-config-json');
+
+ function addJsonValidationListener(textarea, validationId) {
+ let validationTimeout;
+ const statusId = validationId.replace('-validation', '-status');
+ const statusElement = document.getElementById(statusId);
+
+ textarea.addEventListener('input', function() {
+ // Clear previous timeout
+ clearTimeout(validationTimeout);
+
+ // Set a new timeout to validate after user stops typing
+ validationTimeout = setTimeout(() => {
+ const jsonText = textarea.value;
+
+ try {
+ JSON.parse(jsonText);
+ textarea.classList.remove('error');
+ textarea.classList.add('valid');
+ if (statusElement) {
+ statusElement.textContent = 'VALID';
+ statusElement.className = 'json-status valid';
+ }
+ } catch (error) {
+ textarea.classList.remove('valid');
+ textarea.classList.add('error');
+ if (statusElement) {
+ statusElement.textContent = 'INVALID';
+ statusElement.className = 'json-status error';
+ }
+ }
+ }, 500); // Wait 500ms after user stops typing
+ });
+ }
+
+ if (mainConfigTextarea) {
+ addJsonValidationListener(mainConfigTextarea, 'main-config-validation');
+ }
+
+ if (secretsConfigTextarea) {
+ addJsonValidationListener(secretsConfigTextarea, 'secrets-config-validation');
+ }
});
diff --git a/web_interface.py b/web_interface.py
index 97550c29..760282a8 100644
--- a/web_interface.py
+++ b/web_interface.py
@@ -286,5 +286,47 @@ def run_action_route():
'message': f'Error running action: {e}'
}), 400
+@app.route('/save_raw_json', methods=['POST'])
+def save_raw_json_route():
+ try:
+ data = request.get_json()
+ config_type = data.get('config_type')
+ config_data = data.get('config_data')
+
+ if not config_type or not config_data:
+ return jsonify({
+ 'status': 'error',
+ 'message': 'Missing config_type or config_data'
+ }), 400
+
+ if config_type not in ['main', 'secrets']:
+ return jsonify({
+ 'status': 'error',
+ 'message': 'Invalid config_type. Must be "main" or "secrets"'
+ }), 400
+
+ # Validate JSON format
+ try:
+ parsed_data = json.loads(config_data)
+ except json.JSONDecodeError as e:
+ return jsonify({
+ 'status': 'error',
+ 'message': f'Invalid JSON format: {str(e)}'
+ }), 400
+
+ # Save the raw JSON
+ config_manager.save_raw_file_content(config_type, parsed_data)
+
+ return jsonify({
+ 'status': 'success',
+ 'message': f'{config_type.capitalize()} configuration saved successfully!'
+ })
+
+ except Exception as e:
+ return jsonify({
+ 'status': 'error',
+ 'message': f'Error saving raw JSON: {str(e)}'
+ }), 400
+
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
\ No newline at end of file