Add user login to admin interface

This commit is contained in:
Tom Wiesing 2022-11-23 16:57:09 +01:00
parent dbe494751a
commit 82bfc15057
No known key found for this signature in database
15 changed files with 256 additions and 79 deletions

View file

@ -177,31 +177,34 @@
</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">
Composer Status
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
???
</td>
<td>
???
</td>
</tr>
</tbody>
</table>
</div>
</div>
-->
<div class="padding">
<div class="overflow">
<table class="pure-table pure-table-bordered">
<thead>
<tr>
<th colspan="2">
Users
</th>
</tr>
</thead>
<tbody>
{{ $slug := .Instance.Slug }}
{{ range $index, $user := .Info.Users }}
<tr>
<td>
<code>{{ $user }}</code>
</td>
<td>
<small>
<button class="remote-link pure-button pure-button-action" role="link" data-action="login" data-params="{{ $slug }} {{ $user }} ">Login in new window</button>
</small>
</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
</div>
</div>
<div class="pure-u-1-1">

View file

@ -1,6 +1,9 @@
package info
import (
"encoding/json"
"time"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter"
"github.com/FAU-CDI/wisski-distillery/internal/wisski"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
@ -8,28 +11,48 @@ import (
"github.com/tkw1536/goprogram/stream"
)
type instanceActionFunc = func(info *Info, instance *wisski.WissKI, str stream.IOStream) error
type InstanceAction struct {
NumParams int
var socketInstanceActions = map[string]instanceActionFunc{
"snapshot": func(info *Info, instance *wisski.WissKI, str stream.IOStream) error {
return info.Exporter.MakeExport(
str,
exporter.ExportTask{
Dest: "",
Instance: instance,
HandleInteractive func(info *Info, instance *wisski.WissKI, str stream.IOStream, params ...string) error
HandleResult func(info *Info, instance *wisski.WissKI, params ...string) (value any, err error)
}
StagingOnly: false,
},
)
var socketInstanceActions = map[string]InstanceAction{
"snapshot": {
HandleInteractive: func(info *Info, instance *wisski.WissKI, str stream.IOStream, params ...string) error {
return info.Exporter.MakeExport(
str,
exporter.ExportTask{
Dest: "",
Instance: instance,
StagingOnly: false,
},
)
},
},
"rebuild": func(_ *Info, instance *wisski.WissKI, str stream.IOStream) error {
return instance.Barrel().Build(str, true)
"rebuild": {
HandleInteractive: func(_ *Info, instance *wisski.WissKI, str stream.IOStream, params ...string) error {
return instance.Barrel().Build(str, true)
},
},
"update": func(_ *Info, instance *wisski.WissKI, str stream.IOStream) error {
return instance.Drush().Update(str)
"update": {
HandleInteractive: func(_ *Info, instance *wisski.WissKI, str stream.IOStream, params ...string) error {
return instance.Drush().Update(str)
},
},
"cron": func(_ *Info, instance *wisski.WissKI, str stream.IOStream) error {
return instance.Drush().Cron(str)
"cron": {
HandleInteractive: func(_ *Info, instance *wisski.WissKI, str stream.IOStream, params ...string) error {
return instance.Drush().Cron(str)
},
},
"login": {
NumParams: 1,
HandleResult: func(_ *Info, instance *wisski.WissKI, params ...string) (any, error) {
link, err := instance.Drush().Login(stream.FromNil(), params[0])
return link, err
},
},
}
@ -47,22 +70,40 @@ func (info *Info) serveSocket(conn httpx.WebSocketConnection) {
}
}
func (info *Info) handleInstanceAction(conn httpx.WebSocketConnection, action instanceActionFunc) {
var instanceParamsTimeout = time.Second
func (info *Info) handleInstanceAction(conn httpx.WebSocketConnection, action InstanceAction) {
// read the slug
slug, ok := <-conn.Read()
if !ok {
conn.WriteText("Error reading slug")
<-conn.WriteText("Error reading slug")
return
}
// resolve the instance
instance, err := info.Instances.WissKI(string(slug.Bytes))
if err != nil {
conn.WriteText("Instance not found")
<-conn.WriteText("Instance not found")
return
}
// read the parameters
params := make([]string, action.NumParams)
for i := range params {
select {
case message, ok := <-conn.Read():
if !ok {
<-conn.WriteText("Insufficient parameters")
return
}
params[i] = string(message.Bytes)
case <-time.After(instanceParamsTimeout):
<-conn.WriteText("Timed out reading parameters")
return
}
}
// build a stream
writer := &status.LineBuffer{
Line: func(line string) {
@ -74,13 +115,31 @@ func (info *Info) handleInstanceAction(conn httpx.WebSocketConnection, action in
str := stream.NewIOStream(writer, writer, nil, 0)
// and perform the action
{
err := action(info, instance, str)
// handle the interactive action
if action.HandleInteractive != nil {
err := action.HandleInteractive(info, instance, str, params...)
if err != nil {
str.EPrintln(err)
return
}
str.Println("done")
}
// handle the result computation
if action.HandleResult != nil {
result, err := action.HandleResult(info, instance, params...)
if err != nil {
str.Println("false")
return
}
data, err := json.Marshal(result)
if err != nil {
str.Println("false")
return
}
data = append(data, "\n"...)
str.Println("true")
str.Stdout.Write(data)
}
}

View file

@ -16,13 +16,13 @@ var AssetsComponentsIndex = Assets{
// 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.6d1d8ee0.js"></script><script src="/static/ControlIndex.03d7b00f.js" nomodule="" defer></script>`,
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.a72fc239.js"></script><script src="/static/ControlIndex.75d2a312.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.03d7b00f.js"></script><script type="module" src="/static/ControlIndex.6d1d8ee0.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>`,
Scripts: `<script nomodule="" defer src="/static/ControlIndex.75d2a312.js"></script><script type="module" src="/static/ControlIndex.a72fc239.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

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

@ -46,8 +46,8 @@ function makeTextBuffer(target: HTMLElement, scrollContainer: HTMLElement, size:
return println;
}
const elements = document.getElementsByClassName('remote-action')
Array.from(elements).forEach((element) => {
const remote_action = document.getElementsByClassName('remote-action')
Array.from(remote_action).forEach((element) => {
const action = element.getAttribute('data-action') as string;
const reload = element.hasAttribute('data-force-reload');
const param = element.getAttribute('data-param') as string | undefined;
@ -122,4 +122,59 @@ Array.from(elements).forEach((element) => {
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();
});
})
}