live-sfu-demo/internal/server/static/login.html

153 lines
6.6 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>登录 · 直播 SFU 分流 Demo</title>
<link rel="stylesheet" href="/static/styles.css" />
<style>
.login-card { max-width: 420px; margin: 60px auto; }
.role-badge { display:inline-block; padding:2px 8px; border-radius:10px; font-size:12px; background:#eef; margin-left:6px; }
.tabs { display:flex; gap:8px; margin-bottom:16px; }
.tabs button { flex:1; }
.tabs button.active { background:#111; color:#fff; }
</style>
</head>
<body>
<div class="topbar">
<div class="brand">直播 SFU 分流 Demo <small>登录 · Casbin 权限</small></div>
<nav><a href="/">首页</a><a href="/publish">开始直播</a><a href="/watch">观看直播</a></nav>
</div>
<div class="band">
<div class="card login-card">
<h3 id="formTitle">登录</h3>
<div class="tabs">
<button id="tabLogin" class="active" onclick="switchTab('login')">登录</button>
<button id="tabRegister" onclick="switchTab('register')">注册</button>
</div>
<div class="muted" style="margin-bottom:12px">
演示账户:<code>admin / Admin123!</code>(管理员)<br/>
<code>publisher / Publisher123!</code>(可推流/拉流)<br/>
<code>viewer / Viewer123!</code>(仅观看)
<div id="authStatus" style="margin-top:8px;"></div>
</div>
<div>
<label>用户名</label>
<input type="text" id="username" placeholder="username" style="width:100%;margin:6px 0 10px;" />
<label>密码</label>
<input type="password" id="password" placeholder="password" style="width:100%;margin:6px 0 10px;" />
<div id="registerRoleWrap" style="display:none;">
<label>角色(仅管理员注册时可指定)</label>
<select id="role" style="width:100%;margin:6px 0 10px;">
<option value="viewer">viewer - 仅观看</option>
<option value="publisher">publisher - 可推流</option>
<option value="admin">admin - 管理员</option>
</select>
</div>
<div class="row" style="margin-top:10px">
<button id="submitBtn" onclick="submit()">登录</button>
<button class="secondary" onclick="logout()">退出登录</button>
</div>
<div id="msg" class="muted" style="margin-top:10px; white-space:pre-wrap;"></div>
</div>
<div id="me" class="muted" style="margin-top:16px; border-top:1px solid #eee; padding-top:12px;"></div>
<div id="adminPanel" style="display:none; margin-top:16px;">
<h4>用户管理(仅 admin)</h4>
<div id="users" class="targets"></div>
<div class="row" style="margin-top:8px;">
<input type="text" id="targetUser" placeholder="用户名" />
<select id="newRole">
<option value="viewer">viewer</option>
<option value="publisher">publisher</option>
<option value="admin">admin</option>
</select>
<button onclick="updateRole()">更新角色</button>
</div>
</div>
</div>
</div>
<script src="/static/app.js"></script>
<script>
let mode = 'login';
function switchTab(m) {
mode = m;
document.getElementById('tabLogin').classList.toggle('active', m==='login');
document.getElementById('tabRegister').classList.toggle('active', m==='register');
document.getElementById('formTitle').textContent = m==='login' ? '登录' : '注册';
document.getElementById('submitBtn').textContent = m==='login' ? '登录' : '注册';
document.getElementById('registerRoleWrap').style.display = m==='register' ? 'block' : 'none';
}
async function refreshMe() {
const meEl = document.getElementById('me');
const statusEl = document.getElementById('authStatus');
const adminPanel = document.getElementById('adminPanel');
try {
const data = await LiveSFU.apiGetWithAuth('/api/auth/me');
meEl.innerHTML = '已登录:<b>' + data.username + '</b> <span class="role-badge">'+data.role+'</span> <br/>过期:' + new Date(data.expires_at*1000).toLocaleString();
statusEl.innerHTML = '<span class="badge primary">已登录</span> ' + data.username + ' / ' + data.role;
if (data.role === 'admin') {
adminPanel.style.display = 'block';
try {
const users = await LiveSFU.apiGetWithAuth('/api/auth/users');
const container = document.getElementById('users');
container.innerHTML = '';
(users.users||[]).forEach(u=>{
const span=document.createElement('span');
span.className='badge';
span.textContent=u.username+' / '+u.role;
container.appendChild(span);
});
} catch(e) {
document.getElementById('users').innerHTML='<span class="muted">'+e.message+'</span>';
}
} else {
adminPanel.style.display='none';
}
} catch(e) {
meEl.textContent='未登录:'+e.message;
statusEl.innerHTML='<span class="badge bad">未登录</span> 请先登录';
adminPanel.style.display='none';
}
}
async function submit() {
const u=document.getElementById('username').value.trim();
const p=document.getElementById('password').value;
const role=document.getElementById('role').value;
const msg=document.getElementById('msg');
msg.textContent='提交中...';
try {
let data;
if (mode==='login') {
data = await LiveSFU.login(u,p);
} else {
data = await LiveSFU.register(u,p,role);
}
msg.textContent='成功:'+JSON.stringify(data,null,2);
await refreshMe();
} catch(e) {
msg.textContent='失败:'+e.message;
}
}
async function logout() {
await fetch('/api/auth/logout',{method:'POST', headers: LiveSFU.authHeaders()});
LiveSFU.clearToken();
document.getElementById('msg').textContent='已退出';
await refreshMe();
}
async function updateRole() {
const username=document.getElementById('targetUser').value.trim();
const role=document.getElementById('newRole').value;
const msg=document.getElementById('msg');
try {
const data=await LiveSFU.apiPostWithAuth('/api/auth/users/role', {username, role});
msg.textContent='更新成功:'+JSON.stringify(data);
await refreshMe();
} catch(e) {
msg.textContent='更新失败:'+e.message;
}
}
refreshMe();
</script>
</body>
</html>