From e27d5719f037aa9e9dca27c4eff23d4622b5475d Mon Sep 17 00:00:00 2001
From: Philipp Heckel
Date: Sat, 5 Mar 2022 20:24:10 -0500
Subject: [PATCH] Embed new web UI into server
---
.gitignore | 1 +
Makefile | 29 +++++++++-
server/server.go | 53 ++++++++----------
server/static/img/close.svg | 1 -
server/static/img/favicon.png | Bin 4701 -> 0 bytes
server/static/img/ntfy.png | Bin 3627 -> 0 bytes
web/README.md | 44 ---------------
web/public/config.js | 3 -
web/public/favicon.ico | Bin 3870 -> 0 bytes
server/index.gohtml => web/public/home.html | 10 ++--
web/public/index.html | 9 +--
web/public/manifest.json | 15 -----
.../app.css => web/public/static/css/home.css | 0
.../static/font/roboto-v29-latin-300.woff | Bin
.../static/font/roboto-v29-latin-300.woff2 | Bin
.../static/font/roboto-v29-latin-500.woff | Bin
.../static/font/roboto-v29-latin-500.woff2 | Bin
.../static/font/roboto-v29-latin-regular.woff | Bin
.../font/roboto-v29-latin-regular.woff2 | Bin
.../static/img/android-video-overview.mp4 | Bin
.../img/android-video-subscribe-api.mp4 | Bin
.../public}/static/img/badge-appstore.png | Bin
.../public}/static/img/badge-fdroid.png | Bin
.../public}/static/img/badge-googleplay.png | Bin
.../public}/static/img/basic-notification.png | Bin
.../public}/static/img/screenshot-curl.png | Bin
.../public}/static/img/screenshot-docs.png | Bin
.../static/img/screenshot-phone-add.jpg | Bin
.../static/img/screenshot-phone-detail.jpg | Bin
.../static/img/screenshot-phone-main.jpg | Bin
.../img/screenshot-phone-notification.jpg | Bin
.../static/img/screenshot-phone-popover.png | Bin
.../static/img/screenshot-web-detail.png | Bin
.../js/app.js => web/public/static/js/home.js | 0
web/src/app/config.js | 5 +-
web/src/components/App.js | 1 -
web/src/components/Notifications.js | 2 +-
web/src/components/SubscribeDialog.js | 1 +
38 files changed, 64 insertions(+), 110 deletions(-)
delete mode 100644 server/static/img/close.svg
delete mode 100644 server/static/img/favicon.png
delete mode 100644 server/static/img/ntfy.png
delete mode 100644 web/README.md
delete mode 100644 web/public/config.js
delete mode 100644 web/public/favicon.ico
rename server/index.gohtml => web/public/home.html (96%)
delete mode 100644 web/public/manifest.json
rename server/static/css/app.css => web/public/static/css/home.css (100%)
rename {server => web/public}/static/font/roboto-v29-latin-300.woff (100%)
rename {server => web/public}/static/font/roboto-v29-latin-300.woff2 (100%)
rename {server => web/public}/static/font/roboto-v29-latin-500.woff (100%)
rename {server => web/public}/static/font/roboto-v29-latin-500.woff2 (100%)
rename {server => web/public}/static/font/roboto-v29-latin-regular.woff (100%)
rename {server => web/public}/static/font/roboto-v29-latin-regular.woff2 (100%)
rename {server => web/public}/static/img/android-video-overview.mp4 (100%)
rename {server => web/public}/static/img/android-video-subscribe-api.mp4 (100%)
rename {server => web/public}/static/img/badge-appstore.png (100%)
rename {server => web/public}/static/img/badge-fdroid.png (100%)
rename {server => web/public}/static/img/badge-googleplay.png (100%)
rename {server => web/public}/static/img/basic-notification.png (100%)
rename {server => web/public}/static/img/screenshot-curl.png (100%)
rename {server => web/public}/static/img/screenshot-docs.png (100%)
rename {server => web/public}/static/img/screenshot-phone-add.jpg (100%)
rename {server => web/public}/static/img/screenshot-phone-detail.jpg (100%)
rename {server => web/public}/static/img/screenshot-phone-main.jpg (100%)
rename {server => web/public}/static/img/screenshot-phone-notification.jpg (100%)
rename server/static/img/priority-notification.png => web/public/static/img/screenshot-phone-popover.png (100%)
rename {server => web/public}/static/img/screenshot-web-detail.png (100%)
rename server/static/js/app.js => web/public/static/js/home.js (100%)
diff --git a/.gitignore b/.gitignore
index 932e0fcd..9f514857 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@ dist/
build/
.idea/
server/docs/
+server/site/
tools/fbsend/fbsend
playground/
*.iml
diff --git a/Makefile b/Makefile
index a9a9a201..06b4c745 100644
--- a/Makefile
+++ b/Makefile
@@ -44,6 +44,28 @@ docs-deps: .PHONY
docs: docs-deps
mkdocs build
+
+# Web app
+
+web-deps:
+ cd web && npm install
+
+web-build:
+ cd web \
+ && npm run build \
+ && mv build/index.html build/app.html \
+ && rm -rf ../server/site \
+ && mv build ../server/site \
+ && rm \
+ ../server/site/precache* \
+ ../server/site/service-worker.js \
+ ../server/site/asset-manifest.json \
+ ../server/site/static/js/*.js.map \
+ ../server/site/static/js/*.js.LICENSE.txt
+
+web: web-deps web-build
+
+
# Test/check targets
check: test fmt-check vet lint staticcheck
@@ -94,7 +116,7 @@ staticcheck: .PHONY
# Building targets
-build-deps: docs
+build-deps: docs web
which arm-linux-gnueabi-gcc || { echo "ERROR: ARMv6/v7 cross compiler not installed. On Ubuntu, run: apt install gcc-arm-linux-gnueabi"; exit 1; }
which aarch64-linux-gnu-gcc || { echo "ERROR: ARM64 cross compiler not installed. On Ubuntu, run: apt install gcc-aarch64-linux-gnu"; exit 1; }
@@ -105,8 +127,9 @@ build-snapshot: build-deps
goreleaser build --snapshot --rm-dist --debug
build-simple: clean
- mkdir -p dist/ntfy_linux_amd64 server/docs
- touch server/docs/dummy
+ mkdir -p dist/ntfy_linux_amd64 server/docs server/site
+ touch server/docs/index.html
+ touch server/site/app.html
export CGO_ENABLED=1
go build \
-o dist/ntfy_linux_amd64/ntfy \
diff --git a/server/server.go b/server/server.go
index 7e4c551a..9edcc86c 100644
--- a/server/server.go
+++ b/server/server.go
@@ -13,7 +13,6 @@ import (
"golang.org/x/sync/errgroup"
"heckel.io/ntfy/auth"
"heckel.io/ntfy/util"
- "html/template"
"io"
"log"
"net"
@@ -61,35 +60,31 @@ type handleFunc func(http.ResponseWriter, *http.Request, *visitor) error
var (
// If changed, don't forget to update Android App and auth_sqlite.go
- topicRegex = regexp.MustCompile(`^[-_A-Za-z0-9]{1,64}$`) // No /!
- topicPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}$`) // Regex must match JS & Android app!
- jsonPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/json$`)
- ssePathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/sse$`)
- rawPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/raw$`)
- wsPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/ws$`)
- authPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/auth$`)
- publishPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/(publish|send|trigger)$`)
+ topicRegex = regexp.MustCompile(`^[-_A-Za-z0-9]{1,64}$`) // No /!
+ topicPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}$`) // Regex must match JS & Android app!
+ extTopicPathRegex = regexp.MustCompile(`^/[^/]+\.[^/]+/[-_A-Za-z0-9]{1,64}$`) // Extended topic path, for web-app, e.g. /example.com/mytopic
+ jsonPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/json$`)
+ ssePathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/sse$`)
+ rawPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/raw$`)
+ wsPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/ws$`)
+ authPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/auth$`)
+ publishPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/(publish|send|trigger)$`)
staticRegex = regexp.MustCompile(`^/static/.+`)
docsRegex = regexp.MustCompile(`^/docs(|/.*)$`)
fileRegex = regexp.MustCompile(`^/file/([-_A-Za-z0-9]{1,64})(?:\.[A-Za-z0-9]{1,16})?$`)
- disallowedTopics = []string{"docs", "static", "file"} // If updated, also update in Android app
+ disallowedTopics = []string{"docs", "static", "file", "app", "settings"} // If updated, also update in Android app
attachURLRegex = regexp.MustCompile(`^https?://`)
- templateFnMap = template.FuncMap{
- "durationToHuman": util.DurationToHuman,
- }
-
- //go:embed "index.gohtml"
- indexSource string
- indexTemplate = template.Must(template.New("index").Funcs(templateFnMap).Parse(indexSource))
-
//go:embed "example.html"
exampleSource string
- //go:embed static
- webStaticFs embed.FS
- webStaticFsCached = &util.CachingEmbedFS{ModTime: time.Now(), FS: webStaticFs}
+ //go:embed site
+ webFs embed.FS
+ webFsCached = &util.CachingEmbedFS{ModTime: time.Now(), FS: webFs}
+ webSiteDir = "/site"
+ webHomeIndex = "/home.html" // Landing page, only if "web-index: home"
+ webAppIndex = "/app.html" // React app
//go:embed docs
docsStaticFs embed.FS
@@ -284,8 +279,6 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visit
return s.limitRequests(s.handleFile)(w, r, v)
} else if r.Method == http.MethodOptions {
return s.handleOptions(w, r)
- } else if r.Method == http.MethodGet && topicPathRegex.MatchString(r.URL.Path) {
- return s.handleTopic(w, r)
} else if (r.Method == http.MethodPut || r.Method == http.MethodPost) && topicPathRegex.MatchString(r.URL.Path) {
return s.limitRequests(s.authWrite(s.handlePublish))(w, r, v)
} else if r.Method == http.MethodGet && publishPathRegex.MatchString(r.URL.Path) {
@@ -300,15 +293,15 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visit
return s.limitRequests(s.authRead(s.handleSubscribeWS))(w, r, v)
} else if r.Method == http.MethodGet && authPathRegex.MatchString(r.URL.Path) {
return s.limitRequests(s.authRead(s.handleTopicAuth))(w, r, v)
+ } else if r.Method == http.MethodGet && (topicPathRegex.MatchString(r.URL.Path) || extTopicPathRegex.MatchString(r.URL.Path)) {
+ return s.handleTopic(w, r)
}
return errHTTPNotFound
}
func (s *Server) handleHome(w http.ResponseWriter, r *http.Request) error {
- return indexTemplate.Execute(w, &indexPage{
- Topic: r.URL.Path[1:],
- CacheDuration: s.config.CacheDuration,
- })
+ r.URL.Path = webHomeIndex
+ return s.handleStatic(w, r)
}
func (s *Server) handleTopic(w http.ResponseWriter, r *http.Request) error {
@@ -319,7 +312,8 @@ func (s *Server) handleTopic(w http.ResponseWriter, r *http.Request) error {
_, err := io.WriteString(w, `{"unifiedpush":{"version":1}}`+"\n")
return err
}
- return s.handleHome(w, r)
+ r.URL.Path = webAppIndex
+ return s.handleStatic(w, r)
}
func (s *Server) handleEmpty(_ http.ResponseWriter, _ *http.Request, _ *visitor) error {
@@ -339,7 +333,8 @@ func (s *Server) handleExample(w http.ResponseWriter, _ *http.Request) error {
}
func (s *Server) handleStatic(w http.ResponseWriter, r *http.Request) error {
- http.FileServer(http.FS(webStaticFsCached)).ServeHTTP(w, r)
+ r.URL.Path = webSiteDir + r.URL.Path
+ http.FileServer(http.FS(webFsCached)).ServeHTTP(w, r)
return nil
}
diff --git a/server/static/img/close.svg b/server/static/img/close.svg
deleted file mode 100644
index 5f1267d7..00000000
--- a/server/static/img/close.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/server/static/img/favicon.png b/server/static/img/favicon.png
deleted file mode 100644
index 92312feac6a3c9effe8323e76a6d30f2ce88d724..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 4701
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
Zdu`HC4VjGyX*2{dxMQmGUfU`5e*gp9@R9%k
diff --git a/server/static/img/ntfy.png b/server/static/img/ntfy.png
deleted file mode 100644
index 6b969a846efc6c3dac70510793c72253e04389c9..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 3627
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
diff --git a/web/README.md b/web/README.md
deleted file mode 100644
index 953f65b2..00000000
--- a/web/README.md
+++ /dev/null
@@ -1,44 +0,0 @@
-# Create React App example
-
-## How to use
-
-Download the example [or clone the repo](https://github.com/mui/material-ui):
-
-
-
-```sh
-curl https://codeload.github.com/mui/material-ui/tar.gz/master | tar -xz --strip=2 material-ui-master/examples/create-react-app
-cd create-react-app
-```
-
-Install it and run:
-
-```sh
-npm install
-npm start
-```
-
-or:
-
-
-
-[![Edit on CodeSandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/mui/material-ui/tree/master/examples/create-react-app)
-
-
-
-[![Edit on StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/mui/material-ui/tree/master/examples/create-react-app)
-
-## The idea behind the example
-
-
-
-This example demonstrates how you can use [Create React App](https://github.com/facebookincubator/create-react-app).
-It includes `@mui/material` and its peer dependencies, including `emotion`, the default style engine in MUI v5.
-If you prefer, you can [use styled-components instead](https://mui.com/guides/interoperability/#styled-components).
-
-## What's next?
-
-
-
-You now have a working example project.
-You can head back to the documentation, continuing browsing it from the [templates](https://mui.com/getting-started/templates/) section.
diff --git a/web/public/config.js b/web/public/config.js
deleted file mode 100644
index 3283db2e..00000000
--- a/web/public/config.js
+++ /dev/null
@@ -1,3 +0,0 @@
-var config = {
- defaultBaseUrl: 'https://ntfy.sh'
-};
diff --git a/web/public/favicon.ico b/web/public/favicon.ico
deleted file mode 100644
index a11777cc471a4344702741ab1c8a588998b1311a..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 3870
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
baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@
diff --git a/server/index.gohtml b/web/public/home.html
similarity index 96%
rename from server/index.gohtml
rename to web/public/home.html
index 9fec14cf..f054f83a 100644
--- a/server/index.gohtml
+++ b/web/public/home.html
@@ -1,11 +1,10 @@
-{{- /*gotype: heckel.io/ntfy/server.indexPage*/ -}}
ntfy.sh | Send push notifications to your phone via PUT/POST
-
+
@@ -37,9 +36,9 @@
ntfy
- Getting started
+ - Web app
- Android/iOS
- API
- - Self-hosting
- GitHub
@@ -90,7 +89,7 @@
Here's what that looks like in the Android app:
@@ -170,7 +169,6 @@
Made with ❤️ by Philipp C. Heckel
-
-
+
diff --git a/web/public/index.html b/web/public/index.html
index 3512b548..19e14506 100644
--- a/web/public/index.html
+++ b/web/public/index.html
@@ -20,18 +20,15 @@
-
-
-
+
+
+
-
-
-
diff --git a/web/public/manifest.json b/web/public/manifest.json
deleted file mode 100644
index f99717a5..00000000
--- a/web/public/manifest.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "short_name": "Your Orders",
- "name": "Your Orders",
- "icons": [
- {
- "src": "favicon.ico",
- "sizes": "64x64 32x32 24x24 16x16",
- "type": "image/x-icon"
- }
- ],
- "start_url": ".",
- "display": "standalone",
- "theme_color": "#000000",
- "background_color": "#ffffff"
-}
diff --git a/server/static/css/app.css b/web/public/static/css/home.css
similarity index 100%
rename from server/static/css/app.css
rename to web/public/static/css/home.css
diff --git a/server/static/font/roboto-v29-latin-300.woff b/web/public/static/font/roboto-v29-latin-300.woff
similarity index 100%
rename from server/static/font/roboto-v29-latin-300.woff
rename to web/public/static/font/roboto-v29-latin-300.woff
diff --git a/server/static/font/roboto-v29-latin-300.woff2 b/web/public/static/font/roboto-v29-latin-300.woff2
similarity index 100%
rename from server/static/font/roboto-v29-latin-300.woff2
rename to web/public/static/font/roboto-v29-latin-300.woff2
diff --git a/server/static/font/roboto-v29-latin-500.woff b/web/public/static/font/roboto-v29-latin-500.woff
similarity index 100%
rename from server/static/font/roboto-v29-latin-500.woff
rename to web/public/static/font/roboto-v29-latin-500.woff
diff --git a/server/static/font/roboto-v29-latin-500.woff2 b/web/public/static/font/roboto-v29-latin-500.woff2
similarity index 100%
rename from server/static/font/roboto-v29-latin-500.woff2
rename to web/public/static/font/roboto-v29-latin-500.woff2
diff --git a/server/static/font/roboto-v29-latin-regular.woff b/web/public/static/font/roboto-v29-latin-regular.woff
similarity index 100%
rename from server/static/font/roboto-v29-latin-regular.woff
rename to web/public/static/font/roboto-v29-latin-regular.woff
diff --git a/server/static/font/roboto-v29-latin-regular.woff2 b/web/public/static/font/roboto-v29-latin-regular.woff2
similarity index 100%
rename from server/static/font/roboto-v29-latin-regular.woff2
rename to web/public/static/font/roboto-v29-latin-regular.woff2
diff --git a/server/static/img/android-video-overview.mp4 b/web/public/static/img/android-video-overview.mp4
similarity index 100%
rename from server/static/img/android-video-overview.mp4
rename to web/public/static/img/android-video-overview.mp4
diff --git a/server/static/img/android-video-subscribe-api.mp4 b/web/public/static/img/android-video-subscribe-api.mp4
similarity index 100%
rename from server/static/img/android-video-subscribe-api.mp4
rename to web/public/static/img/android-video-subscribe-api.mp4
diff --git a/server/static/img/badge-appstore.png b/web/public/static/img/badge-appstore.png
similarity index 100%
rename from server/static/img/badge-appstore.png
rename to web/public/static/img/badge-appstore.png
diff --git a/server/static/img/badge-fdroid.png b/web/public/static/img/badge-fdroid.png
similarity index 100%
rename from server/static/img/badge-fdroid.png
rename to web/public/static/img/badge-fdroid.png
diff --git a/server/static/img/badge-googleplay.png b/web/public/static/img/badge-googleplay.png
similarity index 100%
rename from server/static/img/badge-googleplay.png
rename to web/public/static/img/badge-googleplay.png
diff --git a/server/static/img/basic-notification.png b/web/public/static/img/basic-notification.png
similarity index 100%
rename from server/static/img/basic-notification.png
rename to web/public/static/img/basic-notification.png
diff --git a/server/static/img/screenshot-curl.png b/web/public/static/img/screenshot-curl.png
similarity index 100%
rename from server/static/img/screenshot-curl.png
rename to web/public/static/img/screenshot-curl.png
diff --git a/server/static/img/screenshot-docs.png b/web/public/static/img/screenshot-docs.png
similarity index 100%
rename from server/static/img/screenshot-docs.png
rename to web/public/static/img/screenshot-docs.png
diff --git a/server/static/img/screenshot-phone-add.jpg b/web/public/static/img/screenshot-phone-add.jpg
similarity index 100%
rename from server/static/img/screenshot-phone-add.jpg
rename to web/public/static/img/screenshot-phone-add.jpg
diff --git a/server/static/img/screenshot-phone-detail.jpg b/web/public/static/img/screenshot-phone-detail.jpg
similarity index 100%
rename from server/static/img/screenshot-phone-detail.jpg
rename to web/public/static/img/screenshot-phone-detail.jpg
diff --git a/server/static/img/screenshot-phone-main.jpg b/web/public/static/img/screenshot-phone-main.jpg
similarity index 100%
rename from server/static/img/screenshot-phone-main.jpg
rename to web/public/static/img/screenshot-phone-main.jpg
diff --git a/server/static/img/screenshot-phone-notification.jpg b/web/public/static/img/screenshot-phone-notification.jpg
similarity index 100%
rename from server/static/img/screenshot-phone-notification.jpg
rename to web/public/static/img/screenshot-phone-notification.jpg
diff --git a/server/static/img/priority-notification.png b/web/public/static/img/screenshot-phone-popover.png
similarity index 100%
rename from server/static/img/priority-notification.png
rename to web/public/static/img/screenshot-phone-popover.png
diff --git a/server/static/img/screenshot-web-detail.png b/web/public/static/img/screenshot-web-detail.png
similarity index 100%
rename from server/static/img/screenshot-web-detail.png
rename to web/public/static/img/screenshot-web-detail.png
diff --git a/server/static/js/app.js b/web/public/static/js/home.js
similarity index 100%
rename from server/static/js/app.js
rename to web/public/static/js/home.js
diff --git a/web/src/app/config.js b/web/src/app/config.js
index 71a9ece3..1976d79e 100644
--- a/web/src/app/config.js
+++ b/web/src/app/config.js
@@ -1,2 +1,5 @@
-const config = window.config;
+//const config = window.config;
+const config = {
+ defaultBaseUrl: "https://ntfy.sh"
+};
export default config;
diff --git a/web/src/components/App.js b/web/src/components/App.js
index c39fd412..7834de47 100644
--- a/web/src/components/App.js
+++ b/web/src/components/App.js
@@ -21,7 +21,6 @@ import {BrowserRouter, Route, Routes, useLocation, useNavigate} from "react-rout
import {subscriptionRoute} from "../app/utils";
// TODO support unsubscribed routes
-// TODO embed into ntfy server
// TODO googlefonts
// TODO new notification indicator
// TODO sound
diff --git a/web/src/components/Notifications.js b/web/src/components/Notifications.js
index d64ef0a6..38e8c9c4 100644
--- a/web/src/components/Notifications.js
+++ b/web/src/components/Notifications.js
@@ -251,7 +251,7 @@ const NothingHereYet = (props) => {
return (
-
+
You haven't received any notifications for this topic yet.
diff --git a/web/src/components/SubscribeDialog.js b/web/src/components/SubscribeDialog.js
index c3a362d5..aa2de679 100644
--- a/web/src/components/SubscribeDialog.js
+++ b/web/src/components/SubscribeDialog.js
@@ -109,6 +109,7 @@ const SubscribePage = (props) => {
margin="dense"
id="topic"
placeholder="Topic name, e.g. phil_alerts"
+ inputProps={{ maxLength: 64 }}
value={props.topic}
onChange={ev => props.setTopic(ev.target.value)}
type="text"