Skip to content

Commit

Permalink
Merge pull request #6311 from inverse-inc/feature/composition-prefere…
Browse files Browse the repository at this point in the history
…nces

UI User Preferences
  • Loading branch information
cgx committed Apr 29, 2021
2 parents cf8e6fb + eb30505 commit e784def
Show file tree
Hide file tree
Showing 11 changed files with 974 additions and 196 deletions.
410 changes: 218 additions & 192 deletions html/pfappserver/root/src/App.vue

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const props = {
}
export const setup = (props, context) => {
const {
rows,
autoFit
Expand Down Expand Up @@ -91,14 +91,14 @@ export const setup = (props, context) => {
invalidFeedback,
validFeedback
} = useInputValidator(metaProps, value)
const inputRows = computed(() => {
if (autoFit.value) {
const r = [...(value.value || '')].filter(c => c === '\n').length + 1
return Math.max(rows.value, r)
}
return rows.value
})
})
return {
// useInput
Expand Down
130 changes: 130 additions & 0 deletions html/pfappserver/root/src/composables/usePreferences.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { ref, watch } from '@vue/composition-api'
import { createDebouncer } from 'promised-debounce'
import store from '@/store' // required for 'system/version'
import apiCall from '@/utils/api'

export const IDENTIFIER_PREFIX = 'pfappserver::' // transparently prefix all identifiers - avoid key collisions

export const api = {
allPreferences: () => {
return apiCall.getQuiet('preferences').then(response => {
return response.data.items
})
},
getPreference: id => {
return apiCall.getQuiet(`preference/${IDENTIFIER_PREFIX}${id}`).then(response => {
return response.data.item
})
},
setPreference: (id, data) => {
if (data) {
let body = {
id: `${IDENTIFIER_PREFIX}${id}`,
value: JSON.stringify({
data,
meta: {
created_at: (new Date()).getTime(),
updated_at: (new Date()).getTime(),
version: store.getters['system/version']
}
})
}
return apiCall.getQuiet(['preference', `${IDENTIFIER_PREFIX}${id}`]).then(response => { // exists
const { data: { item: { value = null } = {} } = {} } = response
if (value) {
const { meta: { created_at = null } = {} } = JSON.parse(value)
if (created_at) { // retain `created_at`
body = {
id: `${IDENTIFIER_PREFIX}${id}`,
value: JSON.stringify({
data,
meta: {
created_at: created_at,
updated_at: (new Date()).getTime(),
version: store.getters['system/version']
}
})
}
}
}
return apiCall.putQuiet(['preference', `${IDENTIFIER_PREFIX}${id}`], body).then(response => {
return response.data
})
}).catch(() => { // not exists
return apiCall.putQuiet(['preference', `${IDENTIFIER_PREFIX}${id}`], body).then(response => {
return response.data
})
})
}
else {
return apiCall.deleteQuiet(['preference', `${IDENTIFIER_PREFIX}${id}`]).then(response => {
return response
})
}
},
removePreference: id => {
return apiCall.deleteQuiet(['preference', `${IDENTIFIER_PREFIX}${id}`]).then(response => {
return response
})
}
}

export const usePreferences = () => {

const preferences = ref(undefined)

api.allPreferences()
.then(response => {
preferences.value = response.map(preference => {
const { id, value } = preference || {}
const _id = id.substr(IDENTIFIER_PREFIX.length) // strip IDENTIFIER_PREFIX
const get = () => api.getPreference(_id)
const set = data => api.setPreference(_id, data)
const remove = () => api.removePreference(_id)
return {
id: _id,
value: JSON.parse(value), // parse
get, set, remove // methods
}
})
})
.catch(() => {
preferences.value = []
})

return preferences
}

export const usePreference = (id, defaultValue) => {

const preference = ref(defaultValue)

api.getPreference(id)
.then(response => {
const { value } = response || {}
const { data } = JSON.parse(value || '{}')
preference.value = data
})
.catch(() => {
preference.value = defaultValue
})
.finally(() => {
// watch after initial mutation
let debouncer
watch(preference, () => {
if (!debouncer)
debouncer = createDebouncer()
debouncer({
handler: () => {
if (!preference.value)
api.removePreference(id)
else
api.setPreference(id, preference.value)
},
time: 1000
})
}, { deep: true })
})

return preference
}
1 change: 1 addition & 0 deletions html/pfappserver/root/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ import 'vue-awesome/icons/unlink'
import 'vue-awesome/icons/upload'
import 'vue-awesome/icons/user'
import 'vue-awesome/icons/user-circle'
import 'vue-awesome/icons/user-lock'
import 'vue-awesome/icons/user-plus'
import 'vue-awesome/icons/user-secret'
import 'vue-awesome/icons/wifi'
Expand Down
2 changes: 2 additions & 0 deletions html/pfappserver/root/src/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import NodesRoute from '@/views/Nodes/_router'
import UsersRoute from '@/views/Users/_router'
import ConfigurationRoute from '@/views/Configuration/_router'
import ConfiguratorRoute from '@/views/Configurator/_router'
import PreferencesRoute from '@/views/Preferences/_router'
import ResetRoute from '@/views/Reset/_router'

Vue.use(Router)
Expand All @@ -30,6 +31,7 @@ let router = new Router({
UsersRoute,
ConfigurationRoute,
ConfiguratorRoute,
PreferencesRoute,
ResetRoute,
DefaultRoute
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
:form="form"
:schema="schema"
:isLoading="isLoading"
:isReadonly="!isNew && !isClone"
>
<b-tabs>
<base-form-tab :title="$i18n.t('General')" active>
<form-group-identifier v-if="!isNew && !isClone"
namespace="ID"
:column-label="$i18n.t('Identifier')"
:disabled="!isNew && !isClone"
/>
<form-group-ca-id namespace="ca_id"
:column-label="$i18n.t('Certificate Authority')"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<template>
<b-tab :title="$t('Permissions')">
<b-card no-body class="mb-3">
<b-card-header>
<h4 class="mb-0">{{ $t('Roles') }}</h4>
</b-card-header>
<b-card-body class="px-3 pt-3 pb-0">
<b-row>
<b-col cols="3" v-for="(acls, role) in aclContextAssociated" :key="role">
<b-card class="mb-3" :title="roleToString(role)">
<b-badge v-for="acl in acls" :key="`${role}_${acl}`"
:title="`${role}_${acl}`" v-b-tooltip.hover.top.d300
variant="secondary" class="mr-1">{{ acl }}</b-badge>
</b-card>
</b-col>
</b-row>
</b-card-body>
</b-card>
</b-tab>
</template>
<script>
const components = {}
import { computed } from '@vue/composition-api'
const setup = (props, context) => {
const { root: { $store } = {} } = context
const aclContext = computed(() => $store.getters['session/aclContext'])
const aclContextAssociated = computed(() => aclContext.value
.sort((a, b) => a.localeCompare(b))
.reduce((associated, role) => {
const [ suffix, ...prefixes ] = role.split('_').reverse()
if (['CREATE', 'DELETE', 'READ', 'UPDATE', 'WRITE'].includes(suffix)) { // split by last '_'
const prefix = prefixes.reverse().join('_')
if (!(prefix in associated))
associated[prefix] = []
associated[prefix].push(suffix)
}
else { // split by first '_'
const [ prefix, ...suffixes ] = role.split('_')
const suffix = suffixes.join('_')
if (!(prefix in associated))
associated[prefix] = []
associated[prefix].push(suffix)
}
return associated
}, {})
)
const roleToString = (role) => {
return role
.split('_')
.map(role => `${role.charAt(0)}${role.slice(1).toLowerCase()}`)
.join(' ')
}
return {
aclContextAssociated,
roleToString
}
}
// @vue/component
export default {
name: 'tab-permissions',
inheritAttrs: false,
components,
setup
}
</script>
Loading

0 comments on commit e784def

Please sign in to comment.