dis: Rework styling and build procedure

This commit is contained in:
Tom Wiesing 2022-10-14 16:48:12 +02:00
parent 1e1d1a3cad
commit cdc7d69ad9
No known key found for this signature in database
51 changed files with 1251 additions and 339 deletions

View file

@ -1,28 +1,40 @@
<!DOCTYPE html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WissKI Distillery</title>
{{ CSS }}
</head>
<body>
<header>
<h1>WissKI Distillery</h1>
</header>
<main>
<div class="pure-g">
<div class="pure-u-1">
<p>
For more information, see <a href="{{ .SelfRedirect }}">{{ .SelfRedirect }}</a>.
</p>
</div>
<div class="pure-u-1">
<h2>WissKIs on this Distillery</h2>
</div>
{{range .Instances}}
{{ if .Running }}
<div class="pure-u-1-3">
<h3>{{.Slug}}</h3>
<p>
<a href="{{.URL}}" target="_blank" rel="noopener noreferrer">{{.URL}}</a><br>
</p>
</div>
{{ end }}
{{ end }}
</main>
<link rel="stylesheet" href="/static/home/index.css">
<title>WissKI Distillery</title>
<h1>WissKI Distillery</h1>
<p>
For more information, see <a href="{{ .SelfRedirect }}">{{ .SelfRedirect }}</a>.
</p>
<h2>WissKIs on this Distillery</h2>
<div>
{{range .Instances}}
{{ if .Running }}
<h3>{{.Slug}}</h3>
<p>
<a href="{{.URL}}" target="_blank" rel="noopener noreferrer">{{.URL}}</a><br />
</p>
{{ end }}
{{ end }}
</div>
<footer>
Generated at <code>{{ .Time }}</code>
</footer>
<script src="/static/home/index.js"></script>
<footer>
Generated at <code>{{ .Time }}</code>
</footer>
{{ JS }}
</body>

View file

@ -3,12 +3,12 @@ package home
import (
"bytes"
"context"
"text/template"
"time"
_ "embed"
"github.com/FAU-CDI/wisski-distillery/internal/component/instances"
"github.com/FAU-CDI/wisski-distillery/internal/component/static"
"github.com/FAU-CDI/wisski-distillery/pkg/timex"
"github.com/tkw1536/goprogram/stream"
"golang.org/x/sync/errgroup"
@ -47,7 +47,7 @@ func (home *Home) updateRender(ctx context.Context, io stream.IOStream) {
//go:embed "home.html"
var homeHTMLStr string
var homeTemplate = template.Must(template.New("home.html").Parse(homeHTMLStr))
var homeTemplate = static.EntryHome.MustParse(homeHTMLStr)
func (home *Home) homeRender() ([]byte, error) {
var context HomeContext

View file

@ -1,80 +1,286 @@
<!DOCTYPE html>
<link rel="stylesheet" href="/static/control/index.css">
<title>Distillery Status Page</title>
<h1 id="top">Distillery Status Page</h1>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Distillery Control Page</title>
{{ CSS }}
</head>
<h2 id="overview">Overview</h2>
<body>
<header>
<h1 id="top">Distillery Control Page</h1>
</header>
<main>
<div class="pure-g">
<div class="pure-u-1-1">
<h2 id="overview">Distillery Configuration</h2>
</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">
Domains
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
Primary
</td>
<td>
<code>{{.Config.DefaultDomain}}</code>
</td>
</tr>
<tr>
<td>
Extra
</td>
<td>
{{ range .Config.SelfExtraDomains }}
<code>{{.}}</code><br />
{{ end }}
</td>
</tr>
<tr>
<td>
Email <small>(HTTPS)</small>
</td>
<td>
<code>{{.Config.CertbotEmail}}</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">
Database Settings
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
MySQL User Prefix
</td>
<td>
<code>{{.Config.MysqlUserPrefix}}</code>
</td>
</tr>
<tr>
<td>
MySQL Database Prefix
</td>
<td>
<code>{{.Config.MysqlDatabasePrefix}}</code>
</td>
</tr>
<tr>
<td>
GraphDB User Prefix
</td>
<td>
<code>{{.Config.GraphDBUserPrefix}}</code>
</td>
</tr>
<tr>
<td>
GraphDB Database Prefix
</td>
<td>
<code>{{.Config.GraphDBRepoPrefix}}</code>
</td>
</tr>
<tr>
<td>
Bookkeeping Database
</td>
<td>
<code>{{.Config.DistilleryDatabase}}</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">
Directory Settings
</th>
</tr>
</thead>
<p>
<b>Domain:</b> <code>{{.Config.DefaultDomain}}</code> <br />
<b>Legacy Domain(s):</b> <code>{{.Config.SelfExtraDomains}}</code><br />
<b>HTTPS Email:</b> <code>{{.Config.CertbotEmail}}</code><br />
<b>Docker Network Name:</b> <code>{{.Config.DockerNetworkName}}</code><br />
<hr />
<b>Homepage Redirect:</b><a href="{{.Config.SelfRedirect}}" target="_blank" rel="noopener noreferrer">{{.Config.SelfRedirect}}</a><br />
<hr />
<b>Backup Age:</b> <code>{{.Config.MaxBackupAge}}</code> Day(s)<br />
<hr />
<b>Base Directory:</b> <code>{{.Config.DeployRoot}}</code><br />
<b>Configuration File:</b> <code>{{.Config.ConfigPath}}</code><br />
<b>Authorized_Keys File:</b> <code>{{.Config.GlobalAuthorizedKeysFile}}</code><br />
<hr />
<b>MySQL User Prefix:</b> <code>{{.Config.MysqlUserPrefix}}</code><br />
<b>MySQL Database Prefix:</b> <code>{{.Config.MysqlDatabasePrefix}}</code><br />
<b>GraphDB User Prefix:</b> <code>{{.Config.GraphDBUserPrefix}}</code><br />
<b>GraphDB Database Prefix:</b> <code>{{.Config.GraphDBRepoPrefix}}</code><br />
<hr />
<b>Bookkeeping Database:</b> <code>{{.Config.DistilleryDatabase}}</code><br />
<hr />
<b>Backups:</b>
<table>
<thead>
<tr>
<th>Path</th>
<th>Created</th>
<th>Packed</th>
</tr>
</thead>
<tbody>
{{ range .Backups }}
<tr>
<td>
<code class="path">{{ .Path }}</code>
</td>
<td>
<code class="date">{{ .Created.Format "2006-01-02T15:04:05Z07:00" }}</code>
</td>
<td>
{{ .Packed }}
</td>
</tr>
{{ end}}
</tbody>
</table>
</p>
<tbody>
<tr>
<td>
<code>root</code>
</td>
<td>
<code>{{.Config.DeployRoot}}</code>
</td>
</tr>
<tr>
<td>
<code>config</code>
</td>
<td>
<code>{{.Config.ConfigPath}}</code>
</td>
</tr>
<tr>
<td>
<code>authorized_keys</code>
</td>
<td>
<code>{{.Config.GlobalAuthorizedKeysFile}}</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">
Misc Settings
</th>
</tr>
</thead>
<h2 id="instances">Instances</h2>
<tbody>
<tr>
<td>
Homepage
</td>
<td>
<a href="{{.Config.SelfRedirect}}" target="_blank" rel="noopener noreferrer">{{.Config.SelfRedirect}}</a>
</td>
</tr>
<tr>
<td>
Docker Network Name
</td>
<td>
<code>{{.Config.DockerNetworkName}}</code>
</td>
</tr>
<tr>
<td>
Backup Age
</td>
<td>
<code>{{.Config.MaxBackupAge}}</code> Day(s)
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<p>
<code>{{ .TotalCount }}</code> instance(s) = <code>{{ .RunningCount }}</code> running + <code>{{ .StoppedCount }}</code> stopped<br />
</p>
<div class="pure-u-1-1">
<h2 id="backups">Backups</h2>
</div>
{{range .Instances}}
<div class="wisski {{ if .Running }}running{{ else }}stopped{{ end }}">
<h3 id="instance-{{.Slug}}">{{.Slug}}{{ if not .Running }}&nbsp;<small>not running</small>{{ end }}</h3>
<p>
<a href="{{.URL}}" target="_blank" rel="noopener noreferrer">{{.URL}}</a><br />
<div class="pure-u-1">
<table class="pure-table pure-table-bordered padding">
<thead>
<tr>
<th>Path</th>
<th>Created</th>
<th>Packed</th>
</tr>
</thead>
<tbody>
{{ range .Backups }}
<tr>
<td>
<code class="path">{{ .Path }}</code>
</td>
<td>
<code class="date">{{ .Created.Format "2006-01-02T15:04:05Z07:00" }}</code>
</td>
<td>
{{ .Packed }}
</td>
</tr>
{{ end}}
</tbody>
</table>
</div>
<small>
<a href="/dis/instance/{{.Slug}}">More Details</a>
</small>
</p>
</div>
{{end}}
<div class="pure-u-1">
<h2 id="instances">Instances</h2>
<footer>
Generated at <code>{{ .Time }}</code>
</footer>
<table class="pure-table pure-table-bordered padding">
<thead>
<tr>
<th>Total</th>
<th>Running</th>
<th>Stopped</th>
</tr>
</thead>
<tbody>
<tr>
<td>
{{ .TotalCount }}
</td>
<td>
{{ .RunningCount }}
</td>
<td>
{{ .StoppedCount }}
</td>
</tr>
</tbody>
</table>
<script src="/static/control/index.js"></script>
<span class="hspace"></span>
</div>
{{range .Instances}}
<div class="pure-u-1 pure-u-xl-1-3">
<div class="padding wisski {{ if .Running }}running{{ else }}stopped{{ end }}">
<h3 id="instance-{{.Slug}}">{{.Slug}}{{ if not .Running }}&nbsp;<small>not running</small>{{ end }}</h3>
<p>
<a href="{{.URL}}" target="_blank" rel="noopener noreferrer">{{.URL}}</a><br>
<small>
<a href="/dis/instance/{{.Slug}}">More Details</a>
</small>
</p>
</div>
</div>
{{end}}
</div>
</main>
<footer>
Generated at <code>{{ .Time }}</code>
</footer>
{{ JS }}
</body>

View file

@ -1,85 +1,300 @@
<!DOCTYPE html>
<link rel="stylesheet" href="/static/control/index.css">
<title>Distillery Status Page - {{ .Info.Slug }}</title>
<h1 id="top">Distillery Status Page - {{ .Info.Slug }}</h1>
<p>
<a href=".">Reload</a>
<a href="/dis/index">Back to index</a>
</p>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Distillery Control Page - {{ .Info.Slug }}</title>
{{ CSS }}
</head>
<div>
<b>Slug:</b> <code>{{ .Info.Slug }}</code> <br />
<b>URL:</b> <a href="{{ .Info.URL }}" target="_blank" rel="noopener noreferrer">{{ .Info.URL }}</a> <br />
<hr />
<b>URI Prefixes: </b>
<ul>
{{ range .Info.Prefixes }}
<li><code>{{ . }}</code></li>
{{ end}}
</ul>
<b>Excluded from Resolver:</b> <code>{{ .Info.NoPrefixes }}</code><br />
<hr />
<b>Running:</b> <code>{{ .Info.Running }}</code> <br />
<b>Locked:</b> <code>{{ .Info.Locked }}</code> <br />
<!-- <b>OwnerEmail:</b> <code>{{ .Instance.OwnerEmail }}</code> <br /> -->
<hr />
<b>Created:</b> <code class="date">{{ .Instance.Created.Format "2006-01-02T15:04:05Z07:00" }}</code> <br />
<b>Last Rebuild:</b> <code class="date">{{ .Info.LastRebuild.Format "2006-01-02T15:04:05Z07:00" }}</code> <br />
<hr>
<p>
<button class="remote-action" data-action="rebuild" data-param="{{ .Instance.Slug }}" data-target="#rebuild" data-buffer="20">Rebuild</button>
<pre class="remote-action-out" id="rebuild"></pre>
</p>
<hr />
<b>FilesystemBase:</b> <code>{{ .Instance.FilesystemBase }}</code> <br />
<b>AutoBlindUpdateEnabled:</b> <code>{{ .Instance.AutoBlindUpdateEnabled }}</code> <br />
<hr />
<b>Pathbuilders:</b> <code class="pathbuilders">{{ .Info.Pathbuilders }}</code><br />
<script>window.pathbuilders={{ .Info.Pathbuilders }};</script>
<hr />
<b>SqlDatabase:</b> <code>{{ .Instance.SqlDatabase }}</code> <br />
<b>SqlUsername:</b> <code>{{ .Instance.SqlUsername }}</code> <br />
<hr />
<b>GraphDBRepository:</b> <code>{{ .Instance.GraphDBRepository }}</code> <br />
<b>GraphDBUsername:</b> <code>{{ .Instance.GraphDBUsername }}</code> <br />
<hr />
<b>Snapshots:</b>
<table>
<thead>
<tr>
<th>Path</th>
<th>Created</th>
<th>Packed</th>
</tr>
</thead>
<tbody>
{{ range .Info.Snapshots }}
<tr>
<td>
<code class="path">{{ .Path }}</code>
</td>
<td>
<code class="date">{{ .Created.Format "2006-01-02T15:04:05Z07:00" }}</code>
</td>
<td>
{{ .Packed }}
</td>
</tr>
{{ end }}
</tbody>
</table>
<hr />
<p>
<button class="remote-action" data-action="snapshot" data-param="{{ .Instance.Slug }}" data-target="#snapshot" data-buffer="20">Take a snapshot</button>
<pre class="remote-action-out" id="snapshot"></pre>
</p>
</div>
<body>
<header>
<h1 id="top">Distillery Control Page - {{ .Info.Slug }}</h1>
<p>
<a href="/dis/instance/{{ .Info.Slug }}">Reload</a>
<a href="/dis/index">Back to index</a>
</p>
</header>
<main>
<div class="pure-g">
<div class="pure-u-1-1">
<h2 id="overview">Instance Overview</h2>
</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">
Overview
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
Slug
</td>
<td>
<code>{{ .Info.Slug }}</code>
</td>
</tr>
<tr>
<td>
URL
</td>
<td>
<a href="{{ .Info.URL }}" target="_blank" rel="noopener noreferrer">{{ .Info.URL }}</a>
</td>
</tr>
<tr>
<td>
Running
</td>
<td>
<code>{{ .Info.Running }}</code>
</td>
</tr>
<tr>
<td>
Locked
</td>
<td>
<code>{{ .Info.Locked }}</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">
Build
<button class="remote-action" data-action="rebuild" data-param="{{ .Instance.Slug }}" data-buffer="1000">Rebuild</button>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
Created
</td>
<td>
<code class="date">{{ .Instance.Created.Format "2006-01-02T15:04:05Z07:00" }}</code>
</td>
</tr>
<tr>
<td>
Last Rebuild
</td>
<td>
<code class="date">{{ .Info.LastRebuild.Format "2006-01-02T15:04:05Z07:00" }}</code>
</td>
</tr>
<tr>
<td>
Automatic Updates
</td>
<td>
<code>{{ .Instance.AutoBlindUpdateEnabled }}</code>
</td>
</tr>
</tbody>
</table>
</div>
<p>
<pre class="remote-action-out" id="rebuild"></pre>
</p>
</div>
</div>
<footer>
Generated at <code>{{ .Time }}</code>
</footer>
<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">
Resolver
</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>
<script src="/static/control/index.js"></script>
<tbody>
<tr>
<td>
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>
<tr>
<td>
Filesystem Base
</td>
<td>
<code>{{ .Instance.FilesystemBase }}</code>
<script>window.pathbuilders = {{ .Info.Pathbuilders }};</script>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="pure-u-1-1">
<h2 id="snapshots">Snapshots</h2>
</div>
<div class="pure-u-1">
<table class="pure-table pure-table-bordered padding">
<thead>
<tr>
<th>Path</th>
<th>Created</th>
<th>Packed</th>
</tr>
</thead>
<tbody>
{{ range .Info.Snapshots }}
<tr>
<td>
<code class="path">{{ .Path }}</code>
</td>
<td>
<code class="date">{{ .Created.Format "2006-01-02T15:04:05Z07:00" }}</code>
</td>
<td>
{{ .Packed }}
</td>
</tr>
{{ end}}
</tbody>
</table>
</div>
<p>
<button class="remote-action" data-action="snapshot" data-param="{{ .Instance.Slug }}" data-buffer="1000">Take a snapshot</button>
</p>
</div>
</div>
</main>
<footer>
Generated at <code>{{ .Time }}</code>
</footer>
{{ JS }}
</body>

View file

@ -1,13 +1,13 @@
package info
import (
"html/template"
"net/http"
"time"
_ "embed"
"github.com/FAU-CDI/wisski-distillery/internal/component/instances"
"github.com/FAU-CDI/wisski-distillery/internal/component/static"
"github.com/FAU-CDI/wisski-distillery/internal/config"
"github.com/FAU-CDI/wisski-distillery/internal/models"
"golang.org/x/sync/errgroup"
@ -15,7 +15,7 @@ import (
//go:embed "html/index.html"
var indexTemplateStr string
var indexTemplate = template.Must(template.New("index.html").Parse(indexTemplateStr))
var indexTemplate = static.EntryControlIndex.MustParse(indexTemplateStr)
type indexPageContext struct {
Time time.Time

View file

@ -2,19 +2,19 @@ package info
import (
_ "embed"
"html/template"
"net/http"
"strings"
"time"
"github.com/FAU-CDI/wisski-distillery/internal/component/instances"
"github.com/FAU-CDI/wisski-distillery/internal/component/static"
"github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
)
//go:embed "html/instance.html"
var instanceTemplateString string
var instanceTemplate = template.Must(template.New("instance.html").Parse(instanceTemplateString))
var instanceTemplate = static.EntryControlInstance.MustParse(instanceTemplateString)
type instancePageContext struct {
Time time.Time

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
html{color:#1a1a1a;background-color:#fdfdfd;font-family:Roboto;font-size:20px;line-height:1.5}body{max-width:36em;-webkit-hyphens:auto;hyphens:auto;overflow-wrap:break-word;text-rendering:optimizelegibility;font-kerning:normal;margin:0 auto;padding:50px}@media (max-width:600px){body{padding:1em;font-size:.9em}}h1{margin-top:1.4em}h2,h3{margin-top:1em}code{color:#00f;font-family:Roboto Mono}p{text-align:justify;margin:1em 0}a,a:visited{color:#1a1a1a}footer{text-align:center;border-top:1px solid #1a1a1a;font-size:small}.header-link{opacity:0;font-size:.8em;text-decoration:none;transition:opacity .2s ease-in-out .1s;position:relative;left:.5em}h2:hover .header-link,h3:hover .header-link,h4:hover .header-link,h5:hover .header-link,h6:hover .header-link{opacity:1}.wisski{padding-left:5px}.wisski.running{background-color:#9ada07}.wisski.stopped{background-color:#ff7a7a}.remote-action-out{font-size:small}

View file

@ -0,0 +1 @@
<link rel="stylesheet" href="/static/control/index.a0bd71f2.css"><script type="module" src="/static/control/index.a27ed337.js"></script>

View file

@ -1 +0,0 @@
(()=>{const e=e=>{const t=document.getElementsByTagName("h"+e);Array.from(t).forEach((e=>{void 0!==e.id&&""!==e.id&&e.appendChild((e=>{const t=document.createElement("a");return t.className="header-link",t.href="#"+e,t.innerHTML="#",t})(e.id))}))};new Array(6).fill(0).forEach(((t,n)=>e(n+1)));const t={date:e=>new Date(e.innerText).toISOString(),path:e=>{const t=e.innerText.split("/");return t[t.length-1]},pathbuilders:()=>{const e=window.pathbuilders,t=document.createElement("span");let o=!1;if(Object.keys(e).forEach((r=>{o=!0;const a=r+".xml",c=e[r];t.append(n(a,r,c,"application/xml")),t.append(document.createTextNode(" "))})),!o)return"(none)";const r=document.createElement("small");return r.append(document.createTextNode("(click to download)")),t.append(r),t}},n=(e,t,n,o)=>{const r=new Blob([n],{type:o??"text/plain"}),a=document.createElement("a");return a.target="_blank",a.download=e,a.href=URL.createObjectURL(r),a.append(document.createTextNode(t)),a};Object.keys(t).forEach((e=>{const n=t[e];document.querySelectorAll("code."+e).forEach((e=>{const t=n(e);if("string"==typeof t)return e.innerHTML="",void e.appendChild(document.createTextNode(t));e.parentNode.replaceChild(t,e)}))}));const o=document.getElementsByClassName("remote-action");Array.from(o).forEach((e=>{const t=e.getAttribute("data-action"),n=e.getAttribute("data-param"),o=document.querySelector(e.getAttribute("data-target")),r=function(){const t=parseInt(e.getAttribute("data-buffer")??"",10)??0;return isFinite(t)&&t>0?t:0}();let a=!1;e.addEventListener("click",(function(c){if(c.preventDefault(),a)return;a=!0,e.setAttribute("disabled","disabled");const d=function(){e.removeAttribute("disabled"),a=!1};o.innerText="";const i=[],s=function(e){0!==r?(i.push(e),i.length>r&&i.splice(0,i.length-r),o.innerText=i.join("\n")):o.innerText+=e+"\n"};var l,u;s("Connecting ..."),(l=e=>{s("Connected"),e.send(t),"string"==typeof n&&e.send(n)},u=e=>{s(e)},new Promise(((e,t)=>{const n=new WebSocket(location.href.replace("http","ws"));n.onclose=e,n.onerror=t,n.onmessage=e=>u(e.data),n.onopen=()=>l(n)}))).then((()=>{s("Connection closed.\n"),d()})).catch((()=>{s("Connection errored.\n"),d()}))}))}))})();

View file

@ -0,0 +1 @@
<link rel="stylesheet" href="/static/control/index.a0bd71f2.css"><script type="module" src="/static/control/index.a27ed337.js"></script>

View file

View file

@ -1 +0,0 @@
html{color:#1a1a1a;background-color:#fdfdfd;font-family:Roboto;font-size:20px;line-height:1.5}body{max-width:36em;-webkit-hyphens:auto;hyphens:auto;overflow-wrap:break-word;text-rendering:optimizelegibility;font-kerning:normal;margin:0 auto;padding:50px}@media (max-width:600px){body{padding:1em;font-size:.9em}}h1{margin-top:1.4em}h2,h3{margin-top:1em}code{color:#00f;font-family:Roboto Mono}p{text-align:justify;margin:1em 0}a,a:visited{color:#1a1a1a}footer{text-align:center;border-top:1px solid #1a1a1a;font-size:small}.header-link{opacity:0;font-size:.8em;text-decoration:none;transition:opacity .2s ease-in-out .1s;position:relative;left:.5em}h2:hover .header-link,h3:hover .header-link,h4:hover .header-link,h5:hover .header-link,h6:hover .header-link{opacity:1}

View file

@ -0,0 +1 @@
<link rel="stylesheet" href="/static/control/index.a0bd71f2.css"><script type="module" src="/static/home/index.38d394c2.js"></script>

View file

@ -1 +0,0 @@
(()=>{const e=e=>{const n=document.getElementsByTagName("h"+e);Array.from(n).forEach((e=>{void 0!==e.id&&""!==e.id&&e.appendChild((e=>{const n=document.createElement("a");return n.className="header-link",n.href="#"+e,n.innerHTML="#",n})(e.id))}))};new Array(6).fill(0).forEach(((n,r)=>e(r+1)))})();

View file

@ -0,0 +1,20 @@
package static
import (
"bytes"
"github.com/FAU-CDI/wisski-distillery/pkg/resources"
)
var EntryHome = mustParseResources("dist/home/index.html")
var EntryControlIndex = mustParseResources("dist/control/index.html")
var EntryControlInstance = mustParseResources("dist/control/instance.html")
// mustParseResources loads the resources from the provided files or panic()s
func mustParseResources(path string) resources.Resources {
data, err := distStaticFS.ReadFile(path)
if err != nil {
panic("mustParseResources: Unable to open " + path)
}
return resources.Parse(bytes.NewReader(data))
}

View file

@ -1,21 +1,13 @@
{
"name": "wisski-distillery-frontend",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"private": true,
"dependencies": {
"dayjs": "^1.11.5",
"parcel": "^2.7.0"
},
"scripts": {
"dist": "parcel build --dist-dir dist --target default --public-url /static/ --no-source-maps"
},
"targets": {
"default": {
"source": [
"src/control/index.ts",
"src/home/index.ts"
]
}
"dist": "parcel build --dist-dir dist --public-url /static/ src/entry/*/*.html --no-source-maps"
}
}
}

View file

@ -1,55 +0,0 @@
/* This file is included globally into every frontend page */
html {
line-height: 1.5;
font-family: Roboto;
font-size: 20px;
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
overflow-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 1em;
}
}
h1 {
margin-top: 1.4em;
}
h2,h3 {
margin-top: 1em;
}
code {
font-family: Roboto Mono;
color: blue;
}
p {
margin: 1em 0;
text-align: justify;
}
a, a:visited {
color: #1a1a1a;
}
footer {
border-top: 1px solid #1a1a1a;
font-size: small;
text-align: center;
}

View file

@ -1 +0,0 @@
// This file is included globally into every distillery frontend page

View file

@ -0,0 +1,34 @@
body {
font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}
header, main, footer {
margin: 2em;
}
.padding {
padding: 1em;
}
.overflow {
overflow-x:auto;
}
.overflow table {
width: 100%;
}
.overflow table td:not(:last-child),
.overflow table th:not(:last-child) {
width:1px;
text-align:left;
white-space: nowrap;
}
.overflow table td:last-child,
.overflow table th:last-child {
white-space: nowrap;
}
.hspace {
display: block;
height: 2em;
}

View file

@ -0,0 +1,4 @@
import "purecss/build/pure.css"
import "purecss/build/grids-responsive.css"
import "./index.css"

View file

@ -1,5 +0,0 @@
import '../global.ts';
import './index.css';
import './highlight.ts';
import './remote.ts';

View file

@ -1,62 +0,0 @@
import connectSocket from '../socket/socket';
const elements = document.getElementsByClassName('remote-action')
Array.from(elements).forEach((element) => {
const action = element.getAttribute('data-action') as string;
const param = element.getAttribute('data-param') as string | undefined;
const target = document.querySelector(element.getAttribute('data-target')!) as HTMLElement;
const bufferSize = (function() {
const number = parseInt(element.getAttribute('data-buffer') ?? "", 10) ?? 0;
return (isFinite(number) && number > 0) ? number : 0;
})()
let running = false
element.addEventListener('click', function(ev) {
ev.preventDefault();
// already running
if (running) return
running = true
element.setAttribute('disabled', 'disabled');
const close = function() {
element.removeAttribute('disabled');
running = false;
}
target.innerText = "";
const buffer: Array<string> = [];
const println = function(line: string) {
if(bufferSize === 0) {
target.innerText += line + "\n";
return;
}
buffer.push(line);
if(buffer.length > bufferSize) {
buffer.splice(0, buffer.length - bufferSize)
}
target.innerText = buffer.join("\n");
}
println("Connecting ...")
// connect to the socket and send the action
connectSocket((socket) => {
println("Connected")
socket.send(action);
if (typeof param === 'string') {
socket.send(param);
}
}, (data) => {
println(data);
}).then(() => {
println("Connection closed.\n")
close();
}).catch(() => {
println("Connection errored.\n")
close();
});
});
})

View file

@ -1,5 +1,3 @@
@import '../global.css';
.wisski {
padding-left: 5px;
}
@ -11,7 +9,3 @@
.wisski.stopped {
background-color: #ff7a7a;
}
.remote-action-out {
font-size: small;
}

View file

@ -0,0 +1 @@
<script type="module" src="./index.ts"></script>

View file

@ -0,0 +1,5 @@
import "~/src/base/index"
import "./index.css"
import "~/src/lib/remote"
import "~/src/lib/highlight"

View file

@ -0,0 +1 @@
<script type="module" src="./index.ts"></script>

View file

@ -0,0 +1 @@
<script type="module" src="./index.ts"></script>

View file

@ -0,0 +1 @@
import "~/src/base/index"

View file

@ -1,2 +0,0 @@
@import './base/base.css';
@import './autolink/autolink.css';

View file

@ -1,2 +0,0 @@
import './base/base.ts';
import './autolink/autolink.ts';

View file

@ -1 +0,0 @@
@import '../global.css';

View file

@ -1,2 +0,0 @@
import '../global.ts';
import './index.css';

View file

@ -1,3 +1,5 @@
import "./index.css"
/** Adapted from http://blog.parkermoore.de/2014/08/01/header-anchor-links-in-vanilla-javascript-for-github-pages-and-jekyll/ */
const anchorForId = (id) => {
const anchor = document.createElement("a")

View file

@ -1,7 +1,7 @@
import dayjs from "dayjs"
const types: Record<string, (element: HTMLElement) => HTMLElement | string> = {
"date": (element) => {
return (new Date(element.innerText)).toISOString()
return dayjs(element.innerText).format('YYYY-MM-DD HH:mm:ss ([UTC]Z)')
},
"path": (element) => {
const text = element.innerText.split("/");

View file

@ -0,0 +1,42 @@
.modal-terminal {
width: 66vw;
height: 66vh;
position: fixed;
left: 17vw;
top: 17vh;
background-color: white;
background-clip: padding-box;
-webkit-background-clip: padding-box;
border-left: 17vw solid rgba(0, 0, 0, 0.8);
border-right: 17vw solid rgba(0, 0, 0, 0.8);
margin-left: -17vw;
margin-right: -17vw;
border-top: 17vh solid rgba(0, 0, 0, 0.8);
border-bottom: 17vh solid rgba(0, 0, 0, 0.8);
margin-top: -17vh;
margin-bottom: -17vh;
overflow: auto;
z-index: 1000;
}
.modal-terminal button {
position: fixed;
top: 17vh;
right: 17vw;
z-index: 1001;
}
.modal-terminal pre,
.modal-terminal button
{
margin: 5px;
}

View file

@ -0,0 +1,112 @@
import "./index.css"
import connectSocket from './socket';
type Println = ((line: string, flush?: boolean) => void) & {
paintedFrames: number;
missedFrames: number;
}
/**
* makeTextBuffer returns a println() function that efficiently writes text into target, and keeps at most size elements in the traceback.
* scrollContainer is used to scroll on every painted update.
*/
function makeTextBuffer(target: HTMLElement, scrollContainer: HTMLElement, size: number): Println {
let lastAnimationFrame: number | null = null; // last scheduled animation frame
const buffer: Array<string> = []; // the internal buffer of lines
const paint = () => {
println.paintedFrames++
target.innerText = buffer.join("\n")
scrollContainer.scrollTop = scrollContainer.scrollHeight
lastAnimationFrame = null
}
const println = (line: string, flush?: boolean) => {
// add the line
buffer.push(line)
if (size !== 0 && buffer.length > size) {
buffer.splice(0, buffer.length - size)
}
// and update the browser in the next animation frame
if (lastAnimationFrame !== null) {
println.missedFrames++
window.cancelAnimationFrame(lastAnimationFrame)
}
// force a repaint!
if(flush) return paint();
// schedule an animation frame
lastAnimationFrame = window.requestAnimationFrame(paint);
}
println.paintedFrames = 0;
println.missedFrames = 0;
return println;
}
const elements = document.getElementsByClassName('remote-action')
Array.from(elements).forEach((element) => {
const action = element.getAttribute('data-action') as string;
const param = element.getAttribute('data-param') as string | undefined;
const bufferSize = (function () {
const number = parseInt(element.getAttribute('data-buffer') ?? "", 10) ?? 0;
return (isFinite(number) && number > 0) ? number : 0;
})()
element.addEventListener('click', function (ev) {
ev.preventDefault();
// create a modal dialog and append it to the body
const modal = document.createElement("div")
modal.className = "modal-terminal"
document.body.append(modal)
// create a <pre> to write stuff into
const target = document.createElement("pre")
const println = makeTextBuffer(target, modal, bufferSize)
modal.append(target)
// create a button to eventually close everything
const button = document.createElement("button")
button.className = "pure-button"
button.append("Close")
button.addEventListener('click', function (event) {
event.preventDefault();
modal.parentNode?.removeChild(modal);
})
// when closing, add a button to the modal!
let didClose = false
const close = function () {
if (didClose) return
didClose = true
modal.append(button)
// DEBUG: print terminal stats!
// const quota = (println.paintedFrames / (println.missedFrames + println.paintedFrames)) * 100
// println(`Terminal: painted=${println.paintedFrames} missed=${println.missedFrames} (${quota}%)`, true)
}
println("Connecting ...", true)
// connect to the socket and send the action
connectSocket((socket) => {
println("Connected", true)
socket.send(action);
if (typeof param === 'string') {
socket.send(param);
}
}, (data) => {
println(data);
}).then(() => {
println("Connection closed.", true)
close();
}).catch(() => {
println("Connection errored.", true)
close();
});
});
})

View file

@ -6,8 +6,10 @@ import (
"embed"
"io/fs"
"net/http"
"strings"
"github.com/FAU-CDI/wisski-distillery/internal/component"
"github.com/FAU-CDI/wisski-distillery/pkg/fsx"
"github.com/tkw1536/goprogram/stream"
)
@ -25,6 +27,13 @@ func (static *Static) Handler(route string, context context.Context, io stream.I
return nil, err
}
// censor *.html in the filesystem
fs = fsx.Censor(fs, func(path string) bool {
suffix := "html"
return len(path) >= len(suffix) && strings.EqualFold(path[len(path)-len(suffix):], suffix)
})
// and serve it
return http.StripPrefix(route, http.FileServer(http.FS(fs))), nil
}

View file

@ -0,0 +1,105 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
"paths": {
"~/*": ["./*"],
}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}

View file

@ -891,6 +891,11 @@ csso@^4.2.0:
dependencies:
css-tree "^1.1.2"
dayjs@^1.11.5:
version "1.11.5"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.5.tgz#00e8cc627f231f9499c19b38af49f56dc0ac5e93"
integrity sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==
detect-libc@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"