diff --git a/conf/configuration.go b/conf/configuration.go index 9913b66dc..c1b18aa78 100644 --- a/conf/configuration.go +++ b/conf/configuration.go @@ -27,9 +27,10 @@ type nd struct { IgnoredArticles string `default:"The El La Los Las Le Les Os As O A"` IndexGroups string `default:"A B C D E F G H I J K L M N O P Q R S T U V W X-Z(XYZ) [Unknown]([)"` - TranscodingCacheSize string `default:"100MB"` // in MB - ImageCacheSize string `default:"100MB"` // in MB - ProbeCommand string `default:"ffmpeg %s -f ffmetadata"` + EnableTranscodingConfig bool `default:"false"` + TranscodingCacheSize string `default:"100MB"` // in MB + ImageCacheSize string `default:"100MB"` // in MB + ProbeCommand string `default:"ffmpeg %s -f ffmetadata"` // DevFlags. These are used to enable/disable debugging and incomplete features DevLogSourceLine bool `default:"false"` diff --git a/server/app/app.go b/server/app/app.go index 52f7f8fc4..846a83587 100644 --- a/server/app/app.go +++ b/server/app/app.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/deluan/navidrome/assets" + "github.com/deluan/navidrome/conf" "github.com/deluan/navidrome/engine/auth" "github.com/deluan/navidrome/model" "github.com/deluan/rest" @@ -41,12 +42,12 @@ func (app *Router) routes(path string) http.Handler { r.Use(mapAuthHeader()) r.Use(jwtauth.Verifier(auth.TokenAuth)) r.Use(authenticator(app.ds)) - app.R(r, "/user", model.User{}) - app.R(r, "/song", model.MediaFile{}) - app.R(r, "/album", model.Album{}) - app.R(r, "/artist", model.Artist{}) - app.R(r, "/transcoding", model.Transcoding{}) - app.R(r, "/player", model.Player{}) + app.R(r, "/user", model.User{}, true) + app.R(r, "/song", model.MediaFile{}, true) + app.R(r, "/album", model.Album{}, true) + app.R(r, "/artist", model.Artist{}, true) + app.R(r, "/player", model.Player{}, true) + app.R(r, "/transcoding", model.Transcoding{}, conf.Server.EnableTranscodingConfig) // Keepalive endpoint to be used to keep the session valid (ex: while playing songs) r.Get("/keepalive/*", func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(`{"response":"ok"}`)) }) @@ -59,18 +60,22 @@ func (app *Router) routes(path string) http.Handler { return r } -func (app *Router) R(r chi.Router, pathPrefix string, model interface{}) { +func (app *Router) R(r chi.Router, pathPrefix string, model interface{}, persistable bool) { constructor := func(ctx context.Context) rest.Repository { return app.ds.Resource(ctx, model) } r.Route(pathPrefix, func(r chi.Router) { r.Get("/", rest.GetAll(constructor)) - r.Post("/", rest.Post(constructor)) + if persistable { + r.Post("/", rest.Post(constructor)) + } r.Route("/{id:[0-9a-f\\-]+}", func(r chi.Router) { r.Use(UrlParams) r.Get("/", rest.Get(constructor)) - r.Put("/", rest.Put(constructor)) - r.Delete("/", rest.Delete(constructor)) + if persistable { + r.Put("/", rest.Put(constructor)) + r.Delete("/", rest.Delete(constructor)) + } }) }) } diff --git a/server/app/serve_index.go b/server/app/serve_index.go index 2086ef798..ca5482faf 100644 --- a/server/app/serve_index.go +++ b/server/app/serve_index.go @@ -22,10 +22,11 @@ func ServeIndex(ds model.DataStore, fs http.FileSystem) http.HandlerFunc { t := getIndexTemplate(r, fs) appConfig := map[string]interface{}{ - "version": consts.Version(), - "firstTime": firstTime, - "baseURL": strings.TrimSuffix(conf.Server.BaseURL, "/"), - "loginBackgroundURL": conf.Server.UILoginBackgroundURL, + "version": consts.Version(), + "firstTime": firstTime, + "baseURL": strings.TrimSuffix(conf.Server.BaseURL, "/"), + "loginBackgroundURL": conf.Server.UILoginBackgroundURL, + "enableTranscodingConfig": conf.Server.EnableTranscodingConfig, } j, _ := json.Marshal(appConfig) diff --git a/ui/src/common/DocLink.js b/ui/src/common/DocLink.js new file mode 100644 index 000000000..75b0b3c0f --- /dev/null +++ b/ui/src/common/DocLink.js @@ -0,0 +1,10 @@ +import React from 'react' +import { docsUrl } from '../utils/docsUrl' + +const DocLink = ({ path, children }) => ( + + {children} + +) + +export default DocLink diff --git a/ui/src/common/index.js b/ui/src/common/index.js index a725a99c4..382c52353 100644 --- a/ui/src/common/index.js +++ b/ui/src/common/index.js @@ -7,6 +7,7 @@ import SimpleList from './SimpleList' import RangeField, { formatRange } from './RangeField' import SongDetails from './SongDetails' import SizeField from './SizeField' +import DocLink from './DocLink' export { Title, @@ -18,5 +19,6 @@ export { SimpleList, RangeField, SongDetails, + DocLink, formatRange, } diff --git a/ui/src/config.js b/ui/src/config.js index 4dff52cee..513f8d660 100644 --- a/ui/src/config.js +++ b/ui/src/config.js @@ -1,8 +1,12 @@ +// These defaults are only used in development mode. When bundled in the app, +// the __APP_CONFIG__ object is dynamically filled by the ServeIndex function, +// in the /server/app/serve_index.go const defaultConfig = { version: 'dev', firstTime: false, baseURL: '', loginBackgroundURL: 'https://source.unsplash.com/random/1600x900?music', + enableTranscodingConfig: true, } let config diff --git a/ui/src/personal/Personal.js b/ui/src/personal/Personal.js index 441df2386..3ad58fd7c 100644 --- a/ui/src/personal/Personal.js +++ b/ui/src/personal/Personal.js @@ -14,6 +14,7 @@ import HelpOutlineIcon from '@material-ui/icons/HelpOutline' import { changeTheme } from './actions' import themes from '../themes' import i18n from '../i18n' +import { docsUrl } from '../utils/docsUrl' const useStyles = makeStyles({ root: { marginTop: '1em' }, @@ -53,9 +54,7 @@ const SelectLanguage = (props) => { choices={langChoices} onChange={(event) => { if (event.target.value === helpKey) { - openInNewTab( - 'https://www.navidrome.org/docs/developers/translations/' - ) + openInNewTab(docsUrl('/docs/developers/translations/')) return } setLocale(event.target.value) @@ -85,9 +84,7 @@ const SelectTheme = (props) => { choices={themeChoices} onChange={(event) => { if (event.target.value === helpKey) { - openInNewTab( - 'https://www.navidrome.org/docs/developers/creating-themes/' - ) + openInNewTab(docsUrl('/docs/developers/creating-themes/')) return } dispatch(changeTheme(event.target.value)) diff --git a/ui/src/transcoding/TranscodingEdit.js b/ui/src/transcoding/TranscodingEdit.js index 9a6c85c9d..f17e5c315 100644 --- a/ui/src/transcoding/TranscodingEdit.js +++ b/ui/src/transcoding/TranscodingEdit.js @@ -7,6 +7,7 @@ import { SimpleForm, useTranslate, } from 'react-admin' +import { Card, CardContent, Typography, Box } from '@material-ui/core' import { Title } from '../common' const TranscodingTitle = ({ record }) => { @@ -18,29 +19,48 @@ const TranscodingTitle = ({ record }) => { } const TranscodingEdit = (props) => ( - } {...props}> - - - - - - - + <> + + + + + NOTE: + {' '} + Navidrome is currently running with the{' '} + + ND_ENABLETRANSCODINGCONFIG=true + + , making it possible to run system commands from the transcoding + settings using the web interface. We recommend to disable it for + security reasons and only enable it when configuring Transcoding + options. + + + + } {...props}> + + + + + + + + ) export default TranscodingEdit diff --git a/ui/src/transcoding/TranscodingList.js b/ui/src/transcoding/TranscodingList.js index 162ad2b0e..1252511e2 100644 --- a/ui/src/transcoding/TranscodingList.js +++ b/ui/src/transcoding/TranscodingList.js @@ -2,6 +2,7 @@ import React from 'react' import { Datagrid, List, TextField } from 'react-admin' import { useMediaQuery } from '@material-ui/core' import { SimpleList, Title } from '../common' +import config from '../config' const TranscodingList = (props) => { const isXsmall = useMediaQuery((theme) => theme.breakpoints.down('xs')) @@ -23,7 +24,7 @@ const TranscodingList = (props) => { tertiaryText={(r) => r.defaultBitRate} /> ) : ( - + diff --git a/ui/src/transcoding/TranscodingShow.js b/ui/src/transcoding/TranscodingShow.js new file mode 100644 index 000000000..932aec42b --- /dev/null +++ b/ui/src/transcoding/TranscodingShow.js @@ -0,0 +1,40 @@ +import React from 'react' +import { TextField, Show, SimpleShowLayout } from 'react-admin' +import { Card, CardContent, Typography, Box } from '@material-ui/core' +import { Title } from '../common' + +const TranscodingTitle = ({ record }) => { + return +} + +const TranscodingShow = (props) => ( + <> + <Card> + <CardContent> + <Typography> + <Box fontWeight="fontWeightBold" component={'span'}> + NOTE: + </Box>{' '} + Changing the transcoding configuration through the web interface is + disabled for security reasons. If you would like to change (edit or + add) transcoding options, restart the server with the{' '} + <Box fontFamily="Monospace" component={'span'}> + ND_ENABLETRANSCODINGCONFIG=true + </Box>{' '} + configuration option. + </Typography> + </CardContent> + </Card> + + <Show title={<TranscodingTitle />} {...props}> + <SimpleShowLayout> + <TextField source="name" /> + <TextField source="targetFormat" /> + <TextField source="defaultBitRate" /> + <TextField source="command" /> + </SimpleShowLayout> + </Show> + </> +) + +export default TranscodingShow diff --git a/ui/src/transcoding/index.js b/ui/src/transcoding/index.js index 25cd643f8..cb3491920 100644 --- a/ui/src/transcoding/index.js +++ b/ui/src/transcoding/index.js @@ -2,10 +2,13 @@ import TransformIcon from '@material-ui/icons/Transform' import TranscodingList from './TranscodingList' import TranscodingEdit from './TranscodingEdit' import TranscodingCreate from './TranscodingCreate' +import TranscodingShow from './TranscodingShow' +import config from '../config' export default { list: TranscodingList, - edit: TranscodingEdit, - create: TranscodingCreate, + edit: config.enableTranscodingConfig && TranscodingEdit, + create: config.enableTranscodingConfig && TranscodingCreate, + show: !config.enableTranscodingConfig && TranscodingShow, icon: TransformIcon, } diff --git a/ui/src/utils/docsUrl.js b/ui/src/utils/docsUrl.js new file mode 100644 index 000000000..2507cc27e --- /dev/null +++ b/ui/src/utils/docsUrl.js @@ -0,0 +1 @@ +export const docsUrl = (path) => `https://www.navidrome.org${path}`