From d9d690774545e05732c0be2aeb06f7f4e346ac5b Mon Sep 17 00:00:00 2001 From: Lukas Heiligenbrunner Date: Fri, 2 Apr 2021 17:04:15 +0000 Subject: [PATCH] delete useless custombackend popup correct load of subpage when standalone binary ability to set external videourl when using standalone binary --- .gitlab-ci.yml | 15 +++- apiGo/api/ApiBase.go | 6 +- apiGo/api/Init.go | 8 +- apiGo/main.go | 12 ++- apiGo/static/StaticServe.go | 89 +++++++++++++++++++ apiGo/static/StaticServe_apionly.go | 6 ++ src/App.tsx | 37 +++----- .../NoBackendConnectionPopup.test.js | 29 ------ .../NoBackendConnectionPopup.tsx | 27 ------ src/pages/Player/Player.tsx | 6 +- src/pages/SettingsPage/GeneralSettings.tsx | 37 +------- src/utils/Api.ts | 44 ++------- src/utils/GlobalInfos.ts | 2 + 13 files changed, 145 insertions(+), 173 deletions(-) create mode 100644 apiGo/static/StaticServe.go create mode 100644 apiGo/static/StaticServe_apionly.go delete mode 100644 src/elements/Popups/NoBackendConnectionPopup/NoBackendConnectionPopup.test.js delete mode 100644 src/elements/Popups/NoBackendConnectionPopup/NoBackendConnectionPopup.tsx diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 62f29d6..ebf243d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,13 +1,14 @@ image: node:14 stages: - - build + - build_frontend + - build_backend - test - packaging - deploy Minimize_Frontend: - stage: build + stage: build_frontend before_script: - yarn install --cache-folder .yarn script: @@ -25,11 +26,15 @@ Minimize_Frontend: Build_Backend: image: golang:latest - stage: build + stage: build_backend script: - cd apiGo - go build -v -o openmediacenter - - env GOOS=windows GOARCH=amd64 go build -v -o openmediacenter.exe + - cp -r ../build/ ./static/ + - go build -v -tags static -o openmediacenter_full + - env GOOS=windows GOARCH=amd64 go build -v -tags static -o openmediacenter.exe + needs: + - Minimize_Frontend artifacts: expire_in: 2 days paths: @@ -41,6 +46,7 @@ Frontend_Tests: - yarn install --cache-folder .yarn script: - yarn run test + needs: [] artifacts: reports: junit: @@ -58,6 +64,7 @@ Backend_Tests: - cd apiGo - go get -u github.com/jstemmer/go-junit-report - go test -v ./... 2>&1 | go-junit-report -set-exit-code > report.xml + needs: [] artifacts: when: always reports: diff --git a/apiGo/api/ApiBase.go b/apiGo/api/ApiBase.go index 26e0e1e..8914dfb 100644 --- a/apiGo/api/ApiBase.go +++ b/apiGo/api/ApiBase.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/json" "fmt" - "log" "net/http" "openmediacenter/apiGo/api/oauth" ) @@ -37,7 +36,7 @@ func AddHandler(action string, apiNode int, n interface{}, h func() []byte) { handlers = append(handlers, Handler{action, h, n, apiNode}) } -func ServerInit(port uint16) { +func ServerInit() { http.Handle(APIPREFIX+"/video", oauth.ValidateToken(videoHandler)) http.Handle(APIPREFIX+"/tags", oauth.ValidateToken(tagHandler)) http.Handle(APIPREFIX+"/settings", oauth.ValidateToken(settingsHandler)) @@ -48,9 +47,6 @@ func ServerInit(port uint16) { // initialize oauth service and add corresponding auth routes oauth.InitOAuth() - - fmt.Printf("OpenMediacenter server up and running on port %d\n", port) - log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil)) } func handleAPICall(action string, requestBody string, apiNode int) []byte { diff --git a/apiGo/api/Init.go b/apiGo/api/Init.go index 0306154..b79eda9 100644 --- a/apiGo/api/Init.go +++ b/apiGo/api/Init.go @@ -3,6 +3,8 @@ package api import ( "encoding/json" "openmediacenter/apiGo/database/settings" + "regexp" + "strings" ) func AddInitHandlers() { @@ -20,11 +22,15 @@ func passwordNeeded() { VideoPath string } + regexMatchUrl := regexp.MustCompile("^http(|s):\\/\\/([0-9]){1,3}\\.([0-9]){1,3}\\.([0-9]){1,3}\\.([0-9]){1,3}:[0-9]{1,5}") + videoUrl := regexMatchUrl.FindString(sett.VideoPath) + serverVideoPath := strings.TrimPrefix(sett.VideoPath, videoUrl) + res := InitialDataTypeResponse{ DarkMode: sett.DarkMode, Pasword: sett.Pasword != "-1", MediacenterName: sett.Mediacenter_name, - VideoPath: sett.VideoPath, + VideoPath: serverVideoPath, } str, _ := json.Marshal(res) diff --git a/apiGo/main.go b/apiGo/main.go index 68674c8..77652c5 100644 --- a/apiGo/main.go +++ b/apiGo/main.go @@ -3,12 +3,16 @@ package main import ( "flag" "fmt" + "log" + "net/http" "openmediacenter/apiGo/api" "openmediacenter/apiGo/database" + "openmediacenter/apiGo/static" ) func main() { fmt.Println("init OpenMediaCenter server") + port := 8081 db, verbose, pathPrefix := handleCommandLineArguments() // todo some verbosity logger or sth @@ -28,7 +32,13 @@ func main() { api.AddActorsHandlers() api.AddInitHandlers() - api.ServerInit(8081) + // add the static files + static.ServeStaticFiles() + + api.ServerInit() + + fmt.Printf("OpenMediacenter server up and running on port %d\n", port) + log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil)) } func handleCommandLineArguments() (*database.DatabaseConfig, bool, *string) { diff --git a/apiGo/static/StaticServe.go b/apiGo/static/StaticServe.go new file mode 100644 index 0000000..e694302 --- /dev/null +++ b/apiGo/static/StaticServe.go @@ -0,0 +1,89 @@ +// +build static + +package static + +import ( + "embed" + "fmt" + "io/fs" + "net/http" + "net/http/httputil" + "net/url" + "openmediacenter/apiGo/database/settings" + "regexp" + "strings" +) + +//go:embed build +var staticFiles embed.FS + +func ServeStaticFiles() { + // http.FS can be used to create a http Filesystem + subfs, _ := fs.Sub(staticFiles, "build") + staticFS := http.FS(subfs) + fs := http.FileServer(staticFS) + + // Serve static files + http.Handle("/", validatePrefix(fs)) + + // we need to proxy the videopath to somewhere in a standalone binary + proxyVideoURL() +} + +type handler struct { + proxy *httputil.ReverseProxy +} + +func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + h.proxy.ServeHTTP(w, r) +} + +func proxyVideoURL() { + conf := settings.LoadSettings() + + // match base url + regexMatchUrl := regexp.MustCompile("^http(|s):\\/\\/([0-9]){1,3}\\.([0-9]){1,3}\\.([0-9]){1,3}\\.([0-9]){1,3}:[0-9]{1,5}") + + var videoUrl *url.URL + if regexMatchUrl.MatchString(conf.VideoPath) { + fmt.Println("matches string...") + var err error + videoUrl, err = url.Parse(regexMatchUrl.FindString(conf.VideoPath)) + if err != nil { + panic(err) + } + } else { + videoUrl, _ = url.Parse("http://127.0.0.1:8081") + } + + director := func(req *http.Request) { + req.URL.Scheme = videoUrl.Scheme + req.URL.Host = videoUrl.Host + } + + serverVideoPath := strings.TrimPrefix(conf.VideoPath, regexMatchUrl.FindString(conf.VideoPath)) + + reverseProxy := &httputil.ReverseProxy{Director: director} + handler := handler{proxy: reverseProxy} + http.Handle(serverVideoPath, handler) +} + +// ValidatePrefix check if requested path is a file -- if not proceed with index.html +func validatePrefix(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + regex := regexp.MustCompile("\\..*$") + matchFile := regex.MatchString(r.URL.Path) + + if matchFile { + h.ServeHTTP(w, r) + } else { + r2 := new(http.Request) + *r2 = *r + r2.URL = new(url.URL) + *r2.URL = *r.URL + r2.URL.Path = "/" + r2.URL.RawPath = "/" + h.ServeHTTP(w, r2) + } + }) +} diff --git a/apiGo/static/StaticServe_apionly.go b/apiGo/static/StaticServe_apionly.go new file mode 100644 index 0000000..5fcd293 --- /dev/null +++ b/apiGo/static/StaticServe_apionly.go @@ -0,0 +1,6 @@ +// +build !static + +package static + +// add nothing on no static build +func ServeStaticFiles() {} diff --git a/src/App.tsx b/src/App.tsx index 42c783b..8f7ff4a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,7 +10,6 @@ import style from './App.module.css'; import SettingsPage from './pages/SettingsPage/SettingsPage'; import CategoryPage from './pages/CategoryPage/CategoryPage'; import {APINode, apiTokenValid, callApiUnsafe, refreshAPIToken} from './utils/Api'; -import {NoBackendConnectionPopup} from './elements/Popups/NoBackendConnectionPopup/NoBackendConnectionPopup'; import {BrowserRouter as Router, NavLink, Route, Switch} from 'react-router-dom'; import Player from './pages/Player/Player'; @@ -22,7 +21,6 @@ import AuthenticationPage from './pages/AuthenticationPage/AuthenticationPage'; interface state { password: boolean | null; // null if uninitialized - true if pwd needed false if not needed mediacentername: string; - onapierror: boolean; } /** @@ -50,7 +48,6 @@ class App extends React.Component<{}, state> { this.state = { mediacentername: 'OpenMediaCenter', - onapierror: false, password: pwdneeded }; @@ -77,26 +74,18 @@ class App extends React.Component<{}, state> { initialAPICall(): void { // this is the first api call so if it fails we know there is no connection to backend - callApiUnsafe( - APINode.Init, - {action: 'loadInitialData'}, - (result: SettingsTypes.initialApiCallData) => { - // set theme - GlobalInfos.enableDarkTheme(result.DarkMode); + callApiUnsafe(APINode.Init, {action: 'loadInitialData'}, (result: SettingsTypes.initialApiCallData) => { + // set theme + GlobalInfos.enableDarkTheme(result.DarkMode); - GlobalInfos.setVideoPath(result.VideoPath); + GlobalInfos.setVideoPath(result.VideoPath); - this.setState({ - mediacentername: result.MediacenterName, - onapierror: false - }); - // set tab title to received mediacenter name - document.title = result.MediacenterName; - }, - () => { - this.setState({onapierror: true}); - } - ); + this.setState({ + mediacentername: result.MediacenterName + }); + // set tab title to received mediacenter name + document.title = result.MediacenterName; + }); } componentDidMount(): void { @@ -148,7 +137,6 @@ class App extends React.Component<{}, state> { {this.routing()} - {this.state.onapierror ? this.ApiError() : null} ); } else { @@ -183,11 +171,6 @@ class App extends React.Component<{}, state> { ); } - - ApiError(): JSX.Element { - // on api error show popup and retry and show again if failing.. - return this.initialAPICall()} />; - } } export default App; diff --git a/src/elements/Popups/NoBackendConnectionPopup/NoBackendConnectionPopup.test.js b/src/elements/Popups/NoBackendConnectionPopup/NoBackendConnectionPopup.test.js deleted file mode 100644 index 6d3bd98..0000000 --- a/src/elements/Popups/NoBackendConnectionPopup/NoBackendConnectionPopup.test.js +++ /dev/null @@ -1,29 +0,0 @@ -import {shallow} from 'enzyme'; -import React from 'react'; -import {NoBackendConnectionPopup} from './NoBackendConnectionPopup'; -import {getBackendDomain} from '../../../utils/Api'; - -describe('', function () { - it('renders without crashing ', function () { - const wrapper = shallow( {}}/>); - wrapper.unmount(); - }); - - it('hides on refresh click', function () { - const func = jest.fn(); - const wrapper = shallow(); - - expect(func).toBeCalledTimes(0); - wrapper.find('button').simulate('click'); - - expect(func).toBeCalledTimes(1); - }); - - it('simulate change of textfield', function () { - const wrapper = shallow( {}}/>); - - wrapper.find('input').simulate('change', {target: {value: 'testvalue'}}); - - expect(getBackendDomain()).toBe('testvalue'); - }); -}); diff --git a/src/elements/Popups/NoBackendConnectionPopup/NoBackendConnectionPopup.tsx b/src/elements/Popups/NoBackendConnectionPopup/NoBackendConnectionPopup.tsx deleted file mode 100644 index 3b3f28e..0000000 --- a/src/elements/Popups/NoBackendConnectionPopup/NoBackendConnectionPopup.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import PopupBase from '../PopupBase'; -import style from '../NewActorPopup/NewActorPopup.module.css'; -import {setCustomBackendDomain} from '../../../utils/Api'; - -interface NBCProps { - onHide: (_: void) => void; -} - -export function NoBackendConnectionPopup(props: NBCProps): JSX.Element { - return ( - -
- { - setCustomBackendDomain(v.target.value); - }} - /> -
- -
- ); -} diff --git a/src/pages/Player/Player.tsx b/src/pages/Player/Player.tsx index a7d1f35..dde90f9 100644 --- a/src/pages/Player/Player.tsx +++ b/src/pages/Player/Player.tsx @@ -13,7 +13,7 @@ import {faPlusCircle} from '@fortawesome/free-solid-svg-icons'; import AddActorPopup from '../../elements/Popups/AddActorPopup/AddActorPopup'; import ActorTile from '../../elements/ActorTile/ActorTile'; import {withRouter} from 'react-router-dom'; -import {APINode, callAPI, getBackendDomain} from '../../utils/Api'; +import {APINode, callAPI} from '../../utils/Api'; import {RouteComponentProps} from 'react-router'; import {GeneralSuccess} from '../../types/GeneralTypes'; import {ActorType, TagType} from '../../types/VideoTypes'; @@ -289,9 +289,7 @@ export class Player extends React.Component { src: (process.env.REACT_APP_CUST_BACK_DOMAIN ? process.env.REACT_APP_CUST_BACK_DOMAIN - : getBackendDomain()) + - GlobalInfos.getVideoPath() + - result.MovieUrl, + : GlobalInfos.getVideoPath()) + result.MovieUrl, type: 'video/mp4', size: 1080 } diff --git a/src/pages/SettingsPage/GeneralSettings.tsx b/src/pages/SettingsPage/GeneralSettings.tsx index df2b6a7..4c604e9 100644 --- a/src/pages/SettingsPage/GeneralSettings.tsx +++ b/src/pages/SettingsPage/GeneralSettings.tsx @@ -6,13 +6,11 @@ import InfoHeaderItem from '../../elements/InfoHeaderItem/InfoHeaderItem'; import {faArchive, faBalanceScaleLeft, faRulerVertical} from '@fortawesome/free-solid-svg-icons'; import {faAddressCard} from '@fortawesome/free-regular-svg-icons'; import {version} from '../../../package.json'; -import {APINode, callAPI, setCustomBackendDomain} from '../../utils/Api'; +import {APINode, callAPI} from '../../utils/Api'; import {SettingsTypes} from '../../types/ApiTypes'; import {GeneralSuccess} from '../../types/GeneralTypes'; interface state { - customapi: boolean; - apipath: string; generalSettings: SettingsTypes.loadGeneralSettingsType; } @@ -27,8 +25,6 @@ class GeneralSettings extends React.Component { super(props); this.state = { - customapi: false, - apipath: '', generalSettings: { DarkMode: true, DBSize: 0, @@ -121,35 +117,6 @@ class GeneralSettings extends React.Component { /> - - { - if (this.state.customapi) { - setCustomBackendDomain(''); - } - - this.setState({customapi: !this.state.customapi}); - }} - /> - {this.state.customapi ? ( - - API Backend url - { - this.setState({apipath: e.target.value}); - setCustomBackendDomain(e.target.value); - }} - /> - - ) : null} - { checked={GlobalInfos.isDarkTheme()} onChange={(): void => { GlobalInfos.enableDarkTheme(!GlobalInfos.isDarkTheme()); - this.forceUpdate(); - // todo initiate rerender }} /> diff --git a/src/utils/Api.ts b/src/utils/Api.ts index 133478e..0649014 100644 --- a/src/utils/Api.ts +++ b/src/utils/Api.ts @@ -1,40 +1,6 @@ import GlobalInfos from './GlobalInfos'; -let customBackendURL: string; - -/** - * get the domain of the api backend - * @return string domain of backend http://x.x.x.x/bla - */ -export function getBackendDomain(): string { - let userAgent = navigator.userAgent.toLowerCase(); - if (userAgent.indexOf(' electron/') > -1) { - // Electron-specific code - force a custom backendurl - return customBackendURL; - } else { - // use custom only if defined - if (customBackendURL) { - return customBackendURL; - } else { - return window.location.origin; - } - } -} - -/** - * set a custom backend domain - * @param domain a url in format [http://x.x.x.x/somanode] - */ -export function setCustomBackendDomain(domain: string): void { - customBackendURL = domain; -} - -/** - * a helper function to get the api path - */ -function getAPIDomain(): string { - return getBackendDomain() + '/api/'; -} +const APIPREFIX: string = '/api/'; /** * interface how an api request should look like @@ -96,7 +62,7 @@ export function refreshAPIToken(callback: (error: string) => void, force?: boole token_type: string; // no camel case allowed because of backendlib } - fetch(getBackendDomain() + '/token', {method: 'POST', body: formData}).then((response) => + fetch('/token', {method: 'POST', body: formData}).then((response) => response.json().then((result: APIToken) => { if (result.error) { callFuncQue(result.error); @@ -223,7 +189,7 @@ export function callAPI( ): void { checkAPITokenValid(() => { console.log(apiToken); - fetch(getAPIDomain() + apinode, { + fetch(APIPREFIX + apinode, { method: 'POST', body: JSON.stringify(fd), headers: new Headers({ @@ -267,7 +233,7 @@ export function callApiUnsafe( callback: (_: T) => void, errorcallback?: (_: string) => void ): void { - fetch(getAPIDomain() + apinode, {method: 'POST', body: JSON.stringify(fd)}) + fetch(APIPREFIX + apinode, {method: 'POST', body: JSON.stringify(fd)}) .then((response) => { if (response.status !== 200) { console.log('Error: ' + response.statusText); @@ -289,7 +255,7 @@ export function callApiUnsafe( */ export function callAPIPlain(apinode: APINode, fd: ApiBaseRequest, callback: (_: string) => void): void { checkAPITokenValid(() => { - fetch(getAPIDomain() + apinode, { + fetch(APIPREFIX + apinode, { method: 'POST', body: JSON.stringify(fd), headers: new Headers({ diff --git a/src/utils/GlobalInfos.ts b/src/utils/GlobalInfos.ts index a50ff2a..37f4065 100644 --- a/src/utils/GlobalInfos.ts +++ b/src/utils/GlobalInfos.ts @@ -23,6 +23,8 @@ class StaticInfos { */ enableDarkTheme(enable = true): void { this.darktheme = enable; + + // trigger onThemeChange handlers this.handlers.map((func) => { return func(); });