Add a form to provision a new instance

This commit is contained in:
Tom Wiesing 2023-02-27 11:10:28 +01:00
parent 80906d3791
commit 53f63d4efd
No known key found for this signature in database
25 changed files with 367 additions and 236 deletions

View file

@ -3,21 +3,21 @@ package cmd
import ( import (
wisski_distillery "github.com/FAU-CDI/wisski-distillery" wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/internal/cli" "github.com/FAU-CDI/wisski-distillery/internal/cli"
"github.com/FAU-CDI/wisski-distillery/pkg/fsx" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/provision"
"github.com/FAU-CDI/wisski-distillery/pkg/logging" "github.com/FAU-CDI/wisski-distillery/pkg/logging"
"github.com/tkw1536/goprogram/exit" "github.com/tkw1536/goprogram/exit"
) )
// Provision is the 'provision' command // Provision is the 'provision' command
var Provision wisski_distillery.Command = provision{} var Provision wisski_distillery.Command = pv{}
type provision struct { type pv struct {
Positionals struct { Positionals struct {
Slug string `positional-arg-name:"slug" required:"1-1" description:"slug of instance to create"` Slug string `positional-arg-name:"slug" required:"1-1" description:"slug of instance to create"`
} `positional-args:"true"` } `positional-args:"true"`
} }
func (provision) Description() wisski_distillery.Description { func (pv) Description() wisski_distillery.Description {
return wisski_distillery.Description{ return wisski_distillery.Description{
Requirements: cli.Requirements{ Requirements: cli.Requirements{
NeedsDistillery: true, NeedsDistillery: true,
@ -27,82 +27,19 @@ func (provision) Description() wisski_distillery.Description {
} }
} }
// TODO: AfterParse to check instance!
var errProvisionAlreadyExists = exit.Error{
Message: "instance %q already exists",
ExitCode: exit.ExitGeneric,
}
var errProvisionGeneric = exit.Error{ var errProvisionGeneric = exit.Error{
Message: "unable to provision instance %s: %s", Message: "unable to provision instance %s: %s",
ExitCode: exit.ExitGeneric, ExitCode: exit.ExitGeneric,
} }
func (p provision) Run(context wisski_distillery.Context) error { // TODO: AfterParse to check instance!
dis := context.Environment
slug := p.Positionals.Slug
// check that it doesn't already exist func (p pv) Run(context wisski_distillery.Context) error {
logging.LogMessage(context.Stderr, context.Context, "Provisioning new WissKI instance %s", slug) instance, err := context.Environment.Provision().Provision(context.Stderr, context.Context, provision.ProvisionFlags{
if exists, err := dis.Instances().Has(context.Context, slug); err != nil || exists { Slug: p.Positionals.Slug,
return errProvisionAlreadyExists.WithMessageF(slug) })
}
// make it in-memory
instance, err := dis.Instances().Create(slug)
if err != nil { if err != nil {
return errProvisionGeneric.WithMessageF(slug, err) return errProvisionGeneric.WithMessageF(p.Positionals.Slug, err)
}
// check that the base directory does not exist
logging.LogMessage(context.Stderr, context.Context, "Checking that base directory %s does not exist", instance.FilesystemBase)
if fsx.IsDirectory(dis.Environment, instance.FilesystemBase) {
return errProvisionAlreadyExists.WithMessageF(slug)
}
// Store in the instances table!
if err := logging.LogOperation(func() error {
if err := instance.Bookkeeping().Save(context.Context); err != nil {
return errProvisionGeneric.WithMessageF(slug, err)
}
return nil
}, context.Stderr, context.Context, "Updating bookkeeping database"); err != nil {
return err
}
// create all the resources!
if err := logging.LogOperation(func() error {
domain := instance.Domain()
for _, pc := range dis.Provisionable() {
logging.LogMessage(context.Stderr, context.Context, "Provisioning %s resources", pc.Name())
err := pc.Provision(context.Context, instance.Instance, domain)
if err != nil {
return err
}
}
return nil
}, context.Stderr, context.Context, "Provisioning instance-specific resources"); err != nil {
return errProvisionGeneric.WithMessageF(slug, err)
}
// run the provision script
if err := logging.LogOperation(func() error {
if err := instance.Provisioner().Provision(context.Context, context.Stderr); err != nil {
return errProvisionGeneric.WithMessageF(slug, err)
}
return nil
}, context.Stderr, context.Context, "Running setup scripts"); err != nil {
return err
}
// start the container!
logging.LogMessage(context.Stderr, context.Context, "Starting Container")
if err := instance.Barrel().Stack().Up(context.Context, context.Stderr); err != nil {
return err
} }
// and we're done! // and we're done!

View file

@ -47,7 +47,7 @@ func (r reserve) Run(context wisski_distillery.Context) error {
// check that it doesn't already exist // check that it doesn't already exist
logging.LogMessage(context.Stderr, context.Context, "Reserving new WissKI instance %s", slug) logging.LogMessage(context.Stderr, context.Context, "Reserving new WissKI instance %s", slug)
if exists, err := dis.Instances().Has(context.Context, slug); err != nil || exists { if exists, err := dis.Instances().Has(context.Context, slug); err != nil || exists {
return errProvisionAlreadyExists.WithMessageF(slug) return errReserveAlreadyExists.WithMessageF(slug)
} }
// make it in-memory // make it in-memory
@ -59,7 +59,7 @@ func (r reserve) Run(context wisski_distillery.Context) error {
// check that the base directory does not exist // check that the base directory does not exist
logging.LogMessage(context.Stderr, context.Context, "Checking that base directory %s does not exist", instance.FilesystemBase) logging.LogMessage(context.Stderr, context.Context, "Checking that base directory %s does not exist", instance.FilesystemBase)
if fsx.IsDirectory(dis.Environment, instance.FilesystemBase) { if fsx.IsDirectory(dis.Environment, instance.FilesystemBase) {
return errProvisionAlreadyExists.WithMessageF(slug) return errReserveAlreadyExists.WithMessageF(slug)
} }
// setup docker stack // setup docker stack

View file

@ -1,7 +1,7 @@
package cli package cli
// =========================================================================================================== // ===========================================================================================================
// This file was generated automatically at 26-02-2023 19:08:42 using gogenlicense. // This file was generated automatically at 27-02-2023 10:11:35 using gogenlicense.
// Do not edit manually, as changes may be overwritten. // Do not edit manually, as changes may be overwritten.
// =========================================================================================================== // ===========================================================================================================
@ -2452,7 +2452,7 @@ package cli
// # Generation // # Generation
// //
// This variable and the associated documentation have been automatically generated using the 'gogenlicense' tool. // This variable and the associated documentation have been automatically generated using the 'gogenlicense' tool.
// It was last updated at 26-02-2023 19:08:42. // It was last updated at 27-02-2023 10:11:35.
var LegalNotices string var LegalNotices string
func init() { func init() {

View file

@ -0,0 +1,92 @@
package provision
import (
"context"
"errors"
"io"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
"github.com/FAU-CDI/wisski-distillery/internal/wisski"
"github.com/FAU-CDI/wisski-distillery/pkg/fsx"
"github.com/FAU-CDI/wisski-distillery/pkg/logging"
)
type Provision struct {
component.Base
Dependencies struct {
Instances *instances.Instances
Provisionable []component.Provisionable
}
}
// ProvisionFlags are flags for a new instance
type ProvisionFlags struct {
Slug string
}
var ErrInstanceAlreadyExists = errors.New("instance with provided slug already exists")
// Provision provisions a new docker compose instance.
func (pv *Provision) Provision(progress io.Writer, ctx context.Context, flags ProvisionFlags) (*wisski.WissKI, error) {
// check that it doesn't already exist
logging.LogMessage(progress, ctx, "Provisioning new WissKI instance %s", flags.Slug)
if exists, err := pv.Dependencies.Instances.Has(ctx, flags.Slug); err != nil || exists {
return nil, ErrInstanceAlreadyExists
}
// make it in-memory
instance, err := pv.Dependencies.Instances.Create(flags.Slug)
if err != nil {
return nil, err
}
// check that the base directory does not exist
logging.LogMessage(progress, ctx, "Checking that base directory %s does not exist", instance.FilesystemBase)
if fsx.IsDirectory(pv.Environment, instance.FilesystemBase) {
return nil, ErrInstanceAlreadyExists
}
// Store in the instances table!
if err := logging.LogOperation(func() error {
if err := instance.Bookkeeping().Save(ctx); err != nil {
return err
}
return nil
}, progress, ctx, "Updating bookkeeping database"); err != nil {
return nil, err
}
// create all the resources!
if err := logging.LogOperation(func() error {
domain := instance.Domain()
for _, pc := range pv.Dependencies.Provisionable {
logging.LogMessage(progress, ctx, "Provisioning %s resources", pc.Name())
err := pc.Provision(ctx, instance.Instance, domain)
if err != nil {
return err
}
}
return nil
}, progress, ctx, "Provisioning instance-specific resources"); err != nil {
return nil, err
}
// run the provision script
if err := logging.LogOperation(func() error {
return instance.Provisioner().Provision(ctx, progress)
}, progress, ctx, "Running setup scripts"); err != nil {
return nil, err
}
// start the container!
logging.LogMessage(progress, ctx, "Starting Container")
if err := instance.Barrel().Stack().Up(ctx, progress); err != nil {
return nil, err
}
// and return the instance
return instance, nil
}

View file

@ -70,7 +70,9 @@ var (
menuUsers = component.MenuItem{Title: "Users", Path: "/admin/users/"} menuUsers = component.MenuItem{Title: "Users", Path: "/admin/users/"}
menuUserCreate = component.MenuItem{Title: "Create User", Path: "/admin/users/create/"} 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() menuInstance = component.DummyMenuItem()
menuGrants = component.DummyMenuItem() menuGrants = component.DummyMenuItem()
menuIngredients = 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) 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) 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 // add a handler for the user page

View file

@ -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>

View file

@ -130,6 +130,9 @@ func (admin *Admin) instances(ctx context.Context) http.Handler {
menuAdmin, menuAdmin,
menuInstances, menuInstances,
), ),
templating.Actions(
menuProvision,
),
) )
return tpl.HTMLHandler(func(r *http.Request) (idx indexContext, err error) { return tpl.HTMLHandler(func(r *http.Request) (idx indexContext, err error) {

View 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
})
}

View file

@ -2,9 +2,12 @@ package socket
import ( import (
"context" "context"
"encoding/json"
"fmt"
"io" "io"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter" "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" "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 // socket specific actions

View file

@ -10,6 +10,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter" "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"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances/purger" "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/FAU-CDI/wisski-distillery/internal/wisski"
"github.com/tkw1536/goprogram/status" "github.com/tkw1536/goprogram/status"
"github.com/tkw1536/pkglib/httpx" "github.com/tkw1536/pkglib/httpx"
@ -19,6 +20,7 @@ type Sockets struct {
component.Base component.Base
Dependencies struct { Dependencies struct {
Provision *provision.Provision
Instances *instances.Instances Instances *instances.Instances
Exporter *exporter.Exporter Exporter *exporter.Exporter
Purger *purger.Purger Purger *purger.Purger

View file

@ -21,4 +21,4 @@ type Assets struct {
Styles template.HTML // <link> tags inserted by the asset 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

View file

@ -24,6 +24,12 @@ var AssetsUser = Assets{
// AssetsAdmin contains assets for the 'Admin' entrypoint. // AssetsAdmin contains assets for the 'Admin' entrypoint.
var AssetsAdmin = Assets{ 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">`, 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">`,
}

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

File diff suppressed because one or more lines are too long

View 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");

View 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")}();

View file

@ -1,6 +1,10 @@
import "~/src/lib/remote" // setup highlighting
import "~/src/lib/highlight" import "~/src/lib/highlight"
// setup remote actions
import setup from "~/src/lib/remote"
setup();
// include the user styles! // include the user styles!
import "../User/index.ts" import "../User/index.ts"
import "../User/index.css" import "../User/index.css"

View file

@ -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');

View file

@ -46,6 +46,7 @@ function makeTextBuffer(target: HTMLElement, scrollContainer: HTMLElement, size:
return println; return println;
} }
export default function setup() {
const remote_action = document.getElementsByClassName('remote-action') const remote_action = document.getElementsByClassName('remote-action')
Array.from(remote_action).forEach((element) => { Array.from(remote_action).forEach((element) => {
const action = element.getAttribute('data-action') as string; const action = element.getAttribute('data-action') as string;
@ -77,12 +78,36 @@ Array.from(remote_action).forEach((element) => {
runValidation() runValidation()
} }
let onClose: (success: boolean) => void | null;
if (typeof reload === 'string') {
onClose = () => {
if (reload === '') location.reload();
else location.href = reload;
}
}
element.addEventListener('click', function (ev) { element.addEventListener('click', function (ev) {
ev.preventDefault(); ev.preventDefault();
// do nothing if the validation fails // do nothing if the validation fails
if (!validate()) return; if (!validate()) return;
// 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 // create a modal dialog and append it to the body
const modal = document.createElement("div") const modal = document.createElement("div")
modal.className = "modal-terminal" modal.className = "modal-terminal"
@ -90,25 +115,21 @@ Array.from(remote_action).forEach((element) => {
// create a <pre> to write stuff into // create a <pre> to write stuff into
const target = document.createElement("pre") const target = document.createElement("pre")
const println = makeTextBuffer(target, modal, bufferSize) const println = makeTextBuffer(target, modal, opts.bufferSize ?? 1000)
modal.append(target) modal.append(target)
// create a button to eventually close everything // create a button to eventually close everything
const button = document.createElement("button") const button = document.createElement("button")
button.className = "pure-button pure-button-success" button.className = "pure-button pure-button-success"
button.append(typeof reload === 'string' ? "Close & Reload" : "Close") button.append(typeof opts?.onClose === 'function' ? "Close & Finish" : "Close")
let success = false;
button.addEventListener('click', function (event) { button.addEventListener('click', function (event) {
event.preventDefault(); event.preventDefault();
if (typeof reload === 'string') { if (typeof opts?.onClose === 'function') {
button.setAttribute('disabled', 'disabled') button.setAttribute('disabled', 'disabled')
target.innerHTML = 'Reloading page ...' target.innerHTML = 'Finishing up ...'
if (reload === '') { opts.onClose(success)
location.reload()
} else {
location.href = reload
}
return; return;
} }
@ -136,73 +157,17 @@ Array.from(remote_action).forEach((element) => {
// connect to the socket and send the action // connect to the socket and send the action
connectSocket((socket) => { connectSocket((socket) => {
println("Connected", true) println("Connected", true)
socket.send(action); socket.send(action)
if (typeof param === 'string') { params.forEach(p => socket.send(p))
socket.send(param);
}
}, (data) => { }, (data) => {
println(data); println(data);
}).then(() => { }).then(() => {
success = true
println("Connection closed.", true) println("Connection closed.", true)
close(); close();
}).catch(() => { }).catch(() => {
success = false
println("Connection errored.", true) println("Connection errored.", true)
close(); 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);
}
connectSocket((socket) => {
socket.send(action);
if (params) {
params.forEach(p => socket.send(p))
}
}, (data) => {
buffer += data + "\n";
}).then(() => {
resolve();
}).catch(() => {
buffer = "false\n";
resolve();
});
})
} }

View file

@ -31,7 +31,6 @@ func (flags Flags) Apply(r *http.Request, funcs ...FlagFunc) Flags {
} }
// RuntimeFlags are passed to the template at runtime. // RuntimeFlags are passed to the template at runtime.
// Any context may e
type RuntimeFlags struct { type RuntimeFlags struct {
Flags Flags

View file

@ -17,6 +17,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances/malt" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances/malt"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances/purger" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances/purger"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/provision"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/resolver" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/resolver"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/admin" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/admin"
@ -102,6 +103,9 @@ func (dis *Distillery) Instances() *instances.Instances {
func (dis *Distillery) Exporter() *exporter.Exporter { func (dis *Distillery) Exporter() *exporter.Exporter {
return export[*exporter.Exporter](dis) return export[*exporter.Exporter](dis)
} }
func (dis *Distillery) Provision() *provision.Provision {
return export[*provision.Provision](dis)
}
func (dis *Distillery) Installable() []component.Installable { func (dis *Distillery) Installable() []component.Installable {
return exportAll[component.Installable](dis) return exportAll[component.Installable](dis)
@ -109,9 +113,6 @@ func (dis *Distillery) Installable() []component.Installable {
func (dis *Distillery) Updatable() []component.Updatable { func (dis *Distillery) Updatable() []component.Updatable {
return exportAll[component.Updatable](dis) return exportAll[component.Updatable](dis)
} }
func (dis *Distillery) Provisionable() []component.Provisionable {
return exportAll[component.Provisionable](dis)
}
func (dis *Distillery) Info() *admin.Admin { func (dis *Distillery) Info() *admin.Admin {
return export[*admin.Admin](dis) return export[*admin.Admin](dis)
} }
@ -162,6 +163,7 @@ func (dis *Distillery) allComponents() []initFunc {
auto[*instances.Instances], auto[*instances.Instances],
auto[*meta.Meta], auto[*meta.Meta],
auto[*malt.Malt], auto[*malt.Malt],
auto[*provision.Provision],
// Purger // Purger
auto[*purger.Purger], auto[*purger.Purger],