attempt number 2
This commit is contained in:
parent
1529e796df
commit
8fc8f72103
@ -140,6 +140,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||||||
fmt.Fprintf(w, "\tAddress:\t%s\n", ser.Address)
|
fmt.Fprintf(w, "\tAddress:\t%s\n", ser.Address)
|
||||||
fmt.Fprintf(w, "\tTLS Cert:\t%s\n", ser.TLSCert)
|
fmt.Fprintf(w, "\tTLS Cert:\t%s\n", ser.TLSCert)
|
||||||
fmt.Fprintf(w, "\tTLS Key:\t%s\n", ser.TLSKey)
|
fmt.Fprintf(w, "\tTLS Key:\t%s\n", ser.TLSKey)
|
||||||
|
fmt.Fprintf(w, "\tExec Enabled:\t%t\n", ser.EnableExec)
|
||||||
fmt.Fprintln(w, "\nDefaults:")
|
fmt.Fprintln(w, "\nDefaults:")
|
||||||
fmt.Fprintf(w, "\tScope:\t%s\n", set.Defaults.Scope)
|
fmt.Fprintf(w, "\tScope:\t%s\n", set.Defaults.Scope)
|
||||||
fmt.Fprintf(w, "\tLocale:\t%s\n", set.Defaults.Locale)
|
fmt.Fprintf(w, "\tLocale:\t%s\n", set.Defaults.Locale)
|
||||||
|
|||||||
@ -64,6 +64,7 @@ func addServerFlags(flags *pflag.FlagSet) {
|
|||||||
flags.Int("img-processors", 4, "image processors count")
|
flags.Int("img-processors", 4, "image processors count")
|
||||||
flags.Bool("disable-thumbnails", false, "disable image thumbnails")
|
flags.Bool("disable-thumbnails", false, "disable image thumbnails")
|
||||||
flags.Bool("disable-preview-resize", false, "disable resize of image previews")
|
flags.Bool("disable-preview-resize", false, "disable resize of image previews")
|
||||||
|
flags.Bool("disable-exec", false, "disables Command Runner feature")
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
@ -241,6 +242,9 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
|
|||||||
_, disablePreviewResize := getParamB(flags, "disable-preview-resize")
|
_, disablePreviewResize := getParamB(flags, "disable-preview-resize")
|
||||||
server.ResizePreview = !disablePreviewResize
|
server.ResizePreview = !disablePreviewResize
|
||||||
|
|
||||||
|
_, disableExec := getParamB(flags, "disable-exec")
|
||||||
|
server.EnableExec = !disableExec
|
||||||
|
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="[{[ .StaticURL ]}]/img/icons/favicon-32x32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="[{[ .StaticURL ]}]/img/icons/favicon-32x32.png">
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="[{[ .StaticURL ]}]/img/icons/favicon-16x16.png">
|
<link rel="icon" type="image/png" sizes="16x16" href="[{[ .StaticURL ]}]/img/icons/favicon-16x16.png">
|
||||||
|
|
||||||
<!-- Add to home screen for Android and modern mobile browsers -->
|
<!-- Add to home screen for Android and modern mobile browsers -->
|
||||||
<link rel="manifest" id="manifestPlaceholder" crossorigin="use-credentials">
|
<link rel="manifest" id="manifestPlaceholder" crossorigin="use-credentials">
|
||||||
<meta name="theme-color" content="#2979ff">
|
<meta name="theme-color" content="#2979ff">
|
||||||
@ -31,7 +31,7 @@
|
|||||||
<!-- Inject Some Variables and generate the manifest json -->
|
<!-- Inject Some Variables and generate the manifest json -->
|
||||||
<script>
|
<script>
|
||||||
window.FileBrowser = JSON.parse(`[{[ .Json ]}]`);
|
window.FileBrowser = JSON.parse(`[{[ .Json ]}]`);
|
||||||
|
|
||||||
var fullStaticURL = window.location.origin + window.FileBrowser.StaticURL;
|
var fullStaticURL = window.location.origin + window.FileBrowser.StaticURL;
|
||||||
var dynamicManifest = {
|
var dynamicManifest = {
|
||||||
"name": window.FileBrowser.Name || 'File Browser',
|
"name": window.FileBrowser.Name || 'File Browser',
|
||||||
|
|||||||
@ -37,7 +37,7 @@
|
|||||||
<delete-button v-show="showDeleteButton"></delete-button>
|
<delete-button v-show="showDeleteButton"></delete-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<shell-button v-show="user.perm.execute" />
|
<shell-button v-if="isExecEnabled && user.perm.execute" />
|
||||||
<switch-button v-show="isListing"></switch-button>
|
<switch-button v-show="isListing"></switch-button>
|
||||||
<download-button v-show="showDownloadButton"></download-button>
|
<download-button v-show="showDownloadButton"></download-button>
|
||||||
<upload-button v-show="showUpload"></upload-button>
|
<upload-button v-show="showUpload"></upload-button>
|
||||||
@ -68,7 +68,7 @@ import CopyButton from './buttons/Copy'
|
|||||||
import ShareButton from './buttons/Share'
|
import ShareButton from './buttons/Share'
|
||||||
import ShellButton from './buttons/Shell'
|
import ShellButton from './buttons/Shell'
|
||||||
import {mapGetters, mapState} from 'vuex'
|
import {mapGetters, mapState} from 'vuex'
|
||||||
import { logoURL } from '@/utils/constants'
|
import { logoURL, enableExec } from '@/utils/constants'
|
||||||
import * as api from '@/api'
|
import * as api from '@/api'
|
||||||
import buttons from '@/utils/buttons'
|
import buttons from '@/utils/buttons'
|
||||||
|
|
||||||
@ -120,6 +120,7 @@ export default {
|
|||||||
'multiple'
|
'multiple'
|
||||||
]),
|
]),
|
||||||
logoURL: () => logoURL,
|
logoURL: () => logoURL,
|
||||||
|
isExecEnabled: () => enableExec,
|
||||||
isMobile () {
|
isMobile () {
|
||||||
return this.width <= 736
|
return this.width <= 736
|
||||||
},
|
},
|
||||||
|
|||||||
@ -9,13 +9,14 @@
|
|||||||
<p><input type="checkbox" :disabled="admin" v-model="perm.delete"> {{ $t('settings.perm.delete') }}</p>
|
<p><input type="checkbox" :disabled="admin" v-model="perm.delete"> {{ $t('settings.perm.delete') }}</p>
|
||||||
<p><input type="checkbox" :disabled="admin" v-model="perm.download"> {{ $t('settings.perm.download') }}</p>
|
<p><input type="checkbox" :disabled="admin" v-model="perm.download"> {{ $t('settings.perm.download') }}</p>
|
||||||
<p><input type="checkbox" :disabled="admin" v-model="perm.modify"> {{ $t('settings.perm.modify') }}</p>
|
<p><input type="checkbox" :disabled="admin" v-model="perm.modify"> {{ $t('settings.perm.modify') }}</p>
|
||||||
<p><input type="checkbox" :disabled="admin" v-model="perm.execute"> {{ $t('settings.perm.execute') }}</p>
|
<p v-if="isExecEnabled"><input type="checkbox" :disabled="admin" v-model="perm.execute"> {{ $t('settings.perm.execute') }}</p>
|
||||||
<p><input type="checkbox" :disabled="admin" v-model="perm.rename"> {{ $t('settings.perm.rename') }}</p>
|
<p><input type="checkbox" :disabled="admin" v-model="perm.rename"> {{ $t('settings.perm.rename') }}</p>
|
||||||
<p><input type="checkbox" :disabled="admin" v-model="perm.share"> {{ $t('settings.perm.share') }}</p>
|
<p><input type="checkbox" :disabled="admin" v-model="perm.share"> {{ $t('settings.perm.share') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { enableExec } from '@/utils/constants'
|
||||||
export default {
|
export default {
|
||||||
name: 'permissions',
|
name: 'permissions',
|
||||||
props: ['perm'],
|
props: ['perm'],
|
||||||
@ -33,7 +34,8 @@ export default {
|
|||||||
|
|
||||||
this.perm.admin = value
|
this.perm.admin = value
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
isExecEnabled: () => enableExec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -25,7 +25,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<permissions :perm.sync="user.perm" />
|
<permissions :perm.sync="user.perm" />
|
||||||
<commands :commands.sync="user.commands" />
|
<commands v-if="isExecEnabled" :commands.sync="user.commands" />
|
||||||
|
|
||||||
<div v-if="!isDefault">
|
<div v-if="!isDefault">
|
||||||
<h3>{{ $t('settings.rules') }}</h3>
|
<h3>{{ $t('settings.rules') }}</h3>
|
||||||
@ -40,6 +40,7 @@ import Languages from './Languages'
|
|||||||
import Rules from './Rules'
|
import Rules from './Rules'
|
||||||
import Permissions from './Permissions'
|
import Permissions from './Permissions'
|
||||||
import Commands from './Commands'
|
import Commands from './Commands'
|
||||||
|
import { enableExec } from '@/utils/constants'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'user',
|
name: 'user',
|
||||||
@ -53,7 +54,8 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
passwordPlaceholder () {
|
passwordPlaceholder () {
|
||||||
return this.isNew ? '' : this.$t('settings.avoidChanges')
|
return this.isNew ? '' : this.$t('settings.avoidChanges')
|
||||||
}
|
},
|
||||||
|
isExecEnabled: () => enableExec
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
'user.perm.admin': function () {
|
'user.perm.admin': function () {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const loginPage = window.FileBrowser.LoginPage
|
|||||||
const theme = window.FileBrowser.Theme
|
const theme = window.FileBrowser.Theme
|
||||||
const enableThumbs = window.FileBrowser.EnableThumbs
|
const enableThumbs = window.FileBrowser.EnableThumbs
|
||||||
const resizePreview = window.FileBrowser.ResizePreview
|
const resizePreview = window.FileBrowser.ResizePreview
|
||||||
|
const enableExec = window.FileBrowser.EnableExec
|
||||||
|
|
||||||
export {
|
export {
|
||||||
name,
|
name,
|
||||||
@ -28,5 +29,6 @@ export {
|
|||||||
loginPage,
|
loginPage,
|
||||||
theme,
|
theme,
|
||||||
enableThumbs,
|
enableThumbs,
|
||||||
resizePreview
|
resizePreview,
|
||||||
|
enableExec
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
<sidebar></sidebar>
|
<sidebar></sidebar>
|
||||||
<main>
|
<main>
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
<shell v-if="isLogged && user.perm.execute" />
|
<shell v-if="isExecEnabled && isLogged && user.perm.execute" />
|
||||||
</main>
|
</main>
|
||||||
<prompts></prompts>
|
<prompts></prompts>
|
||||||
</div>
|
</div>
|
||||||
@ -19,6 +19,7 @@ import Sidebar from '@/components/Sidebar'
|
|||||||
import Prompts from '@/components/prompts/Prompts'
|
import Prompts from '@/components/prompts/Prompts'
|
||||||
import SiteHeader from '@/components/Header'
|
import SiteHeader from '@/components/Header'
|
||||||
import Shell from '@/components/Shell'
|
import Shell from '@/components/Shell'
|
||||||
|
import { enableExec } from '@/utils/constants'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'layout',
|
name: 'layout',
|
||||||
@ -30,7 +31,8 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters([ 'isLogged', 'progress' ]),
|
...mapGetters([ 'isLogged', 'progress' ]),
|
||||||
...mapState([ 'user' ])
|
...mapState([ 'user' ]),
|
||||||
|
isExecEnabled: () => enableExec
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
'$route': function () {
|
'$route': function () {
|
||||||
|
|||||||
@ -14,9 +14,11 @@
|
|||||||
<p class="small">{{ $t('settings.globalRules') }}</p>
|
<p class="small">{{ $t('settings.globalRules') }}</p>
|
||||||
<rules :rules.sync="settings.rules" />
|
<rules :rules.sync="settings.rules" />
|
||||||
|
|
||||||
<h3>{{ $t('settings.executeOnShell') }}</h3>
|
<div v-if="isExecEnabled">
|
||||||
<p class="small">{{ $t('settings.executeOnShellDescription') }}</p>
|
<h3>{{ $t('settings.executeOnShell') }}</h3>
|
||||||
<input class="input input--block" type="text" placeholder="bash -c, cmd /c, ..." v-model="settings.shell" />
|
<p class="small">{{ $t('settings.executeOnShellDescription') }}</p>
|
||||||
|
<input class="input input--block" type="text" placeholder="bash -c, cmd /c, ..." v-model="settings.shell" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3>{{ $t('settings.branding') }}</h3>
|
<h3>{{ $t('settings.branding') }}</h3>
|
||||||
|
|
||||||
@ -67,7 +69,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<form class="card" @submit.prevent="save">
|
<form v-if="isExecEnabled" class="card" @submit.prevent="save">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h2>{{ $t('settings.commandRunner') }}</h2>
|
<h2>{{ $t('settings.commandRunner') }}</h2>
|
||||||
</div>
|
</div>
|
||||||
@ -104,6 +106,7 @@ import { settings as api } from '@/api'
|
|||||||
import UserForm from '@/components/settings/UserForm'
|
import UserForm from '@/components/settings/UserForm'
|
||||||
import Rules from '@/components/settings/Rules'
|
import Rules from '@/components/settings/Rules'
|
||||||
import Themes from '@/components/settings/Themes'
|
import Themes from '@/components/settings/Themes'
|
||||||
|
import { enableExec } from '@/utils/constants'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'settings',
|
name: 'settings',
|
||||||
@ -119,7 +122,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState([ 'user' ])
|
...mapState([ 'user' ]),
|
||||||
|
isExecEnabled: () => enableExec
|
||||||
},
|
},
|
||||||
async created () {
|
async created () {
|
||||||
try {
|
try {
|
||||||
|
|||||||
118
http/commands.go
118
http/commands.go
@ -37,74 +37,76 @@ func wsErr(ws *websocket.Conn, r *http.Request, status int, err error) { //nolin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
func commandsHandler (enableExec bool) handleFunc {
|
||||||
conn, err := upgrader.Upgrade(w, r, nil)
|
return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
if err != nil {
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
return http.StatusInternalServerError, err
|
if err != nil {
|
||||||
}
|
return http.StatusInternalServerError, err
|
||||||
defer conn.Close()
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
var raw string
|
var raw string
|
||||||
|
|
||||||
for {
|
for {
|
||||||
_, msg, err := conn.ReadMessage() //nolint:shadow
|
_, msg, err := conn.ReadMessage() //nolint:shadow
|
||||||
|
if err != nil {
|
||||||
|
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
raw = strings.TrimSpace(string(msg))
|
||||||
|
if raw != "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !enableExec || !d.user.CanExecute(strings.Split(raw, " ")[0]) {
|
||||||
|
if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil { //nolint:shadow
|
||||||
|
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
command, err := runner.ParseCommand(d.settings, raw)
|
||||||
|
if err != nil {
|
||||||
|
if err := conn.WriteMessage(websocket.TextMessage, []byte(err.Error())); err != nil { //nolint:shadow
|
||||||
|
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(command[0], command[1:]...) //nolint:gosec
|
||||||
|
cmd.Dir = d.user.FullPath(r.URL.Path)
|
||||||
|
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
raw = strings.TrimSpace(string(msg))
|
stderr, err := cmd.StderrPipe()
|
||||||
if raw != "" {
|
if err != nil {
|
||||||
break
|
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||||
|
return 0, nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !d.user.CanExecute(strings.Split(raw, " ")[0]) {
|
if err := cmd.Start(); err != nil {
|
||||||
if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil { //nolint:shadow
|
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s := bufio.NewScanner(io.MultiReader(stdout, stderr))
|
||||||
|
for s.Scan() {
|
||||||
|
if err := conn.WriteMessage(websocket.TextMessage, s.Bytes()); err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cmd.Wait(); err != nil {
|
||||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
})
|
||||||
|
}
|
||||||
command, err := runner.ParseCommand(d.settings, raw)
|
|
||||||
if err != nil {
|
|
||||||
if err := conn.WriteMessage(websocket.TextMessage, []byte(err.Error())); err != nil { //nolint:shadow
|
|
||||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
|
||||||
}
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(command[0], command[1:]...) //nolint:gosec
|
|
||||||
cmd.Dir = d.user.FullPath(r.URL.Path)
|
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
stderr, err := cmd.StderrPipe()
|
|
||||||
if err != nil {
|
|
||||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
s := bufio.NewScanner(io.MultiReader(stdout, stderr))
|
|
||||||
for s.Scan() {
|
|
||||||
if err := conn.WriteMessage(websocket.TextMessage, s.Bytes()); err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cmd.Wait(); err != nil {
|
|
||||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, nil
|
|
||||||
})
|
|
||||||
|
|||||||
@ -61,7 +61,7 @@ func NewHandler(imgSvc ImgService, fileCache FileCache, store *storage.Storage,
|
|||||||
api.PathPrefix("/raw").Handler(monkey(rawHandler, "/api/raw")).Methods("GET")
|
api.PathPrefix("/raw").Handler(monkey(rawHandler, "/api/raw")).Methods("GET")
|
||||||
api.PathPrefix("/preview/{size}/{path:.*}").
|
api.PathPrefix("/preview/{size}/{path:.*}").
|
||||||
Handler(monkey(previewHandler(imgSvc, fileCache, server.EnableThumbnails, server.ResizePreview), "/api/preview")).Methods("GET")
|
Handler(monkey(previewHandler(imgSvc, fileCache, server.EnableThumbnails, server.ResizePreview), "/api/preview")).Methods("GET")
|
||||||
api.PathPrefix("/command").Handler(monkey(commandsHandler, "/api/command")).Methods("GET")
|
api.PathPrefix("/command").Handler(monkey(commandsHandler(server.EnableExec), "/api/command")).Methods("GET")
|
||||||
api.PathPrefix("/search").Handler(monkey(searchHandler, "/api/search")).Methods("GET")
|
api.PathPrefix("/search").Handler(monkey(searchHandler, "/api/search")).Methods("GET")
|
||||||
|
|
||||||
public := api.PathPrefix("/public").Subrouter()
|
public := api.PathPrefix("/public").Subrouter()
|
||||||
|
|||||||
@ -41,6 +41,7 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, box *
|
|||||||
"Theme": d.settings.Branding.Theme,
|
"Theme": d.settings.Branding.Theme,
|
||||||
"EnableThumbs": d.server.EnableThumbnails,
|
"EnableThumbs": d.server.EnableThumbnails,
|
||||||
"ResizePreview": d.server.ResizePreview,
|
"ResizePreview": d.server.ResizePreview,
|
||||||
|
"EnableExec": d.server.EnableExec,
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.settings.Branding.Files != "" {
|
if d.settings.Branding.Files != "" {
|
||||||
|
|||||||
@ -40,6 +40,7 @@ type Server struct {
|
|||||||
Log string `json:"log"`
|
Log string `json:"log"`
|
||||||
EnableThumbnails bool `json:"enableThumbnails"`
|
EnableThumbnails bool `json:"enableThumbnails"`
|
||||||
ResizePreview bool `json:"resizePreview"`
|
ResizePreview bool `json:"resizePreview"`
|
||||||
|
EnableExec bool `json:"enableExec"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean cleans any variables that might need cleaning.
|
// Clean cleans any variables that might need cleaning.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user