mirror of
https://github.com/syssecfsu/witty.git
synced 2025-01-25 19:44:24 +01:00
add listusers and replay subcommand
This commit is contained in:
parent
8dfac2d026
commit
a76beab83a
@ -7,7 +7,7 @@ This program allows you to use terminal in the browser. Simply run the program a
|
|||||||
|
|
||||||
2. WiTTY allows users to **easily record, replay, and share console sessions** with just a few clicks. This make it a breeze to answer course-related questions, espeically with the source code. Instead of wall of text to describe their questions, students can just send a recorded session.
|
2. WiTTY allows users to **easily record, replay, and share console sessions** with just a few clicks. This make it a breeze to answer course-related questions, espeically with the source code. Instead of wall of text to describe their questions, students can just send a recorded session.
|
||||||
|
|
||||||
This repository contains a recorded session in the ```assets/extra``` directory ([M1NXZvHdvA8vSCKp_61e5d60f.rec](extra/M1NXZvHdvA8vSCKp_61e5d60f.rec)) that shows me upgrading pihole. Just put the file under the ```records``` directory, run the server, you should find the recording in the ```Recorded Session``` tab.
|
This repository contains a recorded session in the ```assets/extra``` directory ([M1NXZvHdvA8vSCKp_61e5d60f.scr](extra/M1NXZvHdvA8vSCKp_61e5d60f.scr)) that shows me upgrading pihole. Just put the file under the ```records``` directory, run the server, you should find the recording in the ```Recorded Session``` tab.
|
||||||
|
|
||||||
3. More features are planned, suggestions are welcome.
|
3. More features are planned, suggestions are welcome.
|
||||||
|
|
||||||
@ -80,4 +80,8 @@ Most icons were provided by [fontawesome](https://fontawesome.com/) under this [
|
|||||||
|
|
||||||
```https://<witty_server_ip>:8080```
|
```https://<witty_server_ip>:8080```
|
||||||
|
|
||||||
|
8. You can also replay the recorded sessions with witty
|
||||||
|
|
||||||
|
```./witty replay -w 500 records/<recorded file>.scr```
|
||||||
|
|
||||||
The program has been tested on Linux, WSL2, Raspberry Pi 3B (Debian), and MacOSX using Google Chrome, Firefox, and Safari.
|
The program has been tested on Linux, WSL2, Raspberry Pi 3B (Debian), and MacOSX using Google Chrome, Firefox, and Safari.
|
||||||
|
37
main.go
37
main.go
@ -6,19 +6,13 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/syssecfsu/witty/term_conn"
|
||||||
"github.com/syssecfsu/witty/web"
|
"github.com/syssecfsu/witty/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fp, err := os.OpenFile("witty.log", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
defer fp.Close()
|
|
||||||
log.SetOutput(fp)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
fmt.Println("witty (adduser|deluser|run)")
|
fmt.Println("witty (adduser|deluser|replay|run)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,6 +21,11 @@ func main() {
|
|||||||
runCmd.BoolVar(&naked, "n", false, "Run WiTTY without user authentication")
|
runCmd.BoolVar(&naked, "n", false, "Run WiTTY without user authentication")
|
||||||
runCmd.BoolVar(&naked, "naked", false, "Run WiTTY without user authentication")
|
runCmd.BoolVar(&naked, "naked", false, "Run WiTTY without user authentication")
|
||||||
|
|
||||||
|
var wait uint
|
||||||
|
replayCmd := flag.NewFlagSet("replay", flag.ExitOnError)
|
||||||
|
replayCmd.UintVar(&wait, "w", 2000, "Max wait time between outputs")
|
||||||
|
replayCmd.UintVar(&wait, "wait", 2000, "Max wait time between outputs")
|
||||||
|
|
||||||
switch os.Args[1] {
|
switch os.Args[1] {
|
||||||
case "adduser":
|
case "adduser":
|
||||||
if len(os.Args) != 3 {
|
if len(os.Args) != 3 {
|
||||||
@ -42,7 +41,27 @@ func main() {
|
|||||||
}
|
}
|
||||||
web.DelUser(os.Args[2])
|
web.DelUser(os.Args[2])
|
||||||
|
|
||||||
|
case "listusers":
|
||||||
|
web.ListUsers()
|
||||||
|
|
||||||
|
case "replay":
|
||||||
|
replayCmd.Parse(os.Args[2:])
|
||||||
|
|
||||||
|
if len(replayCmd.Args()) != 1 {
|
||||||
|
fmt.Println("witty replay <recorded>")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
term_conn.Replay(replayCmd.Arg(0), wait)
|
||||||
|
|
||||||
case "run":
|
case "run":
|
||||||
|
fp, err := os.OpenFile("witty.log", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
defer fp.Close()
|
||||||
|
log.SetOutput(fp)
|
||||||
|
}
|
||||||
|
|
||||||
runCmd.Parse(os.Args[2:])
|
runCmd.Parse(os.Args[2:])
|
||||||
|
|
||||||
var cmdToExec []string
|
var cmdToExec []string
|
||||||
@ -57,7 +76,7 @@ func main() {
|
|||||||
web.StartWeb(fp, cmdToExec, naked)
|
web.StartWeb(fp, cmdToExec, naked)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
fmt.Println("witty (adduser|deluser|run)")
|
fmt.Println("witty (adduser|deluser|replay|run)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
71
term_conn/replay.go
Normal file
71
term_conn/replay.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package term_conn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
type writeRecord struct {
|
||||||
|
Dur time.Duration `json:"Duration"`
|
||||||
|
Data []byte `json:"Data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Replay(fname string, wait uint) {
|
||||||
|
fp, err := os.Open(fname)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Failed to open record file", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
screen := struct {
|
||||||
|
io.Reader
|
||||||
|
io.Writer
|
||||||
|
}{os.Stdin, os.Stdout}
|
||||||
|
|
||||||
|
t := term.NewTerminal(screen, "$")
|
||||||
|
|
||||||
|
if t == nil {
|
||||||
|
log.Fatalln("Failed to create terminal")
|
||||||
|
}
|
||||||
|
|
||||||
|
w, h, _ := term.GetSize(int(os.Stdout.Fd()))
|
||||||
|
|
||||||
|
if (w != 120) || (h != 36) {
|
||||||
|
log.Println("Set terminal window to 120x36 before continue")
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(fp)
|
||||||
|
|
||||||
|
if decoder == nil {
|
||||||
|
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 [
|
||||||
|
t.Write([]byte("\n\n---beginning of replay---\n\n"))
|
||||||
|
|
||||||
|
decoder.Token()
|
||||||
|
for decoder.More() {
|
||||||
|
var record writeRecord
|
||||||
|
|
||||||
|
if err := decoder.Decode(&record); err != nil {
|
||||||
|
log.Println("Failed to decode record", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if record.Dur > time.Duration(wait)*time.Millisecond {
|
||||||
|
record.Dur = time.Duration(wait) * time.Millisecond
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(record.Dur)
|
||||||
|
t.Write(record.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Write([]byte("\n\n---end of replay---\n\n"))
|
||||||
|
}
|
40
web/user.go
40
web/user.go
@ -5,6 +5,7 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/dchest/uniuri"
|
"github.com/dchest/uniuri"
|
||||||
@ -54,7 +55,7 @@ func addUser(username []byte, passwd []byte) {
|
|||||||
|
|
||||||
output, err := json.Marshal(users)
|
output, err := json.Marshal(users)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to marshal passwords", err)
|
log.Println("Failed to marshal passwords", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +67,7 @@ func AddUser(username string) {
|
|||||||
passwd, err := term.ReadPassword(int(os.Stdin.Fd()))
|
passwd, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to read password", err)
|
log.Println("Failed to read password", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +75,7 @@ func AddUser(username string) {
|
|||||||
passwd2, err := term.ReadPassword(int(os.Stdin.Fd()))
|
passwd2, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to read password", err)
|
log.Println("Failed to read password", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,14 +94,14 @@ func DelUser(username string) {
|
|||||||
exist := false
|
exist := false
|
||||||
file, err := os.ReadFile(userFileName)
|
file, err := os.ReadFile(userFileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to read users file", err)
|
log.Println("Failed to read users file", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(file, &users)
|
err = json.Unmarshal(file, &users)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to parse json format", err)
|
log.Println("Failed to parse json format", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// update the existing user if it exists
|
// update the existing user if it exists
|
||||||
@ -115,7 +116,7 @@ func DelUser(username string) {
|
|||||||
if exist {
|
if exist {
|
||||||
output, err := json.Marshal(users)
|
output, err := json.Marshal(users)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to marshal passwords", err)
|
log.Println("Failed to marshal passwords", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,20 +124,43 @@ func DelUser(username string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ListUsers() {
|
||||||
|
var users []UserRecord
|
||||||
|
var err error
|
||||||
|
|
||||||
|
file, err := os.ReadFile(userFileName)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to read users file", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(file, &users)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to parse json format", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// update the existing user if it exists
|
||||||
|
fmt.Println("Users of the system:")
|
||||||
|
for _, u := range users {
|
||||||
|
fmt.Println(" ", string(u.User))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ValidateUser(username []byte, passwd []byte) bool {
|
func ValidateUser(username []byte, passwd []byte) bool {
|
||||||
var users []UserRecord
|
var users []UserRecord
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
file, err := os.ReadFile(userFileName)
|
file, err := os.ReadFile(userFileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to read users file", err)
|
log.Println("Failed to read users file", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(file, &users)
|
err = json.Unmarshal(file, &users)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to parse json format", err)
|
log.Println("Failed to parse json format", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user