summaryrefslogblamecommitdiffstats
path: root/webapp/src/components/LogModule.vue
blob: 4af4408bb05178c47f596078e0830c32f172a765 (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",
    "refresh" :"Refresh"
  },
  "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",
    "refresh": "Aktualisieren"
  }
}
</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" :background-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 class="non-selectable">
              <v-card-text>
                <v-layout wrap>
                  <v-flex xs12 md6 order-xs2 order-md1>
                    <select-box
                      class="select-box"
                      :value="filter.categoryFilter"
                      @input="setFilter('categoryFilter', $event)"
                      :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-right picker-label mr-2">{{ $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 mr-2"><v-icon small class="mr-1">today</v-icon>{{ filter.fromDate }}</v-btn>
                        </template>
                        <v-date-picker
                          :value="filter.fromDate"
                          @input="setFilter('fromDate', $event)"
                          color="primary"
                          :locale="locale"
                          :width="pickerWidth"
                          first-day-of-week="1"
                        ></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>{{ filter.fromTime }}</v-btn>
                        </template>
                        <v-time-picker
                          :value="filter.fromTime"
                          @input="setFilter('fromTime', $event)"
                          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-right picker-label mr-2">{{ $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 mr-2">
                            <v-icon small class="mr-1">today</v-icon>
                            {{ filter.toDate ? filter.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
                              :value="filter.toDate"
                              @input="setFilter('toDate', $event)"
                              color="primary"
                              :locale="locale"
                              :width="pickerWidth"
                              first-day-of-week="1"
                            ></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>
                            {{ filter.toTime ? filter.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
                              :value="filter.toTime"
                              @input="setFilter('toTime', $event)"
                              color="primary"
                              :locale="locale"
                              format="24hr"
                              :width="pickerWidth"
                            ></v-time-picker>
                          </div>
                        </template>
                      </v-menu>

                    </div>
                  </v-flex>

                  <v-flex xs12 md6 order-xs3>
                    <select-box
                      class="select-box"
                      :value="filter.clientFilter"
                      @input="setFilter('clientFilter', $event)"
                      :items="clientList"
                      :max-columns="selectBoxColumnCount"
                      prepend-icon="computer"
                      :label="$t('clients')"
                      hide-details
                    ></select-box>
                  </v-flex>
                  <v-flex xs12 md6 order-xs4>
                    <select-box
                      class="select-box"
                      :value="filter.userFilter"
                      @input="setFilter('userFilter', $event)"
                      :items="userList"
                      :max-columns="selectBoxColumnCount"
                      prepend-icon="person"
                      :label="$t('users')"
                      hide-details
                    ></select-box>
                  </v-flex>
                  <v-flex xs12 md6 order-xs4>
                    <select-box
                      class="select-box"
                      :value="filter.groupFilter"
                      @input="setFilter('groupFilter', $event)"
                      :items="groupList"
                      :max-columns="selectBoxColumnCount"
                      prepend-icon="category"
                      :label="$t('groups')"
                      hide-details
                    ></select-box>
                  </v-flex>
                  <v-flex xs12 md6 order-xs5 style="display: flex; align-items: flex-end; justify-content: flex-end">
                    <v-btn color="primary" :loading="loading" @click="loadLog">
                      <v-icon left>{{ filterModified ? 'filter_list' : 'refresh' }}</v-icon>{{ $t(filterModified ? 'filter' : 'refresh') }}
                    </v-btn>
                  </v-flex>
                </v-layout>
              </v-card-text>
            </v-card>
            <v-subheader>{{ $t('log') }}</v-subheader>
            <v-card>
              <data-table :loading="loading" :headers="headers" :items="log" min-width="900px" no-select no-sort :row-count="-1" copy-button>
                <template #category="{ item }">
                  <span
                    style="user-select: text"
                    :class="item.category.indexOf('ERROR_') === 0 ? 'error--text' : item.category.indexOf('WARNING_') === 0 ? 'warning--text' : 'primary--text'"
                  >
                    {{ item.category }}
                  </span>
                </template>
                <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 {
      categoryArray: [
        'BACKEND_CREATE',
        'BACKEND_EDIT',
        'BACKEND_DELETE',
        'BACKEND_SYNCSETTINGS_EDIT',
        'BACKEND_CHECKCONNECTION',
        'BACKEND_BATCH_DELETE',
        'ERROR_BACKEND',
        'ERROR_BACKEND_CHECKCONNECTION',
        'ERROR_BACKEND_DELETE',
        'ERROR_BACKEND_IDOIT_OBJECT_DELETION',
        'ERROR_BACKEND_INFOBLOX_OBJECT_DELETION',
        'CLIENT_BOOT',
        'CLIENT_CREATE',
        'CLIENT_EDIT',
        'CLIENT_DELETE',
        'CLIENT_BATCH_DELETE',
        'ERROR_CLIENT_DELETE',
        'ERROR_DHCP',
        'EVENT_CREATE',
        'EVENT_EDIT',
        'EVENT_DELETE',
        'ERROR_EVENT_DELETE',
        'EVENT_BATCH_DELETE',
        'GROUP_CREATE',
        'GROUP_EDIT',
        'GROUP_DELETE',
        'GROUP_BATCH_DELETE',
        'GROUP_BATCH_REMOVE_SUBGROUP',
        'GROUP_REMOVE_SUBGROUP',
        'GROUP_BATCH_ADD_SUBGROUP',
        'GROUP_ADD_SUBGROUP',
        'GROUP_BATCH_REMOVE_CLIENT',
        'GROUP_REMOVE_CLIENT',
        'GROUP_BATCH_ADD_CLIENT',
        'GROUP_ADD_CLIENT',
        'ERROR_GROUP_EDIT',
        'ERROR_GROUP_DELETE',
        'ERROR_GROUP_REMOVE_SUBGROUP',
        'ERROR_GROUP_ADD_SUBGROUP',
        'ERROR_GROUP_REMOVE_CLIENT',
        'ERROR_GROUP_ADD_CLIENT',
        'IPRANGE_CREATE',
        'IPRANGE_EDIT',
        'IPRANGE_DELETE',
        'IPRANGE_BATCH_DELETE',
        'ERROR_IPRANGE_DELETE',
        'IPXEBUILDER_BUILD',
        'IPXEBUILDER_CANCEL',
        'IPXEBUILDER_CLEAN',
        'IPXEBUILDER_SAVE',
        'ERROR_IPXEBUILDER_SAVE',
        'IPXECONFIG_CREATE',
        'IPXECONFIG_EDIT',
        'IPXECONFIG_DELETE',
        'IPXECONFIG_SET_CLIENTS',
        'IPXECONFIG_SET_GROUPS',
        'IPXECONFIG_SET_DEFAULT',
        'IPXECONFIG_BATCH_DELETE',
        'ERROR_IPXECONFIG_DELETE',
        'REGISTRATION',
        'REGISTATIONHOOK_CREATE',
        'REGISTATIONHOOK_EDIT',
        'REGISTATIONHOOK_DELETE',
        'REGISTATIONHOOK_EDIT_ORDER',
        'ROLE_CREATE',
        'ROLE_EDIT',
        'ROLE_DELETE',
        'ROLE_BATCH_DELETE',
        'ERROR_ROLE_DELETE',
        'USER_CREATE',
        'USER_EDIT',
        'USER_EDIT_PASSWORD',
        'USER_DELETE',
        'USER_BATCH_DELETE',
        'USER_GRANT_ROLE',
        'USER_REVOKE_ROLE',
        'ERROR_USER_CREATE',
        'ERROR_USER_EDIT',
        'ERROR_USER_EDIT_PASSWORD',
        'WAKE_ON_LAN',
        'ERROR_WAKE_ON_LAN'
      ],
      tabs: 0,
      log: [],
      headers: [
        { key: 'timestamp', text: this.$t('timestamp'), width: '160px' },
        { key: 'category', text: this.$t('category'), width: '200px' },
        { key: 'description', searchKey: 'searchString', text: this.$t('description') }
      ],
      loading: false,
      toDateMenu: false,
      toTimeMenu: false,
      groupRecursive: false,
      userList: [],
      filterModified: false
    }
  },
  computed: {
    ...mapState('log', ['filter']),
    ...mapState('groups', ['groupList', 'clientList']),
    ...mapGetters(['tabsDark', 'tabsColor', 'tabsSliderColor']),
    CATEGORIES () {
      return this.categoryArray.map((category, index) => { return { id: index, name: category } })
    },
    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
    }
  },
  watch: {
    filter: {
      deep: true,
      handler () {
        this.filterModified = true
      }
    }
  },
  methods: {
    setFilter (filter, value) {
      if (filter === 'toDate' && !this.filter.toTime) this.$store.commit('log/setFilter', { filter: 'toTime', value: this.formatDate(new Date(), { date: false, seconds: false }) })
      if (filter === 'toTime' && !this.filter.toDate) this.$store.commit('log/setFilter', { filter: 'toDate', value: this.formatDate(new Date(), { time: false }) })
      this.$store.commit('log/setFilter', { filter, value })
    },
    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.$store.commit('log/setFilter', { filter: 'toDate', value: null })
      this.$store.commit('log/setFilter', { filter: 'toTime', value: null })
      this.toDateMenu = false
      this.toTimeMenu = false
    },
    async loadLog () {
      this.loading = true
      const queries = []
      if (this.filter.fromDate && this.filter.fromTime) queries.push('from=' + new Date(this.filter.fromDate + ' ' + this.filter.fromTime).getTime() / 1000)
      if (this.filter.toDate && this.filter.toTime) queries.push('to=' + new Date(this.filter.toDate + ' ' + this.filter.toTime).getTime() / 1000)
      if (this.filter.categoryFilter.length) queries.push('categories=' + this.filter.categoryFilter.map(x => x.name).join(','))
      if (this.filter.clientFilter.length) queries.push('clients=' + this.filter.clientFilter.map(x => x.id).join(','))
      if (this.filter.groupFilter.length) queries.push('groups=' + this.filter.groupFilter.map(x => x.id).join(','))
      if (this.filter.userFilter.length) queries.push('users=' + this.filter.userFilter.map(x => x.id).join(','))
      const url = '/api/systemlog' + (queries.length ? '?' + queries.join('&') : '')
      const response = await this.$http.get(url)
      for (let i in response.data) {
        const item = response.data[i]
        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.descriptionHeader = 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
      this.filterModified = false
    }
  },
  created () {
    if (!this.filter.fromDate || !this.filter.fromTime) {
      const date = new Date()
      date.setDate(date.getDate() - 7)
      this.setFilter('fromDate', this.formatDate(date, { time: false }))
      this.setFilter('fromTime', this.formatDate(date, { 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;
}

.theme--dark .primary--text {
  color: white !important;
}

.error--text, .warning--text {
  font-weight: bold;
}
</style>