diff --git a/jumpserver b/jumpserver index f95536d..197fbb7 100755 Binary files a/jumpserver and b/jumpserver differ diff --git a/static/script.js b/static/script.js index e69de29..9b2ea45 100644 --- a/static/script.js +++ b/static/script.js @@ -0,0 +1,114 @@ +document.addEventListener('DOMContentLoaded', function() { + // Form validation enhancement + const addForm = document.querySelector('form[action="/add"]'); + if (addForm) { + initializeAddForm(addForm); + } + + // Table sorting + const tables = document.querySelectorAll('table'); + tables.forEach(initializeTableSort); + + // Search form enhancement + const searchForm = document.querySelector('form[action="/search"]'); + if (searchForm) { + initializeSearchForm(searchForm); + } +}); + +function initializeAddForm(form) { + // Tags input enhancement + const tagsInput = form.querySelector('#tags'); + if (tagsInput) { + // Convert comma-separated input into tag spans + tagsInput.addEventListener('blur', function() { + const tags = this.value.split(',').map(t => t.trim()).filter(t => t); + this.value = tags.join(', '); + }); + + // Basic destination validation + const destInput = form.querySelector('#destination'); + destInput.addEventListener('blur', function() { + const val = this.value.trim(); + if (val.length < 516) { + this.setCustomValidity('Destination appears too short for a valid I2P address'); + } else { + this.setCustomValidity(''); + } + }); + } +} + +function initializeSearchForm(form) { + // Add "clear search" functionality + const queryInput = form.querySelector('#q'); + if (queryInput && queryInput.value) { + const clearButton = document.createElement('button'); + clearButton.type = 'button'; + clearButton.textContent = 'Clear'; + clearButton.className = 'clear-search'; + clearButton.onclick = () => { + queryInput.value = ''; + form.submit(); + }; + queryInput.parentNode.appendChild(clearButton); + } +} + +function initializeTableSort(table) { + const headers = table.querySelectorAll('th'); + headers.forEach((header, index) => { + if (!header.classList.contains('no-sort')) { + header.style.cursor = 'pointer'; + header.addEventListener('click', () => sortTable(table, index)); + header.title = 'Click to sort'; + } + }); +} + +function sortTable(table, column) { + const tbody = table.querySelector('tbody'); + const rows = Array.from(tbody.querySelectorAll('tr')); + const isAsc = table.querySelector('th').classList.contains('sort-asc'); + + // Sort the rows + rows.sort((a, b) => { + let aVal = a.cells[column].textContent.trim(); + let bVal = b.cells[column].textContent.trim(); + + // Handle date sorting + if (isValidDate(aVal) && isValidDate(bVal)) { + return compareStandardDates(aVal, bVal); + } + + // Default string comparison + return isAsc ? bVal.localeCompare(aVal) : aVal.localeCompare(bVal); + }); + + // Update sort indicators + table.querySelectorAll('th').forEach(th => { + th.classList.remove('sort-asc', 'sort-desc'); + }); + table.querySelector(`th:nth-child(${column + 1})`).classList.add( + isAsc ? 'sort-desc' : 'sort-asc' + ); + + // Rebuild the table body + rows.forEach(row => tbody.appendChild(row)); +} + +function isValidDate(dateStr) { + const date = new Date(dateStr); + return date instanceof Date && !isNaN(date); +} + +function compareStandardDates(a, b) { + return new Date(a) - new Date(b); +} + +// Optional: Add keyboard navigation for accessibility +document.addEventListener('keydown', function(e) { + if (e.key === 'Enter' && e.target.tagName === 'TH') { + e.target.click(); + } +}); \ No newline at end of file diff --git a/static/style.css b/static/style.css index e69de29..4a67c06 100644 --- a/static/style.css +++ b/static/style.css @@ -0,0 +1,202 @@ +/* Base styles */ +:root { + --primary-color: #00ff41; + --secondary-color: #0a0a0a; + --accent-color: #ff00ff; + --text-color: #e0e0e0; + --link-color: #00ff41; + --border-color: #2a2a2a; + --input-bg: #1a1a1a; +} + +body { + background-color: var(--secondary-color); + color: var(--text-color); + font-family: 'Courier New', monospace; + line-height: 1.6; + margin: 0; + padding: 20px; + min-height: 100vh; +} + +header { + border-bottom: 2px solid var(--primary-color); + margin-bottom: 2rem; + padding-bottom: 1rem; +} + +h1, h2 { + color: var(--primary-color); + text-transform: uppercase; + letter-spacing: 2px; +} + +/* Navigation */ +#index-links { + margin: 1rem 0; + padding: 1rem 0; + border-bottom: 1px solid var(--border-color); +} + +#index-links a { + margin-right: 1.5rem; + padding: 0.5rem 1rem; + border: 1px solid var(--primary-color); + text-decoration: none; + color: var(--primary-color); + transition: all 0.3s ease; +} + +#index-links a:hover { + background-color: var(--primary-color); + color: var(--secondary-color); + box-shadow: 0 0 10px var(--primary-color); +} + +/* Tables */ +table { + width: 100%; + border-collapse: collapse; + margin: 1rem 0; + background: rgba(10, 10, 10, 0.8); +} + +th, td { + padding: 0.8rem; + text-align: left; + border: 1px solid var(--border-color); +} + +th { + background-color: var(--border-color); + color: var(--primary-color); + text-transform: uppercase; +} + +tr:hover { + background-color: rgba(0, 255, 65, 0.1); +} + +/* Forms */ +form { + max-width: 800px; + margin: 0 auto; +} + +.form-group { + margin-bottom: 1.5rem; +} + +label { + display: block; + margin-bottom: 0.5rem; + color: var(--primary-color); +} + +input, select, textarea { + width: 100%; + padding: 0.8rem; + background-color: var(--input-bg); + border: 1px solid var(--border-color); + color: var(--text-color); + font-family: 'Courier New', monospace; +} + +input:focus, select:focus, textarea:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: 0 0 5px var(--primary-color); +} + +button { + background-color: transparent; + color: var(--primary-color); + border: 2px solid var(--primary-color); + padding: 0.8rem 2rem; + cursor: pointer; + text-transform: uppercase; + letter-spacing: 2px; + transition: all 0.3s ease; +} + +button:hover { + background-color: var(--primary-color); + color: var(--secondary-color); + box-shadow: 0 0 15px var(--primary-color); +} + +/* Tags */ +.tag { + display: inline-block; + padding: 0.2rem 0.5rem; + margin: 0.2rem; + background-color: var(--border-color); + border: 1px solid var(--primary-color); + border-radius: 3px; + font-size: 0.9rem; +} + +/* Search Results */ +#search-results { + margin-top: 2rem; +} + +/* Rules Section */ +#rules { + margin: 2rem 0; + padding: 1rem; + border: 1px solid var(--border-color); + background-color: rgba(10, 10, 10, 0.8); +} + +#rules ul { + list-style-type: none; + padding-left: 0; +} + +#rules li { + margin-bottom: 0.5rem; + padding-left: 1.5rem; + position: relative; +} + +#rules li:before { + content: ">"; + position: absolute; + left: 0; + color: var(--primary-color); +} + +/* Responsive Design */ +@media (max-width: 768px) { + body { + padding: 10px; + } + + table { + display: block; + overflow-x: auto; + } + + #index-links a { + display: block; + margin-bottom: 0.5rem; + text-align: center; + } +} + +th.sort-asc::after { + content: " ▲"; + color: var(--primary-color); +} + +th.sort-desc::after { + content: " ▼"; + color: var(--primary-color); +} + +.clear-search { + margin-left: 0.5rem; + padding: 0.3rem 0.6rem; + font-size: 0.8rem; +} \ No newline at end of file