hodu/xterm.html

278 lines
7.2 KiB
HTML
Raw Normal View History

2024-12-13 02:25:27 +09:00
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
2024-12-14 00:19:12 +09:00
<title>Terminal</title>
2024-12-13 02:25:27 +09:00
<link rel="stylesheet" href="/_ssh/xterm.css" />
<style>
body {
margin: 0;
height: 100%;
2024-12-13 02:25:27 +09:00
}
2024-12-13 02:25:27 +09:00
#terminal-container {
display: flex;
flex-direction: column;
2024-12-13 02:25:27 +09:00
width: 100%;
height: 100vh;
}
2024-12-14 00:19:12 +09:00
#terminal-info-container {
vertical-align: middle;
padding: 3px;
}
2024-12-14 00:19:12 +09:00
#terminal-target {
display: inline-block;
height: 100%;
vertical-align: middle;
}
#terminal-status {
display: inline-block;
font-weight: bold;
height: 100%;
vertical-align: middle;
}
#terminal-errmsg {
display: inline-block;
color: red;
height: 100%;
vertical-align: middle;
}
#terminal-control {
display: inline-block;
height: 100%;
vertical-align: middle;
}
#terminal-control button {
vertical-align: middle;
}
#terminal-view-container {
flex: 1;
width: 100%;
min-height: 0;
/*height: 100%;*/
}
#login-container {
display: flex;
justify-content: center;
align-items: center;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 99999;
}
#login-form-container {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
text-align: center;
}
2024-12-14 00:19:12 +09:00
#login-form-title {
line-height: 2em;
font-size: 2em;
font-weight: bold;
}
2024-12-13 02:25:27 +09:00
</style>
<script src="/_ssh/xterm.js"></script>
<script src="/_ssh/xterm-addon-fit.js"></script>
<script>
2024-12-16 15:19:01 +09:00
const conn_id = '{{ .ConnId }}';
const route_id = '{{ .RouteId }}';
window.onload = function(event) {
const terminal_container = document.getElementById('terminal-container');
2024-12-14 00:19:12 +09:00
const terminal_target = document.getElementById('terminal-target');
const terminal_status = document.getElementById('terminal-status');
const terminal_errmsg = document.getElementById('terminal-errmsg');
const terminal_view_container = document.getElementById('terminal-view-container');
const terminal_disconnect = document.getElementById('terminal-disconnect');
const login_container = document.getElementById('login-container');
2024-12-14 00:19:12 +09:00
const login_form_title = document.getElementById('login-form-title');
const login_form = document.getElementById('login-form');
const username_field = document.getElementById('username');
const password_field= document.getElementById('password');
2024-12-27 14:43:44 +09:00
const term = new window.Terminal({
lineHeight: 1.2,
//fontSize: 14,
fontFamily: 'Ubuntu Mono, Consolas, SF Mono, courier-new, courier, monospace'
});
const fit_addon = new window.FitAddon.FitAddon();
const text_decoder = new TextDecoder();
term.loadAddon(fit_addon)
term.open(terminal_view_container);
2024-12-14 00:19:12 +09:00
let set_terminal_target = function(name) {
2024-12-16 15:19:01 +09:00
terminal_target.innerText = name;
login_form_title.innerText = name
2024-12-14 00:19:12 +09:00
}
let set_terminal_status = function(msg, errmsg) {
2024-12-16 15:19:01 +09:00
if (msg != null) terminal_status.innerText = msg;
if (errmsg != null) terminal_errmsg.innerText = errmsg
}
2024-12-14 00:19:12 +09:00
let adjust_terminal_size_unconnected = function() {
fit_addon.fit();
}
let fetch_session_info = async function() {
2024-12-16 15:19:01 +09:00
let url = window.location.protocol + '//' + window.location.host;
url += `/_ssh/server-conns/${conn_id}/routes/${route_id}`;
2024-12-14 00:19:12 +09:00
try {
const resp = await fetch(url);
2024-12-16 15:19:01 +09:00
if (!resp.ok) throw new Error(`HTTP error in getting route(${conn_id},${route_id}) info - status ${resp.status}`);
2024-12-14 00:19:12 +09:00
const route = await resp.json()
if ('client-peer-name' in route) {
set_terminal_target(route['client-peer-name']) // change to the name
document.title = route['client-peer-name']
}
} catch (e) {
set_terminal_target('');
document.title = '';
2024-12-14 00:19:12 +09:00
set_terminal_status (null, e);
}
}
let toggle_login_form = function(visible) {
2024-12-14 00:19:12 +09:00
if (visible) fetch_session_info();
login_container.style.visibility = (visible? 'visible': 'hidden');
terminal_disconnect.style.visibility = (visible? 'hidden': 'visible');
if (visible) username_field.focus();
else term.focus();
}
toggle_login_form(true);
2024-12-14 00:19:12 +09:00
window.onresize = adjust_terminal_size_unconnected;
adjust_terminal_size_unconnected()
login_form.onsubmit = async function(event) {
event.preventDefault();
toggle_login_form(false)
const username = username_field.value.trim();
const password = password_field.value.trim();
let prefix = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
2024-12-16 15:19:01 +09:00
let url = prefix + window.location.host+ `/_ssh-ws/${conn_id}/${route_id}`;
const socket = new WebSocket(url)
socket.binaryType = 'arraybuffer';
set_terminal_status('Connecting...', '');
2024-12-14 00:19:12 +09:00
const adjust_terminal_size_connected = function() {
fit_addon.fit();
if (socket.readyState == 1) // if open
socket.send(JSON.stringify({ type: 'size', data: [term.rows.toString(), term.cols.toString()] }));
};
socket.onopen = function () {
socket.send(JSON.stringify({ type: 'open', data: [username, password]}));
};
socket.onmessage = function(event) {
try {
let event_text;
event_text = (typeof event.data === 'string')? event.data: text_decoder.decode(new Uint8Array(event.data));
const msg = JSON.parse(event_text);
if (msg.type == "iov") {
for (const data of msg.data) term.write(data);
} else if (msg.type == "status") {
if (msg.data.length >= 1) {
if (msg.data[0] == 'opened') {
set_terminal_status('Connected', '');
2024-12-14 00:19:12 +09:00
adjust_terminal_size_connected()
term.clear()
} else if (msg.data[0] == 'closed') {
// doesn't really matter
// socket.onclose() will be executed anyway
}
}
} else if (msg.type == "error") {
toggle_login_form(true);
2024-12-14 00:19:12 +09:00
window.onresize = adjust_terminal_size_unconnected;
set_terminal_status(null, msg.data.join(' '))
}
} catch (e) {
set_terminal_status('Disconnected', e);
toggle_login_form(true)
2024-12-14 00:19:12 +09:00
window.onresize = adjust_terminal_size_unconnected;
}
};
socket.onerror = function(event) {
set_terminal_status('Disconnected', event);
toggle_login_form(true)
2024-12-14 00:19:12 +09:00
window.onresize = adjust_terminal_size_unconnected;
};
socket.onclose = function() {
set_terminal_status('Disconnected', null);
toggle_login_form(true)
2024-12-14 00:19:12 +09:00
window.onresize = adjust_terminal_size_unconnected;
};
term.onData(function(data) {
if (socket.readyState == 1) // if open
socket.send(JSON.stringify({ type: 'iov', data: [data] }));
});
2024-12-14 00:19:12 +09:00
window.onresize = adjust_terminal_size_connected;
terminal_disconnect.onclick = function(event) {
socket.send(JSON.stringify({ type: 'close', data: [""] }));
//socket.close()
};
}
};
2024-12-13 02:25:27 +09:00
</script>
</head>
<body>
<div id="login-container">
<div id="login-form-container">
2024-12-14 00:19:12 +09:00
<div id="login-form-title"></div>
<form id="login-form">
<label>
Username: <input type="text" id="username" required />
</label>
<br /><br />
<label>
Password: <input type="password" id="password" required />
</label>
<br /><br />
<button type="submit">Connect</button>
</form>
</div>
</div>
<div id="terminal-container">
2024-12-14 00:19:12 +09:00
<div id="terminal-info-container">
<div id="terminal-target"></div>
<div id="terminal-status"></div>
<div id="terminal-errmsg"></div>
<div id="terminal-control">
<button id="terminal-disconnect" type="button">Disconnect</button>
</div>
</div>
<div id="terminal-view-container"></div>
</div>
2024-12-13 02:25:27 +09:00
</body>
</html>