batman (working version kinda)
This commit is contained in:
commit
6dd38036e7
65 changed files with 3950 additions and 0 deletions
183
app/static/css/app.css
Normal file
183
app/static/css/app.css
Normal file
|
@ -0,0 +1,183 @@
|
|||
/* Custom styles for the app */
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
background-color: #f5f7fb;
|
||||
color: #232e3c;
|
||||
}
|
||||
|
||||
.markdown-body {
|
||||
padding: 1rem;
|
||||
background-color: #fff;
|
||||
border: 1px solid rgba(0, 0, 0, 0.125);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.markdown-body h1 {
|
||||
font-size: 1.75rem;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.markdown-body h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.markdown-body h3 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.markdown-body pre {
|
||||
background-color: #f6f8fa;
|
||||
border-radius: 3px;
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.markdown-body table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.markdown-body th,
|
||||
.markdown-body td {
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.markdown-body th {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
/* IP Grid for subnet visualization */
|
||||
.ip-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ip-cell {
|
||||
font-size: 0.75rem;
|
||||
padding: 6px;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
border: 1px solid rgba(0, 0, 0, 0.125);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.ip-cell.used {
|
||||
background-color: rgba(234, 88, 12, 0.1);
|
||||
border-color: rgba(234, 88, 12, 0.5);
|
||||
}
|
||||
|
||||
.ip-cell.available {
|
||||
background-color: rgba(5, 150, 105, 0.1);
|
||||
border-color: rgba(5, 150, 105, 0.5);
|
||||
}
|
||||
|
||||
/* Stats cards on dashboard */
|
||||
.stats-card {
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.stats-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
/* Custom navbar styles */
|
||||
.navbar-brand {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Sidebar styles */
|
||||
.sidebar {
|
||||
width: 260px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 100;
|
||||
background: #fff;
|
||||
box-shadow: 0 0 2rem 0 rgba(136, 152, 170, .15);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sidebar-brand {
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.sidebar-brand-text {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin-left: 0.75rem;
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
padding: 0.75rem 1.5rem;
|
||||
}
|
||||
|
||||
.sidebar-heading {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
color: #8898aa;
|
||||
letter-spacing: 0.04em;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.sidebar-item {
|
||||
display: block;
|
||||
padding: 0.675rem 1.2rem;
|
||||
font-size: 0.875rem;
|
||||
color: #525f7f;
|
||||
border-radius: 0.375rem;
|
||||
margin-bottom: 0.25rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.sidebar-item:hover {
|
||||
color: #5e72e4;
|
||||
background: rgba(94, 114, 228, 0.1);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.sidebar-item.active {
|
||||
color: #5e72e4;
|
||||
background: rgba(94, 114, 228, 0.1);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 260px;
|
||||
}
|
||||
|
||||
/* Responsive sidebar */
|
||||
@media (max-width: 992px) {
|
||||
.sidebar {
|
||||
left: -260px;
|
||||
transition: left 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar.show {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Notification area */
|
||||
#notification-area {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
z-index: 9999;
|
||||
width: 300px;
|
||||
}
|
14
app/static/css/github-markdown.css
Normal file
14
app/static/css/github-markdown.css
Normal file
|
@ -0,0 +1,14 @@
|
|||
/* GitHub Markdown CSS (simplified version) */
|
||||
.markdown-body {
|
||||
box-sizing: border-box;
|
||||
min-width: 200px;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
padding: 45px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.markdown-body {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
142
app/static/js/app.js
Normal file
142
app/static/js/app.js
Normal file
|
@ -0,0 +1,142 @@
|
|||
document.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('App script loaded.');
|
||||
|
||||
// Initialize Tiptap editor if element exists
|
||||
const editorElement = document.getElementById('editor');
|
||||
if (editorElement) {
|
||||
initTiptapEditor(editorElement);
|
||||
}
|
||||
|
||||
// Add Bootstrap tooltips
|
||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||||
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl);
|
||||
});
|
||||
|
||||
// Close flash messages after 5 seconds
|
||||
setTimeout(function () {
|
||||
var alerts = document.querySelectorAll('.alert:not(.alert-persistent)');
|
||||
alerts.forEach(function (alert) {
|
||||
var bsAlert = bootstrap.Alert.getInstance(alert);
|
||||
if (bsAlert) {
|
||||
bsAlert.close();
|
||||
} else {
|
||||
alert.classList.add('fade');
|
||||
setTimeout(function () {
|
||||
alert.remove();
|
||||
}, 150);
|
||||
}
|
||||
});
|
||||
}, 5000);
|
||||
|
||||
// Add event listener for subnet scan buttons with HTMX
|
||||
document.body.addEventListener('htmx:afterOnLoad', function (event) {
|
||||
if (event.detail.xhr.status === 200) {
|
||||
showNotification('Subnet scan started successfully', 'success');
|
||||
}
|
||||
});
|
||||
|
||||
// Add markdown preview for documentation fields
|
||||
const docTextareas = document.querySelectorAll('textarea[name="documentation"]');
|
||||
docTextareas.forEach(function (textarea) {
|
||||
// Only if preview container exists
|
||||
const previewContainer = document.getElementById('markdown-preview');
|
||||
if (previewContainer && textarea) {
|
||||
textarea.addEventListener('input', function () {
|
||||
// Use the server to render the markdown (safer)
|
||||
fetch('/api/markdown-preview', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ content: textarea.value })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
previewContainer.innerHTML = data.html;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function initTiptapEditor(element) {
|
||||
// Load required Tiptap scripts
|
||||
const editorContainer = document.getElementById('editor-container');
|
||||
const preview = document.getElementById('markdown-preview');
|
||||
|
||||
// Initialize the Tiptap editor
|
||||
const { Editor } = window.tiptap;
|
||||
const { StarterKit } = window.tiptapExtensions;
|
||||
|
||||
const editor = new Editor({
|
||||
element: element,
|
||||
extensions: [
|
||||
StarterKit
|
||||
],
|
||||
content: element.getAttribute('data-content') || '',
|
||||
onUpdate: ({ editor }) => {
|
||||
// Update preview with current content
|
||||
if (preview) {
|
||||
const markdown = editor.getHTML();
|
||||
fetch('/api/markdown-preview', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ markdown: markdown })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
preview.innerHTML = data.html;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Store editor reference
|
||||
window.editor = editor;
|
||||
|
||||
// Form submission handling
|
||||
const form = element.closest('form');
|
||||
if (form) {
|
||||
form.addEventListener('submit', () => {
|
||||
const contentInput = form.querySelector('input[name="content"]');
|
||||
if (contentInput) {
|
||||
contentInput.value = editor.getHTML();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Copy to clipboard function
|
||||
function copyToClipboard(text) {
|
||||
navigator.clipboard.writeText(text).then(function () {
|
||||
// Success notification
|
||||
showNotification('Copied to clipboard!', 'success');
|
||||
}, function (err) {
|
||||
// Error notification
|
||||
showNotification('Could not copy text', 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
// Show notification
|
||||
function showNotification(message, type = 'info') {
|
||||
const notificationArea = document.getElementById('notification-area');
|
||||
if (!notificationArea) return;
|
||||
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `alert alert-${type} alert-dismissible fade show`;
|
||||
notification.innerHTML = `
|
||||
${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
`;
|
||||
|
||||
notificationArea.appendChild(notification);
|
||||
|
||||
// Remove notification after 3 seconds
|
||||
setTimeout(() => {
|
||||
notification.classList.remove('show');
|
||||
setTimeout(() => notification.remove(), 150);
|
||||
}, 3000);
|
||||
}
|
9
app/static/js/clipboard.js
Normal file
9
app/static/js/clipboard.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
function copyToClipboard(text) {
|
||||
const elem = document.createElement('textarea');
|
||||
elem.value = text;
|
||||
document.body.appendChild(elem);
|
||||
elem.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(elem);
|
||||
alert('Copied to clipboard');
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue