frontend: Make Control Server nicer
This commit is contained in:
parent
c258b46443
commit
36891d7e7c
16 changed files with 231 additions and 170 deletions
18
cmd/cron.go
18
cmd/cron.go
|
|
@ -6,7 +6,6 @@ import (
|
|||
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/component/instances"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/core"
|
||||
"github.com/tkw1536/goprogram/exit"
|
||||
"github.com/tkw1536/goprogram/status"
|
||||
"github.com/tkw1536/goprogram/stream"
|
||||
)
|
||||
|
|
@ -32,11 +31,6 @@ func (cron) Description() wisski_distillery.Description {
|
|||
}
|
||||
}
|
||||
|
||||
var errCronFailed = exit.Error{
|
||||
Message: "Failed to run cron script for instance %q: exited with code %s",
|
||||
ExitCode: exit.ExitGeneric,
|
||||
}
|
||||
|
||||
func (cr cron) Run(context wisski_distillery.Context) error {
|
||||
// find all the instances!
|
||||
wissKIs, err := context.Environment.Instances().Load(cr.Positionals.Slug...)
|
||||
|
|
@ -46,17 +40,7 @@ func (cr cron) Run(context wisski_distillery.Context) error {
|
|||
|
||||
// and do the actual blind_update!
|
||||
return status.StreamGroup(context.IOStream, cr.Parallel, func(instance instances.WissKI, io stream.IOStream) error {
|
||||
code, err := instance.Shell(io, "/runtime/cron.sh")
|
||||
if err != nil {
|
||||
io.EPrintln(err)
|
||||
}
|
||||
if code != 0 {
|
||||
// keep going, because we want to run as many crons as possible
|
||||
err = errBlindUpdateFailed.WithMessageF(instance.Slug, code)
|
||||
io.EPrintln(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return instance.Cron(io)
|
||||
}, wissKIs, status.SmartMessage(func(item instances.WissKI) string {
|
||||
return fmt.Sprintf("cron %q", item.Slug)
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ func (i info) Run(context wisski_distillery.Context) error {
|
|||
context.Printf("Locked: %v\n", info.Locked)
|
||||
context.Printf("Last Rebuild: %v\n", info.LastRebuild.String())
|
||||
context.Printf("Last Update: %v\n", info.LastUpdate.String())
|
||||
context.Printf("Last Cron: %v\n", info.LastCron.String())
|
||||
|
||||
context.Printf("Skip Prefixes: %v\n", info.NoPrefixes)
|
||||
context.Printf("Prefixes: (count %d)\n", len(info.Prefixes))
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@
|
|||
<main>
|
||||
<div class="pure-g">
|
||||
<div class="pure-u-1-1">
|
||||
<h2 id="overview">Instance Overview</h2>
|
||||
<h2 id="overview">Info & Status</h2>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-xl-1-3">
|
||||
<div class="pure-u-1 pure-u-xl-2-5">
|
||||
<div class="padding">
|
||||
<div class="overflow">
|
||||
<table class="pure-table pure-table-bordered">
|
||||
|
|
@ -70,14 +70,72 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-xl-1-3">
|
||||
<div class="pure-u-1 pure-u-xl-2-5">
|
||||
<div class="padding">
|
||||
<div class="overflow">
|
||||
<table class="pure-table pure-table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">
|
||||
Build
|
||||
Component Settings
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
Directory
|
||||
</td>
|
||||
<td>
|
||||
<code style="overflow: auto;">{{ .Instance.FilesystemBase }}</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
SQL DB
|
||||
</td>
|
||||
<td>
|
||||
<code>{{ .Instance.SqlDatabase }}</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
SQL User
|
||||
</td>
|
||||
<td>
|
||||
<code>{{ .Instance.SqlUsername }}</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
TS Repo
|
||||
</td>
|
||||
<td>
|
||||
<code>{{ .Instance.GraphDBRepository }}</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
TS User
|
||||
</td>
|
||||
<td>
|
||||
<code>{{ .Instance.GraphDBUsername }}</code>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pure-u-1 pure-u-xl-2-5">
|
||||
<div class="padding">
|
||||
<div class="overflow">
|
||||
<table class="pure-table pure-table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">
|
||||
Build Status
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
@ -85,7 +143,7 @@
|
|||
<tr>
|
||||
<td>
|
||||
Created
|
||||
</td>
|
||||
</td>
|
||||
<td>
|
||||
<code class="date">{{ .Instance.Created.Format "2006-01-02T15:04:05Z07:00" }}</code>
|
||||
</td>
|
||||
|
|
@ -101,10 +159,11 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Automatic Updates
|
||||
Last Cron<br>
|
||||
<button class="remote-action pure-button pure-button-action" data-action="cron" data-param="{{ .Instance.Slug }}" data-buffer="1000" data-force-reload="true">Cron</button>
|
||||
</td>
|
||||
<td>
|
||||
<code>{{ .Instance.AutoBlindUpdateEnabled }}</code>
|
||||
<code class="date">{{ .Info.LastRebuild.Format "2006-01-02T15:04:05Z07:00" }}</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
@ -113,7 +172,8 @@
|
|||
<button class="remote-action pure-button pure-button-action" data-action="update" data-param="{{ .Instance.Slug }}" data-buffer="1000" data-force-reload="true">Update</button>
|
||||
</td>
|
||||
<td>
|
||||
<code class="date">{{ .Info.LastUpdate.Format "2006-01-02T15:04:05Z07:00" }}</code>
|
||||
<code class="date">{{ .Info.LastUpdate.Format "2006-01-02T15:04:05Z07:00" }}</code><br>
|
||||
(Automatic: <code>{{ .Instance.AutoBlindUpdateEnabled }}</code>)
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
@ -122,138 +182,89 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pure-u-1 pure-u-xl-1-3">
|
||||
<div class="pure-u-1 pure-u-xl-2-5">
|
||||
<!--
|
||||
<div class="padding">
|
||||
<div class="overflow">
|
||||
<table class="pure-table pure-table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">
|
||||
Resolver
|
||||
Composer Status
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
Excluded
|
||||
???
|
||||
</td>
|
||||
<td>
|
||||
<code>{{ .Info.NoPrefixes }}</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
URI Prefixes
|
||||
</td>
|
||||
<td>
|
||||
{{ range .Info.Prefixes }}
|
||||
<code>{{ . }}</code><br />
|
||||
{{ end}}
|
||||
???
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-xl-1-3">
|
||||
<div class="padding">
|
||||
<div class="overflow">
|
||||
<table class="pure-table pure-table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">
|
||||
Database Settings
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
SQL Database
|
||||
</td>
|
||||
<td>
|
||||
<code>{{ .Instance.SqlDatabase }}</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
SQL Username
|
||||
</td>
|
||||
<td>
|
||||
<code>{{ .Instance.SqlUsername }}</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
GraphDB Repository
|
||||
</td>
|
||||
<td>
|
||||
<code>{{ .Instance.GraphDBRepository }}</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
GraphDB Username
|
||||
</td>
|
||||
<td>
|
||||
<code>{{ .Instance.GraphDBUsername }}</code>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-xl-1-3">
|
||||
<div class="padding">
|
||||
<div class="overflow">
|
||||
<table class="pure-table pure-table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">
|
||||
Whitebox Data
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<div class="pure-u-1-1">
|
||||
<h2 id="wisski">WissKI Data</h2>
|
||||
</div>
|
||||
|
||||
<div class="pure-u-1 pure-u-xl-1-2">
|
||||
<div class="padding">
|
||||
<div class="overflow">
|
||||
<table class="pure-table pure-table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>
|
||||
<th colspan="2">
|
||||
Pathbuilders
|
||||
</td>
|
||||
<td>
|
||||
<code class="pathbuilders">{{ .Info.Pathbuilders }}</code>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-xl-1-3">
|
||||
<div class="padding">
|
||||
<div class="overflow">
|
||||
<table class="pure-table pure-table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">
|
||||
Misc Settings
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{{ range $name, $xml := .Info.Pathbuilders }}
|
||||
<tr>
|
||||
<td>
|
||||
Filesystem Base
|
||||
<code>{{ $name }}</code>
|
||||
</td>
|
||||
<td>
|
||||
<code>{{ .Instance.FilesystemBase }}</code>
|
||||
<script>window.pathbuilders = {{ .Info.Pathbuilders }};</script>
|
||||
<code class="pathbuilder" data-name="{{ $name }}">{{ $xml }}</code>
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-xl-1-2">
|
||||
<div class="padding">
|
||||
<div class="overflow">
|
||||
<table class="pure-table pure-table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
URI Prefixes
|
||||
|
||||
{{ if .Info.NoPrefixes }}
|
||||
(excluded from resolver)
|
||||
{{ end }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range $index, $prefix := .Info.Prefixes }}
|
||||
<tr>
|
||||
<td>
|
||||
<code>{{ $prefix }}</code>
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@ var socketInstanceActions = map[string]instanceActionFunc{
|
|||
"update": func(_ *Info, instance instances.WissKI, str stream.IOStream) error {
|
||||
return instance.BlindUpdate(str)
|
||||
},
|
||||
"cron": func(_ *Info, instance instances.WissKI, str stream.IOStream) error {
|
||||
return instance.Cron(str)
|
||||
},
|
||||
}
|
||||
|
||||
func (info *Info) serveSocket(conn httpx.WebSocketConnection) {
|
||||
|
|
|
|||
36
internal/component/instances/wisski_cron.go
Normal file
36
internal/component/instances/wisski_cron.go
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
package instances
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/tkw1536/goprogram/exit"
|
||||
"github.com/tkw1536/goprogram/stream"
|
||||
)
|
||||
|
||||
var errCronFailed = exit.Error{
|
||||
Message: "Failed to run cron script for instance %q: exited with code %s",
|
||||
ExitCode: exit.ExitGeneric,
|
||||
}
|
||||
|
||||
func (wisski *WissKI) Cron(io stream.IOStream) error {
|
||||
code, err := wisski.Shell(io, "/runtime/cron.sh")
|
||||
if err != nil {
|
||||
io.EPrintln(err)
|
||||
}
|
||||
if code != 0 {
|
||||
// keep going, because we want to run as many crons as possible
|
||||
err = errBlindUpdateFailed.WithMessageF(wisski.Slug, code)
|
||||
io.EPrintln(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wisski *WissKI) LastCron(server *PHPServer) (t time.Time, err error) {
|
||||
var timestamp int64
|
||||
err = wisski.EvalPHPCode(server, ×tamp, `$val = \Drupal::state()->get('system.cron_last'); return $val; `)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return time.Unix(timestamp, 0), nil
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package instances
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
||||
|
|
@ -22,6 +23,7 @@ type WissKIInfo struct {
|
|||
Running bool
|
||||
LastRebuild time.Time
|
||||
LastUpdate time.Time
|
||||
LastCron time.Time
|
||||
|
||||
// List of backups made
|
||||
Snapshots []models.Export
|
||||
|
|
@ -40,9 +42,9 @@ func (wisski *WissKI) Info(quick bool) (info WissKIInfo, err error) {
|
|||
if !quick {
|
||||
server, err := wisski.NewPHPServer()
|
||||
if err == nil {
|
||||
wisski.infoSlow(&info, server, &group)
|
||||
defer server.Close()
|
||||
}
|
||||
wisski.infoSlow(&info, server, &group)
|
||||
}
|
||||
|
||||
err = group.Wait()
|
||||
|
|
@ -87,19 +89,28 @@ func (wisski *WissKI) infoQuick(info *WissKIInfo, group *errgroup.Group) {
|
|||
|
||||
func (wisski *WissKI) infoSlow(info *WissKIInfo, server *PHPServer, group *errgroup.Group) {
|
||||
group.Go(func() (err error) {
|
||||
info.Prefixes, _ = wisski.Prefixes(server)
|
||||
info.Prefixes, err = wisski.Prefixes(server)
|
||||
log.Println("error prefixes: ", err)
|
||||
return nil
|
||||
})
|
||||
|
||||
group.Go(func() error {
|
||||
info.Snapshots, _ = wisski.Snapshots()
|
||||
group.Go(func() (err error) {
|
||||
info.Snapshots, err = wisski.Snapshots()
|
||||
log.Println("error snapshots: ", err)
|
||||
return nil
|
||||
})
|
||||
|
||||
group.Go(func() error {
|
||||
info.Pathbuilders, _ = wisski.AllPathbuilders(server)
|
||||
group.Go(func() (err error) {
|
||||
info.Pathbuilders, err = wisski.AllPathbuilders(server)
|
||||
log.Println("error pathbuilders: ", err)
|
||||
return nil
|
||||
})
|
||||
|
||||
group.Go(func() (err error) {
|
||||
info.LastCron, err = wisski.LastCron(server)
|
||||
log.Println("error cron: ", err)
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// Running checks if this WissKI is currently running.
|
||||
|
|
|
|||
|
|
@ -174,9 +174,13 @@ func (server *PHPServer) MarshalCall(value any, function string, args ...any) er
|
|||
if err != nil {
|
||||
return PHPServerError{Message: errPHPMarshal, Err: err}
|
||||
}
|
||||
userFunctionArgs, err := marshalPHP(args)
|
||||
if err != nil {
|
||||
return PHPServerError{Message: errPHPMarshal, Err: err}
|
||||
|
||||
userFunctionArgs := "[]"
|
||||
if len(args) > 0 {
|
||||
userFunctionArgs, err = marshalPHP(args)
|
||||
if err != nil {
|
||||
return PHPServerError{Message: errPHPMarshal, Err: err}
|
||||
}
|
||||
}
|
||||
code := "return call_user_func_array(" + userFunction + "," + userFunctionArgs + ");"
|
||||
|
||||
|
|
@ -246,6 +250,7 @@ func (server *PHPServer) Close() error {
|
|||
|
||||
// ExecPHPScript executes the PHP code as a script on the given server.
|
||||
// When server is nil, creates a new server and automatically closes it after execution.
|
||||
// Calling this function repeatedly with server = nil is inefficient.
|
||||
//
|
||||
// The script should define a function called entrypoint, and may define additional functions.
|
||||
//
|
||||
|
|
@ -257,8 +262,6 @@ func (server *PHPServer) Close() error {
|
|||
// It's arguments are encoded as json using [json.Marshal] and decoded within php.
|
||||
//
|
||||
// The return value of the function is again marshaled with json and returned to the caller.
|
||||
//
|
||||
// Calling this function is inefficient, and a [NewPHPServer] call should be prefered instead.
|
||||
func (wisski *WissKI) ExecPHPScript(server *PHPServer, value any, code string, entrypoint string, args ...any) (err error) {
|
||||
if server == nil {
|
||||
server, err = wisski.NewPHPServer()
|
||||
|
|
@ -268,13 +271,27 @@ func (wisski *WissKI) ExecPHPScript(server *PHPServer, value any, code string, e
|
|||
defer server.Close()
|
||||
}
|
||||
|
||||
if err := server.MarshalEval(nil, strings.TrimPrefix(code, "<?php")); err != nil {
|
||||
return err
|
||||
if code != "" {
|
||||
if err := server.MarshalEval(nil, strings.TrimPrefix(code, "<?php")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return server.MarshalCall(value, entrypoint, args...)
|
||||
}
|
||||
|
||||
func (wisski *WissKI) EvalPHPCode(server *PHPServer, value any, code string) (err error) {
|
||||
if server == nil {
|
||||
server, err = wisski.NewPHPServer()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer server.Close()
|
||||
}
|
||||
|
||||
return server.MarshalEval(value, code)
|
||||
}
|
||||
|
||||
//go:embed php/server.php
|
||||
var serverPHP string
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package static
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
|
|
@ -30,11 +31,17 @@ func (assets *Assets) MustParse(value string) *template.Template {
|
|||
return template.Must(assets.RegisterFuncs(template.New("")).Parse(value))
|
||||
}
|
||||
|
||||
// RegisterFuncs registers two new template functions called "JS" and "CSS".
|
||||
// Both take no arguments, and return a html-safe version of the Scripts and Style tags to be included.
|
||||
// RegisterFuncs registers three new template functions called "JS", "CSS" and "json".
|
||||
//
|
||||
// "JS" and "CSS" take no arguments, and return appropriate tags to be inserted into html.
|
||||
// json takes a single argument of any type, and returns it's encoding as a string to be inserted into the page.
|
||||
func (assets *Assets) RegisterFuncs(t *template.Template) *template.Template {
|
||||
return t.Funcs(template.FuncMap{
|
||||
"JS": func() template.HTML { return template.HTML(assets.Scripts) },
|
||||
"CSS": func() template.HTML { return template.HTML(assets.Styles) },
|
||||
"json": func(data any) (string, error) {
|
||||
bytes, err := json.Marshal(data)
|
||||
return string(bytes), err
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,17 +5,17 @@ package static
|
|||
// AssetsHomeHome contains assets for the 'HomeHome' entrypoint.
|
||||
var AssetsHomeHome = Assets{
|
||||
Scripts: `<script type="module" src="/static/HomeHome.38d394c2.js"></script><script src="/static/HomeHome.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/HomeHome.38d394c2.js"></script><script src="/static/HomeHome.38d394c2.js" nomodule="" defer></script>`,
|
||||
Styles: `<link rel="stylesheet" href="/static/HomeHome.518b2dbe.css"><link rel="stylesheet" href="/static/HomeHome.38d394c2.css">`,
|
||||
Styles: `<link rel="stylesheet" href="/static/HomeHome.a75f04fa.css"><link rel="stylesheet" href="/static/HomeHome.38d394c2.css">`,
|
||||
}
|
||||
|
||||
// AssetsControlIndex contains assets for the 'ControlIndex' entrypoint.
|
||||
var AssetsControlIndex = Assets{
|
||||
Scripts: `<script type="module" src="/static/HomeHome.38d394c2.js"></script><script src="/static/HomeHome.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/ControlIndex.43f953d2.js"></script><script src="/static/ControlIndex.c70a89e1.js" nomodule="" defer></script>`,
|
||||
Styles: `<link rel="stylesheet" href="/static/HomeHome.518b2dbe.css"><link rel="stylesheet" href="/static/ControlIndex.6d59e220.css"><link rel="stylesheet" href="/static/ControlIndex.6d2ae968.css">`,
|
||||
Scripts: `<script type="module" src="/static/HomeHome.38d394c2.js"></script><script src="/static/HomeHome.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/ControlIndex.cfbf936d.js"></script><script src="/static/ControlIndex.613b02c2.js" nomodule="" defer></script>`,
|
||||
Styles: `<link rel="stylesheet" href="/static/HomeHome.a75f04fa.css"><link rel="stylesheet" href="/static/ControlIndex.6d59e220.css"><link rel="stylesheet" href="/static/ControlIndex.6d2ae968.css">`,
|
||||
}
|
||||
|
||||
// AssetsControlInstance contains assets for the 'ControlInstance' entrypoint.
|
||||
var AssetsControlInstance = Assets{
|
||||
Scripts: `<script nomodule="" defer src="/static/ControlIndex.c70a89e1.js"></script><script type="module" src="/static/ControlIndex.43f953d2.js"></script><script type="module" src="/static/HomeHome.38d394c2.js"></script><script src="/static/HomeHome.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/ControlInstance.66b95713.js"></script><script src="/static/ControlInstance.9cc7166d.js" nomodule="" defer></script>`,
|
||||
Styles: `<link rel="stylesheet" href="/static/HomeHome.518b2dbe.css"><link rel="stylesheet" href="/static/ControlIndex.6d59e220.css"><link rel="stylesheet" href="/static/ControlIndex.6d2ae968.css"><link rel="stylesheet" href="/static/ControlInstance.38d394c2.css">`,
|
||||
Scripts: `<script nomodule="" defer src="/static/ControlIndex.613b02c2.js"></script><script type="module" src="/static/ControlIndex.cfbf936d.js"></script><script type="module" src="/static/HomeHome.38d394c2.js"></script><script src="/static/HomeHome.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/ControlInstance.66b95713.js"></script><script src="/static/ControlInstance.9cc7166d.js" nomodule="" defer></script>`,
|
||||
Styles: `<link rel="stylesheet" href="/static/HomeHome.a75f04fa.css"><link rel="stylesheet" href="/static/ControlIndex.6d59e220.css"><link rel="stylesheet" href="/static/ControlIndex.6d2ae968.css"><link rel="stylesheet" href="/static/ControlInstance.38d394c2.css">`,
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
1
internal/component/static/dist/ControlIndex.613b02c2.js
vendored
Normal file
1
internal/component/static/dist/ControlIndex.613b02c2.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/component/static/dist/ControlIndex.cfbf936d.js
vendored
Normal file
1
internal/component/static/dist/ControlIndex.cfbf936d.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
|
|
@ -20,6 +20,11 @@ footer {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.overflow table td,
|
||||
.overflow table th{
|
||||
padding: .5em .5em;
|
||||
}
|
||||
|
||||
.overflow table td:not(:last-child),
|
||||
.overflow table th:not(:last-child) {
|
||||
width: 1px;
|
||||
|
|
@ -34,7 +39,7 @@ footer {
|
|||
|
||||
.hspace {
|
||||
display: block;
|
||||
height: 2em;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
.pure-button-action {
|
||||
|
|
|
|||
|
|
@ -7,32 +7,19 @@ const types: Record<string, (element: HTMLElement) => HTMLElement | string> = {
|
|||
const text = element.innerText.split("/");
|
||||
return text[text.length - 1];
|
||||
},
|
||||
"pathbuilders": () => {
|
||||
const pathbuilders: {[name: string]: string} = (window as any).pathbuilders; // must be declared globally on page!
|
||||
const wrapper = document.createElement("span");
|
||||
"pathbuilder": (element) => {
|
||||
// create a link and get the blob
|
||||
const filename = (element.getAttribute('data-name') ?? 'pathbuilder') + ".xml"
|
||||
const [link, blob] = make_download_link(filename, element.innerText, "application/xml")
|
||||
|
||||
let found_one = false
|
||||
Object.keys(pathbuilders).forEach(name => {
|
||||
found_one = true
|
||||
|
||||
const filename = name + ".xml"
|
||||
const data = pathbuilders[name]
|
||||
const mime = "application/xml"
|
||||
wrapper.append(make_download_link(filename, name, data, mime))
|
||||
wrapper.append(document.createTextNode(" "))
|
||||
})
|
||||
|
||||
if (!found_one) return '(none)';
|
||||
|
||||
const small = document.createElement('small')
|
||||
small.append(document.createTextNode("(click to download)"))
|
||||
wrapper.append(small)
|
||||
|
||||
return wrapper
|
||||
link.className = "pure-button"
|
||||
const title = filename + ' (' + blob.size + ' Bytes)';
|
||||
link.append(title)
|
||||
return link
|
||||
}
|
||||
}
|
||||
|
||||
const make_download_link = (filename: string, title: string, content: string, type: string) => {
|
||||
const make_download_link = (filename: string, content: string, type: string): [HTMLAnchorElement, Blob] => {
|
||||
const blob = new Blob(
|
||||
[content],
|
||||
{
|
||||
|
|
@ -44,9 +31,8 @@ const make_download_link = (filename: string, title: string, content: string, ty
|
|||
link.target = "_blank"
|
||||
link.download = filename
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.append(document.createTextNode(title))
|
||||
|
||||
return link
|
||||
return [link, blob]
|
||||
}
|
||||
|
||||
Object.keys(types).forEach(key => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue