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
+
+
+
+
+
+
+
+
\ 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
+
+
+
+ Status |
+ Title |
+ URL |
+
+
+
+
+ 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:
+
+ {{ $port := .Port }}
+ {{range .IPs}}
+ - http://{{ . }}:{{ $port }}/apps
+ {{end}}
+
+ {{end}}