mirror of
https://github.com/kevinveenbirkenbach/homepage.veen.world.git
synced 2025-09-09 11:17:12 +02:00
Compare commits
11 Commits
35bfeeb51e
...
20b6c731b8
Author | SHA1 | Date | |
---|---|---|---|
20b6c731b8 | |||
2f63009c31 | |||
f0d4206731 | |||
b8aad8b695 | |||
697696347f | |||
d6389157ec | |||
25dbc3f331 | |||
bb8799eb8a | |||
86fd72b623 | |||
9c24a8658f | |||
5fc19f6ccb |
@@ -11,11 +11,4 @@ RUN pip install --no-cache-dir -r requirements.txt
|
||||
# Copy application code
|
||||
COPY app/ .
|
||||
|
||||
# Set default port environment variable
|
||||
ENV PORT=5000
|
||||
|
||||
# Expose port (optional for documentation)
|
||||
EXPOSE ${PORT}
|
||||
|
||||
# Start command using shell to allow env substitution
|
||||
CMD ["sh", "-c", "exec python app.py --port=${PORT}"]
|
||||
CMD ["python", "app.py"]
|
||||
|
12
app/app.py
12
app/app.py
@@ -4,6 +4,12 @@ import yaml
|
||||
from utils.configuration_resolver import ConfigurationResolver
|
||||
from utils.cache_manager import CacheManager
|
||||
from utils.compute_card_classes import compute_card_classes
|
||||
import logging
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
FLASK_ENV = os.getenv("FLASK_ENV", "production")
|
||||
FLASK_PORT = int(os.getenv("PORT", 5000))
|
||||
print(f"🔧 Starting app on port {FLASK_PORT}, FLASK_ENV={FLASK_ENV}")
|
||||
|
||||
|
||||
# Initialize the CacheManager
|
||||
cache_manager = CacheManager()
|
||||
@@ -31,9 +37,6 @@ def cache_icons_and_logos(app):
|
||||
app.config["platform"]["favicon"]["cache"] = cache_manager.cache_file(app.config["platform"]["favicon"]["source"])
|
||||
app.config["platform"]["logo"]["cache"] = cache_manager.cache_file(app.config["platform"]["logo"]["source"])
|
||||
|
||||
# Get the environment variable FLASK_ENV or set a default value
|
||||
FLASK_ENV = os.getenv("FLASK_ENV", "production")
|
||||
|
||||
# Initialize Flask app
|
||||
app = Flask(__name__)
|
||||
|
||||
@@ -64,5 +67,4 @@ def index():
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
port = int(os.getenv("PORT", 5000))
|
||||
app.run(debug=(FLASK_ENV == "development"), host="0.0.0.0", port=port)
|
||||
app.run(debug=(FLASK_ENV == "development"), host="0.0.0.0", port=FLASK_PORT)
|
||||
|
@@ -647,4 +647,36 @@ navigation:
|
||||
class: fa-solid fa-scale-balanced
|
||||
url: https://s.veen.world/imprint
|
||||
iframe: true
|
||||
|
||||
- name: Settings
|
||||
description: Application settings
|
||||
icon:
|
||||
class: fa-solid fa-cog
|
||||
children:
|
||||
- name: Toggle Fullscreen
|
||||
description: Enter or exit fullscreen mode
|
||||
icon:
|
||||
class: fa-solid fa-expand-arrows-alt
|
||||
onclick: "toggleFullscreen()"
|
||||
- name: Toggle Full Width
|
||||
description: Switch between normal and full-width layout
|
||||
icon:
|
||||
class: fa-solid fa-arrows-left-right
|
||||
onclick: "setFullWidth(!initFullWidthFromUrl())"
|
||||
- name: Open in new tab
|
||||
description: Open the currently embedded iframe URL in a fresh browser tab
|
||||
icon:
|
||||
class: fa-solid fa-up-right-from-square
|
||||
onclick: openIframeInNewTab()
|
||||
- name: Print
|
||||
description: Print the current view
|
||||
icon:
|
||||
class: fa-solid fa-print
|
||||
onclick: window.print()
|
||||
- name: Zoom +
|
||||
icon:
|
||||
class: fa-solid fa-search-plus
|
||||
onclick: zoomPage(1.1)
|
||||
- name: Zoom –
|
||||
icon:
|
||||
class: fa-solid fa-search-minus
|
||||
onclick: zoomPage(0.9)
|
||||
|
86
app/static/js/fullscreen.js
Normal file
86
app/static/js/fullscreen.js
Normal file
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Add or remove the `fullscreen=1` URL parameter.
|
||||
* @param {boolean} enabled
|
||||
*/
|
||||
function updateUrlFullscreen(enabled) {
|
||||
var url = new URL(window.location);
|
||||
if (enabled) url.searchParams.set('fullscreen', '1');
|
||||
else url.searchParams.delete('fullscreen');
|
||||
window.history.replaceState({}, '', url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter fullscreen: hide header/footer, enable full width, recalc scroll,
|
||||
* set both URL params, update button.
|
||||
*/
|
||||
function enterFullscreen() {
|
||||
document.querySelectorAll('header, footer')
|
||||
.forEach(function(el){ el.style.display = 'none'; });
|
||||
setFullWidth(true);
|
||||
updateUrlFullscreen(true);
|
||||
|
||||
if (typeof adjustScrollContainerHeight === 'function') adjustScrollContainerHeight();
|
||||
if (typeof updateCustomScrollbar === 'function') updateCustomScrollbar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit fullscreen: show header/footer, disable full width, recalc scroll,
|
||||
* clear both URL params, update button.
|
||||
*/
|
||||
function exitFullscreen() {
|
||||
document.querySelectorAll('header, footer')
|
||||
.forEach(function(el){ el.style.display = ''; });
|
||||
setFullWidth(false);
|
||||
updateUrlFullscreen(false);
|
||||
|
||||
if (typeof adjustScrollContainerHeight === 'function') adjustScrollContainerHeight();
|
||||
if (typeof updateCustomScrollbar === 'function') updateCustomScrollbar();
|
||||
if (typeof syncIframeHeight === 'function') syncIframeHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle between enter and exit fullscreen.
|
||||
*/
|
||||
function toggleFullscreen() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const isFull = params.get('fullscreen') === '1';
|
||||
|
||||
if (isFull) exitFullscreen();
|
||||
else enterFullscreen();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read `fullscreen` flag from URL on load.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function initFullscreenFromUrl() {
|
||||
return new URLSearchParams(window.location.search).get('fullscreen') === '1';
|
||||
}
|
||||
|
||||
// On page load: apply fullwidth & fullscreen flags
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
// first fullwidth
|
||||
var wasFullWidth = initFullWidthFromUrl();
|
||||
setFullWidth(wasFullWidth);
|
||||
|
||||
// now fullscreen
|
||||
if (initFullscreenFromUrl()) {
|
||||
enterFullscreen();
|
||||
}
|
||||
});
|
||||
|
||||
// Mirror native F11/fullscreen API events
|
||||
document.addEventListener('fullscreenchange', function() {
|
||||
if (document.fullscreenElement) enterFullscreen();
|
||||
else exitFullscreen();
|
||||
});
|
||||
window.addEventListener('resize', function() {
|
||||
var isUiFs = Math.abs(window.innerHeight - screen.height) < 2;
|
||||
if (isUiFs) enterFullscreen();
|
||||
else exitFullscreen();
|
||||
});
|
||||
|
||||
// Expose globally
|
||||
window.fullscreen = enterFullscreen;
|
||||
window.exitFullscreen = exitFullscreen;
|
||||
window.toggleFullscreen = toggleFullscreen;
|
42
app/static/js/fullwidth.js
Normal file
42
app/static/js/fullwidth.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Toggles the .container class between .container and .container-fluid.
|
||||
* @param {boolean} enabled – true = full width, false = normal.
|
||||
*/
|
||||
function setFullWidth(enabled) {
|
||||
var el = document.querySelector('.container, .container-fluid');
|
||||
if (!el) return;
|
||||
console.log(el)
|
||||
if (enabled) {
|
||||
el.classList.replace('container', 'container-fluid');
|
||||
updateUrlFullWidth(true)
|
||||
} else {
|
||||
el.classList.replace('container-fluid', 'container');
|
||||
updateUrlFullWidth(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the URL parameter `fullwidth` and applies full width if it's set.
|
||||
* @returns {boolean} – current full‐width state
|
||||
*/
|
||||
function initFullWidthFromUrl() {
|
||||
var isFull = new URLSearchParams(window.location.search).get('fullwidth') === '1';
|
||||
setFullWidth(isFull);
|
||||
return isFull;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or removes the `fullwidth=1` URL parameter.
|
||||
* @param {boolean} enabled
|
||||
*/
|
||||
function updateUrlFullWidth(enabled) {
|
||||
var url = new URL(window.location);
|
||||
if (enabled) url.searchParams.set('fullwidth', '1');
|
||||
else url.searchParams.delete('fullwidth');
|
||||
window.history.replaceState({}, '', url);
|
||||
}
|
||||
|
||||
// Expose globally
|
||||
window.setFullWidth = setFullWidth;
|
||||
window.initFullWidthFromUrl = initFullWidthFromUrl;
|
||||
window.updateUrlFullWidth = updateUrlFullWidth;
|
@@ -1,71 +1,82 @@
|
||||
// Global variables to store elements and original state
|
||||
let mainElement, originalContent, originalMainStyle, container, customScrollbar;
|
||||
let mainElement, originalContent, originalMainStyle, container, customScrollbar, scrollbarContainer;
|
||||
|
||||
// Function to open a URL in an iframe using global variables
|
||||
// Synchronize the height of the iframe to match the scroll-container or main element
|
||||
function syncIframeHeight() {
|
||||
const iframe = mainElement.querySelector("iframe");
|
||||
if (iframe) {
|
||||
console.log("Setting iframe height based on scroll-container inline styles...");
|
||||
if (scrollbarContainer) {
|
||||
// Prefer inline height, otherwise inline max-height
|
||||
const inlineHeight = scrollbarContainer.style.height;
|
||||
const inlineMax = scrollbarContainer.style.maxHeight;
|
||||
const target = inlineHeight || inlineMax;
|
||||
if (target) {
|
||||
console.log("Using scroll-container inline style:", target);
|
||||
iframe.style.height = target;
|
||||
} else {
|
||||
console.warn("No inline height or max-height set on scroll-container. Using main element height instead.");
|
||||
iframe.style.height = mainElement.style.height;
|
||||
}
|
||||
} else {
|
||||
console.log("No scroll-container found. Using main element height:", mainElement.style.height);
|
||||
iframe.style.height = mainElement.style.height;
|
||||
}
|
||||
} else {
|
||||
console.log("No iframe to resize.");
|
||||
}
|
||||
}
|
||||
|
||||
// Function to open a URL in an iframe
|
||||
function openIframe(url) {
|
||||
// Set a fixed height for the main element if not already set
|
||||
if (!mainElement.style.height) {
|
||||
mainElement.style.height = `${mainElement.clientHeight}px`;
|
||||
// Hide the container (and its scroll-container) so the iframe can appear in its place
|
||||
if (scrollbarContainer) {
|
||||
scrollbarContainer.style.display = 'none';
|
||||
}
|
||||
|
||||
// Replace the container class with container-fluid if not already applied
|
||||
if (container && !container.classList.contains("container-fluid")) {
|
||||
container.classList.replace("container", "container-fluid");
|
||||
}
|
||||
|
||||
// Hide the custom scrollbar
|
||||
// Hide any custom scrollbar element if present
|
||||
if (customScrollbar) {
|
||||
customScrollbar.style.display = "none";
|
||||
customScrollbar.style.display = 'none';
|
||||
}
|
||||
|
||||
// Check if an iframe already exists in the main element
|
||||
// Create or retrieve the iframe in the main element
|
||||
let iframe = mainElement.querySelector("iframe");
|
||||
if (!iframe) {
|
||||
// Create a new iframe element
|
||||
iframe = document.createElement("iframe");
|
||||
iframe.width = "100%";
|
||||
iframe.style.border = "none";
|
||||
iframe.style.height = mainElement.style.height; // Apply fixed height
|
||||
iframe.style.overflow = "auto"; // Enable internal scrollbar
|
||||
iframe.scrolling = "auto"; // Ensure scrollability
|
||||
|
||||
// Clear the main content before appending the iframe
|
||||
mainElement.innerHTML = "";
|
||||
iframe.style.overflow = "auto"; // Enable internal scrolling
|
||||
iframe.scrolling = "auto";
|
||||
mainElement.appendChild(iframe);
|
||||
syncIframeHeight();
|
||||
}
|
||||
|
||||
// Set the URL of the iframe
|
||||
// Set the iframe's source URL
|
||||
iframe.src = url;
|
||||
|
||||
// Push the new URL state without reloading the page
|
||||
const newUrl = new URL(window.location);
|
||||
newUrl.searchParams.set('iframe', url);
|
||||
window.history.pushState({ iframe: url }, '', newUrl);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
// Initialize global variables
|
||||
mainElement = document.querySelector("main");
|
||||
originalContent = mainElement.innerHTML;
|
||||
originalMainStyle = mainElement.getAttribute("style"); // might be null if no inline style exists
|
||||
// Function to restore the original content and show the container again
|
||||
function restoreOriginal() {
|
||||
// Remove the iframe from the DOM
|
||||
const iframe = mainElement.querySelector("iframe");
|
||||
if (iframe) {
|
||||
iframe.remove();
|
||||
}
|
||||
|
||||
container = document.querySelector(".container");
|
||||
customScrollbar = document.getElementById("custom-scrollbar");
|
||||
// Show the original container
|
||||
if (scrollbarContainer) {
|
||||
scrollbarContainer.style.display = '';
|
||||
}
|
||||
|
||||
// Get all links that should open in an iframe
|
||||
const links = document.querySelectorAll(".iframe-link");
|
||||
|
||||
// Add click event listener to each iframe link
|
||||
links.forEach(link => {
|
||||
link.addEventListener("click", function (event) {
|
||||
event.preventDefault(); // Prevent default link behavior
|
||||
const url = this.getAttribute("href");
|
||||
openIframe(url);
|
||||
});
|
||||
});
|
||||
|
||||
// Add click event listener to header h1 to restore the original main content and style
|
||||
const headerH1 = document.querySelector("header h1");
|
||||
if (headerH1) {
|
||||
headerH1.style.cursor = "pointer";
|
||||
headerH1.addEventListener("click", function () {
|
||||
// Restore the original content of the main element (removing the iframe)
|
||||
mainElement.innerHTML = originalContent;
|
||||
// Restore any custom scrollbar
|
||||
if (customScrollbar) {
|
||||
customScrollbar.style.display = '';
|
||||
}
|
||||
|
||||
// Restore the original inline style of the main element
|
||||
if (originalMainStyle !== null) {
|
||||
@@ -74,28 +85,74 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
mainElement.removeAttribute("style");
|
||||
}
|
||||
|
||||
// Optionally revert the container class back to "container" if needed
|
||||
if (container && container.classList.contains("container-fluid")) {
|
||||
container.classList.replace("container-fluid", "container");
|
||||
}
|
||||
// Update the URL to remove the iframe parameter
|
||||
const newUrl = new URL(window.location);
|
||||
newUrl.searchParams.delete("iframe");
|
||||
window.history.pushState({}, '', newUrl);
|
||||
}
|
||||
|
||||
// Optionally show the custom scrollbar again
|
||||
if (customScrollbar) {
|
||||
customScrollbar.style.display = "";
|
||||
}
|
||||
// Initialize event listeners after DOM content is loaded
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
// Cache references to elements and original state
|
||||
mainElement = document.querySelector("main");
|
||||
originalContent = mainElement.innerHTML;
|
||||
originalMainStyle = mainElement.getAttribute("style"); // may be null
|
||||
container = document.querySelector(".container");
|
||||
customScrollbar = document.getElementById("custom-scrollbar");
|
||||
scrollbarContainer = container.querySelector(".scroll-container")
|
||||
|
||||
// Adjust scroll container height if that function exists
|
||||
if (typeof adjustScrollContainerHeight === "function") {
|
||||
adjustScrollContainerHeight();
|
||||
}
|
||||
// Attach click handlers to links that should open in an iframe
|
||||
document.querySelectorAll(".iframe-link").forEach(link => {
|
||||
link.addEventListener("click", function(event) {
|
||||
event.preventDefault(); // prevent full page navigation
|
||||
openIframe(this.href);
|
||||
updateUrlFullWidth(true);
|
||||
});
|
||||
});
|
||||
|
||||
// Clicking the header's H1 will restore the original view
|
||||
const headerH1 = document.querySelector("header h1");
|
||||
if (headerH1) {
|
||||
headerH1.style.cursor = "pointer";
|
||||
headerH1.addEventListener("click", restoreOriginal);
|
||||
}
|
||||
|
||||
// Adjust iframe height on window resize (optional, to keep it responsive)
|
||||
window.addEventListener("resize", function () {
|
||||
const iframe = mainElement.querySelector("iframe");
|
||||
if (iframe) {
|
||||
iframe.style.height = mainElement.style.height;
|
||||
// On full page load, check URL parameters to auto-open an iframe
|
||||
window.addEventListener("load", function() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const iframeUrl = params.get('iframe');
|
||||
if (iframeUrl) {
|
||||
openIframe(iframeUrl);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Handle browser back/forward navigation
|
||||
window.addEventListener('popstate', function(event) {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const iframeUrl = params.get('iframe');
|
||||
|
||||
if (iframeUrl) {
|
||||
openIframe(iframeUrl);
|
||||
} else {
|
||||
restoreOriginal();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Opens the current iframe URL in a new browser tab.
|
||||
*/
|
||||
function openIframeInNewTab() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const iframeUrl = params.get('iframe');
|
||||
if (iframeUrl) {
|
||||
window.open(iframeUrl, '_blank');
|
||||
} else {
|
||||
alert('No iframe is currently open.');
|
||||
}
|
||||
}
|
||||
// expose globally so your template’s onclick can find it
|
||||
window.openIframeInNewTab = openIframeInNewTab;
|
||||
|
||||
// Adjust iframe height on window resize
|
||||
window.addEventListener('resize', syncIframeHeight);
|
||||
|
@@ -49,10 +49,16 @@
|
||||
</div>
|
||||
<!-- Include modal -->
|
||||
{% include "moduls/modal.html.j2" %}
|
||||
<script src="{{ url_for('static', filename='js/modal.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/navigation.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/tooltip.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/custom_scrollbar.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/iframe.js') }}"></script>
|
||||
{% for name in [
|
||||
'modal',
|
||||
'navigation',
|
||||
'tooltip',
|
||||
'container',
|
||||
'fullwidth',
|
||||
'fullscreen',
|
||||
'iframe',
|
||||
] %}
|
||||
<script src="{{ url_for('static', filename='js/' ~ name ~ '.js') }}"></script>
|
||||
{% endfor %}
|
||||
</body>
|
||||
</html>
|
@@ -8,26 +8,39 @@
|
||||
{% endmacro %}
|
||||
<!-- Template for children -->
|
||||
{% macro render_children(children) %}
|
||||
{% for children in children %}
|
||||
{% if children.children %}
|
||||
{% for child in children %}
|
||||
{% if child.children %}
|
||||
<li class="dropdown-submenu position-relative">
|
||||
<a class="dropdown-item dropdown-toggle" title="{{ children.description }}">
|
||||
{{ render_icon_and_name(children) }}
|
||||
<a class="dropdown-item dropdown-toggle" title="{{ child.description }}">
|
||||
{{ render_icon_and_name(child) }}
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
{{ render_children(children.children) }}
|
||||
{{ render_children(child.children) }}
|
||||
</ul>
|
||||
</li>
|
||||
{% elif children.identifier or children.warning or children.info %}
|
||||
|
||||
{% elif child.identifier or child.warning or child.info %}
|
||||
<li>
|
||||
<a class="dropdown-item" onclick='openDynamicPopup({{ children|tojson|safe }})' data-bs-toggle="tooltip" title="{{ children.description }}">
|
||||
{{ render_icon_and_name(children) }}
|
||||
<a class="dropdown-item"
|
||||
onclick='openDynamicPopup({{ child|tojson|safe }})'
|
||||
data-bs-toggle="tooltip"
|
||||
title="{{ child.description }}">
|
||||
{{ render_icon_and_name(child) }}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% else %}
|
||||
<li>
|
||||
<a class="dropdown-item {% if children.iframe %}iframe-link{% endif %}" href="{{ children.url }}" target="{{ children.target|default('_blank') }}" data-bs-toggle="tooltip" title="{{ children.description }}">
|
||||
{{ render_icon_and_name(children) }}
|
||||
<a class="dropdown-item {% if child.iframe %}iframe-link{% endif %}"
|
||||
{% if child.onclick %}
|
||||
onclick="{{ child.onclick }}"
|
||||
{% else %}
|
||||
href="{{ child.url }}"
|
||||
{% endif %}
|
||||
target="{{ child.target|default('_blank') }}"
|
||||
data-bs-toggle="tooltip"
|
||||
title="{{ child.description }}">
|
||||
{{ render_icon_and_name(child) }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
@@ -36,17 +49,23 @@
|
||||
|
||||
<!-- Navigation Bar -->
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light menu-{{menu_type}}">
|
||||
<div class="container-fluid">
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav{{menu_type}}" aria-controls="navbarNav{{menu_type}}" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav{{menu_type}}">
|
||||
<ul class="navbar-nav {% if menu_type == 'header' %}ms-auto{% endif %} btn-group">
|
||||
{% for item in navigation[menu_type].children %}
|
||||
{% if item.url %}
|
||||
<!-- Single Item -->
|
||||
{% if item.url or item.onclick %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link btn btn-light {% if item.iframe %}iframe-link{% endif %}" href="{{ item.url }}" target="{{ item.target|default('_blank') }}" data-bs-toggle="tooltip" title="{{ item.description }}">
|
||||
<a class="nav-link btn btn-light {% if item.iframe %}iframe-link{% endif %}"
|
||||
{% if item.onclick %}
|
||||
onclick="{{ item.onclick }}"
|
||||
{% else %}
|
||||
href="{{ item.url }}"
|
||||
{% endif %}
|
||||
target="{{ item.target|default('_blank') }}"
|
||||
data-bs-toggle="tooltip"
|
||||
title="{{ item.description }}">
|
||||
{{ render_icon_and_name(item) }}
|
||||
</a>
|
||||
</li>
|
||||
@@ -68,5 +87,4 @@
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
@@ -11,6 +11,8 @@ services:
|
||||
- "${PORT:-5000}:${PORT:-5000}"
|
||||
volumes:
|
||||
- ./app:/app
|
||||
- ./.env:/app./.env
|
||||
environment:
|
||||
- PORT=${PORT:-5000}
|
||||
- FLASK_ENV=${FLASK_ENV:-production}
|
||||
restart: unless-stopped
|
||||
|
17
main.py
17
main.py
@@ -25,7 +25,6 @@ import os
|
||||
from dotenv import load_dotenv
|
||||
from pathlib import Path
|
||||
|
||||
# Always load .env from the script's directory
|
||||
dotenv_path = Path(__file__).resolve().parent / ".env"
|
||||
|
||||
if dotenv_path.exists():
|
||||
@@ -34,14 +33,14 @@ else:
|
||||
print(f"⚠️ Warning: No .env file found at {dotenv_path}")
|
||||
PORT = int(os.getenv("PORT", 5000))
|
||||
|
||||
def run_command(command, dry_run=False):
|
||||
def run_command(command, dry_run=False, env=None):
|
||||
"""Utility function to run a shell command."""
|
||||
print(f"Executing: {' '.join(command)}")
|
||||
if dry_run:
|
||||
print("Dry run enabled: command not executed.")
|
||||
return
|
||||
try:
|
||||
subprocess.check_call(command)
|
||||
subprocess.check_call(command, env=env)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error: Command failed with exit code {e.returncode}")
|
||||
sys.exit(e.returncode)
|
||||
@@ -126,7 +125,7 @@ def run_prod(args):
|
||||
"""
|
||||
command = [
|
||||
"docker", "run", "-d",
|
||||
"-p", "{PORT}:{PORT}",
|
||||
"-p", "{PORT}:5000",
|
||||
"--name", "portfolio",
|
||||
"application-portfolio"
|
||||
]
|
||||
@@ -148,18 +147,12 @@ def logs(args):
|
||||
def dev(args):
|
||||
"""
|
||||
Run the application in development mode using docker-compose.
|
||||
|
||||
Command:
|
||||
FLASK_ENV=development docker-compose up -d
|
||||
|
||||
This command sets the FLASK_ENV environment variable to 'development'
|
||||
and starts the application using docker-compose, enabling hot-reloading.
|
||||
"""
|
||||
env = os.environ.copy()
|
||||
env["FLASK_ENV"] = "development"
|
||||
command = ["docker-compose", "up", "-d"]
|
||||
print("Setting FLASK_ENV=development")
|
||||
run_command(command, args.dry_run)
|
||||
print("▶️ Starting in development mode (FLASK_ENV=development)")
|
||||
run_command(command, args.dry_run, env=env)
|
||||
|
||||
def prod(args):
|
||||
"""
|
||||
|
Reference in New Issue
Block a user