feat(webui): add summarize loading indicator, wrap long lines, auto-load last 25 on first channel, optional auto-follow (poll every 3s)
This commit is contained in:
parent
f78dc43374
commit
118cb921f0
1 changed files with 27 additions and 6 deletions
|
|
@ -217,7 +217,7 @@ func (s *Server) handleUI(w http.ResponseWriter, r *http.Request) {
|
||||||
<link rel="stylesheet" href="https://unpkg.com/@picocss/pico@2/css/pico.min.css">
|
<link rel="stylesheet" href="https://unpkg.com/@picocss/pico@2/css/pico.min.css">
|
||||||
<style>
|
<style>
|
||||||
body { padding: 1rem; }
|
body { padding: 1rem; }
|
||||||
pre { max-height: 50vh; overflow: auto; }
|
pre { max-height: 50vh; overflow: auto; white-space: pre-wrap; word-break: break-word; overflow-wrap: anywhere; }
|
||||||
.row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
|
.row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.grid { grid-template-columns: 1fr !important; }
|
.grid { grid-template-columns: 1fr !important; }
|
||||||
|
|
@ -225,7 +225,7 @@ func (s *Server) handleUI(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
const st={token: localStorage.getItem('token')||''};
|
const st={token: localStorage.getItem('token')||'', tailTimer:null, tailLoading:false};
|
||||||
function setToken(v){ st.token=v; localStorage.setItem('token', v); }
|
function setToken(v){ st.token=v; localStorage.setItem('token', v); }
|
||||||
async function api(path, params){
|
async function api(path, params){
|
||||||
const url = new URL(path, window.location.origin);
|
const url = new URL(path, window.location.origin);
|
||||||
|
|
@ -248,26 +248,43 @@ func (s *Server) handleUI(w http.ResponseWriter, r *http.Request) {
|
||||||
async function loadChannels(){
|
async function loadChannels(){
|
||||||
try{ const data = await api('/api/channels');
|
try{ const data = await api('/api/channels');
|
||||||
const sel = document.getElementById('channel'); sel.innerHTML = '';
|
const sel = document.getElementById('channel'); sel.innerHTML = '';
|
||||||
data.forEach(c=>{ const o=document.createElement('option'); o.value=c; o.textContent=c; sel.appendChild(o); });
|
data.forEach(function(c){ const o=document.createElement('option'); o.value=c; o.textContent=c; sel.appendChild(o); });
|
||||||
|
// Auto-tail default channel (first) with 25 lines
|
||||||
|
if(data.length>0){ document.getElementById('limit').value = '25'; doTail(); }
|
||||||
}catch(e){ console.error(e); }
|
}catch(e){ console.error(e); }
|
||||||
}
|
}
|
||||||
async function doTail(){
|
async function doTail(){
|
||||||
|
if(st.tailLoading) return; st.tailLoading=true;
|
||||||
const ch = document.getElementById('channel').value;
|
const ch = document.getElementById('channel').value;
|
||||||
const lim = document.getElementById('limit').value || '100';
|
const lim = document.getElementById('limit').value || '100';
|
||||||
try{ const data = await api('/api/tail',{query:{channel:ch,limit:lim}});
|
try{ const data = await api('/api/tail',{query:{channel:ch,limit:lim}});
|
||||||
const out = data.map(m => (m.time + ' ' + m.author + ': ' + m.body)).join('\n');
|
const out = data.map(m => (m.time + ' ' + m.author + ': ' + m.body)).join('\n');
|
||||||
document.getElementById('tail').textContent = out;
|
document.getElementById('tail').textContent = out;
|
||||||
}catch(e){ document.getElementById('tail').textContent = 'error: '+e; }
|
}catch(e){ document.getElementById('tail').textContent = 'error: '+e; }
|
||||||
|
st.tailLoading=false;
|
||||||
}
|
}
|
||||||
async function doSumm(){
|
async function doSumm(){
|
||||||
const ch = document.getElementById('channel').value;
|
const ch = document.getElementById('channel').value;
|
||||||
const win = document.getElementById('window').value || '6h';
|
const win = document.getElementById('window').value || '6h';
|
||||||
const push = document.getElementById('push').checked ? '1' : '0';
|
const push = document.getElementById('push').checked ? '1' : '0';
|
||||||
|
const btn = document.getElementById('summBtn');
|
||||||
|
const prog = document.getElementById('summProg');
|
||||||
|
btn.disabled = true; prog.style.display = 'inline-block';
|
||||||
try{ const data = await api('/api/trigger',{query:{channel:ch,window:win,push:push}});
|
try{ const data = await api('/api/trigger',{query:{channel:ch,window:win,push:push}});
|
||||||
document.getElementById('summary').textContent = data.summary || '(empty)';
|
document.getElementById('summary').textContent = data.summary || '(empty)';
|
||||||
}catch(e){ document.getElementById('summary').textContent = 'error: '+e; }
|
}catch(e){ document.getElementById('summary').textContent = 'error: '+e; }
|
||||||
|
btn.disabled = false; prog.style.display = 'none';
|
||||||
}
|
}
|
||||||
window.addEventListener('DOMContentLoaded', ()=>{ loadInfo(); loadChannels(); });
|
window.addEventListener('DOMContentLoaded', ()=>{ loadInfo(); loadChannels(); });
|
||||||
|
function onFollowToggle(cb){
|
||||||
|
if(cb.checked){
|
||||||
|
if(st.tailTimer) clearInterval(st.tailTimer);
|
||||||
|
st.tailTimer = setInterval(doTail, 3000);
|
||||||
|
}else{
|
||||||
|
if(st.tailTimer) { clearInterval(st.tailTimer); st.tailTimer=null; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onChannelChange(){ doTail(); }
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
@ -276,8 +293,9 @@ func (s *Server) handleUI(w http.ResponseWriter, r *http.Request) {
|
||||||
<article>
|
<article>
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<label>Auth token<input type="password" id="tok" placeholder="HTTP_TOKEN" oninput="setToken(this.value)"/></label>
|
<label>Auth token<input type="password" id="tok" placeholder="HTTP_TOKEN" oninput="setToken(this.value)"/></label>
|
||||||
<label>Channel<select id="channel"></select></label>
|
<label>Channel<select id="channel" onchange="onChannelChange()"></select></label>
|
||||||
<label>Limit<input type="number" id="limit" value="100"/></label>
|
<label>Limit<input type="number" id="limit" value="25"/></label>
|
||||||
|
<label><input type="checkbox" id="follow" onchange="onFollowToggle(this)"/> Follow</label>
|
||||||
<button onclick="doTail()">Refresh tail</button>
|
<button onclick="doTail()">Refresh tail</button>
|
||||||
</div>
|
</div>
|
||||||
<pre id="tail"></pre>
|
<pre id="tail"></pre>
|
||||||
|
|
@ -286,7 +304,10 @@ func (s *Server) handleUI(w http.ResponseWriter, r *http.Request) {
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<label>Window<input type="text" id="window" value="6h"/></label>
|
<label>Window<input type="text" id="window" value="6h"/></label>
|
||||||
<label><input type="checkbox" id="push"/> Send via Pushover</label>
|
<label><input type="checkbox" id="push"/> Send via Pushover</label>
|
||||||
<button onclick="doSumm()">Summarize</button>
|
<div>
|
||||||
|
<button id="summBtn" onclick="doSumm()">Summarize</button>
|
||||||
|
<progress id="summProg" value="0" max="1" style="display:none; vertical-align: middle;"></progress>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<pre id="summary"></pre>
|
<pre id="summary"></pre>
|
||||||
</article>
|
</article>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue