130 lines
5.1 KiB
HTML
130 lines
5.1 KiB
HTML
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="en">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
<title>DWN Remote Control</title>
|
||
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||
|
|
<style>
|
||
|
|
body { background-color: #1a1a1a; color: #e0e0e0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
|
||
|
|
.card { background-color: #2d2d2d; border: 1px solid #404040; margin-bottom: 20px; }
|
||
|
|
.btn-ws { width: 50px; height: 50px; margin: 5px; font-weight: bold; }
|
||
|
|
.client-item { border-bottom: 1px solid #404040; padding: 10px; cursor: pointer; transition: background 0.2s; }
|
||
|
|
.client-item:hover { background-color: #3d3d3d; }
|
||
|
|
.focused { border-left: 4px solid #0d6efd; background-color: #343a40; }
|
||
|
|
.badge-ws { font-size: 0.8rem; }
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<div class="container py-5">
|
||
|
|
<h1 class="mb-4 text-primary">DWN Remote</h1>
|
||
|
|
|
||
|
|
<div class="row">
|
||
|
|
<div class="col-md-4">
|
||
|
|
<div class="card p-3">
|
||
|
|
<h3>Workspaces</h3>
|
||
|
|
<div id="workspace-grid" class="d-flex flex-wrap">
|
||
|
|
<!-- Workspaces injected here -->
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="card p-3">
|
||
|
|
<h3>Quick Launch</h3>
|
||
|
|
<div class="input-group mb-3">
|
||
|
|
<input type="text" id="cmd-input" class="form-control bg-dark text-white border-secondary" placeholder="Terminal command...">
|
||
|
|
<button class="btn btn-outline-primary" onclick="launchCmd()">Run</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="col-md-8">
|
||
|
|
<div class="card p-3">
|
||
|
|
<h3>Active Windows</h3>
|
||
|
|
<div id="client-list">
|
||
|
|
<!-- Clients injected here -->
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
let ws;
|
||
|
|
const port = 8777;
|
||
|
|
const uri = `ws://${window.location.hostname || 'localhost'}:${port}/ws`;
|
||
|
|
|
||
|
|
function connect() {
|
||
|
|
ws = new WebSocket(uri);
|
||
|
|
ws.onopen = () => {
|
||
|
|
console.log("Connected to DWN API");
|
||
|
|
refresh();
|
||
|
|
};
|
||
|
|
ws.onmessage = (event) => {
|
||
|
|
const data = JSON.parse(event.data);
|
||
|
|
if (Array.isArray(data)) {
|
||
|
|
if (data.length > 0 && 'title' in data[0]) renderClients(data);
|
||
|
|
else if (data.length > 0 && 'layout' in data[0]) renderWorkspaces(data);
|
||
|
|
} else if (data.status === 'ok') {
|
||
|
|
// Refresh status if needed
|
||
|
|
}
|
||
|
|
};
|
||
|
|
ws.onclose = () => setTimeout(connect, 2000);
|
||
|
|
}
|
||
|
|
|
||
|
|
function refresh() {
|
||
|
|
if (ws.readyState === WebSocket.OPEN) {
|
||
|
|
ws.send(JSON.stringify({command: "get_workspaces"}));
|
||
|
|
ws.send(JSON.stringify({command: "get_clients"}));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderWorkspaces(workspaces) {
|
||
|
|
const container = document.getElementById('workspace-grid');
|
||
|
|
container.innerHTML = '';
|
||
|
|
workspaces.forEach(ws_info => {
|
||
|
|
const btn = document.createElement('button');
|
||
|
|
btn.className = `btn btn-ws ${ws_info.client_count > 0 ? 'btn-primary' : 'btn-outline-secondary'}`;
|
||
|
|
btn.innerText = ws_info.id;
|
||
|
|
btn.onclick = () => {
|
||
|
|
ws.send(JSON.stringify({command: "switch_workspace", workspace: ws_info.id}));
|
||
|
|
setTimeout(refresh, 100);
|
||
|
|
};
|
||
|
|
container.appendChild(btn);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderClients(clients) {
|
||
|
|
const container = document.getElementById('client-list');
|
||
|
|
container.innerHTML = '';
|
||
|
|
clients.forEach(c => {
|
||
|
|
const div = document.createElement('div');
|
||
|
|
div.className = `client-item ${c.focused ? 'focused' : ''} d-flex justify-content-between align-items-center`;
|
||
|
|
div.innerHTML = `
|
||
|
|
<div>
|
||
|
|
<strong>${c.title}</strong><br>
|
||
|
|
<small class="text-muted">${c.class}</small>
|
||
|
|
</div>
|
||
|
|
<span class="badge bg-secondary badge-ws">WS ${c.workspace}</span>
|
||
|
|
`;
|
||
|
|
div.onclick = () => {
|
||
|
|
ws.send(JSON.stringify({command: "focus_client", window: c.window}));
|
||
|
|
setTimeout(refresh, 100);
|
||
|
|
};
|
||
|
|
container.appendChild(div);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function launchCmd() {
|
||
|
|
const input = document.getElementById('cmd-input');
|
||
|
|
if (input.value) {
|
||
|
|
ws.send(JSON.stringify({command: "run_command", exec: input.value}));
|
||
|
|
input.value = '';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
connect();
|
||
|
|
setInterval(refresh, 3000);
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|