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.
This commit is contained in:
parent
c172590b92
commit
36eb8d8b2b
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -35,5 +35,8 @@
|
|||
Gotify requires JavaScript.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
<% if (process.env.NODE_ENV === 'production') { %>
|
||||
<script>window.config = %CONFIG%;</script>
|
||||
<% } %>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
37
ui/serve.go
37
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 {
|
||||
return func(ctx *gin.Context) {
|
||||
ctx.Header("Content-Type", contentType)
|
||||
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)
|
||||
}
|
||||
ctx.String(200, content)
|
||||
converted := convert(content)
|
||||
return func(ctx *gin.Context) {
|
||||
ctx.Header("Content-Type", contentType)
|
||||
ctx.String(200, converted)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<IConfig>;
|
||||
}
|
||||
}
|
||||
|
||||
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 extends keyof IConfig>(key: Key, value: IConfig[Key]): void {
|
||||
config[key] = value;
|
||||
}
|
||||
|
||||
export function get<K extends keyof IConfig>(key: K): IConfig[K] {
|
||||
return config[key];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<IVersion>) => {
|
||||
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 ? <Redirect to="/" /> : <Login showRegister={showRegister} />;
|
||||
const loginRoute = () => (loggedIn ? <Redirect to="/" /> : <Login />);
|
||||
const {version} = config.get('version');
|
||||
return (
|
||||
<MuiThemeProvider theme={theme}>
|
||||
<HashRouter>
|
||||
|
|
|
|||
|
|
@ -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<Props> {
|
||||
class Login extends Component<Stores<'currentUser'>> {
|
||||
@observable
|
||||
private username = '';
|
||||
@observable
|
||||
|
|
@ -75,7 +72,7 @@ class Login extends Component<Props> {
|
|||
};
|
||||
|
||||
private registerButton = () => {
|
||||
if (this.props.showRegister)
|
||||
if (config.get('register'))
|
||||
return (
|
||||
<Button
|
||||
id="register"
|
||||
|
|
|
|||
Loading…
Reference in New Issue