Dynamic column configuration depending on which reported by backend, added Camtiler column definition, Screenshot cell renderer and separate ExamineCamModal
This commit is contained in:
		| @@ -5,9 +5,12 @@ div#app { | ||||
| } | ||||
|  | ||||
| .screenshots-drawer { | ||||
|   position: fixed; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   flex-direction: row; | ||||
| } | ||||
|  | ||||
| .screenshots-drawer img { | ||||
|   margin-right: 0; | ||||
| } | ||||
|  | ||||
| .ag-theme-material { | ||||
| @@ -21,6 +24,7 @@ div#app { | ||||
|  | ||||
| .app-title { | ||||
|   font-family: 'Montserrat'; | ||||
|   text-transform: capitalize; | ||||
| } | ||||
|  | ||||
| .vc-container { | ||||
|   | ||||
							
								
								
									
										20
									
								
								src/components/Grid/Main/ScreenshotRenderer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/components/Grid/Main/ScreenshotRenderer.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| export default { | ||||
|     template: `<div> | ||||
|       <div class="screenshots-drawer"> | ||||
|         <img v-for="screenshot in screenshots" :src="screenshot.thumb"/> | ||||
|       </div> | ||||
|     </div>`, | ||||
|     data: function () { | ||||
|         return { | ||||
|             screenshots: [], | ||||
|         }; | ||||
|     }, | ||||
|     beforeMount() { | ||||
|         this.updateImage(this.params); | ||||
|     }, | ||||
|     methods: { | ||||
|         updateImage(params) { | ||||
|             this.screenshots = params.value | ||||
|         }, | ||||
|     }, | ||||
| }; | ||||
							
								
								
									
										46
									
								
								src/components/Grid/Main/configs/camtiler.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/components/Grid/Main/configs/camtiler.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| const config = { | ||||
|     defaultColDef: { | ||||
|         width: 120, | ||||
|         initialPinned: true, | ||||
|         resizable: true, | ||||
|         enableCellChangeFlash: true | ||||
|     }, | ||||
|     columnDefs: [ | ||||
|         { | ||||
|             field: '@timestamp', | ||||
|             width: 140, | ||||
|             sortable: true | ||||
|         }, | ||||
|         { | ||||
|             field: 'source', | ||||
|             tooltipValueGetter: (params) => params.value, | ||||
|             filter: 'ComboboxFilter', | ||||
|             filterParams: { | ||||
|                 options: [], | ||||
|                 field: 'source', | ||||
|                 parentColumn: null, | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             field: 'event', | ||||
|             tooltipValueGetter: (params) => params.value, | ||||
|         }, | ||||
|         { | ||||
|             field: 'started', | ||||
|             width: 140, | ||||
|             tooltipValueGetter: (params) => params.value, | ||||
|         }, | ||||
|         { | ||||
|             field: 'finished', | ||||
|             width: 140, | ||||
|             tooltipValueGetter: (params) => params.value, | ||||
|         }, | ||||
|         { | ||||
|             field: 'screenshots', | ||||
|             cellRenderer: 'ScreenshotRenderer', | ||||
|             width: 450, | ||||
|         }, | ||||
|     ], | ||||
| } | ||||
|  | ||||
| export default config | ||||
| @@ -1,4 +1,4 @@ | ||||
| import ComboboxFilter from "./Filter/ComboboxFilter"; | ||||
| import ComboboxFilter from "../Filter/ComboboxFilter"; | ||||
| 
 | ||||
| const config = { | ||||
|     defaultColDef: { | ||||
| @@ -2,6 +2,7 @@ | ||||
|   <div style="height: 100%; width: 100%;" v-resize="onResize"> | ||||
|     <Header :refresh-filter-state="refreshFilterState" @setup-stream="setupStream" /> | ||||
|     <ag-grid-vue | ||||
|       v-if="columnDefs" | ||||
|       style="width: 100%; height: calc(100% - 52px);" | ||||
|       class="ag-theme-material" | ||||
|       @grid-ready="onGridReady" | ||||
| @@ -19,7 +20,8 @@ | ||||
|       :doesExternalFilterPass="doesExternalFilterPass" | ||||
|       :loadingOverlayComponent="'loadingOverlay'" | ||||
|     ></ag-grid-vue> | ||||
|     <ExamineLogModal :examine-log-content="examineLogContent" :close-modal="closeExamineLog" /> | ||||
|     <ExamineLogModal v-if="backend === 'logmower'" :examine-log-content="examineLogContent" :close-modal="closeExamineLog" /> | ||||
|     <ExamineCamModal v-if="backend === 'camtiler'" :examine-log-content="examineLogContent" :close-modal="closeExamineLog" /> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| @@ -29,22 +31,25 @@ import "ag-grid-community/styles//ag-grid.css"; | ||||
| import "ag-grid-community/styles//ag-theme-material.css"; | ||||
| import { Resize } from 'vuetify/directives'; | ||||
| import ExamineLogModal from "./Modal/ExamineLogModal.vue"; | ||||
| import ExamineCamModal from "./Modal/ExamineCamModal.vue"; | ||||
| import ComboboxFilter from "./Grid/Main/Filter/ComboboxFilter.js"; | ||||
| import MessageWithLevelRenderer from "./Grid/Main/MessageWithLevelRenderer"; | ||||
| import ScreenshotRenderer from "./Grid/Main/ScreenshotRenderer"; | ||||
| import flattenObj from "../helpers/flattenObj"; | ||||
| import parseEventData from "../helpers/parseEventData"; | ||||
| import {mapActions, mapGetters} from 'vuex'; | ||||
| import config from "./Grid/Main/config"; | ||||
| import loadingOverlay from "./Grid/Main/loadingOverlay"; | ||||
| import Header from "./Header/Header.vue"; | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     Header, | ||||
|     ExamineLogModal, | ||||
|     ExamineLogModal, // TODO: dynamic loading | ||||
|     ExamineCamModal, | ||||
|     AgGridVue, | ||||
|     ComboboxFilter, | ||||
|     MessageWithLevelRenderer, | ||||
|     ScreenshotRenderer, | ||||
|     loadingOverlay | ||||
|   }, | ||||
|   directives: { | ||||
| @@ -53,12 +58,14 @@ export default { | ||||
|   data() { | ||||
|     return { | ||||
|       examineLogContent: null, | ||||
|       ...config, | ||||
|       gridApi: null, | ||||
|       gridColumnApi: null, | ||||
|       comboBoxOptions: {}, | ||||
|       es: null, | ||||
|       initialFilter: null, | ||||
|       defaultColDef: null, | ||||
|       columnDefs: null, | ||||
|       backend: 'logmower' | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
| @@ -69,13 +76,31 @@ export default { | ||||
|   }, | ||||
|   watch: { | ||||
|     filterQuery() { | ||||
|       if (this.backend) { | ||||
|         this.setupStream() | ||||
|       } | ||||
|     }, | ||||
|     streaming() { | ||||
|       if (this.backend) { | ||||
|         this.setupStream() | ||||
|       } | ||||
|     }, | ||||
|   }, | ||||
|   created() { | ||||
|     fetch('/events/backend') | ||||
|         .then((response) => response.text()) | ||||
|         .then((response) => { | ||||
|           this.backend = response | ||||
|           import( /* @vite-ignore */  './Grid/Main/configs/' + response) | ||||
|               .then(module => { | ||||
|                 this.defaultColDef = module.default.defaultColDef | ||||
|                 this.columnDefs = module.default.columnDefs | ||||
|               }).catch(err => { | ||||
|                 console.error(err) | ||||
|                 this.$toast.error(`Backend '${response}' not supported`, { | ||||
|                 position: "top-right", | ||||
|             }); | ||||
|           }).then(() => { | ||||
|             let queryParams = new URLSearchParams(window.location.search); | ||||
|             queryParams = Object.fromEntries(queryParams); | ||||
|             this.initialFilter = queryParams | ||||
| @@ -83,6 +108,8 @@ export default { | ||||
|             queryParams['from'] && (queryParams['from'] = Number(queryParams['from'])) | ||||
|             queryParams['to'] && (queryParams['to'] = Number(queryParams['to'])) | ||||
|             this.setFilterQuery(queryParams) | ||||
|           }); | ||||
|       }) | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions({ | ||||
| @@ -239,7 +266,6 @@ export default { | ||||
|     openExamineLog (row) { | ||||
|       const selectedRow = row.data | ||||
|       row.node.setSelected(false) | ||||
|       this.examineLog = true | ||||
|       const flattened = flattenObj(selectedRow) | ||||
|       const pairs = []; | ||||
|       Object.keys(flattened).map((key) => { | ||||
|   | ||||
							
								
								
									
										155
									
								
								src/components/Modal/ExamineCamModal.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								src/components/Modal/ExamineCamModal.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,155 @@ | ||||
| <template> | ||||
|   <v-dialog | ||||
|       v-model="examineLog" | ||||
|       width="50wv" | ||||
|       @click.outside="close" | ||||
|       transition="false" | ||||
|   > | ||||
|     <v-card> | ||||
|       <v-card-text :style="{ height: gridHeight + 'px' }"> | ||||
|         <ag-grid-vue | ||||
|             v-if="examineLogContent" | ||||
|             style="width: 100%; height: 100%;" | ||||
|             class="ag-theme-material" | ||||
|             @grid-ready="onGridReady" | ||||
|             :columnDefs="columnDefs" | ||||
|             :row-data="examineLogContent.filter((val) => {return val.key !== 'screenshots'})" | ||||
|             :supress-horisontal-scroll="true" | ||||
|             :enable-scrolling="true" | ||||
|             :enableCellTextSelection="true" | ||||
|             :ensureDomOrder="true" | ||||
|             @cell-clicked="copyText" | ||||
|           ></ag-grid-vue> | ||||
|       </v-card-text> | ||||
|       <v-card-text v-if="screenshots.length"> | ||||
|         <h5>Screenshots</h5> | ||||
|         <br> | ||||
|         <viewer :images="screenshots" class="screenshots" :style="{ height: screenshotsHeight + 'px' }"> | ||||
|           <img v-for="screenshot in screenshots" :src="screenshot.orig"/> | ||||
|         </viewer> | ||||
|       </v-card-text> | ||||
|       <v-card-actions> | ||||
|         <v-btn color="primary" block @click="closeModal" transition="false">Close</v-btn> | ||||
|       </v-card-actions> | ||||
|     </v-card> | ||||
|   </v-dialog> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { AgGridVue } from "ag-grid-vue3"; | ||||
| import "ag-grid-community/styles//ag-grid.css"; | ||||
| import "ag-grid-community/styles//ag-theme-material.css"; | ||||
| import { VCard, VCardText, VCardActions } from 'vuetify/components/VCard' | ||||
| import { VDialog } from 'vuetify/components/VDialog' | ||||
| import { VBtn } from 'vuetify/components/VBtn' | ||||
| import { VTable } from 'vuetify/components/VTable' | ||||
| import ValueRenderer from "./ValueRenderer"; | ||||
|  | ||||
| export default { | ||||
|   name: "ExamineCamModal", | ||||
|   components: { | ||||
|     AgGridVue, | ||||
|     VCard, | ||||
|     VCardText, | ||||
|     VCardActions, | ||||
|     VBtn, | ||||
|     VDialog, | ||||
|     VTable, | ||||
|     ValueRenderer | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       columnDefs:  [ | ||||
|         { | ||||
|           field: 'key', | ||||
|           sortable: true, | ||||
|           filter: 'agTextColumnFilter', | ||||
|           resizable: true, | ||||
|           width: 10, | ||||
|         }, | ||||
|         { | ||||
|           field: 'value', | ||||
|           sortable: true, | ||||
|           filter: 'agTextColumnFilter', | ||||
|           resizable: true, | ||||
|           cellRenderer: 'ValueRenderer' | ||||
|         }, | ||||
|       ], | ||||
|       screenshots: [] | ||||
|     } | ||||
|   }, | ||||
|   props: { | ||||
|     examineLogContent: Array, | ||||
|     closeModal: Function | ||||
|   }, | ||||
|   computed: { | ||||
|     examineLog() { | ||||
|       return !!this.examineLogContent | ||||
|     }, | ||||
|     gridHeight() { | ||||
|       const computed = ((this.examineLogContent ? this.examineLogContent.length : 0) + 1) * 50 | ||||
|       const max = window.innerHeight * 0.5 | ||||
|       const min = window.innerHeight * 0.2 | ||||
|       return (computed < max ? (computed > min ? computed : min) : max); | ||||
|     }, | ||||
|     screenshotsHeight() { | ||||
|       return (window.innerHeight * 0.8) - this.gridHeight | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     examineLogContent(content, oldContent) { | ||||
|       if (content && oldContent) { | ||||
|         return | ||||
|       } | ||||
|       let screenshots = this.examineLogContent && this.examineLogContent.find((elem) => { | ||||
|         return elem.key === 'screenshots' | ||||
|       }) || null | ||||
|       if (screenshots) { | ||||
|         let id = this.examineLogContent.find((elem) => { | ||||
|           return elem.key === '_id' | ||||
|         }).value | ||||
|         fetch('/events/details/' + id) | ||||
|             .then((response) => response.json()) | ||||
|             .then((response) => { | ||||
|               this.screenshots = response | ||||
|             }); | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     onGridReady(params) { | ||||
|       params.api.sizeColumnsToFit() | ||||
|     }, | ||||
|     close (e) { | ||||
|       if (e.target.className === "v-overlay__scrim") { | ||||
|         this.closeModal() | ||||
|       } | ||||
|     }, | ||||
|     copyText(e) { | ||||
|       navigator.clipboard.writeText(e.value); | ||||
|       this.$toast.success(`Value copied to clipboard`, { | ||||
|         position: "top-right", | ||||
|       }); | ||||
|       setTimeout(this.$toast.clear, 3000); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| </script> | ||||
|  | ||||
| <style> | ||||
| .screenshots { | ||||
|   display: flex; | ||||
|   flex-direction: row; | ||||
|   flex-wrap: wrap; | ||||
|   overflow-y: auto; | ||||
|   height: 500px; | ||||
| } | ||||
| .screenshots img { | ||||
|   width: auto; | ||||
|   height: 150px; | ||||
|   cursor: pointer; | ||||
|   margin-right: 20px; | ||||
|   margin-bottom: 20px; | ||||
| } | ||||
| </style> | ||||
		Reference in New Issue
	
	Block a user