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 { | .screenshots-drawer { | ||||||
|   position: fixed; |  | ||||||
|   display: flex; |   display: flex; | ||||||
|   flex-direction: column; |   flex-direction: row; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .screenshots-drawer img { | ||||||
|  |   margin-right: 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| .ag-theme-material { | .ag-theme-material { | ||||||
| @@ -21,6 +24,7 @@ div#app { | |||||||
|  |  | ||||||
| .app-title { | .app-title { | ||||||
|   font-family: 'Montserrat'; |   font-family: 'Montserrat'; | ||||||
|  |   text-transform: capitalize; | ||||||
| } | } | ||||||
|  |  | ||||||
| .vc-container { | .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 = { | const config = { | ||||||
|     defaultColDef: { |     defaultColDef: { | ||||||
| @@ -2,6 +2,7 @@ | |||||||
|   <div style="height: 100%; width: 100%;" v-resize="onResize"> |   <div style="height: 100%; width: 100%;" v-resize="onResize"> | ||||||
|     <Header :refresh-filter-state="refreshFilterState" @setup-stream="setupStream" /> |     <Header :refresh-filter-state="refreshFilterState" @setup-stream="setupStream" /> | ||||||
|     <ag-grid-vue |     <ag-grid-vue | ||||||
|  |       v-if="columnDefs" | ||||||
|       style="width: 100%; height: calc(100% - 52px);" |       style="width: 100%; height: calc(100% - 52px);" | ||||||
|       class="ag-theme-material" |       class="ag-theme-material" | ||||||
|       @grid-ready="onGridReady" |       @grid-ready="onGridReady" | ||||||
| @@ -19,7 +20,8 @@ | |||||||
|       :doesExternalFilterPass="doesExternalFilterPass" |       :doesExternalFilterPass="doesExternalFilterPass" | ||||||
|       :loadingOverlayComponent="'loadingOverlay'" |       :loadingOverlayComponent="'loadingOverlay'" | ||||||
|     ></ag-grid-vue> |     ></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> |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| @@ -29,22 +31,25 @@ 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 { Resize } from 'vuetify/directives'; | import { Resize } from 'vuetify/directives'; | ||||||
| import ExamineLogModal from "./Modal/ExamineLogModal.vue"; | import ExamineLogModal from "./Modal/ExamineLogModal.vue"; | ||||||
|  | import ExamineCamModal from "./Modal/ExamineCamModal.vue"; | ||||||
| import ComboboxFilter from "./Grid/Main/Filter/ComboboxFilter.js"; | import ComboboxFilter from "./Grid/Main/Filter/ComboboxFilter.js"; | ||||||
| import MessageWithLevelRenderer from "./Grid/Main/MessageWithLevelRenderer"; | import MessageWithLevelRenderer from "./Grid/Main/MessageWithLevelRenderer"; | ||||||
|  | import ScreenshotRenderer from "./Grid/Main/ScreenshotRenderer"; | ||||||
| import flattenObj from "../helpers/flattenObj"; | 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 loadingOverlay from "./Grid/Main/loadingOverlay"; | import loadingOverlay from "./Grid/Main/loadingOverlay"; | ||||||
| import Header from "./Header/Header.vue"; | import Header from "./Header/Header.vue"; | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   components: { |   components: { | ||||||
|     Header, |     Header, | ||||||
|     ExamineLogModal, |     ExamineLogModal, // TODO: dynamic loading | ||||||
|  |     ExamineCamModal, | ||||||
|     AgGridVue, |     AgGridVue, | ||||||
|     ComboboxFilter, |     ComboboxFilter, | ||||||
|     MessageWithLevelRenderer, |     MessageWithLevelRenderer, | ||||||
|  |     ScreenshotRenderer, | ||||||
|     loadingOverlay |     loadingOverlay | ||||||
|   }, |   }, | ||||||
|   directives: { |   directives: { | ||||||
| @@ -53,12 +58,14 @@ export default { | |||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
|       examineLogContent: null, |       examineLogContent: null, | ||||||
|       ...config, |  | ||||||
|       gridApi: null, |       gridApi: null, | ||||||
|       gridColumnApi: null, |       gridColumnApi: null, | ||||||
|       comboBoxOptions: {}, |       comboBoxOptions: {}, | ||||||
|       es: null, |       es: null, | ||||||
|       initialFilter: null, |       initialFilter: null, | ||||||
|  |       defaultColDef: null, | ||||||
|  |       columnDefs: null, | ||||||
|  |       backend: 'logmower' | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
| @@ -69,20 +76,40 @@ export default { | |||||||
|   }, |   }, | ||||||
|   watch: { |   watch: { | ||||||
|     filterQuery() { |     filterQuery() { | ||||||
|       this.setupStream() |       if (this.backend) { | ||||||
|  |         this.setupStream() | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|     streaming() { |     streaming() { | ||||||
|       this.setupStream() |       if (this.backend) { | ||||||
|  |         this.setupStream() | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   created() { |   created() { | ||||||
|     let queryParams = new URLSearchParams(window.location.search); |     fetch('/events/backend') | ||||||
|     queryParams = Object.fromEntries(queryParams); |         .then((response) => response.text()) | ||||||
|     this.initialFilter = queryParams |         .then((response) => { | ||||||
|     queryParams['initial'] = true |           this.backend = response | ||||||
|     queryParams['from'] && (queryParams['from'] = Number(queryParams['from'])) |           import( /* @vite-ignore */  './Grid/Main/configs/' + response) | ||||||
|     queryParams['to'] && (queryParams['to'] = Number(queryParams['to'])) |               .then(module => { | ||||||
|     this.setFilterQuery(queryParams) |                 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 | ||||||
|  |             queryParams['initial'] = true | ||||||
|  |             queryParams['from'] && (queryParams['from'] = Number(queryParams['from'])) | ||||||
|  |             queryParams['to'] && (queryParams['to'] = Number(queryParams['to'])) | ||||||
|  |             this.setFilterQuery(queryParams) | ||||||
|  |           }); | ||||||
|  |       }) | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     ...mapActions({ |     ...mapActions({ | ||||||
| @@ -239,7 +266,6 @@ export default { | |||||||
|     openExamineLog (row) { |     openExamineLog (row) { | ||||||
|       const selectedRow = row.data |       const selectedRow = row.data | ||||||
|       row.node.setSelected(false) |       row.node.setSelected(false) | ||||||
|       this.examineLog = true |  | ||||||
|       const flattened = flattenObj(selectedRow) |       const flattened = flattenObj(selectedRow) | ||||||
|       const pairs = []; |       const pairs = []; | ||||||
|       Object.keys(flattened).map((key) => { |       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