summaryrefslogtreecommitdiffstats
path: root/webapp
diff options
context:
space:
mode:
authorUdo Walter2019-03-22 05:36:03 +0100
committerUdo Walter2019-03-22 05:36:03 +0100
commite6c97c4913e5d24a908c65cc7592d9332df24057 (patch)
tree80662936f9dcb8622a743726fac5fcfccd6bddd2 /webapp
parent[server/registration/backends] Rework addClient and updateClient to receive json (diff)
downloadbas-e6c97c4913e5d24a908c65cc7592d9332df24057.tar.gz
bas-e6c97c4913e5d24a908c65cc7592d9332df24057.tar.xz
bas-e6c97c4913e5d24a908c65cc7592d9332df24057.zip
[webapp] add back to top button + log improvements
Diffstat (limited to 'webapp')
-rw-r--r--webapp/src/components/BackToTopButton.vue56
-rw-r--r--webapp/src/components/DashboardPage.vue4
-rw-r--r--webapp/src/components/DataTable.vue2
-rw-r--r--webapp/src/components/DataTableSearch.vue10
-rw-r--r--webapp/src/components/LogModule.vue70
-rw-r--r--webapp/src/components/LogModuleEntry.vue121
6 files changed, 227 insertions, 36 deletions
diff --git a/webapp/src/components/BackToTopButton.vue b/webapp/src/components/BackToTopButton.vue
new file mode 100644
index 0000000..f70b636
--- /dev/null
+++ b/webapp/src/components/BackToTopButton.vue
@@ -0,0 +1,56 @@
+<i18n>
+{
+ "en": {
+ },
+ "de": {
+ }
+}
+</i18n>
+
+<template>
+ <v-scale-transition>
+ <v-btn
+ class="back-to-top-button"
+ fab
+ color="primary"
+ v-show="visible"
+ v-scroll="handleScroll"
+ @click="$vuetify.goTo(0, { duration })"
+ >
+ <v-icon>keyboard_arrow_up</v-icon>
+ </v-btn>
+ </v-scale-transition>
+</template>
+
+<script>
+
+export default {
+ name: 'BackToTopButton',
+ data () {
+ return {
+ visible: false
+ }
+ },
+ computed: {
+ duration () {
+ return this.$store.state.settings.noAnimations ? 0 : 420
+ }
+ },
+ methods: {
+ handleScroll () {
+ const offset = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0
+ if (offset > 200) this.visible = true
+ else this.visible = false
+ }
+ }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+.back-to-top-button {
+ position: fixed;
+ right: 16px;
+ bottom: 16px;
+}
+</style>
diff --git a/webapp/src/components/DashboardPage.vue b/webapp/src/components/DashboardPage.vue
index 4fbac78..7e3a4c3 100644
--- a/webapp/src/components/DashboardPage.vue
+++ b/webapp/src/components/DashboardPage.vue
@@ -98,13 +98,14 @@
<v-content>
<router-view />
</v-content>
-
+ <back-to-top-button></back-to-top-button>
<notifications-snackbars></notifications-snackbars>
</v-app>
</template>
<script>
import { mapState, mapMutations, mapGetters } from 'vuex'
+import BackToTopButton from '@/components/BackToTopButton'
import NotificationsAlerts from '@/components/NotificationsAlerts'
import NotificationsSnackbars from '@/components/NotificationsSnackbars'
import AccountModule from '@/components/AccountModule'
@@ -120,6 +121,7 @@ export default {
]
},
components: {
+ BackToTopButton,
NotificationsAlerts,
NotificationsSnackbars
},
diff --git a/webapp/src/components/DataTable.vue b/webapp/src/components/DataTable.vue
index c55ed96..8280abf 100644
--- a/webapp/src/components/DataTable.vue
+++ b/webapp/src/components/DataTable.vue
@@ -454,7 +454,7 @@ export default {
.scroller >>> .vue-recycle-scroller__slot {
position: sticky;
top: 0;
- z-index: 1;
+ z-index: 2;
}
.table-head {
diff --git a/webapp/src/components/DataTableSearch.vue b/webapp/src/components/DataTableSearch.vue
index 01c2125..6e6c7e9 100644
--- a/webapp/src/components/DataTableSearch.vue
+++ b/webapp/src/components/DataTableSearch.vue
@@ -31,7 +31,7 @@
solo flat
class="column-select"
v-model="s.key"
- :items="[ { text: $t('all'), key: null }, ...dataKeys ]"
+ :items="[ { text: $t('all'), key: null }, ...keySelectItems ]"
item-text="text"
item-value="key"
color="primary"
@@ -97,6 +97,14 @@ export default {
}
},
computed: {
+ keySelectItems () {
+ const result = []
+ this.dataKeys.forEach(x => {
+ let key = x.searchKey || x.key || x
+ result.push({ text: x.text || x, key })
+ })
+ return result
+ },
dataSearchKeys () {
return this.dataKeys.map(x => x.searchKey || x.key || x)
},
diff --git a/webapp/src/components/LogModule.vue b/webapp/src/components/LogModule.vue
index 05e9a30..173b70e 100644
--- a/webapp/src/components/LogModule.vue
+++ b/webapp/src/components/LogModule.vue
@@ -12,10 +12,9 @@
"category": "Category",
"categories": "Categories",
"description": "Description",
- "group": "Group",
"groups": "Groups",
- "client": "Client",
"clients": "Clients",
+ "users": "Users",
"includeSubgroups": "Include Subgroups"
},
"de": {
@@ -30,10 +29,9 @@
"category": "Kategorie",
"categories": "Kategorien",
"description": "Beschreibung",
- "group": "Gruppe",
"groups": "Gruppen",
- "client": "Client",
"clients": "Clients",
+ "users": "Benutzer",
"includeSubgroups": "Inklusive Untergruppen"
}
}
@@ -58,7 +56,7 @@
<select-box
class="select-box"
v-model="categoryFilter"
- :items="categories"
+ :items="CATEGORIES"
:max-columns="selectBoxColumnCount"
prepend-icon="all_inbox"
:label="$t('categories')"
@@ -147,7 +145,7 @@
v-model="groupFilter"
:items="groupList"
:max-columns="selectBoxColumnCount"
- prepend-icon="device_hub"
+ prepend-icon="category"
:label="$t('groups')"
hide-details
></select-box>
@@ -171,7 +169,11 @@
</v-card>
<v-subheader>{{ $t('log') }}</v-subheader>
<v-card>
- <data-table :headers="headers" :items="log" min-width="1100px" no-select no-sort></data-table>
+ <data-table :headers="headers" :items="log" min-width="900px" no-select no-sort :row-count="-1">
+ <template #description="{ item }">
+ <log-module-entry :item="item"></log-module-entry>
+ </template>
+ </data-table>
</v-card>
</v-tab-item>
</v-tabs-items>
@@ -181,6 +183,7 @@
</template>
<script>
+import LogModuleEntry from '@/components/LogModuleEntry'
import SelectBox from '@/components/SelectBox'
import DataTable from '@/components/DataTable'
import { mapState, mapGetters } from 'vuex'
@@ -188,20 +191,22 @@ import { mapState, mapGetters } from 'vuex'
export default {
name: 'LogModule',
components: {
+ LogModuleEntry,
SelectBox,
DataTable
},
data () {
return {
+ CATEGORIES: [
+ { id: 1, name: 'CLIENT_REGISTRATION' },
+ { id: 2, name: 'BACKEND_ERROR' }
+ ],
tabs: 0,
- categories: [],
log: [],
headers: [
{ key: 'timestamp', text: this.$t('timestamp'), width: '160px' },
{ key: 'category', text: this.$t('category'), width: '160px' },
- { key: 'description', text: this.$t('description') },
- { key: 'group', text: this.$t('group'), width: '180px' },
- { key: 'client', text: this.$t('client'), width: '180px' }
+ { key: 'description', searchKey: 'searchString', text: this.$t('description') }
],
loading: false,
fromDate: null,
@@ -266,8 +271,26 @@ export default {
this.$http.get('/api/log').then(response => {
response.data.forEach(item => {
item.timestamp = new Date(item.timestamp * 1000).toISOString().split('.')[0].replace('T', ' ')
- if (item.group) item.group = item.group.name
- if (item.client) item.client = item.client.name
+ item.searchString = ''
+ // Only show first line of the description
+ if (item.description) {
+ item.searchString += item.description + '\n'
+ const desc = item.description.split('\n')
+ if (desc.length > 1) item.multilineDescription = item.description
+ item.description = desc[0]
+ }
+ // Add client/group/user info to the search string
+ if (item.client) for (let key in item.client) item.searchString += item.client[key] + '\n'
+ if (item.group) for (let key in item.group) item.searchString += item.group[key] + '\n'
+ if (item.user) for (let key in item.user) item.searchString += item.user[key] + '\n'
+ // Add snapshot info to the search string
+ if (item.clientSnapshot) for (let key in item.clientSnapshot) item.searchString += item.clientSnapshot[key] + '\n'
+ if (item.groupSnapshot) for (let key in item.groupSnapshot) item.searchString += item.groupSnapshot[key] + '\n'
+ if (item.userSnapshot) for (let key in item.userSnapshot) item.searchString += item.userSnapshot[key] + '\n'
+ // Add client/group/user object for deleted clients/groups/users
+ if (item.clientSnapshot && !item.client) item.client = { name: item.clientSnapshot.id, deleted: true }
+ if (item.groupSnapshot && !item.group) item.group = { name: item.groupSnapshot.id, deleted: true }
+ if (item.userSnapshot && !item.user) item.user = { name: item.userSnapshot.id, deleted: true }
})
this.log = response.data
this.loading = false
@@ -275,15 +298,12 @@ export default {
}
},
created () {
- this.$store.dispatch('groups/loadLists')
- this.$http.get('/api/log/categories').then(response => {
- this.categories = response.data.map((category, index) => ({ id: index, name: category }))
- })
const date = new Date()
date.setDate(date.getDate() - 7)
this.fromDate = this.formatDate(this.defaultStartDate, { time: false })
this.fromTime = this.formatDate(this.defaultStartDate, { date: false, seconds: false })
this.loadLog()
+ this.$store.dispatch('groups/loadLists')
}
}
</script>
@@ -309,20 +329,4 @@ export default {
min-width: 70px;
width: 70px;
}
-
-.search-wrapper {
- display: flex;
- align-items: center;
-}
-
-.search-box {
- flex: 1;
-}
-
-.toggle-button {
- min-width: 36px;
- text-transform: none;
- font-size: 14px;
- font-weight: 600;
-}
</style>
diff --git a/webapp/src/components/LogModuleEntry.vue b/webapp/src/components/LogModuleEntry.vue
new file mode 100644
index 0000000..3aaa5f2
--- /dev/null
+++ b/webapp/src/components/LogModuleEntry.vue
@@ -0,0 +1,121 @@
+<i18n>
+{
+ "en": {
+ },
+ "de": {
+ }
+}
+</i18n>
+
+<template>
+ <div style="display: flex; align-items: center;">
+ <template v-if="item.client">
+ <v-menu v-model="menus.client" :close-on-content-click="false" offset-y>
+ <template #activator="{ on }">
+ <v-btn v-on="on" outline small class="description-button" :class="{ 'error--text': item.client.deleted }">
+ <v-icon small class="mr-1">computer</v-icon>{{ item.client.name }}
+ </v-btn>
+ </template>
+ <template #default>
+ <v-card style="user-select: text;"><v-card-text>{{ item.clientSnapshot }}</v-card-text></v-card>
+ </template>
+ </v-menu>
+ </template>
+
+ <template v-if="item.group">
+ <v-menu v-model="menus.group" :close-on-content-click="false" offset-y>
+ <template #activator="{ on }">
+ <v-btn v-on="on" outline small class="description-button" :class="{ 'error--text': item.group.deleted }">
+ <v-icon small class="mr-1">category</v-icon>{{ item.group.name }}
+ </v-btn>
+ </template>
+ <template #default>
+ <v-card style="user-select: text;"><v-card-text>{{ item.groupSnapshot }}</v-card-text></v-card>
+ </template>
+ </v-menu>
+ </template>
+
+ <template v-if="item.user">
+ <v-menu v-model="menus.user" :close-on-content-click="false" offset-y>
+ <template #activator="{ on }">
+ <v-btn v-on="on" outline small class="description-button" :class="{ 'error--text': item.user.deleted }">
+ <v-icon small class="mr-1">person</v-icon>{{ item.user.name }}
+ </v-btn>
+ </template>
+ <template #default>
+ <v-card style="user-select: text;"><v-card-text>{{ item.userSnapshot }}</v-card-text></v-card>
+ </template>
+ </v-menu>
+ </template>
+
+ <v-menu
+ v-if="item.multilineDescription"
+ v-model="menus.description"
+ :close-on-content-click="false"
+ nudge-top="12"
+ nudge-left="16"
+ >
+ <template #activator="{ on }">
+ <v-btn v-if="item.multilineDescription" v-on="on" small icon style="margin: 0 2px 0 0; overflow: hidden;">
+ <v-icon :style="menus.description ? 'transform: rotate(180deg)' : ''">arrow_drop_down</v-icon>
+ </v-btn>
+ </template>
+ <template #default>
+ <v-card style="display: flex; padding: 16px">
+ <v-btn small icon style="margin: -4px 2px 0 0; overflow: hidden;" @click="menus.description = false">
+ <v-icon :style="menus.description ? 'transform: rotate(180deg)' : ''">arrow_drop_down</v-icon>
+ </v-btn>
+ <div style="user-select: text; white-space: pre;">{{ item.multilineDescription }}</div>
+ </v-card>
+ </template>
+ </v-menu>
+ <div style="user-select: text;" @click="menus.description = true">{{ item.description }}</div>
+ </div>
+</template>
+
+<script>
+
+export default {
+ name: 'LogModuleEntry',
+ props: {
+ item: {
+ type: Object,
+ default: () => ({})
+ }
+ },
+ data () {
+ return {
+ menus: { description: false, client: false, group: false, user: false }
+ }
+ },
+ watch: {
+ item () {
+ this.menus = { description: false, client: false, group: false, user: false }
+ },
+ menus: {
+ deep: true,
+ handler (menus) {
+ if (menus.description || menus.client || menus.group || menus.user) this.$emit('menu-translate', this.$el.offsetParent.style.transform)
+ }
+ }
+ },
+ methods: {
+ }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+.description-button {
+ min-width: 10px;
+ margin: 0 10px 0 0;
+}
+
+.description-button >>> .v-btn__content {
+ max-width: 180px;
+ overflow: hidden;
+ display: block;
+ text-overflow: ellipsis;
+ text-transform: none;
+}
+</style>