1
0
mirror of https://github.com/TheFunny/ArisuAutoSweeper synced 2025-12-16 19:55:12 +00:00
ArisuAutoSweeper/module/webui/fastapi_backend/templates/index.html
copilot-swe-agent[bot] 4efae500d6 Implement FastAPI backend with REST API and basic frontend
Co-authored-by: TheFunny <26841179+TheFunny@users.noreply.github.com>
2025-11-19 08:08:41 +00:00

372 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom CSS - reuse existing styles -->
<link rel="stylesheet" href="/assets/css/alas.css">
<link rel="stylesheet" href="/assets/css/alas-pc.css">
{% if theme == 'dark' %}
<link rel="stylesheet" href="/assets/css/dark-alas.css">
{% else %}
<link rel="stylesheet" href="/assets/css/light-alas.css">
{% endif %}
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
margin: 0;
padding: 0;
}
.app-container {
display: grid;
grid-template-areas:
"header header header"
"aside menu content";
grid-template-columns: 4rem 12rem 1fr;
grid-template-rows: 3.3125rem 1fr;
height: 100vh;
overflow: hidden;
}
.header {
grid-area: header;
display: flex;
align-items: center;
padding: 0 1rem;
border-bottom: 1px solid var(--border-color, #dee2e6);
}
.aside {
grid-area: aside;
overflow-y: auto;
border-right: 1px solid var(--border-color, #dee2e6);
}
.menu {
grid-area: menu;
overflow-y: auto;
padding: 1rem 0.5rem;
border-right: 1px solid var(--border-color, #dee2e6);
}
.content {
grid-area: content;
overflow-y: auto;
padding: 1rem;
}
.instance-btn {
width: 100%;
margin-bottom: 0.5rem;
}
.log-container {
background: #1e1e1e;
color: #d4d4d4;
padding: 1rem;
border-radius: 4px;
max-height: 400px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
}
.status-badge {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.875rem;
}
.status-running { background: #28a745; color: white; }
.status-stopped { background: #6c757d; color: white; }
.card {
margin-bottom: 1rem;
}
</style>
</head>
<body>
<div class="app-container">
<!-- Header -->
<div class="header">
<h3 style="margin: 0;">{{ title }}</h3>
<div class="ms-auto">
<span id="status-indicator"></span>
</div>
</div>
<!-- Aside Navigation -->
<div class="aside">
<div class="text-center py-3">
<button class="btn btn-sm btn-primary instance-btn" onclick="showHome()">Home</button>
</div>
<div id="instance-list">
{% for instance in instances %}
<div class="text-center py-2">
<button class="btn btn-sm btn-outline-primary instance-btn"
onclick="selectInstance('{{ instance }}')">{{ instance }}</button>
</div>
{% endfor %}
</div>
</div>
<!-- Menu -->
<div class="menu">
<div id="menu-content">
<button class="btn btn-sm btn-menu w-100 mb-2" onclick="showOverview()">Overview</button>
<button class="btn btn-sm btn-menu w-100 mb-2" onclick="showConfig()">Config</button>
<button class="btn btn-sm btn-menu w-100 mb-2" onclick="showLogs()">Logs</button>
</div>
</div>
<!-- Main Content -->
<div class="content">
<div id="main-content">
<h2>Welcome to ArisuAutoSweeper</h2>
<p>Select an instance from the sidebar to get started.</p>
<div class="card">
<div class="card-body">
<h5 class="card-title">System Information</h5>
<div id="system-info">Loading...</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title">Language & Theme</h5>
<div class="mb-3">
<label class="form-label">Language:</label>
<select class="form-select" id="language-select" onchange="changeLanguage()">
<option value="zh-CN" {% if language == 'zh-CN' %}selected{% endif %}>简体中文</option>
<option value="en-US" {% if language == 'en-US' %}selected{% endif %}>English</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Theme:</label>
<select class="form-select" id="theme-select" onchange="changeTheme()">
<option value="default" {% if theme != 'dark' %}selected{% endif %}>Light</option>
<option value="dark" {% if theme == 'dark' %}selected{% endif %}>Dark</option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Custom JavaScript -->
<script>
let currentInstance = null;
let ws = null;
// Load system info on startup
async function loadSystemInfo() {
try {
const response = await fetch('/api/system/info');
const data = await response.json();
document.getElementById('system-info').innerHTML = `
<p><strong>Version:</strong> ${data.version}</p>
<p><strong>Language:</strong> ${data.language}</p>
<p><strong>Theme:</strong> ${data.theme}</p>
`;
} catch (error) {
console.error('Error loading system info:', error);
}
}
function showHome() {
document.getElementById('main-content').innerHTML = `
<h2>Welcome to ArisuAutoSweeper</h2>
<p>Select an instance from the sidebar to get started.</p>
`;
loadSystemInfo();
}
async function selectInstance(instance) {
currentInstance = instance;
document.getElementById('main-content').innerHTML = `
<h2>${instance}</h2>
<div id="instance-overview">Loading...</div>
`;
await loadInstanceOverview(instance);
}
async function loadInstanceOverview(instance) {
try {
const statusResponse = await fetch(`/api/process/${instance}/status`);
const status = await statusResponse.json();
const alive = status.alive;
const statusClass = alive ? 'status-running' : 'status-stopped';
const statusText = alive ? 'Running' : 'Stopped';
document.getElementById('instance-overview').innerHTML = `
<div class="card">
<div class="card-body">
<h5 class="card-title">Status</h5>
<p>Status: <span class="status-badge ${statusClass}">${statusText}</span></p>
<div class="btn-group" role="group">
<button class="btn btn-success" onclick="startProcess('${instance}')">Start</button>
<button class="btn btn-danger" onclick="stopProcess('${instance}')">Stop</button>
<button class="btn btn-warning" onclick="restartProcess('${instance}')">Restart</button>
</div>
</div>
</div>
`;
} catch (error) {
console.error('Error loading instance:', error);
document.getElementById('instance-overview').innerHTML = `
<div class="alert alert-danger">Error loading instance information</div>
`;
}
}
async function startProcess(instance) {
try {
const response = await fetch(`/api/process/${instance}/start`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({})
});
const data = await response.json();
alert(data.message);
await loadInstanceOverview(instance);
} catch (error) {
alert('Error starting process: ' + error);
}
}
async function stopProcess(instance) {
try {
const response = await fetch(`/api/process/${instance}/stop`, {
method: 'POST'
});
const data = await response.json();
alert(data.message);
await loadInstanceOverview(instance);
} catch (error) {
alert('Error stopping process: ' + error);
}
}
async function restartProcess(instance) {
try {
const response = await fetch(`/api/process/${instance}/restart`, {
method: 'POST'
});
const data = await response.json();
alert(data.message);
await loadInstanceOverview(instance);
} catch (error) {
alert('Error restarting process: ' + error);
}
}
function showOverview() {
if (currentInstance) {
loadInstanceOverview(currentInstance);
} else {
alert('Please select an instance first');
}
}
function showConfig() {
document.getElementById('main-content').innerHTML = `
<h2>Configuration</h2>
<p>Configuration editor is under development.</p>
`;
}
function showLogs() {
if (!currentInstance) {
alert('Please select an instance first');
return;
}
document.getElementById('main-content').innerHTML = `
<h2>Logs - ${currentInstance}</h2>
<div class="log-container" id="log-output">
<div>Connecting to log stream...</div>
</div>
`;
// Connect WebSocket for logs
connectLogStream(currentInstance);
}
function connectLogStream(instance) {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws/logs/${instance}`;
ws = new WebSocket(wsUrl);
ws.onopen = () => {
document.getElementById('log-output').innerHTML = '<div>Connected to log stream.</div>';
};
ws.onmessage = (event) => {
const logOutput = document.getElementById('log-output');
if (logOutput) {
const data = JSON.parse(event.data);
logOutput.innerHTML += `<div>[${data.type}] ${JSON.stringify(data)}</div>`;
logOutput.scrollTop = logOutput.scrollHeight;
}
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
ws.onclose = () => {
console.log('WebSocket connection closed');
};
}
async function changeLanguage() {
const language = document.getElementById('language-select').value;
try {
await fetch('/api/system/language', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ language })
});
location.reload();
} catch (error) {
alert('Error changing language: ' + error);
}
}
async function changeTheme() {
const theme = document.getElementById('theme-select').value;
try {
await fetch('/api/system/theme', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ theme })
});
location.reload();
} catch (error) {
alert('Error changing theme: ' + error);
}
}
// Initialize
window.addEventListener('load', () => {
loadSystemInfo();
});
</script>
</body>
</html>