8 changed files with 398 additions and 5 deletions
@ -0,0 +1,227 @@ |
|||||||
|
/* ===== OTB shared branding ===== */ |
||||||
|
body{ |
||||||
|
padding-bottom: 56px; |
||||||
|
} |
||||||
|
|
||||||
|
.site-container{ |
||||||
|
max-width: 1100px; |
||||||
|
margin: 0 auto; |
||||||
|
padding: 20px 18px 0 18px; |
||||||
|
} |
||||||
|
|
||||||
|
.site-header{ |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
.site-nav{ |
||||||
|
display:flex; |
||||||
|
align-items:center; |
||||||
|
justify-content:space-between; |
||||||
|
gap:18px; |
||||||
|
padding:12px 0 22px 0; |
||||||
|
} |
||||||
|
|
||||||
|
.site-brand{ |
||||||
|
display:flex; |
||||||
|
align-items:center; |
||||||
|
gap:14px; |
||||||
|
text-decoration:none; |
||||||
|
color:inherit; |
||||||
|
} |
||||||
|
|
||||||
|
.site-brand img{ |
||||||
|
height:60px; |
||||||
|
width:auto; |
||||||
|
display:block; |
||||||
|
object-fit:contain; |
||||||
|
background: rgba(255,255,255,0.92); |
||||||
|
padding: 6px 12px; |
||||||
|
border-radius: 999px; |
||||||
|
box-shadow: 0 8px 24px rgba(0,0,0,0.35); |
||||||
|
} |
||||||
|
|
||||||
|
.site-title{ |
||||||
|
display:flex; |
||||||
|
flex-direction:column; |
||||||
|
line-height:1.1; |
||||||
|
} |
||||||
|
|
||||||
|
.site-title strong{ |
||||||
|
letter-spacing:.2px; |
||||||
|
color: var(--text, #e8eefc); |
||||||
|
} |
||||||
|
|
||||||
|
.site-title span{ |
||||||
|
color: var(--muted, #aab6d6); |
||||||
|
font-size:13px; |
||||||
|
margin-top:2px; |
||||||
|
} |
||||||
|
|
||||||
|
.site-navlinks{ |
||||||
|
display:flex; |
||||||
|
gap:12px; |
||||||
|
flex-wrap:wrap; |
||||||
|
justify-content:flex-end; |
||||||
|
align-items:center; |
||||||
|
} |
||||||
|
|
||||||
|
.site-navlinks > a, |
||||||
|
.dropdown-toggle{ |
||||||
|
text-decoration:none; |
||||||
|
padding:8px 10px; |
||||||
|
border-radius:12px; |
||||||
|
color: var(--muted, #aab6d6); |
||||||
|
border:1px solid transparent; |
||||||
|
} |
||||||
|
|
||||||
|
.site-navlinks > a:hover, |
||||||
|
.dropdown-toggle:hover{ |
||||||
|
color: var(--text, #e8eefc); |
||||||
|
border-color: rgba(255,255,255,.08); |
||||||
|
background: rgba(255,255,255,.03); |
||||||
|
} |
||||||
|
|
||||||
|
.dropdown{ |
||||||
|
position:relative; |
||||||
|
display:inline-block; |
||||||
|
} |
||||||
|
|
||||||
|
.dropdown-toggle{ |
||||||
|
display:inline-block; |
||||||
|
cursor:pointer; |
||||||
|
} |
||||||
|
|
||||||
|
.dropdown-menu{ |
||||||
|
position:absolute; |
||||||
|
top:calc(100% + 8px); |
||||||
|
right:0; |
||||||
|
min-width:220px; |
||||||
|
display:none; |
||||||
|
padding:10px; |
||||||
|
border-radius:14px; |
||||||
|
background:rgba(18,24,37,.98); |
||||||
|
border:1px solid rgba(255,255,255,.08); |
||||||
|
box-shadow:0 16px 40px rgba(0,0,0,.35); |
||||||
|
z-index:9999; |
||||||
|
} |
||||||
|
|
||||||
|
.dropdown:hover .dropdown-menu, |
||||||
|
.dropdown:focus-within .dropdown-menu{ |
||||||
|
display:block; |
||||||
|
} |
||||||
|
|
||||||
|
.dropdown-menu a{ |
||||||
|
display:block; |
||||||
|
padding:9px 10px; |
||||||
|
border-radius:10px; |
||||||
|
color:var(--muted, #aab6d6); |
||||||
|
text-decoration:none; |
||||||
|
white-space:nowrap; |
||||||
|
margin:0; |
||||||
|
} |
||||||
|
|
||||||
|
.dropdown-menu a + a{ |
||||||
|
margin-top:4px; |
||||||
|
} |
||||||
|
|
||||||
|
.dropdown-menu a:hover{ |
||||||
|
color:var(--text, #e8eefc); |
||||||
|
background:rgba(255,255,255,.04); |
||||||
|
} |
||||||
|
|
||||||
|
.otb-statusbar{ |
||||||
|
position: fixed; |
||||||
|
left: 0; |
||||||
|
right: 0; |
||||||
|
bottom: 0; |
||||||
|
z-index: 9999; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
min-height: 42px; |
||||||
|
padding: 8px 14px; |
||||||
|
background: rgba(8, 16, 32, 0.94); |
||||||
|
border-top: 1px solid rgba(255,255,255,.10); |
||||||
|
backdrop-filter: blur(8px); |
||||||
|
box-shadow: 0 -8px 24px rgba(0,0,0,.28); |
||||||
|
} |
||||||
|
|
||||||
|
.otb-statusbar-inner{ |
||||||
|
width: 100%; |
||||||
|
max-width: 1100px; |
||||||
|
display: flex; |
||||||
|
gap: 10px; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
flex-wrap: wrap; |
||||||
|
text-align: center; |
||||||
|
color: var(--muted, #aab6d6); |
||||||
|
font-size: 12px; |
||||||
|
line-height: 1.35; |
||||||
|
} |
||||||
|
|
||||||
|
.otb-statusbar strong{ |
||||||
|
color: var(--text, #e8eefc); |
||||||
|
font-weight: 700; |
||||||
|
} |
||||||
|
|
||||||
|
.otb-statusbar a{ |
||||||
|
color: #62e6b7; |
||||||
|
text-decoration: none; |
||||||
|
font-weight: 600; |
||||||
|
} |
||||||
|
|
||||||
|
.otb-statusbar a:hover{ |
||||||
|
text-decoration: underline; |
||||||
|
} |
||||||
|
|
||||||
|
.otb-dot{ |
||||||
|
width: 6px; |
||||||
|
height: 6px; |
||||||
|
border-radius: 999px; |
||||||
|
display: inline-block; |
||||||
|
background: rgba(255,255,255,.25); |
||||||
|
flex: 0 0 auto; |
||||||
|
} |
||||||
|
|
||||||
|
@media (max-width: 900px){ |
||||||
|
.site-nav{ |
||||||
|
align-items:flex-start; |
||||||
|
flex-direction:column; |
||||||
|
} |
||||||
|
|
||||||
|
.site-navlinks{ |
||||||
|
justify-content:flex-start; |
||||||
|
} |
||||||
|
|
||||||
|
.dropdown{ |
||||||
|
width:100%; |
||||||
|
} |
||||||
|
|
||||||
|
.dropdown-toggle{ |
||||||
|
width:100%; |
||||||
|
} |
||||||
|
|
||||||
|
.dropdown-menu{ |
||||||
|
position:static; |
||||||
|
right:auto; |
||||||
|
top:auto; |
||||||
|
min-width:100%; |
||||||
|
margin-top:6px; |
||||||
|
} |
||||||
|
|
||||||
|
.site-brand img{ |
||||||
|
height:54px; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@media (max-width: 700px){ |
||||||
|
body{ |
||||||
|
padding-bottom: 72px; |
||||||
|
} |
||||||
|
|
||||||
|
.otb-statusbar-inner{ |
||||||
|
font-size: 11px; |
||||||
|
line-height: 1.25; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,49 @@ |
|||||||
|
#!/bin/bash |
||||||
|
set -e |
||||||
|
SITE_DIR="/var/www/outsidethebox.top" |
||||||
|
cd "$SITE_DIR" || exit 1 |
||||||
|
|
||||||
|
STAMP="$(date +%Y%m%d-%H%M%S)" |
||||||
|
BKDIR="backups/shared-brand-$STAMP" |
||||||
|
mkdir -p "$BKDIR" |
||||||
|
cp -av index.html pricing.html terms.html contact.html assets/style.css "$BKDIR"/ |
||||||
|
|
||||||
|
HEADER="$(cat /home/def/otb-shared-brand/header.html)" |
||||||
|
FOOTER="$(cat /home/def/otb-shared-brand/footer.html)" |
||||||
|
BRANDCSS="$(cat /home/def/otb-shared-brand/brand.css)" |
||||||
|
|
||||||
|
python3 - <<PY |
||||||
|
from pathlib import Path |
||||||
|
import re |
||||||
|
|
||||||
|
header = """$HEADER""" |
||||||
|
footer = """$FOOTER""" |
||||||
|
brandcss = """$BRANDCSS""" |
||||||
|
|
||||||
|
for name in ["index.html", "pricing.html", "terms.html", "contact.html"]: |
||||||
|
p = Path(name) |
||||||
|
text = p.read_text(encoding="utf-8") |
||||||
|
|
||||||
|
if '<header class="site-header">' in text: |
||||||
|
text = re.sub(r'<header class="site-header">.*?</header>', header, text, count=1, flags=re.S) |
||||||
|
elif '<header class="header">' in text: |
||||||
|
text = re.sub(r'<header class="header">.*?</header>', header, text, count=1, flags=re.S) |
||||||
|
else: |
||||||
|
text = text.replace('<body>', '<body>\n' + header, 1) |
||||||
|
|
||||||
|
if '<div class="otb-statusbar">' in text: |
||||||
|
text = re.sub(r'<div class="otb-statusbar">.*?</div>\s*</div>', footer, text, count=1, flags=re.S) |
||||||
|
else: |
||||||
|
text = text.replace('</body>', footer + '\n</body>', 1) |
||||||
|
|
||||||
|
p.write_text(text, encoding="utf-8") |
||||||
|
|
||||||
|
cssp = Path("assets/style.css") |
||||||
|
css = cssp.read_text(encoding="utf-8") |
||||||
|
if "/* ===== OTB shared branding ===== */" in css: |
||||||
|
css = re.sub(r'/\* ===== OTB shared branding ===== \*/.*', brandcss, css, flags=re.S) |
||||||
|
else: |
||||||
|
css += "\n\n" + brandcss |
||||||
|
cssp.write_text(css, encoding="utf-8") |
||||||
|
print("mintme deploy complete") |
||||||
|
PY |
||||||
@ -0,0 +1,71 @@ |
|||||||
|
#!/bin/bash |
||||||
|
set -e |
||||||
|
APP_DIR="/home/def/monitor" |
||||||
|
cd "$APP_DIR" || exit 1 |
||||||
|
|
||||||
|
STAMP="$(date +%Y%m%d-%H%M%S)" |
||||||
|
BKDIR="backups/shared-brand-$STAMP" |
||||||
|
mkdir -p "$BKDIR" |
||||||
|
cp -av frontend/index.html frontend/styles.css "$BKDIR"/ || true |
||||||
|
|
||||||
|
HEADER="$(cat /home/def/otb-shared-brand/header.html)" |
||||||
|
FOOTER="$(cat /home/def/otb-shared-brand/footer.html)" |
||||||
|
BRANDCSS="$(cat /home/def/otb-shared-brand/brand.css)" |
||||||
|
|
||||||
|
cat > frontend/index.html <<HTML |
||||||
|
<!doctype html> |
||||||
|
<html lang="en" data-theme="dark"> |
||||||
|
<head> |
||||||
|
<meta charset="utf-8" /> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" /> |
||||||
|
<title>Monitor</title> |
||||||
|
<link rel="stylesheet" href="/styles.css" /> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
$HEADER |
||||||
|
|
||||||
|
<div class="wrap"> |
||||||
|
<header class="top"> |
||||||
|
<div> |
||||||
|
<div class="title">Monitor</div> |
||||||
|
<div class="sub">7-day snapshot • rotating refresh</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="top-right"> |
||||||
|
<div class="status-pill" id="status">Loading…</div> |
||||||
|
<div class="cycle" id="cycle"></div> |
||||||
|
|
||||||
|
<label class="switch" title="Toggle theme"> |
||||||
|
<input type="checkbox" id="themeToggle" aria-label="Toggle theme" /> |
||||||
|
<span class="slider"></span> |
||||||
|
</label> |
||||||
|
</div> |
||||||
|
</header> |
||||||
|
|
||||||
|
<div class="card"> |
||||||
|
<div id="root"></div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
$FOOTER |
||||||
|
|
||||||
|
<script src="/app.js" defer></script> |
||||||
|
</body> |
||||||
|
</html> |
||||||
|
HTML |
||||||
|
|
||||||
|
python3 - <<PY |
||||||
|
from pathlib import Path |
||||||
|
import re |
||||||
|
brandcss = """$BRANDCSS""" |
||||||
|
p = Path("frontend/styles.css") |
||||||
|
css = p.read_text(encoding="utf-8") |
||||||
|
if "/* ===== OTB shared branding ===== */" in css: |
||||||
|
css = re.sub(r'/\* ===== OTB shared branding ===== \*/.*', brandcss, css, flags=re.S) |
||||||
|
else: |
||||||
|
css = brandcss + "\n\n" + css |
||||||
|
p.write_text(css, encoding="utf-8") |
||||||
|
print("monitor branding css updated") |
||||||
|
PY |
||||||
|
|
||||||
|
/home/def/monitor/deploy-monitor.sh |
||||||
@ -0,0 +1,41 @@ |
|||||||
|
#!/bin/bash |
||||||
|
set -e |
||||||
|
APP_DIR="/home/def/otb_billing" |
||||||
|
cd "$APP_DIR" || exit 1 |
||||||
|
|
||||||
|
STAMP="$(date +%Y%m%d-%H%M%S)" |
||||||
|
BKDIR="backups/shared-brand-$STAMP" |
||||||
|
mkdir -p "$BKDIR" |
||||||
|
cp -av templates/includes/site_nav.html static/css/style.css templates/portal_*.html "$BKDIR"/ |
||||||
|
|
||||||
|
cp -av /home/def/otb-shared-brand/header.html templates/includes/site_nav.html |
||||||
|
|
||||||
|
FOOTER="$(cat /home/def/otb-shared-brand/footer.html)" |
||||||
|
BRANDCSS="$(cat /home/def/otb-shared-brand/brand.css)" |
||||||
|
|
||||||
|
python3 - <<PY |
||||||
|
from pathlib import Path |
||||||
|
import re |
||||||
|
|
||||||
|
footer = """$FOOTER""" |
||||||
|
brandcss = """$BRANDCSS""" |
||||||
|
|
||||||
|
for p in Path("templates").glob("portal_*.html"): |
||||||
|
text = p.read_text(encoding="utf-8") |
||||||
|
if '<div class="otb-statusbar">' in text: |
||||||
|
text = re.sub(r'<div class="otb-statusbar">.*?</div>\s*</div>', footer, text, count=1, flags=re.S) |
||||||
|
else: |
||||||
|
text = text.replace('{% include "footer.html" %}', footer + '\n\n {% include "footer.html" %}') |
||||||
|
p.write_text(text, encoding="utf-8") |
||||||
|
|
||||||
|
cssp = Path("static/css/style.css") |
||||||
|
css = cssp.read_text(encoding="utf-8") |
||||||
|
if "/* ===== OTB shared branding ===== */" in css: |
||||||
|
css = re.sub(r'/\* ===== OTB shared branding ===== \*/.*', brandcss, css, flags=re.S) |
||||||
|
else: |
||||||
|
css += "\n\n" + brandcss |
||||||
|
cssp.write_text(css, encoding="utf-8") |
||||||
|
print("portal deploy complete") |
||||||
|
PY |
||||||
|
|
||||||
|
sudo systemctl restart otb_billing.service |
||||||
Loading…
Reference in new issue