batman (working version kinda)

This commit is contained in:
pika 2025-03-30 19:20:13 +02:00
commit 6dd38036e7
65 changed files with 3950 additions and 0 deletions

View file

@ -0,0 +1,173 @@
<form hx-post="{{ action_url }}" hx-target="#content">
<div class="form-group mb-3">
<label class="form-label">App Name</label>
<input type="text" name="name" class="form-control" value="{{ app.name if app else '' }}" required>
</div>
<div class="form-group mb-3">
<label class="form-label">App Type</label>
<select id="app-type" name="app_type" class="form-select">
<option value="">-- Select Type --</option>
<option value="web">Web Application</option>
<option value="database">Database</option>
<option value="mail">Mail Server</option>
<option value="file">File Server</option>
</select>
</div>
<div class="form-group mb-3">
<label class="form-label">Server</label>
<select name="server_id" class="form-select" required>
{% for server in servers %}
<option value="{{ server.id }}" {% if app and app.server_id==server.id %}selected{% endif %}>
{{ server.hostname }} ({{ server.ip_address }})
</option>
{% endfor %}
</select>
</div>
<div class="form-group mb-3">
<label class="form-label">Documentation</label>
{% include "components/markdown_editor.html" with content=app.documentation if app else "" %}
</div>
<div class="card mb-3">
<div class="card-header">
<div class="d-flex">
<h3 class="card-title">Ports</h3>
<div class="ms-auto">
<button type="button" class="btn btn-sm btn-outline-primary" id="suggest-ports">
Suggest Ports
</button>
</div>
</div>
</div>
<div class="card-body">
<div id="ports-container">
{% if app and app.ports %}
{% for port in app.ports %}
<div class="port-entry mb-2 row">
<div class="col-3">
<input type="number" name="port[]" class="form-control" value="{{ port.port }}" placeholder="Port" min="1"
max="65535">
</div>
<div class="col-3">
<select name="port_type[]" class="form-select">
<option value="tcp" {% if port.type=='tcp' %}selected{% endif %}>TCP</option>
<option value="udp" {% if port.type=='udp' %}selected{% endif %}>UDP</option>
</select>
</div>
<div class="col-5">
<input type="text" name="port_desc[]" class="form-control" value="{{ port.desc }}"
placeholder="Description">
</div>
<div class="col-1">
<button type="button" class="btn btn-sm btn-danger remove-port">×</button>
</div>
</div>
{% endfor %}
{% else %}
<div class="port-entry mb-2 row">
<div class="col-3">
<input type="number" name="port[]" class="form-control" placeholder="Port" min="1" max="65535">
</div>
<div class="col-3">
<select name="port_type[]" class="form-select">
<option value="tcp">TCP</option>
<option value="udp">UDP</option>
</select>
</div>
<div class="col-5">
<input type="text" name="port_desc[]" class="form-control" placeholder="Description">
</div>
<div class="col-1">
<button type="button" class="btn btn-sm btn-danger remove-port">×</button>
</div>
</div>
{% endif %}
</div>
<button type="button" class="btn btn-sm btn-outline-secondary mt-2" id="add-port">
Add Port
</button>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Save</button>
<a href="{{ cancel_url }}" class="btn btn-outline-secondary">Cancel</a>
</div>
</form>
<script>
document.addEventListener('DOMContentLoaded', function () {
// Add new port entry
document.getElementById('add-port').addEventListener('click', function () {
const template = document.querySelector('.port-entry').cloneNode(true);
const inputs = template.querySelectorAll('input, select');
inputs.forEach(input => input.value = '');
if (inputs[0].type === 'number') inputs[0].value = '';
document.getElementById('ports-container').appendChild(template);
// Re-attach remove handlers
attachRemoveHandlers();
});
// Remove port entry
function attachRemoveHandlers() {
document.querySelectorAll('.remove-port').forEach(button => {
button.onclick = function () {
const portEntries = document.querySelectorAll('.port-entry');
if (portEntries.length > 1) {
this.closest('.port-entry').remove();
} else {
const inputs = this.closest('.port-entry').querySelectorAll('input, select');
inputs.forEach(input => input.value = '');
}
};
});
}
attachRemoveHandlers();
// Port suggestions based on app type
document.getElementById('suggest-ports').addEventListener('click', function () {
const appType = document.getElementById('app-type').value;
fetch(`/api/ports/suggest?type=${appType}`)
.then(response => response.json())
.then(data => {
// Clear existing ports
const portsContainer = document.getElementById('ports-container');
portsContainer.innerHTML = '';
// Add suggested ports
data.forEach(port => {
const template = document.createElement('div');
template.className = 'port-entry mb-2 row';
template.innerHTML = `
<div class="col-3">
<input type="number" name="port[]" class="form-control" value="${port.port}" min="1" max="65535">
</div>
<div class="col-3">
<select name="port_type[]" class="form-select">
<option value="tcp" ${port.type === 'tcp' ? 'selected' : ''}>TCP</option>
<option value="udp" ${port.type === 'udp' ? 'selected' : ''}>UDP</option>
</select>
</div>
<div class="col-5">
<input type="text" name="port_desc[]" class="form-control" value="${port.desc}" placeholder="Description">
</div>
<div class="col-1">
<button type="button" class="btn btn-sm btn-danger remove-port">×</button>
</div>
`;
portsContainer.appendChild(template);
});
// Re-attach remove handlers
attachRemoveHandlers();
});
});
});
</script>

View file

@ -0,0 +1,105 @@
<div class="markdown-editor">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="editor-toolbar">
<button type="button" data-action="bold" class="btn btn-sm btn-outline-secondary">
<i class="ti ti-bold"></i>
</button>
<button type="button" data-action="italic" class="btn btn-sm btn-outline-secondary">
<i class="ti ti-italic"></i>
</button>
<button type="button" data-action="heading" class="btn btn-sm btn-outline-secondary">
<i class="ti ti-h-1"></i>
</button>
<button type="button" data-action="bulletList" class="btn btn-sm btn-outline-secondary">
<i class="ti ti-list"></i>
</button>
<button type="button" data-action="orderedList" class="btn btn-sm btn-outline-secondary">
<i class="ti ti-list-numbers"></i>
</button>
<button type="button" data-action="link" class="btn btn-sm btn-outline-secondary">
<i class="ti ti-link"></i>
</button>
<button type="button" data-action="code" class="btn btn-sm btn-outline-secondary">
<i class="ti ti-code"></i>
</button>
</div>
<div id="editor-container" class="mt-2">
<div id="editor"></div>
</div>
<input type="hidden" name="{{ field_name }}" id="markdown-content" value="{{ content }}">
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h3 class="card-title">Preview</h3>
</div>
<div class="card-body p-0">
<div id="markdown-preview" class="markdown-body p-3"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
// Initialize Tiptap editor
const editor = new window.tiptap.Editor({
element: document.getElementById('editor'),
extensions: [
window.tiptapStarterKit.StarterKit,
window.tiptapLink.Link,
window.tiptapCodeBlock.CodeBlock,
window.tiptapImage.Image
],
content: document.getElementById('markdown-content').value || '',
onUpdate: ({ editor }) => {
// Update the hidden input with the markdown content
const markdown = editor.storage.markdown.getMarkdown();
document.getElementById('markdown-content').value = markdown;
// Update preview
updatePreview(markdown);
}
});
// Initialize the preview with the current content
updatePreview(editor.storage.markdown.getMarkdown());
// Initialize the toolbar buttons
document.querySelectorAll('.editor-toolbar button').forEach(button => {
button.addEventListener('click', () => {
const action = button.dataset.action;
if (action === 'link') {
const url = prompt('URL');
if (url) {
editor.chain().focus().setLink({ href: url }).run();
}
} else if (action === 'heading') {
editor.chain().focus().toggleHeading({ level: 1 }).run();
} else {
editor.chain().focus()[`toggle${action.charAt(0).toUpperCase() + action.slice(1)}`]().run();
}
});
});
// Function to update preview
function updatePreview(markdown) {
const converter = new showdown.Converter({
tables: true,
tasklists: true,
strikethrough: true,
ghCodeBlocks: true
});
const html = converter.makeHtml(markdown);
document.getElementById('markdown-preview').innerHTML = html;
}
});
</script>

View file

@ -0,0 +1,6 @@
<div class="port-row" hx-target="this">
<span>{{ port.number }}/{{ port.protocol }}</span>
<button hx-get="/port/{{ port.id }}/copy" class="btn btn-sm">
Kopieren
</button>
</div>