mirror of
https://github.com/syssecfsu/witty.git
synced 2025-03-09 16:00:43 +01:00
WIP
This commit is contained in:
parent
98e37801c4
commit
b458da4414
@ -8,7 +8,7 @@
|
||||
<meta name="author" content="">
|
||||
|
||||
<!-- automatically refresh the page every 30 seconds -->
|
||||
<meta http-equiv="refresh" content="20">
|
||||
<meta http-equiv="refresh" content="30">
|
||||
|
||||
<title>Web Terminal</title>
|
||||
|
||||
|
@ -35,4 +35,4 @@
|
||||
.navbar-xs .navbar-nav>li>a {
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
@ -37,39 +37,40 @@
|
||||
<div id="terminal_view"></div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center">
|
||||
<button type="button" class="btn btn-primary btn-sm" style="margin-right: 3px;">
|
||||
<button type="button" class="btn btn-primary btn-sm" style="margin-right: 3px;" onclick="playbtn()">
|
||||
<img src="/assets/play.svg" id="play-btn" height="18px">
|
||||
</button>
|
||||
<div class="progress" id="replay-control" style="width: 906px;">
|
||||
<div class="progress-bar bg-secondary" role="progressbar" style="width: 25%" aria-valuenow="25"
|
||||
aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
<input type="range" class="form-range" min="0" max="100" id="replay-control"
|
||||
onchange="console.log(this.value)">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
function Init() {
|
||||
let term = createReplayTerminal();
|
||||
var str = [
|
||||
' ┌────────────────────────────────────────────────────────────────────────────┐\n',
|
||||
' │ \u001b[32;1mhttps://github.com/syssecfsu/witty\x1b[0m <- click it! │\n',
|
||||
' └────────────────────────────────────────────────────────────────────────────┘\n',
|
||||
''
|
||||
].join('');
|
||||
term = Init()
|
||||
|
||||
term.writeln(str);
|
||||
var rc = document.querySelector("#replay-control")
|
||||
rc.value = 0
|
||||
|
||||
// adjust the progress bar size to that of terminal
|
||||
let vp = document.querySelector("#terminal")
|
||||
let pbar = document.querySelector("#replay-control")
|
||||
pbar.setAttribute("style", "width:" + (vp.offsetWidth - 32) + "px");
|
||||
var icon = document.getElementById("play-btn")
|
||||
var path = "/records/5FGD56YdzAYDGF9s_61e33645.rec"
|
||||
var pause = false
|
||||
|
||||
|
||||
document.getElementById("play-btn").src = "/assets/pause.svg";
|
||||
function playbtn() {
|
||||
if (icon.src.includes("play")) {
|
||||
icon.src = "/assets/pause.svg"
|
||||
pause = false
|
||||
replay_session(term, path, rc.value,
|
||||
function () {
|
||||
return pause
|
||||
},
|
||||
function (percent) {
|
||||
rc.value = percent
|
||||
})
|
||||
} else {
|
||||
icon.src = "/assets/play.svg"
|
||||
pause = true
|
||||
}
|
||||
}
|
||||
|
||||
Init()
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
187
assets/replay.js
187
assets/replay.js
@ -1,46 +1,145 @@
|
||||
// create a xterm for replay
|
||||
function createReplayTerminal() {
|
||||
// vscode-snazzy https://github.com/Tyriar/vscode-snazzy
|
||||
// copied from xterm.js website
|
||||
var baseTheme = {
|
||||
foreground: '#eff0eb',
|
||||
background: '#282a36',
|
||||
selection: '#97979b33',
|
||||
black: '#282a36',
|
||||
brightBlack: '#686868',
|
||||
red: '#ff5c57',
|
||||
brightRed: '#ff5c57',
|
||||
green: '#5af78e',
|
||||
brightGreen: '#5af78e',
|
||||
yellow: '#f3f99d',
|
||||
brightYellow: '#f3f99d',
|
||||
blue: '#57c7ff',
|
||||
brightBlue: '#57c7ff',
|
||||
magenta: '#ff6ac1',
|
||||
brightMagenta: '#ff6ac1',
|
||||
cyan: '#9aedfe',
|
||||
brightCyan: '#9aedfe',
|
||||
white: '#f1f1f0',
|
||||
brightWhite: '#eff0eb'
|
||||
};
|
||||
|
||||
const term = new Terminal({
|
||||
fontFamily: `'Fira Code', ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation Mono',monospace`,
|
||||
fontSize: 12,
|
||||
theme: baseTheme,
|
||||
convertEol: true,
|
||||
cursorBlink: true,
|
||||
});
|
||||
|
||||
term.open(document.getElementById('terminal_view'));
|
||||
term.resize(124, 37);
|
||||
|
||||
const weblinksAddon = new WebLinksAddon.WebLinksAddon();
|
||||
term.loadAddon(weblinksAddon);
|
||||
|
||||
// fit the xterm viewpoint to parent element
|
||||
const fitAddon = new FitAddon.FitAddon();
|
||||
term.loadAddon(fitAddon);
|
||||
fitAddon.fit();
|
||||
// vscode-snazzy https://github.com/Tyriar/vscode-snazzy
|
||||
// copied from xterm.js website
|
||||
var baseTheme = {
|
||||
foreground: '#eff0eb',
|
||||
background: '#282a36',
|
||||
selection: '#97979b33',
|
||||
black: '#282a36',
|
||||
brightBlack: '#686868',
|
||||
red: '#ff5c57',
|
||||
brightRed: '#ff5c57',
|
||||
green: '#5af78e',
|
||||
brightGreen: '#5af78e',
|
||||
yellow: '#f3f99d',
|
||||
brightYellow: '#f3f99d',
|
||||
blue: '#57c7ff',
|
||||
brightBlue: '#57c7ff',
|
||||
magenta: '#ff6ac1',
|
||||
brightMagenta: '#ff6ac1',
|
||||
cyan: '#9aedfe',
|
||||
brightCyan: '#9aedfe',
|
||||
white: '#f1f1f0',
|
||||
brightWhite: '#eff0eb'
|
||||
};
|
||||
|
||||
return term;
|
||||
}
|
||||
const term = new Terminal({
|
||||
fontFamily: `'Fira Code', ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation Mono',monospace`,
|
||||
fontSize: 12,
|
||||
theme: baseTheme,
|
||||
convertEol: true,
|
||||
cursorBlink: true,
|
||||
});
|
||||
|
||||
term.open(document.getElementById('terminal_view'));
|
||||
term.resize(124, 37);
|
||||
|
||||
const weblinksAddon = new WebLinksAddon.WebLinksAddon();
|
||||
term.loadAddon(weblinksAddon);
|
||||
|
||||
// fit the xterm viewpoint to parent element
|
||||
const fitAddon = new FitAddon.FitAddon();
|
||||
term.loadAddon(fitAddon);
|
||||
fitAddon.fit();
|
||||
|
||||
return term;
|
||||
}
|
||||
|
||||
// sleep for ms seconds
|
||||
function _sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
// we could sleep for a long time
|
||||
// periodically check if we need to end replay.
|
||||
// This is pretty ugly but the callback mess otherwise
|
||||
async function sleep(ms, end) {
|
||||
var loop_cnt = parseInt(ms / 20) + 1
|
||||
|
||||
for (i = 0; i < loop_cnt; i++) {
|
||||
if (end()) {
|
||||
return end()
|
||||
}
|
||||
|
||||
await _sleep(20)
|
||||
}
|
||||
|
||||
return end()
|
||||
}
|
||||
// convert data to uint8array, we cannot convert it to string as
|
||||
// it will mess up special characters
|
||||
function base64ToUint8array(base64) {
|
||||
var raw = window.atob(base64);
|
||||
var rawLength = raw.length;
|
||||
var array = new Uint8Array(new ArrayBuffer(rawLength));
|
||||
|
||||
for (i = 0; i < rawLength; i++) {
|
||||
array[i] = raw.charCodeAt(i);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
// replay session
|
||||
// term: xterm, path: session file to replay,
|
||||
// start: start position to replay in percentile, range 0-100
|
||||
// callback to update the progress bar
|
||||
async function replay_session(term, path, start, end, prog) {
|
||||
var session
|
||||
|
||||
// read file from server
|
||||
await fetch(path)
|
||||
.then(res => res.json())
|
||||
.then(out => {
|
||||
session = out
|
||||
})
|
||||
|
||||
var total_dur = 0
|
||||
var cur = 0
|
||||
|
||||
//calculate the total duration
|
||||
for (const item of session) {
|
||||
item.Duration = parseInt(item.Duration / 1000000)
|
||||
total_dur += item.Duration
|
||||
}
|
||||
|
||||
start = parseInt(total_dur * start / 100)
|
||||
console.log("Total duration:", total_dur, "start replay on", start)
|
||||
|
||||
term.reset()
|
||||
for (const item of session) {
|
||||
cur += item.Duration
|
||||
|
||||
// we will blast through the beginning of the session
|
||||
if (cur >= start) {
|
||||
if (await sleep(item.Duration, end) == true) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (item.Duration >= total_dur / 100) {
|
||||
prog(cur * 100 / total_dur)
|
||||
}
|
||||
|
||||
term.write(base64ToUint8array(item.Data))
|
||||
}
|
||||
}
|
||||
|
||||
function Init() {
|
||||
let term = createReplayTerminal();
|
||||
var str = [
|
||||
' ┌────────────────────────────────────────────────────────────────────────────┐\n',
|
||||
' │ \u001b[32;1mhttps://github.com/syssecfsu/witty\x1b[0m <- click it! │\n',
|
||||
' └────────────────────────────────────────────────────────────────────────────┘\n',
|
||||
''
|
||||
].join('');
|
||||
|
||||
term.writeln(str);
|
||||
|
||||
// adjust the progress bar size to that of terminal
|
||||
var view = document.querySelector("#terminal")
|
||||
var pbar = document.querySelector("#replay-control")
|
||||
pbar.setAttribute("style", "width:" + (view.offsetWidth - 32) + "px");
|
||||
|
||||
return term
|
||||
}
|
@ -33,7 +33,7 @@
|
||||
</header>
|
||||
|
||||
|
||||
<div style="margin-top: 5em;">
|
||||
<div style="margin-top: 3em;">
|
||||
<div id="terminal">
|
||||
<div id="terminal_view"></div>
|
||||
</div>
|
||||
|
@ -40,7 +40,7 @@ func main() {
|
||||
w, h, _ := term.GetSize(int(os.Stdout.Fd()))
|
||||
|
||||
if (w != 120) || (h != 36) {
|
||||
log.Fatalln("Set terminal window to 120x36 before continue")
|
||||
log.Println("Set terminal window to 120x36 before continue")
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(fp)
|
||||
@ -49,6 +49,10 @@ func main() {
|
||||
log.Fatalln("Failed to create JSON decoder")
|
||||
}
|
||||
|
||||
// To work with javascript decoder, we organize the file as
|
||||
// an array of writeRecord. golang decode instead decode
|
||||
// as individual record. Call decoder.Token to skip opening [
|
||||
decoder.Token()
|
||||
for decoder.More() {
|
||||
var record writeRecord
|
||||
|
||||
@ -60,4 +64,6 @@ func main() {
|
||||
time.Sleep(record.Dur)
|
||||
t.Write(record.Data)
|
||||
}
|
||||
|
||||
t.Write([]byte("\n\n---end of replay---\n\n"))
|
||||
}
|
||||
|
1
main.go
1
main.go
@ -140,6 +140,7 @@ func main() {
|
||||
|
||||
// handle static files
|
||||
rt.Static("/assets", "./assets")
|
||||
rt.Static("/records", "./records")
|
||||
|
||||
term_conn.Init(checkOrigin)
|
||||
|
||||
|
@ -249,6 +249,7 @@ out:
|
||||
log.Println("Failed to marshal record", err)
|
||||
} else {
|
||||
tc.record.Write(jbuf)
|
||||
tc.record.Write([]byte(",")) // write a deliminator
|
||||
}
|
||||
|
||||
tc.lastRecTime = time.Now()
|
||||
@ -266,8 +267,17 @@ out:
|
||||
tc.record = nil
|
||||
}
|
||||
|
||||
tc.record.Write([]byte("[")) // write a [ for an array of json objs
|
||||
tc.lastRecTime = time.Now()
|
||||
} else {
|
||||
fsinfo, err := tc.record.Stat()
|
||||
|
||||
if err == nil {
|
||||
tc.record.Truncate(fsinfo.Size() - 1)
|
||||
tc.record.Seek(0, 2) // truncate does not change read/write location
|
||||
tc.record.Write([]byte("]"))
|
||||
}
|
||||
|
||||
tc.record.Close()
|
||||
tc.record = nil
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user