Date range picker for querying by timestamp
This commit is contained in:
		| @@ -21,6 +21,7 @@ | ||||
|     "vue": "^3.2.39", | ||||
|     "vuex": "next", | ||||
|     "vue-select": "beta", | ||||
|     "v-calendar": "next", | ||||
|     "@meforma/vue-toaster": "^1.3.0", | ||||
|     "vuetify": "^3.0.0-beta.0", | ||||
|     "webfontloader": "^1.0.0" | ||||
|   | ||||
							
								
								
									
										81
									
								
								src/components/Grid/Main/Filter/Datepicker.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								src/components/Grid/Main/Filter/Datepicker.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| <template> | ||||
|   <v-date-picker mode="dateTime" v-model="dateRange" locale="et" :is24hr="true" is-range timezone="UTC"> | ||||
|     <template v-slot="{ inputValue, inputEvents }"> | ||||
|       <div class="flex justify-center items-center"> | ||||
|         <input | ||||
|             :value="inputValue.start" | ||||
|             v-on="inputEvents.start" | ||||
|             class="border px-2 py-1 w-32 rounded focus:outline-none focus:border-indigo-300" | ||||
|         /> | ||||
|         <svg | ||||
|             class="w-4 h-4 mx-2" | ||||
|             fill="none" | ||||
|             viewBox="0 0 24 24" | ||||
|             stroke="currentColor" | ||||
|             width="20" | ||||
|         > | ||||
|           <path | ||||
|               stroke-linecap="round" | ||||
|               stroke-linejoin="round" | ||||
|               stroke-width="2" | ||||
|               d="M14 5l7 7m0 0l-7 7m7-7H3" | ||||
|           /> | ||||
|         </svg> | ||||
|         <input | ||||
|             :value="inputValue.end" | ||||
|             v-on="inputEvents.end" | ||||
|             class="border px-2 py-1 w-32 rounded focus:outline-none focus:border-indigo-300" | ||||
|         /> | ||||
|       </div> | ||||
|     </template> | ||||
|   </v-date-picker> | ||||
| </template> | ||||
|  | ||||
|  | ||||
| <script> | ||||
| import {mapActions, mapGetters} from "vuex"; | ||||
|  | ||||
| export default { | ||||
|   name: "Datepicker", | ||||
|   props: { | ||||
|     refresh: {} | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters([ | ||||
|       'filterQuery', | ||||
|     ]), | ||||
|     dateRange: { | ||||
|       get() { | ||||
|         return { | ||||
|           start: this.filterQuery.from ? new Date(this.filterQuery.from) : null, | ||||
|           end: this.filterQuery.to ? new Date(this.filterQuery.to) : null, | ||||
|         } | ||||
|       }, | ||||
|       set(value) { | ||||
|         if (value) { | ||||
|           let toDate = new Date(value.end); | ||||
|           toDate.setSeconds(59, 999); | ||||
|           this.setFilterQueryTimeRange({ | ||||
|             from: (new Date(value.start).getTime()), | ||||
|             to: (toDate.getTime()), | ||||
|           }) | ||||
|           this.refresh() | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions({ | ||||
|       setFilterQueryTimeRange: 'setFilterQueryTimeRange', | ||||
|     }), | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
|  | ||||
| </style> | ||||
| @@ -1,14 +1,24 @@ | ||||
| <template> | ||||
|   <div style="height: 100%; width: 100%; text-align: right"> | ||||
|       <v-btn | ||||
|           color="blue-grey" | ||||
|           class="ma-2" | ||||
|           :prepend-icon="streaming ? 'mdi-pause' :'mdi-play'" | ||||
|           @click="toggleFilterQueryStreaming" | ||||
|       > | ||||
|         Stream new lines | ||||
|       </v-btn> | ||||
|   <ag-grid-vue | ||||
|   <div style="height: 100%; width: 100%;"> | ||||
|    <v-row no-gutters> | ||||
|      <v-col cols="12" sm="4" class="d-flex justify-start flex-wrap"> | ||||
|        <Datepicker class="ma-2" :refresh="refreshFilterState" /> | ||||
|      </v-col> | ||||
|      <v-col cols="12" sm="4" class="d-flex justify-center flex-wrap"> | ||||
|        <h1 class="app-title"> Logmower </h1> | ||||
|      </v-col> | ||||
|      <v-col cols="12" sm="4" class="d-flex justify-end flex-wrap"> | ||||
|        <v-btn | ||||
|            color="blue-grey" | ||||
|            class="ma-2" | ||||
|            :prepend-icon="streaming ? 'mdi-pause' :'mdi-play'" | ||||
|            @click="toggleFilterQueryStreaming" | ||||
|        > | ||||
|          Stream new lines | ||||
|        </v-btn> | ||||
|      </v-col> | ||||
|     </v-row> | ||||
|     <ag-grid-vue | ||||
|       style="width: 100%; height: calc(100% - 52px);" | ||||
|       class="ag-theme-material" | ||||
|       @grid-ready="onGridReady" | ||||
| @@ -22,6 +32,8 @@ | ||||
|       :onRowSelected="openExamineLog" | ||||
|       :supress-horisontal-scroll="true" | ||||
|       :enable-scrolling="true" | ||||
|       :isExternalFilterPresent="() => {return true}" | ||||
|       :doesExternalFilterPass="doesExternalFilterPass" | ||||
|     ></ag-grid-vue> | ||||
|     <ExamineLogModal :examine-log-content="examineLogContent" :close-modal="closeExamineLog" /> | ||||
|   </div> | ||||
| @@ -32,6 +44,7 @@ import { AgGridVue } from "ag-grid-vue3"; | ||||
| import "ag-grid-community/styles//ag-grid.css"; | ||||
| import "ag-grid-community/styles//ag-theme-material.css"; | ||||
| import { VBtn } from 'vuetify/components/VBtn' | ||||
| import { VRow, VCol } from 'vuetify/components/VGrid' | ||||
| import ExamineLogModal from "./Modal/ExamineLogModal.vue"; | ||||
| import ComboboxFilter from "./Grid/Main/Filter/ComboboxFilter.js"; | ||||
| import ErrLevelRenderer from "./Grid/Main/ErrLevelRenderer"; | ||||
| @@ -39,14 +52,18 @@ import flattenObj from "../helpers/flattenObj"; | ||||
| import parseEventData from "../helpers/parseEventData"; | ||||
| import {mapActions, mapGetters} from 'vuex'; | ||||
| import config from "./Grid/Main/config"; | ||||
| import Datepicker from "./Grid/Main/Filter/Datepicker.vue"; | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     Datepicker, | ||||
|     ExamineLogModal, | ||||
|     AgGridVue, | ||||
|     ComboboxFilter, | ||||
|     ErrLevelRenderer, | ||||
|     VBtn | ||||
|     VBtn, | ||||
|     VRow, | ||||
|     VCol | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
| @@ -62,7 +79,7 @@ export default { | ||||
|   computed: { | ||||
|     ...mapGetters([ | ||||
|       'filterQuery', | ||||
|       'streaming', | ||||
|       'streaming' | ||||
|     ]), | ||||
|   }, | ||||
|   watch: { | ||||
| @@ -78,6 +95,8 @@ export default { | ||||
|     queryParams = Object.fromEntries(queryParams); | ||||
|     this.initialFilter = queryParams | ||||
|     queryParams['initial'] = true | ||||
|     queryParams['from'] && (queryParams['from'] = Number(queryParams['from'])) | ||||
|     queryParams['to'] && (queryParams['to'] = Number(queryParams['to'])) | ||||
|     this.setFilterQuery(queryParams) | ||||
|   }, | ||||
|   methods: { | ||||
| @@ -86,6 +105,9 @@ export default { | ||||
|       setFilterQuery: 'setFilterQuery', | ||||
|       toggleFilterQueryStreaming: 'toggleFilterQueryStreaming', | ||||
|     }), | ||||
|     refreshFilterState() { | ||||
|       this.gridApi.onFilterChanged(); | ||||
|     }, | ||||
|     setupStream() { | ||||
|       this.es && this.es.close(); | ||||
|       let url = new URL('/events', window.location.href); | ||||
| @@ -143,6 +165,8 @@ export default { | ||||
|             } | ||||
|           }) | ||||
|           query['streaming'] = this.streaming | ||||
|           this.filterQuery.from && (query['from'] = this.filterQuery.from) | ||||
|           this.filterQuery.to && (query['to'] = this.filterQuery.to) | ||||
|           this.setFilterQuery(query) | ||||
|         } | ||||
|       }); | ||||
| @@ -203,6 +227,13 @@ export default { | ||||
|       }); | ||||
|       setTimeout(this.$toast.clear, 3000); | ||||
|     }, | ||||
|     doesExternalFilterPass(node) { | ||||
|       if (node.data && this.filterQuery.from && this.filterQuery.to) { | ||||
|         let ts = new Date(node.data['@timestamp']).getTime() | ||||
|         return (ts >= this.filterQuery.from && ts <= this.filterQuery.to) | ||||
|       } | ||||
|       return true; | ||||
|     }, | ||||
|     openExamineLog (row) { | ||||
|       const selectedRow = row.data | ||||
|       row.node.setSelected(false) | ||||
|   | ||||
| @@ -3,14 +3,17 @@ import store from "./stores"; | ||||
| import App from './App.vue' | ||||
| import vuetify from './plugins/vuetify' | ||||
| import Toaster from "@meforma/vue-toaster"; | ||||
| import VCalendar from 'v-calendar'; | ||||
| import { loadFonts } from './plugins/webfontloader' | ||||
| import './assets/main.css' | ||||
| import 'vue-select/dist/vue-select.css'; | ||||
| import 'v-calendar/dist/style.css'; | ||||
| loadFonts() | ||||
|  | ||||
| const app = createApp(App); | ||||
| app.use(store); | ||||
| app.use(vuetify); | ||||
| app.use(Toaster); | ||||
| app.use(VCalendar, {}); | ||||
| app.mount("#app"); | ||||
|  | ||||
|   | ||||
| @@ -23,6 +23,13 @@ const store = createStore({ | ||||
|     toggleFilterQueryStreaming(context) { | ||||
|       context.commit("TOGGLE_FILTER_QUERY_STREAMING"); | ||||
|     }, | ||||
|     setFilterQueryTimeRange({commit, state}, {from, to}) { | ||||
|       let query = state.filterQuery | ||||
|       query.from = from | ||||
|       query.to = to | ||||
|       query.streaming = false | ||||
|       commit("SET_FILTER_QUERY", query); | ||||
|     }, | ||||
|   }, | ||||
|   mutations: { | ||||
|     SET_FILTER_OPTIONS(state, payload) { | ||||
| @@ -37,9 +44,15 @@ const store = createStore({ | ||||
|     }, | ||||
|     TOGGLE_FILTER_QUERY_STREAMING(state) { | ||||
|       let query = state.filterQuery | ||||
|       query['streaming'] = (query['streaming'] === undefined) ? false : query['streaming'] | ||||
|       query['streaming'] = !(query['streaming']) | ||||
|       let streaming = (query['streaming'] === undefined) ? false : query['streaming'] | ||||
|       query['streaming'] = !(streaming) | ||||
|       query['initial'] = false | ||||
|  | ||||
|       if (!streaming) { | ||||
|         delete query['from'] | ||||
|         delete query['to'] | ||||
|       } | ||||
|  | ||||
|       state.filterQuery = query | ||||
|     }, | ||||
|   }, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user