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> | ||||
|   <div style="height: 100%; width: 100%;" v-resize="onResize"> | ||||
|    <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"> | ||||
|        <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> | ||||
|     <Header :refresh-filter-state="refreshFilterState" @setup-stream="setupStream" /> | ||||
|     <ag-grid-vue | ||||
|       style="width: 100%; height: calc(100% - 52px);" | ||||
|       class="ag-theme-material" | ||||
| @@ -44,8 +27,6 @@ | ||||
| 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 { Resize } from 'vuetify/directives'; | ||||
| import ExamineLogModal from "./Modal/ExamineLogModal.vue"; | ||||
| import ComboboxFilter from "./Grid/Main/Filter/ComboboxFilter.js"; | ||||
| @@ -54,19 +35,16 @@ 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"; | ||||
| import loadingOverlay from "./Grid/Main/loadingOverlay"; | ||||
| import Header from "./Header/Header.vue"; | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     Datepicker, | ||||
|     Header, | ||||
|     ExamineLogModal, | ||||
|     AgGridVue, | ||||
|     ComboboxFilter, | ||||
|     MessageWithLevelRenderer, | ||||
|     VBtn, | ||||
|     VRow, | ||||
|     VCol, | ||||
|     loadingOverlay | ||||
|   }, | ||||
|   directives: { | ||||
| @@ -110,7 +88,7 @@ export default { | ||||
|     ...mapActions({ | ||||
|       setFilterOptions: 'setFilterOptions', | ||||
|       setFilterQuery: 'setFilterQuery', | ||||
|       toggleFilterQueryStreaming: 'toggleFilterQueryStreaming', | ||||
|       setLastPingReceived: 'setLastPingReceived', | ||||
|     }), | ||||
|     onResize () { | ||||
|       if (this.gridApi) { | ||||
| @@ -129,6 +107,7 @@ export default { | ||||
|       if (url.searchParams.keys().next()) { | ||||
|         let es = new EventSource(url.toString()); | ||||
|         es.onmessage = (e) => this.handleReceiveMessage(e) | ||||
|         es.addEventListener("ping", (e) => this.handleReceivePing()) | ||||
|         es.addEventListener("filters", (e) => this.handleReceiveFilters(e)) | ||||
|         es.addEventListener("timeout", (e) => this.handleReceiveTimeout(e)) | ||||
|         es.addEventListener("completed", (e) => this.handleAllReceived(e)) | ||||
| @@ -247,6 +226,9 @@ export default { | ||||
|     handleAllReceived () { | ||||
|       this.gridApi.hideOverlay(); | ||||
|     }, | ||||
|     handleReceivePing () { | ||||
|       this.setLastPingReceived() | ||||
|     }, | ||||
|     doesExternalFilterPass(node) { | ||||
|       if (node.data && this.filterQuery.from && this.filterQuery.to) { | ||||
|         let ts = new Date(node.data['@timestamp']).getTime() | ||||
|   | ||||
| @@ -4,6 +4,7 @@ const store = createStore({ | ||||
|   state: { | ||||
|     filterOptions: {}, | ||||
|     filterQuery: {}, | ||||
|     lastPingReceived: null, | ||||
|   }, | ||||
|   getters: { | ||||
|     streaming (state) { | ||||
| @@ -11,7 +12,10 @@ const store = createStore({ | ||||
|     }, | ||||
|     filterQuery (state) { | ||||
|       return state.filterQuery | ||||
|     } | ||||
|     }, | ||||
|     lastPingReceived (state) { | ||||
|       return state.lastPingReceived | ||||
|     }, | ||||
|   }, | ||||
|   actions: { | ||||
|     setFilterOptions(context, payload) { | ||||
| @@ -30,6 +34,9 @@ const store = createStore({ | ||||
|       query.streaming = false | ||||
|       commit("SET_FILTER_QUERY", query); | ||||
|     }, | ||||
|     setLastPingReceived(context) { | ||||
|       context.commit("SET_LAST_PING_RECEIVED"); | ||||
|     }, | ||||
|   }, | ||||
|   mutations: { | ||||
|     SET_FILTER_OPTIONS(state, payload) { | ||||
| @@ -55,6 +62,9 @@ const store = createStore({ | ||||
|  | ||||
|       state.filterQuery = query | ||||
|     }, | ||||
|     SET_LAST_PING_RECEIVED(state) { | ||||
|       state.lastPingReceived = (new Date()).getTime() | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -22,5 +22,10 @@ export default { | ||||
|     vuetify: { | ||||
|       // https://github.com/vuetifyjs/vuetify-loader/tree/next/packages/vuetify-loader | ||||
|     } | ||||
|   } | ||||
|   }, | ||||
|   optimizeDeps: { | ||||
|     exclude: [ | ||||
|       "@meforma/vue-toaster" | ||||
|     ] | ||||
|   }, | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user