mirror of
https://github.com/YunoHost/yunohost-admin.git
synced 2024-09-03 20:06:15 +02:00
Update views with ViewTopBar and SearchVue components
This commit is contained in:
parent
2b80a0b1e0
commit
04ad65761e
11 changed files with 354 additions and 402 deletions
|
@ -103,6 +103,7 @@
|
|||
"created_at": "Created at",
|
||||
"custom_app_install": "Install custom app",
|
||||
"custom_app_url_only_github": "Currently only from GitHub",
|
||||
"day_validity": " Expired | 1 day | {count} days",
|
||||
"dead": "Inactive",
|
||||
"delete": "Delete",
|
||||
"description": "Description",
|
||||
|
@ -217,6 +218,17 @@
|
|||
"ipv4": "IPv4",
|
||||
"ipv6": "IPv6",
|
||||
"issues": "{count} issues",
|
||||
"items": {
|
||||
"apps": "no apps | app | {c} apps",
|
||||
"backups": "no backups | backup | {c} backups",
|
||||
"domains": "no domains | domain | {c} domains",
|
||||
"groups": "no groups | group | {c} groups",
|
||||
"installed_apps": "no installed apps | installed app | {c} installed apps",
|
||||
"logs": "no logs | log | {c} logs",
|
||||
"services": "no services | service | {c} services",
|
||||
"users": "no users | user | {c} users"
|
||||
},
|
||||
"items_verbose_count": "There is {items}.",
|
||||
"label": "Label",
|
||||
"label_for_manifestname": "Label for {name}",
|
||||
"last_ran": "Last time ran:",
|
||||
|
@ -264,9 +276,6 @@
|
|||
"groupname": "My group name",
|
||||
"domain": "my-domain.com"
|
||||
},
|
||||
"pluralized": {
|
||||
"day_validity": " Expired | 1 day | {count} days"
|
||||
},
|
||||
"logs": "Logs",
|
||||
"logs_suboperations": "Sub-operations",
|
||||
"logs_operation": "Operations made on system with YunoHost",
|
||||
|
@ -307,15 +316,8 @@
|
|||
"running": "Running",
|
||||
"save": "Save",
|
||||
"search": {
|
||||
"domain": "Search for domains...",
|
||||
"group": "Search for groups...",
|
||||
"installed_app": "Search for installed apps...",
|
||||
"service": "Search for services",
|
||||
"user": "Search for users...",
|
||||
"logs": "Search in logs...",
|
||||
"not_found": {
|
||||
"installed_app": "There is no apps matching your search query."
|
||||
}
|
||||
"for": "Search for {items}...",
|
||||
"not_found": "There is {items} matching your criteria."
|
||||
},
|
||||
"search_for_apps": "Search for apps...",
|
||||
"select_all": "Select all",
|
||||
|
|
|
@ -148,7 +148,10 @@ export default {
|
|||
},
|
||||
|
||||
getters: {
|
||||
users: state => state.users,
|
||||
users: state => {
|
||||
if (state.users) return Object.values(state.users)
|
||||
return state.users
|
||||
},
|
||||
|
||||
userNames: state => {
|
||||
if (state.users) return Object.keys(state.users)
|
||||
|
|
|
@ -1,55 +1,43 @@
|
|||
<template>
|
||||
<div class="app-list">
|
||||
<div class="actions">
|
||||
<b-input-group>
|
||||
<b-input-group-prepend is-text>
|
||||
<icon iname="search" />
|
||||
</b-input-group-prepend>
|
||||
<b-form-input
|
||||
:disabled="!apps"
|
||||
id="search-app" v-model="search"
|
||||
:placeholder="$t('search.installed_app')"
|
||||
/>
|
||||
</b-input-group>
|
||||
<div class="buttons">
|
||||
<b-button variant="success" :to="{ name: 'app-catalog' }">
|
||||
<icon iname="plus" /> {{ $t('install') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="apps !== undefined">
|
||||
<b-alert v-if="apps === null" variant="warning" show>
|
||||
<icon iname="exclamation-triangle" /> {{ $t('no_installed_apps') }}
|
||||
</b-alert>
|
||||
|
||||
<b-list-group v-else-if="filteredApps && filteredApps.length">
|
||||
<b-list-group-item
|
||||
v-for="{ id, name, description, label } in filteredApps" :key="id"
|
||||
:to="{ name: 'app-info', params: { id }}"
|
||||
class="d-flex justify-content-between align-items-center pr-0"
|
||||
>
|
||||
<div>
|
||||
<h5 class="font-weight-bold">{{ label }}
|
||||
<small v-if="name" class="text-secondary">{{ name }}</small>
|
||||
</h5>
|
||||
<p class="m-0">
|
||||
{{ description }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<icon iname="chevron-right" class="lg fs-sm ml-auto" />
|
||||
</b-list-group-item>
|
||||
</b-list-group>
|
||||
<b-alert v-else variant="warning" show>
|
||||
<icon iname="exclamation-triangle" /> {{ $t('search.not_found.installed_app') }}
|
||||
</b-alert>
|
||||
<search-view
|
||||
id="app-list"
|
||||
:search.sync="search"
|
||||
:items="apps"
|
||||
:filtered-items="filteredApps"
|
||||
items-name="installed_apps"
|
||||
>
|
||||
<template #top-bar-buttons>
|
||||
<b-button variant="success" :to="{ name: 'app-catalog' }">
|
||||
<icon iname="plus" />
|
||||
{{ $t('install') }}
|
||||
</b-button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<b-list-group>
|
||||
<b-list-group-item
|
||||
v-for="{ id, name, description, label } in filteredApps" :key="id"
|
||||
:to="{ name: 'app-info', params: { id }}"
|
||||
class="d-flex justify-content-between align-items-center pr-0"
|
||||
>
|
||||
<div>
|
||||
<h5 class="font-weight-bold">
|
||||
{{ label }}
|
||||
<small v-if="name" class="text-secondary">{{ name }}</small>
|
||||
</h5>
|
||||
<p class="m-0">
|
||||
{{ description }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<icon iname="chevron-right" class="lg fs-sm ml-auto" />
|
||||
</b-list-group-item>
|
||||
</b-list-group>
|
||||
</search-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from '@/api'
|
||||
import SearchView from '@/components/SearchView'
|
||||
|
||||
export default {
|
||||
name: 'AppList',
|
||||
|
@ -65,9 +53,10 @@ export default {
|
|||
filteredApps () {
|
||||
if (!this.apps) return
|
||||
const search = this.search.toLowerCase()
|
||||
const match = (item) => item.toLowerCase().includes(search)
|
||||
const match = (item) => item && item.toLowerCase().includes(search)
|
||||
// Check if any value in apps (label, id, name, description) match the search query.
|
||||
return this.apps.filter(app => Object.values(app).some(match))
|
||||
const filtered = this.apps.filter(app => Object.values(app).some(match))
|
||||
return filtered.length > 0 ? filtered : null
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -107,10 +96,8 @@ export default {
|
|||
|
||||
created () {
|
||||
this.fetchData()
|
||||
}
|
||||
},
|
||||
|
||||
components: { SearchView }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
<template>
|
||||
<div class="backup-list">
|
||||
<div class="actions">
|
||||
<div class="buttons ml-auto">
|
||||
<b-button variant="success" :to="{ name: 'backup-create' }">
|
||||
<icon iname="plus" /> {{ $t('backup_new') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
<view-top-bar :button="{ text: $t('backup_new'), icon: 'plus', to: { name: 'backup-create' } }" />
|
||||
|
||||
<b-alert v-if="!archives" variant="warning" show>
|
||||
<icon iname="exclamation-triangle" /> {{ $t('backups_no') }}
|
||||
<icon iname="exclamation-triangle" />
|
||||
{{ $t('items_verbose_count', { items: $tc('items.backups', 0) }) }}
|
||||
</b-alert>
|
||||
|
||||
<b-list-group v-else>
|
||||
<b-list-group-item
|
||||
v-for="{ name, created_at, path, size } in archives" :key="name"
|
||||
|
@ -23,7 +19,9 @@
|
|||
{{ created_at | distanceToNow }}
|
||||
<small>{{ name }} ({{ size | humanSize }})</small>
|
||||
</h5>
|
||||
<p class="mb-0">{{ path }}</p>
|
||||
<p class="mb-0">
|
||||
{{ path }}
|
||||
</p>
|
||||
</div>
|
||||
<icon iname="chevron-right" class="lg fs-sm ml-auto" />
|
||||
</b-list-group-item>
|
||||
|
@ -52,19 +50,14 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
filters: {
|
||||
distanceToNow,
|
||||
readableDate,
|
||||
humanSize
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetchData () {
|
||||
api.get('backup/archives?with_info').then(({ archives }) => {
|
||||
api.get('backup/archives?with_info').then(data => {
|
||||
// FIXME use archives = null if no archives
|
||||
this.archives = Object.entries(archives).map(([name, data]) => {
|
||||
data.name = name
|
||||
return data
|
||||
const archives = Object.entries(data.archives)
|
||||
this.archives = archives.length === 0 ? null : archives.map(([name, infos]) => {
|
||||
infos.name = name
|
||||
return infos
|
||||
}).reverse()
|
||||
})
|
||||
}
|
||||
|
@ -72,6 +65,12 @@ export default {
|
|||
|
||||
created () {
|
||||
this.fetchData()
|
||||
},
|
||||
|
||||
filters: {
|
||||
distanceToNow,
|
||||
readableDate,
|
||||
humanSize
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<template>
|
||||
<div class="diagnosis">
|
||||
<div class="actions">
|
||||
<div class="buttons ml-auto">
|
||||
<b-button @click="shareLogs">
|
||||
<view-top-bar>
|
||||
<template #group-right>
|
||||
<b-button @click="shareLogs" variant="success">
|
||||
<icon iname="cloud-upload" /> {{ $t('logs_share_with_yunopaste') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</view-top-bar>
|
||||
|
||||
<b-alert variant="info" show>
|
||||
{{ $t(reports ? 'diagnosis_explanation' : 'diagnosis_first_run') }}
|
||||
|
@ -119,10 +119,6 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
filters: {
|
||||
distanceToNow
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetchData () {
|
||||
api.get('diagnosis/show?full').then((data) => {
|
||||
|
@ -196,6 +192,10 @@ export default {
|
|||
|
||||
created () {
|
||||
api.post('diagnosis/run?except_if_never_ran_yet').then(this.fetchData)
|
||||
},
|
||||
|
||||
filters: {
|
||||
distanceToNow
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<dd>{{ cert.type }} ({{ name }})</dd>
|
||||
<hr>
|
||||
<dt v-t="'validity'" />
|
||||
<dd>{{ $tc('pluralized.day_validity', cert.validity) }}</dd>
|
||||
<dd>{{ $tc('day_validity', cert.validity) }}</dd>
|
||||
</dl>
|
||||
</b-card>
|
||||
|
||||
|
@ -82,6 +82,7 @@ import api from '@/api'
|
|||
|
||||
export default {
|
||||
name: 'DomainCert',
|
||||
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
|
@ -176,6 +177,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
<template>
|
||||
<div class="domain-list">
|
||||
<div class="actions">
|
||||
<b-input-group>
|
||||
<b-input-group-prepend is-text>
|
||||
<icon iname="search" />
|
||||
</b-input-group-prepend>
|
||||
<b-form-input id="search-domain" v-model="search" :placeholder="$t('search.domain')" />
|
||||
</b-input-group>
|
||||
<div class="buttons">
|
||||
<b-button variant="success" :to="{name: 'domain-add'}">
|
||||
<icon iname="plus" /> {{ $t('domain_add') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
<search-view
|
||||
id="domain-list"
|
||||
:search.sync="search"
|
||||
:items="domains"
|
||||
:filtered-items="filteredDomains"
|
||||
items-name="domains"
|
||||
>
|
||||
<template #top-bar-buttons>
|
||||
<b-button variant="success" :to="{ name: 'domain-add' }">
|
||||
<icon iname="plus" />
|
||||
{{ $t('domain_add') }}
|
||||
</b-button>
|
||||
</template>
|
||||
|
||||
<b-list-group v-if="filteredDomains">
|
||||
<b-list-group>
|
||||
<b-list-group-item
|
||||
v-for="domain in filteredDomains" :key="domain"
|
||||
:to="{ name: 'domain-info', params: { name: domain }}"
|
||||
|
@ -28,39 +27,42 @@
|
|||
<icon iname="star" :title="$t('words.default')" />
|
||||
</small>
|
||||
</h5>
|
||||
<p class="font-italic">https://{{ domain }}</p>
|
||||
<p class="font-italic m-0">
|
||||
https://{{ domain }}
|
||||
</p>
|
||||
</div>
|
||||
<icon iname="chevron-right" class="lg fs-sm ml-auto" />
|
||||
</b-list-group-item>
|
||||
</b-list-group>
|
||||
</div>
|
||||
</search-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
import SearchView from '@/components/SearchView'
|
||||
|
||||
export default {
|
||||
name: 'DomainList',
|
||||
|
||||
data: () => ({
|
||||
search: ''
|
||||
}),
|
||||
|
||||
computed: {
|
||||
filteredDomains () {
|
||||
const domains = this.$store.state.data.domains
|
||||
const mainDomain = this.mainDomain
|
||||
if (!domains || !mainDomain) return
|
||||
const search = this.search.toLowerCase()
|
||||
return domains
|
||||
.filter(name => name.toLowerCase().includes(search))
|
||||
.sort(prevDomain => prevDomain === mainDomain ? -1 : 1)
|
||||
},
|
||||
|
||||
mainDomain () {
|
||||
return this.$store.state.data.main_domain
|
||||
data () {
|
||||
return {
|
||||
search: ''
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
computed: {
|
||||
...mapGetters(['domains', 'mainDomain']),
|
||||
|
||||
filteredDomains () {
|
||||
if (!this.domains || !this.mainDomain) return
|
||||
const search = this.search.toLowerCase()
|
||||
const mainDomain = this.mainDomain
|
||||
const domains = this.domains
|
||||
.filter(name => name.toLowerCase().includes(search))
|
||||
.sort(prevDomain => prevDomain === mainDomain ? -1 : 1)
|
||||
return domains.length > 0 ? domains : null
|
||||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
|
@ -68,28 +70,8 @@ export default {
|
|||
{ uri: 'domains/main', storeKey: 'main_domain' },
|
||||
{ uri: 'domains' }
|
||||
])
|
||||
}
|
||||
},
|
||||
|
||||
components: { SearchView }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
p {
|
||||
margin: 0
|
||||
}
|
||||
|
||||
.skeleton {
|
||||
@each $i, $opacity in 1 .75, 2 .5, 3 .25 {
|
||||
.list-group-item:nth-child(#{$i}) { opacity: $opacity; }
|
||||
}
|
||||
|
||||
h5, p {
|
||||
background-color: $skeleton-color;
|
||||
height: 1.5rem;
|
||||
width: 10rem;
|
||||
}
|
||||
|
||||
small {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,170 +1,171 @@
|
|||
<template lang="html">
|
||||
<div class="group-list">
|
||||
<div class="actions">
|
||||
<b-input-group>
|
||||
<b-input-group-prepend is-text>
|
||||
<icon iname="search" />
|
||||
</b-input-group-prepend>
|
||||
<b-form-input id="search-group" v-model="search" :placeholder="$t('search.group')" />
|
||||
</b-input-group>
|
||||
<div class="buttons">
|
||||
<b-button variant="success" :to="{name: 'group-create'}">
|
||||
<icon iname="plus" /> {{ $t('group_new') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PRIMARY GROUPS CARDS -->
|
||||
<template v-if="normalGroups">
|
||||
<b-card
|
||||
v-for="(group, name, index) in filteredGroups" :key="name"
|
||||
no-body
|
||||
>
|
||||
<b-card-header class="d-flex align-items-center">
|
||||
<h2>
|
||||
<icon iname="group" /> {{ group.isSpecial ? $t('group_' + name) : `${$t('group')} "${name}"` }}
|
||||
</h2>
|
||||
|
||||
<div class="ml-auto">
|
||||
<b-button v-b-toggle="'collapse-' + index" size="sm" variant="outline-secondary">
|
||||
<icon iname="chevron-right" class="rotate" /><span class="sr-only">{{ $t('words.collapse') }}</span>
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
v-if="!group.isSpecial" v-b-modal.delete-modal
|
||||
variant="danger" class="ml-2" size="sm"
|
||||
@click="groupToDelete = name"
|
||||
>
|
||||
<icon :title="$t('delete')" iname="trash-o" /> <span class="sr-only">{{ $t('delete') }}</span>
|
||||
</b-button>
|
||||
</div>
|
||||
</b-card-header>
|
||||
|
||||
<b-collapse :id="'collapse-' + index" visible>
|
||||
<b-card-body>
|
||||
<b-row>
|
||||
<b-col md="3" lg="2">
|
||||
<strong>{{ $t('users') }}</strong>
|
||||
</b-col>
|
||||
|
||||
<b-col>
|
||||
<template v-if="group.isSpecial">
|
||||
<p><icon iname="info-circle" /> {{ $t('group_explain_' + name) }}</p>
|
||||
<p v-if="name === '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 })"
|
||||
/>
|
||||
</template>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<hr>
|
||||
<b-row>
|
||||
<b-col md="3" lg="2">
|
||||
<strong>{{ $t('permissions') }}</strong>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<zone-selectize
|
||||
item-icon="key-modern" item-variant="dark"
|
||||
:choices="group.availablePermissions"
|
||||
:selected="group.permissions"
|
||||
:label="$t('group_add_permission')"
|
||||
:format="formatPermission"
|
||||
:removable="name === 'visitors' ? removable : null"
|
||||
@change="onPermissionChanged({ ...$event, name, groupType: 'normal' })"
|
||||
/>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-card-body>
|
||||
</b-collapse>
|
||||
</b-card>
|
||||
<template>
|
||||
<search-view
|
||||
id="group-list"
|
||||
:search.sync="search"
|
||||
:items="normalGroups"
|
||||
:filtered-items="filteredGroups"
|
||||
items-name="groups"
|
||||
>
|
||||
<template #top-bar-buttons>
|
||||
<b-button variant="success" :to="{ name: 'group-create' }">
|
||||
<icon iname="plus" />
|
||||
{{ $t('group_new') }}
|
||||
</b-button>
|
||||
</template>
|
||||
|
||||
<!-- GROUP SPECIFIC CARD -->
|
||||
<b-card no-body v-if="userGroups">
|
||||
<!-- PRIMARY GROUPS CARDS -->
|
||||
<b-card
|
||||
v-for="(group, name, index) in filteredGroups" :key="name"
|
||||
no-body
|
||||
>
|
||||
<b-card-header class="d-flex align-items-center">
|
||||
<h2>
|
||||
<icon iname="group" /> {{ $t('group_specific_permissions') }}
|
||||
<icon iname="group" /> {{ group.isSpecial ? $t('group_' + name) : `${$t('group')} "${name}"` }}
|
||||
</h2>
|
||||
|
||||
<div class="ml-auto">
|
||||
<b-button v-b-toggle.collapse-specific size="sm" variant="outline-secondary">
|
||||
<b-button v-b-toggle="'collapse-' + index" size="sm" variant="outline-secondary">
|
||||
<icon iname="chevron-right" class="rotate" /><span class="sr-only">{{ $t('words.collapse') }}</span>
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
v-if="!group.isSpecial" v-b-modal.delete-modal
|
||||
variant="danger" class="ml-2" size="sm"
|
||||
@click="groupToDelete = name"
|
||||
>
|
||||
<icon :title="$t('delete')" iname="trash-o" /> <span class="sr-only">{{ $t('delete') }}</span>
|
||||
</b-button>
|
||||
</div>
|
||||
</b-card-header>
|
||||
|
||||
<b-collapse id="collapse-specific" visible>
|
||||
<b-collapse :id="'collapse-' + index" visible>
|
||||
<b-card-body>
|
||||
<div v-for="name in userGroupsNames" :key="name">
|
||||
<b-row>
|
||||
<b-col md="3" lg="2">
|
||||
<icon iname="user" /> <strong>{{ name }}</strong>
|
||||
</b-col>
|
||||
<b-row>
|
||||
<b-col md="3" lg="2">
|
||||
<strong>{{ $t('users') }}</strong>
|
||||
</b-col>
|
||||
|
||||
<b-col>
|
||||
<b-col>
|
||||
<template v-if="group.isSpecial">
|
||||
<p><icon iname="info-circle" /> {{ $t('group_explain_' + name) }}</p>
|
||||
<p v-if="name === 'visitors'">
|
||||
<em>{{ $t('group_explain_visitors_needed_for_external_client') }}</em>
|
||||
</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<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' })"
|
||||
:choices="group.availableMembers" :selected="group.members"
|
||||
item-icon="user"
|
||||
:label="$t('group_add_member')"
|
||||
@change="onUserChanged({ ...$event, name })"
|
||||
/>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<base-selectize
|
||||
v-if="availableMembers.length"
|
||||
:label="$t('group_add_member')"
|
||||
:choices="availableMembers"
|
||||
:selected="userGroupsNames"
|
||||
@selected="onSpecificUserAdded"
|
||||
/>
|
||||
</template>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<hr>
|
||||
<b-row>
|
||||
<b-col md="3" lg="2">
|
||||
<strong>{{ $t('permissions') }}</strong>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<zone-selectize
|
||||
item-icon="key-modern" item-variant="dark"
|
||||
:choices="group.availablePermissions"
|
||||
:selected="group.permissions"
|
||||
:label="$t('group_add_permission')"
|
||||
:format="formatPermission"
|
||||
:removable="name === 'visitors' ? removable : null"
|
||||
@change="onPermissionChanged({ ...$event, name, groupType: 'normal' })"
|
||||
/>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-card-body>
|
||||
</b-collapse>
|
||||
</b-card>
|
||||
|
||||
<!-- DELETE GROUP MODAL -->
|
||||
<b-modal
|
||||
v-if="groupToDelete" id="delete-modal" centered
|
||||
body-bg-variant="danger" body-text-variant="light"
|
||||
@ok="deleteGroup" hide-header
|
||||
>
|
||||
{{ $t('confirm_delete', {name: groupToDelete }) }}
|
||||
</b-modal>
|
||||
</div>
|
||||
<!-- GROUP SPECIFIC CARD -->
|
||||
<template #extra>
|
||||
<b-card no-body v-if="userGroups">
|
||||
<b-card-header class="d-flex align-items-center">
|
||||
<h2>
|
||||
<icon iname="group" /> {{ $t('group_specific_permissions') }}
|
||||
</h2>
|
||||
|
||||
<div class="ml-auto">
|
||||
<b-button v-b-toggle.collapse-specific size="sm" variant="outline-secondary">
|
||||
<icon iname="chevron-right" class="rotate" /><span class="sr-only">{{ $t('words.collapse') }}</span>
|
||||
</b-button>
|
||||
</div>
|
||||
</b-card-header>
|
||||
|
||||
<b-collapse id="collapse-specific" visible>
|
||||
<b-card-body>
|
||||
<div v-for="name in userGroupsNames" :key="name">
|
||||
<b-row>
|
||||
<b-col md="3" lg="2">
|
||||
<icon iname="user" /> <strong>{{ name }}</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' })"
|
||||
/>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<base-selectize
|
||||
v-if="availableMembers.length"
|
||||
:label="$t('group_add_member')"
|
||||
:choices="availableMembers"
|
||||
:selected="userGroupsNames"
|
||||
@selected="onSpecificUserAdded"
|
||||
/>
|
||||
</b-card-body>
|
||||
</b-collapse>
|
||||
</b-card>
|
||||
|
||||
<!-- DELETE GROUP MODAL -->
|
||||
<b-modal
|
||||
v-if="groupToDelete" id="delete-modal" centered
|
||||
body-bg-variant="danger" body-text-variant="light"
|
||||
@ok="deleteGroup" hide-header
|
||||
>
|
||||
{{ $t('confirm_delete', {name: groupToDelete }) }}
|
||||
</b-modal>
|
||||
</template>
|
||||
</search-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
|
||||
import api from '@/api'
|
||||
|
||||
import { isEmptyValue } from '@/helpers/commons'
|
||||
import SearchView from '@/components/SearchView'
|
||||
import ZoneSelectize from '@/components/ZoneSelectize'
|
||||
import BaseSelectize from '@/components/BaseSelectize'
|
||||
|
||||
|
||||
// TODO add global search with type (search by: group, user, permission)
|
||||
// TODO add vuex store update on inputs ?
|
||||
export default {
|
||||
name: 'GroupList',
|
||||
|
||||
data: () => ({
|
||||
search: '',
|
||||
permissions: undefined,
|
||||
normalGroups: undefined,
|
||||
userGroups: undefined,
|
||||
groupToDelete: undefined
|
||||
}),
|
||||
data () {
|
||||
return {
|
||||
search: '',
|
||||
permissions: undefined,
|
||||
normalGroups: undefined,
|
||||
userGroups: undefined,
|
||||
groupToDelete: undefined
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
filteredGroups () {
|
||||
|
@ -177,7 +178,7 @@ export default {
|
|||
filtered[name] = groups[name]
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
return isEmptyValue(filtered) ? null : filtered
|
||||
},
|
||||
|
||||
userGroupsNames () {
|
||||
|
@ -254,7 +255,7 @@ export default {
|
|||
// updates while modifying values.
|
||||
const normalGroups = {}
|
||||
const userGroups = {}
|
||||
const userNames = Object.keys(users)
|
||||
const userNames = users ? Object.keys(users) : []
|
||||
|
||||
for (const groupName in allGroups) {
|
||||
// copy the group to unlink it from the store
|
||||
|
@ -293,6 +294,7 @@ export default {
|
|||
},
|
||||
|
||||
components: {
|
||||
SearchView,
|
||||
ZoneSelectize,
|
||||
BaseSelectize
|
||||
}
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
<template>
|
||||
<div class="service-list">
|
||||
<div class="actions">
|
||||
<b-input-group>
|
||||
<b-input-group-prepend is-text>
|
||||
<icon iname="search" />
|
||||
</b-input-group-prepend>
|
||||
<b-form-input id="search-service" v-model="search" :placeholder="$t('search.service')" />
|
||||
</b-input-group>
|
||||
</div>
|
||||
|
||||
<search-view
|
||||
id="service-list"
|
||||
:search.sync="search"
|
||||
:items="services"
|
||||
:filtered-items="filteredServices"
|
||||
items-name="services"
|
||||
>
|
||||
<b-list-group v-if="filteredServices">
|
||||
<b-list-group-item
|
||||
v-for="{ name, description, status, last_state_change } in filteredServices"
|
||||
|
@ -17,7 +14,10 @@
|
|||
class="d-flex justify-content-between align-items-center pr-0"
|
||||
>
|
||||
<div class="w-100">
|
||||
<h5 class="font-weight-bold">{{ name }} <small class="text-secondary">{{ description }}</small></h5>
|
||||
<h5 class="font-weight-bold">
|
||||
{{ name }}
|
||||
<small class="text-secondary">{{ description }}</small>
|
||||
</h5>
|
||||
<p class="mb-0">
|
||||
<span :class="status === 'running' ? 'text-success' : 'text-danger'">
|
||||
<icon :iname="status === 'running' ? 'check-circle' : 'times'" />
|
||||
|
@ -29,17 +29,18 @@
|
|||
<icon iname="chevron-right" class="lg fs-sm ml-auto" />
|
||||
</b-list-group-item>
|
||||
</b-list-group>
|
||||
</div>
|
||||
</search-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from '@/api'
|
||||
import { distanceToNow } from '@/helpers/filters/date'
|
||||
import SearchView from '@/components/SearchView'
|
||||
|
||||
export default {
|
||||
name: 'ServiceList',
|
||||
|
||||
data: function () {
|
||||
data () {
|
||||
return {
|
||||
search: '',
|
||||
services: undefined
|
||||
|
@ -50,16 +51,13 @@ export default {
|
|||
filteredServices () {
|
||||
if (!this.services) return
|
||||
const search = this.search.toLowerCase()
|
||||
return this.services.filter(({ name }) => {
|
||||
const services = this.services.filter(({ name }) => {
|
||||
return name.toLowerCase().includes(search)
|
||||
})
|
||||
return services.length > 0 ? services : null
|
||||
}
|
||||
},
|
||||
|
||||
filters: {
|
||||
distanceToNow
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetchData () {
|
||||
// simply use the api helper since we will not store the request's result.
|
||||
|
@ -77,6 +75,12 @@ export default {
|
|||
|
||||
created () {
|
||||
this.fetchData()
|
||||
},
|
||||
|
||||
components: { SearchView },
|
||||
|
||||
filters: {
|
||||
distanceToNow
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
<template>
|
||||
<div class="tool-logs">
|
||||
<div class="actions">
|
||||
<b-input-group>
|
||||
<b-input-group-prepend is-text>
|
||||
<icon iname="search" />
|
||||
</b-input-group-prepend>
|
||||
<b-form-input id="search-logs" v-model="search" :placeholder="$t('search.logs')" />
|
||||
</b-input-group>
|
||||
</div>
|
||||
|
||||
<b-card no-body v-if="operations">
|
||||
<search-view
|
||||
id="tool-logs"
|
||||
:search.sync="search"
|
||||
:items="operations"
|
||||
:filtered-items="filteredOperations"
|
||||
items-name="logs"
|
||||
>
|
||||
<b-card no-body>
|
||||
<template v-slot:header>
|
||||
<h2><icon iname="wrench" /> {{ $t('logs_operation') }}</h2>
|
||||
</template>
|
||||
|
@ -25,18 +22,18 @@
|
|||
</b-list-group-item>
|
||||
</b-list-group>
|
||||
</b-card>
|
||||
</div>
|
||||
</search-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from '@/api'
|
||||
import { distanceToNow, readableDate } from '@/helpers/filters/date'
|
||||
|
||||
import SearchView from '@/components/SearchView'
|
||||
|
||||
export default {
|
||||
name: 'ServiceList',
|
||||
|
||||
data: function () {
|
||||
data () {
|
||||
return {
|
||||
search: '',
|
||||
operations: undefined
|
||||
|
@ -47,9 +44,10 @@ export default {
|
|||
filteredOperations () {
|
||||
if (!this.operations) return
|
||||
const search = this.search.toLowerCase()
|
||||
return this.operations.filter(({ description }) => {
|
||||
const operations = this.operations.filter(({ description }) => {
|
||||
return description.toLowerCase().includes(search)
|
||||
})
|
||||
return operations.length > 0 ? operations : null
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -80,6 +78,8 @@ export default {
|
|||
|
||||
created () {
|
||||
this.fetchData()
|
||||
}
|
||||
},
|
||||
|
||||
components: { SearchView }
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,100 +1,75 @@
|
|||
<template>
|
||||
<div class="user-list">
|
||||
<div class="actions">
|
||||
<b-input-group>
|
||||
<b-input-group-prepend is-text>
|
||||
<icon iname="search" />
|
||||
</b-input-group-prepend>
|
||||
<b-form-input id="search-user" v-model="search" :placeholder="$t('search.user')" />
|
||||
</b-input-group>
|
||||
<div class="buttons">
|
||||
<b-button variant="info" :to="{ name: 'group-list'}">
|
||||
<icon iname="key-modern" />
|
||||
{{ $t('groups_and_permissions_manage') }}
|
||||
</b-button>
|
||||
<search-view
|
||||
id="user-list"
|
||||
:search.sync="search"
|
||||
:items="users"
|
||||
:filtered-items="filteredUsers"
|
||||
items-name="users"
|
||||
>
|
||||
<template #top-bar-buttons>
|
||||
<b-button variant="info" :to="{ name: 'group-list' }">
|
||||
<icon iname="key-modern" />
|
||||
{{ $t('groups_and_permissions_manage') }}
|
||||
</b-button>
|
||||
|
||||
<b-button variant="success" :to="{name: 'user-create'}">
|
||||
<icon iname="plus" />
|
||||
{{ $t('users_new') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="users === null">
|
||||
<b-alert variant="warning" show>
|
||||
<icon iname="exclamation-triangle" class="fa-fw mr-1" />
|
||||
{{ $t('users_no') }}
|
||||
</b-alert>
|
||||
<b-button variant="success" :to="{ name: 'user-create' }">
|
||||
<icon iname="plus" />
|
||||
{{ $t('users_new') }}
|
||||
</b-button>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<b-list-group :class="{skeleton: !users}">
|
||||
<b-list-group-item
|
||||
v-for="(user, index) in (users ? filteredUser : 3)"
|
||||
:key="index"
|
||||
:to="users ? { name: 'user-info', params: { name: user.username }} : null"
|
||||
class="d-flex justify-content-between align-items-center pr-0"
|
||||
>
|
||||
<div>
|
||||
<h5 :class="{rounded: !users}" class="font-weight-bold">
|
||||
{{ user.username }}
|
||||
<small class="text-secondary">({{ user.fullname }})</small>
|
||||
</h5>
|
||||
<p :class="{rounded: !users}">
|
||||
{{ user.mail }}
|
||||
</p>
|
||||
</div>
|
||||
<icon iname="chevron-right" class="lg fs-sm ml-auto" />
|
||||
</b-list-group-item>
|
||||
</b-list-group>
|
||||
</template>
|
||||
</div>
|
||||
<b-list-group>
|
||||
<b-list-group-item
|
||||
v-for="user in filteredUsers" :key="user.username"
|
||||
:to="{ name: 'user-info', params: { name: user.username }}"
|
||||
class="d-flex justify-content-between align-items-center pr-0"
|
||||
>
|
||||
<div>
|
||||
<h5 class="font-weight-bold">
|
||||
{{ user.username }}
|
||||
<small class="text-secondary">({{ user.fullname }})</small>
|
||||
</h5>
|
||||
<p class="m-0">
|
||||
{{ user.mail }}
|
||||
</p>
|
||||
</div>
|
||||
<icon iname="chevron-right" class="lg fs-sm ml-auto" />
|
||||
</b-list-group-item>
|
||||
</b-list-group>
|
||||
</search-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
import SearchView from '@/components/SearchView'
|
||||
|
||||
export default {
|
||||
name: 'UserList',
|
||||
data: function () {
|
||||
|
||||
data () {
|
||||
return {
|
||||
search: ''
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
users () {
|
||||
const users = this.$store.state.data.users
|
||||
return users ? Object.values(users) : users
|
||||
},
|
||||
filteredUser () {
|
||||
...mapGetters(['users']),
|
||||
|
||||
filteredUsers () {
|
||||
if (!this.users) return
|
||||
const search = this.search.toLowerCase()
|
||||
return this.users.filter(user => {
|
||||
const filtered = this.users.filter(user => {
|
||||
return user.username.toLowerCase().includes(search)
|
||||
})
|
||||
return filtered.length === 0 ? null : filtered
|
||||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
this.$store.dispatch('FETCH', { uri: 'users' })
|
||||
}
|
||||
},
|
||||
|
||||
components: { SearchView }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
p {
|
||||
margin: 0
|
||||
}
|
||||
|
||||
.skeleton {
|
||||
@each $i, $opacity in 1 .75, 2 .5, 3 .25 {
|
||||
.list-group-item:nth-child(#{$i}) { opacity: $opacity; }
|
||||
}
|
||||
|
||||
h5, p {
|
||||
background-color: $skeleton-color;
|
||||
height: 1.5rem;
|
||||
width: 10rem;
|
||||
}
|
||||
|
||||
small {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in a new issue