İlk commit: Stepstead web (statik panel + db)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
commit
dea01dd6a8
18 changed files with 3764 additions and 0 deletions
214
nav.js
Normal file
214
nav.js
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
/* Stepstead ortak header + footer.
|
||||
Kullanım: <script src="/nav.js?v=7" data-theme="light|dark" defer></script>
|
||||
Panel linkleri role/izin bazlı görünür: GET /auth/me → permissions[].
|
||||
İzinler: upload, lore, map_view, map_edit, users ("*" = admin). */
|
||||
(function(){
|
||||
"use strict";
|
||||
const API="https://api.stepstead.com";
|
||||
const PUBLIC_LINKS=[
|
||||
{href:"/",label:"🏠 Ana Sayfa"},
|
||||
{href:"/db/",label:"📖 Craft Veritabanı"},
|
||||
{href:"/items/",label:"🎒 Eşyalar"},
|
||||
];
|
||||
const PANEL_LINKS=[
|
||||
{href:"/assets/",label:"🎨 Asset Takip",perms:["upload"]},
|
||||
{href:"/assets/upload.html",label:"📤 Dosya Yükle",perms:["upload"]},
|
||||
{href:"/assets/lore.html",label:"📜 Lore",perms:["lore"]},
|
||||
{href:"/assets/quests.html",label:"🗡 Görevler",perms:["lore"]},
|
||||
{href:"/assets/npcs.html",label:"🧙 NPC'ler",perms:["npc"]},
|
||||
{href:"/assets/chars.html",label:"⚔ Karakterler",perms:["chars"]},
|
||||
{href:"/assets/map-editor.html",label:"🗺 Harita Editörü",perms:["map_view","map_edit"]},
|
||||
{href:"/assets/users.html",label:"👥 Kullanıcılar",perms:["users"]},
|
||||
];
|
||||
const script=document.currentScript;
|
||||
const theme=(script&&script.dataset.theme)==="light"?"light":"dark";
|
||||
|
||||
const css=`
|
||||
.ss-nav{font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;line-height:1.4;position:relative}
|
||||
.ss-nav .ss-in{max-width:1040px;margin:0 auto;padding:0 16px;display:flex;align-items:center;gap:4px;flex-wrap:wrap;min-height:46px}
|
||||
.ss-nav .ss-brand{font-weight:700;font-size:15px;text-decoration:none;margin-right:10px;letter-spacing:.2px}
|
||||
.ss-nav a.ss-lnk{text-decoration:none;font-size:13px;padding:5px 10px;border-radius:6px;white-space:nowrap}
|
||||
.ss-nav .ss-adm{display:none;align-items:center;gap:4px;flex-wrap:wrap}
|
||||
.ss-nav .ss-adm.on{display:flex}
|
||||
.ss-nav .ss-admtag{font-size:10px;text-transform:uppercase;letter-spacing:.08em;opacity:.6;margin:0 2px 0 8px}
|
||||
.ss-nav .ss-authbtn{margin-left:auto;background:none;border:1px solid transparent;font-size:13px;padding:5px 10px;border-radius:6px;cursor:pointer;white-space:nowrap}
|
||||
.ss-nav .ss-loginbox{display:none;position:absolute;right:16px;top:100%;margin-top:6px;z-index:50;padding:12px;border-radius:10px;box-shadow:0 8px 28px rgba(0,0,0,.35);min-width:230px}
|
||||
.ss-nav .ss-loginbox.on{display:block}
|
||||
.ss-nav .ss-loginbox input{display:block;width:100%;box-sizing:border-box;margin-bottom:8px;padding:7px 9px;border-radius:6px;font-size:13px}
|
||||
.ss-nav .ss-loginbox button{width:100%;padding:7px;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer}
|
||||
.ss-nav .ss-loginmsg{font-size:12px;margin-top:6px;min-height:14px}
|
||||
.ss-foot{font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;font-size:12px;margin-top:48px}
|
||||
.ss-foot .ss-in{max-width:1040px;margin:0 auto;padding:16px;display:flex;gap:14px;flex-wrap:wrap;align-items:center}
|
||||
.ss-foot a{text-decoration:none}
|
||||
.ss-foot .ss-sp{margin-left:auto}
|
||||
/* dark */
|
||||
.ss-dark.ss-nav{background:#0b1016;border-bottom:1px solid #2c3a4f}
|
||||
.ss-dark.ss-nav .ss-brand{color:#e8b923}
|
||||
.ss-dark.ss-nav a.ss-lnk{color:#8b99a8}
|
||||
.ss-dark.ss-nav a.ss-lnk:hover{color:#e6edf3;background:#1a2230}
|
||||
.ss-dark.ss-nav a.ss-lnk.on{color:#e8b923;background:#1a2230}
|
||||
.ss-dark.ss-nav .ss-admtag{color:#8b99a8}
|
||||
.ss-dark.ss-nav .ss-authbtn{color:#8b99a8;border-color:#2c3a4f;background:#0b1016}
|
||||
.ss-dark.ss-nav .ss-authbtn:hover{color:#e8b923;border-color:#e8b923}
|
||||
.ss-dark.ss-nav .ss-loginbox{background:#1a2230;border:1px solid #2c3a4f}
|
||||
.ss-dark.ss-nav .ss-loginbox input{background:#222d3d;color:#e6edf3;border:1px solid #2c3a4f}
|
||||
.ss-dark.ss-nav .ss-loginbox button{background:#e8b923;color:#1a1300}
|
||||
.ss-dark.ss-nav .ss-loginmsg{color:#e5534b}
|
||||
.ss-dark.ss-foot{background:#0b1016;border-top:1px solid #2c3a4f;color:#8b99a8}
|
||||
.ss-dark.ss-foot a{color:#8b99a8}
|
||||
.ss-dark.ss-foot a:hover{color:#e8b923}
|
||||
/* light */
|
||||
.ss-light.ss-nav{background:#2f4a35;border-bottom:1px solid #253b2a}
|
||||
.ss-light.ss-nav .ss-brand{color:#fff}
|
||||
.ss-light.ss-nav a.ss-lnk{color:rgba(255,255,255,.75)}
|
||||
.ss-light.ss-nav a.ss-lnk:hover{color:#fff;background:rgba(255,255,255,.12)}
|
||||
.ss-light.ss-nav a.ss-lnk.on{color:#fff;background:rgba(255,255,255,.18)}
|
||||
.ss-light.ss-nav .ss-admtag{color:rgba(255,255,255,.6)}
|
||||
.ss-light.ss-nav .ss-authbtn{color:rgba(255,255,255,.75);border-color:rgba(255,255,255,.3);background:transparent}
|
||||
.ss-light.ss-nav .ss-authbtn:hover{color:#fff;border-color:#fff}
|
||||
.ss-light.ss-nav .ss-loginbox{background:#fff;border:1px solid #e4e0d6}
|
||||
.ss-light.ss-nav .ss-loginbox input{background:#f5f3ee;color:#2d3a2e;border:1px solid #e4e0d6}
|
||||
.ss-light.ss-nav .ss-loginbox button{background:#3a5a40;color:#fff}
|
||||
.ss-light.ss-nav .ss-loginmsg{color:#c0392b}
|
||||
.ss-light.ss-foot{background:transparent;border-top:1px solid #e4e0d6;color:#77806f}
|
||||
.ss-light.ss-foot a{color:#77806f}
|
||||
.ss-light.ss-foot a:hover{color:#3a5a40}
|
||||
`;
|
||||
|
||||
let PERMS=[];
|
||||
function tok(){return localStorage.getItem("ss_upload_token")||"";}
|
||||
function hasPerm(p){return PERMS.includes("*")||PERMS.includes(p);}
|
||||
function anyPerm(list){return list.some(hasPerm);}
|
||||
|
||||
function norm(p){return p.replace(/index\.html$/,"");}
|
||||
function link(l){
|
||||
const a=document.createElement("a");
|
||||
a.className="ss-lnk"+(norm(location.pathname)===norm(l.href)?" on":"");
|
||||
a.href=l.href;a.textContent=l.label;
|
||||
return a;
|
||||
}
|
||||
|
||||
function applyPerms(role){
|
||||
const adm=document.getElementById("ssAdmLinks");
|
||||
adm.innerHTML="";
|
||||
// Panel yetkisi olan (bir rolü/izni bulunan) herkes TÜM sekmeleri görür.
|
||||
// İşlem yetkisi sayfa/backend tarafında izne göre ayrıca denetlenir.
|
||||
const isPanel=PERMS.length>0;
|
||||
if(isPanel){
|
||||
const tag=document.createElement("span");
|
||||
tag.className="ss-admtag";tag.textContent=role||"panel";
|
||||
adm.appendChild(tag);
|
||||
for(const l of PANEL_LINKS)adm.appendChild(link(l));
|
||||
adm.classList.add("on");
|
||||
}else adm.classList.remove("on");
|
||||
document.getElementById("ssAuthBtn").textContent=PERMS.length?"🚪 Çıkış":"🔑 Giriş";
|
||||
if(PERMS.length)sessionStorage.setItem("ss_nav_perms",JSON.stringify({p:PERMS,r:role||""}));
|
||||
else sessionStorage.removeItem("ss_nav_perms");
|
||||
}
|
||||
|
||||
function build(){
|
||||
const st=document.createElement("style");st.textContent=css;document.head.appendChild(st);
|
||||
|
||||
const nav=document.createElement("nav");
|
||||
nav.className="ss-nav ss-"+theme;
|
||||
const inner=document.createElement("div");inner.className="ss-in";
|
||||
const brand=document.createElement("a");
|
||||
brand.className="ss-brand";brand.href="/";brand.textContent="👣 Stepstead";
|
||||
inner.appendChild(brand);
|
||||
for(const l of PUBLIC_LINKS)inner.appendChild(link(l));
|
||||
const adm=document.createElement("span");adm.className="ss-adm";adm.id="ssAdmLinks";
|
||||
inner.appendChild(adm);
|
||||
|
||||
const authBtn=document.createElement("button");
|
||||
authBtn.className="ss-authbtn";authBtn.id="ssAuthBtn";authBtn.textContent="🔑 Giriş";
|
||||
authBtn.onclick=()=>{
|
||||
if(PERMS.length){
|
||||
localStorage.removeItem("ss_upload_token");
|
||||
PERMS=[];applyPerms(null);
|
||||
return;
|
||||
}
|
||||
document.getElementById("ssLoginBox").classList.toggle("on");
|
||||
document.getElementById("ssLgEmail").focus();
|
||||
};
|
||||
inner.appendChild(authBtn);
|
||||
|
||||
const box=document.createElement("div");
|
||||
box.className="ss-loginbox";box.id="ssLoginBox";
|
||||
box.innerHTML=`
|
||||
<input type="email" id="ssLgEmail" placeholder="E-posta" autocomplete="username">
|
||||
<input type="password" id="ssLgPass" placeholder="Şifre" autocomplete="current-password">
|
||||
<button id="ssLgBtn">Giriş yap</button>
|
||||
<div class="ss-loginmsg" id="ssLgMsg"></div>`;
|
||||
nav.appendChild(inner);
|
||||
nav.appendChild(box);
|
||||
document.body.prepend(nav);
|
||||
|
||||
document.getElementById("ssLgBtn").onclick=doLogin;
|
||||
document.getElementById("ssLgPass").addEventListener("keydown",e=>{if(e.key==="Enter")doLogin();});
|
||||
document.addEventListener("click",e=>{
|
||||
if(!box.contains(e.target)&&e.target!==authBtn)box.classList.remove("on");
|
||||
});
|
||||
|
||||
const foot=document.createElement("footer");
|
||||
foot.className="ss-foot ss-"+theme;
|
||||
foot.innerHTML=`<div class="ss-in">
|
||||
<span>© ${new Date().getFullYear()} Stepstead — her adımın değer kazanır</span>
|
||||
<span class="ss-sp"><a href="/">Ana Sayfa</a></span>
|
||||
<span><a href="/db/">Craft Veritabanı</a></span>
|
||||
</div>`;
|
||||
document.body.appendChild(foot);
|
||||
}
|
||||
|
||||
async function doLogin(){
|
||||
const msg=document.getElementById("ssLgMsg");
|
||||
msg.textContent="…";
|
||||
try{
|
||||
const r=await fetch(API+"/auth/login",{method:"POST",headers:{"Content-Type":"application/json"},
|
||||
body:JSON.stringify({email:document.getElementById("ssLgEmail").value.trim(),
|
||||
password:document.getElementById("ssLgPass").value})});
|
||||
const b=await r.json().catch(()=>({}));
|
||||
if(!r.ok||!b.access_token){msg.textContent=b.detail||"Giriş başarısız";return;}
|
||||
localStorage.setItem("ss_upload_token",b.access_token);
|
||||
msg.textContent="";
|
||||
await refresh();
|
||||
if(PERMS.length){
|
||||
document.getElementById("ssLoginBox").classList.remove("on");
|
||||
document.getElementById("ssLgPass").value="";
|
||||
}else msg.textContent="Giriş tamam — ama bu hesabın panel yetkisi yok";
|
||||
}catch(e){msg.textContent="Bağlantı hatası";}
|
||||
}
|
||||
|
||||
async function refresh(){
|
||||
if(!tok()){PERMS=[];applyPerms(null);return;}
|
||||
try{
|
||||
const r=await fetch(API+"/auth/me",{headers:{Authorization:"Bearer "+tok()}});
|
||||
if(r.ok){
|
||||
const u=await r.json();
|
||||
PERMS=u.permissions||[];
|
||||
applyPerms(u.role);
|
||||
}else{
|
||||
if(r.status===401)localStorage.removeItem("ss_upload_token");
|
||||
PERMS=[];applyPerms(null);
|
||||
}
|
||||
}catch(e){
|
||||
// geçici ağ hatası: cache'e güven
|
||||
try{
|
||||
const c=JSON.parse(sessionStorage.getItem("ss_nav_perms")||"null");
|
||||
if(c){PERMS=c.p;applyPerms(c.r);return;}
|
||||
}catch(_){}
|
||||
PERMS=[];applyPerms(null);
|
||||
}
|
||||
}
|
||||
window.ssNavRefresh=refresh;
|
||||
|
||||
function init(){
|
||||
build();
|
||||
try{
|
||||
const c=JSON.parse(sessionStorage.getItem("ss_nav_perms")||"null");
|
||||
if(c&&tok()){PERMS=c.p;applyPerms(c.r);}
|
||||
}catch(_){}
|
||||
refresh();
|
||||
}
|
||||
if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",init);
|
||||
else init();
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue