This commit is contained in:
pika 2025-03-31 00:19:49 +02:00
parent d79359cd65
commit 30e9c9328e
18 changed files with 320 additions and 141 deletions

View file

@ -25,9 +25,9 @@
</div>
</div>
<div class="row mt-3">
<div class="row g-3">
<div class="col-md-4">
<!-- Basic Information -->
<!-- Basic Information card -->
<div class="card">
<div class="card-header">
<h3 class="card-title">Basic Information</h3>
@ -35,44 +35,38 @@
<div class="card-body">
<div class="mb-3">
<div class="form-label">Server</div>
<div>
<a href="{{ url_for('dashboard.server_view', server_id=server.id) }}">
{{ server.hostname }}
</a>
({{ server.ip_address }})
</div>
<div><a href="{{ url_for('dashboard.server_view', server_id=app.server.id) }}">{{ app.server.name }}</a> ({{
app.server.ip_address }})</div>
</div>
<div class="mb-3">
<div class="form-label">Created</div>
<div>{{ app.created_at.strftime('%Y-%m-%d %H:%M') }}</div>
<div>{{ app.created_at }}</div>
</div>
<div class="mb-3">
<div class="form-label">Last Updated</div>
<div>{{ app.updated_at.strftime('%Y-%m-%d %H:%M') }}</div>
<div>{{ app.updated_at }}</div>
</div>
</div>
</div>
<!-- Ports -->
<!-- Ports card -->
<div class="card mt-3">
<div class="card-header">
<div class="card-header d-flex justify-content-between align-items-center">
<h3 class="card-title">Ports</h3>
<div class="card-actions">
<button class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#addPortModal">
<span class="ti ti-plus me-1"></span> Add Port
</button>
</div>
<button class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#addPortModal">
<span class="ti ti-plus"></span> Add Port
</button>
</div>
<div class="card-body">
<div class="card-body p-0">
{% if app.ports %}
<div class="table-responsive">
<table class="table table-vcenter">
<table class="table table-vcenter card-table">
<thead>
<tr>
<th>Port</th>
<th>Protocol</th>
<th>Description</th>
<th class="w-1"></th>
<th>PORT</th>
<th>PROTOCOL</th>
<th>DESCRIPTION</th>
<th width="40"></th>
</tr>
</thead>
<tbody>
@ -80,12 +74,12 @@
<tr>
<td>{{ port.port_number }}</td>
<td>{{ port.protocol }}</td>
<td>{{ port.description or 'No description' }}</td>
<td>{{ port.description }}</td>
<td>
<a href="#" data-bs-toggle="modal" data-bs-target="#deletePortModal{{ port.id }}"
class="btn btn-sm btn-ghost-danger">
<button type="button" class="btn btn-sm btn-ghost-danger"
onclick="confirmDeletePort({{ port.id }})">
<span class="ti ti-trash"></span>
</a>
</button>
</td>
</tr>
{% endfor %}
@ -93,7 +87,7 @@
</table>
</div>
{% else %}
<div class="empty">
<div class="empty p-4">
<div class="empty-icon">
<span class="ti ti-plug"></span>
</div>
@ -113,14 +107,14 @@
</div>
<div class="col-md-8">
<!-- Documentation section - ENHANCED -->
<div class="card">
<!-- Documentation section -->
<div class="card h-100">
<div class="card-header">
<h3 class="card-title">Documentation</h3>
</div>
<div class="card-body markdown-content">
<div class="card-body markdown-content p-4">
{% if app.documentation %}
{{ app.documentation|markdown }}
{{ app.documentation|markdown|safe }}
{% else %}
<div class="empty">
<div class="empty-icon">
@ -144,23 +138,28 @@
</div>
<!-- Add Port Modal -->
<div class="modal" id="addPortModal" tabindex="-1">
<div class="modal modal-blur fade" id="addPortModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<form method="POST" action="{{ url_for('api.add_app_port', app_id=app.id) }}">
<div class="modal-header">
<h5 class="modal-title">Add Port</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="addPortForm" action="{{ url_for('api.add_app_port', app_id=app.id) }}" method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="modal-header">
<h5 class="modal-title">Add Port</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label required">Port Number</label>
<input type="number" class="form-control" name="port_number" required min="1" max="65535">
<div class="input-group">
<input type="number" class="form-control" name="port_number" min="1" max="65535" required>
<button class="btn btn-outline-secondary" type="button" id="randomPortBtn">
<span class="ti ti-dice"></span>
</button>
</div>
</div>
<div class="mb-3">
<label class="form-label required">Protocol</label>
<select class="form-select" name="protocol" required>
<label class="form-label">Protocol</label>
<select class="form-select" name="protocol">
<option value="TCP">TCP</option>
<option value="UDP">UDP</option>
<option value="SCTP">SCTP</option>
@ -169,18 +168,34 @@
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<input type="text" class="form-control" name="description" placeholder="What is this port used for?">
<input type="text" class="form-control" name="description" placeholder="Optional description">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Add Port</button>
<button type="button" class="btn btn-link link-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary ms-auto">Add Port</button>
</div>
</form>
</div>
</div>
</div>
<!-- Confirmation Modal for Port Deletion -->
<div class="modal modal-blur fade" id="deletePortModal" tabindex="-1">
<div class="modal-dialog modal-sm modal-dialog-centered">
<div class="modal-content">
<div class="modal-body">
<div class="modal-title">Are you sure?</div>
<div>This will permanently delete this port.</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-link link-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" id="confirmDeleteBtn">Delete Port</button>
</div>
</div>
</div>
</div>
<!-- Delete App Modal -->
<div class="modal modal-blur fade" id="deleteAppModal" tabindex="-1">
<div class="modal-dialog modal-sm modal-dialog-centered">
@ -200,24 +215,181 @@
</div>
</div>
<!-- Delete Port Modals -->
{% for port in app.ports %}
<div class="modal modal-blur fade" id="deletePortModal{{ port.id }}" tabindex="-1">
<div class="modal-dialog modal-sm modal-dialog-centered">
<div class="modal-content">
<div class="modal-body">
<div class="modal-title">Are you sure?</div>
<div>This will delete port {{ port.port_number }}/{{ port.protocol }}.</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<form method="POST" action="{{ url_for('api.delete_port', port_id=port.id) }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-danger">Delete Port</button>
</form>
</div>
</div>
</div>
</div>
{% endfor %}
{% endblock %}
<!-- Add some additional CSS for better markdown rendering -->
<style>
.markdown-content {
line-height: 1.6;
padding: 1.5rem !important;
}
.markdown-content h1,
.markdown-content h2,
.markdown-content h3,
.markdown-content h4,
.markdown-content h5,
.markdown-content h6 {
margin-top: 1.5rem;
margin-bottom: 1rem;
font-weight: 600;
}
.markdown-content h1 {
font-size: 1.8rem;
padding-bottom: 0.3rem;
border-bottom: 1px solid var(--tblr-border-color);
}
.markdown-content h2 {
font-size: 1.5rem;
padding-bottom: 0.3rem;
border-bottom: 1px solid var(--tblr-border-color);
}
.markdown-content p {
margin-bottom: 1rem;
}
.markdown-content ul,
.markdown-content ol {
margin-bottom: 1rem;
padding-left: 2rem;
}
.markdown-content blockquote {
padding: 0.5rem 1rem;
margin-bottom: 1rem;
border-left: 0.25rem solid var(--tblr-border-color);
color: var(--tblr-secondary);
}
.markdown-content pre {
padding: 1rem;
margin-bottom: 1rem;
background-color: var(--tblr-bg-surface);
border-radius: 0.25rem;
overflow-x: auto;
}
.markdown-content code {
padding: 0.2rem 0.4rem;
border-radius: 0.25rem;
background-color: var(--tblr-bg-surface);
font-size: 0.875em;
}
.markdown-content pre code {
padding: 0;
background-color: transparent;
}
.markdown-content img {
max-width: 100%;
border-radius: 0.25rem;
margin: 1rem 0;
}
.markdown-content table {
width: 100%;
margin-bottom: 1rem;
border-collapse: collapse;
}
.markdown-content table th,
.markdown-content table td {
padding: 0.5rem;
border: 1px solid var(--tblr-border-color);
}
.markdown-content table th {
background-color: var(--tblr-bg-surface);
font-weight: 600;
}
/* GitHub-style alerts styling */
.markdown-content blockquote:has(p:first-child:contains("[!NOTE]")),
.markdown-content blockquote:has(p:first-child:contains("[!TIP]")),
.markdown-content blockquote:has(p:first-child:contains("[!IMPORTANT]")),
.markdown-content blockquote:has(p:first-child:contains("[!WARNING]")),
.markdown-content blockquote:has(p:first-child:contains("[!CAUTION]")) {
padding: 1rem;
margin: 1rem 0;
border-left-width: 0.25rem;
border-radius: 0.25rem;
}
.markdown-content blockquote:has(p:first-child:contains("[!NOTE]")) {
background-color: rgba(0, 120, 215, 0.1);
border-left-color: #0078d7;
}
.markdown-content blockquote:has(p:first-child:contains("[!TIP]")) {
background-color: rgba(46, 160, 67, 0.1);
border-left-color: #2ea043;
}
.markdown-content blockquote:has(p:first-child:contains("[!IMPORTANT]")) {
background-color: rgba(162, 80, 214, 0.1);
border-left-color: #a250d6;
}
.markdown-content blockquote:has(p:first-child:contains("[!WARNING]")) {
background-color: rgba(245, 159, 0, 0.1);
border-left-color: #f59f00;
}
.markdown-content blockquote:has(p:first-child:contains("[!CAUTION]")) {
background-color: rgba(215, 58, 73, 0.1);
border-left-color: #d73a49;
}
</style>
<script>
// Store app ID for JavaScript use
const appId = {{ app.id }};
let portToDelete = null;
// Function to handle port delete confirmation
function confirmDeletePort(portId) {
portToDelete = portId;
const modal = new bootstrap.Modal(document.getElementById('deletePortModal'));
modal.show();
}
// Set up event listeners when DOM is ready
document.addEventListener('DOMContentLoaded', function () {
// Handle random port button click
const randomPortBtn = document.getElementById('randomPortBtn');
if (randomPortBtn) {
randomPortBtn.addEventListener('click', function () {
fetch('/api/ports/random')
.then(response => response.json())
.then(data => {
document.querySelector('#addPortForm input[name="port_number"]').value = data.port;
});
});
}
// Handle port deletion confirmation
const confirmDeleteBtn = document.getElementById('confirmDeleteBtn');
if (confirmDeleteBtn) {
confirmDeleteBtn.addEventListener('click', function () {
if (portToDelete) {
// Create a form and submit it programmatically
const form = document.createElement('form');
form.method = 'POST';
form.action = `/api/app/${appId}/port/${portToDelete}/delete`;
const csrfInput = document.createElement('input');
csrfInput.type = 'hidden';
csrfInput.name = 'csrf_token';
csrfInput.value = '{{ csrf_token() }}';
form.appendChild(csrfInput);
document.body.appendChild(form);
form.submit();
}
});
}
});
</script>
{% endblock %}