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