diff options
| author | Kana <[email protected]> | 2021-06-19 02:03:57 +0900 |
|---|---|---|
| committer | GitHub <[email protected]> | 2021-06-19 02:03:57 +0900 |
| commit | 065c5221a0250838f1d1f9bb7a7922ff4f55e038 (patch) | |
| tree | 8a69a81f00e6bff2752f4f7c59dcbbf21f893b20 /src/site | |
| parent | chore: docs update (diff) | |
| parent | fix: potentially fix the blocked extensions array splitting (diff) | |
| download | host.fuwn.me-065c5221a0250838f1d1f9bb7a7922ff4f55e038.tar.xz host.fuwn.me-065c5221a0250838f1d1f9bb7a7922ff4f55e038.zip | |
Merge pull request #278 from Zephyrrus/Zephyrrus-feature/database_based_settings
Zephyrrus feature/database based settings
Diffstat (limited to 'src/site')
| -rw-r--r-- | src/site/components/footer/Footer.vue | 2 | ||||
| -rw-r--r-- | src/site/components/settings/JoiObject.vue | 151 | ||||
| -rw-r--r-- | src/site/components/uploader/Uploader.vue | 2 | ||||
| -rw-r--r-- | src/site/pages/dashboard/admin/settings.vue | 166 | ||||
| -rw-r--r-- | src/site/store/admin.js | 37 | ||||
| -rw-r--r-- | src/site/store/config.js | 33 | ||||
| -rw-r--r-- | src/site/store/index.js | 4 |
7 files changed, 266 insertions, 129 deletions
diff --git a/src/site/components/footer/Footer.vue b/src/site/components/footer/Footer.vue index 0c77603..0e3cceb 100644 --- a/src/site/components/footer/Footer.vue +++ b/src/site/components/footer/Footer.vue @@ -9,7 +9,7 @@ href="https://github.com/pitu" class="no-block">Pitu</a> </span><br> - <span>v{{ version }}</span> + <span>{{ version }}</span> </div> <div class="column is-narrow bottom-up"> <a href="https://github.com/weebdev/chibisafe">GitHub</a> diff --git a/src/site/components/settings/JoiObject.vue b/src/site/components/settings/JoiObject.vue new file mode 100644 index 0000000..8d3a803 --- /dev/null +++ b/src/site/components/settings/JoiObject.vue @@ -0,0 +1,151 @@ +<template> + <div v-if="settings"> + <div v-for="[key, field] in Object.entries(settings)" :key="key"> + <b-field + :label="field.flags.label" + :message="getErrorMessage(key) || field.flags.description" + :type="getValidationType(key)" + class="field" + horizontal> + <b-input + v-if="getDisplayType(field) === 'string'" + v-model="values[key]" + class="chibisafe-input" + expanded /> + <b-input + v-else-if="getDisplayType(field) === 'number'" + v-model="values[key]" + type="number" + class="chibisafe-input" + :min="getMin(field)" + :max="getMax(field)" + expanded /> + <b-switch + v-else-if="getDisplayType(field) === 'boolean'" + v-model="values[key]" + :rounded="false" + :true-value="true" + :false-value="false" /> + <!-- TODO: If array and has allowed items, limit input to those items only --> + <b-taginput + v-else-if="getDisplayType(field) === 'array' || getDisplayType(field) === 'tagInput'" + v-model="values[key]" + ellipsis + icon="label" + :placeholder="field.flags.label" + class="taginp" /> + <div v-else-if="getDisplayType(field) === 'checkbox'"> + <b-checkbox v-for="item in getAllowedItems(field)" :key="item" + v-model="values[key]" + :native-value="item"> + {{ item }} + </b-checkbox> + </div> + </b-field> + <!-- + TODO: Add asterisk to required fields + --> + </div> + </div> +</template> + + +<script> +export default { + name: 'JoiObject', + props: { + settings: { + type: Object, + required: true + }, + errors: { + 'type': Object, + 'default': () => ({}) + } + }, + data() { + return { + values: {} + }; + }, + created() { + for (const [k, v] of Object.entries(this.settings)) { + this.$set(this.values, k, v.value); + } + }, + methods: { + getMin(field) { + if (field.type !== 'number') return; + + for (const rule of field.rules) { + if (rule.name === 'greater') return rule.args.limit + 1; + if (rule.name === 'min') return rule.args.limit; + } + }, + getMax(field) { + if (field.type !== 'number') return; + + for (const rule of field.rules) { + if (rule.name === 'less') return rule.args.limit - 1; + if (rule.name === 'max') return rule.args.limit; + } + }, + getDisplayType(field) { + if (!field.metas) return field.type; + + const foundMeta = field.metas.find(e => e.displayType); + + if (foundMeta) return foundMeta.displayType; + + return field.type; + }, + getAllowedItems(field) { + if (!field.items) return []; + + return field.items.reduce((acc, item) => { + if (!item.allow) return acc; + + return [...acc, ...item.allow]; + }, []); + }, + getValidationType(fieldName) { + if (Array.isArray(this.errors[fieldName])) return 'is-danger'; + return null; + }, + getErrorMessage(fieldName) { + if (Array.isArray(this.errors[fieldName])) return this.errors[fieldName].join('\n'); + return null; + }, + getValues() { + return this.values; + } + } +}; +</script> +<style lang="scss" scoped> + @import '~/assets/styles/_colors.scss'; + + .field { + margin-bottom: 1em; + + ::v-deep .help.is-danger { + white-space: pre-line; + } + } + + .taginp { + ::v-deep { + .taginput-container { + border-color: #585858; + } + + .input::placeholder { + color: $textColor; + } + + .taginput-container, .control, .input { + background-color: transparent; + } + } + } +</style> diff --git a/src/site/components/uploader/Uploader.vue b/src/site/components/uploader/Uploader.vue index abe2128..f2d213a 100644 --- a/src/site/components/uploader/Uploader.vue +++ b/src/site/components/uploader/Uploader.vue @@ -124,7 +124,7 @@ export default { parallelChunkUploads: false, chunkSize: this.config.chunkSize * 1000000, chunksUploaded: this.dropzoneChunksUploaded, - maxFilesize: this.config.maxFileSize, + maxFilesize: this.config.maxUploadSize, previewTemplate: this.$refs.template.innerHTML, dictDefaultMessage: 'Drag & Drop your files or click to browse', headers: { Accept: 'application/vnd.chibisafe.json' } diff --git a/src/site/pages/dashboard/admin/settings.vue b/src/site/pages/dashboard/admin/settings.vue index 038c495..3fce282 100644 --- a/src/site/pages/dashboard/admin/settings.vue +++ b/src/site/pages/dashboard/admin/settings.vue @@ -11,112 +11,18 @@ </h2> <hr> - <b-field - label="Service name" - message="Please enter the name which this service is gonna be identified as" - horizontal> - <b-input - v-model="settings.serviceName" - class="chibisafe-input" - expanded /> - </b-field> - - <b-field - label="Upload folder" - message="Where to store the files relative to the working directory" - horizontal> - <b-input - v-model="settings.uploadFolder" - class="chibisafe-input" - expanded /> - </b-field> - - <b-field - label="Links per album" - message="Maximum links allowed per album" - horizontal> - <b-input - v-model="settings.linksPerAlbum" - class="chibisafe-input" - type="number" - expanded /> - </b-field> - - <b-field - label="Max upload size" - message="Maximum allowed file size in MB" - horizontal> - <b-input - v-model="settings.maxUploadSize" - class="chibisafe-input" - expanded /> - </b-field> - - <b-field - label="Filename length" - message="How many characters long should the generated filenames be" - horizontal> - <b-input - v-model="settings.filenameLength" - class="chibisafe-input" - expanded /> - </b-field> - - <b-field - label="Album link length" - message="How many characters a link for an album should have" - horizontal> - <b-input - v-model="settings.albumLinkLength" - class="chibisafe-input" - expanded /> - </b-field> - - <b-field - label="Generate thumbnails" - message="Generate thumbnails when uploading a file if possible" - horizontal> - <b-switch - v-model="settings.generateThumbnails" - :true-value="true" - :false-value="false" /> - </b-field> - - <b-field - label="Generate zips" - message="Allow generating zips to download entire albums" - horizontal> - <b-switch - v-model="settings.generateZips" - :true-value="true" - :false-value="false" /> - </b-field> - - <b-field - label="Public mode" - message="Enable anonymous uploades" - horizontal> - <b-switch - v-model="settings.publicMode" - :true-value="true" - :false-value="false" /> - </b-field> - - <b-field - label="Enable creating account" - message="Enable creating new accounts in the platform" - horizontal> - <b-switch - v-model="settings.enableAccounts" - :true-value="true" - :false-value="false" /> - </b-field> + <div v-for="[sectionName, fields] in Object.entries(sectionedSettings)" :key="sectionName" class="block"> + <h5 class="title is-5 has-text-grey-lighter"> + {{ sectionName }} + </h5> + <JoiObject ref="jois" :settings="fields" :errors="validationErrors" /> + </div> <div class="mb2 mt2 text-center"> <button class="button is-primary" @click="promptRestartService"> - Save and restart service + Save settings </button> </div> </div> @@ -128,27 +34,69 @@ <script> import { mapState } from 'vuex'; import Sidebar from '~/components/sidebar/Sidebar.vue'; +import JoiObject from '~/components/settings/JoiObject.vue'; export default { components: { - Sidebar + Sidebar, + JoiObject }, middleware: ['auth', 'admin'], - computed: mapState({ - settings: state => state.admin.settings - }), + data() { + return { + validationErrors: {} + }; + }, + computed: { + ...mapState({ + settings: state => state.admin.settings, + settingsSchema: state => state.admin.settingsSchema + }), + sectionedSettings() { + return Object.entries(this.settingsSchema.keys).reduce((acc, [key, field]) => { + if (!field.metas) acc.Other = { ...acc.Other, [key]: field }; + + const sectionMeta = field.metas.find(e => e.section); + if (sectionMeta) { + acc[sectionMeta.section] = { ...acc[sectionMeta.section], [key]: field }; + } else { + acc.Other = { ...acc.Other, [key]: field }; + } + + return acc; + }, {}); + } + }, async asyncData({ app }) { await app.store.dispatch('admin/fetchSettings'); + await app.store.dispatch('admin/getSettingsSchema'); + await app.store.commit('admin/populateSchemaWithValues'); }, methods: { promptRestartService() { this.$buefy.dialog.confirm({ - message: 'Keep in mind that restarting only works if you have PM2 or something similar set up. Continue?', - onConfirm: () => this.restartService() + message: 'Certain changes need for you to manually restart your chibisafe instance, please do so from the terminal. Continue?', + onConfirm: () => this.saveSettings() }); }, - restartService() { - this.$handler.executeAction('admin/restartService'); + async saveSettings() { + // handle refs + let settings = {}; + for (const joiComponent of this.$refs.jois) { + settings = { ...settings, ...joiComponent.getValues() }; + } + + try { + await this.$store.dispatch('admin/saveSettings', settings); + this.$set(this, 'validationErrors', {}); + + await this.$store.dispatch('config/fetchSettings'); + // this.$handler.executeAction('admin/restartService'); + } catch (e) { + if (e.response?.data?.errors) { + this.$set(this, 'validationErrors', e.response.data.errors); + } + } } }, head() { diff --git a/src/site/store/admin.js b/src/site/store/admin.js index cac1cca..9b1d591 100644 --- a/src/site/store/admin.js +++ b/src/site/store/admin.js @@ -12,23 +12,38 @@ export const state = () => ({ }, file: {}, settings: {}, - statistics: {} + statistics: {}, + settingsSchema: { + type: null, + keys: {} + } }); export const actions = { async fetchSettings({ commit }) { - const response = await this.$axios.$get('service/config'); + const response = await this.$axios.$get('service/config/all'); commit('setSettings', response); return response; }, + async saveSettings({ commit }, settings) { + const response = await this.$axios.$post('service/config', { settings }); + + return response; + }, async fetchStatistics({ commit }, category) { const url = category ? `service/statistics/${category}` : 'service/statistics'; const response = await this.$axios.$get(url); - commit('setStatistics', { statistics: response.statistics, category: category }); + commit('setStatistics', { statistics: response.statistics, category }); return response; }, + async getSettingsSchema({ commit }) { + // XXX: Maybe move to the config store? + const response = await this.$axios.$get('service/config/schema'); + + commit('setSettingsSchema', response); + }, async fetchUsers({ commit }) { const response = await this.$axios.$get('admin/users'); commit('setUsers', response); @@ -95,9 +110,6 @@ export const actions = { }; export const mutations = { - setSettings(state, { config }) { - state.settings = config; - }, setStatistics(state, { statistics, category }) { if (category) { state.statistics[category] = statistics[category]; @@ -105,6 +117,12 @@ export const mutations = { state.statistics = statistics; } }, + setSettings(state, { config }) { + state.settings = config; + }, + setSettingsSchema(state, { schema }) { + state.settingsSchema = schema; + }, setUsers(state, { users }) { state.users = users; }, @@ -135,5 +153,12 @@ export const mutations = { state.user.isAdmin = isAdmin; } } + }, + populateSchemaWithValues({ settings, settingsSchema }) { + for (const [key, value] of Object.entries(settings)) { + if (settingsSchema.keys?.[key] !== undefined) { + settingsSchema.keys[key].value = value; + } + } } }; diff --git a/src/site/store/config.js b/src/site/store/config.js index c17632d..124b778 100644 --- a/src/site/store/config.js +++ b/src/site/store/config.js @@ -1,18 +1,33 @@ export const state = () => ({ - development: true, - version: '4.0.0', - URL: 'http://localhost:8080', - baseURL: 'http://localhost:8080/api', + development: process.env.development, + version: '', + URL: process.env.development ? 'http://localhost:5000' : '/', + baseURL: `${process.env.development ? 'http://localhost:5000' : ''}/api`, serviceName: '', - maxFileSize: 100, - chunkSize: 90, - maxLinksPerAlbum: 5, + maxUploadSize: 0, + chunkSize: 0, publicMode: false, userAccounts: false }); +export const actions = { + async fetchSettings({ commit }) { + const response = await this.$axios.$get('service/config'); + commit('setSettings', response); + + return response; + } +}; + export const mutations = { - set(state, config) { - Object.assign(state, config); + setSettings(state, { config }) { + state.version = `v${config.version}`; + state.serviceName = config.serviceName; + state.maxUploadSize = config.maxUploadSize; + state.filenameLength = config.filenameLength; + state.albumLinkLength = config.albumLinkLength; + state.chunkSize = config.chunkSize; + state.publicMode = config.publicMode; + state.userAccounts = config.userAccounts; } }; diff --git a/src/site/store/index.js b/src/site/store/index.js index b94a336..680ae84 100644 --- a/src/site/store/index.js +++ b/src/site/store/index.js @@ -1,8 +1,6 @@ -import config from '../../../dist/config.json'; - export const actions = { async nuxtServerInit({ commit, dispatch }) { - commit('config/set', config); + await dispatch('config/fetchSettings'); const cookies = this.$cookies.getAll(); if (!cookies.token) return dispatch('auth/logout'); |