wip
This commit is contained in:
parent
78ce15e82d
commit
0a31714a93
10 changed files with 159 additions and 149 deletions
13
Dockerfile
13
Dockerfile
|
@ -20,20 +20,19 @@ RUN pip install --upgrade pip && \
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Create the instance directory for SQLite
|
# Create the instance directory for SQLite
|
||||||
RUN mkdir -p instance && \
|
# RUN mkdir -p instance && \
|
||||||
chmod 777 instance
|
# chmod 777 instance
|
||||||
|
|
||||||
# Create a non-root user to run the app
|
# Create a non-root user to run the app
|
||||||
RUN useradd -m appuser && \
|
# RUN useradd -m appuser && \
|
||||||
chown -R appuser:appuser /app
|
# chown -R appuser:appuser /app
|
||||||
|
|
||||||
USER appuser
|
# USER appuser
|
||||||
|
|
||||||
# Set environment variables
|
# Set environment variables
|
||||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
PYTHONUNBUFFERED=1 \
|
PYTHONUNBUFFERED=1 \
|
||||||
SECRET_KEY="" \
|
|
||||||
FLASK_APP=wsgi.py
|
FLASK_APP=wsgi.py
|
||||||
|
|
||||||
# Run gunicorn
|
# Run gunicorn
|
||||||
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--timeout", "120", "--workers", "4", "wsgi:app"]
|
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--timeout", "120", "--workers", "4", "run:app"]
|
||||||
|
|
|
@ -298,7 +298,6 @@ def app_view(app_id):
|
||||||
@login_required
|
@login_required
|
||||||
def app_edit(app_id):
|
def app_edit(app_id):
|
||||||
"""Edit an existing application with comprehensive error handling"""
|
"""Edit an existing application with comprehensive error handling"""
|
||||||
# Get the application and all servers
|
|
||||||
app = App.query.get_or_404(app_id)
|
app = App.query.get_or_404(app_id)
|
||||||
servers = Server.query.all()
|
servers = Server.query.all()
|
||||||
|
|
||||||
|
@ -323,87 +322,57 @@ def app_edit(app_id):
|
||||||
|
|
||||||
# Check for port conflicts proactively
|
# Check for port conflicts proactively
|
||||||
conflicts = []
|
conflicts = []
|
||||||
|
seen_ports = set() # To track ports already seen in this submission
|
||||||
|
|
||||||
for i, (port_number, protocol, _) in enumerate(port_data):
|
for i, (port_number, protocol, _) in enumerate(port_data):
|
||||||
try:
|
try:
|
||||||
clean_port = int(port_number)
|
clean_port = int(port_number)
|
||||||
|
# Check if this port has already been seen in this submission
|
||||||
|
port_key = f"{clean_port}/{protocol}"
|
||||||
|
if port_key in seen_ports:
|
||||||
|
conflicts.append((clean_port, protocol, "Duplicate port in submission"))
|
||||||
|
continue
|
||||||
|
seen_ports.add(port_key)
|
||||||
|
|
||||||
|
# Check if the port is in use by another application
|
||||||
in_use, conflicting_app_name = is_port_in_use(
|
in_use, conflicting_app_name = is_port_in_use(
|
||||||
clean_port, protocol, server_id, exclude_app_id=app_id
|
clean_port, protocol, server_id, exclude_app_id=app_id
|
||||||
)
|
)
|
||||||
|
|
||||||
if in_use:
|
if in_use:
|
||||||
conflicts.append((clean_port, protocol, conflicting_app_name))
|
conflicts.append((clean_port, protocol, f"Port {clean_port}/{protocol} is already in use by application '{conflicting_app_name}'"))
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if conflicts:
|
if conflicts:
|
||||||
# Find the IDs of conflicting apps for linking
|
for conflict in conflicts:
|
||||||
conflict_msgs = []
|
flash(f"Conflict: {conflict[0]}/{conflict[1]} - {conflict[2]}", "danger")
|
||||||
for port, protocol, conflict_app_name in conflicts:
|
return render_template("dashboard/app_edit.html", app=app, servers=servers)
|
||||||
conflict_app = App.query.filter_by(name=conflict_app_name, server_id=server_id).first()
|
|
||||||
if conflict_app:
|
|
||||||
edit_url = url_for('dashboard.app_edit', app_id=conflict_app.id)
|
|
||||||
conflict_msgs.append(
|
|
||||||
f'Port {port}/{protocol} is in use by <a href="{edit_url}">{conflict_app_name}</a>'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
conflict_msgs.append(f'Port {port}/{protocol} is in use by {conflict_app_name}')
|
|
||||||
|
|
||||||
for msg in conflict_msgs:
|
# Update application details
|
||||||
flash(msg, "danger")
|
app.name = name
|
||||||
|
app.server_id = server_id
|
||||||
|
app.documentation = documentation
|
||||||
|
app.url = url
|
||||||
|
|
||||||
return render_template(
|
# Only delete existing ports if new port data is provided
|
||||||
"dashboard/app_form.html",
|
if port_data:
|
||||||
title=f"Edit {app.name}",
|
# Remove existing ports and add new ones
|
||||||
edit_mode=True,
|
Port.query.filter_by(app_id=app_id).delete()
|
||||||
servers=servers,
|
for port_number, protocol, description in port_data:
|
||||||
app=app
|
new_port = Port(
|
||||||
)
|
app_id=app_id,
|
||||||
|
port_number=int(port_number),
|
||||||
|
protocol=protocol,
|
||||||
|
description=description
|
||||||
|
)
|
||||||
|
db.session.add(new_port)
|
||||||
|
|
||||||
# Replace local validation with shared function
|
db.session.commit()
|
||||||
valid, error = validate_app_data(name, server_id, existing_app_id=app_id)
|
flash("Application updated successfully", "success")
|
||||||
|
return redirect(url_for("dashboard.app_view", app_id=app_id))
|
||||||
|
|
||||||
if valid:
|
return render_template("dashboard/app_edit.html", app=app, servers=servers)
|
||||||
# Update application with URL
|
|
||||||
app.name = name
|
|
||||||
app.server_id = server_id
|
|
||||||
app.documentation = documentation
|
|
||||||
app.url = url
|
|
||||||
|
|
||||||
# Update application
|
|
||||||
from app.utils.app_utils import save_app
|
|
||||||
|
|
||||||
success, updated_app, error = save_app(
|
|
||||||
name, server_id, documentation, port_data, app_id, url
|
|
||||||
)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
flash("Application updated successfully", "success")
|
|
||||||
return redirect(url_for("dashboard.app_view", app_id=app_id))
|
|
||||||
else:
|
|
||||||
flash(error, "danger")
|
|
||||||
|
|
||||||
# Extract app name from error and provide link if it's a conflict
|
|
||||||
if "already in use by application" in error:
|
|
||||||
app_name = error.split("'")[1] # Extract app name from error message
|
|
||||||
conflict_app = App.query.filter_by(name=app_name, server_id=server_id).first()
|
|
||||||
if conflict_app:
|
|
||||||
edit_url = url_for('dashboard.app_edit', app_id=conflict_app.id)
|
|
||||||
flash(
|
|
||||||
f'Would you like to edit the conflicting application? '
|
|
||||||
f'<a href="{edit_url}">Edit {app_name}</a>',
|
|
||||||
"info"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
flash(error, "danger")
|
|
||||||
|
|
||||||
# GET request - display the form
|
|
||||||
return render_template(
|
|
||||||
"dashboard/app_form.html",
|
|
||||||
title=f"Edit {app.name}",
|
|
||||||
edit_mode=True,
|
|
||||||
servers=servers,
|
|
||||||
app=app
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/app/<int:app_id>/delete", methods=["POST"])
|
@bp.route("/app/<int:app_id>/delete", methods=["POST"])
|
||||||
|
|
|
@ -4,34 +4,34 @@ import os
|
||||||
bp = Blueprint("static_assets", __name__)
|
bp = Blueprint("static_assets", __name__)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/static/libs/tabler-icons/tabler-icons.min.css")
|
# @bp.route("/static/libs/tabler-icons/tabler-icons.min.css")
|
||||||
def tabler_icons():
|
# def tabler_icons():
|
||||||
"""Serve tabler-icons CSS from node_modules or download if missing"""
|
# """Serve tabler-icons CSS from node_modules or download if missing"""
|
||||||
icons_path = os.path.join(current_app.static_folder, "libs", "tabler-icons")
|
# icons_path = os.path.join(current_app.static_folder, "libs", "tabler-icons")
|
||||||
|
#
|
||||||
# Create directory if it doesn't exist
|
# # Create directory if it doesn't exist
|
||||||
if not os.path.exists(icons_path):
|
# if not os.path.exists(icons_path):
|
||||||
os.makedirs(icons_path)
|
# os.makedirs(icons_path)
|
||||||
|
#
|
||||||
css_file = os.path.join(icons_path, "tabler-icons.min.css")
|
# css_file = os.path.join(icons_path, "tabler-icons.min.css")
|
||||||
|
#
|
||||||
# If file doesn't exist, download from CDN
|
# # If file doesn't exist, download from CDN
|
||||||
if not os.path.exists(css_file):
|
# if not os.path.exists(css_file):
|
||||||
import requests
|
# import requests
|
||||||
|
#
|
||||||
try:
|
# try:
|
||||||
cdn_url = "https://cdn.jsdelivr.net/npm/@tabler/icons@latest/iconfont/tabler-icons.min.css"
|
# cdn_url = "https://cdn.jsdelivr.net/npm/@tabler/core@1.1.1/dist/css/tabler.min.css"
|
||||||
response = requests.get(cdn_url)
|
# response = requests.get(cdn_url)
|
||||||
if response.status_code == 200:
|
# if response.status_code == 200:
|
||||||
with open(css_file, "wb") as f:
|
# with open(css_file, "wb") as f:
|
||||||
f.write(response.content)
|
# f.write(response.content)
|
||||||
print(f"Downloaded tabler-icons.min.css from CDN")
|
# print(f"Downloaded tabler-icons.min.css from CDN")
|
||||||
else:
|
# else:
|
||||||
print(f"Failed to download tabler-icons CSS: {response.status_code}")
|
# print(f"Failed to download tabler-icons CSS: {response.status_code}")
|
||||||
except Exception as e:
|
# except Exception as e:
|
||||||
print(f"Error downloading tabler-icons CSS: {e}")
|
# print(f"Error downloading tabler-icons CSS: {e}")
|
||||||
|
#
|
||||||
return send_from_directory(icons_path, "tabler-icons.min.css")
|
# return send_from_directory(icons_path, "tabler-icons.min.css")
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/static/css/tabler.min.css")
|
@bp.route("/static/css/tabler.min.css")
|
||||||
|
@ -50,7 +50,7 @@ def tabler_css():
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cdn_url = "https://cdn.jsdelivr.net/npm/@tabler/core@latest/dist/css/tabler.min.css"
|
cdn_url = "https://cdn.jsdelivr.net/npm/@tabler/core@1.1.1/dist/css/tabler.min.css"
|
||||||
response = requests.get(cdn_url)
|
response = requests.get(cdn_url)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
with open(css_file, "wb") as f:
|
with open(css_file, "wb") as f:
|
||||||
|
@ -82,7 +82,7 @@ def favicon():
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Using a simple placeholder favicon
|
# Using a simple placeholder favicon
|
||||||
cdn_url = "https://www.google.com/favicon.ico"
|
cdn_url = "https://www.svgrepo.com/show/529863/server-minimalistic.svg"
|
||||||
response = requests.get(cdn_url)
|
response = requests.get(cdn_url)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
with open(favicon_file, "wb") as f:
|
with open(favicon_file, "wb") as f:
|
||||||
|
|
|
@ -119,8 +119,6 @@
|
||||||
<select name="protocols[]" class="form-select">
|
<select name="protocols[]" class="form-select">
|
||||||
<option value="TCP" {% if port.protocol=='TCP' %}selected{% endif %}>TCP</option>
|
<option value="TCP" {% if port.protocol=='TCP' %}selected{% endif %}>TCP</option>
|
||||||
<option value="UDP" {% if port.protocol=='UDP' %}selected{% endif %}>UDP</option>
|
<option value="UDP" {% if port.protocol=='UDP' %}selected{% endif %}>UDP</option>
|
||||||
<option value="SCTP" {% if port.protocol=='SCTP' %}selected{% endif %}>SCTP</option>
|
|
||||||
<option value="OTHER" {% if port.protocol=='OTHER' %}selected{% endif %}>OTHER</option>
|
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -144,8 +142,6 @@
|
||||||
<select name="protocols[]" class="form-select">
|
<select name="protocols[]" class="form-select">
|
||||||
<option value="TCP" selected>TCP</option>
|
<option value="TCP" selected>TCP</option>
|
||||||
<option value="UDP">UDP</option>
|
<option value="UDP">UDP</option>
|
||||||
<option value="SCTP">SCTP</option>
|
|
||||||
<option value="OTHER">OTHER</option>
|
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -251,9 +247,48 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
// Make sure removePortRow is in the global scope
|
||||||
|
window.removePortRow = function (button) {
|
||||||
|
const tbody = document.querySelector('#ports-table tbody');
|
||||||
|
const row = button.closest('tr');
|
||||||
|
|
||||||
|
// Get current number of rows
|
||||||
|
const rowCount = tbody.querySelectorAll('tr').length;
|
||||||
|
|
||||||
|
// If this is the last row, clear its values instead of removing
|
||||||
|
if (rowCount <= 1) {
|
||||||
|
const inputs = row.querySelectorAll('input');
|
||||||
|
inputs.forEach(input => {
|
||||||
|
input.value = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset protocol to TCP
|
||||||
|
const protocolSelect = row.querySelector('select[name="protocols[]"]');
|
||||||
|
if (protocolSelect) {
|
||||||
|
protocolSelect.value = 'TCP';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a visual indicator that this row is empty
|
||||||
|
row.classList.add('table-secondary', 'opacity-50');
|
||||||
|
|
||||||
|
// Show a helping message
|
||||||
|
showNotification('Application saved with no ports. Use "Add Port" to add ports.', 'info');
|
||||||
|
} else {
|
||||||
|
// Remove the row if there are other rows
|
||||||
|
row.remove();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Port management functions
|
// Port management functions
|
||||||
function addPortRow(portNumber = '', protocol = 'TCP', description = '') {
|
function addPortRow(portNumber = '', protocol = 'TCP', description = '') {
|
||||||
const tbody = document.querySelector('#ports-table tbody');
|
const tbody = document.querySelector('#ports-table tbody');
|
||||||
|
|
||||||
|
// Remove the empty row indicator if present
|
||||||
|
const emptyRows = tbody.querySelectorAll('tr.table-secondary.opacity-50');
|
||||||
|
if (emptyRows.length > 0) {
|
||||||
|
emptyRows.forEach(row => row.remove());
|
||||||
|
}
|
||||||
|
|
||||||
const newRow = document.createElement('tr');
|
const newRow = document.createElement('tr');
|
||||||
newRow.innerHTML = `
|
newRow.innerHTML = `
|
||||||
<td class="position-relative">
|
<td class="position-relative">
|
||||||
|
@ -265,8 +300,6 @@
|
||||||
<select name="protocols[]" class="form-select">
|
<select name="protocols[]" class="form-select">
|
||||||
<option value="TCP" ${protocol === 'TCP' ? 'selected' : ''}>TCP</option>
|
<option value="TCP" ${protocol === 'TCP' ? 'selected' : ''}>TCP</option>
|
||||||
<option value="UDP" ${protocol === 'UDP' ? 'selected' : ''}>UDP</option>
|
<option value="UDP" ${protocol === 'UDP' ? 'selected' : ''}>UDP</option>
|
||||||
<option value="SCTP" ${protocol === 'SCTP' ? 'selected' : ''}>SCTP</option>
|
|
||||||
<option value="OTHER" ${protocol === 'OTHER' ? 'selected' : ''}>OTHER</option>
|
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -285,10 +318,6 @@
|
||||||
setTimeout(setupPortValidation, 50);
|
setTimeout(setupPortValidation, 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
function removePortRow(button) {
|
|
||||||
button.closest('tr').remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateRandomPort() {
|
async function generateRandomPort() {
|
||||||
try {
|
try {
|
||||||
const serverId = document.querySelector('select[name="server_id"]').value;
|
const serverId = document.querySelector('select[name="server_id"]').value;
|
||||||
|
|
|
@ -156,7 +156,7 @@
|
||||||
<label class="form-label required">Port Number</label>
|
<label class="form-label required">Port Number</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="number" class="form-control" name="port_number" min="1" max="65535" required>
|
<input type="number" class="form-control" name="port_number" min="1" max="65535" required>
|
||||||
<button class="btn btn-outline-secondary" type="button" id="randomPortBtn">
|
<button class="btn btn-outline-secondary" type="button" id="suggestRandomPort">
|
||||||
<span class="ti ti-dice"></span>
|
<span class="ti ti-dice"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -166,8 +166,6 @@
|
||||||
<select class="form-select" name="protocol">
|
<select class="form-select" name="protocol">
|
||||||
<option value="TCP">TCP</option>
|
<option value="TCP">TCP</option>
|
||||||
<option value="UDP">UDP</option>
|
<option value="UDP">UDP</option>
|
||||||
<option value="SCTP">SCTP</option>
|
|
||||||
<option value="OTHER">OTHER</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
@ -350,56 +348,62 @@
|
||||||
|
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
<script>
|
<script>
|
||||||
// Store app ID for JavaScript use
|
// Global variable to store the port ID to delete
|
||||||
const appId = {{ app.id }};
|
let portIdToDelete = null;
|
||||||
let portToDelete = null;
|
|
||||||
|
|
||||||
// IMPORTANT: Define confirmDeletePort outside the DOMContentLoaded event
|
// Function to confirm port deletion
|
||||||
// so it's available in the global scope
|
|
||||||
function confirmDeletePort(portId) {
|
function confirmDeletePort(portId) {
|
||||||
portToDelete = portId;
|
portIdToDelete = portId;
|
||||||
const modal = new bootstrap.Modal(document.getElementById('deletePortModal'));
|
// Show the delete modal
|
||||||
modal.show();
|
const deleteModal = new bootstrap.Modal(document.getElementById('deletePortModal'));
|
||||||
|
deleteModal.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up event listeners when DOM is ready
|
// Set up the confirm button in the delete modal
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
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');
|
const confirmDeleteBtn = document.getElementById('confirmDeleteBtn');
|
||||||
if (confirmDeleteBtn) {
|
if (confirmDeleteBtn) {
|
||||||
confirmDeleteBtn.addEventListener('click', function () {
|
confirmDeleteBtn.addEventListener('click', function () {
|
||||||
if (portToDelete) {
|
if (portIdToDelete) {
|
||||||
// Create a form and submit it programmatically
|
// Create a form to submit the delete request
|
||||||
const form = document.createElement('form');
|
const form = document.createElement('form');
|
||||||
form.method = 'POST';
|
form.method = 'POST';
|
||||||
form.action = `/api/app/${appId}/port/${portToDelete}/delete`;
|
form.action = `/api/app/{{ app.id }}/port/${portIdToDelete}/delete`;
|
||||||
|
|
||||||
|
// Add CSRF token
|
||||||
const csrfInput = document.createElement('input');
|
const csrfInput = document.createElement('input');
|
||||||
csrfInput.type = 'hidden';
|
csrfInput.type = 'hidden';
|
||||||
csrfInput.name = 'csrf_token';
|
csrfInput.name = 'csrf_token';
|
||||||
csrfInput.value = '{{ csrf_token() }}';
|
csrfInput.value = '{{ csrf_token() }}';
|
||||||
|
|
||||||
form.appendChild(csrfInput);
|
form.appendChild(csrfInput);
|
||||||
|
|
||||||
|
// Append to body, submit, and remove
|
||||||
document.body.appendChild(form);
|
document.body.appendChild(form);
|
||||||
form.submit();
|
form.submit();
|
||||||
|
document.body.removeChild(form);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up the random port button
|
||||||
|
const randomPortBtn = document.getElementById('randomPortBtn');
|
||||||
|
if (randomPortBtn) {
|
||||||
|
randomPortBtn.addEventListener('click', function () {
|
||||||
|
fetch(`/api/server/{{ app.server.id }}/free-port`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success && data.port) {
|
||||||
|
document.querySelector('input[name="port_number"]').value = data.port;
|
||||||
|
} else {
|
||||||
|
alert(data.error || 'Could not generate a random port');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error generating random port:', error);
|
||||||
|
alert('Error generating random port');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -17,8 +17,8 @@
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/app.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/app.css') }}">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/tabler.min.css') }}"
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/tabler.min.css') }}"
|
||||||
onerror="this.onerror=null;this.href='https://cdn.jsdelivr.net/npm/@tabler/core@latest/dist/css/tabler.min.css';">
|
onerror="this.onerror=null;this.href='https://cdn.jsdelivr.net/npm/@tabler/core@latest/dist/css/tabler.min.css';">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='libs/tabler-icons/tabler-icons.min.css') }}"
|
<!-- <link rel="stylesheet" href="{{ url_for('static', filename='libs/tabler-icons/tabler-icons.min.css') }}" -->
|
||||||
onerror="this.onerror=null;this.href='https://cdn.jsdelivr.net/npm/@tabler/icons@latest/iconfont/tabler-icons.min.css';">
|
<!-- onerror="this.onerror=null;this.href='https://cdn.jsdelivr.net/npm/@tabler/icons@latest/iconfont/tabler-icons.min.css';"> -->
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/custom.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/custom.css') }}">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/github-markdown-reading-view.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/github-markdown-reading-view.css') }}">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/github-markdown-source-view.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/github-markdown-source-view.css') }}">
|
||||||
|
|
|
@ -105,6 +105,10 @@ def process_app_ports(app_id, port_data, server_id=None):
|
||||||
if app:
|
if app:
|
||||||
server_id = app.server_id
|
server_id = app.server_id
|
||||||
|
|
||||||
|
# If no port data is provided, that's valid (app with no ports)
|
||||||
|
if not port_data:
|
||||||
|
return True, None
|
||||||
|
|
||||||
# Track the port+protocol combinations we've seen to avoid duplicates
|
# Track the port+protocol combinations we've seen to avoid duplicates
|
||||||
seen_ports = set()
|
seen_ports = set()
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ services:
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
# image: homedocs:latest
|
# image: homedocs:latest
|
||||||
ports:
|
ports:
|
||||||
- "5001:8000"
|
- "5000:8000"
|
||||||
volumes:
|
volumes:
|
||||||
- ./instance:/app/instance # Persist SQLite database
|
- ./instance:/app/instance # Persist SQLite database
|
||||||
# environment:
|
# environment:
|
||||||
|
|
4
run.py
4
run.py
|
@ -10,6 +10,7 @@ from datetime import datetime
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
import json
|
import json
|
||||||
|
from flask_wtf.csrf import CSRFProtect
|
||||||
|
|
||||||
# Add the current directory to Python path
|
# Add the current directory to Python path
|
||||||
current_dir = os.path.abspath(os.path.dirname(__file__))
|
current_dir = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
@ -79,6 +80,9 @@ def register_routes(app):
|
||||||
print("Starting Flask app with SQLite database...")
|
print("Starting Flask app with SQLite database...")
|
||||||
app = create_app("development")
|
app = create_app("development")
|
||||||
|
|
||||||
|
# Set up CSRF protection
|
||||||
|
csrf = CSRFProtect(app)
|
||||||
|
|
||||||
|
|
||||||
@app.shell_context_processor
|
@app.shell_context_processor
|
||||||
def make_shell_context():
|
def make_shell_context():
|
||||||
|
|
1
wsgi.py
1
wsgi.py
|
@ -1,6 +1,7 @@
|
||||||
import os
|
import os
|
||||||
import secrets
|
import secrets
|
||||||
from app import create_app
|
from app import create_app
|
||||||
|
from flask_wtf.csrf import CSRFProtect
|
||||||
|
|
||||||
# Generate a secret key if not provided
|
# Generate a secret key if not provided
|
||||||
if not os.environ.get("SECRET_KEY"):
|
if not os.environ.get("SECRET_KEY"):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue