mirror of
https://github.com/wavetermdev/backup.git
synced 2026-04-22 15:26:58 -07:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 395a315068 | |||
| e6c41788f5 | |||
| 3f988c0e6b | |||
| 0f2f6c9cc0 | |||
| 697f7c0758 |
@@ -48,12 +48,15 @@ class SimpleImageRenderer extends React.Component<
|
||||
return (
|
||||
<div className="image-renderer" style={{ fontSize: this.props.opts.termFontSize }}>
|
||||
<div className="load-error-text">
|
||||
ERROR: file {dataBlob && dataBlob.name ? JSON.stringify(dataBlob.name) : ""} not found
|
||||
ERROR: file {dataBlob?.name ? JSON.stringify(dataBlob.name) : ""} not found
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (this.objUrl == null) {
|
||||
if (dataBlob.name?.endsWith(".svg")) {
|
||||
dataBlob = new Blob([dataBlob], { type: "image/svg+xml" }) as ExtBlob;
|
||||
}
|
||||
this.objUrl = URL.createObjectURL(dataBlob);
|
||||
}
|
||||
let opts = this.props.opts;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
.media-renderer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: var(--termpad);
|
||||
|
||||
video {
|
||||
object-fit: contain;
|
||||
object-position: center;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as React from "react";
|
||||
import * as mobx from "mobx";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as util from "@/util/util";
|
||||
import { GlobalModel } from "@/models";
|
||||
|
||||
import "./media.less";
|
||||
|
||||
@mobxReact.observer
|
||||
class SimpleMediaRenderer extends React.Component<
|
||||
{ data: ExtBlob; context: RendererContext; opts: RendererOpts; savedHeight: number; lineState: LineStateType },
|
||||
{}
|
||||
> {
|
||||
objUrl: string = null;
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.objUrl != null) {
|
||||
URL.revokeObjectURL(this.objUrl);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let dataBlob = this.props.data;
|
||||
if (dataBlob == null || dataBlob.notFound) {
|
||||
return (
|
||||
<div className="media-renderer" style={{ fontSize: this.props.opts.termFontSize }}>
|
||||
<div className="load-error-text">
|
||||
ERROR: file {dataBlob && dataBlob.name ? JSON.stringify(dataBlob.name) : ""} not found
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let fileUrl = this.props.lineState["wave:fileurl"];
|
||||
if (util.isBlank(fileUrl)) {
|
||||
return (
|
||||
<div className="media-renderer" style={{ fontSize: this.props.opts.termFontSize }}>
|
||||
<div className="load-error-text">
|
||||
ERROR: no fileurl found (please use `mediaview` to view media files)
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let fullVideoUrl = GlobalModel.getBaseHostPort() + fileUrl;
|
||||
const opts = this.props.opts;
|
||||
const height = opts.idealSize.height - 10;
|
||||
const width = opts.maxSize.width - 10;
|
||||
return (
|
||||
<div className="media-renderer" style={{ height: height, width: width }}>
|
||||
<video controls>
|
||||
<source src={fullVideoUrl} />
|
||||
</video>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { SimpleMediaRenderer };
|
||||
@@ -32,7 +32,7 @@ class SimplePdfRenderer extends React.Component<
|
||||
);
|
||||
}
|
||||
if (this.objUrl == null) {
|
||||
const pdfBlob = new File([dataBlob], "test.pdf", { type: "application/pdf" });
|
||||
const pdfBlob = new File([dataBlob], dataBlob.name ?? "file.pdf", { type: "application/pdf" });
|
||||
this.objUrl = URL.createObjectURL(pdfBlob);
|
||||
}
|
||||
const opts = this.props.opts;
|
||||
|
||||
@@ -8,6 +8,7 @@ import { SimpleMustacheRenderer } from "./mustache/mustache";
|
||||
import { CSVRenderer } from "./csv/csv";
|
||||
import { OpenAIRenderer, OpenAIRendererModel } from "./openai/openai";
|
||||
import { SimplePdfRenderer } from "./pdf/pdf";
|
||||
import { SimpleMediaRenderer } from "./media/media";
|
||||
import { isBlank } from "@/util/util";
|
||||
import { sprintf } from "sprintf-js";
|
||||
|
||||
@@ -89,6 +90,16 @@ const PluginConfigs: RendererPluginType[] = [
|
||||
mimeTypes: ["application/pdf"],
|
||||
simpleComponent: SimplePdfRenderer,
|
||||
},
|
||||
{
|
||||
name: "media",
|
||||
rendererType: "simple",
|
||||
heightType: "pixels",
|
||||
dataType: "blob",
|
||||
collapseType: "hide",
|
||||
globalCss: null,
|
||||
mimeTypes: ["video/*", "audio/*"],
|
||||
simpleComponent: SimpleMediaRenderer,
|
||||
},
|
||||
];
|
||||
|
||||
class PluginModelClass {
|
||||
|
||||
@@ -74,7 +74,6 @@ var BuildTime = "0"
|
||||
|
||||
var GlobalLock = &sync.Mutex{}
|
||||
var WSStateMap = make(map[string]*scws.WSState) // clientid -> WsState
|
||||
var GlobalAuthKey string
|
||||
var shutdownOnce sync.Once
|
||||
var ContentTypeHeaderValidRe = regexp.MustCompile(`^\w+/[\w.+-]+$`)
|
||||
|
||||
@@ -140,7 +139,7 @@ func HandleWs(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
state := getWSState(clientId)
|
||||
if state == nil {
|
||||
state = scws.MakeWSState(clientId, GlobalAuthKey)
|
||||
state = scws.MakeWSState(clientId, scbase.WaveAuthKey)
|
||||
state.ReplaceShell(shell)
|
||||
setWSState(state)
|
||||
} else {
|
||||
@@ -687,7 +686,7 @@ func AuthKeyMiddleWare(next http.Handler) http.Handler {
|
||||
w.Write([]byte("no x-authkey header"))
|
||||
return
|
||||
}
|
||||
if reqAuthKey != GlobalAuthKey {
|
||||
if reqAuthKey != scbase.WaveAuthKey {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte("x-authkey header is invalid"))
|
||||
return
|
||||
@@ -707,14 +706,14 @@ func AuthKeyWrapAllowHmac(fn WebFnType) WebFnType {
|
||||
w.Write([]byte("no x-authkey header"))
|
||||
return
|
||||
}
|
||||
hmacOk, err := promptenc.ValidateUrlHmac([]byte(GlobalAuthKey), r.URL.Path, qvals)
|
||||
hmacOk, err := promptenc.ValidateUrlHmac([]byte(scbase.WaveAuthKey), r.URL.Path, qvals)
|
||||
if err != nil || !hmacOk {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(fmt.Sprintf("error validating hmac")))
|
||||
return
|
||||
}
|
||||
// fallthrough (hmac is valid)
|
||||
} else if reqAuthKey != GlobalAuthKey {
|
||||
} else if reqAuthKey != scbase.WaveAuthKey {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte("x-authkey header is invalid"))
|
||||
return
|
||||
@@ -733,7 +732,7 @@ func AuthKeyWrap(fn WebFnType) WebFnType {
|
||||
w.Write([]byte("no x-authkey header"))
|
||||
return
|
||||
}
|
||||
if reqAuthKey != GlobalAuthKey {
|
||||
if reqAuthKey != scbase.WaveAuthKey {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte("x-authkey header is invalid"))
|
||||
return
|
||||
@@ -890,12 +889,11 @@ func main() {
|
||||
}
|
||||
return
|
||||
}
|
||||
authKey, err := scbase.ReadWaveAuthKey()
|
||||
err = scbase.InitializeWaveAuthKey()
|
||||
if err != nil {
|
||||
log.Printf("[error] %v\n", err)
|
||||
return
|
||||
}
|
||||
GlobalAuthKey = authKey
|
||||
err = sstore.TryMigrateUp()
|
||||
if err != nil {
|
||||
log.Printf("[error] migrate up: %v\n", err)
|
||||
|
||||
@@ -37,6 +37,7 @@ import (
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/comp"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/dbutil"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/pcloud"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/promptenc"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/releasechecker"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/remote"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/remote/openai"
|
||||
@@ -280,6 +281,7 @@ func init() {
|
||||
registerCmdFn("mdview", MarkdownViewCommand)
|
||||
registerCmdFn("markdownview", MarkdownViewCommand)
|
||||
registerCmdFn("pdfview", PdfViewCommand)
|
||||
registerCmdFn("mediaview", MediaViewCommand)
|
||||
|
||||
registerCmdFn("csvview", CSVViewCommand)
|
||||
}
|
||||
@@ -4861,11 +4863,12 @@ func ImageViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sc
|
||||
if pk.Args[0] == "" {
|
||||
return nil, fmt.Errorf("%s argument cannot be empty", GetCmdStr(pk))
|
||||
}
|
||||
filePath := pk.Args[0]
|
||||
ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_RemoteConnected)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputStr := fmt.Sprintf("%s %q", GetCmdStr(pk), pk.Args[0])
|
||||
outputStr := fmt.Sprintf("%s %q", GetCmdStr(pk), filePath)
|
||||
cmd, err := makeStaticCmd(ctx, GetCmdStr(pk), ids, pk.GetRawStr(), []byte(outputStr))
|
||||
if err != nil {
|
||||
// TODO tricky error since the command was a success, but we can't show the output
|
||||
@@ -4874,7 +4877,7 @@ func ImageViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sc
|
||||
// set the line state
|
||||
lineState := make(map[string]any)
|
||||
lineState[sstore.LineState_Source] = "file"
|
||||
lineState[sstore.LineState_File] = pk.Args[0]
|
||||
lineState[sstore.LineState_File] = filePath
|
||||
update, err := addLineForCmd(ctx, "/"+GetCmdStr(pk), false, ids, cmd, "image", lineState)
|
||||
if err != nil {
|
||||
// TODO tricky error since the command was a success, but we can't show the output
|
||||
@@ -4915,6 +4918,58 @@ func PdfViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbu
|
||||
return update, nil
|
||||
}
|
||||
|
||||
func MakeReadFileUrl(screenId string, lineId string, filePath string) (string, error) {
|
||||
qvals := make(url.Values)
|
||||
qvals.Set("screenid", screenId)
|
||||
qvals.Set("lineid", lineId)
|
||||
qvals.Set("path", filePath)
|
||||
qvals.Set("nonce", uuid.New().String())
|
||||
hmacStr, err := promptenc.ComputeUrlHmac([]byte(scbase.WaveAuthKey), "/api/read-file", qvals)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error computing hmac-url: %v", err)
|
||||
}
|
||||
qvals.Set("hmac", hmacStr)
|
||||
return "/api/read-file?" + qvals.Encode(), nil
|
||||
}
|
||||
|
||||
func MediaViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
|
||||
if len(pk.Args) == 0 {
|
||||
return nil, fmt.Errorf("%s requires an argument (file name)", GetCmdStr(pk))
|
||||
}
|
||||
// TODO more error checking on filename format?
|
||||
if pk.Args[0] == "" {
|
||||
return nil, fmt.Errorf("%s argument cannot be empty", GetCmdStr(pk))
|
||||
}
|
||||
fileName := pk.Args[0]
|
||||
ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_RemoteConnected)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputStr := fmt.Sprintf("%s %q", GetCmdStr(pk), fileName)
|
||||
cmd, err := makeStaticCmd(ctx, GetCmdStr(pk), ids, pk.GetRawStr(), []byte(outputStr))
|
||||
if err != nil {
|
||||
// TODO tricky error since the command was a success, but we can't show the output
|
||||
return nil, err
|
||||
}
|
||||
// compute hmac read-file URL
|
||||
readFileUrl, err := MakeReadFileUrl(ids.ScreenId, cmd.LineId, fileName)
|
||||
if err != nil {
|
||||
// TODO tricky error since the command was a success, but we can't show the output
|
||||
return nil, fmt.Errorf("error making read-file url: %v", err)
|
||||
}
|
||||
// set the line state
|
||||
lineState := make(map[string]any)
|
||||
lineState[sstore.LineState_FileUrl] = readFileUrl
|
||||
lineState[sstore.LineState_File] = fileName
|
||||
update, err := addLineForCmd(ctx, "/"+GetCmdStr(pk), false, ids, cmd, "media", lineState)
|
||||
if err != nil {
|
||||
// TODO tricky error since the command was a success, but we can't show the output
|
||||
return nil, err
|
||||
}
|
||||
update.AddUpdate(sstore.InteractiveUpdate(pk.Interactive))
|
||||
return update, nil
|
||||
}
|
||||
|
||||
func MarkdownViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
|
||||
if len(pk.Args) == 0 {
|
||||
return nil, fmt.Errorf("%s requires an argument (file name)", GetCmdStr(pk))
|
||||
|
||||
@@ -36,6 +36,7 @@ var BareMetaCmds = []BareMetaCmdDecl{
|
||||
{"mdview", "markdownview"},
|
||||
{"csvview", "csvview"},
|
||||
{"pdfview", "pdfview"},
|
||||
{"mediaview", "mediaview"},
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func ComputeUrlHmac(key []byte, baseUrl string, qvals url.Values) (string, error) {
|
||||
if qvals.Has("nonce") {
|
||||
if !qvals.Has("nonce") {
|
||||
return "", fmt.Errorf("nonce is required for hmac")
|
||||
}
|
||||
if qvals.Has("hmac") {
|
||||
@@ -37,7 +37,7 @@ func ValidateUrlHmac(key []byte, baseUrl string, qvalsOrig url.Values) (bool, er
|
||||
qvals := copyUrlValues(qvalsOrig)
|
||||
hmacStr := qvals.Get("hmac")
|
||||
if hmacStr == "" {
|
||||
return false, fmt.Errorf("no hmac key found"))
|
||||
return false, fmt.Errorf("no hmac key found")
|
||||
}
|
||||
qvals.Del("hmac")
|
||||
encStr := baseUrl + "?" + qvals.Encode()
|
||||
|
||||
@@ -38,6 +38,9 @@ const WaveAppPathVarName = "WAVETERM_APP_PATH"
|
||||
const WaveAuthKeyFileName = "waveterm.authkey"
|
||||
const MShellVersion = "v0.5.0"
|
||||
|
||||
// initialized by InitialzeWaveAuthKey (called by main-server)
|
||||
var WaveAuthKey string
|
||||
|
||||
var SessionDirCache = make(map[string]string)
|
||||
var ScreenDirCache = make(map[string]string)
|
||||
var BaseLock = &sync.Mutex{}
|
||||
@@ -108,25 +111,28 @@ func MShellBinaryReader(version string, goos string, goarch string) (io.ReadClos
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
func createWaveAuthKeyFile(fileName string) (string, error) {
|
||||
// also sets WaveAuthKey
|
||||
func createWaveAuthKeyFile(fileName string) error {
|
||||
fd, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
defer fd.Close()
|
||||
keyStr := GenWaveUUID()
|
||||
_, err = fd.Write([]byte(keyStr))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
return keyStr, nil
|
||||
WaveAuthKey = keyStr
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadWaveAuthKey() (string, error) {
|
||||
// sets WaveAuthKey
|
||||
func InitializeWaveAuthKey() error {
|
||||
homeDir := GetWaveHomeDir()
|
||||
err := ensureDir(homeDir)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot find/create WAVETERM_HOME directory %q", homeDir)
|
||||
return fmt.Errorf("cannot find/create WAVETERM_HOME directory %q", homeDir)
|
||||
}
|
||||
fileName := path.Join(homeDir, WaveAuthKeyFileName)
|
||||
fd, err := os.Open(fileName)
|
||||
@@ -134,19 +140,20 @@ func ReadWaveAuthKey() (string, error) {
|
||||
return createWaveAuthKeyFile(fileName)
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error opening wave authkey:%s: %v", fileName, err)
|
||||
return fmt.Errorf("error opening wave authkey:%s: %v", fileName, err)
|
||||
}
|
||||
defer fd.Close()
|
||||
buf, err := io.ReadAll(fd)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error reading wave authkey:%s: %v", fileName, err)
|
||||
return fmt.Errorf("error reading wave authkey:%s: %v", fileName, err)
|
||||
}
|
||||
keyStr := string(buf)
|
||||
_, err = uuid.Parse(keyStr)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid authkey:%s format: %v", fileName, err)
|
||||
return fmt.Errorf("invalid authkey:%s format: %v", fileName, err)
|
||||
}
|
||||
return keyStr, nil
|
||||
WaveAuthKey = keyStr
|
||||
return nil
|
||||
}
|
||||
|
||||
func AcquireWaveLock() (*os.File, error) {
|
||||
|
||||
@@ -63,6 +63,7 @@ const (
|
||||
const (
|
||||
LineState_Source = "prompt:source"
|
||||
LineState_File = "prompt:file"
|
||||
LineState_FileUrl = "wave:fileurl"
|
||||
LineState_Min = "wave:min"
|
||||
LineState_Template = "template"
|
||||
LineState_Mode = "mode"
|
||||
|
||||
Reference in New Issue
Block a user