mirror of
https://gitee.com/xiarenalofs/squad-rain-ops-mini.git
synced 2026-08-05 21:15:52 +08:00
863 lines
29 KiB
HTML
863 lines
29 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>权限管理</title>
|
|
<link rel="stylesheet" href="/lib/css/bootstrap.min.css">
|
|
<link rel="stylesheet" href="/lib/css/font-awesome.min.css">
|
|
<style>
|
|
:root {
|
|
/* Modern Palette */
|
|
--primary-color: #4361ee;
|
|
--secondary-color: #3f37c9;
|
|
--success-color: #4caf50;
|
|
--info-color: #00b4d8;
|
|
--warning-color: #f72585;
|
|
--danger-color: #f44336;
|
|
--bg-color: #f8f9fa;
|
|
--card-bg: #ffffff;
|
|
--text-primary: #2b2d42;
|
|
--text-secondary: #8d99ae;
|
|
--border-color: #edf2f4;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
|
background-color: var(--bg-color);
|
|
color: var(--text-primary);
|
|
margin: 0;
|
|
padding: 24px 40px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100vh;
|
|
box-sizing: border-box;
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
|
|
.header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.title {
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.btn {
|
|
padding: 8px 18px;
|
|
border: none;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
font-size: 0.9rem;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
transition: all 0.2s ease;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.btn-primary {
|
|
background-color: var(--primary-color);
|
|
color: white;
|
|
box-shadow: 0 2px 4px rgba(67, 97, 238, 0.3);
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background-color: var(--secondary-color);
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 4px 8px rgba(67, 97, 238, 0.4);
|
|
}
|
|
|
|
.btn-danger {
|
|
background-color: white;
|
|
color: var(--danger-color);
|
|
border: 1px solid var(--danger-color);
|
|
}
|
|
|
|
.btn-danger:hover {
|
|
background-color: #fff5f5;
|
|
}
|
|
|
|
.btn-secondary {
|
|
background-color: #fff;
|
|
border: 1px solid var(--border-color);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background-color: #f8f9fa;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.container {
|
|
display: flex;
|
|
gap: 24px;
|
|
flex: 1;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.panel {
|
|
background: var(--card-bg);
|
|
border-radius: 12px;
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
|
|
display: flex;
|
|
flex-direction: column;
|
|
border: 1px solid var(--border-color);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.panel-left {
|
|
width: 350px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.panel-right {
|
|
flex: 1;
|
|
}
|
|
|
|
.panel-header {
|
|
padding: 15px;
|
|
border-bottom: 1px solid var(--border-color);
|
|
font-weight: 600;
|
|
background: #f8f9fa;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.panel-content {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 0;
|
|
}
|
|
|
|
/* Group List */
|
|
.group-item {
|
|
padding: 12px 20px;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
cursor: pointer;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.group-item:hover {
|
|
background-color: #f0f7ff;
|
|
}
|
|
|
|
.group-item.active {
|
|
background-color: #e6f0ff;
|
|
border-left: 4px solid var(--primary-color);
|
|
}
|
|
|
|
.group-name {
|
|
font-weight: 500;
|
|
}
|
|
|
|
.group-perms-count {
|
|
font-size: 0.8em;
|
|
color: #888;
|
|
background: #eee;
|
|
padding: 2px 6px;
|
|
border-radius: 10px;
|
|
}
|
|
|
|
/* Permissions Grid */
|
|
.perm-editor {
|
|
padding: 20px;
|
|
border-top: 1px solid var(--border-color);
|
|
background: #fafafa;
|
|
display: none;
|
|
/* Hidden by default */
|
|
}
|
|
|
|
.perm-editor.active {
|
|
display: block;
|
|
}
|
|
|
|
.perm-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 10px;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.perm-checkbox {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 0.9rem;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* Admin List */
|
|
.admin-group-section {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.admin-group-header {
|
|
padding: 10px 20px;
|
|
background-color: #f0f7ff;
|
|
color: var(--primary-color);
|
|
font-weight: 600;
|
|
font-size: 0.95rem;
|
|
border-bottom: 1px solid #e1dfdd;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.admin-row {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 10px 20px;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
}
|
|
|
|
.admin-row:hover {
|
|
background-color: #fafafa;
|
|
}
|
|
|
|
.admin-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.admin-steamid {
|
|
font-family: monospace;
|
|
color: #666;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.admin-name {
|
|
font-weight: 500;
|
|
margin-left: 10px;
|
|
}
|
|
|
|
.admin-actions {
|
|
display: flex;
|
|
gap: 5px;
|
|
}
|
|
|
|
.status-msg {
|
|
margin-right: 15px;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
/* Modal */
|
|
.modal-overlay {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
z-index: 1000;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.modal-overlay.show {
|
|
display: flex;
|
|
}
|
|
|
|
.modal {
|
|
background: white;
|
|
padding: 25px;
|
|
border-radius: 8px;
|
|
width: 400px;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.modal-title {
|
|
margin-top: 0;
|
|
margin-bottom: 20px;
|
|
font-size: 1.2rem;
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.form-label {
|
|
display: block;
|
|
margin-bottom: 5px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.form-input,
|
|
.form-select {
|
|
width: 100%;
|
|
padding: 8px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.form-hint {
|
|
font-size: 0.8rem;
|
|
color: #888;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.modal-footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 10px;
|
|
margin-top: 25px;
|
|
}
|
|
|
|
.empty-state {
|
|
padding: 40px;
|
|
text-align: center;
|
|
color: #999;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div class="header">
|
|
<div class="title">
|
|
<span><i class="fas fa-user-shield"></i></span> 权限管理
|
|
</div>
|
|
<div>
|
|
<span id="status-msg" class="status-msg"></span>
|
|
<button class="btn btn-primary" id="btn-save-config" onclick="saveConfig()">
|
|
<i class="fas fa-save"></i> 保存配置
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="container">
|
|
<!-- Left Panel: Groups -->
|
|
<div class="panel panel-left">
|
|
<div class="panel-header">
|
|
<span>权限组</span>
|
|
<button class="btn btn-secondary" id="btn-add-group" style="padding: 4px 8px; font-size: 0.8rem;"
|
|
onclick="showAddGroupModal()">+ 新建组</button>
|
|
</div>
|
|
<div class="panel-content" id="group-list">
|
|
<!-- Group items here -->
|
|
</div>
|
|
|
|
<div class="perm-editor" id="perm-editor">
|
|
<div style="margin-bottom: 10px; font-weight: bold; display:flex; justify-content:space-between;">
|
|
<span id="current-group-name">Selected Group</span>
|
|
<button class="btn btn-danger" id="btn-delete-group" style="padding: 2px 6px; font-size: 0.7rem;"
|
|
onclick="deleteCurrentGroup()">删除</button>
|
|
</div>
|
|
<div class="perm-grid" id="perm-grid">
|
|
<!-- Checkboxes generated by JS -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right Panel: Admins -->
|
|
<div class="panel panel-right">
|
|
<div class="panel-header">
|
|
<span>管理员列表</span>
|
|
<button class="btn btn-primary" id="btn-add-admin" style="padding: 6px 12px; font-size: 0.85rem;"
|
|
onclick="showAddAdminModal()">+ 添加管理员</button>
|
|
</div>
|
|
<div class="panel-content" id="admin-list">
|
|
<!-- Admin groups here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add Group Modal -->
|
|
<div class="modal-overlay" id="add-group-modal">
|
|
<div class="modal">
|
|
<h3 class="modal-title">新建权限组</h3>
|
|
<div class="form-group">
|
|
<label class="form-label">组名称</label>
|
|
<input type="text" class="form-input" id="new-group-name" placeholder="例如: SuperAdmin">
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" onclick="closeModal('add-group-modal')">取消</button>
|
|
<button class="btn btn-primary" onclick="confirmAddGroup()">创建</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add Admin Modal -->
|
|
<div class="modal-overlay" id="add-admin-modal">
|
|
<div class="modal">
|
|
<h3 class="modal-title">添加管理员</h3>
|
|
<div class="form-group">
|
|
<label class="form-label">SteamID64</label>
|
|
<input type="text" class="form-input" id="new-admin-steamid" placeholder="例如: 76561198...">
|
|
<div class="form-hint"><a href="https://steamid.io/" target="_blank">查询 SteamID</a></div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">备注</label>
|
|
<input type="text" class="form-input" id="new-admin-comment" placeholder="例如: 玩家A">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">分配组</label>
|
|
<select class="form-select" id="new-admin-group">
|
|
<!-- Options populated by JS -->
|
|
</select>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" onclick="closeModal('add-admin-modal')">取消</button>
|
|
<button class="btn btn-primary" onclick="confirmAddAdmin()">添加</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Permission Check
|
|
window.checkPermission = function(key) {
|
|
const isSuper = localStorage.getItem('isSuperAdmin') === 'true';
|
|
if (isSuper) return true;
|
|
try {
|
|
const perms = JSON.parse(localStorage.getItem('permissions') || '[]');
|
|
return perms.includes('all') || perms.includes(key);
|
|
} catch { return false; }
|
|
};
|
|
|
|
(function() {
|
|
const required = 'admins.view';
|
|
if (!checkPermission(required)) {
|
|
document.body.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100vh;color:red;font-size:1.5rem;">Access Denied: Permission "admins.view" required.</div>';
|
|
throw new Error("Access Denied");
|
|
}
|
|
})();
|
|
|
|
// Constants
|
|
const PERMISSIONS = [
|
|
'changemap', 'pause', 'cheat', 'private', 'balance', 'chat',
|
|
'kick', 'ban', 'config', 'cameraman', 'immune', 'manageserver',
|
|
'reserve', 'debug', 'teamchange', 'forceteamchange', 'canseeadminchat'
|
|
];
|
|
|
|
const PERM_DESCRIPTIONS = {
|
|
'changemap': '更换/预设地图',
|
|
'pause': '暂停游戏',
|
|
'cheat': '使用作弊命令',
|
|
'private': '设置服务器密码',
|
|
'balance': '忽略服务器阵营平衡',
|
|
'chat': '管理员聊天/服务器公告',
|
|
'kick': '踢出玩家',
|
|
'ban': '封禁玩家',
|
|
'config': '更改服务器配置',
|
|
'cameraman': '摄影机 - 管理员视角',
|
|
'immune': '无法被 踢出/封禁',
|
|
'manageserver': '关闭服务器',
|
|
'reserve': '预留位',
|
|
'debug': '调试',
|
|
'teamchange': '忽略更换阵营时间限制',
|
|
'forceteamchange': '允许执行强制更换阵营命令',
|
|
'canseeadminchat': '查看 管理员聊天/友军击杀/管理员加入 消息'
|
|
};
|
|
|
|
const CONFIG_FILENAME = 'Admins.cfg';
|
|
|
|
// State
|
|
let groups = {}; // { name: [perms] }
|
|
let admins = []; // { steamId, group, comment }
|
|
let currentGroup = null;
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
initPermGrid();
|
|
loadConfig();
|
|
|
|
// Apply button permissions
|
|
if (!checkPermission('admins.add')) {
|
|
const btnAddGroup = document.getElementById('btn-add-group');
|
|
if (btnAddGroup) btnAddGroup.style.display = 'none';
|
|
|
|
const btnAddAdmin = document.getElementById('btn-add-admin');
|
|
if (btnAddAdmin) btnAddAdmin.style.display = 'none';
|
|
}
|
|
|
|
if (!checkPermission('admins.edit')) {
|
|
const btnSave = document.getElementById('btn-save-config');
|
|
if (btnSave) btnSave.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
function initPermGrid() {
|
|
const grid = document.getElementById('perm-grid');
|
|
grid.innerHTML = '';
|
|
const canEdit = checkPermission('admins.edit');
|
|
|
|
PERMISSIONS.forEach(perm => {
|
|
const label = document.createElement('label');
|
|
label.className = 'perm-checkbox';
|
|
const desc = PERM_DESCRIPTIONS[perm] || '';
|
|
label.innerHTML = `
|
|
<input type="checkbox" value="${perm}" onchange="updateGroupPerms()" ${canEdit ? '' : 'disabled'}>
|
|
<div>
|
|
<div style="font-weight: 500;">${perm}</div>
|
|
<div style="font-size: 0.85em; color: #666;">${desc}</div>
|
|
</div>
|
|
`;
|
|
grid.appendChild(label);
|
|
});
|
|
}
|
|
|
|
async function loadConfig() {
|
|
const status = document.getElementById('status-msg');
|
|
status.textContent = '正在加载配置...';
|
|
|
|
try {
|
|
const response = await fetch(`/api/config/read?filename=${encodeURIComponent(CONFIG_FILENAME)}`);
|
|
if (!response.ok) {
|
|
// Try to load default/empty if not found
|
|
if (response.status === 404) {
|
|
console.log('File not found, starting fresh');
|
|
groups = {
|
|
'SuperAdmin': [...PERMISSIONS],
|
|
'Admin': ['changemap', 'balance', 'chat', 'kick', 'ban', 'cameraman', 'pause'],
|
|
'Moderator': ['changemap', 'chat', 'kick', 'ban']
|
|
};
|
|
admins = [];
|
|
renderAll();
|
|
status.textContent = '新建默认配置';
|
|
return;
|
|
}
|
|
throw new Error('Load failed');
|
|
}
|
|
|
|
const text = await response.text();
|
|
parseConfig(text);
|
|
|
|
// If no groups found (empty or invalid file), load defaults
|
|
if (Object.keys(groups).length === 0) {
|
|
console.log('No groups found, loading defaults');
|
|
groups = {
|
|
'SuperAdmin': ['changemap', 'cheat', 'private', 'balance', 'chat', 'kick', 'ban', 'config', 'cameraman', 'debug', 'pause'],
|
|
'Admin': ['changemap', 'balance', 'chat', 'kick', 'ban', 'cameraman', 'pause'],
|
|
'Moderator': ['changemap', 'chat', 'kick', 'ban'],
|
|
'Whitelist': ['reserve']
|
|
};
|
|
status.textContent = '配置无效,已加载默认值';
|
|
} else {
|
|
status.textContent = '配置已加载';
|
|
}
|
|
|
|
renderAll();
|
|
if (status.textContent === '配置已加载') {
|
|
setTimeout(() => status.textContent = '', 2000);
|
|
}
|
|
|
|
} catch (err) {
|
|
console.error(err);
|
|
status.textContent = '加载失败: ' + err.message;
|
|
status.style.color = 'var(--danger-color)';
|
|
}
|
|
}
|
|
|
|
function parseConfig(text) {
|
|
groups = {};
|
|
admins = [];
|
|
|
|
const lines = text.split('\n');
|
|
lines.forEach(line => {
|
|
line = line.trim();
|
|
if (!line || (line.startsWith('//') && !line.includes('Admin='))) return;
|
|
|
|
// Group=Name:perm1,perm2
|
|
const groupMatch = line.match(/^Group=(.*?):(.*?)(\/\/.*)?$/);
|
|
if (groupMatch) {
|
|
const name = groupMatch[1].trim();
|
|
const perms = groupMatch[2].split(',').map(p => p.trim()).filter(p => p);
|
|
groups[name] = perms;
|
|
return;
|
|
}
|
|
|
|
// Admin=SteamID:Group //Comment
|
|
// Flexible regex to handle comments
|
|
const adminMatch = line.match(/^Admin=(\d+):(.*?)(?:\s*\/\/(.*))?$/);
|
|
if (adminMatch) {
|
|
const steamId = adminMatch[1];
|
|
let groupPart = adminMatch[2].trim();
|
|
let comment = adminMatch[3] ? adminMatch[3].trim() : '';
|
|
|
|
// Handle case where comment starts without // immediately after group (unlikely but possible)
|
|
// But standard format is //
|
|
|
|
admins.push({ steamId, group: groupPart, comment });
|
|
}
|
|
});
|
|
}
|
|
|
|
async function saveConfig() {
|
|
const status = document.getElementById('status-msg');
|
|
status.textContent = '正在保存...';
|
|
status.style.color = '#666';
|
|
|
|
const content = generateConfig();
|
|
|
|
try {
|
|
const response = await fetch('/api/config/save', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
filename: CONFIG_FILENAME,
|
|
content: content
|
|
})
|
|
});
|
|
|
|
if (!response.ok) throw new Error('Save failed');
|
|
|
|
status.textContent = '保存成功!';
|
|
status.style.color = 'var(--success-color)';
|
|
setTimeout(() => status.textContent = '', 3000);
|
|
|
|
} catch (err) {
|
|
status.textContent = '保存失败: ' + err.message;
|
|
status.style.color = 'var(--danger-color)';
|
|
}
|
|
}
|
|
|
|
function generateConfig() {
|
|
let lines = [];
|
|
|
|
// Groups
|
|
for (const [name, perms] of Object.entries(groups)) {
|
|
lines.push(`Group=${name}:${perms.join(',')}`);
|
|
}
|
|
|
|
lines.push('');
|
|
|
|
// Admins grouped by Group
|
|
const adminsByGroup = {};
|
|
// Ensure all defined groups exist as keys
|
|
for (const g in groups) adminsByGroup[g] = [];
|
|
// Also include groups that might be in admins but not in definitions (edge case)
|
|
admins.forEach(a => {
|
|
if (!adminsByGroup[a.group]) adminsByGroup[a.group] = [];
|
|
adminsByGroup[a.group].push(a);
|
|
});
|
|
|
|
for (const [groupName, list] of Object.entries(adminsByGroup)) {
|
|
if (list.length > 0) {
|
|
lines.push(`// ${groupName}`);
|
|
list.forEach(a => {
|
|
let line = `Admin=${a.steamId}:${a.group}`;
|
|
if (a.comment) line += ` // ${a.comment}`;
|
|
lines.push(line);
|
|
});
|
|
lines.push('');
|
|
}
|
|
}
|
|
|
|
return lines.join('\n');
|
|
}
|
|
|
|
// Render Functions
|
|
function renderAll() {
|
|
renderGroups();
|
|
renderAdmins();
|
|
}
|
|
|
|
function renderGroups() {
|
|
const list = document.getElementById('group-list');
|
|
list.innerHTML = '';
|
|
|
|
Object.keys(groups).forEach(name => {
|
|
const el = document.createElement('div');
|
|
el.className = `group-item ${currentGroup === name ? 'active' : ''}`;
|
|
el.onclick = () => selectGroup(name);
|
|
el.innerHTML = `
|
|
<span class="group-name">${name}</span>
|
|
<span class="group-perms-count">${groups[name].length} 权限</span>
|
|
`;
|
|
list.appendChild(el);
|
|
});
|
|
}
|
|
|
|
function selectGroup(name) {
|
|
currentGroup = name;
|
|
document.getElementById('current-group-name').textContent = name;
|
|
document.getElementById('perm-editor').classList.add('active');
|
|
|
|
// Delete permission check
|
|
const btnDelete = document.getElementById('btn-delete-group');
|
|
if (btnDelete) {
|
|
btnDelete.style.display = checkPermission('admins.delete') ? 'inline-block' : 'none';
|
|
}
|
|
|
|
// Update UI
|
|
document.querySelectorAll('.group-item').forEach(el => {
|
|
if (el.querySelector('.group-name').textContent === name) el.classList.add('active');
|
|
else el.classList.remove('active');
|
|
});
|
|
|
|
// Update Checkboxes
|
|
const perms = groups[name] || [];
|
|
document.querySelectorAll('#perm-grid input').forEach(input => {
|
|
input.checked = perms.includes(input.value);
|
|
});
|
|
}
|
|
|
|
function updateGroupPerms() {
|
|
if (!currentGroup) return;
|
|
const inputs = document.querySelectorAll('#perm-grid input:checked');
|
|
groups[currentGroup] = Array.from(inputs).map(i => i.value);
|
|
renderGroups(); // Update count
|
|
// Re-select to keep styling
|
|
document.querySelectorAll('.group-item').forEach(el => {
|
|
if (el.querySelector('.group-name').textContent === currentGroup) el.classList.add('active');
|
|
});
|
|
}
|
|
|
|
function renderAdmins() {
|
|
const list = document.getElementById('admin-list');
|
|
list.innerHTML = '';
|
|
|
|
const adminsByGroup = {};
|
|
// Use groups order if possible
|
|
const groupNames = Object.keys(groups).sort();
|
|
groupNames.forEach(g => adminsByGroup[g] = []);
|
|
|
|
// Add any other groups found in admins
|
|
admins.forEach(a => {
|
|
if (!adminsByGroup[a.group]) adminsByGroup[a.group] = [];
|
|
adminsByGroup[a.group].push(a);
|
|
});
|
|
|
|
// Render
|
|
Object.entries(adminsByGroup).forEach(([groupName, adminList]) => {
|
|
if (adminList.length === 0) return;
|
|
|
|
const section = document.createElement('div');
|
|
section.className = 'admin-group-section';
|
|
|
|
section.innerHTML = `
|
|
<div class="admin-group-header">
|
|
<span>${groupName}</span>
|
|
<span style="font-size: 0.8em; font-weight: normal; color: #666;">${adminList.length} 人</span>
|
|
</div>
|
|
`;
|
|
|
|
adminList.forEach(admin => {
|
|
const row = document.createElement('div');
|
|
row.className = 'admin-row';
|
|
row.innerHTML = `
|
|
<div class="admin-info">
|
|
<div>
|
|
<span class="admin-steamid">${admin.steamId}</span>
|
|
<span class="admin-name">${admin.comment || '无备注'}</span>
|
|
</div>
|
|
</div>
|
|
<div class="admin-actions">
|
|
${checkPermission('admins.delete') ?
|
|
`<button class="btn btn-danger" style="padding: 4px 8px; font-size: 0.8rem;"
|
|
onclick="deleteAdmin('${admin.steamId}')">移除</button>` : ''}
|
|
</div>
|
|
`;
|
|
section.appendChild(row);
|
|
});
|
|
|
|
list.appendChild(section);
|
|
});
|
|
|
|
if (admins.length === 0) {
|
|
list.innerHTML = '<div class="empty-state">暂无管理员,请点击右上角添加</div>';
|
|
}
|
|
}
|
|
|
|
// Actions
|
|
function showAddGroupModal() {
|
|
document.getElementById('new-group-name').value = '';
|
|
document.getElementById('add-group-modal').classList.add('show');
|
|
}
|
|
|
|
function confirmAddGroup() {
|
|
const name = document.getElementById('new-group-name').value.trim();
|
|
if (!name) return alert('请输入组名称');
|
|
if (groups[name]) return alert('组名已存在');
|
|
|
|
groups[name] = [];
|
|
renderGroups();
|
|
selectGroup(name);
|
|
closeModal('add-group-modal');
|
|
}
|
|
|
|
function deleteCurrentGroup() {
|
|
if (!checkPermission('admins.delete')) return;
|
|
if (!currentGroup) return;
|
|
if (!confirm(`确定要删除权限组 "${currentGroup}" 吗?\n所有属于该组的管理员也将失去权限。`)) return;
|
|
|
|
delete groups[currentGroup];
|
|
currentGroup = null;
|
|
document.getElementById('perm-editor').classList.remove('active');
|
|
renderAll();
|
|
}
|
|
|
|
function showAddAdminModal() {
|
|
document.getElementById('new-admin-steamid').value = '';
|
|
document.getElementById('new-admin-comment').value = '';
|
|
|
|
const select = document.getElementById('new-admin-group');
|
|
select.innerHTML = '';
|
|
Object.keys(groups).forEach(g => {
|
|
const opt = document.createElement('option');
|
|
opt.value = g;
|
|
opt.textContent = g;
|
|
select.appendChild(opt);
|
|
});
|
|
|
|
if (currentGroup) select.value = currentGroup;
|
|
|
|
document.getElementById('add-admin-modal').classList.add('show');
|
|
}
|
|
|
|
function confirmAddAdmin() {
|
|
const steamId = document.getElementById('new-admin-steamid').value.trim();
|
|
const comment = document.getElementById('new-admin-comment').value.trim();
|
|
const group = document.getElementById('new-admin-group').value;
|
|
|
|
if (!steamId || !group) return alert('请填写完整信息');
|
|
if (!/^\d+$/.test(steamId)) return alert('SteamID 必须是数字');
|
|
|
|
// Check duplicate
|
|
const exists = admins.find(a => a.steamId === steamId);
|
|
if (exists) {
|
|
if (!confirm(`该 SteamID 已存在 (组: ${exists.group}),要更新吗?`)) return;
|
|
exists.group = group;
|
|
exists.comment = comment;
|
|
} else {
|
|
admins.push({ steamId, group, comment });
|
|
}
|
|
|
|
renderAdmins();
|
|
closeModal('add-admin-modal');
|
|
}
|
|
|
|
function deleteAdmin(steamId) {
|
|
if (!checkPermission('admins.delete')) return;
|
|
if (!confirm('确定要移除该管理员吗?')) return;
|
|
admins = admins.filter(a => a.steamId !== steamId);
|
|
renderAdmins();
|
|
}
|
|
|
|
function closeModal(id) {
|
|
document.getElementById(id).classList.remove('show');
|
|
}
|
|
|
|
// Close modal on outside click
|
|
window.onclick = function (event) {
|
|
if (event.target.classList.contains('modal-overlay')) {
|
|
event.target.classList.remove('show');
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
|
|
</html> |