Dynamic column configuration depending on which reported by backend, added Camtiler column definition, Screenshot cell renderer and separate ExamineCamModal
This commit is contained in:
parent
a9d1211d83
commit
918401ab42
@ -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 {
|
||||||
@ -44,4 +48,4 @@ div#app {
|
|||||||
|
|
||||||
.v--default-css .c-toast {
|
.v--default-css .c-toast {
|
||||||
font-family: 'Roboto Mono'!important;
|
font-family: 'Roboto Mono'!important;
|
||||||
}
|
}
|
||||||
|
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>
|
Loading…
Reference in New Issue
Block a user