internal/dis/componeont/control: Move paths

This commit is contained in:
Tom Wiesing 2022-10-19 11:31:21 +02:00
parent 52559e4d68
commit e5ddede0c7
No known key found for this signature in database
43 changed files with 8 additions and 8 deletions

View file

@ -0,0 +1,70 @@
package home
import (
"context"
"fmt"
"net/http"
"time"
"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/pkg/lazy"
"github.com/tkw1536/goprogram/stream"
)
type Home struct {
component.Base
Instances *instances.Instances
RefreshInterval time.Duration
redirect lazy.Lazy[*Redirect]
instanceNames lazy.Lazy[map[string]struct{}]
homeBytes lazy.Lazy[[]byte]
}
func (*Home) Routes() []string { return []string{"/"} }
func (home *Home) Handler(route string, context context.Context, io stream.IOStream) (http.Handler, error) {
home.updateRedirect(context, io)
home.updateInstances(context, io)
home.updateRender(context, io)
return home, nil
}
func (home *Home) ServeHTTP(w http.ResponseWriter, r *http.Request) {
slug, ok := home.Config.SlugFromHost(r.Host)
switch {
case !ok:
http.NotFound(w, r)
case slug != "":
home.serveWissKI(w, slug, r)
default:
home.serveRoot(w, r)
}
}
func (home *Home) serveRoot(w http.ResponseWriter, r *http.Request) {
// not the root url => server the fallback
if !(r.URL.Path == "" || r.URL.Path == "/") {
home.redirect.Get(nil).ServeHTTP(w, r)
return
}
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(http.StatusAccepted)
w.Write(home.homeBytes.Get(nil))
}
func (home *Home) serveWissKI(w http.ResponseWriter, slug string, r *http.Request) {
if _, ok := home.instanceNames.Get(nil)[slug]; !ok {
// Get(nil) guaranteed to work by precondition
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "WissKI %q not found\n", slug)
return
}
w.WriteHeader(http.StatusBadGateway)
fmt.Fprintf(w, "WissKI %q is currently offline\n", slug)
}

View file

@ -0,0 +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>
<footer>
Generated at <code>{{ .Time }}</code>
</footer>
{{ JS }}
</body>

View file

@ -0,0 +1,94 @@
package home
import (
"bytes"
"context"
"time"
_ "embed"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/info"
"github.com/FAU-CDI/wisski-distillery/pkg/timex"
"github.com/tkw1536/goprogram/stream"
"golang.org/x/sync/errgroup"
)
func (home *Home) updateInstances(ctx context.Context, io stream.IOStream) {
go func() {
for t := range timex.TickContext(ctx, home.RefreshInterval) {
io.Printf("[%s]: reloading instance list\n", t.Format(time.Stamp))
names, _ := home.instanceMap()
home.instanceNames.Set(names)
}
}()
}
func (home *Home) instanceMap() (map[string]struct{}, error) {
wissKIs, err := home.Instances.All()
if err != nil {
return nil, err
}
names := make(map[string]struct{}, len(wissKIs))
for _, w := range wissKIs {
names[w.Slug] = struct{}{}
}
return names, nil
}
func (home *Home) updateRender(ctx context.Context, io stream.IOStream) {
go func() {
for t := range timex.TickContext(ctx, home.RefreshInterval) {
io.Printf("[%s]: reloading home render\n", t.Format(time.Stamp))
bytes, _ := home.homeRender()
home.homeBytes.Set(bytes)
}
}()
}
//go:embed "home.html"
var homeHTMLStr string
var homeTemplate = static.AssetsHomeHome.MustParse(homeHTMLStr)
func (home *Home) homeRender() ([]byte, error) {
var context HomeContext
// setup a couple of static things
context.Time = time.Now().UTC()
context.SelfRedirect = home.Config.SelfRedirect.String()
// find all the WissKIs
wissKIs, err := home.Instances.All()
if err != nil {
return nil, err
}
context.Instances = make([]info.WissKIInfo, len(wissKIs))
// determine their infos
var eg errgroup.Group
for i, instance := range wissKIs {
i := i
wissKI := instance
eg.Go(func() (err error) {
context.Instances[i], err = wissKI.Info().Information(true)
return
})
}
eg.Wait()
// render the template
var buffer bytes.Buffer
homeTemplate.Execute(&buffer, context)
return buffer.Bytes(), nil
}
type HomeContext struct {
Instances []info.WissKIInfo
Time time.Time
SelfRedirect string
}

View file

@ -0,0 +1,117 @@
package home
import (
"context"
"encoding/json"
"net/http"
"strings"
"time"
"github.com/FAU-CDI/wisski-distillery/pkg/timex"
"github.com/tkw1536/goprogram/stream"
)
func (home *Home) updateRedirect(ctx context.Context, io stream.IOStream) {
go func() {
for t := range timex.TickContext(ctx, home.RefreshInterval) {
io.Printf("[%s]: reloading overrides\n", t.Format(time.Stamp))
redirect, _ := home.loadRedirect()
home.redirect.Set(&redirect)
}
}()
}
func (home *Home) loadRedirect() (redirect Redirect, err error) {
if redirect.Overrides == nil {
redirect.Overrides = make(map[string]string)
}
redirect.Overrides[""] = home.Config.SelfRedirect.String()
redirect.Absolute = false
redirect.Permanent = false
// load the overrides file
overrides, err := home.Environment.Open(home.Config.SelfOverridesFile)
if err != nil {
return redirect, err
}
defer overrides.Close()
// decode the overrides file
if err := json.NewDecoder(overrides).Decode(&redirect.Overrides); err != nil {
return redirect, err
}
// and return!
return redirect, nil
}
// Redirect implements a redirect server that redirects all requests.
// It implements http.Handler.
type Redirect struct {
// Target is the target URL to redirect to.
Target string
// Fallback is used when target is the empty string.
Fallback http.Handler
// Absolute determines if the request path should be appended to the target URL when redirecting.
// By default this path is always appended, set Absolute to true to prevent this.
Absolute bool
// Overrides is a map from paths to URLs that should override the default target.
Overrides map[string]string
// Permanent determines if the redirect responses issued should return
// Permanent Redirect (Status Code 308) or Temporary Redirect (Status Code 307).
Permanent bool
}
// Redirect determines the redirect URL for a specific incoming request
// If it returns the empty string, the fallback is used.
func (redirect Redirect) Redirect(r *http.Request) string {
// if we have an override for this URL, use it immediatly
url := strings.TrimSuffix(r.URL.Path, "/")
if override, ok := redirect.Overrides[url]; ok {
return override
}
if redirect.Target == "" {
return ""
}
// if we are in absolute redirect mode, always return the absolute URL
if redirect.Absolute {
return redirect.Target
}
// return the target + the redirected URL
dest := strings.TrimSuffix(redirect.Target, "/") + r.URL.Path
if len(r.URL.RawQuery) > 0 {
dest += "?" + r.URL.RawQuery
}
return dest
}
// ServeHTTP implements the http.Handler interface and redirects a single request to redirect.Target.
func (redirect Redirect) ServeHTTP(w http.ResponseWriter, r *http.Request) {
dest := redirect.Redirect(r)
if dest == "" {
if redirect.Fallback == nil {
http.NotFound(w, r)
return
}
redirect.Fallback.ServeHTTP(w, r)
return
}
// determine if we are temporary or permanent redirect
status := http.StatusTemporaryRedirect
if redirect.Permanent {
status = http.StatusPermanentRedirect
}
// and do the redirect
http.Redirect(w, r, dest, status)
}

View file

@ -0,0 +1,291 @@
<!DOCTYPE html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Distillery Control Page</title>
{{ CSS }}
</head>
<body>
<header>
<h1 id="top">Distillery Control Page</h1>
<small>Generated at <code class="date">{{ .Time.Format "2006-01-02T15:04:05Z07:00" }}</code></small>
<p>
<a class="pure-button pure-button-primary" href="/dis/index">Control</a>
</p>
</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>
<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>
<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>
<div class="pure-u-1-1">
<h2 id="backups">Backups</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 .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>
<div class="pure-u-1">
<h2 id="instances">Instances</h2>
<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>
<span class="hspace"></span>
</div>
{{range .Instances}}
<div class="pure-u-1 pure-u-xl-1-3">
<div class="wisski {{ if .Running }}running{{ else }}stopped{{ end }}">
<h3>
{{.Slug}}
{{ if not .Running }}&nbsp;<small>not running</small>{{ end }}
</h3>
<p>
<a href="{{.URL}}" target="_blank" rel="noopener noreferrer">{{.URL}}</a><br>
<a class="pure-button" href="/dis/instance/{{.Slug}}">Details</a>
</p>
</div>
</div>
{{end}}
</div>
</main>
<footer>
Generated at <code>{{ .Time }}</code>
</footer>
{{ JS }}
</body>

View file

@ -0,0 +1,312 @@
<!DOCTYPE html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Distillery Control Page - {{ .Info.Slug }}</title>
{{ CSS }}
</head>
<body>
<header>
<h1 id="top">Distillery Control Page - {{ .Info.Slug }}</h1>
<small>Generated at <code class="date">{{ .Time.Format "2006-01-02T15:04:05Z07:00" }}</code></small>
<p>
<a class="pure-button" href="/dis/index">Control</a> &gt;
<a class="pure-button pure-button-primary" href="/dis/instance/{{ .Info.Slug }}">Instance</a>
</p>
</header>
<main>
<div class="pure-g">
<div class="pure-u-1-1">
<h2 id="overview">Info &amp; Status</h2>
</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">
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-2-5">
<div class="padding">
<div class="overflow">
<table class="pure-table pure-table-bordered">
<thead>
<tr>
<th colspan="2">
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>
<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 <br>
<button class="remote-action pure-button pure-button-action" data-action="rebuild" data-param="{{ .Instance.Slug }}" data-buffer="1000" data-force-reload="true">Rebuild</button>
</td>
<td>
<code class="date">{{ .Info.LastRebuild.Format "2006-01-02T15:04:05Z07:00" }}</code>
</td>
</tr>
<tr>
<td>
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 class="date">{{ .Info.LastRebuild.Format "2006-01-02T15:04:05Z07:00" }}</code>
</td>
</tr>
<tr>
<td>
Last Update <br>
<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><br>
(Automatic: <code>{{ .Instance.AutoBlindUpdateEnabled }}</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">
Composer Status
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
???
</td>
<td>
???
</td>
</tr>
</tbody>
</table>
</div>
</div>
-->
</div>
<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>
<th colspan="2">
Pathbuilders
</th>
</tr>
</thead>
<tbody>
{{ range $name, $xml := .Info.Pathbuilders }}
<tr>
<td>
<code>{{ $name }}</code>
</td>
<td>
<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>
</div>
</div>
<div class="pure-u-1-1">
<h2 id="snapshots">Snapshots</h2>
<p>
<button class="remote-action pure-button pure-button-action" data-action="snapshot" data-param="{{ .Instance.Slug }}" data-buffer="1000" data-force-reload="true">Take a snapshot</button>
</p>
</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>
</div>
</div>
</main>
{{ JS }}
</body>

View file

@ -0,0 +1,85 @@
package info
import (
"net/http"
"time"
_ "embed"
"github.com/FAU-CDI/wisski-distillery/internal/config"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
"github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/info"
"golang.org/x/sync/errgroup"
)
//go:embed "html/index.html"
var indexTemplateStr string
var indexTemplate = static.AssetsControlIndex.MustParse(indexTemplateStr)
type indexPageContext struct {
Time time.Time
Config *config.Config
Instances []info.WissKIInfo
TotalCount int
RunningCount int
StoppedCount int
Backups []models.Export
}
func (nfo *Info) indexPageAPI(r *http.Request) (idx indexPageContext, err error) {
var group errgroup.Group
group.Go(func() error {
// list all the instances
all, err := nfo.Instances.All()
if err != nil {
return err
}
// get all of their info!
idx.Instances = make([]info.WissKIInfo, len(all))
for i, instance := range all {
{
i := i
instance := instance
// store the info for this group!
group.Go(func() (err error) {
idx.Instances[i], err = instance.Info().Information(true)
return err
})
}
}
return nil
})
// get the log entries
group.Go(func() (err error) {
idx.Backups, err = nfo.SnapshotsLog.For("")
return
})
// get the static properties
idx.Config = nfo.Config
idx.Time = time.Now().UTC()
group.Wait()
// count how many are running and how many are stopped
for _, i := range idx.Instances {
if i.Running {
idx.RunningCount++
} else {
idx.StoppedCount++
}
}
idx.TotalCount = len(idx.Instances)
return
}

View file

@ -0,0 +1,60 @@
package info
import (
"context"
"net/http"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter/logger"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
"github.com/tkw1536/goprogram/stream"
)
type Info struct {
component.Base
Exporter *exporter.Exporter
Instances *instances.Instances
SnapshotsLog *logger.Logger
}
func (*Info) Routes() []string { return []string{"/dis/"} }
func (info *Info) Handler(route string, context context.Context, io stream.IOStream) (http.Handler, error) {
mux := http.NewServeMux()
// handle everything
mux.HandleFunc(route, func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == route {
http.Redirect(w, r, route+"/index", http.StatusTemporaryRedirect)
return
}
http.NotFound(w, r)
})
// add a handler for the index page
mux.Handle(route+"index", httpx.HTMLHandler[indexPageContext]{
Handler: info.indexPageAPI,
Template: indexTemplate,
})
// add a handler for the instance page
mux.Handle(route+"instance/", httpx.HTMLHandler[instancePageContext]{
Handler: info.instancePageAPI,
Template: instanceTemplate,
})
handler := &httpx.WebSocket{
Context: context,
Fallback: mux,
Handler: info.serveSocket,
}
// ensure that everyone is logged in!
return httpx.BasicAuth(handler, "WissKI Distillery Admin", func(user, pass string) bool {
return user == info.Config.DisAdminUser && pass == info.Config.DisAdminPassword
}), nil
}

View file

@ -0,0 +1,52 @@
package info
import (
_ "embed"
"net/http"
"strings"
"time"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
"github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/info"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
)
//go:embed "html/instance.html"
var instanceTemplateString string
var instanceTemplate = static.AssetsControlInstance.MustParse(instanceTemplateString)
type instancePageContext struct {
Time time.Time
Instance models.Instance
Info info.WissKIInfo
}
func (info *Info) instancePageAPI(r *http.Request) (is instancePageContext, err error) {
// find the slug as the last component of path!
slug := strings.TrimSuffix(r.URL.Path, "/")
slug = slug[strings.LastIndex(slug, "/")+1:]
// find the instance itself!
instance, err := info.Instances.WissKI(slug)
if err == instances.ErrWissKINotFound {
return is, httpx.ErrNotFound
}
if err != nil {
return is, err
}
is.Instance = instance.Instance
// get some more info about the wisski
is.Info, err = instance.Info().Information(false)
if err != nil {
return is, err
}
// current time
is.Time = time.Now().UTC()
return
}

View file

@ -0,0 +1,86 @@
package info
import (
"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"
"github.com/tkw1536/goprogram/status"
"github.com/tkw1536/goprogram/stream"
)
type instanceActionFunc = func(info *Info, instance *wisski.WissKI, str stream.IOStream) error
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,
StagingOnly: false,
},
)
},
"rebuild": func(_ *Info, instance *wisski.WissKI, str stream.IOStream) error {
return instance.Barrel().Build(str, true)
},
"update": func(_ *Info, instance *wisski.WissKI, str stream.IOStream) error {
return instance.Drush().Update(str)
},
"cron": func(_ *Info, instance *wisski.WissKI, str stream.IOStream) error {
return instance.Drush().Cron(str)
},
}
func (info *Info) serveSocket(conn httpx.WebSocketConnection) {
// read the next message to act on
message, ok := <-conn.Read()
if !ok {
return
}
// perform an action if it exists!
if action, ok := socketInstanceActions[string(message.Bytes)]; ok {
info.handleInstanceAction(conn, action)
return
}
}
func (info *Info) handleInstanceAction(conn httpx.WebSocketConnection, action instanceActionFunc) {
// read the slug
slug, ok := <-conn.Read()
if !ok {
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")
return
}
// build a stream
writer := &status.LineBuffer{
Line: func(line string) {
<-conn.WriteText(line)
},
FlushLineOnClose: true,
}
defer writer.Close()
str := stream.NewIOStream(writer, writer, nil, 0)
// and perform the action
{
err := action(info, instance, str)
if err != nil {
str.EPrintln(err)
return
}
str.Println("done")
}
}

View file

@ -0,0 +1,47 @@
package static
import (
"encoding/json"
"html/template"
)
// Assets represents a group of assets to be included inside a template.
//
// Assets are generated using the 'build.mjs' script.
// The script is called using 'go:generate', which stores variables in the form of 'Assets{{Name}}' inside this package.
//
// The build script roughly works as follows:
// - Delete any previously generated distribution directory.
// - Bundle the entrypoint sources under 'src/entry/{{Name}}/index.{ts,css}' together with the base './src/base/index.{ts,css}'
// - Store the output inside the 'dist' directory
// - Generate new constants of the form {{Name}}
//
// Each asset group should be registered as a parameter to the 'go:generate' line.
type Assets struct {
Scripts string // <script> tags inserted by the asset
Styles string // <link> tags inserted by the asset
}
//go:generate node build.mjs HomeHome ControlIndex ControlInstance
// MustParse parses a new template from the given source
// and registers the Asset functions to it.
// See [Assets.RegisterFuncs].
func (assets *Assets) MustParse(value string) *template.Template {
return template.Must(assets.RegisterFuncs(template.New("")).Parse(value))
}
// 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
},
})
}

View file

@ -0,0 +1,21 @@
package static
// This file was automatically generated. Do not edit.
// 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.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.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.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">`,
}

View file

@ -0,0 +1,127 @@
import { Parcel } from "@parcel/core"
import { mkdir, rm, writeFile, readFile, unlink, rmdir, } from "fs/promises"
import { join } from "path"
import { parse as parseHTML } from 'node-html-parser';
//
// PARAMETERS
//
const ENTRYPOINTS = process.argv.slice(2)
const ENTRY_DIR = join('.', '.entry-cache') // directory to place entries into
const DIST_DIR = join('.', 'dist')
const PUBLIC_DIR = '/static/'
const DEST_PACKAGE = process.env.GOPACKAGE ?? 'static'
const DEST_FILE = (() => {
const source = (process.env.GOFILE ?? 'assets.go')
const base = source.substring(0, source.length - '.go'.length)
return base + '_dist.go'
})()
//
// PREPARE DIRECTORIES
//
process.stdout.write('Preparing directories ...')
await Promise.all([
mkdir(ENTRY_DIR, { recursive: true }),
rm(DIST_DIR, { recursive: true, force: true })
])
console.log(' Done.')
//
// WRITE ENTRY POINTS
//
process.stdout.write('Collecting entry points ')
const entries = await Promise.all(ENTRYPOINTS.map(async (name) => {
const entry = {
'name': name,
'bundleName': name + '.html',
'src': join(ENTRY_DIR, name + '.html'),
}
const content = `
<script type='module' src='../src/base/index.ts'></script>
<script type='module' src='../src/entry/${name}/index.ts'></script>
<link rel='stylesheet' href='../src/entry/${name}/index.css'>
`;
await writeFile(entry.src, content)
process.stdout.write('.')
return entry;
}))
console.log(' Done.')
//
// BUNDLEING
//
process.stdout.write('Bundleing assets ...')
const bundler = new Parcel({
entries: entries.map(e => e.src),
defaultConfig: '@parcel/config-default',
shouldDisableCache: true,
shouldContentHash: true,
defaultTargetOptions: {
shouldOptimize: true,
shouldScopeHoist: true,
sourceMaps: false,
distDir: DIST_DIR,
publicUrl: PUBLIC_DIR,
engines: {
browsers: "defaults",
}
}
});
const { bundleGraph } = await bundler.run()
console.log(' Done.')
//
// FIND ASSETS IN OUTPUT
//
process.stdout.write('Find Assets in Output ')
const bundles = bundleGraph.getBundles()
const assets = await Promise.all(entries.map(async (entry) => {
const mainBundle = bundles.find(b => b.name === entry.bundleName)
if (mainBundle === undefined) throw new Error('Unable to find bundle for ' + entry.name)
// read, then delete the generated output file
const { filePath } = mainBundle
const html = parseHTML(await readFile(filePath))
await unlink(filePath)
const scripts = html.querySelectorAll('script').map(script => script.outerHTML).join('')
const links = html.querySelectorAll('link').map(link => link.outerHTML).join('')
process.stdout.write('.')
return { ...entry, scripts, links }
}))
console.log(' Done.')
//
// GENERATE GO
//
process.stdout.write(`Writing ${DEST_FILE} ...`)
const goAssets = assets.map(({ name, scripts, links }) => {
return `
// Assets${name} contains assets for the '${name}' entrypoint.
var Assets${name} = Assets{
\tScripts: \`${scripts}\`,
\tStyles: \`${links}\`,\t
}`.trim()
}).join('\n\n')
const goSource = `package ${DEST_PACKAGE}
// This file was automatically generated. Do not edit.
${goAssets}
`;
await writeFile(DEST_FILE, goSource)
console.log(' Done.')

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
.wisski{padding:1em}.wisski h3{padding:0}.wisski a.pure-button{float:right;position:relative;bottom:1em}.wisski.running{background-color:#9ada07}.wisski.stopped{background-color:#ff7a7a}

View file

@ -0,0 +1 @@
.modal-terminal{width:66vw;height:66vh;background-color:#fff;background-clip:padding-box;-webkit-background-clip:padding-box;z-index:1000;border:17vh solid #000c;border-width:17vh 17vw;margin:-17vh -17vw;position:fixed;top:17vh;left:17vw;overflow:auto}.modal-terminal button{z-index:1001;position:fixed;top:17vh;right:17vw}.modal-terminal pre,.modal-terminal button{margin:5px}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},n={},r={},o=e.parcelRequireafa4;null==o&&((o=function(e){if(e in n)return n[e].exports;if(e in r){var o=r[e];delete r[e];var i={id:e,exports:{}};return n[e]=i,o.call(i.exports,i,i.exports),i.exports}var l=new Error("Cannot find module '"+e+"'");throw l.code="MODULE_NOT_FOUND",l}).register=function(e,n){r[e]=n},e.parcelRequireafa4=o),o("gJkWt");

View file

@ -0,0 +1 @@
!function(){var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},n={},o={},r=e.parcelRequireafa4;null==r&&((r=function(e){if(e in n)return n[e].exports;if(e in o){var r=o[e];delete o[e];var i={id:e,exports:{}};return n[e]=i,r.call(i.exports,i,i.exports),i.exports}var f=new Error("Cannot find module '"+e+"'");throw f.code="MODULE_NOT_FOUND",f}).register=function(e,n){o[e]=n},e.parcelRequireafa4=r),r("8s4Fe")}();

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,11 @@
{
"name": "wisski-distillery-frontend",
"version": "1.0.0",
"license": "AGPL-3.0-only",
"private": true,
"dependencies": {
"dayjs": "^1.11.5",
"node-html-parser": "^6.1.1",
"parcel": "^2.7.0"
}
}

View file

@ -0,0 +1,50 @@
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,
.overflow table th{
padding: .5em .5em;
}
.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: 1em;
}
.pure-button-action {
background-color: rgb(66, 184, 221) !important;
}
.pure-button-success {
background-color: rgb(28, 184, 65) !important;
}

View file

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

View file

@ -0,0 +1,21 @@
.wisski {
padding: 1em;
}
.wisski h3 {
padding: 0;
}
.wisski a.pure-button {
float: right;
position: relative;
bottom: 1em;
}
.wisski.running {
background-color: #9ADA07;
}
.wisski.stopped {
background-color: #ff7a7a;
}

View file

@ -0,0 +1,2 @@
import "~/src/lib/remote"
import "~/src/lib/highlight"

View file

@ -0,0 +1 @@
@import url("../ControlIndex/index.css")

View file

@ -0,0 +1 @@
import "../ControlIndex/index"

View file

@ -0,0 +1,22 @@
.header-link {
position: relative;
left: 0.5em;
opacity: 0;
font-size: 0.8em;
transition: opacity 0.2s ease-in-out 0.1s;
-webkit-transition: opacity 0.2s ease-in-out 0.1s;
-moz-transition: opacity 0.2s ease-in-out 0.1s;
-ms-transition: opacity 0.2s ease-in-out 0.1s;
text-decoration: none;
}
h2:hover .header-link,
h3:hover .header-link,
h4:hover .header-link,
h5:hover .header-link,
h6:hover .header-link {
color: black !important;
opacity: 1;
}

View file

@ -0,0 +1,21 @@
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: string) => {
const anchor = document.createElement("a")
anchor.className = "header-link"
anchor.href = "#" + id
anchor.innerHTML = "#"
return anchor
}
const linkifyAnchors = (level: number) => {
const headers = document.getElementsByTagName("h" + level);
Array.from(headers).forEach((header) => {
if (typeof header.id === "undefined" || header.id === "") return
header.appendChild(anchorForId(header.id))
})
}
// linkify all the anchors from 1 ... 6
(new Array(6)).fill(0).forEach((_, i) => linkifyAnchors(i + 1))

View file

@ -0,0 +1,51 @@
import dayjs from "dayjs"
const types: Record<string, (element: HTMLElement) => HTMLElement | string> = {
"date": (element) => {
return dayjs(element.innerText).format('YYYY-MM-DD HH:mm:ss ([UTC]Z)')
},
"path": (element) => {
const text = element.innerText.split("/");
return text[text.length - 1];
},
"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")
link.className = "pure-button"
const title = filename + ' (' + blob.size + ' Bytes)';
link.append(title)
return link
}
}
const make_download_link = (filename: string, content: string, type: string): [HTMLAnchorElement, Blob] => {
const blob = new Blob(
[content],
{
type: type ?? "text/plain"
}
);
const link = document.createElement("a")
link.target = "_blank"
link.download = filename
link.href = URL.createObjectURL(blob)
return [link, blob]
}
Object.keys(types).forEach(key => {
const f = types[key];
const elements = document.querySelectorAll("code." + key) as NodeListOf<HTMLElement>
elements.forEach(element => {
const newElement = f(element)
if (typeof newElement === 'string') {
element.innerHTML = ""
element.appendChild(document.createTextNode(newElement))
return
}
element.parentNode!.replaceChild(newElement, element)
})
})

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,125 @@
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 reload = element.hasAttribute('data-force-reload');
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 pure-button-success"
button.append(reload ? "Close & Reload" : "Close")
button.addEventListener('click', function (event) {
event.preventDefault();
if (reload) {
button.setAttribute('disabled', 'disabled');
target.innerHTML = 'Reloading page ...'
location.reload()
return;
}
modal.parentNode?.removeChild(modal);
})
const onbeforeunload = window.onbeforeunload;
window.onbeforeunload = () => "A remote session is in progress. Are you sure you want to leave?";
// when closing, add a button to the modal!
let didClose = false
const close = function () {
if (didClose) return
didClose = true
window.onbeforeunload = onbeforeunload;
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

@ -0,0 +1,11 @@
export default function connectSocket(onOpen: (socket: WebSocket) => void, onData: (data: any) => void): Promise<CloseEvent> {
return new Promise((rs, rj) => {
const socket = new WebSocket(location.href.replace('http', 'ws'));
socket.onclose = rs;
socket.onerror = rj;
socket.onmessage = (ev) => onData(ev.data)
socket.onopen = () => onOpen(socket);
});
}

View file

@ -0,0 +1,32 @@
// Package static implements serving of fully static resources
package static
import (
"context"
"embed"
"io/fs"
"net/http"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/tkw1536/goprogram/stream"
)
type Static struct {
component.Base
}
func (*Static) Routes() []string { return []string{"/static/"} }
//go:embed dist
var staticFS embed.FS
func (static *Static) Handler(route string, context context.Context, io stream.IOStream) (http.Handler, error) {
// take the filesystem
fs, err := fs.Sub(staticFS, "dist")
if err != nil {
return nil, err
}
// 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. */
}
}

File diff suppressed because it is too large Load diff