From 94da3526d70c42fa2c09067516ea979da87d8f39 Mon Sep 17 00:00:00 2001 From: Tagadda <36127788+Tagadda@users.noreply.github.com> Date: Tue, 1 Feb 2022 16:21:00 +0000 Subject: [PATCH 01/72] ToolSettings view --- app/src/router/routes.js | 9 ++++ app/src/views/tool/ToolList.vue | 1 + app/src/views/tool/ToolSettings.vue | 64 +++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 app/src/views/tool/ToolSettings.vue diff --git a/app/src/router/routes.js b/app/src/router/routes.js index 2f705404..2e404e34 100644 --- a/app/src/router/routes.js +++ b/app/src/router/routes.js @@ -374,6 +374,15 @@ const routes = [ breadcrumb: ['tool-list', 'tool-webadmin'] } }, + { + name: 'tool-settings', + path: '/tools/settings', + component: () => import(/* webpackChunkName: "views/tools/settings" */ '@/views/tool/ToolSettings'), + meta: { + args: { trad: 'tools_yunohost_settings' }, + breadcrumb: ['tool-list', 'tool-settings'] + } + }, { name: 'tool-power', path: '/tools/power', diff --git a/app/src/views/tool/ToolList.vue b/app/src/views/tool/ToolList.vue index 21716ca0..10dd5373 100644 --- a/app/src/views/tool/ToolList.vue +++ b/app/src/views/tool/ToolList.vue @@ -24,6 +24,7 @@ export default { { routeName: 'tool-migrations', icon: 'share', translation: 'migrations' }, { routeName: 'tool-firewall', icon: 'shield', translation: 'firewall' }, { routeName: 'tool-adminpw', icon: 'key-modern', translation: 'tools_adminpw' }, + { routeName: 'tool-settings', icon: 'cog', translation: 'tools_yunohost_settings' }, { routeName: 'tool-webadmin', icon: 'cog', translation: 'tools_webadmin_settings' }, { routeName: 'tool-power', icon: 'power-off', translation: 'tools_shutdown_reboot' } ] diff --git a/app/src/views/tool/ToolSettings.vue b/app/src/views/tool/ToolSettings.vue new file mode 100644 index 00000000..54d32981 --- /dev/null +++ b/app/src/views/tool/ToolSettings.vue @@ -0,0 +1,64 @@ + + + From 00db70764d9d50d8fd11bd74349491865a05d2dd Mon Sep 17 00:00:00 2001 From: Tagada <36127788+Tagadda@users.noreply.github.com> Date: Wed, 9 Feb 2022 22:04:55 +0100 Subject: [PATCH 02/72] Update app/src/router/routes.js Co-authored-by: Axolotle --- app/src/router/routes.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/app/src/router/routes.js b/app/src/router/routes.js index 2e404e34..4253307d 100644 --- a/app/src/router/routes.js +++ b/app/src/router/routes.js @@ -375,13 +375,21 @@ const routes = [ } }, { - name: 'tool-settings', path: '/tools/settings', component: () => import(/* webpackChunkName: "views/tools/settings" */ '@/views/tool/ToolSettings'), - meta: { - args: { trad: 'tools_yunohost_settings' }, - breadcrumb: ['tool-list', 'tool-settings'] - } + children: [ + { + name: 'tool-settings', + path: ':tabId?', + component: () => import(/* webpackChunkName: "components/configPanel" */ '@/components/ConfigPanel'), + props: true, + meta: { + routerParams: [], + args: { trad: 'tools_yunohost_settings' }, + breadcrumb: ['tool-list', 'tool-settings'] + } + } + ] }, { name: 'tool-power', From 2671fd32d3c3dc3293fc6c9bb3741f3811606d82 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 6 Aug 2022 13:50:40 +0200 Subject: [PATCH 03/72] global settings: fix i18n --- app/src/i18n/locales/en.json | 4 ++++ app/src/views/tool/ToolSettings.vue | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/i18n/locales/en.json b/app/src/i18n/locales/en.json index 7eac7dec..81d17059 100644 --- a/app/src/i18n/locales/en.json +++ b/app/src/i18n/locales/en.json @@ -452,6 +452,9 @@ "create": "Create user '{name}'", "delete": "Delete user '{name}'", "update": "Update user '{name}'" + }, + "settings": { + "update": "Update global settings" } }, "run": "Run", @@ -501,6 +504,7 @@ "experimental_description": "Gives you access to experimental features. These are considered unstable and may break your system.
Enable this only if you know what you are doing.", "transitions": "Page transition animations" }, + "tools_yunohost_settings": "YunoHost settings", "tools_webadmin_settings": "Web-admin settings", "traceback": "Traceback", "udp": "UDP", diff --git a/app/src/views/tool/ToolSettings.vue b/app/src/views/tool/ToolSettings.vue index 54d32981..8cf6dfa6 100644 --- a/app/src/views/tool/ToolSettings.vue +++ b/app/src/views/tool/ToolSettings.vue @@ -48,7 +48,7 @@ export default { api.put( 'settings', { key: id_, args: objectToParams(formatedData) }, - { key: 'tools.update_settings', name: this.name } + { key: 'settings.update', name: this.name } ).then(() => { this.$refs.view.fetchQueries({ triggerLoading: true }) }).catch(err => { From 9b57063572678261d9bb2b03a32d2d8e55693f1c Mon Sep 17 00:00:00 2001 From: axolotle Date: Tue, 1 Mar 2022 15:49:10 +0100 Subject: [PATCH 04/72] remove edge case arg injection in formatYunohostArguments --- app/src/helpers/yunohostArguments.js | 11 +---------- app/src/views/app/AppInstall.vue | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/src/helpers/yunohostArguments.js b/app/src/helpers/yunohostArguments.js index e5b01202..97230508 100644 --- a/app/src/helpers/yunohostArguments.js +++ b/app/src/helpers/yunohostArguments.js @@ -259,21 +259,12 @@ export function formatYunoHostArgument (arg) { * @param {String} name - (temp) an app name to build a label field in case of manifest install args * @return {Object} an object containing all parsed values to be used in vue views. */ -export function formatYunoHostArguments (args, name = null) { +export function formatYunoHostArguments (args) { const form = {} const fields = {} const validations = {} const errors = {} - // FIXME yunohost should add the label field by default - if (name) { - args.unshift({ - ask: i18n.t('label_for_manifestname', { name }), - default: name, - name: 'label' - }) - } - for (const arg of args) { const { value, field, validation, error } = formatYunoHostArgument(arg) fields[arg.name] = field diff --git a/app/src/views/app/AppInstall.vue b/app/src/views/app/AppInstall.vue index db0a27b6..dc505a46 100644 --- a/app/src/views/app/AppInstall.vue +++ b/app/src/views/app/AppInstall.vue @@ -92,10 +92,19 @@ export default { manifest.multi_instance = this.$i18n.t(manifest.multi_instance ? 'yes' : 'no') this.infos = Object.fromEntries(infosKeys.map(key => [key, manifest[key]])) - const { form, fields, validations, errors } = formatYunoHostArguments( - manifest.arguments.install, - manifest.name - ) + // FIXME yunohost should add the label field by default + manifest.arguments.install.unshift({ + ask: this.$t('label_for_manifestname', { name: manifest.name }), + default: manifest.name, + name: 'label' + }) + + const { + form, + fields, + validations, + errors + } = formatYunoHostArguments(manifest.arguments.install) this.fields = fields this.form = form From fd19dcebf6378cc609c8b5ddbb4c6332b0ae37c7 Mon Sep 17 00:00:00 2001 From: axolotle Date: Tue, 1 Mar 2022 16:29:00 +0100 Subject: [PATCH 05/72] update FileItem context strategy by reading File content on File change --- .../components/globals/formItems/FileItem.vue | 60 ++++++++++++------- app/src/helpers/commons.js | 23 +++++++ app/src/helpers/yunohostArguments.js | 35 ++++------- 3 files changed, 75 insertions(+), 43 deletions(-) diff --git a/app/src/components/globals/formItems/FileItem.vue b/app/src/components/globals/formItems/FileItem.vue index 8dca3b7b..e7ca0b3e 100644 --- a/app/src/components/globals/formItems/FileItem.vue +++ b/app/src/components/globals/formItems/FileItem.vue @@ -1,19 +1,24 @@ diff --git a/app/src/helpers/yunohostArguments.js b/app/src/helpers/yunohostArguments.js index fe3d08b4..b747483f 100644 --- a/app/src/helpers/yunohostArguments.js +++ b/app/src/helpers/yunohostArguments.js @@ -54,6 +54,49 @@ export function adressToFormValue (address) { } +/** + * Evaluate config panel string expression that can contain regular expressions. + * Expression are evaluated with the config panel form as context. + * + * @param {String} expression - A String to evaluate. + * @param {Object} forms - A nested form used in config panels. + * @return {Boolean} - expression evaluation result. + */ +export function evaluateExpression (expression, forms) { + if (!expression) return true + if (expression === '"false"') return false + + const context = Object.values(forms).reduce((ctx, args) => { + Object.entries(args).forEach(([name, value]) => { + ctx[name] = isObjectLiteral(value) && 'file' in value ? value.content : value + }) + return ctx + }, {}) + + // Allow to use match(var,regexp) function + const matchRe = new RegExp('match\\(\\s*(\\w+)\\s*,\\s*"([^"]+)"\\s*\\)', 'g') + for (const matched of expression.matchAll(matchRe)) { + const [fullMatch, varMatch, regExpMatch] = matched + const varName = varMatch + '__re' + matched.index + context[varName] = new RegExp(regExpMatch, 'm').test(context[varMatch]) + expression = expression.replace(fullMatch, varName) + } + + try { + return !!evaluate(context, expression) + } catch { + return false + } +} + +// Adds a property to an Object that will dynamically returns a expression evaluation result. +function addEvaluationGetter (prop, obj, expr, ctx) { + Object.defineProperty(obj, prop, { + get: () => evaluateExpression(expr, ctx) + }) +} + + /** * Format app install, actions and config panel argument into a data structure that * will be automaticly transformed into a component on screen. @@ -243,12 +286,6 @@ export function formatYunoHostArgument (arg) { field.link = { href: arg.helpLink.href, text: i18n.t(arg.helpLink.text) } } - if (arg.visible) { - field.visible = arg.visible - // Temporary value to wait visible expression to be evaluated - field.isVisible = true - } - return { value, field, @@ -264,10 +301,10 @@ export function formatYunoHostArgument (arg) { * as v-model values, fields that can be passed to a FormField component and validations. * * @param {Array} args - a yunohost arg array written by a packager. - * @param {String} name - (temp) an app name to build a label field in case of manifest install args + * @param {Object|null} forms - nested form used as the expression evualuations context. * @return {Object} an object containing all parsed values to be used in vue views. */ -export function formatYunoHostArguments (args) { +export function formatYunoHostArguments (args, forms) { const form = {} const fields = {} const validations = {} @@ -279,6 +316,12 @@ export function formatYunoHostArguments (args) { form[arg.name] = value if (validation) validations[arg.name] = validation errors[arg.name] = error + + if ('visible' in arg) { + addEvaluationGetter('visible', field, arg.visible, forms) + } else { + field.visible = true + } } return { form, fields, validations, errors } @@ -302,11 +345,20 @@ export function formatYunoHostConfigPanels (data) { if (name) panel.name = formatI18nField(name) if (help) panel.help = formatI18nField(help) - for (const { id: sectionId, name, help, visible, options } of sections) { - const section = { id: sectionId, visible, isVisible: false } - if (help) section.help = formatI18nField(help) - if (name) section.name = formatI18nField(name) - const { form, fields, validations, errors } = formatYunoHostArguments(options) + for (const _section of sections) { + const section = { id: _section.id, visible: true } + if (_section.help) section.help = formatI18nField(_section.help) + if (_section.name) section.name = formatI18nField(_section.name) + if (_section.visible) { + addEvaluationGetter('visible', section, _section.visible, result.forms) + } + + const { + form, + fields, + validations, + errors + } = formatYunoHostArguments(_section.options, result.forms) // Merge all sections forms to the panel to get a unique form Object.assign(result.forms[panelId], form) Object.assign(result.validations[panelId], validations) @@ -322,47 +374,6 @@ export function formatYunoHostConfigPanels (data) { } -export function configPanelsFieldIsVisible (expression, field, forms) { - if (!expression || !field) return true - const context = {} - - const promises = [] - for (const args of Object.values(forms)) { - for (const shortname in args) { - if (args[shortname] instanceof File) { - if (expression.includes(shortname)) { - promises.push(pFileReader(args[shortname], context, shortname, false)) - } - } else { - context[shortname] = args[shortname] - } - } - } - - // Allow to use match(var,regexp) function - const matchRe = new RegExp('match\\(\\s*(\\w+)\\s*,\\s*"([^"]+)"\\s*\\)', 'g') - let i = 0 - Promise.all(promises).then(() => { - for (const matched of expression.matchAll(matchRe)) { - i++ - const varName = matched[1] + '__re' + i.toString() - context[varName] = new RegExp(matched[2], 'm').test(context[matched[1]]) - expression = expression.replace(matched[0], varName) - } - - try { - field.isVisible = evaluate(context, expression) - } catch { - field.isVisible = false - } - }) - - return field.isVisible -} - - - - /** * Format helper for a form value. * Convert Boolean to (1|0) and concatenate adresses. diff --git a/app/src/views/app/AppInstall.vue b/app/src/views/app/AppInstall.vue index dc505a46..8a3e3816 100644 --- a/app/src/views/app/AppInstall.vue +++ b/app/src/views/app/AppInstall.vue @@ -25,8 +25,7 @@ > @@ -47,10 +46,13 @@ + + diff --git a/app/src/i18n/locales/en.json b/app/src/i18n/locales/en.json index 5062e796..e66541aa 100644 --- a/app/src/i18n/locales/en.json +++ b/app/src/i18n/locales/en.json @@ -544,7 +544,9 @@ "words": { "browse": "Browse", "collapse": "Collapse", - "default": "Default" + "default": "Default", + "none": "None", + "separator": ", " }, "wrong_password": "Wrong password", "yes": "Yes", From 52c8fad43a946bac3864d4eeec08249054c8c3bb Mon Sep 17 00:00:00 2001 From: axolotle Date: Wed, 2 Mar 2022 19:46:19 +0100 Subject: [PATCH 10/72] misc form items style and linting fixes --- app/src/components/ConfigPanels.vue | 9 +---- app/src/components/globals/FormField.vue | 15 ++++--- .../globals/formItems/InputItem.vue | 13 +++--- .../globals/formItems/ReadOnlyAlertItem.vue | 40 +++++++++---------- .../globals/formItems/TextAreaItem.vue | 2 +- app/src/scss/main.scss | 9 +++++ app/src/views/_partials/DomainForm.vue | 8 ++-- 7 files changed, 48 insertions(+), 48 deletions(-) diff --git a/app/src/components/ConfigPanels.vue b/app/src/components/ConfigPanels.vue index ff8aa820..cd959b59 100644 --- a/app/src/components/ConfigPanels.vue +++ b/app/src/components/ConfigPanels.vue @@ -1,7 +1,7 @@ @@ -49,10 +49,3 @@ export default { } } - - diff --git a/app/src/components/globals/FormField.vue b/app/src/components/globals/FormField.vue index d7be17e1..3dd85173 100644 --- a/app/src/components/globals/FormField.vue +++ b/app/src/components/globals/FormField.vue @@ -28,15 +28,18 @@ @@ -76,8 +79,8 @@ export default { if ('label' in attrs) { const defaultAttrs = { 'label-cols-md': 4, - 'label-cols-lg': 2, - 'label-class': 'font-weight-bold' + 'label-cols-lg': 3, + 'label-class': ['font-weight-bold', 'py-0'] } if (!('label-cols' in attrs)) { for (const attr in defaultAttrs) { diff --git a/app/src/components/globals/formItems/InputItem.vue b/app/src/components/globals/formItems/InputItem.vue index e9c95a1a..2cd8391d 100644 --- a/app/src/components/globals/formItems/InputItem.vue +++ b/app/src/components/globals/formItems/InputItem.vue @@ -2,7 +2,6 @@ @@ -21,11 +21,6 @@ export default { name: 'InputItem', - data () { - return { - autocomplete_: (this.autocomplete) ? this.autocomplete : (this.type === 'password') ? 'new-password' : null - } - }, props: { value: { type: [String, Number], default: null }, id: { type: String, default: null }, @@ -40,6 +35,12 @@ export default { autocomplete: { type: String, default: null }, pattern: { type: Object, default: null }, name: { type: String, default: null } + }, + + data () { + return { + autocomplete_: (this.autocomplete) ? this.autocomplete : (this.type === 'password') ? 'new-password' : null + } } } diff --git a/app/src/components/globals/formItems/ReadOnlyAlertItem.vue b/app/src/components/globals/formItems/ReadOnlyAlertItem.vue index bc63ff6d..ac1dac55 100644 --- a/app/src/components/globals/formItems/ReadOnlyAlertItem.vue +++ b/app/src/components/globals/formItems/ReadOnlyAlertItem.vue @@ -1,8 +1,10 @@ @@ -11,29 +13,23 @@ export default { name: 'ReadOnlyAlertItem', - data () { - const icons = { - success: 'thumbs-up', - info: 'info-circle', - warning: 'warning', - danger: 'times' - } - return { - icon_: (this.icon) ? this.icon : icons[this.type] - } - }, - props: { id: { type: String, default: null }, label: { type: String, default: null }, type: { type: String, default: null }, icon: { type: String, default: null } + }, + + computed: { + icon_ () { + const icons = { + success: 'thumbs-up', + info: 'info', + warning: 'exclamation', + danger: 'times' + } + return this.icon || icons[this.type] + } } } - - diff --git a/app/src/components/globals/formItems/TextAreaItem.vue b/app/src/components/globals/formItems/TextAreaItem.vue index b9224f10..95af03e8 100644 --- a/app/src/components/globals/formItems/TextAreaItem.vue +++ b/app/src/components/globals/formItems/TextAreaItem.vue @@ -1,6 +1,6 @@ @@ -41,17 +44,21 @@ export default { this.config = formatYunoHostConfigPanels(config) }, - async applyConfig (id) { - const args = await formatFormData( - this.config.forms[id], - { removeEmpty: false, removeNull: true } - ) + async onConfigSubmit ({ id, form, action, name }) { + const args = await formatFormData(form, { removeEmpty: false, removeNull: true }) + const call = action + ? api.put( + `domain/${this.name}/actions/${action}`, + { args: objectToParams(args) }, + { key: 'domains.' + name, name: this.name } + ) + : api.put( + `domains/${this.name}/config/${id}`, + { args: objectToParams(args) }, + { key: 'domains.update_config', id, name: this.name } + ) - api.put( - `domains/${this.name}/config`, - { key: id, args: objectToParams(args) }, - { key: 'domains.update_config', name: this.name } - ).then(() => { + call.then(() => { this.$refs.view.fetchQueries({ triggerLoading: true }) }).catch(err => { if (err.name !== 'APIBadRequestError') throw err From af2b50d2c0903ac4e8491ad4b9f3f85674da2202 Mon Sep 17 00:00:00 2001 From: axolotle Date: Mon, 3 Oct 2022 17:12:18 +0200 Subject: [PATCH 14/72] configpanel: remove args props and handling for now --- app/src/components/ConfigPanel.vue | 28 ++++++++++++---------------- app/src/helpers/yunohostArguments.js | 2 +- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/app/src/components/ConfigPanel.vue b/app/src/components/ConfigPanel.vue index d4dad0da..78d6d1a5 100644 --- a/app/src/components/ConfigPanel.vue +++ b/app/src/components/ConfigPanel.vue @@ -24,7 +24,7 @@ @@ -61,24 +61,20 @@ export default { methods: { onApply () { const panelId = this.panel.id - const nonActionKeys = this.panel.sections.filter(section => { - return !section.isActionSection - }).reduce((keys, { fields }) => { - return keys.concat(Object.keys(fields)) - }, []) - - this.$emit('submit', { - id: this.panel.id, - form: filterObject(this.forms[panelId], ([key]) => nonActionKeys.includes(key)) - }) - }, - - onAction (sectionId, actionId, actionArgs) { - const panelId = this.panel.id this.$emit('submit', { id: panelId, - form: filterObject(this.forms[panelId], ([key]) => actionArgs.includes(key)), + form: this.forms[panelId] + }) + }, + + onAction (sectionId, actionId, actionFields) { + const panelId = this.panel.id + const actionFieldsKeys = Object.keys(actionFields) + + this.$emit('submit', { + id: panelId, + form: filterObject(this.forms[panelId], ([key]) => actionFieldsKeys.includes(key)), action: [panelId, sectionId, actionId].join('.'), name: actionId }) diff --git a/app/src/helpers/yunohostArguments.js b/app/src/helpers/yunohostArguments.js index 6f5893c5..a5d56a6c 100644 --- a/app/src/helpers/yunohostArguments.js +++ b/app/src/helpers/yunohostArguments.js @@ -233,7 +233,7 @@ export function formatYunoHostArgument (arg) { { types: ['button'], name: 'ButtonItem', - props: ['type:style', 'label:ask', 'icon', 'enabled', 'args'], + props: ['type:style', 'label:ask', 'icon', 'enabled'], renderSelf: true } ] From 1bacb6c1dfd19b9317fb29650d4826a3245546e8 Mon Sep 17 00:00:00 2001 From: axolotle Date: Mon, 3 Oct 2022 17:14:47 +0200 Subject: [PATCH 15/72] appinstall: update form building with new component handling --- app/src/views/app/AppInstall.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/views/app/AppInstall.vue b/app/src/views/app/AppInstall.vue index d19003cc..6cfa3fa0 100644 --- a/app/src/views/app/AppInstall.vue +++ b/app/src/views/app/AppInstall.vue @@ -24,9 +24,9 @@ @submit.prevent="performInstall" > From 8b6db431d6069967ba16179c208775f79a97a83a Mon Sep 17 00:00:00 2001 From: axolotle Date: Mon, 3 Oct 2022 17:41:02 +0200 Subject: [PATCH 16/72] formitems: add DisplayTextItem for simple text & fix ButtonItem style --- .../components/globals/formItems/ButtonItem.vue | 1 + .../globals/formItems/DisplayTextItem.vue | 16 ++++++++++++++++ .../globals/formItems/MarkdownItem.vue | 1 - app/src/helpers/yunohostArguments.js | 8 +++++++- 4 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 app/src/components/globals/formItems/DisplayTextItem.vue diff --git a/app/src/components/globals/formItems/ButtonItem.vue b/app/src/components/globals/formItems/ButtonItem.vue index 9f890444..c09f3fb4 100644 --- a/app/src/components/globals/formItems/ButtonItem.vue +++ b/app/src/components/globals/formItems/ButtonItem.vue @@ -4,6 +4,7 @@ :variant="type" @click="$emit('action', $event)" :disabled="!enabled" + class="d-block mb-3" > diff --git a/app/src/components/globals/formItems/DisplayTextItem.vue b/app/src/components/globals/formItems/DisplayTextItem.vue new file mode 100644 index 00000000..186ef1a7 --- /dev/null +++ b/app/src/components/globals/formItems/DisplayTextItem.vue @@ -0,0 +1,16 @@ + + + diff --git a/app/src/components/globals/formItems/MarkdownItem.vue b/app/src/components/globals/formItems/MarkdownItem.vue index 7fd4bacb..359c4e67 100644 --- a/app/src/components/globals/formItems/MarkdownItem.vue +++ b/app/src/components/globals/formItems/MarkdownItem.vue @@ -12,4 +12,3 @@ export default { } } - diff --git a/app/src/helpers/yunohostArguments.js b/app/src/helpers/yunohostArguments.js index a5d56a6c..ffded7a5 100644 --- a/app/src/helpers/yunohostArguments.js +++ b/app/src/helpers/yunohostArguments.js @@ -225,11 +225,17 @@ export function formatYunoHostArgument (arg) { renderSelf: true }, { - types: ['markdown', 'display_text'], + types: ['markdown'], name: 'MarkdownItem', props: ['label:ask'], renderSelf: true }, + { + types: ['display_text'], + name: 'DisplayTextItem', + props: ['label:ask'], + renderSelf: true + }, { types: ['button'], name: 'ButtonItem', From 52f433d751db4e7517090555be7ade47258dfd80 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 4 Oct 2022 17:34:09 +0200 Subject: [PATCH 17/72] Remove stale string about certificates, now handled by the core --- app/src/i18n/locales/en.json | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/app/src/i18n/locales/en.json b/app/src/i18n/locales/en.json index 18b240a8..cf65d4f9 100644 --- a/app/src/i18n/locales/en.json +++ b/app/src/i18n/locales/en.json @@ -550,32 +550,9 @@ "wrong_password": "Wrong password", "yes": "Yes", "yunohost_admin": "YunoHost Admin", - "certificate_alert_not_valid": "CRITICAL: Current certificate is not valid! HTTPS won't work at all!", - "certificate_alert_selfsigned": "WARNING: Current certificate is self-signed. Browsers will display a spooky warning to new visitors!", - "certificate_alert_letsencrypt_about_to_expire": "Current certificate is about to expire. It should soon be renewed automatically.", - "certificate_alert_about_to_expire": "WARNING: Current certificate is about to expire! It will NOT be renewed automatically!", - "certificate_alert_good": "Okay, current certificate looks good!", - "certificate_alert_great": "Great! You're using a valid Let's Encrypt certificate!", - "certificate_alert_unknown": "Unknown status", "certificate_manage": "Manage SSL certificate", "ssl_certificate": "SSL certificate", - "confirm_cert_install_LE": "Are you sure you want to install a Let's Encrypt certificate for this domain?", - "confirm_cert_regen_selfsigned": "Are you sure you want to regenerate a self-signed certificate for this domain?", - "confirm_cert_manual_renew_LE": "Are you sure you want to manually renew the Let's Encrypt certificate for this domain now?", - "confirm_cert_revert_to_selfsigned": "Are you sure you want to revert this domain to a self-signed certificate?", "certificate": "Certificate", - "certificate_status": "Certificate status", - "certificate_authority": "Certification authority", - "validity": "Validity", - "domain_is_eligible_for_ACME": "This domain seems correctly configured to install a Let's Encrypt certificate!", - "domain_not_eligible_for_ACME": "This domain doesn't seem ready for a Let's Encrypt certificate. Please check your DNS configuration and HTTP server reachability. The 'DNS records' and 'Web' section in the diagnosis page can help you understand what is misconfigured.", - "install_letsencrypt_cert": "Install a Let's Encrypt certificate", - "manually_renew_letsencrypt_message": "Certificate will be automatically renewed during the last 15 days of validity. You can manually renew it if you want to. (Not recommended).", - "manually_renew_letsencrypt": "Manually renew now", - "regenerate_selfsigned_cert_message": "If you want, you can regenerate the self-signed certificate.", - "regenerate_selfsigned_cert": "Regenerate self-signed certificate", - "revert_to_selfsigned_cert_message": "If you really want to, you can reinstall a self-signed certificate. (Not recommended)", - "revert_to_selfsigned_cert": "Revert to a self-signed certificate", "purge_user_data_checkbox": "Purge {name}'s data? (This will remove the content of its home and mail directories.)", "purge_user_data_warning": "Purging user's data is not reversible. Be sure you know what you're doing!" } From 75f1fae1d67b778bfb094922d693d457ead6fbd2 Mon Sep 17 00:00:00 2001 From: axolotle Date: Tue, 4 Oct 2022 20:11:11 +0200 Subject: [PATCH 18/72] certinstall: remove independant view --- app/src/router/routes.js | 10 -- app/src/views/domain/DomainCert.vue | 160 ---------------------------- app/src/views/domain/DomainInfo.vue | 7 -- 3 files changed, 177 deletions(-) delete mode 100644 app/src/views/domain/DomainCert.vue diff --git a/app/src/router/routes.js b/app/src/router/routes.js index 2f705404..d817808a 100644 --- a/app/src/router/routes.js +++ b/app/src/router/routes.js @@ -179,16 +179,6 @@ const routes = [ breadcrumb: ['domain-list', 'domain-info', 'domain-dns'] } }, - { - name: 'domain-cert', - path: '/domains/:name/cert-management', - component: () => import(/* webpackChunkName: "views/domain/cert" */ '@/views/domain/DomainCert'), - props: true, - meta: { - args: { trad: 'certificate' }, - breadcrumb: ['domain-list', 'domain-info', 'domain-cert'] - } - }, /* ───────╮ │ APPS │ diff --git a/app/src/views/domain/DomainCert.vue b/app/src/views/domain/DomainCert.vue deleted file mode 100644 index e8e0c523..00000000 --- a/app/src/views/domain/DomainCert.vue +++ /dev/null @@ -1,160 +0,0 @@ - - - diff --git a/app/src/views/domain/DomainInfo.vue b/app/src/views/domain/DomainInfo.vue index ed7cb2ac..3f52e27a 100644 --- a/app/src/views/domain/DomainInfo.vue +++ b/app/src/views/domain/DomainInfo.vue @@ -32,13 +32,6 @@
- -

{{ $t('certificate_manage') }}

- - {{ $t('ssl_certificate') }} - -
-

{{ $t('domain_delete_longdesc') }}

Date: Fri, 7 Oct 2022 13:43:49 +0200 Subject: [PATCH 19/72] login: add card-form with admin username input to login --- app/src/i18n/locales/en.json | 2 +- app/src/store/info.js | 4 +- app/src/views/Login.vue | 85 +++++++++++++++++++++++------------- 3 files changed, 58 insertions(+), 33 deletions(-) diff --git a/app/src/i18n/locales/en.json b/app/src/i18n/locales/en.json index 12acc54f..79b636f6 100644 --- a/app/src/i18n/locales/en.json +++ b/app/src/i18n/locales/en.json @@ -551,7 +551,7 @@ "none": "None", "separator": ", " }, - "wrong_password": "Wrong password", + "wrong_password_or_username": "Wrong password or username", "yes": "Yes", "yunohost_admin": "YunoHost Admin", "certificate_manage": "Manage SSL certificate", diff --git a/app/src/store/info.js b/app/src/store/info.js index 1304c1ed..a34db1a2 100644 --- a/app/src/store/info.js +++ b/app/src/store/info.js @@ -142,8 +142,8 @@ export default { }) }, - 'LOGIN' ({ dispatch }, password) { - return api.post('login', { credentials: password }, null, { websocket: false }).then(() => { + 'LOGIN' ({ dispatch }, credentials) { + return api.post('login', { credentials }, null, { websocket: false }).then(() => { dispatch('CONNECT') }) }, diff --git a/app/src/views/Login.vue b/app/src/views/Login.vue index db50f4e6..79955243 100644 --- a/app/src/views/Login.vue +++ b/app/src/views/Login.vue @@ -1,37 +1,35 @@ diff --git a/app/src/views/tool/ToolList.vue b/app/src/views/tool/ToolList.vue index 0b495e82..e326b5fd 100644 --- a/app/src/views/tool/ToolList.vue +++ b/app/src/views/tool/ToolList.vue @@ -24,7 +24,6 @@ export default { { routeName: 'tool-migrations', icon: 'share', translation: 'migrations' }, { routeName: 'service-list', icon: 'gears', translation: 'services' }, { routeName: 'tool-firewall', icon: 'shield', translation: 'firewall' }, - { routeName: 'tool-adminpw', icon: 'key-modern', translation: 'tools_adminpw' }, { routeName: 'tool-settings', icon: 'sliders', translation: 'tools_yunohost_settings' }, { routeName: 'tool-webadmin', icon: 'sliders', translation: 'tools_webadmin_settings' }, { routeName: 'tool-power', icon: 'power-off', translation: 'tools_shutdown_reboot' } From e3cad1c2b3af70e25658e4d10191457ad375ab06 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 8 Oct 2022 18:56:42 +0200 Subject: [PATCH 26/72] i18n: wording for global setting route --- app/src/i18n/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/i18n/locales/en.json b/app/src/i18n/locales/en.json index 06b3039d..c1f3e4c5 100644 --- a/app/src/i18n/locales/en.json +++ b/app/src/i18n/locales/en.json @@ -455,7 +455,7 @@ "update": "Update user '{name}'" }, "settings": { - "update": "Update '{panel}' global settings panel" + "update": "Update '{panel}' global settings" } }, "run": "Run", From 2a800976129c99482a022357fcf44c204f91bb99 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 9 Oct 2022 17:00:48 +0200 Subject: [PATCH 27/72] Merge firstname and lastname info --- app/src/i18n/locales/en.json | 7 +++-- app/src/i18n/locales/fr.json | 5 +-- app/src/store/data.js | 3 +- app/src/views/user/UserCreate.vue | 48 +++-------------------------- app/src/views/user/UserEdit.vue | 51 +++++-------------------------- 5 files changed, 20 insertions(+), 94 deletions(-) diff --git a/app/src/i18n/locales/en.json b/app/src/i18n/locales/en.json index c1f3e4c5..b790154c 100644 --- a/app/src/i18n/locales/en.json +++ b/app/src/i18n/locales/en.json @@ -329,9 +329,10 @@ "path": "Path", "perform": "Perform", "placeholder": { - "username": "johndoe", - "firstname": "John", - "lastname": "Doe", + "username": "samsmith", + "fullname": "Sam Smith", + "firstname": "Sam", + "lastname": "Smith", "groupname": "My group name", "domain": "my-domain.com", "file": "Browse a file or drag and drop it" diff --git a/app/src/i18n/locales/fr.json b/app/src/i18n/locales/fr.json index 5d03b7d4..14c9bfe2 100644 --- a/app/src/i18n/locales/fr.json +++ b/app/src/i18n/locales/fr.json @@ -335,8 +335,9 @@ "domain": "mon-domaine.fr", "groupname": "Le nom de mon groupe", "lastname": "Dupont", - "firstname": "Jean", - "username": "jeandupont", + "firstname": "Camille", + "fullname": "Camille Dupont", + "username": "camilledupont", "file": "Parcourir un fichier ou le faire glisser et déposer" }, "perform": "Exécuter", diff --git a/app/src/store/data.js b/app/src/store/data.js index 50e39b48..e8a4163a 100644 --- a/app/src/store/data.js +++ b/app/src/store/data.js @@ -48,12 +48,11 @@ export default { Vue.set(state.users_details, username, userData) if (!state.users) return const user = state.users[username] - for (const key of ['firstname', 'lastname', 'mail']) { + for (const key of ['fullname', 'mail']) { if (user[key] !== userData[key]) { Vue.set(user, key, userData[key]) } } - Vue.set(user, 'fullname', `${userData.firstname} ${userData.lastname}`) }, 'UPDATE_USERS_DETAILS' (state, payload) { diff --git a/app/src/views/user/UserCreate.vue b/app/src/views/user/UserCreate.vue index 8f3af02c..1ae9e7c5 100644 --- a/app/src/views/user/UserCreate.vue +++ b/app/src/views/user/UserCreate.vue @@ -9,26 +9,7 @@ - - - +


@@ -82,10 +63,7 @@ export default { form: { username: '', - fullname: { - firstname: '', - lastname: '' - }, + fullname: '', domain: '', password: '', confirmation: '' @@ -104,18 +82,9 @@ export default { fullname: { label: this.$i18n.t('user_fullname'), - id: 'fullname', props: { - firstname: { - id: 'firstname', - label: this.$i18n.t('common.firstname'), - placeholder: this.$i18n.t('placeholder.firstname') - }, - lastname: { - id: 'lastname', - label: this.$i18n.t('common.lastname'), - placeholder: this.$i18n.t('placeholder.lastname') - } + id: 'fullname', + placeholder: this.$i18n.t('placeholder.fullname') } }, @@ -156,10 +125,7 @@ export default { return { form: { username: { required, alphalownum_, notInUsers: unique(this.userNames) }, - fullname: { - firstname: { required, name }, - lastname: { required, name } - }, + fullname: { required, name }, domain: { required }, password: { required, passwordLenght: minLength(8) }, confirmation: { required, passwordMatch: sameAs('password') } @@ -189,10 +155,6 @@ export default { diff --git a/app/src/scss/_variables.scss b/app/src/scss/_variables.scss index 4182d446..5d990484 100644 --- a/app/src/scss/_variables.scss +++ b/app/src/scss/_variables.scss @@ -97,3 +97,7 @@ $fa-font-size-base: $font-size-base; // ╰────────────────────╯ $thin-border: $hr-border-width solid $hr-border-color; + +$btn-padding-y-xs: .25rem; +$btn-padding-x-xs: .35rem; +$btn-line-height-xs: 1.5; diff --git a/app/src/scss/main.scss b/app/src/scss/main.scss index f1fe207a..4f4faee6 100644 --- a/app/src/scss/main.scss +++ b/app/src/scss/main.scss @@ -58,6 +58,11 @@ body { } } +// Add xs sized btn +.btn-xs { + @include button-size($btn-padding-y-xs, $btn-padding-x-xs, $btn-font-size-sm, $btn-line-height-xs, $btn-border-radius-sm); +} + // Allow state of input group to be displayed under the group .input-group .is-invalid ~ .invalid-feedback { display: block; From 8fb7cc8469f67054766277509e020952474754f6 Mon Sep 17 00:00:00 2001 From: axolotle Date: Tue, 1 Feb 2022 17:44:50 +0100 Subject: [PATCH 32/72] update DomainList with RecursiveListGroup --- app/src/i18n/locales/en.json | 6 ++- app/src/views/domain/DomainList.vue | 77 ++++++++++++++++++----------- 2 files changed, 53 insertions(+), 30 deletions(-) diff --git a/app/src/i18n/locales/en.json b/app/src/i18n/locales/en.json index c1f3e4c5..aea969f0 100644 --- a/app/src/i18n/locales/en.json +++ b/app/src/i18n/locales/en.json @@ -160,7 +160,11 @@ "push_force": "Overwrite existing records", "push_force_confirm": "Are you sure you want to force push all suggested dns records? Be aware that it may overwrite manually or important default records set by you or your registrar.", "push_force_warning": "It looks like some DNS records that YunoHost would have set are already in the registrar configuration. You can use the overwrite option if you know what you are doing." - } + }, + "types": { + "main_domain": "Main domain" + }, + "toggle_subdomains": "Toggle subdomains" }, "domain_add": "Add domain", "domain_add_dns_doc": "… and I have set my DNS correctly.", diff --git a/app/src/views/domain/DomainList.vue b/app/src/views/domain/DomainList.vue index 5e2f1795..94135b76 100644 --- a/app/src/views/domain/DomainList.vue +++ b/app/src/views/domain/DomainList.vue @@ -3,9 +3,10 @@ id="domain-list" :search.sync="search" :items="domains" - :filtered-items="filteredDomains" items-name="domains" :queries="queries" + :filtered-items="hasFilteredItems" + @queries-response="onQueriesResponse" > - - -
-
- {{ domain }} - - {{ $t('words.default') }} - + + + + + From b7bc950719439123917d8304c24326b97a0c5aeb Mon Sep 17 00:00:00 2001 From: axolotle Date: Tue, 1 Feb 2022 22:02:57 +0100 Subject: [PATCH 34/72] add ExplainWhat component to display a little help popup --- app/src/components/globals/ExplainWhat.vue | 73 ++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 app/src/components/globals/ExplainWhat.vue diff --git a/app/src/components/globals/ExplainWhat.vue b/app/src/components/globals/ExplainWhat.vue new file mode 100644 index 00000000..e5b3af82 --- /dev/null +++ b/app/src/components/globals/ExplainWhat.vue @@ -0,0 +1,73 @@ + + + + + From dcb9534e77204f227d2ff6f91ab3a5f713fd6e5d Mon Sep 17 00:00:00 2001 From: axolotle Date: Tue, 1 Feb 2022 22:17:32 +0100 Subject: [PATCH 35/72] update DomainInfo page with moar data and config panel --- app/src/i18n/locales/en.json | 38 ++++- app/src/scss/main.scss | 3 + app/src/store/data.js | 17 ++ app/src/views/domain/DomainInfo.vue | 232 ++++++++++++++++++++++------ 4 files changed, 242 insertions(+), 48 deletions(-) diff --git a/app/src/i18n/locales/en.json b/app/src/i18n/locales/en.json index aea969f0..ce100431 100644 --- a/app/src/i18n/locales/en.json +++ b/app/src/i18n/locales/en.json @@ -144,6 +144,14 @@ "disabled": "Disabled", "dns": "DNS", "domain": { + "cert": { + "types": { + "self-signed": "Self-signed", + "lets-encrypt": "Let's Encrypt", + "other-unknown": "Other/Unknown" + }, + "valid_for": "valid for {days}" + }, "config": { "edit": "Edit domain configuration", "title": "Domain configuration" @@ -153,6 +161,19 @@ "auto_config_ignored": "ignored, won't be changed by YunoHost unless you check the overwrite option", "auto_config_ok": "Automatic configuration seems to be OK!", "auto_config_zone": "Current DNS zone", + "semi_auto_status": { + "activable": "Activable", + "activated": "Activated", + "has_changes": "Has changes", + "unavailable": "Unavailable" + }, + "methods": { + "auto": "Automatic", + "handled_in_parent": "Handled in parent domain", + "manual": "Manual", + "none": "None", + "semi_auto": "Semi-automatic" + }, "edit": "Edit DNS configuration", "info": "The automatic DNS records configuration is an experimental feature.
Consider saving your current DNS zone from your DNS registrar's interface before pushing records from here.", "manual_config": "Suggested DNS records for manual configuration", @@ -161,7 +182,20 @@ "push_force_confirm": "Are you sure you want to force push all suggested dns records? Be aware that it may overwrite manually or important default records set by you or your registrar.", "push_force_warning": "It looks like some DNS records that YunoHost would have set are already in the registrar configuration. You can use the overwrite option if you know what you are doing." }, + "explain": { + "main_domain": "The main domain is the domain from which users can connect to the portal (via \"{domain}/yunohost/sso\").
Therefore, it is not possible to delete it.
If you want to delete \"{domain}\", you will first have to choose or add another domain and set it as the main domain." + }, + "info": { + "apps_on_domain": "Apps installed on domain", + "certificate_authority": "SSL Certification authority", + "dns_config_method": "DNS config method", + "dns_semi_auto_config_feature": "Semi-auto DNS configuration feature", + "domain_type": "Domain type", + "registrar": "Registrar" + }, "types": { + "parent_domain": "Parent domain", + "subdomain": "Subdomain", "main_domain": "Main domain" }, "toggle_subdomains": "Toggle subdomains" @@ -554,8 +588,10 @@ "browse": "Browse", "collapse": "Collapse", "default": "Default", + "link": "Link", "none": "None", - "separator": ", " + "separator": ", ", + "valid": "Valid" }, "wrong_password_or_username": "Wrong password or username", "yes": "Yes", diff --git a/app/src/scss/main.scss b/app/src/scss/main.scss index 4f4faee6..0fde40c0 100644 --- a/app/src/scss/main.scss +++ b/app/src/scss/main.scss @@ -68,7 +68,10 @@ body { display: block; } + +.tooltip { top: 0; } // Descriptive list ( elems with inside) +// FIXME REMOVE when every infos switch to `DescriptionRow` .row-line { @include media-breakpoint-up(md) { &:hover { diff --git a/app/src/store/data.js b/app/src/store/data.js index ef82e457..a0fbf983 100644 --- a/app/src/store/data.js +++ b/app/src/store/data.js @@ -22,6 +22,7 @@ export default { state: () => ({ main_domain: undefined, domains: undefined, // Array + domains_details: {}, users: undefined, // basic user data: Object {username: {data}} users_details: {}, // precise user data: Object {username: {data}} groups: undefined, @@ -33,6 +34,22 @@ export default { state.domains = domains }, + 'SET_DOMAINS_DETAILS' (state, [name, { domains }]) { + Vue.set(state.domains_details, name, domains[name]) + }, + + 'UPDATE_DOMAINS_DETAILS' (state, payload) { + // FIXME use a common function to execute the same code ? + this.commit('SET_DOMAINS_DETAILS', payload) + }, + + 'DEL_DOMAINS_DETAILS' (state, [name]) { + Vue.delete(state.domains_details, name) + if (state.domains) { + Vue.delete(state.domains, name) + } + }, + 'ADD_DOMAINS' (state, [{ domain }]) { state.domains.push(domain) }, diff --git a/app/src/views/domain/DomainInfo.vue b/app/src/views/domain/DomainInfo.vue index 3f52e27a..6054683e 100644 --- a/app/src/views/domain/DomainInfo.vue +++ b/app/src/views/domain/DomainInfo.vue @@ -1,83 +1,221 @@ @@ -26,5 +27,25 @@ export default { &.fs-sm { font-size: 1rem; } + + &.variant { + font-size: .8rem; + width: 1.35rem; + min-width: 1.35rem; + height: 1.35rem; + line-height: 165%; + border-radius: 50rem; + + @each $color, $value in $theme-colors { + &.#{$color} { + background-color: $value; + color: $white; + } + } + + &.warning { + color: $black; + } + } } From 69d1d7ec1efc7f71fe7cfcd4ea5b56995a86d086 Mon Sep 17 00:00:00 2001 From: axolotle Date: Thu, 6 Oct 2022 21:07:55 +0200 Subject: [PATCH 37/72] domaininfo: remove some info and update view with action handling --- app/src/i18n/locales/en.json | 8 +-- app/src/router/routes.js | 18 +++-- app/src/store/data.js | 4 +- app/src/views/domain/DomainInfo.vue | 104 ++++++++-------------------- 4 files changed, 47 insertions(+), 87 deletions(-) diff --git a/app/src/i18n/locales/en.json b/app/src/i18n/locales/en.json index ce100431..a42bdd61 100644 --- a/app/src/i18n/locales/en.json +++ b/app/src/i18n/locales/en.json @@ -146,9 +146,9 @@ "domain": { "cert": { "types": { - "self-signed": "Self-signed", - "lets-encrypt": "Let's Encrypt", - "other-unknown": "Other/Unknown" + "selfsigned": "Self-signed", + "letsencrypt": "Let's Encrypt", + "other": "Other/Unknown" }, "valid_for": "valid for {days}" }, @@ -188,8 +188,6 @@ "info": { "apps_on_domain": "Apps installed on domain", "certificate_authority": "SSL Certification authority", - "dns_config_method": "DNS config method", - "dns_semi_auto_config_feature": "Semi-auto DNS configuration feature", "domain_type": "Domain type", "registrar": "Registrar" }, diff --git a/app/src/router/routes.js b/app/src/router/routes.js index ef339870..a610f5c6 100644 --- a/app/src/router/routes.js +++ b/app/src/router/routes.js @@ -141,14 +141,22 @@ const routes = [ } }, { - name: 'domain-info', path: '/domains/:name', component: () => import(/* webpackChunkName: "views/domain/info" */ '@/views/domain/DomainInfo'), props: true, - meta: { - args: { param: 'name' }, - breadcrumb: ['domain-list', 'domain-info'] - } + children: [ + { + name: 'domain-info', + path: ':tabId?', + component: () => import(/* webpackChunkName: "components/configPanel" */ '@/components/ConfigPanel'), + props: true, + meta: { + routerParams: ['name'], // Override router key params to avoid view recreation at tab change. + args: { param: 'name' }, + breadcrumb: ['domain-list', 'domain-info'] + } + } + ] }, { // no need for name here, only children are visited diff --git a/app/src/store/data.js b/app/src/store/data.js index a0fbf983..05bbd685 100644 --- a/app/src/store/data.js +++ b/app/src/store/data.js @@ -34,8 +34,8 @@ export default { state.domains = domains }, - 'SET_DOMAINS_DETAILS' (state, [name, { domains }]) { - Vue.set(state.domains_details, name, domains[name]) + 'SET_DOMAINS_DETAILS' (state, [name, details]) { + Vue.set(state.domains_details, name, details) }, 'UPDATE_DOMAINS_DETAILS' (state, payload) { diff --git a/app/src/views/domain/DomainInfo.vue b/app/src/views/domain/DomainInfo.vue index 6054683e..6a4331cf 100644 --- a/app/src/views/domain/DomainInfo.vue +++ b/app/src/views/domain/DomainInfo.vue @@ -1,5 +1,8 @@ @@ -33,16 +33,23 @@ - -