Moved Header into separate component, added ConnectionMonitor
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				continuous-integration/drone Build is failing
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	continuous-integration/drone Build is failing
				
			This commit is contained in:
		
							
								
								
									
										85
									
								
								src/components/Header/ConnectionMonitor.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/components/Header/ConnectionMonitor.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | |||||||
|  | <template> | ||||||
|  |   <v-tooltip :text="`Last ping from server received at ${secondsFromLastPing} seconds ago`" location="bottom"> | ||||||
|  |     <template v-slot:activator="{ props }"> | ||||||
|  |       <v-icon :color="(secondsFromLastPing > pingThreshold || secondsFromLastPing === null) ? 'red' : 'green'" v-bind="props" class="mr-6">mdi-server</v-icon> | ||||||
|  |     </template> | ||||||
|  |   </v-tooltip> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  | import { VTooltip } from 'vuetify/components/VTooltip' | ||||||
|  | import { VIcon } from 'vuetify/components/VIcon' | ||||||
|  | import {mapGetters} from "vuex"; | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   name: "ConnectionMonitor", | ||||||
|  |   components: { | ||||||
|  |     VTooltip, | ||||||
|  |     VIcon | ||||||
|  |   }, | ||||||
|  |   data() { | ||||||
|  |     return { | ||||||
|  |       pingThreshold: 10, | ||||||
|  |       pingError: false, | ||||||
|  |       secondsFromLastPing: null, | ||||||
|  |       setupStreamInterval: null, | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   computed: { | ||||||
|  |     ...mapGetters([ | ||||||
|  |       'lastPingReceived' | ||||||
|  |     ]) | ||||||
|  |   }, | ||||||
|  |   mounted() { | ||||||
|  |     // window.setInterval(() => { | ||||||
|  |     //   this.monitorServerConnection() | ||||||
|  |     // }, 10000) | ||||||
|  |     window.setInterval(() => { | ||||||
|  |       this.secondsFromLastPing = Math.floor(((new Date()).getTime() - this.lastPingReceived) / 1000) | ||||||
|  |       this.pingError = this.secondsFromLastPing > this.pingThreshold | ||||||
|  |     }, 1000) | ||||||
|  |   }, | ||||||
|  |   watch: { | ||||||
|  |     pingError(error) { | ||||||
|  |       if (error) { | ||||||
|  |         this.$toast.error(`Connection with server lost, attempting to reconnect...`, { | ||||||
|  |           position: "top-right", | ||||||
|  |           duration: false, | ||||||
|  |         }); | ||||||
|  |         this.setupStreamInterval = window.setInterval(() => { | ||||||
|  |           this.$emit('setupStream') | ||||||
|  |         }, 3000) | ||||||
|  |       } else { | ||||||
|  |         window.clearTimeout(this.setupStreamInterval); | ||||||
|  |         this.$toast.clear(); | ||||||
|  |         this.$toast.success(`Connection with server reestablished`, { | ||||||
|  |           position: "top-right", | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     monitorServerConnection () { | ||||||
|  |       if (this.secondsFromLastPing > this.pingThreshold) { | ||||||
|  |         this.pingError = true; | ||||||
|  |         this.$toast.error(`Connection with server lost, attempting to reconnect...`, { | ||||||
|  |           position: "top-right", | ||||||
|  |         }); | ||||||
|  |         this.setupStream(); | ||||||
|  |       } else { | ||||||
|  |         if (this.pingError) { | ||||||
|  |           this.$toast.success(`Connection with server reestablished`, { | ||||||
|  |             position: "top-right", | ||||||
|  |           }); | ||||||
|  |           setTimeout(this.$toast.clear, 3000); | ||||||
|  |         } | ||||||
|  |         this.pingError = false; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style scoped> | ||||||
|  |  | ||||||
|  | </style> | ||||||
							
								
								
									
										58
									
								
								src/components/Header/Header.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/components/Header/Header.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | <template> | ||||||
|  |   <v-row no-gutters> | ||||||
|  |     <v-col cols="12" sm="5" class="d-flex justify-start flex-wrap" > | ||||||
|  |       <Datepicker class="ma-2" :refresh="refreshFilterState" /> | ||||||
|  |     </v-col> | ||||||
|  |     <v-col cols="12" sm="2" class="d-flex justify-center flex-wrap"> | ||||||
|  |       <h1 class="app-title"> Logmower </h1> | ||||||
|  |     </v-col> | ||||||
|  |     <v-col cols="12" sm="5" class="d-flex justify-end flex-wrap align-center"> | ||||||
|  |       <ConnectionMonitor @setup-stream="setupStream" /> | ||||||
|  |       <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> | ||||||
|  | </template> | ||||||
|  | <script> | ||||||
|  | import { VRow, VCol } from 'vuetify/components/VGrid' | ||||||
|  | import { VBtn } from 'vuetify/components/VBtn' | ||||||
|  | import Datepicker from "../Grid/Main/Filter/Datepicker.vue"; | ||||||
|  | import {mapActions, mapGetters} from "vuex"; | ||||||
|  | import ConnectionMonitor from "./ConnectionMonitor.vue"; | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   components: { | ||||||
|  |     ConnectionMonitor, | ||||||
|  |     Datepicker, | ||||||
|  |     VRow, | ||||||
|  |     VCol, | ||||||
|  |     VBtn | ||||||
|  |   }, | ||||||
|  |   props: { | ||||||
|  |     refreshFilterState: Function | ||||||
|  |   }, | ||||||
|  |   computed: { | ||||||
|  |     ...mapGetters([ | ||||||
|  |       'streaming' | ||||||
|  |     ]), | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     ...mapActions({ | ||||||
|  |       toggleFilterQueryStreaming: 'toggleFilterQueryStreaming', | ||||||
|  |     }), | ||||||
|  |     setupStream () { | ||||||
|  |       this.$emit('setupStream') | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style scoped> | ||||||
|  |  | ||||||
|  | </style> | ||||||
| @@ -1,23 +1,6 @@ | |||||||
| <template> | <template> | ||||||
|   <div style="height: 100%; width: 100%;" v-resize="onResize"> |   <div style="height: 100%; width: 100%;" v-resize="onResize"> | ||||||
|    <v-row no-gutters> |     <Header :refresh-filter-state="refreshFilterState" @setup-stream="setupStream" /> | ||||||
|      <v-col cols="12" sm="5" class="d-flex justify-start flex-wrap"> |  | ||||||
|        <Datepicker class="ma-2" :refresh="refreshFilterState" /> |  | ||||||
|      </v-col> |  | ||||||
|      <v-col cols="12" sm="2" class="d-flex justify-center flex-wrap"> |  | ||||||
|        <h1 class="app-title"> Logmower </h1> |  | ||||||
|      </v-col> |  | ||||||
|      <v-col cols="12" sm="5" 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 |     <ag-grid-vue | ||||||
|       style="width: 100%; height: calc(100% - 52px);" |       style="width: 100%; height: calc(100% - 52px);" | ||||||
|       class="ag-theme-material" |       class="ag-theme-material" | ||||||
| @@ -44,8 +27,6 @@ | |||||||
| import { AgGridVue } from "ag-grid-vue3"; | import { AgGridVue } from "ag-grid-vue3"; | ||||||
| import "ag-grid-community/styles//ag-grid.css"; | import "ag-grid-community/styles//ag-grid.css"; | ||||||
| import "ag-grid-community/styles//ag-theme-material.css"; | import "ag-grid-community/styles//ag-theme-material.css"; | ||||||
| import { VBtn } from 'vuetify/components/VBtn' |  | ||||||
| import { VRow, VCol } from 'vuetify/components/VGrid' |  | ||||||
| import { Resize } from 'vuetify/directives'; | import { Resize } from 'vuetify/directives'; | ||||||
| import ExamineLogModal from "./Modal/ExamineLogModal.vue"; | import ExamineLogModal from "./Modal/ExamineLogModal.vue"; | ||||||
| import ComboboxFilter from "./Grid/Main/Filter/ComboboxFilter.js"; | import ComboboxFilter from "./Grid/Main/Filter/ComboboxFilter.js"; | ||||||
| @@ -54,19 +35,16 @@ import flattenObj from "../helpers/flattenObj"; | |||||||
| import parseEventData from "../helpers/parseEventData"; | import parseEventData from "../helpers/parseEventData"; | ||||||
| import {mapActions, mapGetters} from 'vuex'; | import {mapActions, mapGetters} from 'vuex'; | ||||||
| import config from "./Grid/Main/config"; | import config from "./Grid/Main/config"; | ||||||
| import Datepicker from "./Grid/Main/Filter/Datepicker.vue"; |  | ||||||
| import loadingOverlay from "./Grid/Main/loadingOverlay"; | import loadingOverlay from "./Grid/Main/loadingOverlay"; | ||||||
|  | import Header from "./Header/Header.vue"; | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   components: { |   components: { | ||||||
|     Datepicker, |     Header, | ||||||
|     ExamineLogModal, |     ExamineLogModal, | ||||||
|     AgGridVue, |     AgGridVue, | ||||||
|     ComboboxFilter, |     ComboboxFilter, | ||||||
|     MessageWithLevelRenderer, |     MessageWithLevelRenderer, | ||||||
|     VBtn, |  | ||||||
|     VRow, |  | ||||||
|     VCol, |  | ||||||
|     loadingOverlay |     loadingOverlay | ||||||
|   }, |   }, | ||||||
|   directives: { |   directives: { | ||||||
| @@ -110,7 +88,7 @@ export default { | |||||||
|     ...mapActions({ |     ...mapActions({ | ||||||
|       setFilterOptions: 'setFilterOptions', |       setFilterOptions: 'setFilterOptions', | ||||||
|       setFilterQuery: 'setFilterQuery', |       setFilterQuery: 'setFilterQuery', | ||||||
|       toggleFilterQueryStreaming: 'toggleFilterQueryStreaming', |       setLastPingReceived: 'setLastPingReceived', | ||||||
|     }), |     }), | ||||||
|     onResize () { |     onResize () { | ||||||
|       if (this.gridApi) { |       if (this.gridApi) { | ||||||
| @@ -129,6 +107,7 @@ export default { | |||||||
|       if (url.searchParams.keys().next()) { |       if (url.searchParams.keys().next()) { | ||||||
|         let es = new EventSource(url.toString()); |         let es = new EventSource(url.toString()); | ||||||
|         es.onmessage = (e) => this.handleReceiveMessage(e) |         es.onmessage = (e) => this.handleReceiveMessage(e) | ||||||
|  |         es.addEventListener("ping", (e) => this.handleReceivePing()) | ||||||
|         es.addEventListener("filters", (e) => this.handleReceiveFilters(e)) |         es.addEventListener("filters", (e) => this.handleReceiveFilters(e)) | ||||||
|         es.addEventListener("timeout", (e) => this.handleReceiveTimeout(e)) |         es.addEventListener("timeout", (e) => this.handleReceiveTimeout(e)) | ||||||
|         es.addEventListener("completed", (e) => this.handleAllReceived(e)) |         es.addEventListener("completed", (e) => this.handleAllReceived(e)) | ||||||
| @@ -247,6 +226,9 @@ export default { | |||||||
|     handleAllReceived () { |     handleAllReceived () { | ||||||
|       this.gridApi.hideOverlay(); |       this.gridApi.hideOverlay(); | ||||||
|     }, |     }, | ||||||
|  |     handleReceivePing () { | ||||||
|  |       this.setLastPingReceived() | ||||||
|  |     }, | ||||||
|     doesExternalFilterPass(node) { |     doesExternalFilterPass(node) { | ||||||
|       if (node.data && this.filterQuery.from && this.filterQuery.to) { |       if (node.data && this.filterQuery.from && this.filterQuery.to) { | ||||||
|         let ts = new Date(node.data['@timestamp']).getTime() |         let ts = new Date(node.data['@timestamp']).getTime() | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ const store = createStore({ | |||||||
|   state: { |   state: { | ||||||
|     filterOptions: {}, |     filterOptions: {}, | ||||||
|     filterQuery: {}, |     filterQuery: {}, | ||||||
|  |     lastPingReceived: null, | ||||||
|   }, |   }, | ||||||
|   getters: { |   getters: { | ||||||
|     streaming (state) { |     streaming (state) { | ||||||
| @@ -11,7 +12,10 @@ const store = createStore({ | |||||||
|     }, |     }, | ||||||
|     filterQuery (state) { |     filterQuery (state) { | ||||||
|       return state.filterQuery |       return state.filterQuery | ||||||
|     } |     }, | ||||||
|  |     lastPingReceived (state) { | ||||||
|  |       return state.lastPingReceived | ||||||
|  |     }, | ||||||
|   }, |   }, | ||||||
|   actions: { |   actions: { | ||||||
|     setFilterOptions(context, payload) { |     setFilterOptions(context, payload) { | ||||||
| @@ -30,6 +34,9 @@ const store = createStore({ | |||||||
|       query.streaming = false |       query.streaming = false | ||||||
|       commit("SET_FILTER_QUERY", query); |       commit("SET_FILTER_QUERY", query); | ||||||
|     }, |     }, | ||||||
|  |     setLastPingReceived(context) { | ||||||
|  |       context.commit("SET_LAST_PING_RECEIVED"); | ||||||
|  |     }, | ||||||
|   }, |   }, | ||||||
|   mutations: { |   mutations: { | ||||||
|     SET_FILTER_OPTIONS(state, payload) { |     SET_FILTER_OPTIONS(state, payload) { | ||||||
| @@ -55,6 +62,9 @@ const store = createStore({ | |||||||
|  |  | ||||||
|       state.filterQuery = query |       state.filterQuery = query | ||||||
|     }, |     }, | ||||||
|  |     SET_LAST_PING_RECEIVED(state) { | ||||||
|  |       state.lastPingReceived = (new Date()).getTime() | ||||||
|  |     } | ||||||
|   }, |   }, | ||||||
| }); | }); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,5 +22,10 @@ export default { | |||||||
|     vuetify: { |     vuetify: { | ||||||
|       // https://github.com/vuetifyjs/vuetify-loader/tree/next/packages/vuetify-loader |       // https://github.com/vuetifyjs/vuetify-loader/tree/next/packages/vuetify-loader | ||||||
|     } |     } | ||||||
|   } |   }, | ||||||
|  |   optimizeDeps: { | ||||||
|  |     exclude: [ | ||||||
|  |       "@meforma/vue-toaster" | ||||||
|  |     ] | ||||||
|  |   }, | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user