Dynamic filter options, Vuex store to pass them, improved code structure
This commit is contained in:
parent
ad2daab97d
commit
4fb4c9670d
@ -19,6 +19,7 @@
|
|||||||
"single-spa-vue": "^2.5.1",
|
"single-spa-vue": "^2.5.1",
|
||||||
"systemjs-webpack-interop": "^2.3.7",
|
"systemjs-webpack-interop": "^2.3.7",
|
||||||
"vue": "^3.2.39",
|
"vue": "^3.2.39",
|
||||||
|
"vuex": "next",
|
||||||
"vue-select": "beta",
|
"vue-select": "beta",
|
||||||
"vuetify": "^3.0.0-beta.0",
|
"vuetify": "^3.0.0-beta.0",
|
||||||
"webfontloader": "^1.0.0"
|
"webfontloader": "^1.0.0"
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-select
|
<v-select
|
||||||
v-model="filter"
|
v-model="filterValue"
|
||||||
:options="options"
|
:options="options"
|
||||||
class="ag-custom-component-popup"
|
@open="updateOptions"
|
||||||
></v-select>
|
></v-select>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -15,19 +15,31 @@ export default {
|
|||||||
vSelect
|
vSelect
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
options: {
|
field: {
|
||||||
},
|
},
|
||||||
changeValue: {
|
changeValue: {
|
||||||
|
},
|
||||||
|
filter: {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
filter: null,
|
options: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
computed: {
|
||||||
filter(value) {
|
filterValue: {
|
||||||
this.changeValue(value)
|
get() {
|
||||||
|
return this.filter
|
||||||
|
},
|
||||||
|
set(newValue) {
|
||||||
|
this.changeValue(newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateOptions() {
|
||||||
|
this.options = this.$store.state.filterOptions[this.field] ?? []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,8 @@ export default {
|
|||||||
Combobox
|
Combobox
|
||||||
},
|
},
|
||||||
template: `<Combobox
|
template: `<Combobox
|
||||||
:options="params.options"
|
:field="params.field"
|
||||||
|
:filter="filter"
|
||||||
:change-value="updateFilter"
|
:change-value="updateFilter"
|
||||||
/>`,
|
/>`,
|
||||||
data: function () {
|
data: function () {
|
||||||
|
60
src/components/Grid/Main/config.js
Normal file
60
src/components/Grid/Main/config.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import ComboboxFilter from "../../Filter/ComboboxFilter";
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
defaultColDef: {
|
||||||
|
width: 120,
|
||||||
|
initialPinned: true,
|
||||||
|
resizable: true,
|
||||||
|
enableCellChangeFlash: true
|
||||||
|
},
|
||||||
|
columnDefs: [
|
||||||
|
{
|
||||||
|
field: '@timestamp',
|
||||||
|
width: 70,
|
||||||
|
sortable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'kubernetes.namespace',
|
||||||
|
headerName: 'namespace',
|
||||||
|
tooltipValueGetter: (params) => params.value,
|
||||||
|
filter: ComboboxFilter,
|
||||||
|
filterParams: {
|
||||||
|
options: [],
|
||||||
|
field: 'kubernetes.namespace',
|
||||||
|
parentColumn: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'kubernetes.pod.name',
|
||||||
|
headerName: 'pod',
|
||||||
|
tooltipValueGetter: (params) => params.value,
|
||||||
|
filter: ComboboxFilter,
|
||||||
|
filterParams: {
|
||||||
|
options: [],
|
||||||
|
field: 'kubernetes.pod.name',
|
||||||
|
parentColumn: 'kubernetes.namespace',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'kubernetes.container.name',
|
||||||
|
headerName: 'container',
|
||||||
|
tooltipValueGetter: (params) => params.value,
|
||||||
|
filter: ComboboxFilter,
|
||||||
|
filterParams: {
|
||||||
|
options: [],
|
||||||
|
field: 'kubernetes.container.name',
|
||||||
|
parentColumn: 'kubernetes.pod.name',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'message',
|
||||||
|
tooltipValueGetter: (params) => params.value,
|
||||||
|
width: 500,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'stream',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
export default config
|
@ -24,6 +24,10 @@ 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 ExamineLogModal from "./Modal/ExamineLogModal.vue";
|
import ExamineLogModal from "./Modal/ExamineLogModal.vue";
|
||||||
import ComboboxFilter from "./Filter/ComboboxFilter.js";
|
import ComboboxFilter from "./Filter/ComboboxFilter.js";
|
||||||
|
import flattenObj from "../helpers/flattenObj";
|
||||||
|
import parseEventData from "../helpers/parseEventData";
|
||||||
|
import { mapActions } from 'vuex';
|
||||||
|
import config from "./Grid/Main/config";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -34,103 +38,123 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
examineLogContent: null,
|
examineLogContent: null,
|
||||||
|
...config,
|
||||||
gridApi: null,
|
gridApi: null,
|
||||||
gridColumnApi: null,
|
gridColumnApi: null,
|
||||||
defaultColDef: {
|
|
||||||
width: 50,
|
|
||||||
initialPinned: true,
|
|
||||||
resizable: true,
|
|
||||||
enableCellChangeFlash: true
|
|
||||||
},
|
|
||||||
currentRowCount: 0,
|
|
||||||
comboBoxOptions: {},
|
comboBoxOptions: {},
|
||||||
viewRowCount: 20,
|
es: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
columnDefs() {
|
filterQuery() {
|
||||||
return [
|
return this.$store.state.filterQuery
|
||||||
{
|
},
|
||||||
field: '@timestamp',
|
},
|
||||||
width: 70,
|
watch: {
|
||||||
sortable: true
|
filterQuery() {
|
||||||
},
|
this.setupStream()
|
||||||
{
|
},
|
||||||
field: 'kubernetes.namespace',
|
|
||||||
headerName: 'namespace',
|
|
||||||
tooltipValueGetter: (params) => params.value,
|
|
||||||
filter: ComboboxFilter,
|
|
||||||
filterParams: {
|
|
||||||
options: this.comboBoxOptions['kubernetes.namespace'],
|
|
||||||
field: 'kubernetes.namespace',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'kubernetes.pod.name',
|
|
||||||
headerName: 'pod',
|
|
||||||
tooltipValueGetter: (params) => params.value,
|
|
||||||
filter: ComboboxFilter,
|
|
||||||
filterParams: {
|
|
||||||
options: this.comboBoxOptions['kubernetes.pod.name'],
|
|
||||||
field: 'kubernetes.pod.name',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'kubernetes.container.name',
|
|
||||||
headerName: 'container',
|
|
||||||
tooltipValueGetter: (params) => params.value,
|
|
||||||
filter: ComboboxFilter,
|
|
||||||
filterParams: {
|
|
||||||
options: this.comboBoxOptions['kubernetes.container.name'],
|
|
||||||
field: 'kubernetes.container.name',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'message',
|
|
||||||
tooltipValueGetter: (params) => params.value,
|
|
||||||
width: 500,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'stream',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.setupStream()
|
// TODO: monitor actual URL
|
||||||
|
this.setFilterQuery([])
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions({
|
||||||
|
setFilterOptions: 'setFilterOptions',
|
||||||
|
setFilterQuery: 'setFilterQuery',
|
||||||
|
}),
|
||||||
setupStream() {
|
setupStream() {
|
||||||
let es = new EventSource('/events');
|
this.es && this.es.close();
|
||||||
|
let url = new URL('/events', window.location.href);
|
||||||
|
this.filterQuery.map((e) => {
|
||||||
|
url.searchParams.append(e.key, e.value);
|
||||||
|
})
|
||||||
|
let es = new EventSource(url.toString());
|
||||||
es.onmessage = (e) => this.handleReceiveMessage(e)
|
es.onmessage = (e) => this.handleReceiveMessage(e)
|
||||||
es.addEventListener("filters", (e) => this.handleReceiveFilters(e))
|
es.addEventListener("filters", (e) => this.handleReceiveFilters(e))
|
||||||
|
this.es = es
|
||||||
},
|
},
|
||||||
onGridReady(params) {
|
onGridReady(params) {
|
||||||
this.gridApi = params.api;
|
this.gridApi = params.api;
|
||||||
this.gridColumnApi = params.columnApi;
|
this.gridColumnApi = params.columnApi;
|
||||||
|
this.gridColumnApi.applyColumnState({
|
||||||
|
state: [{
|
||||||
|
colId: '@timestamp',
|
||||||
|
sort: 'desc'
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
this.gridApi.addGlobalListener((type, event) => {
|
||||||
|
if (type === 'filterChanged') {
|
||||||
|
let changedColumn = event.columns[0] ? (event.columns[0].colId) : null
|
||||||
|
let query = []
|
||||||
|
let gridColumns = event.columnApi.columnModel.gridColumns
|
||||||
|
gridColumns.map((column) => {
|
||||||
|
// Reset child column filter if parent changed
|
||||||
|
let parentColumn = column?.colDef?.filterParams?.parentColumn
|
||||||
|
if (parentColumn && changedColumn === parentColumn) {
|
||||||
|
let filterInstance = this.gridApi.getFilterInstance(column.colId);
|
||||||
|
column.filterActive = null
|
||||||
|
filterInstance.updateFilter(null)
|
||||||
|
this.gridApi.onFilterChanged();
|
||||||
|
}
|
||||||
|
if (column.filterActive) {
|
||||||
|
query.push({
|
||||||
|
key: column.colId,
|
||||||
|
value: column.filterActive
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.setFilterQuery(query)
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
handleReceiveMessage (event) {
|
handleReceiveMessage (event) {
|
||||||
const eventData = this.parseEventData(event.data);
|
const eventData = parseEventData(event.data);
|
||||||
const res = this.gridApi.applyTransaction({
|
const res = this.gridApi.applyTransaction({
|
||||||
add: [eventData]
|
add: [eventData]
|
||||||
});
|
});
|
||||||
const rowNode = res.add[0]
|
const rowNode = res.add[0]
|
||||||
this.gridApi.flashCells({ rowNodes: [rowNode]});
|
this.gridApi.flashCells({ rowNodes: [rowNode]});
|
||||||
this.gridApi.sizeColumnsToFit()
|
|
||||||
},
|
},
|
||||||
handleReceiveFilters (event) {
|
handleReceiveFilters (event) {
|
||||||
this.comboBoxOptions = this.parseEventData(event.data);
|
let data = parseEventData(event.data);
|
||||||
},
|
let opts = this.comboBoxOptions
|
||||||
parseEventData (eventData) {
|
for (let k in data) {
|
||||||
try {
|
if (!(k in opts)) {
|
||||||
let json = JSON.parse(eventData)
|
opts[k] = []
|
||||||
if (!json.message) {
|
}
|
||||||
json.message = JSON.stringify(json.json)
|
// TODO: proper merging
|
||||||
|
if ((data[k].parentKey) || (opts[k].length === 0)) {
|
||||||
|
opts[k].push(data[k])
|
||||||
}
|
}
|
||||||
return json
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e, eventData)
|
|
||||||
}
|
}
|
||||||
|
this.comboBoxOptions = opts
|
||||||
|
|
||||||
|
let correctOptions = {};
|
||||||
|
for (let column in opts) {
|
||||||
|
correctOptions[column] = []
|
||||||
|
let columnDef = this.columnDefs.find((columnDef) => {
|
||||||
|
return columnDef.field === column
|
||||||
|
});
|
||||||
|
let parentColumnName = columnDef?.filterParams?.parentColumn;
|
||||||
|
let possibleColumnOptions = opts[column].filter((k) => {
|
||||||
|
return k.parentKey === parentColumnName
|
||||||
|
})
|
||||||
|
if (possibleColumnOptions.length === 1) {
|
||||||
|
correctOptions[column] = possibleColumnOptions[0].options
|
||||||
|
} else if (possibleColumnOptions.length > 1) {
|
||||||
|
let filterInstance = this.gridApi.getFilterInstance(parentColumnName)
|
||||||
|
possibleColumnOptions.forEach((opt) => {
|
||||||
|
if (filterInstance && (opt.parentValue === filterInstance.filter)) {
|
||||||
|
correctOptions[column] = opt.options
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.gridApi.sizeColumnsToFit()
|
||||||
|
|
||||||
|
this.setFilterOptions(correctOptions)
|
||||||
},
|
},
|
||||||
openExamineLog (row) {
|
openExamineLog (row) {
|
||||||
const selectedRow = row.data
|
const selectedRow = row.data
|
||||||
@ -152,19 +176,5 @@ export default {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const flattenObj = (ob) => {
|
|
||||||
let result = {};
|
|
||||||
for (const i in ob) {
|
|
||||||
if ((typeof ob[i]) === 'object' && !Array.isArray(ob[i])) {
|
|
||||||
const temp = flattenObj(ob[i]);
|
|
||||||
for (const j in temp) {
|
|
||||||
result[i + '.' + j] = temp[j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
result[i] = ob[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
17
src/helpers/flattenObj.js
Normal file
17
src/helpers/flattenObj.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
const flattenObj = (ob) => {
|
||||||
|
let result = {};
|
||||||
|
for (const i in ob) {
|
||||||
|
if ((typeof ob[i]) === 'object' && !Array.isArray(ob[i])) {
|
||||||
|
const temp = flattenObj(ob[i]);
|
||||||
|
for (const j in temp) {
|
||||||
|
result[i + '.' + j] = temp[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result[i] = ob[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default flattenObj;
|
13
src/helpers/parseEventData.js
Normal file
13
src/helpers/parseEventData.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
const parseEventData = (eventData) => {
|
||||||
|
try {
|
||||||
|
let json = JSON.parse(eventData)
|
||||||
|
if (!json.message && json.json) {
|
||||||
|
json.message = JSON.stringify(json.json)
|
||||||
|
}
|
||||||
|
return json
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e, eventData)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default parseEventData;
|
@ -1,4 +1,5 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
|
import store from "./stores";
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import vuetify from './plugins/vuetify'
|
import vuetify from './plugins/vuetify'
|
||||||
import { loadFonts } from './plugins/webfontloader'
|
import { loadFonts } from './plugins/webfontloader'
|
||||||
@ -6,6 +7,8 @@ import './assets/main.css'
|
|||||||
import 'vue-select/dist/vue-select.css';
|
import 'vue-select/dist/vue-select.css';
|
||||||
loadFonts()
|
loadFonts()
|
||||||
|
|
||||||
createApp(App)
|
const app = createApp(App);
|
||||||
.use(vuetify)
|
app.use(store);
|
||||||
.mount('#app')
|
app.use(vuetify);
|
||||||
|
app.mount("#app");
|
||||||
|
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
import { ref, computed } from 'vue'
|
|
||||||
import { defineStore } from 'pinia'
|
|
||||||
|
|
||||||
export const useCounterStore = defineStore('counter', () => {
|
|
||||||
const count = ref(0)
|
|
||||||
const doubleCount = computed(() => count.value * 2)
|
|
||||||
function increment() {
|
|
||||||
count.value++
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return { count, doubleCount, increment }
|
|
||||||
})
|
|
26
src/stores/index.js
Normal file
26
src/stores/index.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { createStore } from "vuex"
|
||||||
|
|
||||||
|
const store = createStore({
|
||||||
|
state: {
|
||||||
|
filterOptions: {},
|
||||||
|
filterQuery: []
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
setFilterOptions(context, payload) {
|
||||||
|
context.commit("SET_FILTER_OPTIONS", payload);
|
||||||
|
},
|
||||||
|
setFilterQuery(context, payload) {
|
||||||
|
context.commit("SET_FILTER_QUERY", payload);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
SET_FILTER_OPTIONS(state, payload) {
|
||||||
|
state.filterOptions = payload
|
||||||
|
},
|
||||||
|
SET_FILTER_QUERY(state, payload) {
|
||||||
|
state.filterQuery = payload
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default store
|
Loading…
Reference in New Issue
Block a user