mirror of
https://github.com/ChrispyBacon-dev/DockFlare.git
synced 2026-04-28 03:39:32 +00:00
Revert: Roll back to stable state v2.0.1
This commit is contained in:
parent
f61fc7e5ff
commit
244e4635df
13 changed files with 32 additions and 181 deletions
10
README.MD
10
README.MD
|
|
@ -1,5 +1,5 @@
|
|||
<p align="center">
|
||||
<a href="https://dockflare.app/podcast.mp4" title="Now you're thinking with tunnels">
|
||||
<a href="https://dockflare.app" title="Now you're thinking with tunnels">
|
||||
<img src="images/bannertr.png" width="500px" alt="DockFlare Banner" />
|
||||
</a>
|
||||
</p>
|
||||
|
|
@ -9,15 +9,9 @@
|
|||
<p align="center">
|
||||
<em>Go from container to publicly-secured URL in seconds. No manual Cloudflare dashboard configuration required.</em>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://dockflare.app/podcast" target="_blank" rel="noopener noreferrer">
|
||||
<img src="https://img.shields.io/badge/DockFlare-Podcast-20a6e2?style=for-the-badge&logo=soundcloud" alt="Listen to the DockFlare Podcast">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/ChrispyBacon-dev/DockFlare/releases/tag/v2.0.5"><img src="https://img.shields.io/badge/Release-v2.0.5-blue.svg?style=for-the-badge" alt="Release"></a>
|
||||
<a href="https://github.com/ChrispyBacon-dev/DockFlare/releases/tag/v2.0.0"><img src="https://img.shields.io/badge/Release-v2.0.1-blue.svg?style=for-the-badge" alt="Release"></a>
|
||||
<a href="https://hub.docker.com/r/alplat/dockflare"><img src="https://img.shields.io/docker/pulls/alplat/dockflare?style=for-the-badge" alt="Docker Pulls"></a>
|
||||
<a href="https://www.python.org/"><img src="https://img.shields.io/badge/Made%20with-Python-1f425f.svg?style=for-the-badge" alt="Python"></a>
|
||||
<a href="https://github.com/ChrispyBacon-dev/DockFlare/blob/main/LICENSE.MD"><img src="https://img.shields.io/badge/License-GPL--3.0-blue.svg?style=for-the-badge" alt="License"></a>
|
||||
|
|
|
|||
|
|
@ -21,35 +21,10 @@ import sys
|
|||
import os
|
||||
|
||||
from flask import Flask
|
||||
from flask_login import LoginManager, UserMixin
|
||||
from flask_wtf.csrf import CSRFProtect
|
||||
import docker
|
||||
from docker.errors import APIError
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
|
||||
from . import config
|
||||
|
||||
# --- Authentication Setup ---
|
||||
if config.DOCKFLARE_PASSWORD and not config.DOCKFLARE_PASSWORD.startswith('pbkdf2:sha256:'):
|
||||
logging.warning("DOCKFLARE_PASSWORD is not hashed. Hashing now. Please update your environment variable to the new hashed password.")
|
||||
config.DOCKFLARE_PASSWORD = generate_password_hash(config.DOCKFLARE_PASSWORD)
|
||||
logging.warning(f"Hashed password: {config.DOCKFLARE_PASSWORD}")
|
||||
|
||||
login_manager = LoginManager()
|
||||
|
||||
class User(UserMixin):
|
||||
def __init__(self, id):
|
||||
self.id = id
|
||||
|
||||
@staticmethod
|
||||
def get(user_id):
|
||||
if config.DOCKFLARE_PASSWORD and user_id == 'dockflare_user':
|
||||
return User(user_id)
|
||||
return None
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
return User.get(user_id)
|
||||
from . import config
|
||||
|
||||
tunnel_state = { "name": config.TUNNEL_NAME, "id": None, "token": None, "status_message": "Initializing...", "error": None }
|
||||
cloudflared_agent_state = { "container_status": "unknown", "last_action_status": None }
|
||||
|
|
@ -100,19 +75,11 @@ except Exception as e:
|
|||
logging.error(f"FATAL: Failed to connect to Docker daemon: {e}")
|
||||
docker_client = None
|
||||
|
||||
csrf = CSRFProtect()
|
||||
|
||||
def create_app():
|
||||
|
||||
app_instance = Flask(__name__)
|
||||
app_instance.config['SECRET_KEY'] = config.SECRET_KEY
|
||||
app_instance.config['PREFERRED_URL_SCHEME'] = 'http'
|
||||
|
||||
if config.DOCKFLARE_PASSWORD:
|
||||
login_manager.init_app(app_instance)
|
||||
login_manager.login_view = 'web.login'
|
||||
csrf.init_app(app_instance)
|
||||
|
||||
app_instance = Flask(__name__)
|
||||
app_instance.secret_key = os.urandom(24)
|
||||
app_instance.config['PREFERRED_URL_SCHEME'] = 'http'
|
||||
app_instance.reconciliation_info = {
|
||||
"in_progress": False,
|
||||
"progress": 0,
|
||||
|
|
@ -122,12 +89,12 @@ def create_app():
|
|||
"status": "Not started"
|
||||
}
|
||||
|
||||
with app_instance.app_context():
|
||||
from .web import routes as web_routes
|
||||
with app_instance.app_context():
|
||||
from .web import routes as web_routes
|
||||
app_instance.register_blueprint(web_routes.bp)
|
||||
logging.info("Web blueprint registered.")
|
||||
from .web.api_v2_routes import api_v2_bp
|
||||
app_instance.register_blueprint(api_v2_bp)
|
||||
from .web.api_v2_routes import api_v2_bp
|
||||
app_instance.register_blueprint(api_v2_bp)
|
||||
logging.info("API v2 blueprint registered.")
|
||||
return app_instance
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ from dotenv import load_dotenv
|
|||
load_dotenv()
|
||||
|
||||
# --- DockFlare Version ---
|
||||
APP_VERSION = "v2.0.5"
|
||||
APP_VERSION = "v2.0.1"
|
||||
# --- web: https://dockflare.app ---
|
||||
# --- github: https://github.com/ChrispyBacon-dev/DockFlare ---
|
||||
|
||||
|
|
@ -68,9 +68,6 @@ CUSTOM_LABEL_PREFIX = os.getenv('LABEL_PREFIX')
|
|||
# DEPRECATED: This will be removed in a future version.
|
||||
LABEL_PREFIX = CUSTOM_LABEL_PREFIX or PRIMARY_LABEL_PREFIX
|
||||
|
||||
DOCKFLARE_PASSWORD = os.getenv('DOCKFLARE_PASSWORD')
|
||||
SECRET_KEY = os.getenv('SECRET_KEY')
|
||||
|
||||
GRACE_PERIOD_SECONDS = int(os.getenv('GRACE_PERIOD_SECONDS', 28800))
|
||||
CLEANUP_INTERVAL_SECONDS = int(os.getenv('CLEANUP_INTERVAL_SECONDS', 60))
|
||||
AGENT_STATUS_UPDATE_INTERVAL_SECONDS = int(os.getenv('AGENT_STATUS_UPDATE_INTERVAL_SECONDS', 10))
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -656,30 +656,6 @@ document.querySelectorAll('.tunnel-dns-toggle').forEach(button => {
|
|||
}
|
||||
|
||||
startServerPing();
|
||||
|
||||
try {
|
||||
const securityWarning = document.getElementById('security-warning');
|
||||
if (securityWarning) {
|
||||
|
||||
if (window.sessionStorage && sessionStorage.getItem('security_warning_dismissed') !== 'true') {
|
||||
securityWarning.style.display = 'flex';
|
||||
}
|
||||
|
||||
const dismissButton = document.getElementById('dismiss-security-warning');
|
||||
if (dismissButton) {
|
||||
dismissButton.addEventListener('click', function() {
|
||||
securityWarning.style.display = 'none';
|
||||
|
||||
if (window.sessionStorage) {
|
||||
sessionStorage.setItem('security_warning_dismissed', 'true');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("DockFlare: Could not display the security warning.", e);
|
||||
|
||||
}
|
||||
|
||||
// Universal Cleanup
|
||||
window.addEventListener('beforeunload', function() {
|
||||
|
|
|
|||
|
|
@ -70,17 +70,6 @@
|
|||
</header>
|
||||
|
||||
<main class="mx-auto px-4 sm:px-6 lg:px-8 py-8 sm:py-12 flex-grow w-full max-w-screen-2xl">
|
||||
{% if not (config.DOCKFLARE_PASSWORD and config.SECRET_KEY) %}
|
||||
<div id="security-warning" class="alert alert-error shadow-lg mb-8" style="display: none;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
|
||||
<div>
|
||||
<h3 class="font-bold">Security Warning!</h3>
|
||||
<div class="text-xs">Your DockFlare instance is not password protected. Please set the `DOCKFLARE_PASSWORD` and `SECRET_KEY` environment variables to secure your instance.</div>
|
||||
<a href="https://github.com/ChrispyBacon-dev/DockFlare/releases/tag/v2.0.5" target="_blank" rel="noopener noreferrer" class="link link-hover text-info">View Release Notes for instructions.</a>
|
||||
</div>
|
||||
<button id="dismiss-security-warning" class="btn btn-sm">Okay</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
<h3 id="access_group_modal_title" class="font-bold text-lg mb-6">Create New Access Group</h3>
|
||||
|
||||
<form id="access_group_form" method="POST" class="space-y-6 protocol-aware-form">
|
||||
{% if config.DOCKFLARE_PASSWORD and config.SECRET_KEY %}<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>{% endif %}
|
||||
<input type="hidden" name="original_group_id" id="original_group_id">
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
|
|
|
|||
|
|
@ -53,7 +53,6 @@
|
|||
Edit
|
||||
</button>
|
||||
<form action="{{ url_for('web.delete_access_group', group_id=group_id) }}" method="post" onsubmit="return confirm('Are you sure you want to delete the Access Group \'{{ details.display_name }}\'? This cannot be undone.');" class="protocol-aware-form">
|
||||
{% if config.DOCKFLARE_PASSWORD and config.SECRET_KEY %}<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>{% endif %}
|
||||
<button type="submit" class="btn btn-xs btn-error btn-outline" {{ 'disabled' if group_id in used_group_ids else '' }} title="{{ 'Cannot delete: group is in use' if group_id in used_group_ids else 'Delete Group' }}">
|
||||
Delete
|
||||
</button>
|
||||
|
|
@ -179,7 +178,6 @@
|
|||
</div>
|
||||
|
||||
<form action="{{ url_for('web.restore_state_backup') }}" method="POST" enctype="multipart/form-data" class="protocol-aware-form space-y-3">
|
||||
{% if config.DOCKFLARE_PASSWORD and config.SECRET_KEY %}<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>{% endif %}
|
||||
<input type="file" name="backup_file" class="file-input file-input-bordered w-full" accept=".json" required />
|
||||
<button type="submit" class="btn btn-error w-full" onclick="return confirm('Are you sure you want to overwrite your current state with this backup? This cannot be undone.')">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
|
|
@ -238,12 +236,10 @@
|
|||
<div class="w-full sm:w-auto flex-shrink-0">
|
||||
{% if agent_state.container_status=='running' %}
|
||||
<form action="{{ url_for('web.stop_tunnel_route') }}" method="post" class="protocol-aware-form">
|
||||
{% if config.DOCKFLARE_PASSWORD and config.SECRET_KEY %}<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>{% endif %}
|
||||
<button type="submit" class="btn btn-sm btn-error w-full sm:w-auto">Stop Agent</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form action="{{ url_for('web.start_tunnel_route') }}" method="post" class="protocol-aware-form">
|
||||
{% if config.DOCKFLARE_PASSWORD and config.SECRET_KEY %}<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>{% endif %}
|
||||
<button type="submit" class="btn btn-sm btn-success w-full sm:w-auto">Start Agent</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -139,7 +139,6 @@
|
|||
<button tabindex="0" role="button" class="btn btn-xs btn-info btn-outline" data-hostname="{{ hostname }}">Edit Policy</button>
|
||||
<div tabindex="0" class="dropdown-content z-[50] menu p-3 shadow-xl bg-base-100 dark:bg-base-200 rounded-box w-64 sm:w-72">
|
||||
<form action="{{ url_for('web.ui_update_access_policy', hostname=hostname) }}" method="POST" class="protocol-aware-form space-y-3">
|
||||
{% if config.DOCKFLARE_PASSWORD and config.SECRET_KEY %}<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>{% endif %}
|
||||
<div>
|
||||
<label class="block text-xs font-medium opacity-80 mb-1">Policy Type:</label>
|
||||
<select name="access_policy_type" class="policy-type-select select select-bordered select-xs w-full" data-hostname="{{ hostname }}">
|
||||
|
|
@ -161,7 +160,6 @@
|
|||
</div>
|
||||
{% if details.access_policy_ui_override and details.source != 'manual' %}
|
||||
<form action="{{ url_for('web.revert_access_policy_to_labels', hostname=hostname) }}" method="POST" onsubmit="return confirm('Revert policy for {{ display_hostname }} to be managed by labels/defaults?');" class="protocol-aware-form inline-block">
|
||||
{% if config.DOCKFLARE_PASSWORD and config.SECRET_KEY %}<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>{% endif %}
|
||||
<button type="submit" class="btn btn-xs btn-warning btn-outline" title="Revert to Label/Default Policy">Revert</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
|
@ -186,12 +184,10 @@
|
|||
Edit
|
||||
</button>
|
||||
<form action="{{ url_for('web.ui_delete_manual_rule_route', rule_key_from_url=hostname) }}" method="post" onsubmit="return confirm('Delete manual rule for {{ display_hostname }}?');" class="protocol-aware-form">
|
||||
{% if config.DOCKFLARE_PASSWORD and config.SECRET_KEY %}<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>{% endif %}
|
||||
<button type="submit" class="btn btn-xs btn-error btn-outline">Delete</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form action="{{ url_for('web.force_delete_rule_route', hostname=hostname) }}" method="post" onsubmit="return confirm('Force delete rule and DNS for {{ display_hostname }} immediately?');" class="protocol-aware-form">
|
||||
{% if config.DOCKFLARE_PASSWORD and config.SECRET_KEY %}<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>{% endif %}
|
||||
<button type="submit" class="btn btn-xs btn-error btn-outline">Delete</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
|
@ -226,7 +222,6 @@
|
|||
</form>
|
||||
<h3 class="font-bold text-lg mb-6">Add New Manual Ingress Rule</h3>
|
||||
<form action="{{ url_for('web.ui_add_manual_rule_route') }}" method="POST" class="protocol-aware-form flex flex-col flex-grow min-h-0">
|
||||
{% if config.DOCKFLARE_PASSWORD and config.SECRET_KEY %}<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>{% endif %}
|
||||
<div class="flex-grow overflow-y-auto space-y-6 p-1">
|
||||
<div>
|
||||
<h4 class="text-md font-semibold mb-2">Public Hostname</h4>
|
||||
|
|
@ -377,7 +372,6 @@
|
|||
</form>
|
||||
<h3 class="font-bold text-lg mb-6">Edit Manual Ingress Rule</h3>
|
||||
<form action="{{ url_for('web.ui_edit_manual_rule_route') }}" method="POST" class="protocol-aware-form flex flex-col flex-grow min-h-0" id="edit_manual_rule_form">
|
||||
{% if config.DOCKFLARE_PASSWORD and config.SECRET_KEY %}<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>{% endif %}
|
||||
<div class="flex-grow overflow-y-auto space-y-6 p-1">
|
||||
<input type="hidden" name="original_rule_key" id="edit_original_rule_key" />
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import traceback
|
|||
import json
|
||||
from datetime import datetime, timezone
|
||||
from flask import Blueprint, jsonify, request, current_app, url_for
|
||||
from flask_login import current_user
|
||||
|
||||
from app import config, docker_client, tunnel_state, cloudflared_agent_state, log_queue
|
||||
from app.core.state_manager import managed_rules, state_lock, save_state
|
||||
|
|
@ -55,12 +54,6 @@ from app.core.utils import get_rule_key
|
|||
|
||||
api_v2_bp = Blueprint('api_v2', __name__, url_prefix='/api/v2')
|
||||
|
||||
@api_v2_bp.before_request
|
||||
def before_request_api():
|
||||
if config.DOCKFLARE_PASSWORD and config.SECRET_KEY:
|
||||
if not current_user.is_authenticated:
|
||||
return jsonify({"status": "error", "message": "Unauthorized"}), 401
|
||||
|
||||
def serialize_rule(rule_data):
|
||||
if not rule_data:
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -1,7 +1 @@
|
|||
from flask_wtf import FlaskForm
|
||||
from wtforms import PasswordField, SubmitField
|
||||
from wtforms.validators import DataRequired
|
||||
|
||||
class LoginForm(FlaskForm):
|
||||
password = PasswordField('Password', validators=[DataRequired()])
|
||||
submit = SubmitField('Login')
|
||||
# Placeholder as of right now <3
|
||||
|
|
@ -29,12 +29,10 @@ from app.core import access_manager
|
|||
from urllib.parse import urlparse, urlunparse
|
||||
from flask import (
|
||||
Blueprint, render_template, jsonify, redirect, url_for, request, Response,
|
||||
stream_with_context, current_app, flash, session
|
||||
stream_with_context, current_app
|
||||
)
|
||||
from flask_login import login_user, logout_user, login_required, current_user
|
||||
from werkzeug.security import check_password_hash
|
||||
|
||||
from app import config, docker_client, tunnel_state, cloudflared_agent_state, log_queue, User
|
||||
from app import config, docker_client, tunnel_state, cloudflared_agent_state, log_queue
|
||||
from app.core.state_manager import managed_rules, access_groups, state_lock, save_state, load_state
|
||||
from app.core.tunnel_manager import (
|
||||
start_cloudflared_container,
|
||||
|
|
@ -59,49 +57,18 @@ from app.core.access_manager import (
|
|||
find_cloudflare_access_application_by_hostname
|
||||
)
|
||||
from app.core.reconciler import reconcile_state_threaded
|
||||
from app.core.docker_handler import is_valid_hostname, is_valid_service
|
||||
from app.core.docker_handler import is_valid_hostname, is_valid_service
|
||||
from app.core.utils import get_rule_key
|
||||
from app.web.forms import LoginForm
|
||||
|
||||
bp = Blueprint('web', __name__)
|
||||
|
||||
@bp.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if not config.DOCKFLARE_PASSWORD:
|
||||
return redirect(url_for('web.status_page'))
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for('web.status_page'))
|
||||
|
||||
form = LoginForm()
|
||||
if request.method == 'POST':
|
||||
password = request.form.get('password')
|
||||
if check_password_hash(config.DOCKFLARE_PASSWORD, password):
|
||||
user = User.get('dockflare_user')
|
||||
login_user(user)
|
||||
return redirect(url_for('web.status_page'))
|
||||
else:
|
||||
flash("Invalid password", "error")
|
||||
|
||||
return render_template('login.html', form=form)
|
||||
|
||||
@bp.route('/logout')
|
||||
@login_required
|
||||
def logout():
|
||||
if not config.DOCKFLARE_PASSWORD:
|
||||
return redirect(url_for('web.status_page'))
|
||||
logout_user()
|
||||
return redirect(url_for('web.login'))
|
||||
|
||||
def get_display_token_ui(token_value):
|
||||
if not token_value: return "Not available"
|
||||
return f"{token_value[:5]}...{token_value[-5:]}" if len(token_value) > 10 else "Token (short)"
|
||||
|
||||
@bp.before_app_request
|
||||
def before_request():
|
||||
if config.DOCKFLARE_PASSWORD and config.SECRET_KEY:
|
||||
if not current_user.is_authenticated and request.endpoint and 'static' not in request.endpoint and request.endpoint != 'web.login':
|
||||
return redirect(url_for('web.login'))
|
||||
|
||||
@bp.before_app_request
|
||||
def detect_protocol_bp():
|
||||
|
||||
forwarded_proto = request.headers.get('X-Forwarded-Proto', '').lower()
|
||||
current_app.config['PREFERRED_URL_SCHEME'] = 'https' if forwarded_proto == 'https' or request.is_secure else 'http'
|
||||
|
||||
|
|
@ -113,24 +80,13 @@ def add_security_headers_bp(response):
|
|||
|
||||
is_https = current_app.config.get('PREFERRED_URL_SCHEME') == 'https'
|
||||
|
||||
csp = {
|
||||
"default-src": ["'self'"],
|
||||
# Allow inline scripts for onclick handlers to work
|
||||
"script-src": ["'self'", "'unsafe-inline'"],
|
||||
# Allow styles from rsms.me for the Inter font
|
||||
"style-src": ["'self'", "'unsafe-inline'", "https://rsms.me"],
|
||||
"img-src": ["'self'", "data:"],
|
||||
# Allow fonts from rsms.me
|
||||
"font-src": ["'self'", "https://rsms.me"],
|
||||
"connect-src": ["'self'"],
|
||||
"frame-src": ["'none'"]
|
||||
}
|
||||
|
||||
if is_https:
|
||||
csp["upgrade-insecure-requests"] = []
|
||||
|
||||
csp_string = "; ".join([f"{key} {' '.join(value)}" if value else key for key, value in csp.items()])
|
||||
response.headers['Content-Security-Policy'] = csp_string
|
||||
csp = ("default-src * 'unsafe-inline' 'unsafe-eval' data: blob:; "
|
||||
"script-src * 'unsafe-inline' 'unsafe-eval'; "
|
||||
"style-src * 'unsafe-inline'; "
|
||||
"img-src * data: blob:; font-src * data:; "
|
||||
"connect-src *; frame-src *; ")
|
||||
if is_https: csp += "upgrade-insecure-requests; "
|
||||
response.headers['Content-Security-Policy'] = csp
|
||||
response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
|
||||
if is_https: response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
|
||||
|
||||
|
|
@ -150,8 +106,7 @@ def inject_protocol_bp():
|
|||
'base_url': base_url,
|
||||
'host': request.host,
|
||||
'request_scheme': request.scheme,
|
||||
'app_version': config.APP_VERSION,
|
||||
'config': config
|
||||
'app_version': config.APP_VERSION
|
||||
}
|
||||
|
||||
@bp.route('/')
|
||||
|
|
|
|||
|
|
@ -14,11 +14,8 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
# dockflare/requirements.txt
|
||||
requests==2.31.0
|
||||
docker==6.1.3
|
||||
python-dotenv==1.0.0
|
||||
Flask==2.3.3
|
||||
Flask-Login==0.6.2
|
||||
Flask-WTF==1.1.1
|
||||
waitress==2.1.2
|
||||
werkzeug==2.3.7
|
||||
requests
|
||||
docker
|
||||
python-dotenv
|
||||
Flask
|
||||
waitress
|
||||
Loading…
Add table
Add a link
Reference in a new issue