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
|
|
@ -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!
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
92
internal/dis/component/provision/provision.go
Normal file
92
internal/dis/component/provision/provision.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
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) {
|
||||||
|
|
|
||||||
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 (
|
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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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">`,
|
||||||
|
}
|
||||||
|
|
|
||||||
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"
|
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"
|
||||||
|
|
|
||||||
|
|
@ -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,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();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue