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.OnUserDeleted(pluginManager.RemoveUser)
|
||||||
userChangeNotifier.OnUserAdded(pluginManager.InitializeForUserID)
|
userChangeNotifier.OnUserAdded(pluginManager.InitializeForUserID)
|
||||||
|
|
||||||
ui.Register(g)
|
ui.Register(g, *vInfo, conf.Registration)
|
||||||
|
|
||||||
g.GET("/health", healthHandler.Health)
|
g.GET("/health", healthHandler.Health)
|
||||||
g.GET("/swagger", docs.Serve)
|
g.GET("/swagger", docs.Serve)
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ rules:
|
||||||
jest/expect-expect: off
|
jest/expect-expect: off
|
||||||
jest/no-jasmine-globals: off
|
jest/no-jasmine-globals: off
|
||||||
"@typescript-eslint/require-await": off
|
"@typescript-eslint/require-await": off
|
||||||
|
"@typescript-eslint/restrict-template-expressions": off
|
||||||
|
|
||||||
"@typescript-eslint/array-type": [error, {default: array-simple}]
|
"@typescript-eslint/array-type": [error, {default: array-simple}]
|
||||||
"@typescript-eslint/await-thenable": error
|
"@typescript-eslint/await-thenable": error
|
||||||
|
|
|
||||||
|
|
@ -35,5 +35,8 @@
|
||||||
Gotify requires JavaScript.
|
Gotify requires JavaScript.
|
||||||
</noscript>
|
</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
<% if (process.env.NODE_ENV === 'production') { %>
|
||||||
|
<script>window.config = %CONFIG%;</script>
|
||||||
|
<% } %>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
37
ui/serve.go
37
ui/serve.go
|
|
@ -1,32 +1,51 @@
|
||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-contrib/gzip"
|
"github.com/gin-contrib/gzip"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gobuffalo/packr/v2"
|
"github.com/gobuffalo/packr/v2"
|
||||||
|
"github.com/gotify/server/v2/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
var box = packr.New("ui", "../ui/build")
|
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.
|
// 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 := r.Group("/", gzip.Gzip(gzip.DefaultCompression))
|
||||||
ui.GET("/", serveFile("index.html", "text/html"))
|
ui.GET("/", serveFile("index.html", "text/html", func(content string) string {
|
||||||
ui.GET("/index.html", serveFile("index.html", "text/html"))
|
return strings.Replace(content, "%CONFIG%", string(uiConfigBytes), 1)
|
||||||
ui.GET("/manifest.json", serveFile("manifest.json", "application/json"))
|
}))
|
||||||
ui.GET("/assets-manifest.json", serveFile("asserts-manifest.json", "application/json"))
|
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)))
|
ui.GET("/static/*any", gin.WrapH(http.FileServer(box)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveFile(name, contentType string) gin.HandlerFunc {
|
func noop(s string) string {
|
||||||
return func(ctx *gin.Context) {
|
return s
|
||||||
ctx.Header("Content-Type", contentType)
|
}
|
||||||
|
|
||||||
|
func serveFile(name, contentType string, convert func(string) string) gin.HandlerFunc {
|
||||||
content, err := box.FindString(name)
|
content, err := box.FindString(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
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 {
|
export interface IConfig {
|
||||||
url: string;
|
url: string;
|
||||||
|
register: boolean;
|
||||||
|
version: IVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
let config: IConfig;
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
declare global {
|
||||||
export function set(c: IConfig) {
|
interface Window {
|
||||||
config = c;
|
config?: Partial<IConfig>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function get(val: 'url'): string {
|
const config: IConfig = {
|
||||||
return config[val];
|
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 {PluginStore} from './plugin/PluginStore';
|
||||||
import {registerReactions} from './reactions';
|
import {registerReactions} from './reactions';
|
||||||
|
|
||||||
const defaultDevConfig = {
|
const devUrl = 'http://localhost:3000/';
|
||||||
url: 'http://localhost:3000/',
|
|
||||||
};
|
|
||||||
|
|
||||||
const {port, hostname, protocol, pathname} = window.location;
|
const {port, hostname, protocol, pathname} = window.location;
|
||||||
const slashes = protocol.concat('//');
|
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 url = slashes.concat(port ? hostname.concat(':', port) : hostname) + path;
|
||||||
const urlWithSlash = url.endsWith('/') ? url : url.concat('/');
|
const urlWithSlash = url.endsWith('/') ? url : url.concat('/');
|
||||||
|
|
||||||
const defaultProdConfig = {
|
const prodUrl = urlWithSlash;
|
||||||
url: urlWithSlash,
|
|
||||||
};
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
declare global {
|
|
||||||
interface Window {
|
|
||||||
config: config.IConfig;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const initStores = (): StoreMapping => {
|
const initStores = (): StoreMapping => {
|
||||||
const snackManager = new SnackManager();
|
const snackManager = new SnackManager();
|
||||||
|
|
@ -62,9 +51,10 @@ const initStores = (): StoreMapping => {
|
||||||
|
|
||||||
(function clientJS() {
|
(function clientJS() {
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
config.set(window.config || defaultProdConfig);
|
config.set('url', prodUrl);
|
||||||
} else {
|
} else {
|
||||||
config.set(window.config || defaultDevConfig);
|
config.set('url', devUrl);
|
||||||
|
config.set('register', true);
|
||||||
}
|
}
|
||||||
const stores = initStores();
|
const stores = initStores();
|
||||||
initAxios(stores.currentUser, stores.snackManager.snack);
|
initAxios(stores.currentUser, stores.snackManager.snack);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import {createMuiTheme, MuiThemeProvider, Theme, WithStyles, withStyles} from '@material-ui/core';
|
import {createMuiTheme, MuiThemeProvider, Theme, WithStyles, withStyles} from '@material-ui/core';
|
||||||
import CssBaseline from '@material-ui/core/CssBaseline';
|
import CssBaseline from '@material-ui/core/CssBaseline';
|
||||||
import axios, {AxiosResponse} from 'axios';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {HashRouter, Redirect, Route, Switch} from 'react-router-dom';
|
import {HashRouter, Redirect, Route, Switch} from 'react-router-dom';
|
||||||
import Header from './Header';
|
import Header from './Header';
|
||||||
|
|
@ -21,7 +20,6 @@ import {observer} from 'mobx-react';
|
||||||
import {observable} from 'mobx';
|
import {observable} from 'mobx';
|
||||||
import {inject, Stores} from '../inject';
|
import {inject, Stores} from '../inject';
|
||||||
import {ConnectionErrorBanner} from '../common/ConnectionErrorBanner';
|
import {ConnectionErrorBanner} from '../common/ConnectionErrorBanner';
|
||||||
import {IVersion} from '../types';
|
|
||||||
|
|
||||||
const styles = (theme: Theme) => ({
|
const styles = (theme: Theme) => ({
|
||||||
content: {
|
content: {
|
||||||
|
|
@ -57,31 +55,18 @@ const isThemeKey = (value: string | null): value is ThemeKey =>
|
||||||
class Layout extends React.Component<
|
class Layout extends React.Component<
|
||||||
WithStyles<'content'> & Stores<'currentUser' | 'snackManager'>
|
WithStyles<'content'> & Stores<'currentUser' | 'snackManager'>
|
||||||
> {
|
> {
|
||||||
private static defaultVersion = '0.0.0';
|
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
private currentTheme: ThemeKey = 'dark';
|
private currentTheme: ThemeKey = 'dark';
|
||||||
@observable
|
@observable
|
||||||
private showSettings = false;
|
private showSettings = false;
|
||||||
@observable
|
@observable
|
||||||
private version = Layout.defaultVersion;
|
|
||||||
@observable
|
|
||||||
private navOpen = false;
|
private navOpen = false;
|
||||||
@observable
|
|
||||||
private showRegister = true; //TODO https://github.com/gotify/server/pull/394#discussion_r650559205
|
|
||||||
|
|
||||||
private setNavOpen(open: boolean) {
|
private setNavOpen(open: boolean) {
|
||||||
this.navOpen = open;
|
this.navOpen = open;
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidMount() {
|
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);
|
const localStorageTheme = window.localStorage.getItem(localStorageThemeKey);
|
||||||
if (isThemeKey(localStorageTheme)) {
|
if (isThemeKey(localStorageTheme)) {
|
||||||
this.currentTheme = localStorageTheme;
|
this.currentTheme = localStorageTheme;
|
||||||
|
|
@ -91,7 +76,7 @@ class Layout extends React.Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const {version, showSettings, currentTheme, showRegister} = this;
|
const {showSettings, currentTheme} = this;
|
||||||
const {
|
const {
|
||||||
classes,
|
classes,
|
||||||
currentUser: {
|
currentUser: {
|
||||||
|
|
@ -104,8 +89,8 @@ class Layout extends React.Component<
|
||||||
},
|
},
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const theme = themeMap[currentTheme];
|
const theme = themeMap[currentTheme];
|
||||||
const loginRoute = () =>
|
const loginRoute = () => (loggedIn ? <Redirect to="/" /> : <Login />);
|
||||||
loggedIn ? <Redirect to="/" /> : <Login showRegister={showRegister} />;
|
const {version} = config.get('version');
|
||||||
return (
|
return (
|
||||||
<MuiThemeProvider theme={theme}>
|
<MuiThemeProvider theme={theme}>
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,11 @@ import DefaultPage from '../common/DefaultPage';
|
||||||
import {observable} from 'mobx';
|
import {observable} from 'mobx';
|
||||||
import {observer} from 'mobx-react';
|
import {observer} from 'mobx-react';
|
||||||
import {inject, Stores} from '../inject';
|
import {inject, Stores} from '../inject';
|
||||||
|
import * as config from '../config';
|
||||||
import RegistrationDialog from './Register';
|
import RegistrationDialog from './Register';
|
||||||
|
|
||||||
type Props = Stores<'currentUser'> & {
|
|
||||||
showRegister: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class Login extends Component<Props> {
|
class Login extends Component<Stores<'currentUser'>> {
|
||||||
@observable
|
@observable
|
||||||
private username = '';
|
private username = '';
|
||||||
@observable
|
@observable
|
||||||
|
|
@ -75,7 +72,7 @@ class Login extends Component<Props> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private registerButton = () => {
|
private registerButton = () => {
|
||||||
if (this.props.showRegister)
|
if (config.get('register'))
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
id="register"
|
id="register"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue