wip
This commit is contained in:
parent
d79359cd65
commit
30e9c9328e
18 changed files with 320 additions and 141 deletions
BIN
__pycache__/config.cpython-313.pyc
Normal file
BIN
__pycache__/config.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
app/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/core/__pycache__/auth.cpython-313.pyc
Normal file
BIN
app/core/__pycache__/auth.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/core/__pycache__/extensions.cpython-313.pyc
Normal file
BIN
app/core/__pycache__/extensions.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/core/__pycache__/models.cpython-313.pyc
Normal file
BIN
app/core/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/core/__pycache__/template_filters.cpython-313.pyc
Normal file
BIN
app/core/__pycache__/template_filters.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/routes/__pycache__/api.cpython-313.pyc
Normal file
BIN
app/routes/__pycache__/api.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/routes/__pycache__/auth.cpython-313.pyc
Normal file
BIN
app/routes/__pycache__/auth.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/routes/__pycache__/dashboard.cpython-313.pyc
Normal file
BIN
app/routes/__pycache__/dashboard.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/routes/__pycache__/importexport.cpython-313.pyc
Normal file
BIN
app/routes/__pycache__/importexport.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/routes/__pycache__/ipam.cpython-313.pyc
Normal file
BIN
app/routes/__pycache__/ipam.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/routes/__pycache__/static.cpython-313.pyc
Normal file
BIN
app/routes/__pycache__/static.cpython-313.pyc
Normal file
Binary file not shown.
|
@ -1,4 +1,4 @@
|
|||
from flask import Blueprint, jsonify, request, abort, current_app, render_template
|
||||
from flask import Blueprint, jsonify, request, abort, current_app, render_template, redirect, url_for
|
||||
from flask_login import login_required
|
||||
from app.core.models import Subnet, Server, App, Port
|
||||
from app.core.extensions import db
|
||||
|
@ -197,15 +197,6 @@ def status():
|
|||
return jsonify({"status": "OK"})
|
||||
|
||||
|
||||
@bp.route("/markdown-preview", methods=["POST"])
|
||||
@csrf.exempt # Remove this line in production! Temporary fix for demo purposes
|
||||
def markdown_preview():
|
||||
data = request.json
|
||||
md_content = data.get("markdown", "")
|
||||
html = markdown.markdown(md_content)
|
||||
return jsonify({"html": html})
|
||||
|
||||
|
||||
@bp.route("/ports/suggest", methods=["GET"])
|
||||
def suggest_ports():
|
||||
app_type = request.args.get("type", "").lower()
|
||||
|
@ -296,15 +287,15 @@ def add_app_port(app_id):
|
|||
valid, clean_port, error = validate_port_data(port_number, protocol, description)
|
||||
|
||||
if not valid:
|
||||
return jsonify({"success": False, "error": error}), 400
|
||||
flash(error, "danger")
|
||||
return redirect(url_for("dashboard.app_view", app_id=app_id)) if not request.is_xhr else jsonify({"success": False, "error": error}), 400
|
||||
|
||||
# Check if port already exists
|
||||
existing_port = Port.query.filter_by(app_id=app_id, port_number=clean_port).first()
|
||||
if existing_port:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"Port {clean_port} already exists for this application"
|
||||
}), 400
|
||||
error_msg = f"Port {clean_port} already exists for this application"
|
||||
flash(error_msg, "warning")
|
||||
return redirect(url_for("dashboard.app_view", app_id=app_id)) if not request.is_xhr else jsonify({"success": False, "error": error_msg}), 400
|
||||
|
||||
# Create new port
|
||||
new_port = Port(
|
||||
|
@ -316,10 +307,17 @@ def add_app_port(app_id):
|
|||
db.session.add(new_port)
|
||||
db.session.commit()
|
||||
|
||||
flash(f"Port {clean_port}/{protocol} added successfully", "success")
|
||||
success_msg = f"Port {clean_port}/{protocol} added successfully"
|
||||
flash(success_msg, "success")
|
||||
|
||||
# If it's a regular form submission (not AJAX), redirect
|
||||
if not request.is_xhr and not request.is_json:
|
||||
return redirect(url_for("dashboard.app_view", app_id=app_id))
|
||||
|
||||
# Otherwise return JSON for API/AJAX calls
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": f"Port {clean_port}/{protocol} added successfully",
|
||||
"message": success_msg,
|
||||
"port": {
|
||||
"id": new_port.id,
|
||||
"number": new_port.port_number,
|
||||
|
@ -330,7 +328,8 @@ def add_app_port(app_id):
|
|||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
flash(f"Error: {str(e)}", "danger")
|
||||
return redirect(url_for("dashboard.app_view", app_id=app_id)) if not request.is_xhr else jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
|
||||
@bp.route("/app/<int:app_id>/ports", methods=["GET"])
|
||||
|
@ -357,25 +356,26 @@ def get_app_ports(app_id):
|
|||
return jsonify(result)
|
||||
|
||||
|
||||
@bp.route("/port/<int:port_id>/delete", methods=["POST"])
|
||||
@bp.route("/app/<int:app_id>/port/<int:port_id>/delete", methods=["POST"])
|
||||
@login_required
|
||||
def delete_port(port_id):
|
||||
"""Delete a port"""
|
||||
# Add CSRF validation
|
||||
if request.is_json: # For AJAX requests
|
||||
csrf_token = request.json.get("csrf_token")
|
||||
if not csrf_token or not csrf.validate_csrf(csrf_token):
|
||||
return jsonify({"success": False, "error": "CSRF validation failed"}), 403
|
||||
|
||||
def delete_app_port(app_id, port_id):
|
||||
"""Delete a port from an application"""
|
||||
app = App.query.get_or_404(app_id)
|
||||
port = Port.query.get_or_404(port_id)
|
||||
|
||||
|
||||
if port.app_id != app.id:
|
||||
flash("Port does not belong to this application", "danger")
|
||||
return redirect(url_for("dashboard.app_view", app_id=app_id))
|
||||
|
||||
try:
|
||||
db.session.delete(port)
|
||||
db.session.commit()
|
||||
return jsonify({"success": True, "message": f"Port {port.number} deleted"})
|
||||
flash(f"Port {port.port_number}/{port.protocol} deleted successfully", "success")
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
flash(f"Error deleting port: {str(e)}", "danger")
|
||||
|
||||
return redirect(url_for("dashboard.app_view", app_id=app_id))
|
||||
|
||||
|
||||
@bp.route("/subnets/<int:subnet_id>/servers", methods=["GET"])
|
||||
|
|
BIN
app/scripts/__pycache__/ip_scanner.cpython-313.pyc
Normal file
BIN
app/scripts/__pycache__/ip_scanner.cpython-313.pyc
Normal file
Binary file not shown.
|
@ -66,34 +66,6 @@
|
|||
<small class="form-hint">Select the server where this application runs</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Documentation</label>
|
||||
<ul class="nav nav-tabs mb-2" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<a href="#markdown-edit" class="nav-link active" data-bs-toggle="tab" role="tab">Edit</a>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<a href="#markdown-preview" class="nav-link" data-bs-toggle="tab" role="tab" id="preview-tab">Preview</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="markdown-edit" role="tabpanel">
|
||||
<textarea class="form-control" name="documentation" id="documentation" rows="6"
|
||||
placeholder="Document your application using Markdown...">{{ app.documentation if app else '' }}</textarea>
|
||||
<small class="form-hint">
|
||||
Markdown formatting is supported. Include details about what this application does, contact info, etc.
|
||||
</small>
|
||||
</div>
|
||||
<div class="tab-pane" id="markdown-preview" role="tabpanel">
|
||||
<div class="markdown-content border rounded p-3" style="min-height: 12rem;">
|
||||
<div id="preview-content">Preview will be shown here...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hr-text">Port Configuration</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<label class="form-label mb-0">Application Ports</label>
|
||||
|
@ -112,16 +84,16 @@
|
|||
<table class="table table-vcenter card-table" id="ports-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 20%">Port Number</th>
|
||||
<th style="width: 20%">Protocol</th>
|
||||
<th style="width: 50%">Description</th>
|
||||
<th style="width: 10%">Actions</th>
|
||||
<th>Port</th>
|
||||
<th>Protocol</th>
|
||||
<th>Description</th>
|
||||
<th width="40"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if app and app.ports %}
|
||||
{% for port in app.ports %}
|
||||
<tr data-port-id="{{ port.id }}">
|
||||
<tr>
|
||||
<td>
|
||||
<input type="number" name="port_numbers[]" class="form-control" min="1" max="65535"
|
||||
value="{{ port.port_number }}" required>
|
||||
|
@ -146,13 +118,22 @@
|
|||
</tr>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<!-- New rows will be added here dynamically -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<small class="form-hint">Configure the network ports used by this application</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Documentation</label>
|
||||
<textarea class="form-control" name="documentation" id="documentation" rows="10"
|
||||
placeholder="Enter documentation in Markdown format">{{ app.documentation if app else '' }}</textarea>
|
||||
<small class="form-hint">
|
||||
Use <a href="https://www.markdownguide.org/basic-syntax/" target="_blank">Markdown syntax</a>
|
||||
to format your documentation. The content will be rendered when viewing the application.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="form-footer">
|
||||
<button type="submit" class="btn btn-primary">Save Application</button>
|
||||
{% if edit_mode %}
|
||||
|
@ -175,11 +156,45 @@
|
|||
const appId = null;
|
||||
{% endif %}
|
||||
|
||||
// Setup markdown preview
|
||||
setupMarkdownPreview();
|
||||
// Initialize everything once the DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Connect port management buttons
|
||||
const addPortBtn = document.getElementById('add-port-btn');
|
||||
const randomPortBtn = document.getElementById('random-port-btn');
|
||||
|
||||
// Setup port management
|
||||
setupPortHandlers();
|
||||
if (addPortBtn) {
|
||||
addPortBtn.addEventListener('click', function () {
|
||||
addPortRow();
|
||||
});
|
||||
}
|
||||
|
||||
if (randomPortBtn) {
|
||||
randomPortBtn.addEventListener('click', function () {
|
||||
generateRandomPort();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Show notifications
|
||||
function showNotification(message, type = 'info') {
|
||||
const alertDiv = document.createElement('div');
|
||||
alertDiv.className = `alert alert-${type} alert-dismissible fade show`;
|
||||
alertDiv.innerHTML = `
|
||||
${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
`;
|
||||
|
||||
const container = document.querySelector('.card-body');
|
||||
if (container) {
|
||||
container.insertBefore(alertDiv, container.firstChild);
|
||||
|
||||
// Auto-dismiss after 5 seconds
|
||||
setTimeout(() => {
|
||||
alertDiv.classList.remove('show');
|
||||
setTimeout(() => alertDiv.remove(), 150);
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
@ -244,13 +259,5 @@
|
|||
showNotification('Failed to generate random port', 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
function setupPortHandlers() {
|
||||
// Add port button
|
||||
document.getElementById('add-port-btn')?.addEventListener('click', () => addPortRow());
|
||||
|
||||
// Random port button
|
||||
document.getElementById('random-port-btn')?.addEventListener('click', generateRandomPort);
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -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 %}
|
BIN
app/utils/__pycache__/app_utils.cpython-313.pyc
Normal file
BIN
app/utils/__pycache__/app_utils.cpython-313.pyc
Normal file
Binary file not shown.
BIN
instance/app.db
Normal file
BIN
instance/app.db
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue