api: Cleanup websocket protocol
This commit cleans up the websocket protocol to be in line with the documentation.
This commit is contained in:
parent
16fa721048
commit
1c68893a02
31 changed files with 3549 additions and 120 deletions
|
|
@ -84,14 +84,6 @@ func (admin *Admin) HandleRoute(ctx context.Context, route string) (handler http
|
|||
|
||||
router := httprouter.New()
|
||||
|
||||
{
|
||||
handler = &httpx.WebSocket{
|
||||
Context: ctx,
|
||||
Fallback: router,
|
||||
Handler: admin.Dependencies.Sockets.Serve,
|
||||
}
|
||||
}
|
||||
|
||||
// add a handler for the index page
|
||||
{
|
||||
index := admin.index(ctx)
|
||||
|
|
@ -161,7 +153,7 @@ func (admin *Admin) HandleRoute(ctx context.Context, route string) (handler http
|
|||
// add a router for the login page
|
||||
router.Handler(http.MethodPost, route+"login", admin.loginHandler(ctx))
|
||||
|
||||
return
|
||||
return router, nil
|
||||
}
|
||||
|
||||
func (admin *Admin) loginHandler(ctx context.Context) http.Handler {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/scopes"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/provision"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
||||
|
|
@ -15,7 +16,7 @@ import (
|
|||
func (sockets *Sockets) Actions() ActionMap {
|
||||
return map[string]Action{
|
||||
// generic actions
|
||||
"backup": sockets.Generic(0, func(ctx context.Context, sockets *Sockets, in io.Reader, out io.Writer, params ...string) error {
|
||||
"backup": sockets.Generic(scopes.ScopeUserAdmin, "", 0, func(ctx context.Context, sockets *Sockets, in io.Reader, out io.Writer, params ...string) error {
|
||||
return sockets.Dependencies.Exporter.MakeExport(
|
||||
ctx,
|
||||
out,
|
||||
|
|
@ -27,7 +28,7 @@ func (sockets *Sockets) Actions() ActionMap {
|
|||
},
|
||||
)
|
||||
}),
|
||||
"provision": sockets.Generic(1, func(ctx context.Context, sockets *Sockets, in io.Reader, out io.Writer, params ...string) error {
|
||||
"provision": sockets.Generic(scopes.ScopeUserAdmin, "", 1, func(ctx context.Context, sockets *Sockets, in io.Reader, out io.Writer, params ...string) error {
|
||||
// read the flags of the instance to be provisioned
|
||||
var flags provision.Flags
|
||||
if err := json.Unmarshal([]byte(params[0]), &flags); err != nil {
|
||||
|
|
@ -52,7 +53,7 @@ func (sockets *Sockets) Actions() ActionMap {
|
|||
|
||||
// instance-specific actions!
|
||||
|
||||
"snapshot": sockets.Instance(0, func(ctx context.Context, socket *Sockets, instance *wisski.WissKI, in io.Reader, out io.Writer, params ...string) error {
|
||||
"snapshot": sockets.Instance(scopes.ScopeUserAdmin, "", 0, func(ctx context.Context, socket *Sockets, instance *wisski.WissKI, in io.Reader, out io.Writer, params ...string) error {
|
||||
return socket.Dependencies.Exporter.MakeExport(
|
||||
ctx,
|
||||
out,
|
||||
|
|
@ -64,7 +65,7 @@ func (sockets *Sockets) Actions() ActionMap {
|
|||
},
|
||||
)
|
||||
}),
|
||||
"rebuild": sockets.Instance(1, func(ctx context.Context, _ *Sockets, instance *wisski.WissKI, in io.Reader, out io.Writer, params ...string) error {
|
||||
"rebuild": sockets.Instance(scopes.ScopeUserAdmin, "", 1, func(ctx context.Context, _ *Sockets, instance *wisski.WissKI, in io.Reader, out io.Writer, params ...string) error {
|
||||
// read the flags of the instance to be provisioned
|
||||
var system models.System
|
||||
if err := json.Unmarshal([]byte(params[0]), &system); err != nil {
|
||||
|
|
@ -72,20 +73,23 @@ func (sockets *Sockets) Actions() ActionMap {
|
|||
}
|
||||
return instance.SystemManager().Apply(ctx, out, system, true)
|
||||
}),
|
||||
"update": sockets.Instance(0, func(ctx context.Context, _ *Sockets, instance *wisski.WissKI, in io.Reader, out io.Writer, params ...string) error {
|
||||
"update": sockets.Instance(scopes.ScopeUserAdmin, "", 0, func(ctx context.Context, _ *Sockets, instance *wisski.WissKI, in io.Reader, out io.Writer, params ...string) error {
|
||||
return instance.Drush().Update(ctx, out)
|
||||
}),
|
||||
"cron": sockets.Instance(0, func(ctx context.Context, _ *Sockets, instance *wisski.WissKI, in io.Reader, str io.Writer, params ...string) error {
|
||||
"cron": sockets.Instance(scopes.ScopeUserAdmin, "", 0, func(ctx context.Context, _ *Sockets, instance *wisski.WissKI, in io.Reader, str io.Writer, params ...string) error {
|
||||
return instance.Drush().Cron(ctx, str)
|
||||
}),
|
||||
"start": sockets.Instance(0, func(ctx context.Context, _ *Sockets, instance *wisski.WissKI, in io.Reader, out io.Writer, params ...string) error {
|
||||
"start": sockets.Instance(scopes.ScopeUserAdmin, "", 0, func(ctx context.Context, _ *Sockets, instance *wisski.WissKI, in io.Reader, out io.Writer, params ...string) error {
|
||||
return instance.Barrel().Stack().Up(ctx, out)
|
||||
}),
|
||||
"stop": sockets.Instance(0, func(ctx context.Context, _ *Sockets, instance *wisski.WissKI, in io.Reader, out io.Writer, params ...string) error {
|
||||
"stop": sockets.Instance(scopes.ScopeUserAdmin, "", 0, func(ctx context.Context, _ *Sockets, instance *wisski.WissKI, in io.Reader, out io.Writer, params ...string) error {
|
||||
return instance.Barrel().Stack().Down(ctx, out)
|
||||
}),
|
||||
"purge": sockets.Instance(0, func(ctx context.Context, sockets *Sockets, instance *wisski.WissKI, in io.Reader, out io.Writer, params ...string) error {
|
||||
"purge": sockets.Instance(scopes.ScopeUserAdmin, "", 0, func(ctx context.Context, sockets *Sockets, instance *wisski.WissKI, in io.Reader, out io.Writer, params ...string) error {
|
||||
return sockets.Dependencies.Purger.Purge(ctx, out, instance.Slug)
|
||||
}),
|
||||
"never": sockets.Generic(scopes.ScopeNever, "", 0, func(ctx context.Context, sockets *Sockets, in io.Reader, out io.Writer, params ...string) error {
|
||||
panic("never called")
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,11 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/scopes"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/tkw1536/pkglib/httpx"
|
||||
"github.com/tkw1536/pkglib/status"
|
||||
)
|
||||
|
||||
// ActionMap handles a set of WebSocket actions
|
||||
|
|
@ -27,7 +29,7 @@ func (err errPanic) Error() string {
|
|||
return fmt.Sprintf("fatal error: %v", err.value)
|
||||
}
|
||||
|
||||
// Handle handles a new incoming websocket connection.
|
||||
// Handle handles a new incoming websocket connection using the given authentication.
|
||||
//
|
||||
// There are two kinds of messages:
|
||||
//
|
||||
|
|
@ -40,7 +42,7 @@ func (err errPanic) Error() string {
|
|||
// Finally it will send a ResultMessage once handling is complete.
|
||||
//
|
||||
// A corresponding client implementation of this can be found in ..../remote/proto.ts
|
||||
func (am ActionMap) Handle(conn httpx.WebSocketConnection) (name string, err error) {
|
||||
func (am ActionMap) Handle(auth *auth.Auth, conn httpx.WebSocketConnection) (name string, err error) {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// once we have finished executing send a binary message (indicating success) to the client.
|
||||
|
|
@ -123,12 +125,17 @@ func (am ActionMap) Handle(conn httpx.WebSocketConnection) (name string, err err
|
|||
|
||||
// check that the given action exists!
|
||||
// and has the right number of parameters!
|
||||
action, ok := am[call.Name]
|
||||
action, ok := am[call.Call]
|
||||
if !ok || action.Handle == nil {
|
||||
return call.Name, errUnknownAction
|
||||
return call.Call, errUnknownAction
|
||||
}
|
||||
if action.NumParams != len(call.Params) {
|
||||
return call.Name, errIncorrectParams
|
||||
return call.Call, errIncorrectParams
|
||||
}
|
||||
|
||||
// check that we have the given permission
|
||||
if err := auth.CheckScope(action.ScopeParam, action.scope(), conn.Request()); err != nil {
|
||||
return call.Call, err
|
||||
}
|
||||
|
||||
// create a context to be canceled once done
|
||||
|
|
@ -170,17 +177,22 @@ func (am ActionMap) Handle(conn httpx.WebSocketConnection) (name string, err err
|
|||
}
|
||||
}()
|
||||
|
||||
// create a linebuffer to write the output line by line
|
||||
output := &status.LineBuffer{
|
||||
Line: func(line string) {
|
||||
<-conn.WriteText(line)
|
||||
},
|
||||
FlushLineOnClose: true,
|
||||
}
|
||||
defer output.Close()
|
||||
// write the output to the client as it comes in!
|
||||
// NOTE(twiesing): We may eventually need buffering here ...
|
||||
output := WriteFunc(func(b []byte) (int, error) {
|
||||
<-conn.WriteText(string(b))
|
||||
return len(b), nil
|
||||
})
|
||||
|
||||
// handle the actual
|
||||
return call.Name, action.Handle(ctx, inputR, output, call.Params...)
|
||||
return call.Call, action.Handle(ctx, inputR, output, call.Params...)
|
||||
}
|
||||
|
||||
// WriteFunc implements io.Writer using a function.
|
||||
type WriteFunc func([]byte) (int, error)
|
||||
|
||||
func (wf WriteFunc) Write(b []byte) (int, error) {
|
||||
return wf(b)
|
||||
}
|
||||
|
||||
// Action is something that can be handled via a WebSocket connection.
|
||||
|
|
@ -188,6 +200,11 @@ type Action struct {
|
|||
// NumPara
|
||||
NumParams int
|
||||
|
||||
// Scope and ScopeParam indicate the scope required by the caller.
|
||||
// TODO(twiesing): Once we actually include scopes, make them dynamic
|
||||
Scope component.Scope
|
||||
ScopeParam string
|
||||
|
||||
// Handle handles this action.
|
||||
//
|
||||
// ctx is closed once the underlying connection is closed.
|
||||
|
|
@ -196,9 +213,18 @@ type Action struct {
|
|||
Handle func(ctx context.Context, in io.Reader, out io.Writer, params ...string) error
|
||||
}
|
||||
|
||||
// scope returns the actual scope required by this action.
|
||||
// If the caller did not provide an actual scope, uses ScopeNever
|
||||
func (action Action) scope() component.Scope {
|
||||
if action.Scope == "" {
|
||||
return scopes.ScopeNever
|
||||
}
|
||||
return action.Scope
|
||||
}
|
||||
|
||||
// CallMessage is sent by the client to the server to invoke a remote procedure
|
||||
type CallMessage struct {
|
||||
Name string `json:"name"`
|
||||
Call string `json:"call"`
|
||||
Params []string `json:"params,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@ package socket
|
|||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/scopes"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances/purger"
|
||||
|
|
@ -25,22 +28,44 @@ type Sockets struct {
|
|||
Instances *instances.Instances
|
||||
Exporter *exporter.Exporter
|
||||
Purger *purger.Purger
|
||||
Auth *auth.Auth
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
_ component.Routeable = (*Sockets)(nil)
|
||||
)
|
||||
|
||||
func (socket *Sockets) Routes() component.Routes {
|
||||
return component.Routes{
|
||||
Prefix: "/api/v1/ws",
|
||||
Exact: true,
|
||||
Decorator: socket.Dependencies.Auth.Require(true, scopes.ScopeUserValid, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (sockets *Sockets) HandleRoute(ctx context.Context, path string) (http.Handler, error) {
|
||||
return &httpx.WebSocket{
|
||||
Context: ctx,
|
||||
Handler: sockets.Serve,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Serve handles a connection to the websocket api
|
||||
func (socket *Sockets) Serve(conn httpx.WebSocketConnection) {
|
||||
// handle the websocket connection!
|
||||
name, err := socket.actions.Get(socket.Actions).Handle(conn)
|
||||
name, err := socket.actions.Get(socket.Actions).Handle(socket.Dependencies.Auth, conn)
|
||||
if err != nil {
|
||||
zerolog.Ctx(conn.Context()).Err(err).Str("name", name).Msg("Error handling websocket")
|
||||
}
|
||||
}
|
||||
|
||||
// Generic returns a new action that calls handler with the provided number of parameters
|
||||
func (sockets *Sockets) Generic(numParams int, handler func(ctx context.Context, socket *Sockets, in io.Reader, out io.Writer, params ...string) error) Action {
|
||||
func (sockets *Sockets) Generic(scope component.Scope, scopeParam string, numParams int, handler func(ctx context.Context, socket *Sockets, in io.Reader, out io.Writer, params ...string) error) Action {
|
||||
return Action{
|
||||
NumParams: numParams,
|
||||
Scope: scope,
|
||||
ScopeParam: scopeParam,
|
||||
NumParams: numParams,
|
||||
Handle: func(ctx context.Context, in io.Reader, out io.Writer, params ...string) error {
|
||||
return handler(ctx, sockets, in, out, params...)
|
||||
},
|
||||
|
|
@ -48,8 +73,11 @@ func (sockets *Sockets) Generic(numParams int, handler func(ctx context.Context,
|
|||
}
|
||||
|
||||
// Insstance returns a new action that calls handler with a specific WissKI instance
|
||||
func (sockets *Sockets) Instance(numParams int, handler func(ctx context.Context, sockets *Sockets, instance *wisski.WissKI, in io.Reader, out io.Writer, params ...string) error) Action {
|
||||
func (sockets *Sockets) Instance(scope component.Scope, scopeParam string, numParams int, handler func(ctx context.Context, sockets *Sockets, instance *wisski.WissKI, in io.Reader, out io.Writer, params ...string) error) Action {
|
||||
return Action{
|
||||
Scope: scope,
|
||||
ScopeParam: scopeParam,
|
||||
|
||||
NumParams: numParams + 1,
|
||||
Handle: func(ctx context.Context, in io.Reader, out io.Writer, params ...string) error {
|
||||
instance, err := sockets.Dependencies.Instances.WissKI(ctx, params[0])
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -18,24 +18,24 @@ var AssetsDefault = Assets{
|
|||
|
||||
// AssetsUser contains assets for the 'User' entrypoint.
|
||||
var AssetsUser = Assets{
|
||||
Scripts: `<script type="module" src="/⛰/Default.38d394c2.js"></script><script src="/⛰/Default.38d394c2.js" nomodule="" defer></script><script type="module" src="/⛰/User.47a3b7e3.js"></script><script src="/⛰/User.924f7900.js" nomodule="" defer></script>`,
|
||||
Scripts: `<script type="module" src="/⛰/Default.38d394c2.js"></script><script src="/⛰/Default.38d394c2.js" nomodule="" defer></script><script type="module" src="/⛰/User.fce9a3e3.js"></script><script src="/⛰/User.e4c5f849.js" nomodule="" defer></script>`,
|
||||
Styles: `<link rel="stylesheet" href="/⛰/Default.938b4407.css"><link rel="stylesheet" href="/⛰/User.68febbf8.css"><link rel="stylesheet" href="/⛰/User.840de3b4.css">`,
|
||||
}
|
||||
|
||||
// AssetsAdmin contains assets for the 'Admin' entrypoint.
|
||||
var AssetsAdmin = Assets{
|
||||
Scripts: `<script nomodule="" defer src="/⛰/User.924f7900.js"></script><script type="module" src="/⛰/User.47a3b7e3.js"></script><script type="module" src="/⛰/Default.38d394c2.js"></script><script src="/⛰/Default.38d394c2.js" nomodule="" defer></script><script type="module" src="/⛰/Admin.ad1b495b.js"></script><script src="/⛰/Admin.6daf9fdd.js" nomodule="" defer></script>`,
|
||||
Scripts: `<script nomodule="" defer src="/⛰/User.e4c5f849.js"></script><script type="module" src="/⛰/User.fce9a3e3.js"></script><script type="module" src="/⛰/Default.38d394c2.js"></script><script src="/⛰/Default.38d394c2.js" nomodule="" defer></script><script type="module" src="/⛰/Admin.87f202f8.js"></script><script src="/⛰/Admin.1b10eebb.js" nomodule="" defer></script>`,
|
||||
Styles: `<link rel="stylesheet" href="/⛰/Default.938b4407.css"><link rel="stylesheet" href="/⛰/Admin.a1e05c23.css"><link rel="stylesheet" href="/⛰/User.840de3b4.css"><link rel="stylesheet" href="/⛰/User.68febbf8.css"><link rel="stylesheet" href="/⛰/Admin.d79c7b30.css">`,
|
||||
}
|
||||
|
||||
// AssetsAdminProvision contains assets for the 'AdminProvision' entrypoint.
|
||||
var AssetsAdminProvision = Assets{
|
||||
Scripts: `<script nomodule="" defer src="/⛰/User.924f7900.js"></script><script nomodule="" defer src="/⛰/Admin.6daf9fdd.js"></script><script type="module" src="/⛰/User.47a3b7e3.js"></script><script type="module" src="/⛰/Admin.ad1b495b.js"></script><script type="module" src="/⛰/Default.38d394c2.js"></script><script src="/⛰/Default.38d394c2.js" nomodule="" defer></script><script type="module" src="/⛰/AdminProvision.b7679968.js"></script><script src="/⛰/AdminProvision.12a47f22.js" nomodule="" defer></script>`,
|
||||
Scripts: `<script nomodule="" defer src="/⛰/User.e4c5f849.js"></script><script nomodule="" defer src="/⛰/Admin.1b10eebb.js"></script><script type="module" src="/⛰/User.fce9a3e3.js"></script><script type="module" src="/⛰/Admin.87f202f8.js"></script><script type="module" src="/⛰/Default.38d394c2.js"></script><script src="/⛰/Default.38d394c2.js" nomodule="" defer></script><script type="module" src="/⛰/AdminProvision.9cbda41c.js"></script><script src="/⛰/AdminProvision.68dbff79.js" nomodule="" defer></script>`,
|
||||
Styles: `<link rel="stylesheet" href="/⛰/Default.938b4407.css"><link rel="stylesheet" href="/⛰/Admin.a1e05c23.css"><link rel="stylesheet" href="/⛰/User.840de3b4.css"><link rel="stylesheet" href="/⛰/User.68febbf8.css"><link rel="stylesheet" href="/⛰/Admin.d79c7b30.css"><link rel="stylesheet" href="/⛰/AdminProvision.38d394c2.css">`,
|
||||
}
|
||||
|
||||
// AssetsAdminRebuild contains assets for the 'AdminRebuild' entrypoint.
|
||||
var AssetsAdminRebuild = Assets{
|
||||
Scripts: `<script nomodule="" defer src="/⛰/User.924f7900.js"></script><script nomodule="" defer src="/⛰/Admin.6daf9fdd.js"></script><script type="module" src="/⛰/User.47a3b7e3.js"></script><script type="module" src="/⛰/Admin.ad1b495b.js"></script><script type="module" src="/⛰/Default.38d394c2.js"></script><script src="/⛰/Default.38d394c2.js" nomodule="" defer></script><script type="module" src="/⛰/AdminRebuild.330247d9.js"></script><script src="/⛰/AdminRebuild.527a9616.js" nomodule="" defer></script>`,
|
||||
Scripts: `<script nomodule="" defer src="/⛰/User.e4c5f849.js"></script><script nomodule="" defer src="/⛰/Admin.1b10eebb.js"></script><script type="module" src="/⛰/User.fce9a3e3.js"></script><script type="module" src="/⛰/Admin.87f202f8.js"></script><script type="module" src="/⛰/Default.38d394c2.js"></script><script src="/⛰/Default.38d394c2.js" nomodule="" defer></script><script type="module" src="/⛰/AdminRebuild.0149f285.js"></script><script src="/⛰/AdminRebuild.6953ed8a.js" nomodule="" defer></script>`,
|
||||
Styles: `<link rel="stylesheet" href="/⛰/Default.938b4407.css"><link rel="stylesheet" href="/⛰/Admin.a1e05c23.css"><link rel="stylesheet" href="/⛰/User.840de3b4.css"><link rel="stylesheet" href="/⛰/User.68febbf8.css"><link rel="stylesheet" href="/⛰/Admin.d79c7b30.css"><link rel="stylesheet" href="/⛰/AdminRebuild.38d394c2.css">`,
|
||||
}
|
||||
|
|
|
|||
1
internal/dis/component/server/assets/dist/Admin.1b10eebb.js
vendored
Normal file
1
internal/dis/component/server/assets/dist/Admin.1b10eebb.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
internal/dis/component/server/assets/dist/Admin.87f202f8.js
vendored
Normal file
1
internal/dis/component/server/assets/dist/Admin.87f202f8.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
!function(){var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},n={},o={},t=e.parcelRequireafa4;null==t&&((t=function(e){if(e in n)return n[e].exports;if(e in o){var t=o[e];delete o[e];var r={id:e,exports:{}};return n[e]=r,t.call(r.exports,r,r.exports),r.exports}var l=new Error("Cannot find module '"+e+"'");throw l.code="MODULE_NOT_FOUND",l}).register=function(e,n){o[e]=n},e.parcelRequireafa4=t),t("dK5Bi");var r,l=t("8vh0V");async function i(e){return new Promise(((n,o)=>{(0,l.createModal)("provision",[JSON.stringify(e)],{bufferSize:0,onClose:(t,r)=>{t?n(e.Slug):o(new Error(null!=r?r:"unspecified error"))}})}))}const d=document.getElementById("provision"),a=document.getElementById("slug"),u=document.getElementById("php"),c=document.getElementById("opcacheDevelopment");d.addEventListener("submit",(e=>{e.preventDefault(),i({Slug:a.value,System:{PHP:u.value,OpCacheDevelopment:c.checked}}).then((e=>{location.href="/admin/instance/"+e})).catch((e=>{console.error(e),location.reload()}))})),null===(r=d.querySelector("fieldset"))||void 0===r||r.removeAttribute("disabled")}();
|
||||
1
internal/dis/component/server/assets/dist/AdminProvision.68dbff79.js
vendored
Normal file
1
internal/dis/component/server/assets/dist/AdminProvision.68dbff79.js
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
!function(){var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},n={},t={},o=e.parcelRequireafa4;null==o&&((o=function(e){if(e in n)return n[e].exports;if(e in t){var o=t[e];delete t[e];var r={id:e,exports:{}};return n[e]=r,o.call(r.exports,r,r.exports),r.exports}var i=new Error("Cannot find module '"+e+"'");throw i.code="MODULE_NOT_FOUND",i}).register=function(e,n){t[e]=n},e.parcelRequireafa4=o),o("dK5Bi");var r,i=o("8vh0V");async function l(e){return await new Promise(((n,t)=>{(0,i.createModal)("provision",[JSON.stringify(e)],{bufferSize:0,onClose:(o,r)=>{o?n(e.Slug):t(new Error(null!=r?r:"unspecified error"))}})}))}const a=document.getElementById("provision"),d=document.getElementById("slug"),u=document.getElementById("php"),c=document.getElementById("opcacheDevelopment");a.addEventListener("submit",(e=>{e.preventDefault(),l({Slug:d.value,System:{PHP:u.value,OpCacheDevelopment:c.checked}}).then((e=>{location.href="/admin/instance/"+e})).catch((e=>{console.error(e),location.reload()}))})),null===(r=a.querySelector("fieldset"))||void 0===r||r.removeAttribute("disabled")}();
|
||||
1
internal/dis/component/server/assets/dist/AdminProvision.9cbda41c.js
vendored
Normal file
1
internal/dis/component/server/assets/dist/AdminProvision.9cbda41c.js
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},n={},t={},o=e.parcelRequireafa4;null==o&&((o=function(e){if(e in n)return n[e].exports;if(e in t){var o=t[e];delete t[e];var r={id:e,exports:{}};return n[e]=r,o.call(r.exports,r,r.exports),r.exports}var i=new Error("Cannot find module '"+e+"'");throw i.code="MODULE_NOT_FOUND",i}).register=function(e,n){t[e]=n},e.parcelRequireafa4=o),o("8xGhL");var r=o("12vpF");async function i(e){return await new Promise(((n,t)=>{(0,r.createModal)("provision",[JSON.stringify(e)],{bufferSize:0,onClose:(o,r)=>{o?n(e.Slug):t(new Error(r??"unspecified error"))}})}))}const l=document.getElementById("provision"),a=document.getElementById("slug"),d=document.getElementById("php"),u=document.getElementById("opcacheDevelopment");l.addEventListener("submit",(e=>{e.preventDefault(),i({Slug:a.value,System:{PHP:d.value,OpCacheDevelopment:u.checked}}).then((e=>{location.href="/admin/instance/"+e})).catch((e=>{console.error(e),location.reload()}))})),l.querySelector("fieldset")?.removeAttribute("disabled");
|
||||
|
|
@ -1 +0,0 @@
|
|||
var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},n={},t={},o=e.parcelRequireafa4;null==o&&((o=function(e){if(e in n)return n[e].exports;if(e in t){var o=t[e];delete t[e];var r={id:e,exports:{}};return n[e]=r,o.call(r.exports,r,r.exports),r.exports}var i=new Error("Cannot find module '"+e+"'");throw i.code="MODULE_NOT_FOUND",i}).register=function(e,n){t[e]=n},e.parcelRequireafa4=o),o("8xGhL");var r=o("12vpF");async function i(e){return new Promise(((n,t)=>{(0,r.createModal)("provision",[JSON.stringify(e)],{bufferSize:0,onClose:(o,r)=>{o?n(e.Slug):t(new Error(r??"unspecified error"))}})}))}const l=document.getElementById("provision"),a=document.getElementById("slug"),d=document.getElementById("php"),u=document.getElementById("opcacheDevelopment");l.addEventListener("submit",(e=>{e.preventDefault(),i({Slug:a.value,System:{PHP:d.value,OpCacheDevelopment:u.checked}}).then((e=>{location.href="/admin/instance/"+e})).catch((e=>{console.error(e),location.reload()}))})),l.querySelector("fieldset")?.removeAttribute("disabled");
|
||||
1
internal/dis/component/server/assets/dist/AdminRebuild.0149f285.js
vendored
Normal file
1
internal/dis/component/server/assets/dist/AdminRebuild.0149f285.js
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},n={},t={},o=e.parcelRequireafa4;null==o&&((o=function(e){if(e in n)return n[e].exports;if(e in t){var o=t[e];delete t[e];var r={id:e,exports:{}};return n[e]=r,o.call(r.exports,r,r.exports),r.exports}var i=new Error("Cannot find module '"+e+"'");throw i.code="MODULE_NOT_FOUND",i}).register=function(e,n){t[e]=n},e.parcelRequireafa4=o),o("8xGhL");var r=o("12vpF");async function i(e,n){return await new Promise(((t,o)=>{(0,r.createModal)("rebuild",[e,JSON.stringify(n)],{bufferSize:0,onClose:(n,r)=>{n?t(e):o(new Error(r??"unspecified error"))}})}))}const l=document.getElementById("slug"),a=document.getElementById("provision"),d=document.getElementById("php"),u=document.getElementById("opcacheDevelopment");a.addEventListener("submit",(e=>{e.preventDefault(),i(l.value,{PHP:d.value,OpCacheDevelopment:u.checked}).then((e=>{location.href="/admin/instance/"+e})).catch((e=>{console.error(e),location.reload()}))})),a.querySelector("fieldset")?.removeAttribute("disabled");
|
||||
|
|
@ -1 +0,0 @@
|
|||
var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},n={},t={},o=e.parcelRequireafa4;null==o&&((o=function(e){if(e in n)return n[e].exports;if(e in t){var o=t[e];delete t[e];var r={id:e,exports:{}};return n[e]=r,o.call(r.exports,r,r.exports),r.exports}var i=new Error("Cannot find module '"+e+"'");throw i.code="MODULE_NOT_FOUND",i}).register=function(e,n){t[e]=n},e.parcelRequireafa4=o),o("8xGhL");var r=o("12vpF");async function i(e,n){return new Promise(((t,o)=>{(0,r.createModal)("rebuild",[e,JSON.stringify(n)],{bufferSize:0,onClose:(n,r)=>{n?t(e):o(new Error(r??"unspecified error"))}})}))}const l=document.getElementById("slug"),d=document.getElementById("provision"),a=document.getElementById("php"),u=document.getElementById("opcacheDevelopment");d.addEventListener("submit",(e=>{e.preventDefault(),i(l.value,{PHP:a.value,OpCacheDevelopment:u.checked}).then((e=>{location.href="/admin/instance/"+e})).catch((e=>{console.error(e),location.reload()}))})),d.querySelector("fieldset")?.removeAttribute("disabled");
|
||||
|
|
@ -1 +0,0 @@
|
|||
!function(){var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},n={},t={},o=e.parcelRequireafa4;null==o&&((o=function(e){if(e in n)return n[e].exports;if(e in t){var o=t[e];delete t[e];var r={id:e,exports:{}};return n[e]=r,o.call(r.exports,r,r.exports),r.exports}var l=new Error("Cannot find module '"+e+"'");throw l.code="MODULE_NOT_FOUND",l}).register=function(e,n){t[e]=n},e.parcelRequireafa4=o),o("dK5Bi");var r,l=o("8vh0V");async function i(e,n){return new Promise(((t,o)=>{(0,l.createModal)("rebuild",[e,JSON.stringify(n)],{bufferSize:0,onClose:(n,r)=>{n?t(e):o(new Error(null!=r?r:"unspecified error"))}})}))}const d=document.getElementById("slug"),a=document.getElementById("provision"),u=document.getElementById("php"),c=document.getElementById("opcacheDevelopment");a.addEventListener("submit",(e=>{e.preventDefault(),i(d.value,{PHP:u.value,OpCacheDevelopment:c.checked}).then((e=>{location.href="/admin/instance/"+e})).catch((e=>{console.error(e),location.reload()}))})),null===(r=a.querySelector("fieldset"))||void 0===r||r.removeAttribute("disabled")}();
|
||||
1
internal/dis/component/server/assets/dist/AdminRebuild.6953ed8a.js
vendored
Normal file
1
internal/dis/component/server/assets/dist/AdminRebuild.6953ed8a.js
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
!function(){var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},n={},t={},o=e.parcelRequireafa4;null==o&&((o=function(e){if(e in n)return n[e].exports;if(e in t){var o=t[e];delete t[e];var r={id:e,exports:{}};return n[e]=r,o.call(r.exports,r,r.exports),r.exports}var i=new Error("Cannot find module '"+e+"'");throw i.code="MODULE_NOT_FOUND",i}).register=function(e,n){t[e]=n},e.parcelRequireafa4=o),o("dK5Bi");var r,i=o("8vh0V");async function l(e,n){return await new Promise(((t,o)=>{(0,i.createModal)("rebuild",[e,JSON.stringify(n)],{bufferSize:0,onClose:(n,r)=>{n?t(e):o(new Error(null!=r?r:"unspecified error"))}})}))}const d=document.getElementById("slug"),a=document.getElementById("provision"),u=document.getElementById("php"),c=document.getElementById("opcacheDevelopment");a.addEventListener("submit",(e=>{e.preventDefault(),l(d.value,{PHP:u.value,OpCacheDevelopment:c.checked}).then((e=>{location.href="/admin/instance/"+e})).catch((e=>{console.error(e),location.reload()}))})),null===(r=a.querySelector("fieldset"))||void 0===r||r.removeAttribute("disabled")}();
|
||||
|
|
@ -1 +0,0 @@
|
|||
var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},t={},n={},r=e.parcelRequireafa4;null==r&&((r=function(e){if(e in t)return t[e].exports;if(e in n){var r=n[e];delete n[e];var o={id:e,exports:{}};return t[e]=o,r.call(o.exports,o,o.exports),o.exports}var i=new Error("Cannot find module '"+e+"'");throw i.code="MODULE_NOT_FOUND",i}).register=function(e,t){n[e]=t},e.parcelRequireafa4=r),r.register("gkpdw",(function(e,t){r("hZNgY"),r("lFehX")})),r.register("hZNgY",(function(e,t){document.querySelectorAll(".copy").forEach((e=>{e.addEventListener("click",(()=>{navigator.clipboard&&navigator.clipboard.writeText(e.innerText)}))}))})),r.register("lFehX",(function(e,t){document.querySelectorAll("span").forEach((e=>{e.hasAttribute("data-reveal")&&function(e,t){const n=e.getAttribute("data-reveal")??"(no content)";let r=!0;const o=()=>{r=!0,e.innerText="(click to reveal)"};o();const i=()=>{r=!1;const t=document.createElement("code");t.append(n),t.addEventListener("click",(e=>{e.preventDefault(),navigator.clipboard&&navigator.clipboard.writeText(n)})),t.style.userSelect="all",e.innerHTML="",e.append(t)};e.addEventListener("click",(e=>{e.preventDefault(),r&&(i(),setTimeout(o,t))}))}(e,1e4)}))})),r("gkpdw");
|
||||
|
|
@ -1 +0,0 @@
|
|||
!function(){var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},t={},n={},r=e.parcelRequireafa4;null==r&&((r=function(e){if(e in t)return t[e].exports;if(e in n){var r=n[e];delete n[e];var o={id:e,exports:{}};return t[e]=o,r.call(o.exports,o,o.exports),o.exports}var i=new Error("Cannot find module '"+e+"'");throw i.code="MODULE_NOT_FOUND",i}).register=function(e,t){n[e]=t},e.parcelRequireafa4=r),r.register("kEAtK",(function(e,t){r("15EWx"),r("fp21h")})),r.register("15EWx",(function(e,t){document.querySelectorAll(".copy").forEach((e=>{e.addEventListener("click",(()=>{navigator.clipboard&&navigator.clipboard.writeText(e.innerText)}))}))})),r.register("fp21h",(function(e,t){document.querySelectorAll("span").forEach((e=>{e.hasAttribute("data-reveal")&&function(e,t){var n;const r=null!==(n=e.getAttribute("data-reveal"))&&void 0!==n?n:"(no content)";let o=!0;const i=()=>{o=!0,e.innerText="(click to reveal)"};i();const a=()=>{o=!1;const t=document.createElement("code");t.append(r),t.addEventListener("click",(e=>{e.preventDefault(),navigator.clipboard&&navigator.clipboard.writeText(r)})),t.style.userSelect="all",e.innerHTML="",e.append(t)};e.addEventListener("click",(e=>{e.preventDefault(),o&&(a(),setTimeout(i,t))}))}(e,1e4)}))})),r("kEAtK")}();
|
||||
1
internal/dis/component/server/assets/dist/User.e4c5f849.js
vendored
Normal file
1
internal/dis/component/server/assets/dist/User.e4c5f849.js
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
!function(){function e(e,t,n,r){Object.defineProperty(e,t,{get:n,set:r,enumerable:!0,configurable:!0})}var t="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},n={},r={},o=t.parcelRequireafa4;null==o&&((o=function(e){if(e in n)return n[e].exports;if(e in r){var t=r[e];delete r[e];var o={id:e,exports:{}};return n[e]=o,t.call(o.exports,o,o.exports),o.exports}var i=new Error("Cannot find module '"+e+"'");throw i.code="MODULE_NOT_FOUND",i}).register=function(e,t){r[e]=t},t.parcelRequireafa4=o),o.register("kEAtK",(function(e,t){o("15EWx"),o("fp21h")})),o.register("15EWx",(function(e,t){var n=o("17GEA");document.querySelectorAll(".copy").forEach((e=>{e.addEventListener("click",(()=>{navigator.clipboard&&(0,n.discard)(navigator.clipboard.writeText(e.innerText))}))}))})),o.register("17GEA",(function(t,n){e(t.exports,"discard",(function(){return o})),e(t.exports,"runMutexExclusive",(function(){return i}));const r=console.error.bind(console);function o(e){e.then((()=>{})).catch(r)}function i(e,t){o(e.runExclusive(t))}})),o.register("fp21h",(function(e,t){document.querySelectorAll("span").forEach((e=>{e.hasAttribute("data-reveal")&&function(e,t){var n;const r=null!==(n=e.getAttribute("data-reveal"))&&void 0!==n?n:"(no content)";let o=!0;const i=()=>{o=!0,e.innerText="(click to reveal)"};i();const c=()=>{o=!1;const t=document.createElement("code");t.append(r),t.addEventListener("click",(e=>{e.preventDefault(),navigator.clipboard&&navigator.clipboard.writeText(r).then((()=>{})).catch(console.error.bind(console))})),t.style.userSelect="all",e.innerHTML="",e.append(t)};e.addEventListener("click",(e=>{e.preventDefault(),o&&(c(),setTimeout(i,t))}))}(e,1e4)}))})),o("kEAtK")}();
|
||||
1
internal/dis/component/server/assets/dist/User.fce9a3e3.js
vendored
Normal file
1
internal/dis/component/server/assets/dist/User.fce9a3e3.js
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
function e(e,t,n,r){Object.defineProperty(e,t,{get:n,set:r,enumerable:!0,configurable:!0})}var t="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},n={},r={},o=t.parcelRequireafa4;null==o&&((o=function(e){if(e in n)return n[e].exports;if(e in r){var t=r[e];delete r[e];var o={id:e,exports:{}};return n[e]=o,t.call(o.exports,o,o.exports),o.exports}var i=new Error("Cannot find module '"+e+"'");throw i.code="MODULE_NOT_FOUND",i}).register=function(e,t){r[e]=t},t.parcelRequireafa4=o),o.register("gkpdw",(function(e,t){o("hZNgY"),o("lFehX")})),o.register("hZNgY",(function(e,t){var n=o("7E9W8");document.querySelectorAll(".copy").forEach((e=>{e.addEventListener("click",(()=>{navigator.clipboard&&(0,n.discard)(navigator.clipboard.writeText(e.innerText))}))}))})),o.register("7E9W8",(function(t,n){e(t.exports,"discard",(function(){return o})),e(t.exports,"runMutexExclusive",(function(){return i}));const r=console.error.bind(console);function o(e){e.then((()=>{})).catch(r)}function i(e,t){o(e.runExclusive(t))}})),o.register("lFehX",(function(e,t){document.querySelectorAll("span").forEach((e=>{e.hasAttribute("data-reveal")&&function(e,t){const n=e.getAttribute("data-reveal")??"(no content)";let r=!0;const o=()=>{r=!0,e.innerText="(click to reveal)"};o();const i=()=>{r=!1;const t=document.createElement("code");t.append(n),t.addEventListener("click",(e=>{e.preventDefault(),navigator.clipboard&&navigator.clipboard.writeText(n).then((()=>{})).catch(console.error.bind(console))})),t.style.userSelect="all",e.innerHTML="",e.append(t)};e.addEventListener("click",(e=>{e.preventDefault(),r&&(i(),setTimeout(o,t))}))}(e,1e4)}))})),o("gkpdw");
|
||||
|
|
@ -1,36 +1,63 @@
|
|||
import './index.css'
|
||||
import callServerAction, { ResultMessage } from './proto'
|
||||
|
||||
type Println = ((line: string, flush?: boolean) => void) & {
|
||||
type Print = ((text: string, flush?: boolean) => void) & {
|
||||
paintedFrames: number
|
||||
missedFrames: number
|
||||
}
|
||||
|
||||
const NEW_LINE = '\n'
|
||||
const NEW_LINE_LENGTH = NEW_LINE.length
|
||||
|
||||
/**
|
||||
* trimLines trims buffer so that it contains as most count lines
|
||||
*/
|
||||
function trimLines (buffer: string, lines: number): string {
|
||||
if (lines <= 0 || isNaN(lines) || !isFinite(lines)) return buffer
|
||||
|
||||
let count = 0
|
||||
let index = buffer.length
|
||||
|
||||
// while we still have sufficient space
|
||||
while (count < lines) {
|
||||
// get the next start of the line
|
||||
index = buffer.lastIndexOf(NEW_LINE, index - 1)
|
||||
if (index === -1) {
|
||||
return buffer
|
||||
}
|
||||
|
||||
// increase the count
|
||||
count++
|
||||
}
|
||||
|
||||
return buffer.substring(index + NEW_LINE_LENGTH)
|
||||
}
|
||||
|
||||
/**
|
||||
* makeTextBuffer returns a println() function that efficiently writes text into target, and keeps at most size elements in the traceback.
|
||||
* scrollContainer is used to scroll on every painted update.
|
||||
*/
|
||||
function makeTextBuffer (target: HTMLElement, scrollContainer: HTMLElement, size: number): Println {
|
||||
function makeTextBuffer (target: HTMLElement, scrollContainer: HTMLElement, size: number): Print {
|
||||
let lastAnimationFrame: number | null = null // last scheduled animation frame
|
||||
|
||||
const buffer: string[] = [] // the internal buffer of lines
|
||||
// text buffer
|
||||
let buffer: string = ''
|
||||
const paint = (): void => {
|
||||
println.paintedFrames++
|
||||
target.innerText = buffer.join('\n')
|
||||
print.paintedFrames++
|
||||
target.innerText = buffer
|
||||
scrollContainer.scrollTop = scrollContainer.scrollHeight
|
||||
lastAnimationFrame = null
|
||||
}
|
||||
|
||||
const println = (line: string, flush?: boolean): void => {
|
||||
// add the line
|
||||
buffer.push(line)
|
||||
if (size !== 0 && buffer.length > size) {
|
||||
buffer.splice(0, buffer.length - size)
|
||||
}
|
||||
const print = (text: string, flush?: boolean): void => {
|
||||
// add text to the buffer and normalize
|
||||
buffer += text.replace(/^\s*[\r\n]/gm, '\r\n')
|
||||
|
||||
// trim the buffer to the specified number of lines
|
||||
buffer = trimLines(buffer, size)
|
||||
// and update the browser in the next animation frame
|
||||
if (lastAnimationFrame !== null) {
|
||||
println.missedFrames++
|
||||
print.missedFrames++
|
||||
window.cancelAnimationFrame(lastAnimationFrame)
|
||||
}
|
||||
|
||||
|
|
@ -40,10 +67,10 @@ function makeTextBuffer (target: HTMLElement, scrollContainer: HTMLElement, size
|
|||
// schedule an animation frame
|
||||
lastAnimationFrame = window.requestAnimationFrame(paint)
|
||||
}
|
||||
println.paintedFrames = 0
|
||||
println.missedFrames = 0
|
||||
print.paintedFrames = 0
|
||||
print.missedFrames = 0
|
||||
|
||||
return println
|
||||
return print
|
||||
}
|
||||
|
||||
export default function setup (): void {
|
||||
|
|
@ -131,7 +158,7 @@ export function createModal (action: string, params: string[], opts: Partial<Mod
|
|||
|
||||
// create a <pre> to write stuff into
|
||||
const target = document.createElement('pre')
|
||||
const println = makeTextBuffer(target, modal, opts.bufferSize ?? 1000)
|
||||
const print = makeTextBuffer(target, modal, opts.bufferSize ?? 1000)
|
||||
modal.append(target)
|
||||
|
||||
// create a button to eventually close everything
|
||||
|
|
@ -171,9 +198,9 @@ export function createModal (action: string, params: string[], opts: Partial<Mod
|
|||
result = message
|
||||
|
||||
if (result.success) {
|
||||
println('Process completed successfully. ', true)
|
||||
print('Process completed successfully.\n', true)
|
||||
} else {
|
||||
println('Process reported error: ' + result.message, true)
|
||||
print('Process reported error: ' + result.message + '\n', true)
|
||||
}
|
||||
|
||||
window.onbeforeunload = onbeforeunload
|
||||
|
|
@ -181,17 +208,20 @@ export function createModal (action: string, params: string[], opts: Partial<Mod
|
|||
modal.removeChild(cancelButton)
|
||||
modal.append(finishButton)
|
||||
|
||||
const quota = (println.paintedFrames / (println.missedFrames + println.paintedFrames)) * 100
|
||||
console.debug(`Terminal: painted=${println.paintedFrames} missed=${println.missedFrames} (${quota}%)`, true)
|
||||
const quota = (print.paintedFrames / (print.missedFrames + print.paintedFrames)) * 100
|
||||
console.debug(`Terminal: painted=${print.paintedFrames} missed=${print.missedFrames} (${quota}%)`, true)
|
||||
}
|
||||
|
||||
println('Connecting ...', true)
|
||||
print('Connecting ...', true)
|
||||
|
||||
// backendURL is the backend url to connect to
|
||||
const backendURL = location.protocol.replace('http', 'ws') + '//' + location.host + '/api/v1/ws'
|
||||
|
||||
// connect to the socket and send the action
|
||||
callServerAction(
|
||||
location.href.replace('http', 'ws'),
|
||||
backendURL,
|
||||
{
|
||||
name: action,
|
||||
call: action,
|
||||
params
|
||||
},
|
||||
(
|
||||
|
|
@ -202,12 +232,12 @@ export function createModal (action: string, params: string[], opts: Partial<Mod
|
|||
cancelButton.addEventListener('click', (event) => {
|
||||
event.preventDefault()
|
||||
|
||||
println('Cancelling', true)
|
||||
print('^C\n', true)
|
||||
cancel()
|
||||
})
|
||||
println('Connected', true)
|
||||
print(' Connected.\n', true)
|
||||
},
|
||||
println
|
||||
print
|
||||
).then(close)
|
||||
.catch(() => {
|
||||
close({ success: false, message: 'connection closed unexpectedly' })
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Mutex } from 'async-mutex'
|
|||
|
||||
import { runMutexExclusive } from '~/src/lib/discard'
|
||||
|
||||
export interface CallMessage { name: string, params?: string[] | null }
|
||||
export interface CallMessage { call: string, params?: string[] | null }
|
||||
export type ResultMessage = { success: true } | { success: false, message: string }
|
||||
export interface SignalMessage { signal: string }
|
||||
function isResultMessage (value: any): value is ResultMessage {
|
||||
|
|
@ -18,8 +18,8 @@ function isResultMessage (value: any): value is ResultMessage {
|
|||
* Opens a WebSocket connection and calls a server action
|
||||
* @param endpoint Endpoint to call
|
||||
* @param call Function to call
|
||||
* @param onOpen callback for once the connection is opened. The send function can be used to send additional text to the server.
|
||||
* @param onText called when the connection receives some text
|
||||
* @param onOpen callback for once the connection is opened. The send function can be used to send additional text to the server. It should include newlines.
|
||||
* @param onText called when the connection receives some text, including newlines.
|
||||
* @returns a promise that is resolved once the conneciton is closed. Rejected if the connection errors.
|
||||
*/
|
||||
export default async function callServerAction (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue