mirror of
https://github.com/YunoHost/yunohost-admin.git
synced 2024-09-03 20:06:15 +02:00
Merge pull request #352 from YunoHost/enh-permissions
Enh permissions (new selectize + confirmation for ssh/sftp)
This commit is contained in:
commit
9e70259899
18 changed files with 321 additions and 417 deletions
|
@ -14,7 +14,6 @@ import { openWebSocket, getResponseData, handleError } from './handlers'
|
|||
* @property {Boolean} wait - If `true`, will display the waiting modal.
|
||||
* @property {Boolean} websocket - if `true`, will open a websocket connection.
|
||||
* @property {Boolean} initial - if `true` and an error occurs, the dismiss button will trigger a go back in history.
|
||||
* @property {Boolean} noCache - if `true`, will disable the cache mecanism for this call.
|
||||
* @property {Boolean} asFormData - if `true`, will send the data with a body encoded as `"multipart/form-data"` instead of `"x-www-form-urlencoded"`).
|
||||
*/
|
||||
|
||||
|
|
|
@ -1,151 +0,0 @@
|
|||
<template>
|
||||
<div class="selectize-base">
|
||||
<b-input-group>
|
||||
<b-input-group-prepend is-text>
|
||||
<icon iname="search-plus" />
|
||||
<span class="ml-1">{{ label }}</span>
|
||||
</b-input-group-prepend>
|
||||
<b-form-input
|
||||
:class="visible ? null : 'collapsed'"
|
||||
aria-controls="collapse" :aria-expanded="visible ? 'true' : 'false'"
|
||||
@focus="onInputFocus" @blur="onInputBlur" @keydown="onInputKeydown"
|
||||
v-model="search" ref="input"
|
||||
/>
|
||||
</b-input-group>
|
||||
|
||||
<b-collapse ref="collapse" v-model="visible">
|
||||
<b-list-group tabindex="-1" @mouseover="onChoiceListOver" v-if="visible">
|
||||
<b-list-group-item
|
||||
v-for="(item, index) in filteredChoices" :key="item"
|
||||
tabindex="-1" :active="index === focusedIndex" ref="choiceList"
|
||||
@mousedown.prevent @mouseup.prevent="onSelect(item)"
|
||||
>
|
||||
{{ item | filter(format) }}
|
||||
</b-list-group-item>
|
||||
</b-list-group>
|
||||
</b-collapse>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// FIXME add accessibility to ChoiceList
|
||||
|
||||
export default {
|
||||
name: 'BaseSelectize',
|
||||
|
||||
props: {
|
||||
choices: { type: Array, required: true },
|
||||
label: { type: String, default: null },
|
||||
// FIXME find a better way to pass filters
|
||||
format: { type: Function, default: null }
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
visible: false,
|
||||
search: '',
|
||||
focusedIndex: 0
|
||||
}),
|
||||
|
||||
computed: {
|
||||
filteredChoices () {
|
||||
const search = this.search.toLowerCase()
|
||||
return this.choices.filter(item => {
|
||||
return item.toLowerCase().includes(search)
|
||||
}).sort()
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onInputFocus ({ relatedTarget }) {
|
||||
this.visible = true
|
||||
this.focusedIndex = 0
|
||||
// timeout needed else scrollIntoView won't work
|
||||
if (!this.$refs.choiceList) return
|
||||
setTimeout(() => {
|
||||
this.$refs.choiceList[0].scrollIntoView({ behavior: 'instant', block: 'nearest', inline: 'nearest' })
|
||||
}, 50)
|
||||
},
|
||||
|
||||
onInputBlur ({ relatedTarget }) {
|
||||
if (!this.$refs.collapse.$el.contains(relatedTarget)) {
|
||||
this.visible = false
|
||||
}
|
||||
},
|
||||
|
||||
onInputKeydown (e) {
|
||||
const { key } = e
|
||||
const choicesLen = this.filteredChoices.length
|
||||
if (choicesLen < 1) return
|
||||
|
||||
if (key === 'ArrowDown') {
|
||||
e.preventDefault()
|
||||
if (this.focusedIndex <= choicesLen) {
|
||||
this.focusedIndex++
|
||||
}
|
||||
} else if (key === 'ArrowUp') {
|
||||
e.preventDefault()
|
||||
if (this.focusedIndex > 0) {
|
||||
this.focusedIndex--
|
||||
}
|
||||
} else if (key === 'Enter') {
|
||||
this.onSelect(this.filteredChoices[this.focusedIndex])
|
||||
this.focusedIndex = 0
|
||||
} else {
|
||||
this.focusedIndex = 0
|
||||
}
|
||||
const elemToFocus = this.$refs.choiceList[this.focusedIndex]
|
||||
if (elemToFocus) {
|
||||
elemToFocus.scrollIntoView({ behavior: 'instant', block: 'nearest', inline: 'nearest' })
|
||||
}
|
||||
},
|
||||
|
||||
onChoiceListOver ({ target }) {
|
||||
const index = this.$refs.choiceList.indexOf(target)
|
||||
if (index > -1) {
|
||||
this.focusedIndex = index
|
||||
}
|
||||
},
|
||||
|
||||
onSelect (item) {
|
||||
this.$emit('selected', { item, index: this.choices.indexOf(item) })
|
||||
}
|
||||
},
|
||||
|
||||
filters: {
|
||||
filter: function (text, func) {
|
||||
if (func) return func(text)
|
||||
else return text
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.collapse {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
// disable collapse animation
|
||||
.collapsing {
|
||||
-webkit-transition: none;
|
||||
transition: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.list-group {
|
||||
margin-top: .5rem;
|
||||
max-height: 10rem;
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
min-height: 2rem;
|
||||
line-height: 1.75rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
154
app/src/components/TagsSelectize.vue
Normal file
154
app/src/components/TagsSelectize.vue
Normal file
|
@ -0,0 +1,154 @@
|
|||
<template>
|
||||
<div class="tags-selectize">
|
||||
<b-form-tags
|
||||
v-bind="$attrs" v-on="$listeners"
|
||||
:value="value" :id="id"
|
||||
size="lg" class="p-0 border-0" no-outer-focus
|
||||
>
|
||||
<template v-slot="{ tags, disabled, addTag, removeTag }">
|
||||
<ul v-if="!noTags && tags.length > 0" class="list-inline d-inline-block mb-2">
|
||||
<li v-for="tag in tags" :key="id + '-' + tag" class="list-inline-item">
|
||||
<b-form-tag
|
||||
@remove="onRemoveTag({ option: tag, removeTag })"
|
||||
:title="tag"
|
||||
:disabled="disabled || disabledItems.includes(tag)"
|
||||
variant="light"
|
||||
class="border border-dark mb-2"
|
||||
>
|
||||
<icon v-if="tagIcon" :iname="tagIcon" /> {{ tag }}
|
||||
</b-form-tag>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<b-dropdown
|
||||
ref="dropdown"
|
||||
variant="outline-dark" block menu-class="w-100"
|
||||
@keydown.native="onDropdownKeydown"
|
||||
>
|
||||
<template #button-content>
|
||||
<icon iname="search-plus" /> {{ label }}
|
||||
</template>
|
||||
|
||||
<b-dropdown-group class="search-group">
|
||||
<b-dropdown-form @submit.stop.prevent="() => {}">
|
||||
<b-form-group
|
||||
:label="$t('search.for', { items: itemsName })"
|
||||
label-cols-md="auto" label-size="sm" :label-for="id + '-search-input'"
|
||||
:invalid-feedback="$t('search.not_found', { items: $tc('items.' + itemsName, 0) })"
|
||||
:state="searchState" :disabled="disabled"
|
||||
class="mb-0"
|
||||
>
|
||||
<b-form-input
|
||||
ref="search-input" v-model="search"
|
||||
:id="id + '-search-input'"
|
||||
type="search" size="sm" autocomplete="off"
|
||||
/>
|
||||
</b-form-group>
|
||||
</b-dropdown-form>
|
||||
<b-dropdown-divider />
|
||||
</b-dropdown-group>
|
||||
|
||||
<b-dropdown-item-button
|
||||
v-for="option in availableOptions"
|
||||
:key="option"
|
||||
@click="onAddTag({ option, addTag })"
|
||||
>
|
||||
{{ option }}
|
||||
</b-dropdown-item-button>
|
||||
<b-dropdown-text v-if="!criteria && availableOptions.length === 0">
|
||||
<icon iname="exclamation-triangle" />
|
||||
{{ $t('items_verbose_items_left', { items: $tc('items.' + itemsName, 0) }) }}
|
||||
</b-dropdown-text>
|
||||
</b-dropdown>
|
||||
</template>
|
||||
</b-form-tags>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TagsSelectize',
|
||||
|
||||
props: {
|
||||
value: { type: Array, required: true },
|
||||
options: { type: Array, required: true },
|
||||
id: { type: String, required: true },
|
||||
itemsName: { type: String, required: true },
|
||||
disabledItems: { type: Array, default: () => ([]) },
|
||||
// By default `addTag` and `removeTag` have to be executed manually by listening to 'tag-update'.
|
||||
auto: { type: Boolean, default: false },
|
||||
noTags: { type: Boolean, default: false },
|
||||
label: { type: String, default: null },
|
||||
tagIcon: { type: String, default: null }
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
search: ''
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
criteria () {
|
||||
return this.search.trim().toLowerCase()
|
||||
},
|
||||
|
||||
availableOptions () {
|
||||
const criteria = this.criteria
|
||||
const options = this.options.filter(opt => {
|
||||
return this.value.indexOf(opt) === -1 && !this.disabledItems.includes(opt)
|
||||
})
|
||||
if (criteria) {
|
||||
return options.filter(opt => opt.toLowerCase().indexOf(criteria) > -1)
|
||||
}
|
||||
return options
|
||||
},
|
||||
|
||||
searchState () {
|
||||
return this.criteria && this.availableOptions.length === 0 ? false : null
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onAddTag ({ option, addTag }) {
|
||||
this.$emit('tag-update', { action: 'add', option, applyMethod: addTag })
|
||||
this.search = ''
|
||||
if (this.auto) {
|
||||
addTag(option)
|
||||
}
|
||||
},
|
||||
|
||||
onRemoveTag ({ option, removeTag }) {
|
||||
this.$emit('tag-update', { action: 'remove', option, applyMethod: removeTag })
|
||||
if (this.auto) {
|
||||
removeTag(option)
|
||||
}
|
||||
},
|
||||
|
||||
onDropdownKeydown (e) {
|
||||
// Allow to start searching after dropdown opening
|
||||
if (
|
||||
!['Tab', 'Space'].includes(e.code) &&
|
||||
e.target === this.$refs.dropdown.$el.lastElementChild
|
||||
) {
|
||||
this.$refs['search-input'].focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep .dropdown-menu {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
padding-top: 0;
|
||||
|
||||
.search-group {
|
||||
padding-top: .5rem;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,98 +0,0 @@
|
|||
<template lang="html">
|
||||
<div class="selectize-zone">
|
||||
<div id="selected-items" v-if="selected.length > 0">
|
||||
<b-button-group size="sm" v-for="item in filteredSelected" :key="item">
|
||||
<b-button :to="itemRoute ? {name: itemRoute, params: {name: item}} : null" class="item-btn btn-light btn-outline-dark">
|
||||
<icon :iname="itemIcon" /> {{ item | filter(format) }}
|
||||
</b-button>
|
||||
<b-button
|
||||
v-if="!removable || removable(item)"
|
||||
class="remove-btn btn-outline-dark" variant="warning"
|
||||
@click="onRemove(item)"
|
||||
>
|
||||
<icon :title="$t('delete')" iname="minus" />
|
||||
</b-button>
|
||||
</b-button-group>
|
||||
</div>
|
||||
|
||||
<base-selectize
|
||||
v-if="choices.length"
|
||||
:choices="choices"
|
||||
:format="format"
|
||||
:label="label"
|
||||
@selected="$emit('change', { ...$event, action: 'add' })"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseSelectize from '@/components/BaseSelectize'
|
||||
|
||||
export default {
|
||||
name: 'ZoneSelectize',
|
||||
|
||||
props: {
|
||||
itemIcon: { type: String, default: null },
|
||||
itemRoute: { type: String, default: null },
|
||||
selected: { type: Array, required: true },
|
||||
// needed by SelectizeBase
|
||||
choices: { type: Array, required: true },
|
||||
label: { type: String, default: null },
|
||||
format: { type: Function, default: null },
|
||||
removable: { type: Function, default: null }
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
visible: false,
|
||||
search: '',
|
||||
focusedIndex: 0
|
||||
}),
|
||||
|
||||
computed: {
|
||||
filteredSelected () {
|
||||
return [...this.selected].sort()
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onRemove (item) {
|
||||
this.$emit('change', { item, index: this.selected.indexOf(item), action: 'remove' })
|
||||
}
|
||||
},
|
||||
|
||||
filters: {
|
||||
filter: function (text, func) {
|
||||
if (func) return func(text)
|
||||
else return text
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
BaseSelectize
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#selected-items {
|
||||
margin-bottom: .75rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.btn-group {
|
||||
margin-right: .5rem;
|
||||
margin-bottom: .5rem;
|
||||
|
||||
.item-btn {
|
||||
.icon {
|
||||
margin-right: .25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fa-minus {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
</style>
|
|
@ -28,8 +28,8 @@
|
|||
"confirm_app_default": "أمتأكد مِن أنك تود تعيين هذا التطبيق كبرنامج إفتراضي ؟",
|
||||
"confirm_change_maindomain": "متأكد من أنك تريد تغيير النطاق الرئيسي ؟",
|
||||
"confirm_delete": "هل تود حقًا حذف {name} ؟",
|
||||
"confirm_firewall_open": "متأكد مِن أنك تود فتح منفذ {port} ؟ (بروتوكول : {protocol}، إتصال : {connection})",
|
||||
"confirm_firewall_close": "متأكد مِن أنك تود إغلاق منفذ {port} ؟ (بروتوكول : {protocol}، إتصال : {connection})",
|
||||
"confirm_firewall_allow": "متأكد مِن أنك تود فتح منفذ {port} ؟ (بروتوكول : {protocol}، إتصال : {connection})",
|
||||
"confirm_firewall_disallow": "متأكد مِن أنك تود إغلاق منفذ {port} ؟ (بروتوكول : {protocol}، إتصال : {connection})",
|
||||
"confirm_install_custom_app": "إنّ خيار تنصيب تطبيقات خارجية قد يؤثر على أمان نظامكم. ربما وجب عليكم ألا تقوموا بالتنصيب إلا إن كنتم حقا مدركون بما أنتم فاعلين. هل أنتم مستعدون للمخاطرة؟",
|
||||
"confirm_install_domain_root": "لن يكون بإمكانك تنصيب أي برنامج آخر على {domain}. هل تريد المواصلة ؟",
|
||||
"confirm_postinstall": "إنك بصدد إطلاق خطوة ما بعد التنصيب على النطاق {domain}. سوف تستغرق العملية بضع دقائق، لذلك *يُرجى عدم إيقاف العملية*.",
|
||||
|
|
|
@ -28,8 +28,8 @@
|
|||
"confirm_app_default": "Està segur de voler fer aquesta aplicació predeterminada?",
|
||||
"confirm_change_maindomain": "Està segur de voler canviar el domini principal?",
|
||||
"confirm_delete": "Està segur de voler eliminar {name}?",
|
||||
"confirm_firewall_open": "Està segur de voler obrir el port {port}? (protocol: {protocol}, connexió: {connection})",
|
||||
"confirm_firewall_close": "Està segur de voler tancar el port {port}? (protocol: {protocol}, connexió: {connection})",
|
||||
"confirm_firewall_allow": "Està segur de voler obrir el port {port}? (protocol: {protocol}, connexió: {connection})",
|
||||
"confirm_firewall_disallow": "Està segur de voler tancar el port {port}? (protocol: {protocol}, connexió: {connection})",
|
||||
"confirm_install_custom_app": "ATENCIÓ! La instal·lació d'aplicacions de terceres parts pot comprometre la integritat i seguretat del seu sistema. No hauríeu d'instal·lar-ne a no ser que sapigueu el que feu. Esteu segurs de voler córrer aquest risc?",
|
||||
"confirm_install_domain_root": "No podrà instal·lar cap altra aplicació {domain}. Vol continuar?",
|
||||
"confirm_migrations_skip": "Saltar-se les migracions no està recomanat. Està segur de voler continuar?",
|
||||
|
|
|
@ -185,8 +185,8 @@
|
|||
"confirm_update_apps": "Möchtest du wirklich alle Anwendungen aktualisieren?",
|
||||
"confirm_upnp_enable": "Möchtest du wirklich UPnP aktivieren?",
|
||||
"confirm_upnp_disable": "Möchtest du wirklich UPnP deaktivieren?",
|
||||
"confirm_firewall_open": "Möchtest du wirklich Port {port}1 öffnen? (Protokoll: {protocol}2, Verbindung: {connection}3)",
|
||||
"confirm_firewall_close": "Möchtest du wirklich Port {port}1 schließen? (Protokoll: {protocol}2, Verbindung: {connection}3)",
|
||||
"confirm_firewall_allow": "Möchtest du wirklich Port {port}1 öffnen? (Protokoll: {protocol}2, Verbindung: {connection}3)",
|
||||
"confirm_firewall_disallow": "Möchtest du wirklich Port {port}1 schließen? (Protokoll: {protocol}2, Verbindung: {connection}3)",
|
||||
"confirm_update_specific_app": "Möchtest du wirklich {app} aktualisieren?",
|
||||
"confirm_reboot_action_reboot": "Möchtest du wirklich den Server neustarten?",
|
||||
"confirm_reboot_action_shutdown": "Möchtest du wirklich den Server herunterfahren?",
|
||||
|
|
|
@ -94,6 +94,7 @@
|
|||
"confirm_delete": "Are you sure you want to delete {name}?",
|
||||
"confirm_firewall_allow": "Are you sure you want to open port {port} (protocol: {protocol}, connection: {connection})",
|
||||
"confirm_firewall_disallow": "Are you sure you want to close port {port} (protocol: {protocol}, connection: {connection})",
|
||||
"confirm_group_add_access_permission": "Are you sure you want to grant {perm} access to {name}? Such access significantly increases the attack surface if {name} happens to be a malicious person. You should only do so if you TRUST this person/group.",
|
||||
"confirm_install_custom_app": "WARNING! Installing 3rd party applications may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. Are you willing to take that risk?",
|
||||
"confirm_install_domain_root": "Are you sure you want to install this application on '/'? You will not be able to install any other app on {domain}",
|
||||
"confirm_app_install": "Are you sure you want to install this application?",
|
||||
|
@ -242,10 +243,12 @@
|
|||
"groups": "no groups | group | {c} groups",
|
||||
"installed_apps": "no installed apps | installed app | {c} installed apps",
|
||||
"logs": "no logs | log | {c} logs",
|
||||
"permissions": "no permissions | permission | {c} permissions",
|
||||
"services": "no services | service | {c} services",
|
||||
"users": "no users | user | {c} users"
|
||||
},
|
||||
"items_verbose_count": "There is {items}.",
|
||||
"items_verbose_count": "There are {items}.",
|
||||
"items_verbose_items_left": "There are {items} left.",
|
||||
"label": "Label",
|
||||
"label_for_manifestname": "Label for {name}",
|
||||
"last_ran": "Last time ran:",
|
||||
|
@ -412,7 +415,7 @@
|
|||
"save": "Save",
|
||||
"search": {
|
||||
"for": "Search for {items}...",
|
||||
"not_found": "There is {items} matching your criteria."
|
||||
"not_found": "There are {items} matching your criteria."
|
||||
},
|
||||
"select_all": "Select all",
|
||||
"select_none": "Select none",
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
"hook_conf_ssh": "SSH",
|
||||
"confirm_update_system": "Ĉu vi certas, ke vi volas ĝisdatigi ĉiujn sistemajn pakaĵojn ?",
|
||||
"installation_complete": "Kompleta instalado",
|
||||
"confirm_firewall_open": "Ĉu vi certas, ke vi volas malfermi havenojn {port} ? (Protokoloj {protocol}, konekto: {connection})",
|
||||
"confirm_firewall_allow": "Ĉu vi certas, ke vi volas malfermi havenojn {port} ? (Protokoloj {protocol}, konekto: {connection})",
|
||||
"confirm_postinstall": "Vi tuj lanĉos la postinstalaran procezon sur la domajno {domain}. Eble daŭras kelkajn minutojn, *ne interrompu la operacion*.",
|
||||
"description": "priskribo",
|
||||
"hook_conf_ynh_mysql": "MySQL pasvorto",
|
||||
|
@ -84,7 +84,7 @@
|
|||
"hook_data_mail": "Poŝto",
|
||||
"backup_create": "Krei sekurkopion",
|
||||
"confirm_uninstall": "Ĉu vi certas, ke vi volas malinstali {name} ?",
|
||||
"confirm_firewall_close": "Ĉu vi certas, ke vi volas fermi havenon {port} ? (protokolo: {protocol}, rilato: {connection})",
|
||||
"confirm_firewall_disallow": "Ĉu vi certas, ke vi volas fermi havenon {port} ? (protokolo: {protocol}, rilato: {connection})",
|
||||
"created_at": "Kreita ĉe",
|
||||
"confirm_app_change_url": "Ĉu vi certas, ke vi volas ŝanĝi la URL-aliron de la aplikaĵo?",
|
||||
"ipv6": "IPv6",
|
||||
|
|
|
@ -180,8 +180,8 @@
|
|||
"revert_to_selfsigned_cert_message": "Si realmente lo desea, puede reinstalar un certificado autofirmado. (No recomendado)",
|
||||
"revert_to_selfsigned_cert": "Volver a un certificado autofirmado",
|
||||
"user_mailbox_use": "Espacio utilizado",
|
||||
"confirm_firewall_open": "¿Está seguro de que desea abrir el puerto {port}? (protocolo: {protocol}, conexión: {connection})",
|
||||
"confirm_firewall_close": "¿Está seguro de que desea cerrar el puerto {port}? (protocolo: {protocol}, conexión: {connection})",
|
||||
"confirm_firewall_allow": "¿Está seguro de que desea abrir el puerto {port}? (protocolo: {protocol}, conexión: {connection})",
|
||||
"confirm_firewall_disallow": "¿Está seguro de que desea cerrar el puerto {port}? (protocolo: {protocol}, conexión: {connection})",
|
||||
"confirm_service_start": "¿Está seguro de que desea iniciar {name}?",
|
||||
"confirm_service_stop": "¿Está seguro de que desea parar {name}?",
|
||||
"confirm_update_apps": "¿Está seguro de que desea actualizar todas las aplicaciones?",
|
||||
|
|
|
@ -180,8 +180,8 @@
|
|||
"revert_to_selfsigned_cert_message": "Si vous le souhaitez vraiment, vous pouvez réinstaller un certificat auto-signé (non recommandé).",
|
||||
"revert_to_selfsigned_cert": "Retourner à un certificat auto-signé",
|
||||
"user_mailbox_use": "Espace utilisé de la boite aux lettres",
|
||||
"confirm_firewall_open": "Voulez-vous vraiment ouvrir le port {port} ? (protocole : {protocol}, connexion : {connection})",
|
||||
"confirm_firewall_close": "Voulez-vous vraiment fermer le port {port} ? (protocole : {protocol}, connexion : {connection})",
|
||||
"confirm_firewall_allow": "Voulez-vous vraiment ouvrir le port {port} ? (protocole : {protocol}, connexion : {connection})",
|
||||
"confirm_firewall_disallow": "Voulez-vous vraiment fermer le port {port} ? (protocole : {protocol}, connexion : {connection})",
|
||||
"confirm_service_start": "Voulez-vous vraiment démarrer {name} ?",
|
||||
"confirm_service_stop": "Voulez-vous vraiment arrêter {name} ?",
|
||||
"confirm_update_apps": "Voulez-vous vraiment mettre à jour toutes les applications ?",
|
||||
|
|
|
@ -160,8 +160,8 @@
|
|||
"app_info_changeurl_desc": "Cambia l'URL di accesso di questa applicazione (dominio e/o percorso).",
|
||||
"app_info_change_url_disabled_tooltip": "Questa funzionalità non è ancora stata implementata in questa applicazione",
|
||||
"confirm_app_change_url": "Sei sicuro di voler cambiare l'URL di accesso all'applicazione ?",
|
||||
"confirm_firewall_open": "Sei sicuro di voler aprire la porta {port}? (protocollo: {protocol}, connessione: {connection})",
|
||||
"confirm_firewall_close": "Sei sicuro di voler chiudere la porta {port}? (protocollo: {protocol}, connessione: {connection})",
|
||||
"confirm_firewall_allow": "Sei sicuro di voler aprire la porta {port}? (protocollo: {protocol}, connessione: {connection})",
|
||||
"confirm_firewall_disallow": "Sei sicuro di voler chiudere la porta {port}? (protocollo: {protocol}, connessione: {connection})",
|
||||
"confirm_migrations_skip": "Saltare le migrazioni è sconsigliato. Sei sicuro di volerlo fare?",
|
||||
"confirm_service_start": "Sei sicuro di voler eseguire {name}?",
|
||||
"confirm_service_stop": "Sei sicuro di voler fermare {name}?",
|
||||
|
|
|
@ -28,8 +28,8 @@
|
|||
"confirm_app_default": "Volètz vertadièrament definir aquesta aplicacion coma aplicacion per defaut ?",
|
||||
"confirm_change_maindomain": "Volètz vertadièrament cambiar lo domeni màger ?",
|
||||
"confirm_delete": "Volètz vertadièrament escafar {name} ?",
|
||||
"confirm_firewall_open": "Volètz vertadièrament dobrir lo pòrt {port} ? (protocòl : {protocol}, connexion : {connection})",
|
||||
"confirm_firewall_close": "Volètz vertadièrament tampar lo pòrt {port} ? (protocòl : {protocol}, connexion : {connection})",
|
||||
"confirm_firewall_allow": "Volètz vertadièrament dobrir lo pòrt {port} ? (protocòl : {protocol}, connexion : {connection})",
|
||||
"confirm_firewall_disallow": "Volètz vertadièrament tampar lo pòrt {port} ? (protocòl : {protocol}, connexion : {connection})",
|
||||
"confirm_install_custom_app": "Atencion ! L’installacion d’aplicacions tèrças pòt perilhar l’integritat e la seguretat del sistèma. Auriatz PAS de n’installar levat que saupèssetz çò que fasètz. Volètz vertadièrament córrer aqueste risc ?",
|
||||
"confirm_install_domain_root": "Poiretz pas installar mai aplicacions sus {domain}. Contunhar ?",
|
||||
"confirm_migrations_skip": "Passar las migracions es pas recomandat. Volètz vertadièrament o far ?",
|
||||
|
|
|
@ -30,8 +30,8 @@
|
|||
"logout": "Wyloguj",
|
||||
"ok": "OK",
|
||||
"confirm_app_install": "Czy na pewno chcesz zainstalować tę aplikację?",
|
||||
"confirm_firewall_close": "Czy na pewno chcesz zamknąć port {port} (protocol:{protocol}, connection: {connection})",
|
||||
"confirm_firewall_open": "Czy na pewno chcesz otworzyć port {port} (protocol:{protocol}, connection: {connection})",
|
||||
"confirm_firewall_disallow": "Czy na pewno chcesz zamknąć port {port} (protocol:{protocol}, connection: {connection})",
|
||||
"confirm_firewall_allow": "Czy na pewno chcesz otworzyć port {port} (protocol:{protocol}, connection: {connection})",
|
||||
"confirm_delete": "Czy na pewno chcesz usunąć {name}?",
|
||||
"confirm_change_maindomain": "Czy na pewno chcesz zmienić domenę podstawową?",
|
||||
"confirm_app_default": "Czy na pewno chcesz ustawić tę aplikację jako domyślną?",
|
||||
|
|
|
@ -137,8 +137,8 @@
|
|||
"backup_new": "Nova cópia de segurança",
|
||||
"check": "Verificação",
|
||||
"confirm_app_change_url": "Tem certeza que quer mudar o endereço URL para acessar esta aplicação?",
|
||||
"confirm_firewall_open": "Tem certeza que quer abrir a porta {port}? (protocolo: {protocol}, conexão: {connection})",
|
||||
"confirm_firewall_close": "Tem certeza que quer fechar a porta {port}? (protocolo: {protocol}, conexão: {connection})",
|
||||
"confirm_firewall_allow": "Tem certeza que quer abrir a porta {port}? (protocolo: {protocol}, conexão: {connection})",
|
||||
"confirm_firewall_disallow": "Tem certeza que quer fechar a porta {port}? (protocolo: {protocol}, conexão: {connection})",
|
||||
"confirm_install_custom_app": "CUIDADO! Instalar aplicações de terceiros pode comprometer a integridade e a segurança do seu sistema. Provavelmente NÃO deveria instalar esta aplicação se não tiver certeza do que está fazendo. Quer correr esses riscos?",
|
||||
"confirm_install_domain_root": "Não será mas capaz de instalar outras aplicações em {domain}. Quer continuar?",
|
||||
"confirm_install_app_lowquality": "Aviso: esta aplicação pode funcionar mais não está bem integrada em Yunohost. Algumas funcionalidades como logon único e/ou cópia de segurança/restauro pode não ser disponível.",
|
||||
|
|
|
@ -33,8 +33,8 @@
|
|||
"confirm_app_default": "Вы хотите сделать это приложение приложением по умолчанию?",
|
||||
"confirm_change_maindomain": "Вы хотите изменить главный домен?",
|
||||
"confirm_delete": "Вы хотите удалить {name}1 ?",
|
||||
"confirm_firewall_open": "Вы хотите открыть порт {port}1 ? (протокол {protocol}2, соединение {connection}3)",
|
||||
"confirm_firewall_close": "Вы хотите закрыть порт {port}1 ? (протокол {protocol}2, соединение {connection}3)",
|
||||
"confirm_firewall_allow": "Вы хотите открыть порт {port}1 ? (протокол {protocol}2, соединение {connection}3)",
|
||||
"confirm_firewall_disallow": "Вы хотите закрыть порт {port}1 ? (протокол {protocol}2, соединение {connection}3)",
|
||||
"confirm_install_custom_app": "Установка сторонних приложений может повредить безопасности вашей системы. Установка на вашу ответственность.",
|
||||
"confirm_install_domain_root": "Вы больше не сможете устанавливать приложения на {domain}1. Продолжить?",
|
||||
"confirm_restore": "Вы хотите восстановить {name}1 ?",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Vue from 'vue'
|
||||
|
||||
import api from '@/api'
|
||||
import { isEmptyValue } from '@/helpers/commons'
|
||||
|
||||
|
||||
export default {
|
||||
|
@ -14,31 +15,31 @@ export default {
|
|||
}),
|
||||
|
||||
mutations: {
|
||||
'SET_DOMAINS' (state, domains) {
|
||||
'SET_DOMAINS' (state, [domains]) {
|
||||
state.domains = domains
|
||||
},
|
||||
|
||||
'ADD_DOMAINS' (state, { domain }) {
|
||||
'ADD_DOMAINS' (state, [{ domain }]) {
|
||||
state.domains.push(domain)
|
||||
},
|
||||
|
||||
'DEL_DOMAINS' (state, domain) {
|
||||
'DEL_DOMAINS' (state, [domain]) {
|
||||
state.domains.splice(state.domains.indexOf(domain), 1)
|
||||
},
|
||||
|
||||
'SET_MAIN_DOMAIN' (state, response) {
|
||||
'SET_MAIN_DOMAIN' (state, [response]) {
|
||||
state.main_domain = response.current_main_domain
|
||||
},
|
||||
|
||||
'UPDATE_MAIN_DOMAIN' (state, domain) {
|
||||
'UPDATE_MAIN_DOMAIN' (state, [domain]) {
|
||||
state.main_domain = domain
|
||||
},
|
||||
|
||||
'SET_USERS' (state, users) {
|
||||
'SET_USERS' (state, [users]) {
|
||||
state.users = Object.keys(users).length === 0 ? null : users
|
||||
},
|
||||
|
||||
'ADD_USERS' (state, user) {
|
||||
'ADD_USERS' (state, [user]) {
|
||||
if (!state.users) state.users = {}
|
||||
Vue.set(state.users, user.username, user)
|
||||
},
|
||||
|
@ -60,7 +61,7 @@ export default {
|
|||
this.commit('SET_USERS_DETAILS', payload)
|
||||
},
|
||||
|
||||
'DEL_USERS_DETAILS' (state, username) {
|
||||
'DEL_USERS_DETAILS' (state, [username]) {
|
||||
Vue.delete(state.users_details, username)
|
||||
if (state.users) {
|
||||
Vue.delete(state.users, username)
|
||||
|
@ -70,60 +71,80 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
'SET_GROUPS' (state, groups) {
|
||||
'SET_GROUPS' (state, [groups]) {
|
||||
state.groups = groups
|
||||
},
|
||||
|
||||
'ADD_GROUPS' (state, { name }) {
|
||||
'ADD_GROUPS' (state, [{ name }]) {
|
||||
if (state.groups !== undefined) {
|
||||
Vue.set(state.groups, name, { members: [], permissions: [] })
|
||||
}
|
||||
},
|
||||
|
||||
'DEL_GROUPS' (state, groupname) {
|
||||
'UPDATE_GROUPS' (state, [data, { groupName }]) {
|
||||
Vue.set(state.groups, groupName, data)
|
||||
},
|
||||
|
||||
'DEL_GROUPS' (state, [groupname]) {
|
||||
Vue.delete(state.groups, groupname)
|
||||
},
|
||||
|
||||
'SET_PERMISSIONS' (state, permissions) {
|
||||
'SET_PERMISSIONS' (state, [permissions]) {
|
||||
state.permissions = permissions
|
||||
},
|
||||
|
||||
'UPDATE_PERMISSIONS' (state, [_, { groupName, action, permId }]) {
|
||||
// FIXME hacky way to update the store
|
||||
const permissions = state.groups[groupName].permissions
|
||||
if (action === 'add') {
|
||||
permissions.push(permId)
|
||||
} else if (action === 'remove') {
|
||||
const index = permissions.indexOf(permId)
|
||||
if (index > -1) permissions.splice(index, 1)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
'GET' ({ state, commit, rootState }, { uri, param, humanKey, storeKey = uri, options = {} }) {
|
||||
const noCache = !rootState.cache || options.noCache || false
|
||||
'GET' (
|
||||
{ state, commit, rootState },
|
||||
{ uri, param, storeKey = uri, humanKey, noCache, options, ...extraParams }
|
||||
) {
|
||||
const currentState = param ? state[storeKey][param] : state[storeKey]
|
||||
// if data has already been queried, simply return
|
||||
if (currentState !== undefined && !noCache) return currentState
|
||||
|
||||
const ignoreCache = !rootState.cache || noCache || false
|
||||
if (currentState !== undefined && !ignoreCache) return currentState
|
||||
return api.fetch('GET', param ? `${uri}/${param}` : uri, null, humanKey, options).then(responseData => {
|
||||
const data = responseData[storeKey] ? responseData[storeKey] : responseData
|
||||
commit('SET_' + storeKey.toUpperCase(), param ? [param, data] : data)
|
||||
commit(
|
||||
'SET_' + storeKey.toUpperCase(),
|
||||
[param, data, extraParams].filter(item => !isEmptyValue(item))
|
||||
)
|
||||
return param ? state[storeKey][param] : state[storeKey]
|
||||
})
|
||||
},
|
||||
|
||||
'POST' ({ state, commit }, { uri, storeKey = uri, data, humanKey, options }) {
|
||||
'POST' ({ state, commit }, { uri, storeKey = uri, data, humanKey, options, ...extraParams }) {
|
||||
return api.fetch('POST', uri, data, humanKey, options).then(responseData => {
|
||||
// FIXME api/domains returns null
|
||||
if (responseData === null) responseData = data
|
||||
responseData = responseData[storeKey] ? responseData[storeKey] : responseData
|
||||
commit('ADD_' + storeKey.toUpperCase(), responseData)
|
||||
commit('ADD_' + storeKey.toUpperCase(), [responseData, extraParams].filter(item => !isEmptyValue(item)))
|
||||
return state[storeKey]
|
||||
})
|
||||
},
|
||||
|
||||
'PUT' ({ state, commit }, { uri, param, storeKey = uri, data, humanKey, options }) {
|
||||
'PUT' ({ state, commit }, { uri, param, storeKey = uri, data, humanKey, options, ...extraParams }) {
|
||||
return api.fetch('PUT', param ? `${uri}/${param}` : uri, data, humanKey, options).then(responseData => {
|
||||
const data = responseData[storeKey] ? responseData[storeKey] : responseData
|
||||
commit('UPDATE_' + storeKey.toUpperCase(), param ? [param, data] : data)
|
||||
commit('UPDATE_' + storeKey.toUpperCase(), [param, data, extraParams].filter(item => !isEmptyValue(item)))
|
||||
return param ? state[storeKey][param] : state[storeKey]
|
||||
})
|
||||
},
|
||||
|
||||
'DELETE' ({ commit }, { uri, param, storeKey = uri, data, humanKey, options }) {
|
||||
'DELETE' ({ commit }, { uri, param, storeKey = uri, data, humanKey, options, ...extraParams }) {
|
||||
return api.fetch('DELETE', param ? `${uri}/${param}` : uri, data, humanKey, options).then(() => {
|
||||
commit('DEL_' + storeKey.toUpperCase(), param)
|
||||
commit('DEL_' + storeKey.toUpperCase(), [param, extraParams].filter(item => !isEmptyValue(item)))
|
||||
})
|
||||
}
|
||||
},
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<view-search
|
||||
items-name="groups"
|
||||
:search.sync="search"
|
||||
:items="normalGroups"
|
||||
:items="primaryGroups"
|
||||
:filtered-items="filteredGroups"
|
||||
:queries="queries"
|
||||
@queries-response="onQueriesResponse"
|
||||
|
@ -16,13 +16,13 @@
|
|||
|
||||
<!-- PRIMARY GROUPS CARDS -->
|
||||
<card
|
||||
v-for="(group, name) in filteredGroups" :key="name" collapsable
|
||||
:title="group.isSpecial ? $t('group_' + name) : `${$t('group')} '${name}'`" icon="group"
|
||||
v-for="(group, groupName) in filteredGroups" :key="groupName" collapsable
|
||||
:title="group.isSpecial ? $t('group_' + groupName) : `${$t('group')} '${groupName}'`" icon="group"
|
||||
>
|
||||
<template #header-buttons>
|
||||
<!-- DELETE GROUP -->
|
||||
<b-button
|
||||
v-if="!group.isSpecial" @click="deleteGroup(name)"
|
||||
v-if="!group.isSpecial" @click="deleteGroup(groupName)"
|
||||
size="sm" variant="danger"
|
||||
>
|
||||
<icon iname="trash-o" /> {{ $t('delete') }}
|
||||
|
@ -37,18 +37,18 @@
|
|||
<b-col>
|
||||
<template v-if="group.isSpecial">
|
||||
<p class="text-primary">
|
||||
<icon iname="info-circle" /> {{ $t('group_explain_' + name) }}
|
||||
<icon iname="info-circle" /> {{ $t('group_explain_' + groupName) }}
|
||||
</p>
|
||||
<p class="text-primary" v-if="name === 'visitors'">
|
||||
<p class="text-primary" v-if="groupName === 'visitors'">
|
||||
<em>{{ $t('group_explain_visitors_needed_for_external_client') }}</em>
|
||||
</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<zone-selectize
|
||||
:choices="group.availableMembers" :selected="group.members"
|
||||
item-icon="user"
|
||||
:label="$t('group_add_member')"
|
||||
@change="onUserChanged({ ...$event, name })"
|
||||
<tags-selectize
|
||||
v-model="group.members" :options="usersOptions"
|
||||
:id="groupName + '-users'" :label="$t('group_add_member')"
|
||||
tag-icon="user" items-name="users"
|
||||
@tag-update="onUserChanged({ ...$event, groupName })"
|
||||
/>
|
||||
</template>
|
||||
</b-col>
|
||||
|
@ -60,50 +60,45 @@
|
|||
<strong>{{ $t('permissions') }}</strong>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<zone-selectize
|
||||
item-icon="key-modern"
|
||||
:choices="group.availablePermissions"
|
||||
:selected="group.permissions"
|
||||
:label="$t('group_add_permission')"
|
||||
:format="formatPermission"
|
||||
:removable="name === 'visitors' ? removable : null"
|
||||
@change="onPermissionChanged({ ...$event, name, groupType: 'normal' })"
|
||||
<tags-selectize
|
||||
v-model="group.permissions" :options="permissionsOptions"
|
||||
:id="groupName + '-perms'" :label="$t('group_add_permission')"
|
||||
tag-icon="key-modern" items-name="permissions"
|
||||
@tag-update="onPermissionChanged({ ...$event, groupName })"
|
||||
:disabled-items="group.disabledItems"
|
||||
/>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</card>
|
||||
|
||||
<!-- GROUP SPECIFIC CARD -->
|
||||
<!-- USER GROUPS CARD -->
|
||||
<card
|
||||
v-if="userGroups" collapsable
|
||||
:title="$t('group_specific_permissions')" icon="group"
|
||||
>
|
||||
<template v-for="(name, index) in userGroupsNames">
|
||||
<b-row :key="name">
|
||||
<template v-for="(userName, index) in activeUserGroups">
|
||||
<b-row :key="userName">
|
||||
<b-col md="3" lg="2">
|
||||
<icon iname="user" /> <strong>{{ name }}</strong>
|
||||
<icon iname="user" /> <strong>{{ userName }}</strong>
|
||||
</b-col>
|
||||
|
||||
<b-col>
|
||||
<zone-selectize
|
||||
item-icon="key-modern" item-variant="dark"
|
||||
:choices="userGroups[name].availablePermissions"
|
||||
:selected="userGroups[name].permissions"
|
||||
:label="$t('group_add_permission')"
|
||||
:format="formatPermission"
|
||||
@change="onPermissionChanged({ ...$event, name, groupType: 'user' })"
|
||||
<tags-selectize
|
||||
v-model="userGroups[userName].permissions" :options="permissionsOptions"
|
||||
:id="userName + '-perms'" :label="$t('group_add_permission')"
|
||||
tag-icon="key-modern" items-name="permissions"
|
||||
@tag-update="onPermissionChanged({ ...$event, groupName: userName })"
|
||||
/>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<hr :key="index">
|
||||
</template>
|
||||
|
||||
<base-selectize
|
||||
v-if="availableMembers.length"
|
||||
:label="$t('group_add_member')"
|
||||
:choices="availableMembers"
|
||||
:selected="userGroupsNames"
|
||||
@selected="onSpecificUserAdded"
|
||||
<tags-selectize
|
||||
v-model="activeUserGroups" :options="usersOptions"
|
||||
id="user-groups" :label="$t('group_add_member')"
|
||||
no-tags items-name="users"
|
||||
@tag-update="onSpecificUserAdded"
|
||||
/>
|
||||
</card>
|
||||
</view-search>
|
||||
|
@ -114,8 +109,7 @@ import Vue from 'vue'
|
|||
|
||||
import api from '@/api'
|
||||
import { isEmptyValue } from '@/helpers/commons'
|
||||
import ZoneSelectize from '@/components/ZoneSelectize'
|
||||
import BaseSelectize from '@/components/BaseSelectize'
|
||||
import TagsSelectize from '@/components/TagsSelectize'
|
||||
|
||||
// TODO add global search with type (search by: group, user, permission)
|
||||
// TODO add vuex store update on inputs ?
|
||||
|
@ -123,8 +117,7 @@ export default {
|
|||
name: 'GroupList',
|
||||
|
||||
components: {
|
||||
ZoneSelectize,
|
||||
BaseSelectize
|
||||
TagsSelectize
|
||||
},
|
||||
|
||||
data () {
|
||||
|
@ -136,134 +129,117 @@ export default {
|
|||
],
|
||||
search: '',
|
||||
permissions: undefined,
|
||||
normalGroups: undefined,
|
||||
userGroups: undefined
|
||||
permissionsOptions: undefined,
|
||||
primaryGroups: undefined,
|
||||
userGroups: undefined,
|
||||
usersOptions: undefined,
|
||||
activeUserGroups: undefined
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
filteredGroups () {
|
||||
const groups = this.normalGroups
|
||||
const groups = this.primaryGroups
|
||||
if (!groups) return
|
||||
const search = this.search.toLowerCase()
|
||||
const filtered = {}
|
||||
for (const name in groups) {
|
||||
if (name.toLowerCase().includes(search)) {
|
||||
filtered[name] = groups[name]
|
||||
for (const groupName in groups) {
|
||||
if (groupName.toLowerCase().includes(search)) {
|
||||
filtered[groupName] = groups[groupName]
|
||||
}
|
||||
}
|
||||
return isEmptyValue(filtered) ? null : filtered
|
||||
},
|
||||
|
||||
userGroupsNames () {
|
||||
const groups = this.userGroups
|
||||
if (!groups) return
|
||||
return Object.keys(groups).filter(name => {
|
||||
return groups[name].permissions !== null
|
||||
})
|
||||
},
|
||||
|
||||
availableMembers () {
|
||||
const groups = this.userGroups
|
||||
if (!groups) return
|
||||
return Object.keys(groups).filter(name => {
|
||||
return groups[name].permissions === null
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onQueriesResponse (users, allGroups, permissions) {
|
||||
onQueriesResponse (users, allGroups, permsDict) {
|
||||
// Do not use computed properties to get values from the store here to avoid auto
|
||||
// updates while modifying values.
|
||||
const normalGroups = {}
|
||||
const userGroups = {}
|
||||
const permissions = Object.entries(permsDict).map(([id, value]) => ({ id, ...value }))
|
||||
const userNames = users ? Object.keys(users) : []
|
||||
const primaryGroups = {}
|
||||
const userGroups = {}
|
||||
|
||||
for (const groupName in allGroups) {
|
||||
// copy the group to unlink it from the store
|
||||
const group = { ...allGroups[groupName] }
|
||||
group.availablePermissions = Object.keys(permissions).filter(perm => {
|
||||
// Remove 'email', 'xmpp' and protected permissions in visitors's permission choice list
|
||||
if (groupName === 'visitors' && (['mail.main', 'xmpp.main'].includes(perm) || permissions[perm].protected)) {
|
||||
return false
|
||||
}
|
||||
return !group.permissions.includes(perm)
|
||||
group.permissions = group.permissions.map((perm) => {
|
||||
return permsDict[perm].label
|
||||
})
|
||||
|
||||
if (userNames.includes(groupName)) {
|
||||
if (group.permissions.length === 0) {
|
||||
// This forbid the user to appear in the displayed user list
|
||||
group.permissions = null
|
||||
}
|
||||
userGroups[groupName] = group
|
||||
continue
|
||||
}
|
||||
|
||||
if (['visitors', 'all_users'].includes(groupName)) {
|
||||
group.isSpecial = true
|
||||
} else {
|
||||
group.availableMembers = userNames.filter(name => {
|
||||
return !group.members.includes(name)
|
||||
})
|
||||
group.isSpecial = ['visitors', 'all_users'].includes(groupName)
|
||||
|
||||
if (groupName === 'visitors') {
|
||||
// Forbid to add or remove a protected permission on group `visitors`
|
||||
group.disabledItems = permissions.filter(({ id }) => {
|
||||
return ['mail.main', 'xmpp.main'].includes(id) || permsDict[id].protected
|
||||
}).map(({ id }) => permsDict[id].label)
|
||||
}
|
||||
normalGroups[groupName] = group
|
||||
|
||||
primaryGroups[groupName] = group
|
||||
}
|
||||
|
||||
this.permissions = permissions
|
||||
this.normalGroups = normalGroups
|
||||
this.userGroups = isEmptyValue(userGroups) ? null : userGroups
|
||||
},
|
||||
const activeUserGroups = Object.entries(userGroups).filter(([_, group]) => {
|
||||
return group.permissions.length > 0
|
||||
}).map(([name]) => name)
|
||||
|
||||
onPermissionChanged ({ item, index, name, groupType, action }) {
|
||||
// const uri = 'users/permissions/' + item
|
||||
// const data = { [action]: name }
|
||||
const from = action === 'add' ? 'availablePermissions' : 'permissions'
|
||||
const to = action === 'add' ? 'permissions' : 'availablePermissions'
|
||||
api.put(
|
||||
`users/permissions/${item}/${action}/${name}`,
|
||||
{},
|
||||
{ key: 'permissions.' + action, perm: item.replace('.main', ''), name }
|
||||
).then(() => {
|
||||
this[groupType + 'Groups'][name][from].splice(index, 1)
|
||||
this[groupType + 'Groups'][name][to].push(item)
|
||||
Object.assign(this, {
|
||||
permissions,
|
||||
permissionsOptions: permissions.map(perm => perm.label),
|
||||
primaryGroups,
|
||||
userGroups: isEmptyValue(userGroups) ? null : userGroups,
|
||||
usersOptions: userNames,
|
||||
activeUserGroups
|
||||
})
|
||||
},
|
||||
|
||||
onUserChanged ({ item, index, name, action }) {
|
||||
const from = action === 'add' ? 'availableMembers' : 'members'
|
||||
const to = action === 'add' ? 'members' : 'availableMembers'
|
||||
async onPermissionChanged ({ option, groupName, action, applyMethod }) {
|
||||
const permId = this.permissions.find(perm => perm.label === option).id
|
||||
if (action === 'add' && ['sftp.main', 'ssh.main'].includes(permId)) {
|
||||
const confirmed = await this.$askConfirmation(
|
||||
this.$i18n.t('confirm_group_add_access_permission', { name: groupName, perm: option })
|
||||
)
|
||||
if (!confirmed) return
|
||||
}
|
||||
api.put(
|
||||
`users/groups/${name}/${action}/${item}`,
|
||||
// FIXME hacky way to update the store
|
||||
{ uri: `users/permissions/${permId}/${action}/${groupName}`, storeKey: 'permissions', groupName, action, permId },
|
||||
{},
|
||||
{ key: 'groups.' + action, user: item, name }
|
||||
).then(() => {
|
||||
this.normalGroups[name][from].splice(index, 1)
|
||||
this.normalGroups[name][to].push(item)
|
||||
})
|
||||
{ key: 'permissions.' + action, perm: option, name: groupName }
|
||||
).then(() => applyMethod(option))
|
||||
},
|
||||
|
||||
onSpecificUserAdded ({ item }) {
|
||||
this.userGroups[item].permissions = []
|
||||
onUserChanged ({ option, groupName, action, applyMethod }) {
|
||||
api.put(
|
||||
{ uri: `users/groups/${groupName}/${action}/${option}`, storeKey: 'groups', groupName },
|
||||
{},
|
||||
{ key: 'groups.' + action, user: option, name: groupName }
|
||||
).then(() => applyMethod(option))
|
||||
},
|
||||
|
||||
// FIXME Find a way to pass a filter to a component
|
||||
formatPermission (name) {
|
||||
return this.permissions[name].label
|
||||
onSpecificUserAdded ({ option: userName, action, applyMethod }) {
|
||||
if (action === 'add') {
|
||||
this.userGroups[userName].permissions = []
|
||||
applyMethod(userName)
|
||||
}
|
||||
},
|
||||
|
||||
removable (name) {
|
||||
return this.permissions[name].protected === false
|
||||
},
|
||||
|
||||
async deleteGroup (name) {
|
||||
const confirmed = await this.$askConfirmation(this.$i18n.t('confirm_delete', { name }))
|
||||
async deleteGroup (groupName) {
|
||||
const confirmed = await this.$askConfirmation(this.$i18n.t('confirm_delete', { name: groupName }))
|
||||
if (!confirmed) return
|
||||
|
||||
api.delete(
|
||||
{ uri: 'users/groups', param: name, storeKey: 'groups' }, {}, { key: 'groups.delete', name }
|
||||
{ uri: 'users/groups', param: groupName, storeKey: 'groups' },
|
||||
{},
|
||||
{ key: 'groups.delete', name: groupName }
|
||||
).then(() => {
|
||||
Vue.delete(this.normalGroups, name)
|
||||
Vue.delete(this.primaryGroups, groupName)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue