update pre commit skript, and responsive menu
This commit is contained in:
parent
b7acc01e90
commit
e48cf69b5d
|
|
@ -15,7 +15,7 @@ RUN npm install --production
|
||||||
COPY backend/src ./src
|
COPY backend/src ./src
|
||||||
|
|
||||||
# Copy production environment configuration
|
# Copy production environment configuration
|
||||||
#COPY docker/prod/backend/config/.env ./.env
|
# COPY docker/prod/backend/config/.env ./.env
|
||||||
|
|
||||||
# Create data directories for file storage
|
# Create data directories for file storage
|
||||||
RUN mkdir -p src/data/images src/data/previews src/data/groups
|
RUN mkdir -p src/data/images src/data/previews src/data/groups
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ services:
|
||||||
environment:
|
environment:
|
||||||
- REMOVE_IMAGES=false
|
- REMOVE_IMAGES=false
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- ADMIN_SESSION_SECRET=MvFhivVIPIXvSGvWGfGOiQCkUJrmUsjWQTNGUgnSmtpsGHQlKruTBEBZgbVvOHHr
|
- ADMIN_SESSION_SECRET=${ADMIN_SESSION_SECRET}
|
||||||
- ADMIN_SESSION_DIR=/usr/src/app/src/data/sessions
|
- ADMIN_SESSION_DIR=/usr/src/app/src/data/sessions
|
||||||
# ⚠️ Für HTTP-only Labs per Override auf "false" setzen (nicht im Repo committen)
|
# ⚠️ Für HTTP-only Labs per Override auf "false" setzen (nicht im Repo committen)
|
||||||
- ADMIN_SESSION_COOKIE_SECURE=true
|
- ADMIN_SESSION_COOKIE_SECURE=true
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,38 @@ header {
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
display: none;
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu span {
|
||||||
|
width: 28px;
|
||||||
|
height: 3px;
|
||||||
|
background-color: #edf0f1;
|
||||||
|
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu:focus-visible {
|
||||||
|
outline: 2px solid #edf0f1;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu--open span:nth-child(1) {
|
||||||
|
transform: translateY(9px) rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu--open span:nth-child(2) {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu--open span:nth-child(3) {
|
||||||
|
transform: translateY(-9px) rotate(-45deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay {
|
.overlay {
|
||||||
|
|
@ -121,6 +153,8 @@ header {
|
||||||
font-size: 60px;
|
font-size: 60px;
|
||||||
color: #edf0f1;
|
color: #edf0f1;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-height: 450px) {
|
@media screen and (max-height: 450px) {
|
||||||
|
|
@ -140,6 +174,6 @@ header {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.menu {
|
.menu {
|
||||||
display: initial;
|
display: flex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { NavLink, useLocation } from 'react-router-dom'
|
import { NavLink, useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
import '../Css/Navbar.css'
|
import '../Css/Navbar.css'
|
||||||
|
|
@ -9,11 +9,25 @@ import { Lock as LockIcon } from '@mui/icons-material';
|
||||||
function Navbar() {
|
function Navbar() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isManagementPage = location.pathname.startsWith('/manage/');
|
const isManagementPage = location.pathname.startsWith('/manage/');
|
||||||
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMenuOpen(false);
|
||||||
|
}, [location.pathname]);
|
||||||
|
|
||||||
|
const toggleMenu = () => setMenuOpen(prev => !prev);
|
||||||
|
const closeMenu = () => setMenuOpen(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<header>
|
<header>
|
||||||
<div className="logo"><NavLink className="logo" exact to="/"><img src={logo} className="imageNav" alt="Logo"/><p className="logo">Upload your Project Images</p></NavLink></div>
|
<div className="logo">
|
||||||
<nav>
|
<NavLink className="logo" exact to="/">
|
||||||
|
<img src={logo} className="imageNav" alt="Logo" />
|
||||||
|
<p className="logo">Upload your Project Images</p>
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
<nav aria-label="Hauptnavigation">
|
||||||
<ul className="nav__links">
|
<ul className="nav__links">
|
||||||
<li><NavLink to="/groups" activeClassName="active">Groups</NavLink></li>
|
<li><NavLink to="/groups" activeClassName="active">Groups</NavLink></li>
|
||||||
<li><NavLink to="/moderation" activeClassName="active"><LockIcon style={{ fontSize: 18, verticalAlign: 'text-bottom', marginRight: 6 }} aria-hidden="true" />Moderation</NavLink></li>
|
<li><NavLink to="/moderation" activeClassName="active"><LockIcon style={{ fontSize: 18, verticalAlign: 'text-bottom', marginRight: 6 }} aria-hidden="true" />Moderation</NavLink></li>
|
||||||
|
|
@ -23,8 +37,39 @@ function Navbar() {
|
||||||
)}
|
)}
|
||||||
<li><a href="https://gitea.lan.hobbyhimmel.de/hobbyhimmel/Project-Image-Uploader" target="_blank" rel="noopener noreferrer">About</a></li>
|
<li><a href="https://gitea.lan.hobbyhimmel.de/hobbyhimmel/Project-Image-Uploader" target="_blank" rel="noopener noreferrer">About</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`menu${menuOpen ? ' menu--open' : ''}`}
|
||||||
|
onClick={toggleMenu}
|
||||||
|
aria-label="Navigation umschalten"
|
||||||
|
aria-expanded={menuOpen}
|
||||||
|
aria-controls="mobile-nav"
|
||||||
|
>
|
||||||
|
<span />
|
||||||
|
<span />
|
||||||
|
<span />
|
||||||
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
<div
|
||||||
|
id="mobile-nav"
|
||||||
|
className={`overlay${menuOpen ? ' overlay--active' : ''}`}
|
||||||
|
aria-hidden={!menuOpen}
|
||||||
|
>
|
||||||
|
<button type="button" className="close" onClick={closeMenu} aria-label="Navigation schließen">×</button>
|
||||||
|
<div className="overlay__content">
|
||||||
|
<NavLink to="/groups" activeClassName="active" onClick={closeMenu}>Groups</NavLink>
|
||||||
|
<NavLink to="/moderation" activeClassName="active" onClick={closeMenu}>
|
||||||
|
<LockIcon style={{ fontSize: 24, verticalAlign: 'text-bottom', marginRight: 12 }} aria-hidden="true" />Moderation
|
||||||
|
</NavLink>
|
||||||
|
<NavLink exact to="/" activeClassName="active" onClick={closeMenu}>Upload</NavLink>
|
||||||
|
{isManagementPage && (
|
||||||
|
<NavLink to={location.pathname} activeClassName="active" onClick={closeMenu}>Mein Upload</NavLink>
|
||||||
|
)}
|
||||||
|
<a href="https://gitea.lan.hobbyhimmel.de/hobbyhimmel/Project-Image-Uploader" target="_blank" rel="noopener noreferrer" onClick={closeMenu}>About</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,59 @@
|
||||||
import React from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { NavLink, useLocation } from 'react-router-dom'
|
import { NavLink, useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
import '../Css/Navbar.css'
|
import '../Css/Navbar.css'
|
||||||
|
|
||||||
import logo from '../../../Images/logo.png'
|
import logo from '../../../Images/logo.png'
|
||||||
import { Lock as LockIcon } from '@mui/icons-material';
|
|
||||||
|
|
||||||
function Navbar() {
|
function Navbar() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isManagementPage = location.pathname.startsWith('/manage/');
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMenuOpen(false);
|
||||||
|
}, [location.pathname]);
|
||||||
|
|
||||||
|
const toggleMenu = () => setMenuOpen(prev => !prev);
|
||||||
|
const closeMenu = () => setMenuOpen(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<header>
|
<header>
|
||||||
<div className="logo"><NavLink className="logo" exact to="/"><img src={logo} className="imageNav" alt="Logo"/><p className="logo">Upload your Project Images</p></NavLink></div>
|
<div className="logo">
|
||||||
<nav>
|
<NavLink className="logo" exact to="/">
|
||||||
|
<img src={logo} className="imageNav" alt="Logo" />
|
||||||
|
<p className="logo">Upload your Project Images</p>
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
<nav aria-label="Hauptnavigation">
|
||||||
<ul className="nav__links">
|
<ul className="nav__links">
|
||||||
<li><a href="https://gitea.lan.hobbyhimmel.de/hobbyhimmel/Project-Image-Uploader" target="_blank" rel="noopener noreferrer">About</a></li>
|
<li><a href="https://gitea.lan.hobbyhimmel.de/hobbyhimmel/Project-Image-Uploader" target="_blank" rel="noopener noreferrer">About</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`menu${menuOpen ? ' menu--open' : ''}`}
|
||||||
|
onClick={toggleMenu}
|
||||||
|
aria-label="Navigation umschalten"
|
||||||
|
aria-expanded={menuOpen}
|
||||||
|
aria-controls="mobile-nav-upload"
|
||||||
|
>
|
||||||
|
<span />
|
||||||
|
<span />
|
||||||
|
<span />
|
||||||
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
<div
|
||||||
|
id="mobile-nav-upload"
|
||||||
|
className={`overlay${menuOpen ? ' overlay--active' : ''}`}
|
||||||
|
aria-hidden={!menuOpen}
|
||||||
|
>
|
||||||
|
<button type="button" className="close" onClick={closeMenu} aria-label="Navigation schließen">×</button>
|
||||||
|
<div className="overlay__content">
|
||||||
|
<a href="https://gitea.lan.hobbyhimmel.de/hobbyhimmel/Project-Image-Uploader" target="_blank" rel="noopener noreferrer" onClick={closeMenu}>About</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
TARGET_FILE="$ROOT_DIR/docker/prod/docker-compose.yml"
|
TARGET_FILE="$ROOT_DIR/docker/prod/docker-compose.yml"
|
||||||
ANCHOR_LINE=" - ADMIN_SESSION_DIR=/usr/src/app/src/data/sessions"
|
ANCHOR_LINE=" - ADMIN_SESSION_DIR=/usr/src/app/src/data/sessions"
|
||||||
EXPECTED_LINE=" - ADMIN_SESSION_COOKIE_SECURE=true"
|
EXPECTED_LINE=" - ADMIN_SESSION_COOKIE_SECURE=true"
|
||||||
|
SECRET_ANCHOR_LINE=' - NODE_ENV=production'
|
||||||
|
SECRET_EXPECTED_LINE=' - ADMIN_SESSION_SECRET=${ADMIN_SESSION_SECRET}'
|
||||||
|
SECRET_VALUE='${ADMIN_SESSION_SECRET}'
|
||||||
|
|
||||||
if [[ ! -f "$TARGET_FILE" ]]; then
|
if [[ ! -f "$TARGET_FILE" ]]; then
|
||||||
exit 0
|
exit 0
|
||||||
|
|
@ -13,6 +16,9 @@ fi
|
||||||
export TARGET_FILE
|
export TARGET_FILE
|
||||||
export ANCHOR_LINE
|
export ANCHOR_LINE
|
||||||
export EXPECTED_LINE
|
export EXPECTED_LINE
|
||||||
|
export SECRET_ANCHOR_LINE
|
||||||
|
export SECRET_EXPECTED_LINE
|
||||||
|
export SECRET_VALUE
|
||||||
|
|
||||||
result=$(python3 <<'PY'
|
result=$(python3 <<'PY'
|
||||||
import os
|
import os
|
||||||
|
|
@ -23,25 +29,57 @@ import sys
|
||||||
path = pathlib.Path(os.environ['TARGET_FILE'])
|
path = pathlib.Path(os.environ['TARGET_FILE'])
|
||||||
anchor = os.environ['ANCHOR_LINE']
|
anchor = os.environ['ANCHOR_LINE']
|
||||||
expected = os.environ['EXPECTED_LINE']
|
expected = os.environ['EXPECTED_LINE']
|
||||||
|
secret_anchor = os.environ['SECRET_ANCHOR_LINE']
|
||||||
|
secret_expected = os.environ['SECRET_EXPECTED_LINE']
|
||||||
|
secret_value = os.environ['SECRET_VALUE']
|
||||||
|
|
||||||
text = path.read_text()
|
text = path.read_text()
|
||||||
|
new_text = text
|
||||||
changed = False
|
changed = False
|
||||||
|
|
||||||
if 'ADMIN_SESSION_COOKIE_SECURE' in text:
|
cookie_pattern = re.compile(r'(\-\s*ADMIN_SESSION_COOKIE_SECURE\s*=\s*)([^\n\r]+)')
|
||||||
pattern = re.compile(r'(\-\s*ADMIN_SESSION_COOKIE_SECURE\s*=\s*)([^\n\r]+)')
|
secret_pattern = re.compile(r'(\-\s*ADMIN_SESSION_SECRET\s*=\s*)([^\n\r]+)')
|
||||||
new_text, count = pattern.subn(r'\1true', text, count=1)
|
|
||||||
if count:
|
def ensure_entry(text, *, pattern, value, anchor_line, expected_line, label):
|
||||||
changed = new_text != text
|
match = pattern.search(text)
|
||||||
else:
|
if match:
|
||||||
if anchor not in text:
|
desired = f"{match.group(1)}{value}"
|
||||||
print('ERROR: Anchor line not found for ADMIN_SESSION_COOKIE_SECURE insertion', file=sys.stderr)
|
if match.group(0) == desired:
|
||||||
|
return text, False
|
||||||
|
return pattern.sub(lambda m: f"{m.group(1)}{value}", text, count=1), True
|
||||||
|
if anchor_line not in text:
|
||||||
|
print(f"ERROR: Anchor line not found for {label}", file=sys.stderr)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
new_text = text.replace(anchor, anchor + '\n' + expected, 1)
|
return text.replace(anchor_line, anchor_line + '\n' + expected_line, 1), True
|
||||||
changed = True
|
|
||||||
|
new_text, cookie_changed = ensure_entry(
|
||||||
|
new_text,
|
||||||
|
pattern=cookie_pattern,
|
||||||
|
value='true',
|
||||||
|
anchor_line=anchor,
|
||||||
|
expected_line=expected,
|
||||||
|
label='ADMIN_SESSION_COOKIE_SECURE'
|
||||||
|
)
|
||||||
|
changed = changed or cookie_changed
|
||||||
|
|
||||||
if expected not in new_text:
|
if expected not in new_text:
|
||||||
print('ERROR: Failed to ensure ADMIN_SESSION_COOKIE_SECURE=true in docker-compose.yml', file=sys.stderr)
|
print('ERROR: Failed to ensure ADMIN_SESSION_COOKIE_SECURE=true in docker-compose.yml', file=sys.stderr)
|
||||||
sys.exit(3)
|
sys.exit(3)
|
||||||
|
|
||||||
|
new_text, secret_changed = ensure_entry(
|
||||||
|
new_text,
|
||||||
|
pattern=secret_pattern,
|
||||||
|
value=secret_value,
|
||||||
|
anchor_line=secret_anchor,
|
||||||
|
expected_line=secret_expected,
|
||||||
|
label='ADMIN_SESSION_SECRET'
|
||||||
|
)
|
||||||
|
changed = changed or secret_changed
|
||||||
|
|
||||||
|
if secret_expected not in new_text:
|
||||||
|
print('ERROR: Failed to ensure ADMIN_SESSION_SECRET uses environment variable in docker-compose.yml', file=sys.stderr)
|
||||||
|
sys.exit(4)
|
||||||
|
|
||||||
if changed:
|
if changed:
|
||||||
path.write_text(new_text)
|
path.write_text(new_text)
|
||||||
print('UPDATED')
|
print('UPDATED')
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user