From 241d376ef45f49d09e6e4791f8748fd23bc15b2f Mon Sep 17 00:00:00 2001 From: William Petit Date: Tue, 16 Jan 2024 09:27:04 +0100 Subject: [PATCH] feat: embed optional apps in player server --- .env.dist | 3 +- cmd/mobile/main.go | 6 +- go.mod | 8 +- go.sum | 4 +- internal/command/player/run.go | 18 ++- modd.conf | 1 + pkg/server/apps.go | 91 +++++++++++++++ pkg/server/apps/home/app.js | 19 +++ pkg/server/apps/home/index.html | 16 +++ pkg/server/apps/home/manifest.json | 10 ++ pkg/server/apps/home/style.css | 23 ++++ pkg/server/apps/lib/style.css | 77 ++++++++++++ pkg/server/apps/remote-control/app.js | 66 +++++++++++ pkg/server/apps/remote-control/index.html | 35 ++++++ pkg/server/apps/remote-control/manifest.json | 10 ++ pkg/server/apps/screen-sharing/app.js | 116 +++++++++++++++++++ pkg/server/apps/screen-sharing/index.html | 18 +++ pkg/server/apps/screen-sharing/manifest.json | 10 ++ pkg/server/http.go | 40 ++++++- pkg/server/options.go | 10 ++ pkg/server/server.go | 5 + pkg/server/templates/idle.html.gotmpl | 16 ++- 22 files changed, 586 insertions(+), 16 deletions(-) create mode 100644 pkg/server/apps.go create mode 100644 pkg/server/apps/home/app.js create mode 100644 pkg/server/apps/home/index.html create mode 100644 pkg/server/apps/home/manifest.json create mode 100644 pkg/server/apps/home/style.css create mode 100644 pkg/server/apps/lib/style.css create mode 100644 pkg/server/apps/remote-control/app.js create mode 100644 pkg/server/apps/remote-control/index.html create mode 100644 pkg/server/apps/remote-control/manifest.json create mode 100644 pkg/server/apps/screen-sharing/app.js create mode 100644 pkg/server/apps/screen-sharing/index.html create mode 100644 pkg/server/apps/screen-sharing/manifest.json diff --git a/.env.dist b/.env.dist index df10f3e..1b55a44 100644 --- a/.env.dist +++ b/.env.dist @@ -1,2 +1,3 @@ ARCAST_DESKTOP_ADDITIONAL_CHROME_ARGS= -ARCAST_DESKTOP_INSTANCE_ID= \ No newline at end of file +ARCAST_DESKTOP_INSTANCE_ID= +ARCAST_DESKTOP_WEB_APPS=true \ No newline at end of file diff --git a/cmd/mobile/main.go b/cmd/mobile/main.go index ca85e5a..b710b6f 100644 --- a/cmd/mobile/main.go +++ b/cmd/mobile/main.go @@ -77,7 +77,11 @@ func main() { logger.Fatal(ctx, "could not retrieve instance id", logger.CapturedE(errors.WithStack(err))) } - server := server.New(browser, server.WithInstanceID(instanceID)) + server := server.New( + browser, + server.WithInstanceID(instanceID), + server.WithWebApps(true), + ) if err := server.Start(); err != nil { logger.Fatal(ctx, "could not start server", logger.CapturedE(errors.WithStack(err))) diff --git a/go.mod b/go.mod index 267657e..39671e3 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,13 @@ go 1.21.4 require ( gioui.org v0.4.1 - git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0 github.com/davecgh/go-spew v1.1.1 github.com/gioui-plugins/gio-plugins v0.0.0-20230625001848-8f18aae6c91c - github.com/google/uuid v1.3.0 + github.com/go-chi/cors v1.2.1 github.com/grandcat/zeroconf v1.0.1-0.20230119201135-e4f60f8407b1 + github.com/jaevor/go-nanoid v1.3.0 github.com/pkg/errors v0.9.1 + github.com/wlynxg/anet v0.0.1 github.com/zserge/lorca v0.1.10 ) @@ -17,6 +18,7 @@ require ( cdr.dev/slog v1.6.1 // indirect gioui.org/cpu v0.0.0-20220412190645-f1e9e8c3b1f7 // indirect gioui.org/shader v1.0.8 // indirect + git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/charmbracelet/lipgloss v0.7.1 // indirect @@ -25,7 +27,6 @@ require ( github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 // indirect github.com/inkeliz/go_inkwasm v0.0.0-20220912074516-049d3472c98a // indirect - github.com/jaevor/go-nanoid v1.3.0 // indirect github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect github.com/jedib0t/go-pretty/v6 v6.4.9 // indirect github.com/leodido/go-urn v1.2.1 // indirect @@ -37,7 +38,6 @@ require ( github.com/muesli/termenv v0.15.2 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/wlynxg/anet v0.0.1 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect go.opentelemetry.io/otel v1.21.0 // indirect go.opentelemetry.io/otel/trace v1.21.0 // indirect diff --git a/go.sum b/go.sum index aa66902..30be749 100644 --- a/go.sum +++ b/go.sum @@ -34,6 +34,8 @@ github.com/gioui-plugins/gio-plugins v0.0.0-20230625001848-8f18aae6c91c h1:naFDa github.com/gioui-plugins/gio-plugins v0.0.0-20230625001848-8f18aae6c91c/go.mod h1:nBuRsi6udr2x6eorarLHtRkoRaWBICt+WzaE7zQXgYY= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= +github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -50,8 +52,6 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grandcat/zeroconf v1.0.1-0.20230119201135-e4f60f8407b1 h1:cNb52t5fkWv8ZiicKWnc2eZnhsCCoH7WmRBMIbMp04Q= github.com/grandcat/zeroconf v1.0.1-0.20230119201135-e4f60f8407b1/go.mod h1:I6CSXU4zCGL08JOk9NbcT0ofAgnIkS/fVXbYzfSoDic= github.com/inkeliz/go_inkwasm v0.0.0-20220912074516-049d3472c98a h1:uZklbtdSPrDL/d1EUKd9s8a0Byla2TT01Wg/GZ4xj0w= diff --git a/internal/command/player/run.go b/internal/command/player/run.go index f5c1eae..c4b55d8 100644 --- a/internal/command/player/run.go +++ b/internal/command/player/run.go @@ -26,11 +26,21 @@ func Run() *cli.Command { EnvVars: []string{"ARCAST_DESKTOP_INSTANCE_ID"}, Value: "", }, + &cli.StringFlag{ + Name: "address", + EnvVars: []string{"ARCAST_DESKTOP_ADDRESS"}, + Value: ":", + }, &cli.IntFlag{ Name: "window-height", EnvVars: []string{"ARCAST_DESKTOP_WINDOW_HEIGHT"}, Value: defaults.Height, }, + &cli.BoolFlag{ + Name: "web-apps", + EnvVars: []string{"ARCAST_DESKTOP_WEB_APPS"}, + Value: false, + }, &cli.IntFlag{ Name: "window-width", EnvVars: []string{"ARCAST_DESKTOP_WINDOW_WIDTH"}, @@ -41,6 +51,8 @@ func Run() *cli.Command { windowHeight := ctx.Int("window-height") windowWidth := ctx.Int("window-width") chromeArgs := addFlagsPrefix(ctx.StringSlice("additional-chrome-arg")...) + webApps := ctx.Bool("web-apps") + serverAddress := ctx.String("address") browser := lorca.NewBrowser( lorca.WithAdditionalChromeArgs(chromeArgs...), @@ -69,7 +81,11 @@ func Run() *cli.Command { instanceID = server.NewRandomInstanceID() } - server := server.New(browser, server.WithInstanceID(instanceID)) + server := server.New(browser, + server.WithInstanceID(instanceID), + server.WithWebApps(webApps), + server.WithAddress(serverAddress), + ) if err := server.Start(); err != nil { return errors.Wrap(err, "could not start server") diff --git a/modd.conf b/modd.conf index 5f59064..8d97c04 100644 --- a/modd.conf +++ b/modd.conf @@ -1,5 +1,6 @@ **/*.go pkg/server/templates/**.gotmpl +pkg/server/apps/** modd.conf .env { prep: make build-client diff --git a/pkg/server/apps.go b/pkg/server/apps.go new file mode 100644 index 0000000..5cbc942 --- /dev/null +++ b/pkg/server/apps.go @@ -0,0 +1,91 @@ +package server + +import ( + "embed" + "encoding/json" + "io/fs" + "net/http" + "os" + "path/filepath" + + "github.com/pkg/errors" + "gitlab.com/wpetit/goweb/api" + + _ "embed" +) + +var ( + apps []App + //go:embed apps/** + appsFS embed.FS +) + +func init() { + if err := initializeAppsList(); err != nil { + panic(errors.WithStack(err)) + } +} + +func initializeAppsList() error { + files, err := fs.Glob(appsFS, "apps/*") + if err != nil { + return errors.WithStack(err) + } + + for _, f := range files { + stat, err := fs.Stat(appsFS, f) + if err != nil { + return errors.WithStack(err) + } + + if !stat.IsDir() { + continue + } + + rawManifest, err := fs.ReadFile(appsFS, filepath.Join(f, "manifest.json")) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + continue + } + + return errors.WithStack(err) + } + + var app App + + if err := json.Unmarshal(rawManifest, &app); err != nil { + return errors.WithStack(err) + } + + app.ID = filepath.Base(f) + + apps = append(apps, app) + } + + return nil +} + +func (s *Server) handleDefaultApp(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, "/apps/"+s.defaultWebApp, http.StatusTemporaryRedirect) +} + +type AppsResponse struct { + Apps []App +} + +type App struct { + ID string `json:"id"` + Title map[string]string `json:"title"` + Description map[string]string `json:"description"` + Icon string `json:"icon"` +} + +func (s *Server) handleApps(w http.ResponseWriter, r *http.Request) { + api.DataResponse(w, http.StatusOK, struct { + DefaultApp string `json:"defaultApp"` + Apps []App `json:"apps"` + }{ + DefaultApp: s.defaultWebApp, + Apps: apps, + }) +} diff --git a/pkg/server/apps/home/app.js b/pkg/server/apps/home/app.js new file mode 100644 index 0000000..0cbf315 --- /dev/null +++ b/pkg/server/apps/home/app.js @@ -0,0 +1,19 @@ +fetch('/api/v1/apps') + .then(res => res.json()) + .then(res => { + const defaultApp = res.data.defaultApp + const apps = res.data.apps + + const container = document.createElement("div") + container.className = "container" + apps.forEach(app => { + if (app.id === defaultApp) return + const appLink = document.createElement("a") + appLink.className = "app-link" + appLink.href = "/apps/" + app.id + appLink.innerText = app.title['fr'] + container.appendChild(appLink) + }) + + document.getElementById("main").replaceWith(container); + }) \ No newline at end of file diff --git a/pkg/server/apps/home/index.html b/pkg/server/apps/home/index.html new file mode 100644 index 0000000..2671835 --- /dev/null +++ b/pkg/server/apps/home/index.html @@ -0,0 +1,16 @@ + + + + + + Arcast - Home + + + + + +
+

Loading...

+
+ + \ No newline at end of file diff --git a/pkg/server/apps/home/manifest.json b/pkg/server/apps/home/manifest.json new file mode 100644 index 0000000..c132d33 --- /dev/null +++ b/pkg/server/apps/home/manifest.json @@ -0,0 +1,10 @@ +{ + "title": { + "fr": "Accueil", + "en": "Home" + }, + "description": { + "fr": "Voir la liste des applications", + "en": "See apps list" + } +} \ No newline at end of file diff --git a/pkg/server/apps/home/style.css b/pkg/server/apps/home/style.css new file mode 100644 index 0000000..dc151dd --- /dev/null +++ b/pkg/server/apps/home/style.css @@ -0,0 +1,23 @@ +.mt { + margin-top: 1em; + display: block; +} + +.app-link { + display: block; + background: white; + border-radius: 5px; + width: 100px; + height: 100px; + box-shadow: 1px 1px 3px #ccc; + color: #333; + text-decoration: none; + text-align: center; + padding-top: 30px; +} + +.app-link:hover { + background-color: #abdbdb; + box-shadow: 1px 1px 3px #aaa; + text-shadow: 1px 1px white; +} \ No newline at end of file diff --git a/pkg/server/apps/lib/style.css b/pkg/server/apps/lib/style.css new file mode 100644 index 0000000..4121a75 --- /dev/null +++ b/pkg/server/apps/lib/style.css @@ -0,0 +1,77 @@ +html { + box-sizing: border-box; + font-size: 16px; + width: 100%; + height: 100%; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + width: 100%; + height: 100%; + background-color: #e1e1e1; +} + +*, +*:before, +*:after { + box-sizing: inherit; +} + +ol, +ul { + list-style: none; +} + +body, +h1, +h2, +h3, +h4, +h5, +h6, +p, +ol, +ul { + margin: 0; + padding: 0; + font-weight: normal; +} + +.container { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + flex-wrap: wrap; + gap: 10px; +} + +.panel { + display: block; + background-color: #fff; + border-radius: 5px; + padding: 10px 20px; + box-shadow: 2px 2px #3333331d; +} + +.panel p, .panel ul { + margin-top: 10px; +} + +.text-centered { + text-align: center; +} + +.text-italic { + font-style: italic; +} + +.text-small { + font-size: 0.8em; +} + +.fullwidth { + width: 100%; +} \ No newline at end of file diff --git a/pkg/server/apps/remote-control/app.js b/pkg/server/apps/remote-control/app.js new file mode 100644 index 0000000..d1efeb3 --- /dev/null +++ b/pkg/server/apps/remote-control/app.js @@ -0,0 +1,66 @@ +function main() { + refreshStatus(); + setInterval(refreshStatus, 10000) +} + +function refreshStatus() { + return fetch("/api/v1/status") + .then(res => res.json()) + .then(res => { + const newStatus = document.createElement("tr") + newStatus.id = "status" + + let td = document.createElement("td") + td.innerText = res.data.status + newStatus.appendChild(td) + + td = document.createElement("td") + td.innerText = res.data.title + newStatus.appendChild(td) + + td = document.createElement("td") + td.innerText = res.data.url + document.getElementById("url-input").placeholder = res.data.url + newStatus.appendChild(td) + + document.getElementById("status").replaceWith(newStatus) + }) + .catch(err => { + console.error(err); + window.location.reload() + }) +} + +function castUrl() { + const urlInput = document.getElementById("url-input") + const url = urlInput.value + if (url === "") return Promise.resolve() + return fetch("/api/v1/cast", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + "url": url, + }) + }) + .then(res => res.json()) + .then(() => refreshStatus()) + .then(() => { + urlInput.value = "" + }) +} + +function reset() { + const urlInput = document.getElementById("url-input") + return fetch("/api/v1/cast", { + method: "DELETE", + }) + .then(res => res.json()) + .then(() => refreshStatus()) + .then(() => { + urlInput.value = "" + }) +} + +main() \ No newline at end of file diff --git a/pkg/server/apps/remote-control/index.html b/pkg/server/apps/remote-control/index.html new file mode 100644 index 0000000..990ffbb --- /dev/null +++ b/pkg/server/apps/remote-control/index.html @@ -0,0 +1,35 @@ + + + + + + Arcast - Remote Control + + + + +
+
+

Remote control

+ + + + + + + + + + + + + +
StatusTitleURL
Refreshing...
+ + + + +
+
+ + \ No newline at end of file diff --git a/pkg/server/apps/remote-control/manifest.json b/pkg/server/apps/remote-control/manifest.json new file mode 100644 index 0000000..65837ea --- /dev/null +++ b/pkg/server/apps/remote-control/manifest.json @@ -0,0 +1,10 @@ +{ + "title": { + "fr": "Contrôle à distance", + "en": "Remote control" + }, + "description": { + "fr": "Contrôler l'afficheur numérique", + "en": "Control the cast player" + } +} \ No newline at end of file diff --git a/pkg/server/apps/screen-sharing/app.js b/pkg/server/apps/screen-sharing/app.js new file mode 100644 index 0000000..6184c9e --- /dev/null +++ b/pkg/server/apps/screen-sharing/app.js @@ -0,0 +1,116 @@ +const displayMediaOptions = { + video: { + displaySurface: "browser", + }, + audio: { + suppressLocalAudioPlayback: false, + }, + preferCurrentTab: false, + selfBrowserSurface: "exclude", + systemAudio: "include", + surfaceSwitching: "include", + monitorTypeSurfaces: "include", +}; + +const peerConnection = new RTCPeerConnection(); +const broadcast = new BroadcastChannel("screen_sharing"); + +function main() { + peerConnection.onconnectionstatechange = (evt) => { + console.log("Connection state changed", evt) + } + + peerConnection.oniceconnectionstatechange = () => { + console.log('ICE state: ',peerConn.iceConnectionState); + } + + peerConnection.ontrack = function (e) { + var vid = document.createElement("video"); + document.body.appendChild(vid); + vid.srcObject = e.streams[0]; + }; + + broadcast.onmessage = (evt) => { + const message = JSON.parse(evt.data); + console.log("Received message", message) + if (message.type === "answer") { + peerConnection.setRemoteDescription( + new RTCSessionDescription(message.payload), + () => {}, + error, + ); + } + }; + + const urlParams = new URLSearchParams(window.location.search); + const b64Offer = urlParams.get('offer'); + if (!b64Offer) return; + + const offer = JSON.parse(atob(b64Offer)); + peerConnection.setRemoteDescription( + new RTCSessionDescription(offer), + () => { + peerConnection.createAnswer(function (answer) { + peerConnection.setLocalDescription( + answer, + function () { + broadcast.postMessage(JSON.stringify({ + type: 'answer', + payload: answer + })) + }, + error, + ); + }, error); + }, + error, + ); +} + +function shareScreen() { + return navigator.mediaDevices + .getDisplayMedia(displayMediaOptions) + .then(captureStream => { + peerConnection.addStream(captureStream); + + peerConnection.createOffer(function (offer) { + peerConnection.setLocalDescription( + offer, + () => { + const b64Offer = btoa(JSON.stringify(offer)) + const url = "https://127.0.0.1:" + window.location.port + "?offer=" + encodeURIComponent(b64Offer) + console.log(url) + fetch("/api/v1/cast", { + method: 'POST', + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + "url": url, + }) + }) + }, + error, + ); + }, error); + }) + .catch(err => { + console.error(err) + }) +} + +function endCall() { + var videos = document.getElementsByTagName("video"); + for (var i = 0; i < videos.length; i++) { + videos[i].pause(); + } + + peerConnection.close(); +} + +function error(err) { + console.error(err); + endCall(); +} + +main() \ No newline at end of file diff --git a/pkg/server/apps/screen-sharing/index.html b/pkg/server/apps/screen-sharing/index.html new file mode 100644 index 0000000..f813843 --- /dev/null +++ b/pkg/server/apps/screen-sharing/index.html @@ -0,0 +1,18 @@ + + + + + + Arcast - Screen sharing + + + + +
+
+

Screen sharing

+ +
+
+ + \ No newline at end of file diff --git a/pkg/server/apps/screen-sharing/manifest.json b/pkg/server/apps/screen-sharing/manifest.json new file mode 100644 index 0000000..a621713 --- /dev/null +++ b/pkg/server/apps/screen-sharing/manifest.json @@ -0,0 +1,10 @@ +{ + "title": { + "fr": "Partage d'écran", + "en": "Screen sharing" + }, + "description": { + "fr": "Partager son écran", + "en": "Share your screen" + } +} \ No newline at end of file diff --git a/pkg/server/http.go b/pkg/server/http.go index 36ffdc0..3d11b10 100644 --- a/pkg/server/http.go +++ b/pkg/server/http.go @@ -9,6 +9,7 @@ import ( "strconv" "github.com/go-chi/chi/v5" + "github.com/go-chi/cors" "github.com/pkg/errors" "gitlab.com/wpetit/goweb/api" "gitlab.com/wpetit/goweb/logger" @@ -34,11 +35,36 @@ func init() { func (s *Server) startHTTPServer(ctx context.Context) error { router := chi.NewRouter() + if s.webApps { + ips, err := getLANIPv4Addrs() + if err != nil { + return errors.WithStack(err) + } + + allowedOrigins := make([]string, len(ips)) + for idx, ip := range ips { + allowedOrigins[idx] = fmt.Sprintf("http://%s:%d", ip, s.port) + } + + router.Use(cors.Handler(cors.Options{ + AllowedOrigins: allowedOrigins, + AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, + AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"}, + AllowCredentials: false, + })) + } + router.Get("/", s.handleHome) router.Post("/api/v1/cast", s.handleCast) router.Delete("/api/v1/cast", s.handleReset) router.Get("/api/v1/status", s.handleStatus) + if s.webApps { + router.Get("/apps", s.handleDefaultApp) + router.Get("/api/v1/apps", s.handleApps) + router.Handle("/apps/*", http.FileServer(http.FS(appsFS))) + } + server := http.Server{ Addr: s.address, Handler: router, @@ -167,9 +193,10 @@ func (s *Server) handleStatus(w http.ResponseWriter, r *http.Request) { func (s *Server) handleHome(w http.ResponseWriter, r *http.Request) { type templateData struct { - IPs []string - Port int - ID string + IPs []string + Port int + ID string + WebApps bool } ips, err := getLANIPv4Addrs() @@ -180,9 +207,10 @@ func (s *Server) handleHome(w http.ResponseWriter, r *http.Request) { } d := templateData{ - ID: s.instanceID, - IPs: ips, - Port: s.port, + ID: s.instanceID, + IPs: ips, + Port: s.port, + WebApps: s.webApps, } if err := idleTemplate.Execute(w, d); err != nil { diff --git a/pkg/server/options.go b/pkg/server/options.go index 4e0f632..71a340a 100644 --- a/pkg/server/options.go +++ b/pkg/server/options.go @@ -20,6 +20,8 @@ type Options struct { InstanceID string Address string DisableServiceDiscovery bool + WebApps bool + DefaultWebApp string } type OptionFunc func(opts *Options) @@ -29,6 +31,8 @@ func NewOptions(funcs ...OptionFunc) *Options { InstanceID: NewRandomInstanceID(), Address: ":", DisableServiceDiscovery: false, + WebApps: false, + DefaultWebApp: "home", } for _, fn := range funcs { @@ -38,6 +42,12 @@ func NewOptions(funcs ...OptionFunc) *Options { return opts } +func WithWebApps(enabled bool) OptionFunc { + return func(opts *Options) { + opts.WebApps = enabled + } +} + func WithAddress(addr string) OptionFunc { return func(opts *Options) { opts.Address = addr diff --git a/pkg/server/server.go b/pkg/server/server.go index 3ea403c..0d9757b 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -16,6 +16,9 @@ type Server struct { port int disableServiceDiscovery bool + webApps bool + defaultWebApp string + ctx context.Context cancel context.CancelFunc } @@ -71,6 +74,8 @@ func New(browser browser.Browser, funcs ...OptionFunc) *Server { browser: browser, instanceID: opts.InstanceID, address: opts.Address, + webApps: opts.WebApps, + defaultWebApp: opts.DefaultWebApp, disableServiceDiscovery: opts.DisableServiceDiscovery, } } diff --git a/pkg/server/templates/idle.html.gotmpl b/pkg/server/templates/idle.html.gotmpl index 3ea0931..fe4bfef 100644 --- a/pkg/server/templates/idle.html.gotmpl +++ b/pkg/server/templates/idle.html.gotmpl @@ -76,13 +76,18 @@ .text-small { font-size: 0.8em; } + + .mt { + margin-top: 1em; + display: block; + }
-

Arcast

+

Arcast - Idle

Instance ID:

{{ .ID }} @@ -94,6 +99,15 @@

  • {{ . }}:{{ $port }}
  • {{end}} + {{if .WebApps }} +

    Apps:

    + + {{end}}