From 36eb8d8b2b74a650ebe8c4d8b622d797c33fc695 Mon Sep 17 00:00:00 2001 From: Jannis Mattheis Date: Mon, 2 Aug 2021 13:02:17 +0200 Subject: [PATCH] Inject register & version information into index.html The registration form will always be shown inside the dev mode, because there is no api that transmits if registration is enabled. --- router/router.go | 2 +- ui/.eslintrc.yml | 1 + ui/public/index.html | 3 +++ ui/serve.go | 41 +++++++++++++++++++++++++++++----------- ui/src/config.ts | 28 +++++++++++++++++++++------ ui/src/index.tsx | 20 +++++--------------- ui/src/layout/Layout.tsx | 21 +++----------------- ui/src/user/Login.tsx | 9 +++------ 8 files changed, 68 insertions(+), 57 deletions(-) diff --git a/router/router.go b/router/router.go index edb9736..6aaf9f0 100644 --- a/router/router.go +++ b/router/router.go @@ -55,7 +55,7 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co userChangeNotifier.OnUserDeleted(pluginManager.RemoveUser) userChangeNotifier.OnUserAdded(pluginManager.InitializeForUserID) - ui.Register(g) + ui.Register(g, *vInfo, conf.Registration) g.GET("/health", healthHandler.Health) g.GET("/swagger", docs.Serve) diff --git a/ui/.eslintrc.yml b/ui/.eslintrc.yml index 3895dcd..a5d545b 100644 --- a/ui/.eslintrc.yml +++ b/ui/.eslintrc.yml @@ -60,6 +60,7 @@ rules: jest/expect-expect: off jest/no-jasmine-globals: off "@typescript-eslint/require-await": off + "@typescript-eslint/restrict-template-expressions": off "@typescript-eslint/array-type": [error, {default: array-simple}] "@typescript-eslint/await-thenable": error diff --git a/ui/public/index.html b/ui/public/index.html index b641782..5ea8d02 100644 --- a/ui/public/index.html +++ b/ui/public/index.html @@ -35,5 +35,8 @@ Gotify requires JavaScript.
+<% if (process.env.NODE_ENV === 'production') { %> + +<% } %> diff --git a/ui/serve.go b/ui/serve.go index f67a9e1..903caae 100644 --- a/ui/serve.go +++ b/ui/serve.go @@ -1,32 +1,51 @@ package ui import ( + "encoding/json" "net/http" + "strings" "github.com/gin-contrib/gzip" "github.com/gin-gonic/gin" "github.com/gobuffalo/packr/v2" + "github.com/gotify/server/v2/model" ) var box = packr.New("ui", "../ui/build") +type uiConfig struct { + Register bool `json:"register"` + Version model.VersionInfo `json:"version"` +} + // Register registers the ui on the root path. -func Register(r *gin.Engine) { +func Register(r *gin.Engine, version model.VersionInfo, register bool) { + uiConfigBytes, err := json.Marshal(uiConfig{Version: version, Register: register}) + if err != nil { + panic(err) + } ui := r.Group("/", gzip.Gzip(gzip.DefaultCompression)) - ui.GET("/", serveFile("index.html", "text/html")) - ui.GET("/index.html", serveFile("index.html", "text/html")) - ui.GET("/manifest.json", serveFile("manifest.json", "application/json")) - ui.GET("/assets-manifest.json", serveFile("asserts-manifest.json", "application/json")) + ui.GET("/", serveFile("index.html", "text/html", func(content string) string { + return strings.Replace(content, "%CONFIG%", string(uiConfigBytes), 1) + })) + ui.GET("/index.html", serveFile("index.html", "text/html", noop)) + ui.GET("/manifest.json", serveFile("manifest.json", "application/json", noop)) + ui.GET("/asset-manifest.json", serveFile("asset-manifest.json", "application/json", noop)) ui.GET("/static/*any", gin.WrapH(http.FileServer(box))) } -func serveFile(name, contentType string) gin.HandlerFunc { +func noop(s string) string { + return s +} + +func serveFile(name, contentType string, convert func(string) string) gin.HandlerFunc { + content, err := box.FindString(name) + if err != nil { + panic(err) + } + converted := convert(content) return func(ctx *gin.Context) { ctx.Header("Content-Type", contentType) - content, err := box.FindString(name) - if err != nil { - panic(err) - } - ctx.String(200, content) + ctx.String(200, converted) } } diff --git a/ui/src/config.ts b/ui/src/config.ts index 40cd0bc..7fa744b 100644 --- a/ui/src/config.ts +++ b/ui/src/config.ts @@ -1,13 +1,29 @@ +import {IVersion} from './types'; + export interface IConfig { url: string; + register: boolean; + version: IVersion; } -let config: IConfig; - -export function set(c: IConfig) { - config = c; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +declare global { + interface Window { + config?: Partial; + } } -export function get(val: 'url'): string { - return config[val]; +const config: IConfig = { + url: 'unset', + register: false, + version: {commit: 'unknown', buildDate: 'unknown', version: 'unknown'}, + ...window.config, +}; + +export function set(key: Key, value: IConfig[Key]): void { + config[key] = value; +} + +export function get(key: K): IConfig[K] { + return config[key]; } diff --git a/ui/src/index.tsx b/ui/src/index.tsx index 5b2cc04..8073207 100644 --- a/ui/src/index.tsx +++ b/ui/src/index.tsx @@ -16,9 +16,7 @@ import {ClientStore} from './client/ClientStore'; import {PluginStore} from './plugin/PluginStore'; import {registerReactions} from './reactions'; -const defaultDevConfig = { - url: 'http://localhost:3000/', -}; +const devUrl = 'http://localhost:3000/'; const {port, hostname, protocol, pathname} = window.location; const slashes = protocol.concat('//'); @@ -26,16 +24,7 @@ const path = pathname.endsWith('/') ? pathname : pathname.substring(0, pathname. const url = slashes.concat(port ? hostname.concat(':', port) : hostname) + path; const urlWithSlash = url.endsWith('/') ? url : url.concat('/'); -const defaultProdConfig = { - url: urlWithSlash, -}; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -declare global { - interface Window { - config: config.IConfig; - } -} +const prodUrl = urlWithSlash; const initStores = (): StoreMapping => { const snackManager = new SnackManager(); @@ -62,9 +51,10 @@ const initStores = (): StoreMapping => { (function clientJS() { if (process.env.NODE_ENV === 'production') { - config.set(window.config || defaultProdConfig); + config.set('url', prodUrl); } else { - config.set(window.config || defaultDevConfig); + config.set('url', devUrl); + config.set('register', true); } const stores = initStores(); initAxios(stores.currentUser, stores.snackManager.snack); diff --git a/ui/src/layout/Layout.tsx b/ui/src/layout/Layout.tsx index 286fd6a..2b03da4 100644 --- a/ui/src/layout/Layout.tsx +++ b/ui/src/layout/Layout.tsx @@ -1,6 +1,5 @@ import {createMuiTheme, MuiThemeProvider, Theme, WithStyles, withStyles} from '@material-ui/core'; import CssBaseline from '@material-ui/core/CssBaseline'; -import axios, {AxiosResponse} from 'axios'; import * as React from 'react'; import {HashRouter, Redirect, Route, Switch} from 'react-router-dom'; import Header from './Header'; @@ -21,7 +20,6 @@ import {observer} from 'mobx-react'; import {observable} from 'mobx'; import {inject, Stores} from '../inject'; import {ConnectionErrorBanner} from '../common/ConnectionErrorBanner'; -import {IVersion} from '../types'; const styles = (theme: Theme) => ({ content: { @@ -57,31 +55,18 @@ const isThemeKey = (value: string | null): value is ThemeKey => class Layout extends React.Component< WithStyles<'content'> & Stores<'currentUser' | 'snackManager'> > { - private static defaultVersion = '0.0.0'; - @observable private currentTheme: ThemeKey = 'dark'; @observable private showSettings = false; @observable - private version = Layout.defaultVersion; - @observable private navOpen = false; - @observable - private showRegister = true; //TODO https://github.com/gotify/server/pull/394#discussion_r650559205 private setNavOpen(open: boolean) { this.navOpen = open; } public componentDidMount() { - this.registration = true; //TODO https://github.com/gotify/server/pull/394#discussion_r650559205 - if (this.version === Layout.defaultVersion) { - axios.get(config.get('url') + 'version').then((resp: AxiosResponse) => { - this.version = resp.data.version; - }); - } - const localStorageTheme = window.localStorage.getItem(localStorageThemeKey); if (isThemeKey(localStorageTheme)) { this.currentTheme = localStorageTheme; @@ -91,7 +76,7 @@ class Layout extends React.Component< } public render() { - const {version, showSettings, currentTheme, showRegister} = this; + const {showSettings, currentTheme} = this; const { classes, currentUser: { @@ -104,8 +89,8 @@ class Layout extends React.Component< }, } = this.props; const theme = themeMap[currentTheme]; - const loginRoute = () => - loggedIn ? : ; + const loginRoute = () => (loggedIn ? : ); + const {version} = config.get('version'); return ( diff --git a/ui/src/user/Login.tsx b/ui/src/user/Login.tsx index 6f7ac61..de5f880 100644 --- a/ui/src/user/Login.tsx +++ b/ui/src/user/Login.tsx @@ -7,14 +7,11 @@ import DefaultPage from '../common/DefaultPage'; import {observable} from 'mobx'; import {observer} from 'mobx-react'; import {inject, Stores} from '../inject'; +import * as config from '../config'; import RegistrationDialog from './Register'; -type Props = Stores<'currentUser'> & { - showRegister: boolean; -}; - @observer -class Login extends Component { +class Login extends Component> { @observable private username = ''; @observable @@ -75,7 +72,7 @@ class Login extends Component { }; private registerButton = () => { - if (this.props.showRegister) + if (config.get('register')) return (