diff --git a/static/css/edit.css b/static/css/edit.css new file mode 100644 index 0000000..6e49aaa --- /dev/null +++ b/static/css/edit.css @@ -0,0 +1,29 @@ +.CodeMirror { + height: 500px !important; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace; + font-size: 13px; +} + +.CodeMirror-gutters { + background-color: #263238; + border-right: 1px solid #37474f; +} + +.CodeMirror-linenumber { + color: #546e7a; +} + +.CodeMirror-cursor { + border-left: 1px solid #fff; +} + +#edit_form button { + white-space: nowrap; +} + +@media (max-width: 768px) { + .CodeMirror { + height: 300px !important; + font-size: 12px; + } +} \ No newline at end of file diff --git a/static/js/editor.js b/static/js/editor.js new file mode 100644 index 0000000..bce5b37 --- /dev/null +++ b/static/js/editor.js @@ -0,0 +1,133 @@ +/** + * HAProxy Configuration Editor + * Auto-grow textarea + CodeMirror integration + */ + +document.addEventListener('DOMContentLoaded', function() { + // Auto-grow textarea (fallback if CodeMirror fails) + initAutoGrowTextarea(); + + // Try to initialize CodeMirror + initCodeMirror(); +}); + +/** + * Initialize auto-grow textarea + */ +function initAutoGrowTextarea() { + 'use strict'; + const ta = document.getElementById('haproxy_config'); + if (!ta) return; + + const autoGrow = () => { + ta.style.height = 'auto'; + ta.style.height = (ta.scrollHeight + 6) + 'px'; + }; + + ta.addEventListener('input', autoGrow); + ta.addEventListener('change', autoGrow); + + // Initial auto-size + autoGrow(); + + // Resize on window resize + window.addEventListener('resize', autoGrow); + + console.log('[Editor] Auto-grow textarea initialized'); +} + +/** + * Initialize CodeMirror editor + */ +function initCodeMirror() { + 'use strict'; + + // Check if CodeMirror is available + if (typeof CodeMirror === 'undefined') { + console.warn('[Editor] CodeMirror not loaded, using fallback textarea'); + document.getElementById('haproxy_config').style.display = 'block'; + return; + } + + try { + const editorElement = document.getElementById('haproxy_editor'); + if (!editorElement) { + console.warn('[Editor] haproxy_editor element not found'); + return; + } + + const editor = CodeMirror.fromTextArea(editorElement, { + lineNumbers: true, + lineWrapping: true, + indentUnit: 4, + indentWithTabs: false, + theme: 'material-darker', + mode: 'text/x-nginx-conf', + styleActiveLine: true, + styleSelectedText: true, + highlightSelectionMatches: { annotateScrollbar: true }, + foldGutter: true, + gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], + matchBrackets: true, + autoCloseBrackets: true, + extraKeys: { + 'Ctrl-S': function() { + document.querySelector('button[value="save"]').click(); + }, + 'Ctrl-L': function() { + editor.clearHistory(); + }, + 'Ctrl-/': 'toggleComment' + } + }); + + // Hide fallback textarea + document.getElementById('haproxy_config').style.display = 'none'; + + // Update line/col info + editor.on('cursorActivity', function() { + const pos = editor.getCursor(); + document.getElementById('line_col').textContent = + `Line ${pos.line + 1}, Col ${pos.ch + 1}`; + document.getElementById('char_count').textContent = + editor.getValue().length; + }); + + // Auto-save to localStorage + let saveTimeout; + editor.on('change', function() { + clearTimeout(saveTimeout); + saveTimeout = setTimeout(() => { + localStorage.setItem('haproxy_draft', editor.getValue()); + }, 1000); + }); + + // Recover from localStorage + const draft = localStorage.getItem('haproxy_draft'); + const currentContent = editorElement.value.trim(); + + if (draft && draft.trim() !== currentContent && currentContent === '') { + if (confirm('📝 Recover unsaved draft?')) { + editor.setValue(draft); + localStorage.removeItem('haproxy_draft'); + } + } + + // Form submission - sync values + const editForm = document.getElementById('edit_form'); + editForm.addEventListener('submit', function(e) { + editorElement.value = editor.getValue(); + document.getElementById('haproxy_config').value = editor.getValue(); + }); + + // Initial info + document.getElementById('char_count').textContent = editor.getValue().length; + + console.log('[Editor] CodeMirror initialized successfully'); + + } catch (e) { + console.warn('[Editor] CodeMirror initialization failed:', e); + // Fallback textarea is already visible + document.getElementById('haproxy_config').style.display = 'block'; + } +} diff --git a/templates/base.html b/templates/base.html index ed5c4b3..29ae675 100644 --- a/templates/base.html +++ b/templates/base.html @@ -7,6 +7,7 @@ {% block title %}HAProxy Configurator{% endblock %} + {% block head %}{% endblock %} diff --git a/templates/edit.html b/templates/edit.html index ccc9a39..4fe1a13 100644 --- a/templates/edit.html +++ b/templates/edit.html @@ -1,34 +1,91 @@ {% extends "base.html" %} -{% set active_page = "" %} -{% block title %}HAProxy • Edit{% endblock %} -{% block breadcrumb %}{% endblock %} -{% block content %} -
-
-

Edit HAProxy configuration

-
-
- - -
-
- - -
-
- {% if check_output %} - - {% endif %} -
+{% set active_page = "edit" %} + +{% block title %}HAProxy • Configuration Editor{% endblock %} + +{% block breadcrumb %} + +{% endblock %} + +{% block content %} + + + + + + +{% if check_output %} + -{% endblock %} -{% block page_js %} - +{% endif %} + + +
+
+
HAProxy Configuration Editor
+ Real-time editor with syntax highlighting +
+ +
+
+ +
+ + + +
+ + +
+
+ + + + Cancel + +
+ + + Line 1, Col 1 | + 0 characters + +
+
+
+
+ + + + + + + + {% endblock %}