aboutsummaryrefslogtreecommitdiff
path: root/src/site
diff options
context:
space:
mode:
authorKana <[email protected]>2021-06-19 02:03:57 +0900
committerGitHub <[email protected]>2021-06-19 02:03:57 +0900
commit065c5221a0250838f1d1f9bb7a7922ff4f55e038 (patch)
tree8a69a81f00e6bff2752f4f7c59dcbbf21f893b20 /src/site
parentchore: docs update (diff)
parentfix: potentially fix the blocked extensions array splitting (diff)
downloadhost.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.vue2
-rw-r--r--src/site/components/settings/JoiObject.vue151
-rw-r--r--src/site/components/uploader/Uploader.vue2
-rw-r--r--src/site/pages/dashboard/admin/settings.vue166
-rw-r--r--src/site/store/admin.js37
-rw-r--r--src/site/store/config.js33
-rw-r--r--src/site/store/index.js4
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');