diff --git a/package.json b/package.json
index 836330e..86b8887 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"single-spa-vue": "^2.5.1",
"systemjs-webpack-interop": "^2.3.7",
"vue": "^3.2.39",
+ "vuex": "next",
"vue-select": "beta",
"vuetify": "^3.0.0-beta.0",
"webfontloader": "^1.0.0"
diff --git a/src/components/Filter/Combobox.vue b/src/components/Filter/Combobox.vue
index 4ddef6c..3685343 100644
--- a/src/components/Filter/Combobox.vue
+++ b/src/components/Filter/Combobox.vue
@@ -1,8 +1,8 @@
@@ -15,19 +15,31 @@ export default {
vSelect
},
props: {
- options: {
+ field: {
},
changeValue: {
+ },
+ filter: {
}
},
data() {
return {
- filter: null,
+ options: []
}
},
- watch: {
- filter(value) {
- this.changeValue(value)
+ computed: {
+ filterValue: {
+ get() {
+ return this.filter
+ },
+ set(newValue) {
+ this.changeValue(newValue)
+ }
+ }
+ },
+ methods: {
+ updateOptions() {
+ this.options = this.$store.state.filterOptions[this.field] ?? []
}
}
}
diff --git a/src/components/Filter/ComboboxFilter.js b/src/components/Filter/ComboboxFilter.js
index 7b49d00..586a2c8 100644
--- a/src/components/Filter/ComboboxFilter.js
+++ b/src/components/Filter/ComboboxFilter.js
@@ -7,7 +7,8 @@ export default {
Combobox
},
template: ``,
data: function () {
diff --git a/src/components/Grid/Main/config.js b/src/components/Grid/Main/config.js
new file mode 100644
index 0000000..2daeddc
--- /dev/null
+++ b/src/components/Grid/Main/config.js
@@ -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
\ No newline at end of file
diff --git a/src/components/LogViewer.vue b/src/components/LogViewer.vue
index 637fc94..c367c74 100644
--- a/src/components/LogViewer.vue
+++ b/src/components/LogViewer.vue
@@ -24,6 +24,10 @@ import "ag-grid-community/styles//ag-grid.css";
import "ag-grid-community/styles//ag-theme-material.css";
import ExamineLogModal from "./Modal/ExamineLogModal.vue";
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 {
components: {
@@ -34,103 +38,123 @@ export default {
data() {
return {
examineLogContent: null,
+ ...config,
gridApi: null,
gridColumnApi: null,
- defaultColDef: {
- width: 50,
- initialPinned: true,
- resizable: true,
- enableCellChangeFlash: true
- },
- currentRowCount: 0,
comboBoxOptions: {},
- viewRowCount: 20,
+ es: null,
}
},
computed: {
- columnDefs() {
- return [
- {
- field: '@timestamp',
- width: 70,
- sortable: true
- },
- {
- 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',
- },
- ];
- }
+ filterQuery() {
+ return this.$store.state.filterQuery
+ },
+ },
+ watch: {
+ filterQuery() {
+ this.setupStream()
+ },
},
created() {
- this.setupStream()
+ // TODO: monitor actual URL
+ this.setFilterQuery([])
},
methods: {
+ ...mapActions({
+ setFilterOptions: 'setFilterOptions',
+ setFilterQuery: 'setFilterQuery',
+ }),
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.addEventListener("filters", (e) => this.handleReceiveFilters(e))
+ this.es = es
},
onGridReady(params) {
this.gridApi = params.api;
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) {
- const eventData = this.parseEventData(event.data);
+ const eventData = parseEventData(event.data);
const res = this.gridApi.applyTransaction({
add: [eventData]
});
const rowNode = res.add[0]
this.gridApi.flashCells({ rowNodes: [rowNode]});
- this.gridApi.sizeColumnsToFit()
},
handleReceiveFilters (event) {
- this.comboBoxOptions = this.parseEventData(event.data);
- },
- parseEventData (eventData) {
- try {
- let json = JSON.parse(eventData)
- if (!json.message) {
- json.message = JSON.stringify(json.json)
+ let data = parseEventData(event.data);
+ let opts = this.comboBoxOptions
+ for (let k in data) {
+ if (!(k in opts)) {
+ opts[k] = []
+ }
+ // 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) {
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;
-};
+
diff --git a/src/helpers/flattenObj.js b/src/helpers/flattenObj.js
new file mode 100644
index 0000000..e2c7765
--- /dev/null
+++ b/src/helpers/flattenObj.js
@@ -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;
\ No newline at end of file
diff --git a/src/helpers/parseEventData.js b/src/helpers/parseEventData.js
new file mode 100644
index 0000000..c7fe398
--- /dev/null
+++ b/src/helpers/parseEventData.js
@@ -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;
\ No newline at end of file
diff --git a/src/main.js b/src/main.js
index 24dac50..14f0d63 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,4 +1,5 @@
import { createApp } from 'vue'
+import store from "./stores";
import App from './App.vue'
import vuetify from './plugins/vuetify'
import { loadFonts } from './plugins/webfontloader'
@@ -6,6 +7,8 @@ import './assets/main.css'
import 'vue-select/dist/vue-select.css';
loadFonts()
-createApp(App)
- .use(vuetify)
- .mount('#app')
+const app = createApp(App);
+app.use(store);
+app.use(vuetify);
+app.mount("#app");
+
diff --git a/src/stores/counter.js b/src/stores/counter.js
deleted file mode 100644
index cfaade1..0000000
--- a/src/stores/counter.js
+++ /dev/null
@@ -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 }
-})
diff --git a/src/stores/index.js b/src/stores/index.js
new file mode 100644
index 0000000..50c5422
--- /dev/null
+++ b/src/stores/index.js
@@ -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
\ No newline at end of file