Files
squad-rain-ops-mini/wwwroot/rcon-console.html

674 lines
20 KiB
HTML
Raw Permalink 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.0">
<title>RCON 高级控制台 - RainOps Mini</title>
<link rel="stylesheet" href="/lib/css/bootstrap.min.css">
<link rel="stylesheet" href="/lib/css/font-awesome.min.css">
<style>
:root {
--primary-color: #0056b3;
--bg-color: #f0f2f5;
--card-bg: #ffffff;
--text-color: #2c3e50;
--border-color: #d1d5db;
--console-bg: #1e1e1e;
--console-text: #d4d4d4;
}
body {
font-family: 'Segoe UI', system-ui, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
margin: 0;
padding: 0;
height: 100vh;
display: flex;
flex-direction: column;
}
.navbar {
background-color: var(--card-bg);
padding: 0 20px;
height: 50px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
z-index: 100;
}
.navbar-brand {
font-weight: 600;
font-size: 1.1rem;
color: var(--primary-color);
text-decoration: none;
display: flex;
align-items: center;
gap: 10px;
}
.navbar-links a {
color: #666;
text-decoration: none;
font-size: 0.9rem;
cursor: pointer;
padding: 5px 10px;
border-radius: 4px;
transition: all 0.2s;
}
.navbar-links a:hover {
background-color: #f0f0f0;
color: var(--primary-color);
}
.main-container {
flex: 1;
display: flex;
overflow: hidden;
padding: 20px;
gap: 20px;
}
/* Left Panel: Console */
.console-panel {
flex: 1;
display: flex;
flex-direction: column;
background: var(--card-bg);
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
overflow: hidden;
}
.console-header {
padding: 10px 15px;
background: #f8f9fa;
border-bottom: 1px solid var(--border-color);
font-weight: 600;
display: flex;
justify-content: space-between;
align-items: center;
}
.console-output {
flex: 1;
background-color: var(--console-bg);
color: var(--console-text);
padding: 15px;
overflow-y: auto;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 0.9rem;
white-space: pre-wrap;
word-wrap: break-word;
}
.console-entry {
margin-bottom: 8px;
line-height: 1.4;
border-bottom: 1px solid #333;
padding-bottom: 4px;
}
.console-entry.user {
color: #4ec9b0;
}
.console-entry.server {
color: #ce9178;
}
.console-entry.error {
color: #f44336;
}
.console-entry .timestamp {
color: #6a9955;
margin-right: 10px;
font-size: 0.8em;
user-select: none;
}
.input-area {
padding: 15px;
background: #fff;
border-top: 1px solid var(--border-color);
display: flex;
gap: 10px;
}
.cmd-input {
flex: 1;
padding: 8px 12px;
border: 1px solid var(--border-color);
border-radius: 4px;
font-family: 'Consolas', monospace;
font-size: 0.95rem;
}
.cmd-input:focus {
outline: none;
border-color: var(--primary-color);
}
.btn-send {
padding: 8px 20px;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 600;
}
.btn-send:hover {
background-color: #005a9e;
}
.btn-clear {
padding: 4px 10px;
background: transparent;
border: 1px solid var(--border-color);
border-radius: 3px;
cursor: pointer;
font-size: 0.8rem;
}
.btn-clear:hover {
background: #f0f0f0;
}
/* Right Panel: Quick Actions */
.sidebar-panel {
width: 300px;
background: var(--card-bg);
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
display: flex;
flex-direction: column;
overflow: hidden;
}
.sidebar-header {
padding: 15px;
border-bottom: 1px solid var(--border-color);
font-weight: 600;
background: #f8f9fa;
}
.sidebar-content {
flex: 1;
overflow-y: auto;
padding: 15px;
}
.command-group {
margin-bottom: 20px;
}
.group-title {
font-size: 0.85rem;
color: #666;
margin-bottom: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 600;
}
.btn-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.quick-btn {
padding: 10px;
background: #fff;
border: 1px solid var(--border-color);
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
text-align: center;
transition: all 0.2s;
color: var(--text-color);
}
.quick-btn:hover {
border-color: var(--primary-color);
background: #f0f8ff;
color: var(--primary-color);
}
.quick-btn:active {
transform: translateY(1px);
}
.quick-btn small {
display: block;
font-size: 0.75em;
color: #999;
margin-top: 3px;
}
/* Modal Styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background: var(--card-bg);
padding: 20px;
border-radius: 8px;
width: 320px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
animation: modalFadeIn 0.2s;
}
@keyframes modalFadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid var(--border-color);
}
.modal-header h3 {
margin: 0;
font-size: 1.1rem;
color: var(--primary-color);
}
.btn-close {
background: none;
border: none;
font-size: 1.5rem;
line-height: 1;
cursor: pointer;
color: #999;
padding: 0 5px;
}
.btn-close:hover {
color: #333;
}
.modal-body {
margin-bottom: 20px;
}
.modal-body label {
display: block;
margin-bottom: 8px;
font-weight: 500;
}
.modal-footer {
display: flex;
gap: 10px;
}
.btn-primary {
background-color: var(--primary-color);
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
flex: 1;
font-weight: 600;
}
.btn-primary:hover {
background-color: #004494;
}
.btn-secondary {
background-color: #6c757d;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
flex: 1;
font-weight: 600;
}
.btn-secondary:hover {
background-color: #5a6268;
}
</style>
</head>
<body>
<!-- Speed Adjustment Modal -->
<div id="speed-modal" class="modal-overlay" style="display: none;">
<div class="modal-content">
<div class="modal-header">
<h3>调整服务器流速</h3>
<button class="btn-close" onclick="closeSpeedModal()">×</button>
</div>
<div class="modal-body">
<label for="speed-input">设置游戏速度倍率 (1-15):</label>
<input type="number" id="speed-input" min="1" max="15" value="1" class="cmd-input" style="width: 100%; box-sizing: border-box;">
</div>
<div class="modal-footer">
<button class="btn-secondary" onclick="resetGameSpeed()">恢复原版 (1x)</button>
<button class="btn-primary" onclick="applyGameSpeed()">应用设置</button>
</div>
</div>
</div>
<script>
(function() {
const required = 'rcon';
const permsStr = localStorage.getItem('permissions');
const isSuper = localStorage.getItem('isSuperAdmin') === 'true';
if (isSuper) return;
if (!permsStr) {
document.body.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100vh;color:red;font-size:1.5rem;">Access Denied: Not logged in.</div>';
throw new Error("Access Denied");
}
try {
const perms = JSON.parse(permsStr);
if (!perms.includes('all') && !perms.includes(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 "' + required + '" required.</div>';
throw new Error("Access Denied");
}
} catch(e) {
if(e.message === "Access Denied") throw e;
console.error(e);
}
})();
</script>
<!-- Navbar removed -->
<div class="main-container">
<!-- Console Area -->
<div class="console-panel">
<div class="console-header">
<span>终端输出</span>
<button class="btn-clear" onclick="clearConsole()">清空日志</button>
</div>
<div class="console-output" id="console-output">
<div class="console-entry server">
<span class="timestamp">[System]</span> 欢迎使用 RCON 高级控制台。请在下方输入命令或使用右侧快捷按钮。
</div>
</div>
<div class="input-area">
<input type="text" id="cmd-input" class="cmd-input" placeholder="输入 RCON 命令 (例如: ListPlayers)..."
autocomplete="off">
<button class="btn-send" onclick="submitCommand()">发送</button>
</div>
</div>
<!-- Sidebar: Quick Commands -->
<div class="sidebar-panel">
<div class="sidebar-header">
快捷指令
</div>
<div class="sidebar-content" id="quick-actions-container">
<!-- Dynamically populated -->
</div>
</div>
</div>
<script>
// Configuration for Quick Buttons
// You can edit this array to change buttons
const commandGroups = [
{
title: "常用命令",
buttons: [
{ label: "当前地图", cmd: "ShowCurrentMap", desc: "显示地图信息" },
{ label: "下一地图", cmd: "ShowNextMap", desc: "查看下一张地图" },
]
},
{
title: "管理命令",
buttons: [
{ label: "重载配置文件", cmd: "AdminReloadServerConfig", desc: "发送全服广播" },
]
},
{
title: "高级玩法",
buttons: [
{
label: "开启坦克大战",
handler: () => toggleTankMode(true),
desc: "一键开启载具混战模式 (慎用)"
},
{
label: "关闭坦克大战",
handler: () => toggleTankMode(false),
desc: "恢复默认限制"
}, {
label: "服务器加速",
handler: () => showSpeedModal(),
desc: "设置服务器游戏速度 (Slomo)"
}
]
}
];
// Initialization
document.addEventListener('DOMContentLoaded', () => {
renderQuickActions();
// Enter key support
document.getElementById('cmd-input').addEventListener('keypress', function (e) {
if (e.key === 'Enter') {
submitCommand();
}
});
// Focus input
document.getElementById('cmd-input').focus();
});
function renderQuickActions() {
const container = document.getElementById('quick-actions-container');
container.innerHTML = '';
commandGroups.forEach(group => {
const groupDiv = document.createElement('div');
groupDiv.className = 'command-group';
const title = document.createElement('div');
title.className = 'group-title';
title.innerText = group.title;
groupDiv.appendChild(title);
const grid = document.createElement('div');
grid.className = 'btn-grid';
group.buttons.forEach(btn => {
const button = document.createElement('button');
button.className = 'quick-btn';
if (btn.handler) {
// Custom handler
button.onclick = btn.handler;
} else {
// Default command behavior
button.onclick = () => {
document.getElementById('cmd-input').value = btn.cmd;
submitCommand();
};
}
const labelSpan = document.createElement('span');
labelSpan.innerText = btn.label;
const descSmall = document.createElement('small');
descSmall.innerText = btn.desc || "";
button.appendChild(labelSpan);
if (btn.desc) button.appendChild(descSmall);
grid.appendChild(button);
});
groupDiv.appendChild(grid);
container.appendChild(groupDiv);
});
}
async function submitCommand() {
const inputEl = document.getElementById('cmd-input');
const cmd = inputEl.value.trim();
if (!cmd) return;
// Log user input
logToConsole(cmd, 'user');
// Clear input
inputEl.value = '';
await executeRconCommand(cmd);
}
async function executeRconCommand(cmd) {
try {
const response = await fetch('/api/command', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ command: cmd })
});
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
const result = await response.text();
logToConsole(result, 'server');
} catch (err) {
logToConsole(`Error sending command: ${err.message}`, 'error');
}
}
async function toggleTankMode(enable) {
if (enable) {
if (!confirm("⚠️ 这是一个敏感操作!\n\n确定要开启坦克大战模式吗\n这将解除所有载具和兵种限制。")) {
return;
}
}
const val = enable ? "1" : "0";
const commands = [
`AdminNoRespawnTimer ${val}`,
`AdminForceAllVehicleAvailability ${val}`,
`AdminForceAllRoleAvailability ${val}`,
`AdminDisableVehicleKitRequirement ${val}`,
`AdminDisableVehicleclaiming ${val}`
];
const actionName = enable ? "开启坦克大战" : "关闭坦克大战";
logToConsole(`=== 正在执行: ${actionName} ===`, 'server');
for (const cmd of commands) {
logToConsole(`> ${cmd}`, 'user');
await executeRconCommand(cmd);
// Add a small delay to prevent flooding if necessary, though RCON is usually fast
await new Promise(r => setTimeout(r, 100));
}
logToConsole(`=== ${actionName} 执行完毕 ===`, 'server');
}
// Speed Modal Functions
function showSpeedModal() {
document.getElementById('speed-modal').style.display = 'flex';
document.getElementById('speed-input').focus();
}
function closeSpeedModal() {
document.getElementById('speed-modal').style.display = 'none';
}
async function applyGameSpeed() {
const speedInput = document.getElementById('speed-input');
let val = parseFloat(speedInput.value);
if (isNaN(val) || val < 1 || val > 15) {
alert("请输入 1 到 15 之间的数值!");
return;
}
// Ensure it's a clean number/string
closeSpeedModal();
const cmd = `AdminSlomo ${val}`;
logToConsole(`> ${cmd} (应用流速)`, 'user');
await executeRconCommand(cmd);
}
async function resetGameSpeed() {
closeSpeedModal();
document.getElementById('speed-input').value = "1";
const cmd = `AdminSlomo 1`;
logToConsole(`> ${cmd} (恢复原版流速)`, 'user');
await executeRconCommand(cmd);
}
// Close modal if clicking outside
window.onclick = function(event) {
const modal = document.getElementById('speed-modal');
if (event.target === modal) {
closeSpeedModal();
}
}
function logToConsole(text, type) {
const consoleOutput = document.getElementById('console-output');
const entry = document.createElement('div');
entry.className = `console-entry ${type}`;
const now = new Date();
const timeStr = now.toLocaleTimeString();
entry.innerHTML = `<span class="timestamp">[${timeStr}]</span> ${escapeHtml(text)}`;
consoleOutput.appendChild(entry);
consoleOutput.scrollTop = consoleOutput.scrollHeight;
}
function clearConsole() {
document.getElementById('console-output').innerHTML = '';
}
function escapeHtml(text) {
if (!text) return "";
return text
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
</script>
</body>
</html>