404 lines
No EOL
13 KiB
HTML
404 lines
No EOL
13 KiB
HTML
{% extends "layout.html" %}
|
|
|
|
{% block title %}{{ title }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-xl">
|
|
<div class="page-header d-print-none">
|
|
<div class="row align-items-center">
|
|
<div class="col">
|
|
<div class="page-pretitle">
|
|
Application Details
|
|
</div>
|
|
<h2 class="page-title">
|
|
{{ app.name }}
|
|
</h2>
|
|
</div>
|
|
<div class="col-auto ms-auto d-print-none">
|
|
<div class="btn-list">
|
|
<a href="{{ url_for('dashboard.app_edit', app_id=app.id) }}" class="btn btn-primary d-none d-sm-inline-block">
|
|
<span class="ti ti-edit"></span>
|
|
Edit Application
|
|
</a>
|
|
<button class="btn btn-danger d-none d-sm-inline-block" data-bs-toggle="modal"
|
|
data-bs-target="#deleteAppModal">
|
|
<span class="ti ti-trash"></span>
|
|
Delete
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-3">
|
|
<div class="col-md-4">
|
|
<!-- Basic Information card -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3 class="card-title">Basic Information</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-3">
|
|
<div class="form-label">Server</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 }}</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<div class="form-label">Last Updated</div>
|
|
<div>{{ app.updated_at }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Ports card -->
|
|
<div class="card mt-3">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h3 class="card-title">Ports</h3>
|
|
<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 p-0">
|
|
{% if app.ports %}
|
|
<div class="table-responsive">
|
|
<table class="table table-vcenter card-table">
|
|
<thead>
|
|
<tr>
|
|
<th>PORT</th>
|
|
<th>PROTOCOL</th>
|
|
<th>DESCRIPTION</th>
|
|
<th class="w-1"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for port in app.ports %}
|
|
<tr>
|
|
<td>{{ port.port_number }}</td>
|
|
<td><span class="badge bg-blue">{{ port.protocol }}</span></td>
|
|
<td>{{ port.description }}</td>
|
|
<td>
|
|
<button type="button" class="btn btn-sm btn-ghost-danger"
|
|
onclick="confirmDeletePort({{ port.id }})">
|
|
<span class="ti ti-trash"></span>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<div class="empty p-4">
|
|
<div class="empty-icon">
|
|
<span class="ti ti-plug"></span>
|
|
</div>
|
|
<p class="empty-title">No ports configured</p>
|
|
<p class="empty-subtitle text-muted">Add ports to document what this application uses</p>
|
|
<div class="empty-action">
|
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addPortModal">
|
|
<span class="ti ti-plus me-1"></span> Add Port
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-8">
|
|
<!-- Documentation section -->
|
|
<div class="card h-100">
|
|
<div class="card-header">
|
|
<h3 class="card-title">Documentation</h3>
|
|
</div>
|
|
<div class="card-body markdown-content p-4">
|
|
{% if app.documentation %}
|
|
{{ app.documentation|markdown|safe }}
|
|
{% else %}
|
|
<div class="empty">
|
|
<div class="empty-icon">
|
|
<span class="ti ti-file-text"></span>
|
|
</div>
|
|
<p class="empty-title">No documentation available</p>
|
|
<p class="empty-subtitle text-muted">
|
|
Add documentation to this application to keep track of important information.
|
|
</p>
|
|
<div class="empty-action">
|
|
<a href="{{ url_for('dashboard.app_edit', app_id=app.id) }}" class="btn btn-primary">
|
|
<span class="ti ti-edit me-2"></span> Add Documentation
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add Port Modal -->
|
|
<div class="modal modal-blur fade" id="addPortModal" tabindex="-1">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<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-body">
|
|
<div class="mb-3">
|
|
<label class="form-label required">Port Number</label>
|
|
<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">Protocol</label>
|
|
<select class="form-select" name="protocol">
|
|
<option value="TCP">TCP</option>
|
|
<option value="UDP">UDP</option>
|
|
<option value="SCTP">SCTP</option>
|
|
<option value="OTHER">OTHER</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Description</label>
|
|
<input type="text" class="form-control" name="description" placeholder="Optional description">
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<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>
|
|
|
|
<!-- Delete Port Modal -->
|
|
<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">
|
|
<div class="modal-content">
|
|
<div class="modal-body">
|
|
<div class="modal-title">Are you sure?</div>
|
|
<div>This will permanently delete the application "{{ app.name }}" and all its ports.</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('dashboard.app_delete', app_id=app.id) }}">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<button type="submit" class="btn btn-danger">Delete Application</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 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>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
// Store app ID for JavaScript use
|
|
const appId = {{ app.id }};
|
|
let portToDelete = null;
|
|
|
|
// IMPORTANT: Define confirmDeletePort outside the DOMContentLoaded event
|
|
// so it's available in the global scope
|
|
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;
|
|
})
|
|
.catch(error => {
|
|
console.error('Error fetching random port:', error);
|
|
});
|
|
});
|
|
}
|
|
|
|
// 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 %} |