153 lines
6.6 KiB
HTML
153 lines
6.6 KiB
HTML
<!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>
|