mirror of
https://github.com/andrey271192/amnezia_web.git
synced 2026-09-20 14:41:59 +00:00
feat(panel): donor strip + Telegram sponsor in footer
Public GET /api/panel/footer with env-backed URLs (defaults: Boosty + t.me sponsor). install.sh forwards PANEL_FOOTER_*. Promo only in admin SPA, not nginx landing. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -147,6 +147,62 @@ function fmtBytesRu(n) {
|
||||
return `${Math.round(x / (1024 * 102.4)) / 10} MiB`;
|
||||
}
|
||||
|
||||
/** Ссылки доната и Telegram‑спонсора: шапка + подвал панели (`GET /api/panel/footer`). */
|
||||
function wirePanelPromoAnchor(el, url, label) {
|
||||
const u = typeof url === "string" ? url.trim() : "";
|
||||
const lb = typeof label === "string" ? label.trim() : "";
|
||||
if (!el || !u || !lb) {
|
||||
if (el) {
|
||||
el.classList.add("hidden");
|
||||
el.removeAttribute("href");
|
||||
el.textContent = "";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
el.href = u;
|
||||
el.textContent = lb;
|
||||
el.classList.remove("hidden");
|
||||
return true;
|
||||
}
|
||||
|
||||
async function hydratePanelPromoFooter() {
|
||||
const strip = document.getElementById("panel-promo-strip");
|
||||
const sub = document.getElementById("panel-promo-subtitle");
|
||||
const aDonTop = document.getElementById("panel-strip-donate");
|
||||
const sepTop = document.getElementById("panel-strip-sep");
|
||||
const aTgTop = document.getElementById("panel-strip-telegram");
|
||||
const gap1 = document.getElementById("footer-promo-gap1");
|
||||
const aDonF = document.getElementById("footer-promo-donate");
|
||||
const sepMid = document.getElementById("footer-promo-sep-mid");
|
||||
const aTgF = document.getElementById("footer-promo-telegram");
|
||||
|
||||
try {
|
||||
const r = await fetch("/api/panel/footer", { credentials: "same-origin" });
|
||||
if (!r.ok) return;
|
||||
const j = await r.json().catch(() => null);
|
||||
if (!j || typeof j !== "object") return;
|
||||
|
||||
const subtitle = typeof j.promoSubtitle === "string" ? j.promoSubtitle.trim() : "";
|
||||
if (sub) sub.textContent = subtitle;
|
||||
|
||||
const okD = wirePanelPromoAnchor(aDonTop, j.donateUrl, j.donateLabel);
|
||||
const okT = wirePanelPromoAnchor(aTgTop, j.telegramUrl, j.telegramLabel);
|
||||
if (sepTop) sepTop.classList.toggle("hidden", !(okD && okT));
|
||||
|
||||
wirePanelPromoAnchor(aDonF, j.donateUrl, j.donateLabel);
|
||||
wirePanelPromoAnchor(aTgF, j.telegramUrl, j.telegramLabel);
|
||||
|
||||
const footerD = aDonF && !aDonF.classList.contains("hidden");
|
||||
const footerT = aTgF && !aTgF.classList.contains("hidden");
|
||||
if (gap1) gap1.classList.toggle("hidden", !(footerD || footerT));
|
||||
if (sepMid) sepMid.classList.toggle("hidden", !(footerD && footerT));
|
||||
|
||||
if (strip && (okD || okT)) strip.classList.remove("hidden");
|
||||
} catch {
|
||||
/* офлайн или сбой — блок остаётся скрытым */
|
||||
}
|
||||
}
|
||||
|
||||
function ensureEditionBannerInstallLogPanel() {
|
||||
if (!editionBanner) return null;
|
||||
let wrap = editionBanner.querySelector(".edition-banner-install-log");
|
||||
@@ -1603,4 +1659,5 @@ async function boot() {
|
||||
}
|
||||
}
|
||||
|
||||
void hydratePanelPromoFooter();
|
||||
boot();
|
||||
|
||||
@@ -7,9 +7,20 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;600;700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/styles.css?v=1.2.19">
|
||||
<link rel="stylesheet" href="/styles.css?v=1.2.20">
|
||||
</head>
|
||||
<body class="page">
|
||||
<aside id="panel-promo-strip" class="panel-promo-strip hidden" role="complementary" aria-label="Поддержка проекта">
|
||||
<div class="panel-promo-strip-inner">
|
||||
<p id="panel-promo-subtitle" class="panel-promo-subtitle muted"></p>
|
||||
<div class="panel-promo-links">
|
||||
<a id="panel-strip-donate" class="panel-promo-a hidden" target="_blank" rel="noopener noreferrer"></a>
|
||||
<span id="panel-strip-sep" class="panel-promo-sep hidden" aria-hidden="true">·</span>
|
||||
<a id="panel-strip-telegram" class="panel-promo-a hidden" target="_blank" rel="noopener noreferrer"></a>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div id="login-gate" class="gate hidden" aria-hidden="true">
|
||||
<div class="gate-card">
|
||||
<p class="eyebrow">Панель сервера</p>
|
||||
@@ -222,9 +233,13 @@
|
||||
<strong class="support-title">amnezia_web</strong>
|
||||
<span class="support-sep" aria-hidden="true">·</span>
|
||||
<a href="https://github.com/andrey271192/amnezia_web" target="_blank" rel="noopener noreferrer">GitHub</a>
|
||||
<span id="footer-promo-gap1" class="support-sep support-footer-promo-gap hidden" aria-hidden="true">·</span>
|
||||
<a id="footer-promo-donate" class="support-promo-link hidden" target="_blank" rel="noopener noreferrer"></a>
|
||||
<span id="footer-promo-sep-mid" class="support-sep hidden" aria-hidden="true">·</span>
|
||||
<a id="footer-promo-telegram" class="support-promo-link hidden" target="_blank" rel="noopener noreferrer"></a>
|
||||
</div>
|
||||
<p class="support-blurb">
|
||||
Базовая панель: AWG‑клиенты, статусы и MTProto‑прокси (Docker) без сторонней интеграции Telegram‑ботов.
|
||||
Базовая панель: AWG‑клиенты, статусы и MTProto‑прокси (Docker). Ссылки поддержки автора только в этом интерфейсе панели, не на отдельных пользовательских лендингах.
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
@@ -262,6 +277,6 @@
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<script src="/app.js?v=1.2.19" type="module"></script>
|
||||
<script src="/app.js?v=1.2.20" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -43,6 +43,64 @@ body.page {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.panel-promo-strip {
|
||||
flex-shrink: 0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 40;
|
||||
border-bottom: 1px solid rgba(180, 83, 9, 0.28);
|
||||
background: linear-gradient(90deg, rgba(254, 243, 199, 0.95), rgba(255, 237, 213, 0.97));
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
.panel-promo-strip-inner {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.5rem 1.25rem;
|
||||
max-width: min(1120px, 100%);
|
||||
margin: 0 auto;
|
||||
padding: 0.5rem clamp(1rem, 3vw, 1.5rem);
|
||||
}
|
||||
|
||||
.panel-promo-subtitle {
|
||||
margin: 0;
|
||||
flex: 1 1 16rem;
|
||||
font-size: 0.84rem;
|
||||
line-height: 1.5;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.panel-promo-links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.35rem 0.65rem;
|
||||
}
|
||||
|
||||
.panel-promo-a {
|
||||
font-weight: 650;
|
||||
font-size: 0.88rem;
|
||||
color: #9a3412;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.panel-promo-a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.panel-promo-sep {
|
||||
color: var(--muted);
|
||||
font-weight: 700;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.support-line .support-promo-link {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.shell {
|
||||
flex: 1 0 auto;
|
||||
max-width: min(1120px, 100%);
|
||||
|
||||
Reference in New Issue
Block a user