refactor: split main.py i moduler (config/cache/store/services/authentik) + async dashboard (TTFB ~6ms, indhold via /dashboard-content) + TTL-cache på Authentik-læsninger
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+27
-55
@@ -1,61 +1,25 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Weircon — forside{% endblock %}
|
||||
|
||||
{% macro card(a) %}
|
||||
<div class="card" data-slug="{{ a.slug }}" data-search="{{ (a.name ~ ' ' ~ a.desc ~ ' ' ~ a.host ~ ' ' ~ a.group)|lower }}">
|
||||
<a class="card-link" href="{{ a.url }}" aria-label="{{ a.name }}"></a>
|
||||
<button type="button" class="fav{% if a.slug in favorites %} on{% endif %}" data-slug="{{ a.slug }}"
|
||||
aria-pressed="{{ 'true' if a.slug in favorites else 'false' }}" title="Markér som favorit" aria-label="Favorit">★</button>
|
||||
<div class="head">
|
||||
<span class="ico">{% if a.icon and a.icon.startswith('http') %}<img src="{{ a.icon }}" alt="">{% else %}{{ (a.name or '?')[0]|upper }}{% endif %}</span>
|
||||
<span class="name">{{ a.name }}</span>
|
||||
</div>
|
||||
<div class="sub" title="{{ a.desc if a.desc else a.host }}">{{ a.desc if a.desc else a.host }}</div>
|
||||
<div class="go">Åbn →</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Hej, {{ user.name }}</h1>
|
||||
<p class="lead">Tjenester du har adgang til.</p>
|
||||
{% if error %}<div class="err">{{ error }}</div>{% endif %}
|
||||
{% if saved %}<div class="flash">{{ saved }}</div>{% endif %}
|
||||
{% if groups %}
|
||||
<div class="toolbar">
|
||||
<div class="filter">
|
||||
<span class="mag">⌕</span>
|
||||
<input id="appfilter" type="search" placeholder="Filtrér…" autocomplete="off" aria-label="Filtrér tjenester" />
|
||||
</div>
|
||||
<div class="viewtoggle" role="group" aria-label="Visning">
|
||||
<button type="button" data-view="grouped" class="{{ 'on' if view != 'alpha' }}">Grupperet</button>
|
||||
<button type="button" data-view="alpha" class="{{ 'on' if view == 'alpha' }}">Alfabetisk</button>
|
||||
|
||||
<div id="dash">
|
||||
<div class="grid" style="margin-top:1.6rem" aria-hidden="true">
|
||||
{% for i in range(8) %}
|
||||
<div class="card skel"><div class="head"><span class="ico"></span><span class="sk" style="width:55%;height:1em"></span></div><span class="sk" style="width:80%;height:.8em;margin-top:.8rem"></span></div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section id="favsec" class="appgroup"{% if not fav_apps %} style="display:none"{% endif %}>
|
||||
<h2>★ Favoritter</h2>
|
||||
<div class="grid" id="favgrid">{% for a in fav_apps %}{{ card(a) }}{% endfor %}</div>
|
||||
</section>
|
||||
|
||||
<div id="view-grouped"{% if view == 'alpha' %} hidden{% endif %}>
|
||||
{% for g in groups %}
|
||||
<section class="appgroup"><h2>{{ g.name }}</h2><div class="grid">{% for a in g.apps %}{{ card(a) }}{% endfor %}</div></section>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div id="view-alpha"{% if view != 'alpha' %} hidden{% endif %}>
|
||||
<section class="appgroup"><h2>Alle tjenester</h2><div class="grid">{% for a in apps_sorted %}{{ card(a) }}{% endfor %}</div></section>
|
||||
</div>
|
||||
<p id="nomatch" class="muted" style="display:none;margin-top:1rem">Ingen tjenester matcher.</p>
|
||||
{% elif not error %}
|
||||
<p class="muted" style="margin-top:1.6rem">Ingen apps fundet for din konto endnu.</p>
|
||||
{% endif %}
|
||||
{% if is_admin %}
|
||||
<section class="appgroup">
|
||||
<h2>Administration</h2>
|
||||
<div class="grid">
|
||||
<a class="card" href="/admin" style="position:relative">
|
||||
<div class="head"><span class="ico">⚙️</span><span class="name">Admin-panel</span></div>
|
||||
<div class="sub">IP-labels, cookies, statistik, app-beskrivelser m.m.</div>
|
||||
<div class="sub">Brugere, grupper, drift, ikoner m.m.</div>
|
||||
<div class="go">Åbn →</div>
|
||||
</a>
|
||||
</div>
|
||||
@@ -75,6 +39,9 @@
|
||||
border:0; background:transparent; color:var(--border); cursor:pointer; font-size:1.05rem; line-height:1; padding:0; transition:color .15s, transform .15s; }
|
||||
.fav:hover { color:var(--muted); transform:scale(1.15); }
|
||||
.fav.on { color:#f5c518; }
|
||||
.card.skel { opacity:.5; pointer-events:none; }
|
||||
.card.skel .ico { background:var(--surface2); }
|
||||
.sk { display:inline-block; background:var(--surface2); border-radius:5px; }
|
||||
[hidden] { display:none !important; }
|
||||
</style>
|
||||
<script>
|
||||
@@ -90,29 +57,33 @@
|
||||
b.classList.toggle('on',on); b.setAttribute('aria-pressed', on?'true':'false');
|
||||
});
|
||||
var favgrid=document.getElementById('favgrid'), favsec=document.getElementById('favsec');
|
||||
if(!favgrid) return;
|
||||
var has=favgrid.querySelector('.card[data-slug="'+CSS.escape(slug)+'"]');
|
||||
if(on && !has){
|
||||
var src=document.querySelector('#view-grouped .card[data-slug="'+CSS.escape(slug)+'"]') || document.querySelector('.card[data-slug="'+CSS.escape(slug)+'"]');
|
||||
if(src){ var c=src.cloneNode(true); favgrid.appendChild(c); }
|
||||
if(src){ favgrid.appendChild(src.cloneNode(true)); }
|
||||
} else if(!on && has){ has.remove(); }
|
||||
favsec.style.display = favgrid.children.length ? '' : 'none';
|
||||
if(favsec) favsec.style.display = favgrid.children.length ? '' : 'none';
|
||||
}
|
||||
// delegation: virker også for async-indlæst indhold
|
||||
document.addEventListener('click',function(e){
|
||||
var fav=e.target.closest && e.target.closest('.fav');
|
||||
if(fav){ e.preventDefault(); e.stopPropagation();
|
||||
var slug=fav.getAttribute('data-slug'), on=!fav.classList.contains('on');
|
||||
setFav(slug,on);
|
||||
fetch('/api/favorite',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({slug:slug,on:on})});
|
||||
return;
|
||||
}
|
||||
return; }
|
||||
var vb=e.target.closest && e.target.closest('.viewtoggle button');
|
||||
if(vb){ var v=vb.getAttribute('data-view'); setView(v);
|
||||
fetch('/api/view',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({view:v})});
|
||||
}
|
||||
fetch('/api/view',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({view:v})}); }
|
||||
});
|
||||
// filter på tværs af synlige kort
|
||||
var inp=document.getElementById('appfilter');
|
||||
if(inp){
|
||||
document.addEventListener('keydown',function(e){
|
||||
var inp=document.getElementById('appfilter');
|
||||
if(inp && e.key==='/' && document.activeElement!==inp && !/input|textarea/i.test(document.activeElement.tagName)){ e.preventDefault(); inp.focus(); }
|
||||
});
|
||||
function initDash(){
|
||||
var inp=document.getElementById('appfilter');
|
||||
if(!inp || inp.__wired) return; inp.__wired=true;
|
||||
inp.addEventListener('input',function(){
|
||||
var q=inp.value.trim().toLowerCase(), shown=0;
|
||||
document.querySelectorAll('.card[data-search]').forEach(function(c){
|
||||
@@ -125,10 +96,11 @@
|
||||
});
|
||||
var nm=document.getElementById('nomatch'); if(nm) nm.style.display=shown?'none':'block';
|
||||
});
|
||||
document.addEventListener('keydown',function(e){
|
||||
if(e.key==='/' && document.activeElement!==inp && !/input|textarea/i.test(document.activeElement.tagName)){ e.preventDefault(); inp.focus(); }
|
||||
});
|
||||
}
|
||||
fetch('/dashboard-content',{headers:{'X-Requested-With':'fetch'}})
|
||||
.then(function(r){ if(!r.ok) throw 0; return r.text(); })
|
||||
.then(function(html){ document.getElementById('dash').innerHTML=html; initDash(); })
|
||||
.catch(function(){ document.getElementById('dash').innerHTML='<p class="muted" style="margin-top:1.6rem">Kunne ikke hente tjenester — prøv at genindlæse siden.</p>'; });
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
{% macro card(a) %}
|
||||
<div class="card" data-slug="{{ a.slug }}" data-search="{{ (a.name ~ ' ' ~ a.desc ~ ' ' ~ a.host ~ ' ' ~ a.group)|lower }}">
|
||||
<a class="card-link" href="{{ a.url }}" aria-label="{{ a.name }}"></a>
|
||||
<button type="button" class="fav{% if a.slug in favorites %} on{% endif %}" data-slug="{{ a.slug }}"
|
||||
aria-pressed="{{ 'true' if a.slug in favorites else 'false' }}" title="Markér som favorit" aria-label="Favorit">★</button>
|
||||
<div class="head">
|
||||
<span class="ico">{% if a.icon and a.icon.startswith('http') %}<img src="{{ a.icon }}" alt="">{% else %}{{ (a.name or '?')[0]|upper }}{% endif %}</span>
|
||||
<span class="name">{{ a.name }}</span>
|
||||
</div>
|
||||
<div class="sub" title="{{ a.desc if a.desc else a.host }}">{{ a.desc if a.desc else a.host }}</div>
|
||||
<div class="go">Åbn →</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
{% if error %}<div class="err">{{ error }}</div>{% endif %}
|
||||
{% if groups %}
|
||||
<div class="toolbar">
|
||||
<div class="filter"><span class="mag">⌕</span>
|
||||
<input id="appfilter" type="search" placeholder="Filtrér…" autocomplete="off" aria-label="Filtrér tjenester"></div>
|
||||
<div class="viewtoggle" role="group" aria-label="Visning">
|
||||
<button type="button" data-view="grouped" class="{{ 'on' if view != 'alpha' }}">Grupperet</button>
|
||||
<button type="button" data-view="alpha" class="{{ 'on' if view == 'alpha' }}">Alfabetisk</button>
|
||||
</div>
|
||||
</div>
|
||||
<section id="favsec" class="appgroup"{% if not fav_apps %} style="display:none"{% endif %}>
|
||||
<h2>★ Favoritter</h2>
|
||||
<div class="grid" id="favgrid">{% for a in fav_apps %}{{ card(a) }}{% endfor %}</div>
|
||||
</section>
|
||||
<div id="view-grouped"{% if view == 'alpha' %} hidden{% endif %}>
|
||||
{% for g in groups %}
|
||||
<section class="appgroup"><h2>{{ g.name }}</h2><div class="grid">{% for a in g.apps %}{{ card(a) }}{% endfor %}</div></section>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div id="view-alpha"{% if view != 'alpha' %} hidden{% endif %}>
|
||||
<section class="appgroup"><h2>Alle tjenester</h2><div class="grid">{% for a in apps_sorted %}{{ card(a) }}{% endfor %}</div></section>
|
||||
</div>
|
||||
<p id="nomatch" class="muted" style="display:none;margin-top:1rem">Ingen tjenester matcher.</p>
|
||||
{% elif not error %}
|
||||
<p class="muted" style="margin-top:1.6rem">Ingen apps fundet for din konto endnu.</p>
|
||||
{% endif %}
|
||||
Reference in New Issue
Block a user