feat(mse): minijinja template engine, shared header partial

- tmpl.rs: OnceLock ile build-time compile edilmiş minijinja env
- _header.html: ortak header partial — logo, nav (active_page değişkeni), mode-toggle, çıkış
- index.html, bots.html, positions.html: hardcoded header → {% include "_header.html" %}
- bots.html: + Yeni Bot butonu header'dan page-toolbar'a taşındı

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mukan Erkin TÖRÜK 2026-04-19 20:26:15 +03:00
parent 5db9332157
commit 64806dd32e
7 changed files with 55 additions and 49 deletions

View file

@ -70,7 +70,7 @@ async fn bots_handler(
Some(t) => { let db = state.db.lock().await; db.session_exists(&t).unwrap_or(false) }
None => false,
};
if authed { axum::response::Html(include_str!("../web/bots.html")).into_response() }
if authed { axum::response::Html(crate::tmpl::render("bots.html", "bots")).into_response() }
else { Redirect::to("/").into_response() }
}
@ -83,7 +83,7 @@ async fn positions_handler(
Some(t) => { let db = state.db.lock().await; db.session_exists(&t).unwrap_or(false) }
None => false,
};
if authed { axum::response::Html(include_str!("../web/positions.html")).into_response() }
if authed { axum::response::Html(crate::tmpl::render("positions.html", "positions")).into_response() }
else { Redirect::to("/").into_response() }
}
@ -116,7 +116,7 @@ async fn dashboard_handler(
None => false,
};
if authed {
axum::response::Html(include_str!("../web/index.html")).into_response()
axum::response::Html(crate::tmpl::render("index.html", "dashboard")).into_response()
} else {
Redirect::to("/").into_response()
}

View file

@ -3,6 +3,7 @@ mod binance;
mod bot;
mod config;
mod storage;
mod tmpl;
use std::path::Path;
use std::sync::Arc;

24
src/tmpl.rs Normal file
View file

@ -0,0 +1,24 @@
use std::sync::OnceLock;
use minijinja::{Environment, context};
static ENV: OnceLock<Environment<'static>> = OnceLock::new();
fn env() -> &'static Environment<'static> {
ENV.get_or_init(|| {
let mut e = Environment::new();
e.add_template_owned("_header.html", include_str!("web/_header.html").to_string()).unwrap();
e.add_template_owned("index.html", include_str!("web/index.html").to_string()).unwrap();
e.add_template_owned("bots.html", include_str!("web/bots.html").to_string()).unwrap();
e.add_template_owned("bot.html", include_str!("web/bot.html").to_string()).unwrap();
e.add_template_owned("positions.html", include_str!("web/positions.html").to_string()).unwrap();
e
})
}
pub fn render(template: &str, active_page: &str) -> String {
env()
.get_template(template)
.unwrap()
.render(context! { active_page => active_page })
.unwrap()
}

15
src/web/_header.html Normal file
View file

@ -0,0 +1,15 @@
<header>
<div class="logo"><span class="logo-crypto">Crypto</span><span class="logo-fox">Fox</span></div>
<div class="h-sep"></div>
<nav>
<a href="/dashboard" class="nav-link{% if active_page == 'dashboard' %} active{% endif %}">Dashboard</a>
<a href="/bots" class="nav-link{% if active_page == 'bots' %} active{% endif %}">Botlar</a>
<a href="/positions" class="nav-link{% if active_page == 'positions' %} active{% endif %}">Pozisyonlar</a>
</nav>
<div class="spacer"></div>
<div class="mode-toggle">
<button class="mode-btn" id="btn-live">Canlı</button>
<button class="mode-btn" id="btn-testnet">Testnet</button>
</div>
<button class="btn btn-logout" onclick="logout()">Çıkış</button>
</header>

View file

@ -46,6 +46,8 @@
/* ── MAIN ── */
main { padding: 24px; max-width: 1400px; margin: 0 auto; }
.page-toolbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; }
.page-title { font-size: 16px; font-weight: 600; }
/* ── GROUP HEADER ── */
.group-label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); margin-bottom: 12px; display: flex; align-items: center; gap: 8px; }
@ -116,24 +118,14 @@
</head>
<body>
<header>
<div class="logo"><span class="logo-crypto">Crypto</span><span class="logo-fox">Fox</span></div>
<div class="h-sep"></div>
<nav>
<a href="/dashboard" class="nav-link">Dashboard</a>
<a href="/bots" class="nav-link active">Botlar</a>
<a href="/positions" class="nav-link">Pozisyonlar</a>
</nav>
<div class="spacer"></div>
<div class="mode-toggle">
<button class="mode-btn" id="btn-live" onclick="setMode('live')">Canlı</button>
<button class="mode-btn" id="btn-testnet" onclick="setMode('testnet')">Testnet</button>
</div>
<button class="btn btn-primary" onclick="openModal()">+ Yeni Bot</button>
<button class="btn btn-logout" onclick="logout()">Çıkış</button>
</header>
{% include "_header.html" %}
<main>
<div class="page-toolbar">
<span class="page-title">Botlar</span>
<button class="btn btn-primary" onclick="openModal()">+ Yeni Bot</button>
</div>
<div class="group-section group-running">
<div class="group-label">Çalışıyor <span class="count" id="count-running">0</span></div>
<div class="cards-grid" id="grid-running"><div class="empty-state">Çalışan bot yok</div></div>

View file

@ -144,21 +144,8 @@
</div>
</div>
<header>
<div class="logo"><span class="logo-crypto">Crypto</span><span class="logo-fox">Fox</span></div>
<div class="h-sep"></div>
<nav>
<a href="/dashboard" class="nav-link active">Dashboard</a>
<a href="/bots" class="nav-link">Botlar</a>
<a href="/positions" class="nav-link">Pozisyonlar</a>
</nav>
<div class="spacer"></div>
<div class="mode-toggle">
<button class="mode-btn" id="btn-testnet" onclick="switchMode('testnet')">Testnet</button>
<button class="mode-btn" id="btn-live" onclick="switchMode('live')">Canlı</button>
</div>
<button class="btn btn-logout" onclick="logout()">Çıkış</button>
</header>
{% include "_header.html" %}
<main>
<section>

View file

@ -45,21 +45,8 @@
</head>
<body>
<header>
<div class="logo"><span class="logo-crypto">Crypto</span><span class="logo-fox">Fox</span></div>
<div class="h-sep"></div>
<nav>
<a href="/dashboard" class="nav-link">Dashboard</a>
<a href="/bots" class="nav-link">Botlar</a>
<a href="/positions" class="nav-link active">Pozisyonlar</a>
</nav>
<div class="spacer"></div>
<div class="mode-toggle">
<button class="mode-btn" id="btn-testnet">Testnet</button>
<button class="mode-btn" id="btn-live">Canlı</button>
</div>
<button class="btn btn-logout" onclick="logout()">Çıkış</button>
</header>
{% include "_header.html" %}
<main>
<section>