Skip to content

Commit 36c2a5c

Browse files
committed
added server log option
1 parent 6c65d85 commit 36c2a5c

File tree

1 file changed

+355
-0
lines changed

1 file changed

+355
-0
lines changed

examples/valkey-cli-with-server.html

Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>TryValkey</title>
7+
8+
<!-- Scripts -->
9+
<script src="../build/libv86.js"></script>
10+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/xterm.min.js"></script>
11+
<script src="https://cdnjs.cloudflare.com/ajax/libs/pako/2.1.0/pako.min.js"></script>
12+
<script src="../src/browser/serial_xterm.js"></script>
13+
<link href='https://fonts.googleapis.com/css?family=Open Sans' rel='stylesheet'>
14+
<!-- Styles -->
15+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/xterm.css" />
16+
<link href='https://fonts.googleapis.com/css?family=Open Sans' rel='stylesheet'>
17+
18+
<style>
19+
/* General Styles */
20+
* {
21+
box-sizing: border-box;
22+
margin: 0;
23+
padding: 0;
24+
}
25+
26+
body {
27+
font-family: 'Open Sans', sans-serif;
28+
background: #f5f5f7;
29+
color: #333;
30+
line-height: 1.6;
31+
display: flex;
32+
flex-direction: column;
33+
align-items: center;
34+
justify-content: flex-start;
35+
min-height: 100vh;
36+
padding: 40px 20px;
37+
}
38+
39+
/* Header Section */
40+
.header {
41+
display: flex;
42+
align-items: center;
43+
justify-content: center;
44+
margin-bottom: 40px;
45+
}
46+
47+
.header svg {
48+
width: 70px;
49+
height: 70px;
50+
margin-right: 15px;
51+
}
52+
53+
/* Container Styles */
54+
.container {
55+
border-radius: 16px;
56+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
57+
display: flex;
58+
flex-direction: row;
59+
padding: 10px;
60+
overflow-y: hidden;
61+
height: 500px;
62+
}
63+
64+
h1, h2 {
65+
text-align: center;
66+
}
67+
68+
h1 {
69+
font-size: 3rem;
70+
color: #003366;
71+
font-weight: 600;
72+
}
73+
74+
/* Introduction Text */
75+
h2 {
76+
font-size: 1.2rem;
77+
font-weight: 400;
78+
color: #555;
79+
margin-bottom: 20px;
80+
text-align: center;
81+
}
82+
83+
.cli-section,
84+
.log-section {
85+
width: 50%; /* Split the container into two equal sections */
86+
height: 100%;
87+
display: flex;
88+
flex-direction: column;
89+
justify-content: flex-start;
90+
overflow: hidden;
91+
padding: 0 10px; /* Add padding for better spacing */
92+
background: none;
93+
}
94+
95+
.cli-description,
96+
.log-description {
97+
font-size: 1.2rem; /* Match the styling of the original h2 */
98+
font-weight: 400;
99+
color: #555;
100+
margin-bottom: 10px; /* Add spacing below the description */
101+
text-align: center; /* Center-align the text */
102+
}
103+
104+
#terminal-container,
105+
#log-terminal-container {
106+
background: #000;
107+
color: #fff;
108+
border: 1px solid #555; /* Optional: add a border to match styling */
109+
padding: 10px;
110+
font-family: monospace;
111+
height: 100%;
112+
max-height: 100%;
113+
width: 100%;
114+
}
115+
#terminal-container {
116+
overflow-y: hidden; /* xterm container already has scroll bar */
117+
}
118+
#log-terminal-container {
119+
overflow-y: scroll; /* Enable vertical scrolling for both sections */
120+
}
121+
122+
.xterm-viewport {
123+
width: 100% !important;
124+
height: 100% !important;
125+
}
126+
.xterm-screen {
127+
width: 100% !important;
128+
height: 100% !important;
129+
}
130+
131+
132+
#loadingContainer {
133+
text-align: center;
134+
margin-top: 20px;
135+
}
136+
137+
#progressText {
138+
font-size: 18px;
139+
margin-bottom: 10px;
140+
}
141+
142+
#progressBar {
143+
width: 80%;
144+
height: 20px;
145+
border: 1px solid #ddd;
146+
border-radius: 5px;
147+
background-color: #e9ecef;
148+
}
149+
150+
</style>
151+
</head>
152+
<body>
153+
<!-- Header with Valkey Logo -->
154+
<div class="header">
155+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 73" fill="none">
156+
<g id="Group-copy">
157+
<path id="Path" fill="#123678" fill-rule="evenodd" stroke="none" d="M 13.482285 60.694962 L 0.998384 52.884399 L 0.998384 19.502914 L 31.527868 2.001205 L 61.317604 19.532024 L 61.317604 54.64489 L 31.054855 71.68927 L 20.548372 65.115807 L 20.548372 51.041328 L 20.548372 49.119896 L 14.851504 45.555508 L 14.851504 27.453159 L 31.346497 17.99712 L 47.464485 27.482262 L 47.464485 46.451157 L 34.703495 53.638138 L 34.703495 45.998573 C 38.52874 44.52552 41.274452 40.739189 41.274452 36.270489 C 41.274452 30.510658 36.712814 25.88438 31.158138 25.88438 C 25.603172 25.88438 21.041817 30.510658 21.041817 36.270489 C 21.041817 40.739189 23.787249 44.52552 27.612494 45.998573 L 27.612494 60.473576 L 31.261133 62.756348 L 53.635483 50.15464 L 53.635483 23.924595 L 31.477489 10.884869 L 8.680504 23.953705 L 8.680504 48.628967 L 13.482285 51.633297 L 13.482285 60.694962 Z M 31.158138 31.498383 C 33.671822 31.498383 35.660439 33.664162 35.660439 36.270489 C 35.660439 38.876804 33.671822 41.042587 31.158138 41.042587 C 28.644447 41.042587 26.655558 38.876804 26.655558 36.270489 C 26.655558 33.664162 28.644447 31.498383 31.158138 31.498383 Z"/>
158+
</g>
159+
</svg>
160+
<h1>Try Valkey</h1>
161+
</div>
162+
<h2>This is an in-browser Valkey Server and CLI. </h2>
163+
164+
<div class="container">
165+
<!-- CLI Section -->
166+
<div class="cli-section">
167+
<h2 class="cli-description">CLI:</h2>
168+
<div id="terminal-container"></div>
169+
</div>
170+
171+
<!-- Log Section -->
172+
<div class="log-section">
173+
<h2 class="log-description">Server Log:</h2>
174+
<div id="log-terminal-container"></div>
175+
</div>
176+
</div>
177+
178+
<!-- Loading Section -->
179+
<div id="loadingContainer">
180+
<p id="progressText">Preparing to load...</p>
181+
<progress id="progressBar" value="0" max="100"></progress>
182+
</div>
183+
184+
<script>
185+
"use strict";
186+
187+
const FILE_URL = "../images/try-valkey.bin.gz"; // Path to the .gz file
188+
const CACHE_KEY = "valkey_binary_cache";
189+
const LAST_MODIFIED_KEY = "valkey_last_modified";
190+
const LOG_FILE_PATH = "/tmp/valkey.log";
191+
let emulator;
192+
193+
async function openIndexedDB() {
194+
return new Promise((resolve, reject) => {
195+
const request = indexedDB.open("binaryCacheDB", 1);
196+
request.onerror = () => reject("Error opening IndexedDB");
197+
request.onsuccess = () => resolve(request.result);
198+
request.onupgradeneeded = (event) => {
199+
const db = event.target.result;
200+
db.createObjectStore("cache", { keyPath: "key" });
201+
};
202+
});
203+
}
204+
205+
async function getCachedBinary(db) {
206+
return new Promise((resolve, reject) => {
207+
const transaction = db.transaction(["cache"], "readonly");
208+
const objectStore = transaction.objectStore("cache");
209+
const request = objectStore.get(CACHE_KEY);
210+
request.onerror = () => reject("Error retrieving cached binary");
211+
request.onsuccess = () => resolve(request.result ? request.result.data : null);
212+
});
213+
}
214+
215+
async function saveBinaryToCache(db, data) {
216+
return new Promise((resolve, reject) => {
217+
const transaction = db.transaction(["cache"], "readwrite");
218+
const objectStore = transaction.objectStore("cache");
219+
const request = objectStore.put({ key: CACHE_KEY, data });
220+
request.onerror = () => reject("Error saving binary to cache");
221+
request.onsuccess = () => resolve();
222+
});
223+
}
224+
225+
async function checkIfUpdated() {
226+
return new Promise((resolve, reject) => {
227+
const xhr = new XMLHttpRequest();
228+
xhr.open("HEAD", FILE_URL, true);
229+
xhr.onload = () => {
230+
const serverLastModified = xhr.getResponseHeader("Last-Modified");
231+
const cachedLastModified = localStorage.getItem(LAST_MODIFIED_KEY);
232+
if (!serverLastModified || serverLastModified !== cachedLastModified) {
233+
localStorage.setItem(LAST_MODIFIED_KEY, serverLastModified);
234+
resolve(true);
235+
} else {
236+
resolve(false);
237+
}
238+
};
239+
xhr.onerror = () => reject("Error checking file version");
240+
xhr.send();
241+
});
242+
}
243+
244+
function downloadAndDecompressBinary(callback) {
245+
const xhr = new XMLHttpRequest();
246+
xhr.open("GET", FILE_URL, true);
247+
xhr.responseType = "arraybuffer";
248+
xhr.onprogress = (event) => {
249+
if (event.lengthComputable) {
250+
const percentComplete = (event.loaded / event.total) * 100;
251+
document.getElementById("progressBar").value = percentComplete;
252+
}
253+
};
254+
xhr.onload = () => {
255+
if (xhr.status === 200) {
256+
document.getElementById("progressText").innerText = "Decompressing image...";
257+
const decompressedData = pako.ungzip(new Uint8Array(xhr.response));
258+
callback(decompressedData);
259+
}
260+
};
261+
xhr.onerror = () => {
262+
document.getElementById("progressText").innerText = "Download failed!";
263+
};
264+
xhr.send();
265+
}
266+
267+
async function loadEmulator(decompressedData) {
268+
const progressText = document.getElementById("progressText");
269+
const blob = new Blob([decompressedData], { type: "application/octet-stream" });
270+
const imgUrl = URL.createObjectURL(blob);
271+
progressText.innerText = "Starting emulator...";
272+
273+
emulator = new V86({
274+
wasm_path: "../build/v86.wasm",
275+
memory_size: 512 * 1024 * 1024,
276+
vga_memory_size: 8 * 1024 * 1024,
277+
bios: { url: "../bios/seabios.bin" },
278+
vga_bios: { url: "../bios/vgabios.bin" },
279+
filesystem: {
280+
baseurl: "../images/alpine-rootfs-flat-new",
281+
basefs: "../images/alpine-fs-new.json",
282+
},
283+
autostart: true,
284+
bzimage_initrd_from_filesystem: true,
285+
cmdline: "rw root=host9p rootfstype=9p rootflags=trans=virtio,cache=loose modules=virtio_pci tsc=reliable",
286+
initial_state: { url: imgUrl },
287+
});
288+
289+
const serialAdapter = new SerialAdapterXtermJS(document.getElementById('terminal-container'), emulator.bus);
290+
serialAdapter.show();
291+
292+
document.getElementById("loadingContainer").style.display = "none";
293+
294+
initLogWindow();
295+
startLogUpdateInterval();
296+
}
297+
298+
function initLogWindow() {
299+
const logTerminalDiv = document.getElementById('log-terminal-container');
300+
updateLogWindow(logTerminalDiv);
301+
}
302+
303+
async function updateLogWindow(logTerminalDiv) {
304+
try {
305+
const content = await emulator.read_file(LOG_FILE_PATH);
306+
if (content) {
307+
logTerminalDiv.textContent = '';
308+
const lines = new TextDecoder().decode(content).split('\n');
309+
lines.forEach(line => {
310+
const logLine = document.createElement('div');
311+
logLine.textContent = line;
312+
logTerminalDiv.appendChild(logLine);
313+
});
314+
logTerminalDiv.scrollTop = logTerminalDiv.scrollHeight;
315+
}
316+
} catch (error) {
317+
console.error("Error reading log file:", error);
318+
}
319+
}
320+
321+
function startLogUpdateInterval() {
322+
const logTerminalDiv = document.getElementById('log-terminal-container');
323+
setInterval(() => updateLogWindow(logTerminalDiv), 1000);
324+
}
325+
326+
window.onload = async function () {
327+
const db = await openIndexedDB();
328+
329+
try {
330+
const needsDownload = await checkIfUpdated();
331+
332+
if (needsDownload) {
333+
downloadAndDecompressBinary(async (decompressedData) => {
334+
await saveBinaryToCache(db, decompressedData);
335+
loadEmulator(decompressedData);
336+
});
337+
} else {
338+
const cachedBinary = await getCachedBinary(db);
339+
if (cachedBinary) {
340+
loadEmulator(cachedBinary);
341+
} else {
342+
downloadAndDecompressBinary(async (decompressedData) => {
343+
await saveBinaryToCache(db, decompressedData);
344+
loadEmulator(decompressedData);
345+
});
346+
}
347+
}
348+
} catch (error) {
349+
console.error("Error loading binary: ", error);
350+
document.getElementById("progressText").innerText = "Failed to load binary.";
351+
}
352+
};
353+
</script>
354+
</body>
355+
</html>

0 commit comments

Comments
 (0)