Dynamic column configuration depending on which reported by backend, added Camtiler column definition, Screenshot cell renderer and separate ExamineCamModal

This commit is contained in:
Erki Aas 2022-12-12 23:20:57 +02:00
parent a9d1211d83
commit 918401ab42
6 changed files with 269 additions and 18 deletions

View File

@ -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 {
@ -44,4 +48,4 @@ div#app {
.v--default-css .c-toast {
font-family: 'Roboto Mono'!important;
}
}

View 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
},
},
};

View 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

View File

@ -1,4 +1,4 @@
import ComboboxFilter from "./Filter/ComboboxFilter";
import ComboboxFilter from "../Filter/ComboboxFilter";
const config = {
defaultColDef: {

View File

@ -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,20 +76,40 @@ export default {
},
watch: {
filterQuery() {
this.setupStream()
if (this.backend) {
this.setupStream()
}
},
streaming() {
this.setupStream()
if (this.backend) {
this.setupStream()
}
},
},
created() {
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)
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
queryParams['initial'] = true
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) => {

View 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>