add minifying, cache busting, and auto prod and dev mode
This commit is contained in:
@ -9,6 +9,13 @@ RUN pip install --no-cache-dir -r requirements.txt
|
|||||||
# Copy all application files
|
# Copy all application files
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
# Build assets for production
|
||||||
|
RUN python build_assets.py
|
||||||
|
|
||||||
|
# Set production environment
|
||||||
|
ENV FLASK_ENV=production
|
||||||
|
ENV FLASK_DEBUG=0
|
||||||
|
|
||||||
# Ensure proper permissions
|
# Ensure proper permissions
|
||||||
RUN chmod +x app.py
|
RUN chmod +x app.py
|
||||||
|
|
||||||
|
|||||||
50
Makefile
Normal file
50
Makefile
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
.PHONY: help build clean install run test deploy
|
||||||
|
|
||||||
|
help: ## Show this help message
|
||||||
|
@echo "Available commands:"
|
||||||
|
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
||||||
|
|
||||||
|
install: ## Install Python dependencies
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
build: ## Build minified assets with cache busting
|
||||||
|
python build_assets.py
|
||||||
|
|
||||||
|
clean: ## Clean old cache-busted assets
|
||||||
|
python build_assets.py clean
|
||||||
|
|
||||||
|
run: ## Run the Flask development server
|
||||||
|
export FLASK_ENV=development && export FLASK_DEBUG=1 && python app.py
|
||||||
|
|
||||||
|
run-prod: ## Run the Flask production server
|
||||||
|
export FLASK_ENV=production && export FLASK_DEBUG=0 && python app.py
|
||||||
|
|
||||||
|
test: ## Run tests (placeholder)
|
||||||
|
@echo "No tests configured yet"
|
||||||
|
|
||||||
|
deploy: clean build ## Deploy: clean old assets and build new ones
|
||||||
|
@echo "Deployment completed!"
|
||||||
|
|
||||||
|
docker-build: ## Build Docker image
|
||||||
|
docker build -t kobelly .
|
||||||
|
|
||||||
|
docker-run: ## Run Docker container (production)
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
docker-run-dev: ## Run Docker container (development)
|
||||||
|
docker-compose -f docker-compose.dev.yml up -d
|
||||||
|
|
||||||
|
docker-stop: ## Stop Docker container
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
docker-stop-dev: ## Stop Docker container (development)
|
||||||
|
docker-compose -f docker-compose.dev.yml down
|
||||||
|
|
||||||
|
docker-logs: ## Show Docker logs
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
docker-logs-dev: ## Show Docker logs (development)
|
||||||
|
docker-compose -f docker-compose.dev.yml logs -f
|
||||||
|
|
||||||
|
dev-setup: install build ## Setup development environment
|
||||||
|
@echo "Development environment ready!"
|
||||||
116
README.md
116
README.md
@ -8,8 +8,9 @@ A professional Flask-based website for promoting small business web development
|
|||||||
- 📱 **Responsive Design**: Modern, mobile-friendly interface
|
- 📱 **Responsive Design**: Modern, mobile-friendly interface
|
||||||
- 🎨 **Professional UI**: Clean, modern design with Bootstrap 5
|
- 🎨 **Professional UI**: Clean, modern design with Bootstrap 5
|
||||||
- 🐳 **Docker Support**: Easy containerization and deployment
|
- 🐳 **Docker Support**: Easy containerization and deployment
|
||||||
- ⚡ **Fast Performance**: Optimized for speed and performance
|
- ⚡ **Fast Performance**: Optimized for speed and performance with minified assets
|
||||||
- 🔧 **No Database Required**: Simple, lightweight setup
|
- 🔧 **No Database Required**: Simple, lightweight setup
|
||||||
|
- 🚀 **Asset Optimization**: CSS/JS minification with cache busting
|
||||||
|
|
||||||
## Pages
|
## Pages
|
||||||
|
|
||||||
@ -27,16 +28,45 @@ A professional Flask-based website for promoting small business web development
|
|||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Run the application:**
|
2. **Build assets (optional, for production-like experience):**
|
||||||
|
```bash
|
||||||
|
make build
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Run the application:**
|
||||||
```bash
|
```bash
|
||||||
python app.py
|
python app.py
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Access the website:**
|
4. **Access the website:**
|
||||||
Open your browser and go to `http://localhost:5000`
|
Open your browser and go to `http://localhost:5000`
|
||||||
|
|
||||||
|
### Using Make Commands
|
||||||
|
|
||||||
|
The project includes a Makefile for common tasks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make help # Show all available commands
|
||||||
|
make install # Install dependencies
|
||||||
|
make build # Build minified assets
|
||||||
|
make clean # Clean old assets
|
||||||
|
make run # Run development server
|
||||||
|
make run-prod # Run production server
|
||||||
|
make deploy # Clean and build assets
|
||||||
|
make dev-setup # Setup development environment
|
||||||
|
|
||||||
|
# Docker commands
|
||||||
|
make docker-run # Run production Docker container
|
||||||
|
make docker-run-dev # Run development Docker container
|
||||||
|
make docker-stop # Stop production container
|
||||||
|
make docker-stop-dev # Stop development container
|
||||||
|
make docker-logs # View production logs
|
||||||
|
make docker-logs-dev # View development logs
|
||||||
|
```
|
||||||
|
|
||||||
### Docker Deployment
|
### Docker Deployment
|
||||||
|
|
||||||
|
#### Production Mode (Default)
|
||||||
1. **Build and run with Docker Compose:**
|
1. **Build and run with Docker Compose:**
|
||||||
```bash
|
```bash
|
||||||
docker-compose up --build
|
docker-compose up --build
|
||||||
@ -48,6 +78,25 @@ A professional Flask-based website for promoting small business web development
|
|||||||
docker run -p 5000:5000 kobelly-website
|
docker run -p 5000:5000 kobelly-website
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Development Mode
|
||||||
|
For local development with Docker (with live reload and debug mode):
|
||||||
|
|
||||||
|
1. **Build and run development container:**
|
||||||
|
```bash
|
||||||
|
docker-compose -f docker-compose.dev.yml up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Or use Make commands:**
|
||||||
|
```bash
|
||||||
|
make docker-run-dev # Start development container
|
||||||
|
make docker-logs-dev # View development logs
|
||||||
|
make docker-stop-dev # Stop development container
|
||||||
|
```
|
||||||
|
|
||||||
|
**Development vs Production:**
|
||||||
|
- **Development**: Debug mode enabled, live code reloading, unminified assets
|
||||||
|
- **Production**: Debug disabled, minified assets, optimized performance
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -56,7 +105,17 @@ Kobelly/
|
|||||||
├── requirements.txt # Python dependencies
|
├── requirements.txt # Python dependencies
|
||||||
├── Dockerfile # Docker configuration
|
├── Dockerfile # Docker configuration
|
||||||
├── docker-compose.yml # Docker Compose configuration
|
├── docker-compose.yml # Docker Compose configuration
|
||||||
|
├── Makefile # Build and deployment commands
|
||||||
|
├── build_assets.py # Asset minification script
|
||||||
├── babel.cfg # Babel configuration for translations
|
├── babel.cfg # Babel configuration for translations
|
||||||
|
├── static/ # Static assets
|
||||||
|
│ ├── css/ # CSS files
|
||||||
|
│ │ ├── main.css # Main stylesheet
|
||||||
|
│ │ └── main.min.css # Minified CSS (generated)
|
||||||
|
│ ├── js/ # JavaScript files
|
||||||
|
│ │ ├── main.js # Main script
|
||||||
|
│ │ └── main.min.js # Minified JS (generated)
|
||||||
|
│ └── images/ # Images and icons
|
||||||
├── templates/ # HTML templates
|
├── templates/ # HTML templates
|
||||||
│ ├── base.html # Base template with navigation
|
│ ├── base.html # Base template with navigation
|
||||||
│ ├── index.html # Homepage
|
│ ├── index.html # Homepage
|
||||||
@ -111,12 +170,62 @@ Users can switch languages using the language selector in the navigation bar. Th
|
|||||||
pybabel compile -d translations
|
pybabel compile -d translations
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Asset Optimization
|
||||||
|
|
||||||
|
The project includes automatic CSS and JavaScript minification with cache busting for optimal performance.
|
||||||
|
|
||||||
|
### Asset Building
|
||||||
|
|
||||||
|
**Development Mode:**
|
||||||
|
- Uses unminified assets for easier debugging
|
||||||
|
- Assets are served directly from source files
|
||||||
|
|
||||||
|
**Production Mode:**
|
||||||
|
- Automatically serves minified assets
|
||||||
|
- Cache busting prevents browser caching issues
|
||||||
|
- Assets are optimized for faster loading
|
||||||
|
|
||||||
|
### Building Assets
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build minified assets
|
||||||
|
make build
|
||||||
|
|
||||||
|
# Clean old cache-busted assets
|
||||||
|
make clean
|
||||||
|
|
||||||
|
# Full deployment (clean + build)
|
||||||
|
make deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Asset Files
|
||||||
|
|
||||||
|
- **CSS**: `static/css/main.css` → `static/css/main.min.css`
|
||||||
|
- **JavaScript**: `static/js/main.js` → `static/js/main.min.js`
|
||||||
|
|
||||||
|
The build process creates cache-busted versions with MD5 hashes in the filename to ensure browsers always load the latest version.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
### Environment Variables
|
### Environment Variables
|
||||||
|
|
||||||
- `SECRET_KEY`: Secret key for Flask sessions (default: 'your-secret-key-change-in-production')
|
- `SECRET_KEY`: Secret key for Flask sessions (default: 'your-secret-key-change-in-production')
|
||||||
- `FLASK_ENV`: Flask environment (development/production)
|
- `FLASK_ENV`: Flask environment (development/production)
|
||||||
|
- `FLASK_DEBUG`: Debug mode (0=disabled, 1=enabled, defaults to 1 in development)
|
||||||
|
|
||||||
|
**Environment Modes:**
|
||||||
|
|
||||||
|
**Development (Local):**
|
||||||
|
```bash
|
||||||
|
FLASK_ENV=development
|
||||||
|
FLASK_DEBUG=1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Production (Docker):**
|
||||||
|
```bash
|
||||||
|
FLASK_ENV=production
|
||||||
|
FLASK_DEBUG=0
|
||||||
|
```
|
||||||
|
|
||||||
### Production Deployment
|
### Production Deployment
|
||||||
|
|
||||||
@ -135,6 +244,7 @@ For production deployment:
|
|||||||
- **Internationalization**: Flask-Babel
|
- **Internationalization**: Flask-Babel
|
||||||
- **Containerization**: Docker, Docker Compose
|
- **Containerization**: Docker, Docker Compose
|
||||||
- **Styling**: Custom CSS with CSS variables
|
- **Styling**: Custom CSS with CSS variables
|
||||||
|
- **Asset Management**: Flask-Assets with CSS/JS minification
|
||||||
|
|
||||||
## Browser Support
|
## Browser Support
|
||||||
|
|
||||||
|
|||||||
42
app.py
42
app.py
@ -1,4 +1,5 @@
|
|||||||
from flask import Flask, render_template, request, session, redirect, url_for, make_response
|
from flask import Flask, render_template, request, session, redirect, url_for, make_response
|
||||||
|
from flask_assets import Environment, Bundle
|
||||||
from translation_manager import init_app, translate, get_current_language, create_language_selector, set_language
|
from translation_manager import init_app, translate, get_current_language, create_language_selector, set_language
|
||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@ -7,6 +8,29 @@ from xml.etree import ElementTree as ET
|
|||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.secret_key = os.environ.get('SECRET_KEY', 'your-secret-key-change-in-production')
|
app.secret_key = os.environ.get('SECRET_KEY', 'your-secret-key-change-in-production')
|
||||||
|
|
||||||
|
# Initialize Flask-Assets
|
||||||
|
assets = Environment(app)
|
||||||
|
assets.url = app.static_url_path
|
||||||
|
|
||||||
|
# Configure assets for production
|
||||||
|
if not app.debug:
|
||||||
|
assets.auto_build = False
|
||||||
|
assets.cache = True
|
||||||
|
assets.manifest = 'file'
|
||||||
|
|
||||||
|
# Register asset bundles
|
||||||
|
css_bundle = assets.register('css_all', Bundle(
|
||||||
|
'css/main.css',
|
||||||
|
filters='cssmin',
|
||||||
|
output='css/main.min.css'
|
||||||
|
))
|
||||||
|
|
||||||
|
js_bundle = assets.register('js_all', Bundle(
|
||||||
|
'js/main.js',
|
||||||
|
filters='jsmin',
|
||||||
|
output='js/main.min.js'
|
||||||
|
))
|
||||||
|
|
||||||
# Initialize the translation system
|
# Initialize the translation system
|
||||||
init_app(app)
|
init_app(app)
|
||||||
|
|
||||||
@ -90,5 +114,21 @@ def set_language_route(language):
|
|||||||
return redirect(request.referrer or url_for('index'))
|
return redirect(request.referrer or url_for('index'))
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
@app.route('/build-assets')
|
||||||
|
def build_assets():
|
||||||
|
"""Build minified assets with cache busting"""
|
||||||
|
if app.debug:
|
||||||
|
try:
|
||||||
|
# Build assets
|
||||||
|
css_bundle.build()
|
||||||
|
js_bundle.build()
|
||||||
|
return {'status': 'success', 'message': 'Assets built successfully'}
|
||||||
|
except Exception as e:
|
||||||
|
return {'status': 'error', 'message': str(e)}, 500
|
||||||
|
else:
|
||||||
|
return {'status': 'error', 'message': 'Asset building only available in debug mode'}, 403
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(debug=True, host='0.0.0.0', port=5000)
|
# Check environment variables for debug mode
|
||||||
|
debug_mode = os.environ.get('FLASK_DEBUG', '1') == '1' and os.environ.get('FLASK_ENV') != 'production'
|
||||||
|
app.run(debug=debug_mode, host='0.0.0.0', port=5000)
|
||||||
143
build_assets.py
Normal file
143
build_assets.py
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Asset build script for Kobelly Web Solutions
|
||||||
|
Handles CSS and JS minification with cache busting
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import hashlib
|
||||||
|
import shutil
|
||||||
|
from datetime import datetime
|
||||||
|
from cssmin import cssmin
|
||||||
|
from jsmin import jsmin
|
||||||
|
|
||||||
|
def get_file_hash(filepath):
|
||||||
|
"""Generate MD5 hash of file content for cache busting"""
|
||||||
|
with open(filepath, 'rb') as f:
|
||||||
|
return hashlib.md5(f.read()).hexdigest()[:8]
|
||||||
|
|
||||||
|
def minify_css(input_file, output_file):
|
||||||
|
"""Minify CSS file"""
|
||||||
|
try:
|
||||||
|
with open(input_file, 'r', encoding='utf-8') as f:
|
||||||
|
css_content = f.read()
|
||||||
|
|
||||||
|
minified_css = cssmin(css_content)
|
||||||
|
|
||||||
|
with open(output_file, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(minified_css)
|
||||||
|
|
||||||
|
print(f"✓ CSS minified: {input_file} → {output_file}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Error minifying CSS {input_file}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def minify_js(input_file, output_file):
|
||||||
|
"""Minify JavaScript file"""
|
||||||
|
try:
|
||||||
|
with open(input_file, 'r', encoding='utf-8') as f:
|
||||||
|
js_content = f.read()
|
||||||
|
|
||||||
|
minified_js = jsmin(js_content)
|
||||||
|
|
||||||
|
with open(output_file, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(minified_js)
|
||||||
|
|
||||||
|
print(f"✓ JS minified: {input_file} → {output_file}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Error minifying JS {input_file}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def create_cache_busted_assets():
|
||||||
|
"""Create cache-busted versions of assets"""
|
||||||
|
static_dir = 'static'
|
||||||
|
css_dir = os.path.join(static_dir, 'css')
|
||||||
|
js_dir = os.path.join(static_dir, 'js')
|
||||||
|
|
||||||
|
# Ensure directories exist
|
||||||
|
os.makedirs(css_dir, exist_ok=True)
|
||||||
|
os.makedirs(js_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# CSS files
|
||||||
|
css_files = [
|
||||||
|
('main.css', 'main.min.css')
|
||||||
|
]
|
||||||
|
|
||||||
|
# JS files
|
||||||
|
js_files = [
|
||||||
|
('main.js', 'main.min.js')
|
||||||
|
]
|
||||||
|
|
||||||
|
print("🔨 Building assets...")
|
||||||
|
print(f"📅 Build time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Process CSS files
|
||||||
|
for input_file, output_file in css_files:
|
||||||
|
input_path = os.path.join(css_dir, input_file)
|
||||||
|
output_path = os.path.join(css_dir, output_file)
|
||||||
|
|
||||||
|
if os.path.exists(input_path):
|
||||||
|
if minify_css(input_path, output_path):
|
||||||
|
# Generate cache-busted filename
|
||||||
|
file_hash = get_file_hash(output_path)
|
||||||
|
cache_busted_name = f"main.{file_hash}.min.css"
|
||||||
|
cache_busted_path = os.path.join(css_dir, cache_busted_name)
|
||||||
|
|
||||||
|
# Copy minified file to cache-busted version
|
||||||
|
shutil.copy2(output_path, cache_busted_path)
|
||||||
|
print(f"✓ Cache-busted CSS created: {cache_busted_name}")
|
||||||
|
else:
|
||||||
|
print(f"⚠️ CSS file not found: {input_path}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Process JS files
|
||||||
|
for input_file, output_file in js_files:
|
||||||
|
input_path = os.path.join(js_dir, input_file)
|
||||||
|
output_path = os.path.join(js_dir, output_file)
|
||||||
|
|
||||||
|
if os.path.exists(input_path):
|
||||||
|
if minify_js(input_path, output_path):
|
||||||
|
# Generate cache-busted filename
|
||||||
|
file_hash = get_file_hash(output_path)
|
||||||
|
cache_busted_name = f"main.{file_hash}.min.js"
|
||||||
|
cache_busted_path = os.path.join(js_dir, cache_busted_name)
|
||||||
|
|
||||||
|
# Copy minified file to cache-busted version
|
||||||
|
shutil.copy2(output_path, cache_busted_path)
|
||||||
|
print(f"✓ Cache-busted JS created: {cache_busted_name}")
|
||||||
|
else:
|
||||||
|
print(f"⚠️ JS file not found: {input_path}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("🎉 Asset build completed!")
|
||||||
|
|
||||||
|
def clean_old_assets():
|
||||||
|
"""Clean old cache-busted assets"""
|
||||||
|
static_dir = 'static'
|
||||||
|
css_dir = os.path.join(static_dir, 'css')
|
||||||
|
js_dir = os.path.join(static_dir, 'js')
|
||||||
|
|
||||||
|
# Remove old cache-busted files
|
||||||
|
for directory in [css_dir, js_dir]:
|
||||||
|
if os.path.exists(directory):
|
||||||
|
for filename in os.listdir(directory):
|
||||||
|
if filename.startswith('main.') and filename.endswith('.min.css') or filename.endswith('.min.js'):
|
||||||
|
if not filename in ['main.min.css', 'main.min.js']: # Keep the base minified files
|
||||||
|
filepath = os.path.join(directory, filename)
|
||||||
|
os.remove(filepath)
|
||||||
|
print(f"🗑️ Removed old asset: {filename}")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if len(sys.argv) > 1 and sys.argv[1] == 'clean':
|
||||||
|
print("🧹 Cleaning old assets...")
|
||||||
|
clean_old_assets()
|
||||||
|
print("✓ Cleanup completed!")
|
||||||
|
else:
|
||||||
|
clean_old_assets()
|
||||||
|
create_cache_busted_assets()
|
||||||
23
docker-compose.dev.yml
Normal file
23
docker-compose.dev.yml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "10332:5000"
|
||||||
|
environment:
|
||||||
|
- FLASK_ENV=development
|
||||||
|
- FLASK_DEBUG=1
|
||||||
|
- SECRET_KEY=dev-secret-key-change-in-production
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
- /app/static/css/main.min.css
|
||||||
|
- /app/static/js/main.min.js
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
- nginx-proxy-manager_default
|
||||||
|
|
||||||
|
networks:
|
||||||
|
nginx-proxy-manager_default:
|
||||||
|
external: true
|
||||||
@ -6,8 +6,9 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "10332:5000"
|
- "10332:5000"
|
||||||
environment:
|
environment:
|
||||||
- FLASK_ENV=development
|
- FLASK_ENV=production
|
||||||
- SECRET_KEY=your-secret-key-change-in-production
|
- SECRET_KEY=your-secret-key-change-in-production
|
||||||
|
- FLASK_DEBUG=0
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
- default
|
- default
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
Flask==2.3.3
|
Flask==2.3.3
|
||||||
Werkzeug==2.3.7
|
Werkzeug==2.3.7
|
||||||
|
Flask-Assets==2.1.0
|
||||||
|
cssmin==0.2.0
|
||||||
|
jsmin==3.0.1
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
blinker==1.6.3
|
blinker==1.6.3
|
||||||
# Translation system - JSON-based, no compilation needed
|
# Translation system - JSON-based, no compilation needed
|
||||||
@ -37,7 +37,9 @@
|
|||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
|
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
|
{% assets "css_all" %}
|
||||||
|
<link rel="stylesheet" href="{{ ASSET_URL }}">
|
||||||
|
{% endassets %}
|
||||||
|
|
||||||
<!-- Structured Data (JSON-LD) -->
|
<!-- Structured Data (JSON-LD) -->
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
@ -187,6 +189,8 @@
|
|||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
|
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
|
||||||
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
|
{% assets "js_all" %}
|
||||||
|
<script src="{{ ASSET_URL }}"></script>
|
||||||
|
{% endassets %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user