mirror of
https://github.com/syssecfsu/witty.git
synced 2024-12-24 11:42:34 +01:00
WIP
This commit is contained in:
parent
4d06a2c710
commit
9d62279215
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
|
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||||
|
#
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
witty
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
@ -1,116 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
||||||
<meta name="description" content="">
|
|
||||||
<meta name="author" content="">
|
|
||||||
|
|
||||||
<!-- automatically refresh the page every 30 seconds -->
|
|
||||||
<!-- <meta http-equiv="refresh" content="30"> -->
|
|
||||||
|
|
||||||
<title>Web Terminal</title>
|
|
||||||
|
|
||||||
<!-- Bootstrap core CSS -->
|
|
||||||
<script src="/assets/external/bootstrap.min.js"></script>
|
|
||||||
<link href="/assets/external/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link href="/assets/main.css" rel="stylesheet">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<nav class="navbar navbar-dark bg-dark shadow-sm navbar-xs">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<a class="navbar-brand mx-auto" href="https://github.com/syssecfsu/witty" target="_blank">
|
|
||||||
<img src="/assets/img/logo.svg" style="margin-right: 0.5rem;" height="32" class="d-inline-block align-text-top">
|
|
||||||
WiTTY: Web-based interactive TTY
|
|
||||||
</a>
|
|
||||||
<a class="btn btn-primary btn-sm float-end" href="/new"
|
|
||||||
onClick="setTimeout(function(){window.location.reload()}, 2000);" target="_blank" role="button">
|
|
||||||
New Session
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<div class="container-fluid" style="margin-top:1em;">
|
|
||||||
<nav>
|
|
||||||
<div class="nav nav-tabs" id="nav-tab" role="tablist">
|
|
||||||
<button class="nav-link border bg-light" id="nav-home-tab" data-bs-toggle="tab" data-bs-target="#nav-home"
|
|
||||||
type="button" role="tab" aria-controls="nav-home" aria-selected="true">Live</button>
|
|
||||||
<button class="nav-link border active bg-light" id="nav-profile-tab" data-bs-toggle="tab"
|
|
||||||
data-bs-target="#nav-profile" type="button" role="tab" aria-controls="nav-profile"
|
|
||||||
aria-selected="false">Saved</button>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="tab-content bg-light" id="nav-tabContent">
|
|
||||||
<div class="tab-pane fade" id="nav-home" role="tabpanel" aria-labelledby="nav-home-tab">
|
|
||||||
<div class="card-deck row justify-content-center">
|
|
||||||
|
|
||||||
<!-- repeat this for each interactive session -->
|
|
||||||
{{range .players}}
|
|
||||||
<div class="card shadow-sm border-danger bg-white mb-3" style="width: 16rem; margin:1em;">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title">Interactive session</h5>
|
|
||||||
<p class="card-text">From <em>{{.Ip}}</em>, running <strong>{{.Cmd}}</strong>, session ID:
|
|
||||||
<u>{{.Id}}</u>
|
|
||||||
</p>
|
|
||||||
<a class="btn btn-outline-success btn-sm float-end" href="/view/{{.Id}}" target="_blank"
|
|
||||||
role="button">
|
|
||||||
<img src="/assets/img/view.svg" height="18px">
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tab-pane fade show active" id="nav-profile" role="tabpanel" aria-labelledby="nav-profile-tab">
|
|
||||||
<div class="card-deck row justify-content-center">
|
|
||||||
|
|
||||||
<!-- repeat this for each recorded session -->
|
|
||||||
{{range .records}}
|
|
||||||
<div class="card shadow-sm border-info bg-light mb-3" style="width: 16rem; margin:1em;">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title">Recorded session</h5>
|
|
||||||
<p class="card-text">File name: <u>{{.Fname}}</u>, file size: <em>{{.Fsize}}KB</em>,
|
|
||||||
recorded at <strong>{{.Time}}</strong>, duration: <mark>{{.Duration}}s</mark>,
|
|
||||||
</p>
|
|
||||||
<div class="btn-toolbar float-end" role="toolbar" aria-label="records buttons">
|
|
||||||
<a class="btn btn-outline-success btn-sm m-1" href="/replay/{{.Fname}}" target="_blank" role="button">
|
|
||||||
<img src="/assets/img/play.svg" height="18px">
|
|
||||||
</a>
|
|
||||||
<a class="btn btn-outline-success btn-sm m-1" href="/records/{{.Fname}}" role="button" download>
|
|
||||||
<img src="/assets/img/download.svg" height="18px">
|
|
||||||
</a>
|
|
||||||
<button type="button" class="btn btn-outline-success btn-sm m-1" onclick="del_btn({{.Fname}})">
|
|
||||||
<img src="/assets/img/delete.svg" height="18px">
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function del_btn(path) {
|
|
||||||
fetch("/delete/" + path)
|
|
||||||
setTimeout(function () {
|
|
||||||
window.location.reload()
|
|
||||||
}, 800);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
100
assets/template/index.html
Normal file
100
assets/template/index.html
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<meta name="description" content="">
|
||||||
|
<meta name="author" content="">
|
||||||
|
|
||||||
|
<!-- automatically refresh the page every 30 seconds -->
|
||||||
|
<!-- <meta http-equiv="refresh" content="30"> -->
|
||||||
|
|
||||||
|
<title>Web Terminal</title>
|
||||||
|
|
||||||
|
<!-- Bootstrap core CSS -->
|
||||||
|
<script src="/assets/external/bootstrap.min.js"></script>
|
||||||
|
<link href="/assets/external/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="/assets/main.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<nav class="navbar navbar-dark bg-dark shadow-sm navbar-xs">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand mx-auto" href="https://github.com/syssecfsu/witty" target="_blank">
|
||||||
|
<img src="/assets/img/logo.svg" style="margin-right: 0.5rem;" height="32"
|
||||||
|
class="d-inline-block align-text-top">
|
||||||
|
WiTTY: Web-based interactive TTY
|
||||||
|
</a>
|
||||||
|
<a class="btn btn-primary btn-sm float-end" href="/new" onClick="setTimeout(function(){refresh(true)}, 1000)"
|
||||||
|
target="_blank" role="button">
|
||||||
|
New Session
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div class="container-fluid" style="margin-top:1em;">
|
||||||
|
<ul class="nav nav-tabs" id="js_sucks" role="tablist">
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link bg-light active" id="interactive-tab" data-bs-toggle="tab"
|
||||||
|
data-bs-target="#interactive-cnt" type="button" role="tab" aria-controls="interactive-cnt"
|
||||||
|
aria-selected="true">Live Session</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link bg-light" id="saved-tab" data-bs-toggle="tab" data-bs-target="#saved-cnt"
|
||||||
|
type="button" role="tab" aria-controls="saved-cnt" aria-selected="false">Saved Session</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="tab-content bg-light border border-top-0 border-info rounded-bottom" id="nav-tabContent">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var active_tab = 0
|
||||||
|
|
||||||
|
function del_btn(path) {
|
||||||
|
fetch("/delete/" + path)
|
||||||
|
setTimeout(function () {
|
||||||
|
refresh(true)
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fresh the page every 10 seconds, in case active sessions are closed.
|
||||||
|
function refresh(once) {
|
||||||
|
tabs = document.getElementById("nav-tabContent")
|
||||||
|
fetch("/update/" + active_tab)
|
||||||
|
.then((response) => {
|
||||||
|
return response.text();
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
tabs.innerHTML = result;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (once == false) {
|
||||||
|
setTimeout(function () {
|
||||||
|
refresh(false)
|
||||||
|
}, 10000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh(false)
|
||||||
|
|
||||||
|
var itab = document.querySelector('#interactive-tab')
|
||||||
|
itab.addEventListener('shown.bs.tab', function (event) {
|
||||||
|
active_tab = 0
|
||||||
|
})
|
||||||
|
|
||||||
|
var stab = document.querySelector('#saved-tab')
|
||||||
|
stab.addEventListener('shown.bs.tab', function (event) {
|
||||||
|
active_tab = 1
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
50
assets/template/tab.html
Normal file
50
assets/template/tab.html
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<div class="tab-pane {{.active0}}" id="interactive-cnt" role="tabpanel" aria-labelledby="interactive-tab">
|
||||||
|
<div class="card-deck row justify-content-center">
|
||||||
|
|
||||||
|
<!-- repeat this for each interactive session -->
|
||||||
|
{{range .players}}
|
||||||
|
<div class="card shadow-sm border-danger bg-white mb-3" style="width: 16rem; margin:1em;">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Interactive session</h5>
|
||||||
|
<p class="card-text">From <em>{{.Ip}}</em>, running <strong>{{.Cmd}}</strong>, session ID:
|
||||||
|
<u>{{.Id}}</u>
|
||||||
|
</p>
|
||||||
|
<a class="btn btn-outline-success btn-sm float-end" href="/view/{{.Id}}" target="_blank" role="button">
|
||||||
|
<img src="/assets/img/view.svg" height="18px">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-pane {{.active1}}" id="saved-cnt" role="tabpanel" aria-labelledby="saved-tab">
|
||||||
|
<div class="card-deck row justify-content-center">
|
||||||
|
|
||||||
|
<!-- repeat this for each recorded session -->
|
||||||
|
{{range .records}}
|
||||||
|
<div class="card shadow-sm border-info bg-light mb-3" style="width: 16rem; margin:1em;">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Recorded session</h5>
|
||||||
|
<p class="card-text">File name: <u>{{.Fname}}</u>, file size: <em>{{.Fsize}}KB</em>,
|
||||||
|
recorded at <strong>{{.Time}}</strong>, duration: <mark>{{.Duration}}s</mark>,
|
||||||
|
</p>
|
||||||
|
<div class="btn-toolbar float-end" role="toolbar" aria-label="records buttons">
|
||||||
|
<a class="btn btn-outline-success btn-sm m-1" href="/replay/{{.Fname}}" target="_blank"
|
||||||
|
role="button">
|
||||||
|
<img src="/assets/img/play.svg" height="18px">
|
||||||
|
</a>
|
||||||
|
<a class="btn btn-outline-success btn-sm m-1" href="/records/{{.Fname}}" role="button" download>
|
||||||
|
<img src="/assets/img/download.svg" height="18px">
|
||||||
|
</a>
|
||||||
|
<button type="button" class="btn btn-outline-success btn-sm m-1" onclick="del_btn({{.Fname}})">
|
||||||
|
<img src="/assets/img/delete.svg" height="18px">
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
48
main.go
48
main.go
@ -86,10 +86,7 @@ func getDuration(fname string) int64 {
|
|||||||
return dur/1000 + 1
|
return dur/1000 + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func fillIndex(c *gin.Context) {
|
func collectTabData(c *gin.Context) (players []InteractiveSession, records []RecordedSession) {
|
||||||
var players []InteractiveSession
|
|
||||||
var records []RecordedSession
|
|
||||||
|
|
||||||
term_conn.ForEachSession(func(tc *term_conn.TermConn) {
|
term_conn.ForEachSession(func(tc *term_conn.TermConn) {
|
||||||
players = append(players, InteractiveSession{
|
players = append(players, InteractiveSession{
|
||||||
Id: tc.Name,
|
Id: tc.Name,
|
||||||
@ -99,6 +96,7 @@ func fillIndex(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
files, err := ioutil.ReadDir("./records/")
|
files, err := ioutil.ReadDir("./records/")
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, finfo := range files {
|
for _, finfo := range files {
|
||||||
fname := finfo.Name()
|
fname := finfo.Name()
|
||||||
@ -118,11 +116,7 @@ func fillIndex(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "index.html", gin.H{
|
return
|
||||||
"title": "interactive terminal",
|
|
||||||
"players": players,
|
|
||||||
"records": records,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -148,12 +142,44 @@ func main() {
|
|||||||
rt := gin.Default()
|
rt := gin.Default()
|
||||||
|
|
||||||
rt.SetTrustedProxies(nil)
|
rt.SetTrustedProxies(nil)
|
||||||
rt.LoadHTMLGlob("./assets/*.html")
|
rt.LoadHTMLGlob("./assets/template/*")
|
||||||
|
|
||||||
// Fill in the index page
|
// Fill in the index page
|
||||||
rt.GET("/", func(c *gin.Context) {
|
rt.GET("/", func(c *gin.Context) {
|
||||||
host = &c.Request.Host
|
host = &c.Request.Host
|
||||||
fillIndex(c)
|
players, records := collectTabData(c)
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "index.html", gin.H{
|
||||||
|
"title": "interactive terminal",
|
||||||
|
"players": players,
|
||||||
|
"records": records,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// to update the tabs of current interactive and saved sessions
|
||||||
|
rt.GET("/update/:active", func(c *gin.Context) {
|
||||||
|
var active0, active1 string
|
||||||
|
|
||||||
|
// setup which tab is active, it is hard to do in javascript at
|
||||||
|
// client side due to timing issues.
|
||||||
|
which := c.Param("active")
|
||||||
|
if which == "0" {
|
||||||
|
active0 = "active"
|
||||||
|
active1 = ""
|
||||||
|
} else {
|
||||||
|
active0 = ""
|
||||||
|
active1 = "active"
|
||||||
|
}
|
||||||
|
|
||||||
|
host = &c.Request.Host
|
||||||
|
players, records := collectTabData(c)
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "tab.html", gin.H{
|
||||||
|
"players": players,
|
||||||
|
"records": records,
|
||||||
|
"active0": active0,
|
||||||
|
"active1": active1,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// create a new interactive session
|
// create a new interactive session
|
||||||
|
Loading…
Reference in New Issue
Block a user