summaryrefslogblamecommitdiffstats
path: root/webapp/src/components/LogModule.vue
blob: 62089346586dfce29a4caaea13281c589ea2e207 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14













                                 
                       
                         
                     













                                           
                        
                         
                        



















                                                                                                                      
                                                       


                                              
                                         
                                                              




                                               
                                                                             










































































                                                                                                                                                                                     











                                                         




                                                         
                                             



                                           
                                             

                                        

                                          
                                                         

                                           


                                  
                                                                                                                     






                                                                                                                                          




                                                                                                              








                       
                                                        






                                              
                   




              



                                               
              



                                                                         
                                                                                       










                            


                       









                                                                                      




                                                         










                                                              
                                                                                                           

                    
                                                                                           


            
                                     






                                                                                                 
                                                                     
                                                                             










                             








                                                                                                                                
                                       
                                                                           



















                                                                                                                            






                                

                                    
                                                                           

                                                                                           
                                            





                                                              
























                                                                   
        
<i18n>
{
  "en": {
    "systemLog": "System Log",
    "from": "From",
    "to": "To",
    "today": "Today",
    "now": "Now",
    "log": "Log",
    "filter": "Filter",
    "timestamp": "Timestamp",
    "category": "Category",
    "categories": "Categories",
    "description": "Description",
    "groups": "Groups",
    "clients": "Clients",
    "users": "Users",
    "includeSubgroups": "Include Subgroups"
  },
  "de": {
    "systemLog": "System Protokoll",
    "from": "Von",
    "to": "Bis",
    "today": "Heute",
    "now": "Jetzt",
    "log": "Protokoll",
    "filter": "Filter",
    "timestamp": "Zeitstempel",
    "category": "Kategorie",
    "categories": "Kategorien",
    "description": "Beschreibung",
    "groups": "Gruppen",
    "clients": "Clients",
    "users": "Benutzer",
    "includeSubgroups": "Inklusive Untergruppen"
  }
}
</i18n>

<template>
  <v-container fill-height>
    <v-layout>
      <v-flex xl10 offset-xl1 lg12>
        <v-card class="tabbar-card">
          <v-tabs v-model="tabs" grow hide-slider :dark="tabsDark" :color="tabsColor" :slider-color="tabsSliderColor">
              <v-tab><v-icon class="tabbar-tabicon">error_outline</v-icon>{{ $t('systemLog') }}</v-tab>
          </v-tabs>
        </v-card>
        <v-tabs-items v-model="tabs" style="padding-bottom: 20px">
          <v-tab-item>
            <v-subheader>{{ $t('filter') }}</v-subheader>
            <v-card>
              <v-card-text>
                <v-layout wrap>
                  <v-flex xs12 md6 order-xs2 order-md1>
                    <select-box
                      class="select-box"
                      v-model="categoryFilter"
                      :items="CATEGORIES"
                      :max-columns="categorySelectColumnCount"
                      prepend-icon="all_inbox"
                      :label="$t('categories')"
                      hide-details
                    ></select-box>
                  </v-flex>
                  <v-flex xs12 md6 order-xs1 order-md2 class="text-md-right">
                    <div style="display: inline-block; white-space: nowrap">
                      <span class="text-xs-right picker-label">{{ $t('from') }}</span>

                      <v-menu left offset-y :close-on-content-click="false" style="display: inline-block">
                        <template #activator="{ on }">
                          <v-btn v-on="on" small class="date-picker-button"><v-icon small class="mr-1">today</v-icon>{{ fromDate }}</v-btn>
                        </template>
                        <v-date-picker
                          v-model="fromDate"
                          color="primary"
                          :locale="locale"
                          :width="pickerWidth"
                        ></v-date-picker>
                      </v-menu>

                      <v-menu left offset-y :close-on-content-click="false" style="display: inline-block">
                        <template #activator="{ on }">
                          <v-btn v-on="on" small class="time-picker-button"><v-icon small class="mr-1">schedule</v-icon>{{ fromTime }}</v-btn>
                        </template>
                        <v-time-picker
                          v-model="fromTime"
                          color="primary"
                          :locale="locale"
                          format="24hr"
                          :width="pickerWidth"
                        ></v-time-picker>
                      </v-menu>

                    </div>
                    <div style="display: inline-block; white-space: nowrap">
                      <span class="text-xs-right picker-label">{{ $t('to') }}</span>

                      <v-menu v-model="toDateMenu" left offset-y :close-on-content-click="false" style="display: inline-block">
                        <template #activator="{ on }">
                          <v-btn v-on="on" small class="date-picker-button">
                            <v-icon small class="mr-1">today</v-icon>
                            {{ toDate ? toDate : $t('today') }}
                          </v-btn>
                        </template>
                        <template #default>
                          <div style="position: relative">
                            <v-btn @click="toNow" dark color="primary" small style="position: absolute; z-index: 1; top: 0; right: 0; min-width: fit-content">{{ $t('now') }}</v-btn>
                            <v-date-picker
                              v-model="toDate"
                              color="primary"
                              :locale="locale"
                              :width="pickerWidth"
                            ></v-date-picker>
                          </div>
                        </template>
                      </v-menu>
                      <v-menu v-model="toTimeMenu" left offset-y :close-on-content-click="false" style="display: inline-block">
                        <template #activator="{ on }">
                          <v-btn v-on="on" small class="time-picker-button">
                            <v-icon small class="mr-1">schedule</v-icon>
                            {{ toTime ? toTime : $t('now') }}
                          </v-btn>
                        </template>
                        <template #default>
                          <div style="position: relative">
                            <v-btn @click="toNow" dark color="primary" small style="position: absolute; z-index: 1; top: 0; left: 0; min-width: fit-content">{{ $t('now') }}</v-btn>
                            <v-time-picker
                              v-model="toTime"
                              color="primary"
                              :locale="locale"
                              format="24hr"
                              :width="pickerWidth"
                            ></v-time-picker>
                          </div>
                        </template>
                      </v-menu>

                    </div>
                  </v-flex>

                  <v-flex xs12 md3 order-xs3>
                    <select-box
                      class="select-box"
                      v-model="clientFilter"
                      :items="clientList"
                      :max-columns="selectBoxColumnCount"
                      prepend-icon="computer"
                      :label="$t('clients')"
                      hide-details
                    ></select-box>
                  </v-flex>
                  <v-flex xs12 md3 order-xs4>
                    <select-box
                      class="select-box"
                      v-model="groupFilter"
                      :items="groupList"
                      :max-columns="selectBoxColumnCount"
                      prepend-icon="category"
                      :label="$t('groups')"
                      hide-details
                    ></select-box>
                  </v-flex>
                  <v-flex xs12 md3 order-xs4>
                    <select-box
                      class="select-box"
                      v-model="userFilter"
                      :items="userList"
                      :max-columns="selectBoxColumnCount"
                      prepend-icon="person"
                      :label="$t('users')"
                      hide-details
                    ></select-box>
                  </v-flex>
                  <v-flex xs12 md3 order-xs5 style="display: flex; align-items: flex-end; justify-content: flex-end">
                    <v-btn color="primary" :loading="loading" @click="loadLog"><v-icon left>filter_list</v-icon>{{ $t('filter') }}</v-btn>
                  </v-flex>
                </v-layout>
              </v-card-text>
            </v-card>
            <v-subheader>{{ $t('log') }}</v-subheader>
            <v-card>
              <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>
      </v-flex>
    </v-layout>
  </v-container>
</template>

<script>
import LogModuleEntry from '@/components/LogModuleEntry'
import SelectBox from '@/components/SelectBox'
import DataTable from '@/components/DataTable'
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,
      log: [],
      headers: [
        { key: 'timestamp', text: this.$t('timestamp'), width: '160px' },
        { key: 'category', text: this.$t('category'), width: '160px' },
        { key: 'description', searchKey: 'searchString', text: this.$t('description') }
      ],
      loading: false,
      fromDate: null,
      fromTime: null,
      toDate: null,
      toTime: null,
      toDateMenu: false,
      toTimeMenu: false,
      categoryFilter: [],
      groupFilter: [],
      groupRecursive: false,
      clientFilter: [],
      userFilter: [],
      userList: []
    }
  },
  computed: {
    ...mapState('groups', ['groupList', 'clientList']),
    ...mapGetters(['tabsDark', 'tabsColor', 'tabsSliderColor']),
    locale () { return this.$store.state.settings.locale },
    selectBoxColumnCount () {
      if (this.$vuetify.breakpoint.mdOnly || this.$vuetify.breakpoint.xsOnly) return 1
      return 2
    },
    categorySelectColumnCount () {
      if (this.$vuetify.breakpoint.xsOnly) return 1
      else if (this.$vuetify.breakpoint.lgAndUp) return 3
      return 2
    },
    pickerWidth () {
      return this.$vuetify.breakpoint.xsOnly ? 250 : undefined
    },
    defaultStartDate () {
      const date = new Date()
      date.setDate(date.getDate() - 7)
      return date
    }
  },
  watch: {
    toDate (value) {
      if (!this.toTime && value) this.toTime = this.formatDate(new Date(), { date: false, seconds: false })
    },
    toTime (value) {
      if (!this.toDate && value) this.toDate = this.formatDate(new Date(), { time: false })
    }
  },
  methods: {
    formatDate (date, options = {}) {
      var result = ''
      const pad = x => x < 10 ? '0' + x : x
      if (options.date !== false) {
        result += date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate())
      }
      if (options.time !== false) {
        if (result !== '') result += ' '
        result += pad(date.getHours()) + ':' + pad(date.getMinutes())
        if (options.seconds !== false) result += ':' + pad(date.getSeconds())
      }
      return result
    },
    toNow () {
      this.toDate = null
      this.toTime = null
      this.toDateMenu = false
      this.toTimeMenu = false
    },
    loadLog () {
      this.loading = true
      const queries = []
      if (this.fromDate && this.fromTime) queries.push('from=' + new Date(this.fromDate + ' ' + this.fromTime).getTime() / 1000)
      if (this.toDate && this.toTime) queries.push('to=' + new Date(this.toDate + ' ' + this.toTime).getTime() / 1000)
      if (this.categoryFilter.length) queries.push('categories=' + this.categoryFilter.map(x => x.name).join(','))
      if (this.clientFilter.length) queries.push('clients=' + this.clientFilter.map(x => x.id).join(','))
      if (this.groupFilter.length) queries.push('groups=' + this.groupFilter.map(x => x.id).join(','))
      if (this.userFilter.length) queries.push('users=' + this.userFilter.map(x => x.id).join(','))
      const url = '/api/log' + (queries.length ? '?' + queries.join('&') : '')
      this.$http.get(url).then(response => {
        response.data.forEach(item => {
          item.timestamp = this.formatDate(new Date(item.timestamp * 1000))
          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
      })
    }
  },
  created () {
    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')
    this.$http.get('/api/users').then(response => {
      this.userList = Object.freeze(response.data.map(x => ({
        id: x.id,
        name: x.username + (x.name ? ' (' + x.name + ')' : '')
      })))
    })
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.select-box {
  margin: 6px !important;
}

.picker-label {
  display: inline-block;
  width: 40px;
}

.date-picker-button {
  min-width: 110px;
  width: 110px;
  margin-right: 0;
}

.time-picker-button {
  min-width: 70px;
  width: 70px;
}
</style>