Add a form to provision a new instance
This commit is contained in:
parent
80906d3791
commit
53f63d4efd
25 changed files with 367 additions and 236 deletions
|
|
@ -70,7 +70,9 @@ var (
|
|||
menuUsers = component.MenuItem{Title: "Users", Path: "/admin/users/"}
|
||||
menuUserCreate = component.MenuItem{Title: "Create User", Path: "/admin/users/create/"}
|
||||
|
||||
menuInstances = component.MenuItem{Title: "Instances", Path: "/admin/instance/"}
|
||||
menuProvision = component.MenuItem{Title: "Provision", Path: "/admin/instances/provision/"}
|
||||
|
||||
menuInstances = component.MenuItem{Title: "Instances", Path: "/admin/instances/"}
|
||||
menuInstance = component.DummyMenuItem()
|
||||
menuGrants = component.DummyMenuItem()
|
||||
menuIngredients = component.DummyMenuItem()
|
||||
|
|
@ -94,10 +96,13 @@ func (admin *Admin) HandleRoute(ctx context.Context, route string) (handler http
|
|||
router.Handler(http.MethodGet, route, index)
|
||||
}
|
||||
|
||||
// add a handler for the instances page
|
||||
// add a handler for the instances (and provision) page
|
||||
{
|
||||
instances := admin.instances(ctx)
|
||||
router.Handler(http.MethodGet, route+"instance/", instances)
|
||||
router.Handler(http.MethodGet, route+"instances/", instances)
|
||||
|
||||
provision := admin.instanceProvision(ctx)
|
||||
router.Handler(http.MethodGet, route+"instances/provision", provision)
|
||||
}
|
||||
|
||||
// add a handler for the user page
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
<div class="pure-u-1-1">
|
||||
<form class="pure-form pure-form-aligned" id="provision">
|
||||
<fieldset disabled="disabled">
|
||||
<div class="pure-control-group">
|
||||
<label for="slug">Slug</label>
|
||||
<input name="slug" id="slug" placeholder="" autocomplete="slug">
|
||||
</div>
|
||||
<input type="submit" value="Provision" class="pure-button">
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
|
|
@ -130,6 +130,9 @@ func (admin *Admin) instances(ctx context.Context) http.Handler {
|
|||
menuAdmin,
|
||||
menuInstances,
|
||||
),
|
||||
templating.Actions(
|
||||
menuProvision,
|
||||
),
|
||||
)
|
||||
|
||||
return tpl.HTMLHandler(func(r *http.Request) (idx indexContext, err error) {
|
||||
|
|
|
|||
42
internal/dis/component/server/admin/instance_provision.go
Normal file
42
internal/dis/component/server/admin/instance_provision.go
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed "html/instance_provision.html"
|
||||
var instanceProvisionHTML []byte
|
||||
var instanceProvisionTemplate = templating.Parse[instanceProvisionContext](
|
||||
"instance_provision.html", instanceProvisionHTML, nil,
|
||||
|
||||
templating.Title("Provision New Instance"),
|
||||
templating.Assets(assets.AssetsAdminProvision),
|
||||
)
|
||||
|
||||
type instanceProvisionContext struct {
|
||||
templating.RuntimeFlags
|
||||
|
||||
// nothing for the moment
|
||||
}
|
||||
|
||||
func (admin *Admin) instanceProvision(ctx context.Context) http.Handler {
|
||||
tpl := instanceProvisionTemplate.Prepare(
|
||||
admin.Dependencies.Templating,
|
||||
|
||||
templating.Crumbs(
|
||||
menuAdmin,
|
||||
menuInstances,
|
||||
menuProvision,
|
||||
),
|
||||
)
|
||||
|
||||
return tpl.HTMLHandler(func(r *http.Request) (ipc instanceProvisionContext, err error) {
|
||||
return ipc, nil
|
||||
})
|
||||
}
|
||||
|
|
@ -2,9 +2,12 @@ package socket
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"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/wisski"
|
||||
)
|
||||
|
||||
|
|
@ -24,6 +27,32 @@ var actions = map[string]SocketAction{
|
|||
)
|
||||
},
|
||||
},
|
||||
"provision": {
|
||||
NumParams: 1,
|
||||
HandleInteractive: func(ctx context.Context, sockets *Sockets, out io.Writer, params ...string) error {
|
||||
|
||||
// read the flags of the instance to be provisioned
|
||||
var flags provision.ProvisionFlags
|
||||
if err := json.Unmarshal([]byte(params[0]), &flags); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
instance, err := sockets.Dependencies.Provision.Provision(
|
||||
out,
|
||||
ctx,
|
||||
flags,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "URL: %s\n", instance.URL().String())
|
||||
fmt.Fprintf(out, "Username: %s\n", instance.DrupalUsername)
|
||||
fmt.Fprintf(out, "Password: %s\n", instance.DrupalPassword)
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// socket specific actions
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"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"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/provision"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/wisski"
|
||||
"github.com/tkw1536/goprogram/status"
|
||||
"github.com/tkw1536/pkglib/httpx"
|
||||
|
|
@ -19,6 +20,7 @@ type Sockets struct {
|
|||
component.Base
|
||||
|
||||
Dependencies struct {
|
||||
Provision *provision.Provision
|
||||
Instances *instances.Instances
|
||||
Exporter *exporter.Exporter
|
||||
Purger *purger.Purger
|
||||
|
|
|
|||
|
|
@ -21,4 +21,4 @@ type Assets struct {
|
|||
Styles template.HTML // <link> tags inserted by the asset
|
||||
}
|
||||
|
||||
//go:generate node build.mjs Default User Admin
|
||||
//go:generate node build.mjs Default User Admin AdminProvision
|
||||
|
|
|
|||
|
|
@ -24,6 +24,12 @@ var AssetsUser = Assets{
|
|||
|
||||
// AssetsAdmin contains assets for the 'Admin' entrypoint.
|
||||
var AssetsAdmin = Assets{
|
||||
Scripts: `<script nomodule="" defer src="/this-is-fine/User.b2f9a57c.js"></script><script type="module" src="/this-is-fine/User.e0367d79.js"></script><script type="module" src="/this-is-fine/Default.38d394c2.js"></script><script src="/this-is-fine/Default.38d394c2.js" nomodule="" defer></script><script type="module" src="/this-is-fine/Admin.6a4184fa.js"></script><script src="/this-is-fine/Admin.6f0f8426.js" nomodule="" defer></script>`,
|
||||
Scripts: `<script nomodule="" defer src="/this-is-fine/User.b2f9a57c.js"></script><script type="module" src="/this-is-fine/User.e0367d79.js"></script><script type="module" src="/this-is-fine/Default.38d394c2.js"></script><script src="/this-is-fine/Default.38d394c2.js" nomodule="" defer></script><script type="module" src="/this-is-fine/Admin.620228db.js"></script><script src="/this-is-fine/Admin.939dcd95.js" nomodule="" defer></script>`,
|
||||
Styles: `<link rel="stylesheet" href="/this-is-fine/Default.938b4407.css"><link rel="stylesheet" href="/this-is-fine/Admin.a1e05c23.css"><link rel="stylesheet" href="/this-is-fine/User.840de3b4.css"><link rel="stylesheet" href="/this-is-fine/User.68febbf8.css"><link rel="stylesheet" href="/this-is-fine/Admin.6d2ae968.css">`,
|
||||
}
|
||||
|
||||
// AssetsAdminProvision contains assets for the 'AdminProvision' entrypoint.
|
||||
var AssetsAdminProvision = Assets{
|
||||
Scripts: `<script nomodule="" defer src="/this-is-fine/User.b2f9a57c.js"></script><script nomodule="" defer src="/this-is-fine/Admin.939dcd95.js"></script><script type="module" src="/this-is-fine/User.e0367d79.js"></script><script type="module" src="/this-is-fine/Admin.620228db.js"></script><script type="module" src="/this-is-fine/Default.38d394c2.js"></script><script src="/this-is-fine/Default.38d394c2.js" nomodule="" defer></script><script type="module" src="/this-is-fine/AdminProvision.3cf9e19e.js"></script><script src="/this-is-fine/AdminProvision.d195fd59.js" nomodule="" defer></script>`,
|
||||
Styles: `<link rel="stylesheet" href="/this-is-fine/Default.938b4407.css"><link rel="stylesheet" href="/this-is-fine/Admin.a1e05c23.css"><link rel="stylesheet" href="/this-is-fine/User.840de3b4.css"><link rel="stylesheet" href="/this-is-fine/User.68febbf8.css"><link rel="stylesheet" href="/this-is-fine/Admin.6d2ae968.css"><link rel="stylesheet" href="/this-is-fine/AdminProvision.38d394c2.css">`,
|
||||
}
|
||||
|
|
|
|||
1
internal/dis/component/server/assets/dist/Admin.620228db.js
vendored
Normal file
1
internal/dis/component/server/assets/dist/Admin.620228db.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
File diff suppressed because one or more lines are too long
1
internal/dis/component/server/assets/dist/Admin.939dcd95.js
vendored
Normal file
1
internal/dis/component/server/assets/dist/Admin.939dcd95.js
vendored
Normal file
File diff suppressed because one or more lines are too long
0
internal/dis/component/server/assets/dist/AdminProvision.38d394c2.css
vendored
Normal file
0
internal/dis/component/server/assets/dist/AdminProvision.38d394c2.css
vendored
Normal file
1
internal/dis/component/server/assets/dist/AdminProvision.3cf9e19e.js
vendored
Normal file
1
internal/dis/component/server/assets/dist/AdminProvision.3cf9e19e.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={},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 i=new Error("Cannot find module '"+e+"'");throw i.code="MODULE_NOT_FOUND",i}).register=function(e,n){o[e]=n},e.parcelRequireafa4=t),t("8xGhL");var r=t("12vpF");const i=document.getElementById("provision"),l=document.getElementById("slug");i.addEventListener("submit",(e=>{e.preventDefault();const n={Slug:l.value};(0,r.createModal)("provision",[JSON.stringify(n)],{bufferSize:0,onClose:e=>{e?location.href="/admin/instance/"+n.Slug:location.reload()}})})),i.querySelector("fieldset")?.removeAttribute("disabled");
|
||||
1
internal/dis/component/server/assets/dist/AdminProvision.d195fd59.js
vendored
Normal file
1
internal/dis/component/server/assets/dist/AdminProvision.d195fd59.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={},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 i={id:e,exports:{}};return n[e]=i,t.call(i.exports,i,i.exports),i.exports}var r=new Error("Cannot find module '"+e+"'");throw r.code="MODULE_NOT_FOUND",r}).register=function(e,n){o[e]=n},e.parcelRequireafa4=t),t("dK5Bi");var i,r=t("8vh0V");const l=document.getElementById("provision"),d=document.getElementById("slug");l.addEventListener("submit",(e=>{e.preventDefault();const n={Slug:d.value};(0,r.createModal)("provision",[JSON.stringify(n)],{bufferSize:0,onClose:e=>{e?location.href="/admin/instance/"+n.Slug:location.reload()}})})),null===(i=l.querySelector("fieldset"))||void 0===i||i.removeAttribute("disabled")}();
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
import "~/src/lib/remote"
|
||||
// setup highlighting
|
||||
import "~/src/lib/highlight"
|
||||
|
||||
// setup remote actions
|
||||
import setup from "~/src/lib/remote"
|
||||
setup();
|
||||
|
||||
// include the user styles!
|
||||
import "../User/index.ts"
|
||||
import "../User/index.css"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
import "../Admin/index.ts"
|
||||
import "../Admin/index.css"
|
||||
|
||||
import { createModal } from "~/src/lib/remote"
|
||||
|
||||
const provision = document.getElementById("provision") as HTMLFormElement;
|
||||
const slug = document.getElementById("slug") as HTMLInputElement;
|
||||
|
||||
// add an event handler to open the modal form!
|
||||
provision.addEventListener('submit', (evt) => {
|
||||
evt.preventDefault();
|
||||
|
||||
// flags used to create the server
|
||||
const flags = { Slug: slug.value };
|
||||
|
||||
// open a modal to provision a new instance
|
||||
createModal("provision", [JSON.stringify(flags)], {
|
||||
bufferSize: 0,
|
||||
onClose: (success: boolean) => {
|
||||
if (success) {
|
||||
location.href = "/admin/instance/" + flags.Slug
|
||||
} else {
|
||||
location.reload();
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
// enable the form!
|
||||
provision.querySelector('fieldset')?.removeAttribute('disabled');
|
||||
|
||||
|
|
@ -46,163 +46,128 @@ function makeTextBuffer(target: HTMLElement, scrollContainer: HTMLElement, size:
|
|||
return println;
|
||||
}
|
||||
|
||||
const remote_action = document.getElementsByClassName('remote-action')
|
||||
Array.from(remote_action).forEach((element) => {
|
||||
const action = element.getAttribute('data-action') as string;
|
||||
const reload = element.getAttribute('data-force-reload');
|
||||
const param = element.getAttribute('data-param') as string | undefined;
|
||||
export default function setup() {
|
||||
const remote_action = document.getElementsByClassName('remote-action')
|
||||
Array.from(remote_action).forEach((element) => {
|
||||
const action = element.getAttribute('data-action') as string;
|
||||
const reload = element.getAttribute('data-force-reload');
|
||||
const param = element.getAttribute('data-param') as string | undefined;
|
||||
|
||||
const confirmElementName = element.getAttribute('data-confirm-param');
|
||||
const confirmElement = (confirmElementName ? document.querySelector(confirmElementName) : null) as HTMLInputElement | null;
|
||||
|
||||
const confirmElementName = element.getAttribute('data-confirm-param');
|
||||
const confirmElement = (confirmElementName ? document.querySelector(confirmElementName) : null) as HTMLInputElement | null;
|
||||
|
||||
const bufferSize = (function () {
|
||||
const number = parseInt(element.getAttribute('data-buffer') ?? "", 10) ?? 0;
|
||||
return (isFinite(number) && number > 0) ? number : 0;
|
||||
})()
|
||||
|
||||
const validate = function() {
|
||||
if (!confirmElement) return true
|
||||
return confirmElement.value === param;
|
||||
}
|
||||
|
||||
if (confirmElement) {
|
||||
const runValidation = () => {
|
||||
if (validate()) {
|
||||
element.removeAttribute('disabled')
|
||||
} else {
|
||||
element.setAttribute('disabled', 'disabled')
|
||||
}
|
||||
const bufferSize = (function () {
|
||||
const number = parseInt(element.getAttribute('data-buffer') ?? "", 10) ?? 0;
|
||||
return (isFinite(number) && number > 0) ? number : 0;
|
||||
})()
|
||||
|
||||
const validate = function() {
|
||||
if (!confirmElement) return true
|
||||
return confirmElement.value === param;
|
||||
}
|
||||
confirmElement.addEventListener('change', runValidation)
|
||||
runValidation()
|
||||
}
|
||||
|
||||
element.addEventListener('click', function (ev) {
|
||||
ev.preventDefault();
|
||||
|
||||
// do nothing if the validation fails
|
||||
if (!validate()) return;
|
||||
|
||||
// create a modal dialog and append it to the body
|
||||
const modal = document.createElement("div")
|
||||
modal.className = "modal-terminal"
|
||||
document.body.append(modal)
|
||||
|
||||
// create a <pre> to write stuff into
|
||||
const target = document.createElement("pre")
|
||||
const println = makeTextBuffer(target, modal, bufferSize)
|
||||
modal.append(target)
|
||||
|
||||
|
||||
// create a button to eventually close everything
|
||||
const button = document.createElement("button")
|
||||
button.className = "pure-button pure-button-success"
|
||||
button.append(typeof reload === 'string' ? "Close & Reload" : "Close")
|
||||
button.addEventListener('click', function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (typeof reload === 'string') {
|
||||
button.setAttribute('disabled', 'disabled')
|
||||
target.innerHTML = 'Reloading page ...'
|
||||
if (reload === '') {
|
||||
location.reload()
|
||||
|
||||
if (confirmElement) {
|
||||
const runValidation = () => {
|
||||
if (validate()) {
|
||||
element.removeAttribute('disabled')
|
||||
} else {
|
||||
location.href = reload
|
||||
element.setAttribute('disabled', 'disabled')
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
modal.parentNode?.removeChild(modal);
|
||||
})
|
||||
|
||||
const onbeforeunload = window.onbeforeunload;
|
||||
window.onbeforeunload = () => "A remote session is in progress. Are you sure you want to leave?";
|
||||
|
||||
// when closing, add a button to the modal!
|
||||
let didClose = false
|
||||
const close = function () {
|
||||
if (didClose) return
|
||||
didClose = true
|
||||
|
||||
window.onbeforeunload = onbeforeunload;
|
||||
modal.append(button)
|
||||
// DEBUG: print terminal stats!
|
||||
// const quota = (println.paintedFrames / (println.missedFrames + println.paintedFrames)) * 100
|
||||
// println(`Terminal: painted=${println.paintedFrames} missed=${println.missedFrames} (${quota}%)`, true)
|
||||
confirmElement.addEventListener('change', runValidation)
|
||||
runValidation()
|
||||
}
|
||||
|
||||
println("Connecting ...", true)
|
||||
|
||||
// connect to the socket and send the action
|
||||
connectSocket((socket) => {
|
||||
println("Connected", true)
|
||||
socket.send(action);
|
||||
if (typeof param === 'string') {
|
||||
socket.send(param);
|
||||
let onClose: (success: boolean) => void | null;
|
||||
if (typeof reload === 'string') {
|
||||
onClose = () => {
|
||||
if (reload === '') location.reload();
|
||||
else location.href = reload;
|
||||
}
|
||||
}, (data) => {
|
||||
println(data);
|
||||
}).then(() => {
|
||||
println("Connection closed.", true)
|
||||
close();
|
||||
}).catch(() => {
|
||||
println("Connection errored.", true)
|
||||
close();
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
const remote_link = document.getElementsByClassName('remote-link')
|
||||
Array.from(remote_link).forEach((element) => {
|
||||
const action = element.getAttribute('data-action') as string;
|
||||
const param = element.getAttribute('data-params') as string | undefined;
|
||||
const params = param?.split(" ");
|
||||
|
||||
element.addEventListener('click', function (ev) {
|
||||
ev.preventDefault();
|
||||
|
||||
getValue(action, params).then(v => {
|
||||
window.open(v);
|
||||
}).catch(e => {
|
||||
console.error(e);
|
||||
})
|
||||
});
|
||||
})
|
||||
|
||||
async function getValue(action: string, params?: Array<string>): Promise<any> {
|
||||
return new Promise((rs, rj) => {
|
||||
let buffer = "";
|
||||
var resolve = function() {
|
||||
const index = buffer.indexOf('\n')
|
||||
if (index < 0) {
|
||||
rj("invalid buffer");
|
||||
return
|
||||
}
|
||||
|
||||
// check that the server sent back true
|
||||
const ok = buffer.substring(0, index) === 'true';
|
||||
if(!ok) {
|
||||
rj(buffer);
|
||||
return
|
||||
}
|
||||
|
||||
// parse the rest as json
|
||||
const value = JSON.parse(buffer.substring(index+1))
|
||||
rs(value);
|
||||
}
|
||||
|
||||
element.addEventListener('click', function (ev) {
|
||||
ev.preventDefault();
|
||||
|
||||
// do nothing if the validation fails
|
||||
if (!validate()) return;
|
||||
|
||||
connectSocket((socket) => {
|
||||
socket.send(action);
|
||||
if (params) {
|
||||
params.forEach(p => socket.send(p))
|
||||
}
|
||||
}, (data) => {
|
||||
buffer += data + "\n";
|
||||
}).then(() => {
|
||||
resolve();
|
||||
}).catch(() => {
|
||||
buffer = "false\n";
|
||||
resolve();
|
||||
// create a modal dialog
|
||||
const params = (typeof param === 'string') ? [param] : [];
|
||||
createModal(action, params, {
|
||||
onClose: onClose,
|
||||
bufferSize: bufferSize,
|
||||
});
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
type ModalOptions = {
|
||||
bufferSize: number;
|
||||
onClose: (success: boolean) => void
|
||||
}
|
||||
export function createModal(action: string, params: string[], opts: Partial<ModalOptions>) {
|
||||
// create a modal dialog and append it to the body
|
||||
const modal = document.createElement("div")
|
||||
modal.className = "modal-terminal"
|
||||
document.body.append(modal)
|
||||
|
||||
// create a <pre> to write stuff into
|
||||
const target = document.createElement("pre")
|
||||
const println = makeTextBuffer(target, modal, opts.bufferSize ?? 1000)
|
||||
modal.append(target)
|
||||
|
||||
// create a button to eventually close everything
|
||||
const button = document.createElement("button")
|
||||
button.className = "pure-button pure-button-success"
|
||||
button.append(typeof opts?.onClose === 'function' ? "Close & Finish" : "Close")
|
||||
let success = false;
|
||||
button.addEventListener('click', function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (typeof opts?.onClose === 'function') {
|
||||
button.setAttribute('disabled', 'disabled')
|
||||
target.innerHTML = 'Finishing up ...'
|
||||
opts.onClose(success)
|
||||
return;
|
||||
}
|
||||
|
||||
modal.parentNode?.removeChild(modal);
|
||||
})
|
||||
|
||||
const onbeforeunload = window.onbeforeunload;
|
||||
window.onbeforeunload = () => "A remote session is in progress. Are you sure you want to leave?";
|
||||
|
||||
// when closing, add a button to the modal!
|
||||
let didClose = false
|
||||
const close = function () {
|
||||
if (didClose) return
|
||||
didClose = true
|
||||
|
||||
window.onbeforeunload = onbeforeunload;
|
||||
modal.append(button)
|
||||
// DEBUG: print terminal stats!
|
||||
// const quota = (println.paintedFrames / (println.missedFrames + println.paintedFrames)) * 100
|
||||
// println(`Terminal: painted=${println.paintedFrames} missed=${println.missedFrames} (${quota}%)`, true)
|
||||
}
|
||||
|
||||
println("Connecting ...", true)
|
||||
|
||||
// connect to the socket and send the action
|
||||
connectSocket((socket) => {
|
||||
println("Connected", true)
|
||||
socket.send(action)
|
||||
params.forEach(p => socket.send(p))
|
||||
}, (data) => {
|
||||
println(data);
|
||||
}).then(() => {
|
||||
success = true
|
||||
println("Connection closed.", true)
|
||||
close();
|
||||
}).catch(() => {
|
||||
success = false
|
||||
println("Connection errored.", true)
|
||||
close();
|
||||
});
|
||||
}
|
||||
|
|
@ -31,7 +31,6 @@ func (flags Flags) Apply(r *http.Request, funcs ...FlagFunc) Flags {
|
|||
}
|
||||
|
||||
// RuntimeFlags are passed to the template at runtime.
|
||||
// Any context may e
|
||||
type RuntimeFlags struct {
|
||||
Flags
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue