feat: dashboard feature ktx (#242)
* add dashboard functionality * fix: regression issue linting errors * fix: UI changes * fix: temporary * fix: reverted the file name change --------- Co-authored-by: ktx-keshavi <keshavi.bhalani@kiara.tech>
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 756 B |
|
After Width: | Height: | Size: 844 B |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15Z" stroke="#ec1414" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M5.19995 8H10.8" stroke="#ec1414" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 418 B |
@@ -1,18 +1,3 @@
|
||||
<!-- Copyright 2022 Zinc Labs Inc. and Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http:www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<q-card class="column full-height">
|
||||
<q-card-section class="q-px-md q-py-md text-black">
|
||||
@@ -37,7 +22,7 @@
|
||||
</q-card-section>
|
||||
<q-separator />
|
||||
<q-card-section class="q-w-md q-mx-lg">
|
||||
<q-form ref="adddashboardForm" @submit="onSubmit">
|
||||
<q-form ref="addDashboardForm" @submit="onSubmit">
|
||||
<q-input
|
||||
v-if="beingUpdated"
|
||||
v-model="dashboardData.id"
|
||||
@@ -45,11 +30,10 @@
|
||||
:disabled="beingUpdated"
|
||||
:label="t('dashboard.id')"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
v-model="dashboardData.name"
|
||||
:placeholder="t('dashboard.nameHolder')"
|
||||
:label="t('dashboard.name') + '*'"
|
||||
:placeholder="t('dashboard.nameHolder')"
|
||||
color="input-border"
|
||||
bg-color="input-bg"
|
||||
class="q-py-md showLabelOnTop"
|
||||
@@ -57,11 +41,13 @@
|
||||
outlined
|
||||
filled
|
||||
dense
|
||||
:rules="[(val) => !!val || t('dashboard.nameRequired')]"
|
||||
/>
|
||||
<span> </span>
|
||||
<q-input
|
||||
v-model="dashboardData.type"
|
||||
v-model="dashboardData.description"
|
||||
:placeholder="t('dashboard.typeHolder')"
|
||||
:label="t('dashboard.typeDesc') + '*'"
|
||||
:label="t('dashboard.typeDesc')"
|
||||
color="input-border"
|
||||
bg-color="input-bg"
|
||||
class="q-py-md showLabelOnTop"
|
||||
@@ -82,6 +68,7 @@
|
||||
no-caps
|
||||
/>
|
||||
<q-btn
|
||||
:disable="dashboardData.name === ''"
|
||||
:label="t('dashboard.save')"
|
||||
class="q-mb-md text-bold no-border q-ml-md"
|
||||
color="secondary"
|
||||
@@ -107,13 +94,14 @@ const defaultValue = () => {
|
||||
return {
|
||||
id: "",
|
||||
name: "",
|
||||
description: "",
|
||||
};
|
||||
};
|
||||
|
||||
let calldashboard: Promise<{ data: any }>;
|
||||
let callDashboard: Promise<{ data: any }>;
|
||||
|
||||
export default defineComponent({
|
||||
name: "ComponentAddUpdateUser",
|
||||
name: "ComponentAddDashboard",
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
@@ -124,7 +112,7 @@ export default defineComponent({
|
||||
setup() {
|
||||
const store: any = useStore();
|
||||
const beingUpdated: any = ref(false);
|
||||
const adddashboardForm: any = ref(null);
|
||||
const addDashboardForm: any = ref(null);
|
||||
const disableColor: any = ref("");
|
||||
const dashboardData: any = ref(defaultValue());
|
||||
const isValidIdentifier: any = ref(true);
|
||||
@@ -141,7 +129,7 @@ export default defineComponent({
|
||||
beingUpdated,
|
||||
status,
|
||||
dashboardData,
|
||||
adddashboardForm,
|
||||
addDashboardForm,
|
||||
store,
|
||||
getRandInteger,
|
||||
isValidIdentifier,
|
||||
@@ -155,6 +143,7 @@ export default defineComponent({
|
||||
this.dashboardData = {
|
||||
id: this.modelValue.id,
|
||||
name: this.modelValue.name,
|
||||
description: this.modelValue.description,
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -171,7 +160,7 @@ export default defineComponent({
|
||||
message: "Please wait...",
|
||||
timeout: 2000,
|
||||
});
|
||||
this.adddashboardForm.validate().then((valid: any) => {
|
||||
this.addDashboardForm.validate().then((valid: any) => {
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
@@ -191,7 +180,7 @@ export default defineComponent({
|
||||
};
|
||||
const obj = toRaw(this.dashboardData);
|
||||
baseObj.title = obj.name;
|
||||
baseObj.description = obj.type;
|
||||
baseObj.description = obj.description;
|
||||
baseObj.dashboardId = "DashID_" + this.getRandInteger();
|
||||
baseObj.created = new Date().toISOString();
|
||||
baseObj.owner =
|
||||
@@ -199,15 +188,37 @@ export default defineComponent({
|
||||
" " +
|
||||
toRaw(this.store.state.currentuser.last_name);
|
||||
|
||||
calldashboard = dashboardService.create(
|
||||
callDashboard = dashboardService.create(
|
||||
this.store.state.selectedOrganization.identifier,
|
||||
baseObj.dashboardId,
|
||||
JSON.stringify(JSON.stringify(baseObj))
|
||||
);
|
||||
}
|
||||
calldashboard.then((res: { data: any }) => {
|
||||
this.$emit("updated");
|
||||
});
|
||||
callDashboard
|
||||
.then((res: { data: any }) => {
|
||||
const data = res.data;
|
||||
this.dashboardData = {
|
||||
id: "",
|
||||
name: "",
|
||||
description: "",
|
||||
};
|
||||
|
||||
this.$emit("update:modelValue", data);
|
||||
this.$emit("updated");
|
||||
console.log("Done saving");
|
||||
this.addDashboardForm.resetValidation();
|
||||
dismiss();
|
||||
})
|
||||
.catch((err: any) => {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
message: JSON.stringify(
|
||||
err.response.data["error"] || "Dashboard creation failed."
|
||||
),
|
||||
});
|
||||
console.log(err);
|
||||
dismiss();
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<div class="plotlycontainer">
|
||||
<div class="drag-allow">
|
||||
<PanelHeader
|
||||
:panelDataElement="props.data"
|
||||
:dashboardId="$route.query.dashboard"
|
||||
@clicked="onClickChild"
|
||||
/>
|
||||
</div>
|
||||
<ChartRender
|
||||
:data="props.data"
|
||||
:selectedTimeDate="props.selectedTimeDate"
|
||||
></ChartRender>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import ChartRender from "./addPanel/ChartRender.vue";
|
||||
import PanelHeader from "./PanelHeader.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "PanelContainer",
|
||||
emits: ["updated:chart"],
|
||||
props: ["data", "selectedTimeDate"],
|
||||
components: {
|
||||
ChartRender,
|
||||
PanelHeader,
|
||||
},
|
||||
setup(props) {
|
||||
//get props data
|
||||
const getPanelDataElement = () => {
|
||||
return props.data;
|
||||
};
|
||||
return {
|
||||
props,
|
||||
getPanelDataElement,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getDashboardId() {
|
||||
return this.$route.query.dashboard;
|
||||
},
|
||||
onClickChild(panelDataElementValue: any) {
|
||||
this.$emit("updated:chart", panelDataElementValue);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.plotlycontainer {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,116 @@
|
||||
<!-- Copyright 2022 Zinc Labs Inc. and Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http:www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<q-bar class="bg-white">
|
||||
<!-- <q-separator dark vertical inset /> -->
|
||||
<div class="">
|
||||
{{ renderTitle }}
|
||||
</div>
|
||||
<q-space />
|
||||
<!-- <q-toolbar-title>{{ panelDataElementObject.title }}</q-toolbar-title>
|
||||
<q-separator dark vertical inset /> -->
|
||||
<q-btn-dropdown dense flat label="" no-caps >
|
||||
<q-list dense>
|
||||
<q-item clickable v-close-popup @click="onPanelModifyClick('EditPanel')">
|
||||
<q-item-section>
|
||||
<q-item-label class="q-pa-sm">Edit Panel</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup @click="onPanelModifyClick('DeletePanel')">
|
||||
<q-item-section>
|
||||
<q-item-label class="q-pa-sm">Delete Panel</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<!-- <q-item clickable v-close-popup @click="onPanelModifyClick('PrintPanel')">
|
||||
<q-item-section>
|
||||
<q-item-label class="q-pa-sm">Print Panel</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup @click="onPanelModifyClick('SharePanel')">
|
||||
<q-item-section>
|
||||
<q-item-label class="q-pa-sm">Share Panel</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item> -->
|
||||
</q-list>
|
||||
</q-btn-dropdown>
|
||||
</q-bar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { log } from "console";
|
||||
import { computed, defineComponent } from "vue";
|
||||
import { toRaw } from 'vue';
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
name: "PanelHeader",
|
||||
props: ["panelDataElement", "dashboardId"],
|
||||
panelData: [],
|
||||
|
||||
setup(props, { emit }) {
|
||||
const panelDataElementObject = toRaw(props.panelDataElement)
|
||||
const dashboardId = props.dashboardId
|
||||
const router = useRouter();
|
||||
|
||||
const renderTitle = computed(() => {
|
||||
return props.panelDataElement.config?.title
|
||||
})
|
||||
const addNewPanel = () => {
|
||||
// return router.push({
|
||||
// path: "/addPanel",
|
||||
// query: { dashboard: String(dashboardId), panelId:panelDataElementObject.id },
|
||||
// });
|
||||
return router.push({
|
||||
path: "/addPanel",
|
||||
query: { dashboard: String(dashboardId), panelId:panelDataElementObject.id },
|
||||
});
|
||||
}
|
||||
|
||||
const deletePanel = () => {
|
||||
// return router.push({
|
||||
// path: "/viewDashboard",
|
||||
// query: { dashboard: String(dashboardId), panelId:panelDataElementObject.id },
|
||||
// });
|
||||
return router.push({
|
||||
path: "/viewDashboard",
|
||||
query: { dashboard: String(dashboardId), panelId:panelDataElementObject.id },
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
panelDataElementObject,
|
||||
dashboardId,
|
||||
addNewPanel,
|
||||
deletePanel,
|
||||
renderTitle
|
||||
};
|
||||
}, methods: {
|
||||
|
||||
onPanelModifyClick(evt: any) {
|
||||
if(evt == 'EditPanel'){this.addNewPanel()}
|
||||
else if(evt == 'DeletePanel') {//this.deletePanel()
|
||||
this.$emit('clicked', this.panelDataElementObject)
|
||||
}
|
||||
else {console.log(evt)}
|
||||
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,329 @@
|
||||
<template>
|
||||
<div ref="editorRef" id="editor"></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, onMounted, onUpdated, watch } from "vue";
|
||||
import * as monaco from "monaco-editor";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
query: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
fields: {
|
||||
type: Array,
|
||||
default: [],
|
||||
},
|
||||
functions: {
|
||||
type: Array,
|
||||
default: [],
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ["update-query", "run-query", "update:query"],
|
||||
setup(props, { emit }) {
|
||||
const editorRef: any = ref();
|
||||
let editorObj: any = null;
|
||||
|
||||
const createDependencyProposals = (range: any) => {
|
||||
const keywords = [
|
||||
{
|
||||
label: "and",
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: "and ",
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: "or",
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: "or ",
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: "like",
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: "like '%${1:params}%' ",
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: "in",
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: "in ('${1:params}') ",
|
||||
range: range,
|
||||
insertTextRules:
|
||||
monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
},
|
||||
{
|
||||
label: "not in",
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: "not in ('${1:params}') ",
|
||||
range: range,
|
||||
insertTextRules:
|
||||
monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
},
|
||||
{
|
||||
label: "between",
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: "between('${1:params}','${1:params}') ",
|
||||
range: range,
|
||||
insertTextRules:
|
||||
monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
},
|
||||
{
|
||||
label: "not between",
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: "not between('${1:params}','${1:params}') ",
|
||||
range: range,
|
||||
insertTextRules:
|
||||
monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
},
|
||||
{
|
||||
label: "is null",
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: "is null ",
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: "is not null",
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: "is not null ",
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: ">",
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: "> ",
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: "<",
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: "< ",
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: ">=",
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: ">= ",
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: "<=",
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: "<= ",
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: "<>",
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: "<> ",
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: "=",
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: "= ",
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: "!=",
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: "!= ",
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: "()",
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: "(${1:condition}) ",
|
||||
range: range,
|
||||
insertTextRules:
|
||||
monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
},
|
||||
];
|
||||
|
||||
props.fields.forEach((field: any) => {
|
||||
if (field.name == "_timestamp") {
|
||||
return;
|
||||
}
|
||||
let itemObj = {
|
||||
label: field.name,
|
||||
kind: monaco.languages.CompletionItemKind.Text,
|
||||
insertText: field.name,
|
||||
range: range,
|
||||
insertTextRules:
|
||||
monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
};
|
||||
keywords.push(itemObj);
|
||||
});
|
||||
|
||||
props.functions.forEach((field: any) => {
|
||||
let itemObj = {
|
||||
label: field.name,
|
||||
kind: monaco.languages.CompletionItemKind.Text,
|
||||
insertText: field.name + field.args,
|
||||
range: range,
|
||||
insertTextRules:
|
||||
monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
};
|
||||
keywords.push(itemObj);
|
||||
});
|
||||
|
||||
return keywords;
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
// editorRef.value.addEventListener("keyup", onKeyUp);
|
||||
|
||||
monaco.editor.defineTheme("myCustomTheme", {
|
||||
base: "vs", // can also be vs-dark or hc-black
|
||||
inherit: true, // can also be false to completely replace the builtin rules
|
||||
rules: [{ token: "comment", background: "FF0000" }],
|
||||
colors: {
|
||||
"editor.foreground": "#000000",
|
||||
"editor.background": "#F7F7F7",
|
||||
"editorCursor.foreground": "#000000",
|
||||
"editor.lineHighlightBackground": "#000000",
|
||||
"editorLineNumber.foreground": "#000000",
|
||||
"editor.border": "#000000",
|
||||
},
|
||||
});
|
||||
|
||||
monaco.languages.registerCompletionItemProvider("sql", {
|
||||
provideCompletionItems: function (model, position) {
|
||||
// find out if we are completing a property in the 'dependencies' object.
|
||||
var textUntilPosition = model.getValueInRange({
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: position.lineNumber,
|
||||
endColumn: position.column,
|
||||
});
|
||||
|
||||
var word = model.getWordUntilPosition(position);
|
||||
var range = {
|
||||
startLineNumber: position.lineNumber,
|
||||
endLineNumber: position.lineNumber,
|
||||
startColumn: word.startColumn,
|
||||
endColumn: word.endColumn,
|
||||
};
|
||||
|
||||
let arr = textUntilPosition.trim().split(" ");
|
||||
let filteredSuggestions = [];
|
||||
|
||||
filteredSuggestions = createDependencyProposals(range);
|
||||
filteredSuggestions = filteredSuggestions.filter((item) => {
|
||||
return item.label.toLowerCase().includes(word.word.toLowerCase());
|
||||
});
|
||||
|
||||
// if (filteredSuggestions.length == 0) {
|
||||
const lastElement = arr.pop();
|
||||
|
||||
filteredSuggestions.push({
|
||||
label: `match_all('${lastElement}')`,
|
||||
kind: monaco.languages.CompletionItemKind.Text,
|
||||
insertText: `match_all('${lastElement}')`,
|
||||
range: range,
|
||||
});
|
||||
filteredSuggestions.push({
|
||||
label: `match_all_no_case('${lastElement}')`,
|
||||
kind: monaco.languages.CompletionItemKind.Text,
|
||||
insertText: `match_all_no_case('${lastElement}')`,
|
||||
range: range,
|
||||
});
|
||||
|
||||
return {
|
||||
suggestions: filteredSuggestions,
|
||||
};
|
||||
// } else {
|
||||
// return {
|
||||
// suggestions: filteredSuggestions,
|
||||
// };
|
||||
// }
|
||||
},
|
||||
});
|
||||
|
||||
editorObj = monaco.editor.create(editorRef.value, {
|
||||
value: props.query,
|
||||
language: "sql",
|
||||
theme: "myCustomTheme",
|
||||
showFoldingControls: "never",
|
||||
wordWrap: "on",
|
||||
lineNumbers: "on",
|
||||
lineNumbersMinChars: 0,
|
||||
overviewRulerLanes: 0,
|
||||
fixedOverflowWidgets: false,
|
||||
overviewRulerBorder: false,
|
||||
lineDecorationsWidth: 15,
|
||||
hideCursorInOverviewRuler: true,
|
||||
renderLineHighlight: "none",
|
||||
glyphMargin: false,
|
||||
folding: false,
|
||||
scrollBeyondLastColumn: 0,
|
||||
scrollBeyondLastLine: true,
|
||||
scrollbar: { horizontal: "auto", vertical: "visible" },
|
||||
find: {
|
||||
addExtraSpaceOnTop: false,
|
||||
autoFindInSelection: "never",
|
||||
seedSearchStringFromSelection: "never",
|
||||
},
|
||||
minimap: { enabled: false },
|
||||
readOnly: props.readOnly
|
||||
});
|
||||
|
||||
editorObj.onDidChangeModelContent((e: any) => {
|
||||
emit("update-query", editorObj.getValue());
|
||||
emit("update:query", editorObj.getValue());
|
||||
});
|
||||
|
||||
editorObj.createContextKey("ctrlenter", true);
|
||||
editorObj.addCommand(
|
||||
monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter,
|
||||
function () {
|
||||
emit("run-query");
|
||||
},
|
||||
"ctrlenter"
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
const setValue = (value: string) => {
|
||||
editorObj.setValue(value);
|
||||
};
|
||||
|
||||
// update readonly when prop value changes
|
||||
watch(() => props.readOnly, () => {
|
||||
editorObj.updateOptions({ readOnly: props.readOnly })
|
||||
})
|
||||
|
||||
// update readonly when prop value changes
|
||||
watch(() => props.query, () => {
|
||||
if(props.readOnly) {
|
||||
editorObj.getModel().setValue(props.query)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
editorRef,
|
||||
editorObj,
|
||||
setValue,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#editor {
|
||||
min-height: 4rem;
|
||||
border-radius: 5px;
|
||||
border: 0px solid #dbdbdb;
|
||||
}
|
||||
|
||||
.monaco-editor,
|
||||
.monaco-editor .monaco-editor {
|
||||
padding: 0px 0px 0px 0px !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,301 @@
|
||||
<template>
|
||||
<div>
|
||||
<q-bar class="q-pa-sm bg-white">
|
||||
<span class="text-subtitle2 text-weight-bold">SQL</span>
|
||||
<q-space />
|
||||
|
||||
<q-toggle
|
||||
v-model="dashboardPanelData.layout.showCustomQuery"
|
||||
:label="t('Custom SQL')"
|
||||
/>
|
||||
<q-btn-dropdown
|
||||
color="white"
|
||||
text-color="black"
|
||||
@click="onDropDownClick"
|
||||
/>
|
||||
</q-bar>
|
||||
</div>
|
||||
<div class="row" v-if="showQuery">
|
||||
<!-- <div class="col">
|
||||
<q-input
|
||||
v-model="dashboardPanelData.data.query"
|
||||
filled
|
||||
clearable
|
||||
autogrow
|
||||
color="green-8"
|
||||
label="Please provide SQL Query for Visualization"
|
||||
hint="Press Apply Button to complete query preview"
|
||||
:disable="!dashboardPanelData.meta.stream.showCustomQuery"
|
||||
/>
|
||||
</div> -->
|
||||
<div class="col">
|
||||
<query-editor
|
||||
ref="queryEditorRef"
|
||||
class="monaco-editor"
|
||||
v-model:query="dashboardPanelData.data.query"
|
||||
v-model:fields="dashboardPanelData.meta.stream.selectedStreamFields"
|
||||
v-model:functions="dashboardPanelData.meta.stream.functions"
|
||||
@run-query="searchData"
|
||||
:readOnly="!dashboardPanelData.layout.showCustomQuery"
|
||||
></query-editor>
|
||||
<!-- @update-query="updateQueryValue" -->
|
||||
<div style="color: red;" class="q-mx-sm">{{ data.queryErrors.join(', ') }} </div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
// @ts-nocheck
|
||||
import { defineComponent, ref, watch, reactive, toRaw } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useQuasar } from "quasar";
|
||||
import { Parser } from "node-sql-parser";
|
||||
|
||||
import QueryEditor from "./QueryEditor.vue";
|
||||
import useDashboardPanelData from "../../composables/useDashboardPanel";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ComponentSearchSearchBar",
|
||||
components: {
|
||||
QueryEditor,
|
||||
},
|
||||
emits: ["searchdata"],
|
||||
methods: {
|
||||
searchData() {
|
||||
if (this.searchdashboardPanelData.loading == false) {
|
||||
// this.searchObj.runQuery = true;
|
||||
this.$emit("searchdata");
|
||||
}
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
// show the query box
|
||||
const showQuery = ref(true)
|
||||
|
||||
const data = reactive({
|
||||
queryErrors: [] // custom query errors
|
||||
})
|
||||
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const $q = useQuasar();
|
||||
const { dashboardPanelData } = useDashboardPanelData()
|
||||
const parser = new Parser();
|
||||
let streamName = "";
|
||||
|
||||
// toggle show query view
|
||||
const onDropDownClick= () =>{
|
||||
showQuery.value = !showQuery.value
|
||||
}
|
||||
|
||||
// Generate the query when the fields are updated
|
||||
watch(() => [
|
||||
dashboardPanelData.data.fields.stream,
|
||||
dashboardPanelData.data.fields.x,
|
||||
dashboardPanelData.data.fields.y,
|
||||
dashboardPanelData.data.fields.filter,
|
||||
dashboardPanelData.layout.showCustomQuery
|
||||
], () => {
|
||||
|
||||
// only continue if current mode is auto query generation
|
||||
if(!dashboardPanelData.layout.showCustomQuery){
|
||||
console.log("Updating query");
|
||||
|
||||
// STEP 1: first check if there is at least 1 field selected
|
||||
if(dashboardPanelData.data.fields.x.length == 0 && dashboardPanelData.data.fields.y.length == 0) {
|
||||
dashboardPanelData.data.query = ""
|
||||
return;
|
||||
}
|
||||
|
||||
// STEP 2: Now, continue if we have at least 1 field selected
|
||||
// merge the fields list
|
||||
let query = "SELECT "
|
||||
const fields = [...dashboardPanelData.data.fields.x, ...dashboardPanelData.data.fields.y]
|
||||
const filter = [...dashboardPanelData.data.fields?.filter]
|
||||
const array = fields.map((field, i) => {
|
||||
let selector = ""
|
||||
// TODO: add aggregator
|
||||
if(field.aggregationFunction) {
|
||||
selector += `${field.aggregationFunction}(${field.column})`
|
||||
} else {
|
||||
selector += `${field.column}`
|
||||
}
|
||||
selector += ` as ${field.label}${i==fields.length-1 ? ' ' : ', '}`
|
||||
return selector
|
||||
})
|
||||
query += array.join("")
|
||||
|
||||
// now add from stream name
|
||||
query += ` FROM '${dashboardPanelData.data.fields.stream}' `
|
||||
|
||||
query += filter.length ? "WHERE " : ""
|
||||
const filterData = filter?.map((field, i)=>{
|
||||
let selectFilter = ""
|
||||
if(field.type == "list" && field.values?.length > 0){
|
||||
selectFilter += `${field.column} IN (${field.values.map(it => `'${it}'`).join(', ')})`
|
||||
}else if (field.type == "condition" && field.operator != null && field.value != null && field.value != ''){
|
||||
selectFilter += `${field.column} `
|
||||
switch(field.operator) {
|
||||
case "=":
|
||||
case "<>":
|
||||
case "<":
|
||||
case ">":
|
||||
case "<=":
|
||||
case ">=":
|
||||
selectFilter += `${field.operator} ${field.value}`
|
||||
break;
|
||||
case "Contains":
|
||||
selectFilter += `LIKE '%${field.value}%'`
|
||||
break;
|
||||
case "Not Contains":
|
||||
selectFilter += `NOT LIKE '%${field.value}%'`
|
||||
break;
|
||||
default:
|
||||
selectFilter += `${field.operator} ${field.value}`
|
||||
break;
|
||||
}
|
||||
}
|
||||
return selectFilter
|
||||
})
|
||||
console.log("query: filterData",filterData);
|
||||
|
||||
query += filterData.filter((it: any)=> it).join(" AND ")
|
||||
|
||||
// add group by statement
|
||||
query += ` GROUP BY ${dashboardPanelData.data.fields.x[0]?.label}`
|
||||
|
||||
console.log('generated query: ', query)
|
||||
|
||||
dashboardPanelData.data.query = query
|
||||
}
|
||||
}, {deep: true})
|
||||
|
||||
watch(() => dashboardPanelData.data.query, ()=>{
|
||||
console.log("query changes in search bar",dashboardPanelData.layout.showCustomQuery);
|
||||
|
||||
// only continue if current mode is show custom query
|
||||
if(dashboardPanelData.layout.showCustomQuery){
|
||||
updateQueryValue()
|
||||
}
|
||||
})
|
||||
|
||||
//TODO: verification to perform when the query is updated
|
||||
const updateQueryValue = () => {
|
||||
// store the query in the dashboard panel data
|
||||
// dashboardPanelData.meta.editorValue = value;
|
||||
// dashboardPanelData.data.query = value;
|
||||
|
||||
if (dashboardPanelData.layout.showCustomQuery) {
|
||||
console.log("query: value", dashboardPanelData.data.query);
|
||||
|
||||
// empty the errors
|
||||
data.queryErrors = []
|
||||
console.log("----------", dashboardPanelData.data.query);
|
||||
console.log("---sql mode-------", dashboardPanelData.layout.showCustomQuery);
|
||||
|
||||
// Get the parsed query
|
||||
try {
|
||||
dashboardPanelData.meta.parsedQuery = parser.astify(dashboardPanelData.data.query);
|
||||
console.log(dashboardPanelData.meta.parsedQuery)
|
||||
} catch(e) {
|
||||
console.log("error")
|
||||
console.log(e)
|
||||
// exit as there is an invalid query
|
||||
data.queryErrors.push("Invalid SQL Syntax")
|
||||
return null;
|
||||
}
|
||||
console.log("--parsed query---", dashboardPanelData.meta.parsedQuery);
|
||||
|
||||
if(!dashboardPanelData.meta.parsedQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We have the parsed query, now get the columns and tables
|
||||
// get the columns first
|
||||
if(Array.isArray(dashboardPanelData.meta.parsedQuery?.columns)
|
||||
&& dashboardPanelData.meta.parsedQuery?.columns?.length > 0) {
|
||||
dashboardPanelData.meta.stream.customQueryFields = []
|
||||
dashboardPanelData.meta.parsedQuery.columns.forEach((item: any, index: any) => {
|
||||
let val;
|
||||
// if there is a lable, use that, else leave it
|
||||
if (item["as"] === undefined || item["as"] === null) {
|
||||
val = item["expr"]["column"];
|
||||
} else {
|
||||
val = item["as"];
|
||||
}
|
||||
if (!dashboardPanelData.meta.stream.customQueryFields.find(it => it.name == val)) {
|
||||
dashboardPanelData.meta.stream.customQueryFields.push({name: val, type: ''});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
data.queryErrors.push("Invalid Columns")
|
||||
}
|
||||
|
||||
// now check if the correct stream is selected
|
||||
if (dashboardPanelData.meta.parsedQuery.from?.length > 0) {
|
||||
console.log("---parsedQuery.from--------",dashboardPanelData.meta.parsedQuery.from);
|
||||
|
||||
const streamFound = dashboardPanelData.meta.stream.streamResults.find(it => it.name == dashboardPanelData.meta.parsedQuery.from[0].table)
|
||||
if(streamFound) {
|
||||
if(dashboardPanelData.data.fields.stream != streamFound.name) {
|
||||
dashboardPanelData.data.fields.stream = streamFound.name
|
||||
}
|
||||
} else {
|
||||
data.queryErrors.push("Invalid stream")
|
||||
}
|
||||
|
||||
// if (dashboardPanelData.meta.parsedQuery.from[0].table !== dashboardPanelData.data.fields.stream) {
|
||||
// let streamFound = false;
|
||||
// const streamNameFromQuery = dashboardPanelData.meta.parsedQuery.from[0].table;
|
||||
|
||||
// dashboardPanelData.meta.stream.streamResults.forEach((stream) => {
|
||||
// console.log("Query: get stream data from streamResults", stream);
|
||||
|
||||
// if (stream.name == streamNameFromQuery) {
|
||||
// streamFound = true;
|
||||
// let itemObj = {
|
||||
// label: stream.name,
|
||||
// value: stream.name,
|
||||
// };
|
||||
// dashboardPanelData.data.stream.selectedStream = itemObj;
|
||||
// stream.schema.forEach((field) => {
|
||||
// dashboardPanelData.meta.stream.selectedStreamFields.push({
|
||||
// name: field.name,
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
||||
// if (streamFound == false) {
|
||||
// dashboardPanelData.data.stream.selectedStream = {};
|
||||
// dashboardPanelData.meta.stream.selectedStreamFields = [];
|
||||
// $q.notify({
|
||||
// message: "Stream not found",
|
||||
// color: "negative",
|
||||
// position: "top",
|
||||
// timeout: 2000,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
} else {
|
||||
data.queryErrors.push("Stream name required")
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
return {
|
||||
t,
|
||||
router,
|
||||
updateQueryValue,
|
||||
onDropDownClick,
|
||||
showQuery,
|
||||
dashboardPanelData,
|
||||
data
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
<template>
|
||||
<div v-if="props.data.type == 'table'" class="q-pa-md">
|
||||
<q-table
|
||||
class="my-sticky-virtscroll-table"
|
||||
virtual-scroll
|
||||
v-model:pagination="pagination"
|
||||
:rows-per-page-options="[0]"
|
||||
:virtual-scroll-sticky-size-start="48"
|
||||
dense
|
||||
:rows="searchQueryData?.data || []"
|
||||
row-key="id"
|
||||
>
|
||||
</q-table>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div ref="plotRef" :id="chartID" class="plotlycontainer"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
defineComponent,
|
||||
onMounted,
|
||||
onUpdated,
|
||||
ref,
|
||||
reactive,
|
||||
nextTick,
|
||||
watch,
|
||||
computed,
|
||||
onActivated,
|
||||
} from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { useQuasar, date } from "quasar";
|
||||
import queryService from "../../../services/nativequery";
|
||||
import Plotly from "plotly.js";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ChartRender",
|
||||
props: ["data", "selectedTimeDate"],
|
||||
|
||||
setup(props) {
|
||||
const $q = useQuasar();
|
||||
const store = useStore();
|
||||
const searchQueryData = reactive({
|
||||
data: [],
|
||||
});
|
||||
|
||||
onUpdated(() => {
|
||||
console.log("updated");
|
||||
if (props.data.type != "table") {
|
||||
renderChart();
|
||||
}
|
||||
});
|
||||
|
||||
const plotRef: any = ref(null);
|
||||
const chartID = ref("chart1");
|
||||
|
||||
let selectedTimeObj = computed(function () {
|
||||
return props.selectedTimeDate;
|
||||
});
|
||||
|
||||
// If query changes, we need to get the data again and rerender the chart
|
||||
watch(
|
||||
() => [props.data, props.selectedTimeDate],
|
||||
() => {
|
||||
if (props.data.query) {
|
||||
fetchQueryData();
|
||||
} else {
|
||||
Plotly.react(
|
||||
plotRef.value,
|
||||
[],
|
||||
{},
|
||||
{
|
||||
responsive: true,
|
||||
displaylogo: false,
|
||||
displayModeBar: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
// just wait till the component is mounted and then create a plotly instance
|
||||
onMounted(async () => {
|
||||
await nextTick();
|
||||
if (props.data.type != "table") {
|
||||
await Plotly.newPlot(
|
||||
plotRef.value,
|
||||
[{}],
|
||||
{},
|
||||
{
|
||||
responsive: true,
|
||||
displaylogo: false,
|
||||
displayModeBar: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (props.data.query) {
|
||||
fetchQueryData();
|
||||
}
|
||||
});
|
||||
|
||||
// wrap the text for long x axis names
|
||||
const addBreaksAtLength = 12;
|
||||
const textwrapper = function (traces: any) {
|
||||
traces = traces.map((text: any) => {
|
||||
let rxp = new RegExp(".{1," + addBreaksAtLength + "}", "g");
|
||||
if (text) {
|
||||
return text?.match(rxp)?.join("<br>");
|
||||
} else {
|
||||
return " ";
|
||||
}
|
||||
});
|
||||
return traces;
|
||||
};
|
||||
|
||||
// Chart Related Functions
|
||||
const fetchQueryData = async () => {
|
||||
const queryData = props.data.query;
|
||||
const chartParams = {
|
||||
title: "Found " + "2" + " hits in " + "10" + " ms",
|
||||
};
|
||||
|
||||
const sqlQueryModified = queryData;
|
||||
|
||||
// get query object
|
||||
// TODO: update queryData to sqlQueryModified
|
||||
const timestamps = selectedTimeObj.value;
|
||||
|
||||
let startISOTimestamp: any;
|
||||
let endISOTimestamp: any;
|
||||
if (
|
||||
timestamps.start_time != "Invalid Date" &&
|
||||
timestamps.end_time != "Invalid Date"
|
||||
) {
|
||||
startISOTimestamp =
|
||||
new Date(timestamps.start_time.toISOString()).getTime() * 1000;
|
||||
endISOTimestamp =
|
||||
new Date(timestamps.end_time.toISOString()).getTime() * 1000;
|
||||
}
|
||||
const query = {
|
||||
query: {
|
||||
sql: queryData,
|
||||
sql_mode: "full",
|
||||
start_time: startISOTimestamp,
|
||||
end_time: endISOTimestamp,
|
||||
},
|
||||
};
|
||||
|
||||
await queryService
|
||||
.runquery(query, store.state.selectedOrganization.identifier)
|
||||
.then((res) => {
|
||||
searchQueryData.data = res.data.hits;
|
||||
|
||||
$q.notify({
|
||||
type: "positive",
|
||||
message: "Query applied successfully.",
|
||||
timeout: 5000,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
$q.notify({
|
||||
type: "negative",
|
||||
message: "Something went wrong!",
|
||||
timeout: 5000,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// If data or chart type is updated, rerender the chart
|
||||
watch(
|
||||
() => [searchQueryData.data, props.data.type],
|
||||
() => {
|
||||
console.log("Query: new data received");
|
||||
if (props.data.type != "table") {
|
||||
renderChart();
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
const renderChart = async () => {
|
||||
console.log("Query: rendering chart");
|
||||
console.log("Query: chart type", props.data.type);
|
||||
// Step 1: Get the Y-Axis Count
|
||||
const xAxisKey = getXAxisKey();
|
||||
const yAxisKeys = getYAxisKeys();
|
||||
|
||||
console.log("xaxis=", textwrapper(getAxisDataFromKey(xAxisKey)));
|
||||
|
||||
let traces;
|
||||
|
||||
traces = yAxisKeys.map((key: any) => {
|
||||
const trace = {
|
||||
name: props.data.fields?.y.find((it: any) => it.label == key).column,
|
||||
...getTraceValuesByChartType(xAxisKey, key),
|
||||
showlegend: props.data.config?.show_legends,
|
||||
marker: {
|
||||
color:
|
||||
props.data.fields?.y.find((it: any) => it.label == key).color ||
|
||||
"#5960b2",
|
||||
opacity: 0.8,
|
||||
},
|
||||
};
|
||||
return trace;
|
||||
});
|
||||
|
||||
console.log("Query: populating traces: ", traces);
|
||||
|
||||
const layout: any = {
|
||||
title: false,
|
||||
showlegend: props.data.config?.show_legends,
|
||||
font: { size: 12 },
|
||||
autosize: true,
|
||||
legend: {
|
||||
bgcolor: "#f7f7f7",
|
||||
},
|
||||
xaxis: {
|
||||
tickangle: -20,
|
||||
automargin: true,
|
||||
},
|
||||
yaxis: {
|
||||
automargin: true,
|
||||
},
|
||||
margin: {
|
||||
l: 32,
|
||||
r: 16,
|
||||
t: 38,
|
||||
b: 32,
|
||||
},
|
||||
...getPropsByChartTypeForLayout(),
|
||||
};
|
||||
|
||||
Plotly.react(plotRef.value, traces, layout, {
|
||||
responsive: true,
|
||||
displaylogo: false,
|
||||
displayModeBar: false,
|
||||
});
|
||||
};
|
||||
|
||||
const getTraceValuesByChartType = (xAxisKey: string, yAxisKey: string) => {
|
||||
const trace: any = {
|
||||
...getPropsByChartTypeForTraces(),
|
||||
};
|
||||
if (props.data.type == "pie") {
|
||||
trace["labels"] = textwrapper(getAxisDataFromKey(xAxisKey));
|
||||
trace["values"] = getAxisDataFromKey(yAxisKey);
|
||||
} else if (props.data.type == "h-bar") {
|
||||
trace["y"] = textwrapper(getAxisDataFromKey(xAxisKey));
|
||||
trace["x"] = getAxisDataFromKey(yAxisKey);
|
||||
} else {
|
||||
trace["x"] = textwrapper(getAxisDataFromKey(xAxisKey));
|
||||
trace["y"] = getAxisDataFromKey(yAxisKey);
|
||||
}
|
||||
return trace;
|
||||
};
|
||||
|
||||
const getXAxisKey = () => {
|
||||
return props.data.fields.x.map((it: any) => it.label)[0];
|
||||
};
|
||||
|
||||
const getYAxisKeys = () => {
|
||||
return props.data.fields.y.map((it: any) => it.label);
|
||||
};
|
||||
|
||||
const getAxisDataFromKey = (key: string) => {
|
||||
return searchQueryData.data.map((item) => item[key]);
|
||||
};
|
||||
|
||||
const getPropsByChartTypeForTraces = () => {
|
||||
switch (props.data.type) {
|
||||
case "bar":
|
||||
return {
|
||||
type: "bar",
|
||||
};
|
||||
case "line":
|
||||
return {
|
||||
mode: "lines",
|
||||
};
|
||||
case "scatter":
|
||||
return {
|
||||
mode: "markers",
|
||||
};
|
||||
case "pie":
|
||||
return {
|
||||
type: "pie",
|
||||
};
|
||||
case "h-bar":
|
||||
return {
|
||||
type: "bar",
|
||||
orientation: "h",
|
||||
};
|
||||
case "area":
|
||||
return {
|
||||
fill: "tozeroy", //TODO: hoe to change the color of plot chart
|
||||
type: "scatter",
|
||||
};
|
||||
default:
|
||||
return {
|
||||
type: "bar",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const getPropsByChartTypeForLayout = async () => {
|
||||
switch (props.data.type) {
|
||||
case "bar":
|
||||
return {
|
||||
barmode: "group",
|
||||
};
|
||||
case "line":
|
||||
return {};
|
||||
case "scatter":
|
||||
return {
|
||||
scattermode: "group",
|
||||
};
|
||||
case "pie":
|
||||
return {};
|
||||
case "h-bar":
|
||||
return {
|
||||
barmode: "group",
|
||||
};
|
||||
case "area":
|
||||
return {};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
plotRef,
|
||||
props,
|
||||
searchQueryData,
|
||||
pagination: ref({
|
||||
rowsPerPage: 0,
|
||||
}),
|
||||
chartID,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.my-sticky-virtscroll-table {
|
||||
/* height or max-height is important */
|
||||
height: 410px;
|
||||
|
||||
.q-table__top,
|
||||
.q-table__bottom,
|
||||
thead tr:first-child th {
|
||||
/* bg color is important for th; just specify one */
|
||||
background-color: #fff;
|
||||
}
|
||||
thead tr th {
|
||||
position: sticky;
|
||||
z-index: 1;
|
||||
}
|
||||
/* this will be the loading indicator */
|
||||
thead tr:last-child th {
|
||||
/* height of all previous header rows */
|
||||
top: 48px;
|
||||
}
|
||||
thead tr:first-child th {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div style="height: 100%">
|
||||
<div class="q-pa-none" style="max-width: 75px">
|
||||
<q-list separator>
|
||||
<q-item
|
||||
:class="
|
||||
selectedChartType === item.id ? 'q-pa-none bg-grey-3' : 'q-pa-none'
|
||||
"
|
||||
v-for="(item, index) in ChartsArray"
|
||||
:key="index"
|
||||
clickable
|
||||
v-ripple
|
||||
@click="$emit('update:selectedChartType', item.id)"
|
||||
>
|
||||
<q-item-section class="q-pa-sm">
|
||||
<q-icon
|
||||
size="sm"
|
||||
color="primary"
|
||||
:name="item.image"
|
||||
class="q-mx-auto q-mb-sm"
|
||||
/>
|
||||
<q-item-label class="q-pa-none q-mx-auto" caption>{{
|
||||
item.title
|
||||
}}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from "vue";
|
||||
import { getImageURL } from "../../../utils/zincutils";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ChartSelection",
|
||||
props: ["selectedChartType"],
|
||||
emits: ["update:selectedChartType"],
|
||||
|
||||
setup() {
|
||||
const chartsArray = ref([
|
||||
{
|
||||
image: "img:" + getImageURL("images/dashboard/charts/area-graph.png"),
|
||||
title: "Area",
|
||||
id: "area",
|
||||
},
|
||||
{
|
||||
image: "img:" + getImageURL("images/dashboard/charts/bar-chart.png"),
|
||||
title: "Bar",
|
||||
id: "bar",
|
||||
},
|
||||
{
|
||||
image: "img:" + getImageURL("images/dashboard/charts/bar-graph.png"),
|
||||
title: "Horizontal",
|
||||
id: "h-bar",
|
||||
},
|
||||
{
|
||||
image: "img:" + getImageURL("images/dashboard/charts/line-chart.png"),
|
||||
title: "Line",
|
||||
id: "line",
|
||||
},
|
||||
{
|
||||
image: "img:" + getImageURL("images/dashboard/charts/pie-chart.png"),
|
||||
title: "Pie",
|
||||
id: "pie",
|
||||
},
|
||||
{
|
||||
image:
|
||||
"img:" + getImageURL("images/dashboard/charts/scatter-graph.png"),
|
||||
title: "Scatter",
|
||||
id: "scatter",
|
||||
},
|
||||
{
|
||||
image: "img:" + getImageURL("images/dashboard/charts/table.png"),
|
||||
title: "Table",
|
||||
id: "table",
|
||||
},
|
||||
]);
|
||||
|
||||
return {
|
||||
ChartsArray: chartsArray,
|
||||
};
|
||||
},
|
||||
components: {},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="col" style="height: 100%">
|
||||
<div class="q-pa-sm">
|
||||
<span class="text-weight-bold">Fields</span>
|
||||
</div>
|
||||
<div
|
||||
class="scroll"
|
||||
style="width: 100%;"
|
||||
v-if="true"
|
||||
>
|
||||
<index-list/>
|
||||
</div>
|
||||
<!-- <div v-else-if="dashboardPanelData.meta.stream.loading == true">
|
||||
<q-spinner-dots
|
||||
color="primary"
|
||||
size="40px"
|
||||
style="margin: 0 auto; display: block"
|
||||
/>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, ref } from "vue"
|
||||
import { useQuasar, date } from "quasar";
|
||||
import { useStore } from "vuex";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRouter } from "vue-router";
|
||||
import IndexList from "./IndexList.vue"
|
||||
import useDashboardPanelData from "../../../composables/useDashboardPanel";
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
name: "GetFields",
|
||||
props:[],
|
||||
components: {
|
||||
IndexList,
|
||||
},
|
||||
setup(){
|
||||
const store = useStore();
|
||||
const router = useRouter();
|
||||
const $q = useQuasar();
|
||||
const { t } = useI18n();
|
||||
const {dashboardPanelData} = useDashboardPanelData()
|
||||
|
||||
return {
|
||||
store,
|
||||
router,
|
||||
dashboardPanelData
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,515 @@
|
||||
<template>
|
||||
<div class="column index-menu">
|
||||
<div>
|
||||
<q-select
|
||||
v-model="dashboardPanelData.data.fields.stream"
|
||||
:label="
|
||||
dashboardPanelData.data.fields.stream ? '' : t('search.selectIndex')
|
||||
"
|
||||
:options="data.indexOptions"
|
||||
data-cy="index-dropdown"
|
||||
input-debounce="0"
|
||||
behavior="menu"
|
||||
filled
|
||||
borderless
|
||||
dense
|
||||
>
|
||||
<template #no-option>
|
||||
<q-item>
|
||||
<q-item-section> {{ t("search.noResult") }}</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
</div>
|
||||
<div class="index-table q-mt-xs">
|
||||
<q-table
|
||||
:columns="[
|
||||
{
|
||||
name: 'name',
|
||||
field: 'name',
|
||||
align: 'left',
|
||||
label: 'Field',
|
||||
sortable: true,
|
||||
},
|
||||
]"
|
||||
:rows="data.currentFieldsList"
|
||||
row-key="name"
|
||||
:filter="dashboardPanelData.meta.stream.filterField"
|
||||
:filter-method="filterFieldFn"
|
||||
:pagination="{ rowsPerPage: 10000 }"
|
||||
hide-header
|
||||
hide-bottom
|
||||
id="fieldList"
|
||||
>
|
||||
<template #body-cell-name="props">
|
||||
<q-tr :props="props">
|
||||
<q-td
|
||||
class="field_list"
|
||||
:class="
|
||||
dashboardPanelData.data.fields.x.find((it: any)=>it.column === props.row.name) ||
|
||||
dashboardPanelData.data.fields.y.find((it: any)=>it.column === props.row.name) ||
|
||||
dashboardPanelData.data.fields.filter.find((it: any)=>it.column === props.row.name)
|
||||
? 'selected'
|
||||
: ''
|
||||
"
|
||||
:props="props"
|
||||
>
|
||||
<div class="field_overlay">
|
||||
<div class="field_label">
|
||||
{{ props.row.name }}
|
||||
</div>
|
||||
<div class="field_icons">
|
||||
<q-btn
|
||||
color="white"
|
||||
padding="sm"
|
||||
text-color="black"
|
||||
@click="addXAxisItem(props.row.name)"
|
||||
>
|
||||
<div>+X</div>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
color="white"
|
||||
padding="sm"
|
||||
text-color="black"
|
||||
@click="addYAxisItem(props.row.name)"
|
||||
>
|
||||
<div>+Y</div>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
color="white"
|
||||
padding="sm"
|
||||
text-color="black"
|
||||
@click="addFilteredItem(props.row.name)"
|
||||
>
|
||||
<div>+F</div>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
<template #top-right>
|
||||
<q-input
|
||||
v-model="dashboardPanelData.meta.stream.filterField"
|
||||
data-cy="index-field-search-input"
|
||||
filled
|
||||
borderless
|
||||
dense
|
||||
clearable
|
||||
debounce="1"
|
||||
:placeholder="t('search.searchField')"
|
||||
>
|
||||
<template #prepend>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
</q-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
defineComponent,
|
||||
reactive,
|
||||
ref,
|
||||
onMounted,
|
||||
computed,
|
||||
watch,
|
||||
onActivated,
|
||||
} from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useStore } from "vuex";
|
||||
import { useQuasar } from "quasar";
|
||||
import { useRouter } from "vue-router";
|
||||
import useDashboardPanelData from "../../../composables/useDashboardPanel";
|
||||
import IndexService from "../../../services/index";
|
||||
import queryService from "../../../services/nativequery";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ComponentSearchIndexSelect",
|
||||
props: ["selectedXAxisValue", "selectedYAxisValue"],
|
||||
emits: ["update:selectedXAxisValue", "update:selectedYAxisValue"],
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const data = reactive({
|
||||
schemaList: [],
|
||||
indexOptions: [],
|
||||
currentFieldsList: [],
|
||||
});
|
||||
const $q = useQuasar();
|
||||
const { dashboardPanelData, addXAxisItem, addYAxisItem } =
|
||||
useDashboardPanelData();
|
||||
const x = ref();
|
||||
const y = ref();
|
||||
console.log("schemaList:", data.schemaList);
|
||||
// console.log("schemaList name:", schemaList.data );
|
||||
console.log("indexOptions:", data.indexOptions);
|
||||
|
||||
onActivated(() => {
|
||||
console.log("inside mounted");
|
||||
getStreamList();
|
||||
});
|
||||
|
||||
// update the selected stream fields list
|
||||
watch(
|
||||
() => [data.schemaList, dashboardPanelData.data.fields.stream],
|
||||
() => {
|
||||
const fields: any = data.schemaList.find(
|
||||
(it: any) => it.name == dashboardPanelData.data.fields.stream
|
||||
);
|
||||
dashboardPanelData.meta.stream.selectedStreamFields =
|
||||
fields?.schema || [];
|
||||
}
|
||||
);
|
||||
|
||||
// update the current list fields if any of the lists changes
|
||||
watch(
|
||||
() => [
|
||||
dashboardPanelData.meta.stream.selectedStreamFields,
|
||||
dashboardPanelData.meta.stream.customQueryFields,
|
||||
],
|
||||
() => {
|
||||
console.log("updated custom query fields or selected stream fields");
|
||||
|
||||
data.currentFieldsList = [];
|
||||
data.currentFieldsList = [
|
||||
...dashboardPanelData.meta.stream.customQueryFields,
|
||||
...dashboardPanelData.meta.stream.selectedStreamFields,
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
// get the stream list by making an API call
|
||||
const getStreamList = () => {
|
||||
IndexService.nameList(
|
||||
store.state.selectedOrganization.identifier,
|
||||
"",
|
||||
true
|
||||
).then((res) => {
|
||||
data.schemaList = res.data.list;
|
||||
dashboardPanelData.meta.stream.streamResults = res.data.list;
|
||||
data.indexOptions = res.data.list.map((data: any) => {
|
||||
return data.name;
|
||||
});
|
||||
|
||||
// set the first stream as the selected stream when the api loads the data
|
||||
if (
|
||||
!dashboardPanelData.data.fields.stream &&
|
||||
data.indexOptions.length > 0
|
||||
) {
|
||||
dashboardPanelData.data.fields.stream = data.indexOptions[0];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const filterFieldFn = (rows: any, terms: any) => {
|
||||
var filtered = [];
|
||||
if (terms != "") {
|
||||
terms = terms.toLowerCase();
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
if (rows[i]["name"].toLowerCase().includes(terms)) {
|
||||
filtered.push(rows[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
};
|
||||
|
||||
const addFilteredItem = (name: string) => {
|
||||
console.log("name=", name);
|
||||
if (!dashboardPanelData.data.fields.filter) {
|
||||
dashboardPanelData.data.fields.filter = [];
|
||||
}
|
||||
|
||||
if (
|
||||
!dashboardPanelData.data.fields.filter.find(
|
||||
(it: any) => it.column == name
|
||||
)
|
||||
) {
|
||||
console.log("data");
|
||||
|
||||
dashboardPanelData.data.fields.filter.push({
|
||||
type: "list",
|
||||
values: [],
|
||||
column: name,
|
||||
operator: null,
|
||||
value: null,
|
||||
});
|
||||
}
|
||||
|
||||
if (!dashboardPanelData.meta.filterValue) {
|
||||
dashboardPanelData.meta.filterValue = [];
|
||||
}
|
||||
|
||||
// dashboardPanelData.meta.filterValue.push({
|
||||
// column: name,
|
||||
// value: ["abc", "def"]
|
||||
// })
|
||||
console.log("filterValue array= ", dashboardPanelData.meta.filterValue);
|
||||
if (
|
||||
!dashboardPanelData.meta.filterValue.find(
|
||||
(it: any) => it.column == name
|
||||
)
|
||||
) {
|
||||
let queryData = "SELECT ";
|
||||
|
||||
// get unique value of the selected fields
|
||||
queryData += `${name} as value`;
|
||||
|
||||
//now add the selected stream
|
||||
queryData += ` FROM '${dashboardPanelData.data.fields.stream}'`;
|
||||
|
||||
console.log("queryData= ", queryData);
|
||||
// add group by statement
|
||||
queryData += ` GROUP BY value`;
|
||||
|
||||
const query = {
|
||||
query: { sql: queryData, sql_mode: "full" },
|
||||
};
|
||||
|
||||
queryService
|
||||
.runquery(query, store.state.selectedOrganization.identifier)
|
||||
.then((res) => {
|
||||
console.log(
|
||||
"-distinct vals--",
|
||||
res.data.hits.map((it: any) => it.value).filter((it: any) => it)
|
||||
);
|
||||
|
||||
dashboardPanelData.meta.filterValue.push({
|
||||
column: name,
|
||||
value: res.data.hits
|
||||
.map((it: any) => it.value)
|
||||
.filter((it: any) => it),
|
||||
});
|
||||
|
||||
// dashboardPanelData.data.fields.filter.find((it:any) => it.column == name).values = res.data.hits.map((it:any)=>it.value)
|
||||
|
||||
// $q.notify({
|
||||
// type: "positive",
|
||||
// message: "Query applied successfully.",
|
||||
// timeout: 5000,
|
||||
// });
|
||||
})
|
||||
.catch((error) => {
|
||||
$q.notify({
|
||||
type: "negative",
|
||||
message: "Something went wrong!",
|
||||
timeout: 5000,
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
t,
|
||||
store,
|
||||
router,
|
||||
filterFieldFn,
|
||||
addXAxisItem,
|
||||
addYAxisItem,
|
||||
addFilteredItem,
|
||||
data,
|
||||
getStreamList,
|
||||
dashboardPanelData,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.q-menu {
|
||||
box-shadow: 0px 3px 15px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(0.5rem);
|
||||
border-radius: 0px;
|
||||
|
||||
.q-virtual-scroll__content {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
.index-menu {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.q-field {
|
||||
&__control {
|
||||
height: 35px;
|
||||
padding: 0px 5px;
|
||||
min-height: auto !important;
|
||||
|
||||
&-container {
|
||||
padding-top: 0px !important;
|
||||
}
|
||||
}
|
||||
&__native :first-of-type {
|
||||
padding-top: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.q-select {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.index-table {
|
||||
width: 100%;
|
||||
// border: 1px solid rgba(0, 0, 0, 0.02);
|
||||
.q-table {
|
||||
display: block;
|
||||
}
|
||||
tr {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
tbody,
|
||||
tr,
|
||||
td {
|
||||
width: 100%;
|
||||
display: block;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.q-table__top {
|
||||
padding: 0px;
|
||||
}
|
||||
.q-table__control,
|
||||
label.q-field {
|
||||
width: 100%;
|
||||
}
|
||||
.q-table thead tr,
|
||||
.q-table tbody td {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.q-table__top {
|
||||
border-bottom: unset;
|
||||
}
|
||||
}
|
||||
.field-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.field_list {
|
||||
padding: 0px;
|
||||
margin-bottom: 0.125rem;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
cursor: default;
|
||||
|
||||
.field_overlay {
|
||||
justify-content: space-between;
|
||||
background-color: transparent;
|
||||
transition: all 0.3s ease;
|
||||
padding: 0px 10px;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
line-height: 2rem;
|
||||
overflow: hidden;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
border-radius: 0px;
|
||||
height: 25px;
|
||||
|
||||
.field_icons {
|
||||
padding: 0 0 0 0.25rem;
|
||||
transition: all 0.3s ease;
|
||||
background-color: white;
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
opacity: 0;
|
||||
right: 0;
|
||||
|
||||
.q-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.field_label {
|
||||
pointer-events: none;
|
||||
font-size: 0.825rem;
|
||||
position: relative;
|
||||
display: inline;
|
||||
z-index: 2;
|
||||
left: 0;
|
||||
// text-transform: capitalize;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected {
|
||||
.field_overlay {
|
||||
background-color: rgba(89, 96, 178, 0.3);
|
||||
|
||||
.field_icons {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.field_overlay {
|
||||
box-shadow: 0px 4px 15px rgba(0, 0, 0, 0.17);
|
||||
background-color: white;
|
||||
|
||||
.field_icons {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.field_overlay {
|
||||
box-shadow: 0px 4px 15px rgba(0, 0, 0, 0.17);
|
||||
|
||||
.field_icons {
|
||||
background-color: white;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.q-item {
|
||||
color: $dark-page;
|
||||
min-height: 1.3rem;
|
||||
padding: 5px 10px;
|
||||
|
||||
&__label {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
&.q-manual-focusable--focused > .q-focus-helper {
|
||||
background: none !important;
|
||||
opacity: 0.3 !important;
|
||||
}
|
||||
|
||||
&.q-manual-focusable--focused > .q-focus-helper,
|
||||
&--active {
|
||||
background-color: $selected-list-bg !important;
|
||||
}
|
||||
|
||||
&.q-manual-focusable--focused > .q-focus-helper,
|
||||
&:hover,
|
||||
&--active {
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
.q-field--dense .q-field__before,
|
||||
.q-field--dense .q-field__prepend {
|
||||
padding: 0px 0px 0px 0px;
|
||||
height: auto;
|
||||
line-height: auto;
|
||||
}
|
||||
.q-field__native,
|
||||
.q-field__input {
|
||||
padding: 0px 0px 0px 0px;
|
||||
}
|
||||
|
||||
.q-field--dense .q-field__label {
|
||||
top: 5px;
|
||||
}
|
||||
.q-field--dense .q-field__control,
|
||||
.q-field--dense .q-field__marginal {
|
||||
height: 34px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,701 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="q-pa-sm">
|
||||
<span class="text-weight-bold">Layout</span>
|
||||
</div>
|
||||
<q-separator />
|
||||
<div>
|
||||
<q-expansion-item
|
||||
default-opened
|
||||
dense
|
||||
:label="
|
||||
dashboardPanelData.data.type == 'table'
|
||||
? t(`First Column`)
|
||||
: dashboardPanelData.data.type == 'h-bar'
|
||||
? t(`Y-Axis (Columns)`)
|
||||
: t(`X-Axis (Row)`)
|
||||
"
|
||||
>
|
||||
<div class="column index-menu q-mb-md">
|
||||
<div class="index-table q-mt-xs">
|
||||
<q-table
|
||||
v-model:selected="dashboardPanelData.data.fields.x"
|
||||
:columns="[
|
||||
{
|
||||
name: 'column',
|
||||
field: 'column',
|
||||
align: 'left',
|
||||
label: 'Field',
|
||||
},
|
||||
]"
|
||||
:rows="dashboardPanelData.data.fields.x"
|
||||
row-key="name"
|
||||
class="field-table"
|
||||
id="fieldList"
|
||||
:hide-pagination="true"
|
||||
hide-header
|
||||
hide-bottom
|
||||
>
|
||||
<template #body-cell-column="props">
|
||||
<q-tr :props="props">
|
||||
<q-td :props="props" class="field_list">
|
||||
<div class="field_overlay">
|
||||
<div class="field_label">
|
||||
{{ props.row.column }}
|
||||
</div>
|
||||
<div class="field_icons">
|
||||
<q-icon
|
||||
:name="
|
||||
'img:' +
|
||||
getImageURL('images/layout/remove_icon.svg')
|
||||
"
|
||||
size="1rem"
|
||||
@click="removeXAxisItem(props.row.column)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
</q-table>
|
||||
</div>
|
||||
<div
|
||||
class="text-caption text-weight-bold text-center q-ma-sm"
|
||||
v-if="dashboardPanelData.data.fields.x.length < 1"
|
||||
>
|
||||
Please add a field from the list
|
||||
</div>
|
||||
</div>
|
||||
</q-expansion-item>
|
||||
<q-separator />
|
||||
<q-expansion-item
|
||||
dense
|
||||
default-opened
|
||||
:label="
|
||||
dashboardPanelData.data.type == 'table'
|
||||
? t(`Other Columns`)
|
||||
: dashboardPanelData.data.type == 'h-bar'
|
||||
? t(`X-Axis (Row)`)
|
||||
: t(`Y-Axis (Columns)`)
|
||||
"
|
||||
>
|
||||
<div class="column index-menu q-mb-lg">
|
||||
<div class="index-table q-mt-xs">
|
||||
<q-table
|
||||
v-model:selected="dashboardPanelData.data.fields.y"
|
||||
:columns="[
|
||||
{
|
||||
name: 'column',
|
||||
field: 'column',
|
||||
align: 'left',
|
||||
label: 'Field',
|
||||
},
|
||||
]"
|
||||
:rows="dashboardPanelData.data.fields.y"
|
||||
row-key="column"
|
||||
class="field-table"
|
||||
id="fieldList"
|
||||
virtual-scroll
|
||||
v-model:pagination="pagination"
|
||||
:rows-per-page-options="[0]"
|
||||
:virtual-scroll-sticky-size-start="48"
|
||||
:hide-pagination="true"
|
||||
hide-header
|
||||
hide-bottom
|
||||
>
|
||||
<template #body-cell-column="props">
|
||||
<q-tr :props="props">
|
||||
<q-td :props="props" class="field_list">
|
||||
<div class="field_overlay" :title="props.row.column">
|
||||
<div class="field_label">
|
||||
{{ props.row.column }}
|
||||
<!-- <div>
|
||||
<q-expansion-item
|
||||
expand-separator
|
||||
icon="perm_identity"
|
||||
label=""
|
||||
caption=""
|
||||
>
|
||||
<div class="color-input-wrapper">
|
||||
<input type="color" v-model="dashboardPanelData.data.fields.y.color">
|
||||
</div>
|
||||
<div>
|
||||
<q-select
|
||||
v-model="dashboardPanelData.data.fields.y.aggregationFunction"
|
||||
:options="triggerOperators"
|
||||
dense
|
||||
filled
|
||||
:label="t('Aggregation')"
|
||||
></q-select>
|
||||
</div>
|
||||
</q-expansion-item>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<div class="field_icons">
|
||||
<!-- TODO: aggregation and color picker and label fields -->
|
||||
|
||||
<div>
|
||||
<q-btn
|
||||
size="xs"
|
||||
color="red"
|
||||
round
|
||||
dense
|
||||
@click="props.expand = !props.expand"
|
||||
:icon="props.expand ? 'remove' : 'add'"
|
||||
/>
|
||||
<q-icon
|
||||
:name="
|
||||
'img:' +
|
||||
getImageURL('images/layout/remove_icon.svg')
|
||||
"
|
||||
size="1rem"
|
||||
@click="removeYAxisItem(props.row.column)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
<q-tr
|
||||
v-show="props.expand"
|
||||
:props="props"
|
||||
style="height: min-content"
|
||||
>
|
||||
<q-td colspan="100%">
|
||||
<div>
|
||||
<div class="flex items-center">
|
||||
<div class="q-pa-xs" style="width: 180px">
|
||||
<q-select
|
||||
v-model="
|
||||
dashboardPanelData.data.fields.y[props.pageIndex]
|
||||
.aggregationFunction
|
||||
"
|
||||
:options="triggerOperators"
|
||||
dense
|
||||
filled
|
||||
label="Aggregation"
|
||||
></q-select>
|
||||
</div>
|
||||
<div class="color-input-wrapper">
|
||||
<input
|
||||
type="color"
|
||||
v-model="
|
||||
dashboardPanelData.data.fields.y[props.pageIndex]
|
||||
.color
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<q-input
|
||||
dense
|
||||
filled
|
||||
label="Label"
|
||||
v-model="
|
||||
dashboardPanelData.data.fields.y[props.pageIndex]
|
||||
.label
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
</q-table>
|
||||
</div>
|
||||
<div
|
||||
class="text-caption text-weight-bold text-center q-ma-sm"
|
||||
v-if="dashboardPanelData.data.fields.y.length < 1"
|
||||
>
|
||||
Please add a field from the list
|
||||
</div>
|
||||
</div>
|
||||
</q-expansion-item>
|
||||
<q-separator />
|
||||
<q-expansion-item default-opened dense :label="t(`Config`)">
|
||||
<div>
|
||||
<q-toggle
|
||||
v-model="dashboardPanelData.data.config.show_legends"
|
||||
label="Show Legends"
|
||||
/>
|
||||
<q-form ref="" class="q-pa-sm">
|
||||
<q-input
|
||||
v-model="dashboardPanelData.data.config.title"
|
||||
:label="t('panel.name')"
|
||||
class="q-py-md showLabelOnTop"
|
||||
stack-label
|
||||
filled
|
||||
dense
|
||||
/>
|
||||
<q-input
|
||||
v-model="dashboardPanelData.data.config.description"
|
||||
type="textarea"
|
||||
:label="t('panel.typeDesc')"
|
||||
class="q-py-md showLabelOnTop"
|
||||
stack-label
|
||||
filled
|
||||
dense
|
||||
/>
|
||||
</q-form>
|
||||
</div>
|
||||
</q-expansion-item>
|
||||
</div>
|
||||
<q-separator />
|
||||
<q-expansion-item default-opened dense :label="t(`Filters`)">
|
||||
<div class="column index-menu q-mb-lg">
|
||||
<div class="index-table q-mt-xs">
|
||||
<q-table
|
||||
v-model:selected="dashboardPanelData.data.fields.filter"
|
||||
:columns="[
|
||||
{
|
||||
name: 'column',
|
||||
field: 'column',
|
||||
align: 'left',
|
||||
label: 'Filter',
|
||||
},
|
||||
]"
|
||||
:rows="dashboardPanelData.data.fields.filter"
|
||||
row-key="column"
|
||||
class="field-table"
|
||||
id="fieldList"
|
||||
virtual-scroll
|
||||
v-model:pagination="pagination"
|
||||
:rows-per-page-options="[0]"
|
||||
:virtual-scroll-sticky-size-start="48"
|
||||
:hide-pagination="true"
|
||||
hide-header
|
||||
hide-bottom
|
||||
>
|
||||
<template #body-cell-column="props">
|
||||
<q-tr :props="props">
|
||||
<q-td :props="props" class="field_list">
|
||||
<div class="field_overlay" :title="props.row.column">
|
||||
<div class="field_label">
|
||||
{{ props.row.column }}
|
||||
</div>
|
||||
<div class="field_icons">
|
||||
<div>
|
||||
<q-btn
|
||||
size="xs"
|
||||
color="red"
|
||||
round
|
||||
dense
|
||||
@click="props.expand = !props.expand"
|
||||
:icon="props.expand ? 'remove' : 'add'"
|
||||
/>
|
||||
<q-icon
|
||||
:name="
|
||||
'img:' +
|
||||
getImageURL('images/layout/remove_icon.svg')
|
||||
"
|
||||
size="1rem"
|
||||
@click="removeFilterItem(props.row.column)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
<q-tr
|
||||
v-show="props.expand"
|
||||
:props="props"
|
||||
style="height: min-content"
|
||||
>
|
||||
<q-td colspan="100%">
|
||||
<div class="q-pa-xs">
|
||||
<div class="q-gutter-xs">
|
||||
<q-tabs
|
||||
v-model="
|
||||
dashboardPanelData.data.fields.filter[props.pageIndex]
|
||||
.type
|
||||
"
|
||||
>
|
||||
<q-tab
|
||||
name="list"
|
||||
label="List"
|
||||
style="width: auto"
|
||||
></q-tab>
|
||||
<q-tab
|
||||
name="condition"
|
||||
label="Condition"
|
||||
style="width: auto"
|
||||
></q-tab>
|
||||
</q-tabs>
|
||||
<q-separator></q-separator>
|
||||
<q-tab-panels
|
||||
dense
|
||||
v-model="
|
||||
dashboardPanelData.data.fields.filter[props.pageIndex]
|
||||
.type
|
||||
"
|
||||
animated
|
||||
style="background-color: #f5f5f5"
|
||||
>
|
||||
<q-tab-panel dense name="condition">
|
||||
<div class="flex justify-between">
|
||||
<q-select
|
||||
dense
|
||||
filled
|
||||
v-model="
|
||||
dashboardPanelData.data.fields.filter[
|
||||
props.pageIndex
|
||||
].operator
|
||||
"
|
||||
:options="options"
|
||||
label="Operator"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<q-input
|
||||
dense
|
||||
filled
|
||||
v-model="
|
||||
dashboardPanelData.data.fields.filter[
|
||||
props.pageIndex
|
||||
].value
|
||||
"
|
||||
label="Value"
|
||||
style="width: 100%; margin-top: 5px"
|
||||
/>
|
||||
</div>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel dense name="list">
|
||||
<q-select
|
||||
dense
|
||||
filled
|
||||
v-model="
|
||||
dashboardPanelData.data.fields.filter[
|
||||
props.pageIndex
|
||||
].values
|
||||
"
|
||||
:options="dashboardPanelData.meta.filterValue.find((it: any)=>it.column == props.row.column)?.value"
|
||||
label="Select Filter"
|
||||
multiple
|
||||
emit-value
|
||||
map-options
|
||||
>
|
||||
<template v-slot:selected>
|
||||
{{
|
||||
dashboardPanelData.data.fields.filter[
|
||||
props.pageIndex
|
||||
].values[0]?.length > 15
|
||||
? dashboardPanelData.data.fields.filter[
|
||||
props.pageIndex
|
||||
].values[0]?.substring(0, 15) + "..."
|
||||
: dashboardPanelData.data.fields.filter[
|
||||
props.pageIndex
|
||||
].values[0]
|
||||
}}
|
||||
|
||||
{{
|
||||
dashboardPanelData.data.fields.filter[
|
||||
props.pageIndex
|
||||
].values?.length > 1
|
||||
? " +" +
|
||||
(dashboardPanelData.data.fields.filter[
|
||||
props.pageIndex
|
||||
].values?.length -
|
||||
1)
|
||||
: ""
|
||||
}}
|
||||
</template>
|
||||
<template
|
||||
v-slot:option="{
|
||||
itemProps,
|
||||
opt,
|
||||
selected,
|
||||
toggleOption,
|
||||
}"
|
||||
>
|
||||
<q-item v-bind="itemProps">
|
||||
<q-item-section side>
|
||||
<q-checkbox
|
||||
dense
|
||||
:model-value="selected"
|
||||
@update:model-value="toggleOption(opt)"
|
||||
></q-checkbox>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<div v-html="opt"></div>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
</q-tab-panel>
|
||||
</q-tab-panels>
|
||||
</div>
|
||||
</div>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
</q-table>
|
||||
</div>
|
||||
<div
|
||||
class="text-caption text-weight-bold text-center q-ma-sm"
|
||||
v-if="dashboardPanelData.meta.filterValue.length < 1"
|
||||
>
|
||||
Please add a field from the list
|
||||
</div>
|
||||
</div>
|
||||
<div></div>
|
||||
</q-expansion-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, reactive } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useQuasar } from "quasar";
|
||||
import useDashboardPanelData from "../../../composables/useDashboardPanel";
|
||||
import { getImageURL } from "../../../utils/zincutils";
|
||||
|
||||
export default defineComponent({
|
||||
name: "dashboard-layout",
|
||||
components: {},
|
||||
setup() {
|
||||
const showXAxis = ref(true);
|
||||
const panelName = ref("");
|
||||
const panelDesc = ref("");
|
||||
const { t } = useI18n();
|
||||
const $q = useQuasar();
|
||||
const {
|
||||
dashboardPanelData,
|
||||
removeXAxisItem,
|
||||
removeYAxisItem,
|
||||
removeFilterItem,
|
||||
} = useDashboardPanelData();
|
||||
const triggerOperators: any = ref(["count", "sum", "avg", "min", "max"]);
|
||||
|
||||
return {
|
||||
showXAxis,
|
||||
t,
|
||||
panelName,
|
||||
panelDesc,
|
||||
dashboardPanelData,
|
||||
removeXAxisItem,
|
||||
removeYAxisItem,
|
||||
triggerOperators,
|
||||
removeFilterItem,
|
||||
pagination: ref({
|
||||
rowsPerPage: 0,
|
||||
}),
|
||||
model: ref([]),
|
||||
tab: ref("General"),
|
||||
options: ["=", "<>", ">=", "<=", ">", "<", "Contains", "Not Contains"],
|
||||
getImageURL,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.color-input-wrapper {
|
||||
height: 1.5em;
|
||||
width: 1.5em;
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
.color-input-wrapper input[type="color"] {
|
||||
position: absolute;
|
||||
height: 4em;
|
||||
width: 4em;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
overflow: hidden;
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.q-menu {
|
||||
box-shadow: 0px 3px 15px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(0.5rem);
|
||||
border-radius: 0px;
|
||||
|
||||
.q-virtual-scroll__content {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
.index-menu {
|
||||
width: 100%;
|
||||
|
||||
.q-field {
|
||||
&__control {
|
||||
height: 35px;
|
||||
padding: 0px 5px;
|
||||
min-height: auto !important;
|
||||
|
||||
&-container {
|
||||
padding-top: 0px !important;
|
||||
}
|
||||
}
|
||||
&__native :first-of-type {
|
||||
padding-top: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.q-select {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.index-table {
|
||||
width: 100%;
|
||||
// border: 1px solid rgba(0, 0, 0, 0.02);
|
||||
.q-table {
|
||||
display: block;
|
||||
}
|
||||
tr {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
tbody,
|
||||
tr,
|
||||
td {
|
||||
width: 100%;
|
||||
display: block;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.q-table__top {
|
||||
padding: 0px;
|
||||
}
|
||||
.q-table__control,
|
||||
label.q-field {
|
||||
width: 100%;
|
||||
}
|
||||
.q-table thead tr,
|
||||
.q-table tbody td {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.q-table__top {
|
||||
border-bottom: unset;
|
||||
}
|
||||
}
|
||||
.field-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.field_list {
|
||||
padding: 0px;
|
||||
margin-bottom: 0.125rem;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
cursor: default;
|
||||
|
||||
.field_overlay {
|
||||
justify-content: space-between;
|
||||
background-color: transparent;
|
||||
transition: all 0.3s ease;
|
||||
padding: 0px 10px;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
line-height: 2rem;
|
||||
overflow: hidden;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
border-radius: 0px;
|
||||
height: 25px;
|
||||
|
||||
.field_icons {
|
||||
padding: 0 0.625rem 0 0.25rem;
|
||||
transition: all 0.3s ease;
|
||||
background-color: white;
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
opacity: 0;
|
||||
right: 0;
|
||||
|
||||
.q-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.field_label {
|
||||
pointer-events: none;
|
||||
font-size: 0.825rem;
|
||||
position: relative;
|
||||
display: inline;
|
||||
z-index: 2;
|
||||
left: 0;
|
||||
// text-transform: capitalize;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected {
|
||||
.field_overlay {
|
||||
background-color: rgba(89, 96, 178, 0.3);
|
||||
|
||||
.field_icons {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.field_overlay {
|
||||
box-shadow: 0px 4px 15px rgba(0, 0, 0, 0.17);
|
||||
background-color: white;
|
||||
|
||||
.field_icons {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.field_overlay {
|
||||
box-shadow: 0px 4px 15px rgba(0, 0, 0, 0.17);
|
||||
|
||||
.field_icons {
|
||||
background-color: white;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.q-item {
|
||||
color: $dark-page;
|
||||
min-height: 1.3rem;
|
||||
padding: 5px 10px;
|
||||
|
||||
&__label {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
&.q-manual-focusable--focused > .q-focus-helper {
|
||||
background: none !important;
|
||||
opacity: 0.3 !important;
|
||||
}
|
||||
|
||||
&.q-manual-focusable--focused > .q-focus-helper,
|
||||
&--active {
|
||||
background-color: $selected-list-bg !important;
|
||||
}
|
||||
|
||||
&.q-manual-focusable--focused > .q-focus-helper,
|
||||
&:hover,
|
||||
&--active {
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
.q-field--dense .q-field__before,
|
||||
.q-field--dense .q-field__prepend {
|
||||
padding: 0px 0px 0px 0px;
|
||||
height: auto;
|
||||
line-height: auto;
|
||||
}
|
||||
.q-field__native,
|
||||
.q-field__input {
|
||||
padding: 0px 0px 0px 0px;
|
||||
}
|
||||
|
||||
.q-field--dense .q-field__label {
|
||||
top: 5px;
|
||||
}
|
||||
.q-field--dense .q-field__control,
|
||||
.q-field--dense .q-field__marginal {
|
||||
height: 34px;
|
||||
}
|
||||
</style>
|
||||
@@ -20,7 +20,7 @@
|
||||
<script lang="ts">
|
||||
import Plotly from "plotly.js";
|
||||
|
||||
import { defineComponent, onMounted, ref, onUpdated } from "vue";
|
||||
import { defineComponent, onMounted, ref, onUpdated, nextTick } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ReactiveChart",
|
||||
@@ -34,6 +34,7 @@ export default defineComponent({
|
||||
y: [],
|
||||
unparsed_x: [],
|
||||
name: "barchart",
|
||||
// showlegend: true,
|
||||
type: "bar",
|
||||
marker: {
|
||||
color: "#5960b2",
|
||||
@@ -51,25 +52,23 @@ export default defineComponent({
|
||||
font: { size: 12 },
|
||||
autosize: true,
|
||||
legend: {
|
||||
bgcolor: "red",
|
||||
},
|
||||
margin: {
|
||||
l: 32,
|
||||
r: 16,
|
||||
t: 38,
|
||||
b: 32,
|
||||
bgcolor: "#f7f7f7",
|
||||
},
|
||||
xaxis: {
|
||||
ticklen: 5,
|
||||
nticks: 15,
|
||||
tickangle: -20,
|
||||
automargin: true,
|
||||
},
|
||||
yaxis: {
|
||||
automargin: true,
|
||||
},
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick();
|
||||
await Plotly.newPlot(plotref.value, [trace], layout, {
|
||||
responsive: true,
|
||||
displaylogo: false,
|
||||
displayModeBar: false,
|
||||
displayModeBar: true,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -80,6 +79,16 @@ export default defineComponent({
|
||||
}
|
||||
});
|
||||
|
||||
// wrap the text for long x axis names
|
||||
const addBreaksAtLength = 12;
|
||||
const textwrapper = function (traces: any) {
|
||||
traces = traces.map((text: any) => {
|
||||
let rxp = new RegExp(".{1," + addBreaksAtLength + "}", "g");
|
||||
return text.match(rxp).join("<br>");
|
||||
});
|
||||
return traces;
|
||||
};
|
||||
|
||||
onUpdated(async () => {
|
||||
if (props.data) {
|
||||
chartID.value = "chart_" + props.data.id;
|
||||
@@ -91,7 +100,7 @@ export default defineComponent({
|
||||
y: any,
|
||||
params: { title: any; unparsed_x_data: any }
|
||||
) => {
|
||||
trace.x = x;
|
||||
trace.x = textwrapper(x);
|
||||
trace.y = y;
|
||||
// layout.title.text = params.title;
|
||||
trace.unparsed_x = params.unparsed_x_data;
|
||||
@@ -99,43 +108,16 @@ export default defineComponent({
|
||||
forceReLayout();
|
||||
};
|
||||
// created force relayout function to avoid infinite loop
|
||||
const forceReLayout = (flag = true) => {
|
||||
const forceReLayout = async (flag = true) => {
|
||||
zoomFlag.value = flag;
|
||||
const update: any = {
|
||||
"xaxis.autorange": true,
|
||||
"yaxis.autorange": true,
|
||||
};
|
||||
await nextTick();
|
||||
Plotly.relayout(plotref.value, update);
|
||||
};
|
||||
|
||||
const onPlotZoom = () => {
|
||||
if (
|
||||
plotref.value.layout.xaxis.range.length == 2 &&
|
||||
zoomFlag.value == false
|
||||
) {
|
||||
const start = Math.round(plotref.value.layout.xaxis.range[0]);
|
||||
const end = Math.round(plotref.value.layout.xaxis.range[1]);
|
||||
|
||||
if (
|
||||
start >= 0 &&
|
||||
end >= 0 &&
|
||||
trace.unparsed_x[start] != undefined &&
|
||||
trace.unparsed_x[end] != undefined
|
||||
) {
|
||||
zoomFlag.value = true;
|
||||
let start_d = new Date(Date.parse(trace.unparsed_x[start] + "Z"));
|
||||
let end_d = new Date(Date.parse(trace.unparsed_x[end] + "Z"));
|
||||
|
||||
emit("updated:chart", {
|
||||
start: start_d.toLocaleString("sv-SE"),
|
||||
end: end_d.toLocaleString("sv-SE"),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
zoomFlag.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
plotref,
|
||||
reDraw,
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<script lang="ts">
|
||||
import Plotly from "plotly.js";
|
||||
|
||||
import { defineComponent, onMounted, ref, onUpdated } from "vue";
|
||||
import { defineComponent, onMounted, ref, onUpdated, nextTick } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ReactiveLineChart",
|
||||
@@ -34,6 +34,7 @@ export default defineComponent({
|
||||
y: [],
|
||||
unparsed_x: [],
|
||||
mode: "lines",
|
||||
showlegend: true,
|
||||
name: "Markers",
|
||||
};
|
||||
const chartID = ref("");
|
||||
@@ -47,15 +48,22 @@ export default defineComponent({
|
||||
},
|
||||
font: { size: 12 },
|
||||
autosize: true,
|
||||
xaxis: {
|
||||
automargin: true,
|
||||
tickangle: -20,
|
||||
},
|
||||
yaxis: {
|
||||
tickangle: 0,
|
||||
},
|
||||
legend: {
|
||||
bgcolor: "red",
|
||||
bgcolor: "#f7f7f7",
|
||||
},
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await Plotly.newPlot(plotref.value, [trace], layout, {
|
||||
responsive: true,
|
||||
displayModeBar: false,
|
||||
displayModeBar: true,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -66,12 +74,22 @@ export default defineComponent({
|
||||
}
|
||||
});
|
||||
|
||||
// wrap the text for long x axis names
|
||||
const addBreaksAtLength = 12;
|
||||
const textwrapper = function (traces: any) {
|
||||
traces = traces.map((text: any) => {
|
||||
let rxp = new RegExp(".{1," + addBreaksAtLength + "}", "g");
|
||||
return text.match(rxp).join("<br>");
|
||||
});
|
||||
return traces;
|
||||
};
|
||||
|
||||
const reDraw: any = (
|
||||
x: any,
|
||||
y: any,
|
||||
params: { title: any; unparsed_x_data: any }
|
||||
) => {
|
||||
trace.x = x;
|
||||
trace.x = textwrapper(x);
|
||||
trace.y = y;
|
||||
// layout.title.text = params.title;
|
||||
trace.unparsed_x = params.unparsed_x_data;
|
||||
@@ -80,13 +98,14 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
// created force relayout function to avoid infinite loop
|
||||
const forceReLayout = (flag = true) => {
|
||||
const forceReLayout = async (flag = true) => {
|
||||
zoomFlag.value = flag;
|
||||
|
||||
const update: any = {
|
||||
"xaxis.autorange": true,
|
||||
"yaxis.autorange": true,
|
||||
};
|
||||
await nextTick();
|
||||
Plotly.relayout(plotref.value, update);
|
||||
};
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
<template>
|
||||
<div class="plotlycontainer">
|
||||
<div
|
||||
class="q-pa-sm q-gutter-sm innerPlotlycontainer"
|
||||
v-if="getVizualizationSelector() === 'BarChart'"
|
||||
>
|
||||
<!-- inside barchart-- {{ plotData }}-- -->
|
||||
@@ -28,7 +27,6 @@
|
||||
<ReactiveBarChart :data="plotData"></ReactiveBarChart>
|
||||
</div>
|
||||
<div
|
||||
class="q-pa-sm q-gutter-sm innerPlotlycontainer"
|
||||
v-else-if="getVizualizationSelector() === 'TimeSeries'"
|
||||
>
|
||||
<PanelHeader
|
||||
@@ -39,7 +37,6 @@
|
||||
<ReactiveLineChart :data="plotData"></ReactiveLineChart>
|
||||
</div>
|
||||
<div
|
||||
class="q-pa-sm col-4 q-gutter-sm innerPlotlycontainer"
|
||||
:id="panelDataElement.id"
|
||||
v-else-if="getVizualizationSelector() === 'Table'"
|
||||
>
|
||||
@@ -51,7 +48,6 @@
|
||||
<ReactiveTableChart ref="plotChart" :data="plotData"></ReactiveTableChart>
|
||||
</div>
|
||||
<div
|
||||
class="q-pa-sm q-gutter-sm innerPlotlycontainer"
|
||||
v-else-if="getVizualizationSelector() === 'Stat'"
|
||||
>
|
||||
<PanelHeader
|
||||
@@ -59,7 +55,7 @@
|
||||
:dashboardId="getDashboardId()"
|
||||
@clicked="onClickChild"
|
||||
/>
|
||||
<ReactiveTableChart ref="plotChart" :data="plotData" />
|
||||
<ReactiveTableChart ref="plotChart" :data="plotData"/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<h2>{{ getVizualizationSelector() }}</h2>
|
||||
@@ -81,7 +77,7 @@ import * as Plotly from "plotly.js";
|
||||
import { computed, defineComponent, onMounted, onUpdated, ref } from "vue";
|
||||
import { toRaw, unref, watch } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { modifySQLQuery } from "../../../utils/commons";
|
||||
import {modifySQLQuery} from '../../../utils/commons'
|
||||
|
||||
export default defineComponent({
|
||||
name: "PanelContainer",
|
||||
@@ -108,14 +104,14 @@ export default defineComponent({
|
||||
|
||||
watch(selectedTimeObj, () => {
|
||||
if (toRaw(unref(selectedTimeObj))) {
|
||||
renderPanel(props.panelDataElement, false);
|
||||
renderPanel(props.panelDataElement, false)
|
||||
}
|
||||
});
|
||||
|
||||
const renderPanel = async (panelDataElement: any, onMounted: boolean) => {
|
||||
const chartParams = {
|
||||
title: "Found " + "2" + " hits in " + "10" + " ms",
|
||||
};
|
||||
};
|
||||
const sqlQueryModified = modifySQLQuery(
|
||||
toRaw(unref(selectedTimeObj)),
|
||||
panelDataElement.query[0]
|
||||
@@ -124,13 +120,19 @@ export default defineComponent({
|
||||
query: { sql: sqlQueryModified, sql_mode: "full" },
|
||||
};
|
||||
|
||||
console.log("query===",query);
|
||||
|
||||
const panelData = toRaw(store.state.currentPanelsData);
|
||||
console.log("panelData====",panelData);
|
||||
|
||||
const existingPanel = panelData.find(
|
||||
(panel: any) => panel.id == panelDataElement.id
|
||||
(panel:any) => panel.id == panelDataElement.id
|
||||
);
|
||||
console.log("existingPanel---",existingPanel);
|
||||
|
||||
|
||||
if (existingPanel && onMounted) {
|
||||
plotData.value = existingPanel;
|
||||
plotData.value = existingPanel;
|
||||
} else {
|
||||
await queryService
|
||||
.runquery(query, store.state.selectedOrganization.identifier)
|
||||
@@ -155,14 +157,12 @@ export default defineComponent({
|
||||
data_vals.map((row) => row[colIndex])
|
||||
);
|
||||
|
||||
const styledHeader = header_vals.map(
|
||||
(header) => "<b>" + header + "</b>"
|
||||
);
|
||||
const styledHeader = header_vals.map((header) => "<b>" + header + "</b>");
|
||||
const columnColor = output[0].map((col: any, index: any) => {
|
||||
if (index % 2 == 0) {
|
||||
return "#FFF";
|
||||
} else {
|
||||
return "#C9CCE6";
|
||||
return "#f7f7f7";
|
||||
}
|
||||
});
|
||||
|
||||
@@ -172,18 +172,18 @@ export default defineComponent({
|
||||
header: {
|
||||
values: styledHeader,
|
||||
align: "center",
|
||||
height: 30,
|
||||
line: { width: 1, color: "black" },
|
||||
fill: { color: "#7A80C2" },
|
||||
font: { family: "Arial", size: 14, color: "white" },
|
||||
height: 28,
|
||||
line: { width: 1, color: "#91909091" },
|
||||
fill: { color: "#B1B5E5" },
|
||||
font: { family: "Nunito Sans", size: 12, color: "black" },
|
||||
},
|
||||
cells: {
|
||||
values: output,
|
||||
align: "center",
|
||||
height: 25,
|
||||
line: { color: "black", width: 1 },
|
||||
height: 28,
|
||||
line: { color: "#D4D4D4", width: 1 },
|
||||
fill: { color: [columnColor] },
|
||||
font: { family: "Arial", size: 11, color: ["black"] },
|
||||
font: { family: "Nunito Sans", size: 12, color: ["black"] },
|
||||
},
|
||||
};
|
||||
|
||||
@@ -263,7 +263,4 @@ export default defineComponent({
|
||||
.plotlycontainer {
|
||||
height: 100%;
|
||||
}
|
||||
.innerPlotlycontainer {
|
||||
height: 90%;
|
||||
}
|
||||
</style>
|
||||
@@ -15,32 +15,22 @@
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<q-toolbar class="col-5 bg-indigo-1">
|
||||
<q-bar class="bg-white">
|
||||
<!-- <q-separator dark vertical inset /> -->
|
||||
<q-toolbar-title>
|
||||
<div class="text-subtitle1">
|
||||
{{ renderTitle }}
|
||||
</div>
|
||||
</q-toolbar-title>
|
||||
<q-separator vertical inset />
|
||||
<div class="">
|
||||
{{ renderTitle }}
|
||||
</div>
|
||||
<q-space />
|
||||
<!-- <q-toolbar-title>{{ panelDataElementObject.title }}</q-toolbar-title>
|
||||
<q-separator dark vertical inset /> -->
|
||||
<q-btn-dropdown flat label="" no-caps>
|
||||
<q-list>
|
||||
<q-item
|
||||
clickable
|
||||
v-close-popup
|
||||
@click="onPanelModifyClick('EditPanel')"
|
||||
>
|
||||
<q-btn-dropdown dense flat label="" no-caps >
|
||||
<q-list dense>
|
||||
<q-item clickable v-close-popup @click="onPanelModifyClick('EditPanel')">
|
||||
<q-item-section>
|
||||
<q-item-label class="q-pa-sm">Edit Panel</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item
|
||||
clickable
|
||||
v-close-popup
|
||||
@click="onPanelModifyClick('DeletePanel')"
|
||||
>
|
||||
<q-item clickable v-close-popup @click="onPanelModifyClick('DeletePanel')">
|
||||
<q-item-section>
|
||||
<q-item-label class="q-pa-sm">Delete Panel</q-item-label>
|
||||
</q-item-section>
|
||||
@@ -57,64 +47,69 @@
|
||||
</q-item> -->
|
||||
</q-list>
|
||||
</q-btn-dropdown>
|
||||
</q-toolbar>
|
||||
</q-bar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { log } from "console";
|
||||
import { computed, defineComponent } from "vue";
|
||||
import { toRaw } from "vue";
|
||||
import { toRaw } from 'vue';
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
name: "PanelHeader",
|
||||
props: ["panelDataElement", "dashboardId"],
|
||||
panelData: [],
|
||||
|
||||
setup(props, { emit }) {
|
||||
const panelDataElementObject = toRaw(props.panelDataElement);
|
||||
const dashboardId = props.dashboardId;
|
||||
const panelDataElementObject = toRaw(props.panelDataElement)
|
||||
const dashboardId = props.dashboardId
|
||||
const router = useRouter();
|
||||
|
||||
const renderTitle = computed(() => {
|
||||
return props.panelDataElement.title;
|
||||
});
|
||||
return props.panelDataElement.title
|
||||
})
|
||||
const addNewPanel = () => {
|
||||
// return router.push({
|
||||
// path: "/addPanel",
|
||||
// query: { dashboard: String(dashboardId), panelId:panelDataElementObject.id },
|
||||
// });
|
||||
return router.push({
|
||||
path: "/editPanel",
|
||||
query: {
|
||||
dashboard: String(dashboardId),
|
||||
panelId: panelDataElementObject.id,
|
||||
},
|
||||
query: { dashboard: String(dashboardId), panelId:panelDataElementObject.id },
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const deletePanel = () => {
|
||||
// return router.push({
|
||||
// path: "/viewDashboard",
|
||||
// query: { dashboard: String(dashboardId), panelId:panelDataElementObject.id },
|
||||
// });
|
||||
};
|
||||
return router.push({
|
||||
path: "/viewDashboard",
|
||||
query: { dashboard: String(dashboardId), panelId:panelDataElementObject.id },
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
panelDataElementObject,
|
||||
dashboardId,
|
||||
addNewPanel,
|
||||
deletePanel,
|
||||
renderTitle,
|
||||
renderTitle
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
}, methods: {
|
||||
|
||||
onPanelModifyClick(evt: any) {
|
||||
if (evt == "EditPanel") {
|
||||
this.addNewPanel();
|
||||
} else if (evt == "DeletePanel") {
|
||||
//this.deletePanel()
|
||||
this.$emit("clicked", this.panelDataElementObject);
|
||||
} else {
|
||||
console.log(evt);
|
||||
if(evt == 'EditPanel'){this.addNewPanel()}
|
||||
else if(evt == 'DeletePanel') {//this.deletePanel()
|
||||
this.$emit('clicked', this.panelDataElementObject)
|
||||
}
|
||||
else {console.log(evt)}
|
||||
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<script lang="ts">
|
||||
import Plotly from "plotly.js";
|
||||
|
||||
import { defineComponent, onMounted, ref, onUpdated } from "vue";
|
||||
import { defineComponent, onMounted, ref, onUpdated, nextTick } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ReactiveTableChart",
|
||||
@@ -55,7 +55,7 @@ export default defineComponent({
|
||||
const plotref: any = ref(props.data ? props.data.id : 1);
|
||||
const zoomFlag: any = ref(false);
|
||||
const chartID = ref("");
|
||||
const tableId = ref(props?.data?.id);
|
||||
const tableId = ref(props?.data?.id)
|
||||
const trace: any = [
|
||||
{
|
||||
type: "table",
|
||||
@@ -122,13 +122,14 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
// created force relayout function to avoid infinite loop
|
||||
const forceReLayout = (flag = true) => {
|
||||
const forceReLayout = async (flag = true) => {
|
||||
zoomFlag.value = flag;
|
||||
|
||||
const update: any = {
|
||||
"xaxis.autorange": true,
|
||||
"yaxis.autorange": true,
|
||||
};
|
||||
await nextTick()
|
||||
Plotly.relayout(plotref.value, update);
|
||||
};
|
||||
|
||||
@@ -165,7 +166,7 @@ export default defineComponent({
|
||||
reDraw,
|
||||
chartID,
|
||||
forceReLayout,
|
||||
tableId,
|
||||
tableId
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -173,6 +174,6 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.plotlycontainer {
|
||||
height: 100%;
|
||||
height: 90%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import { reactive } from "vue";
|
||||
const getDefaultDashboardPanelData = () => (
|
||||
{
|
||||
data: {
|
||||
id: "",
|
||||
type: "bar",
|
||||
fields: {
|
||||
stream: '',
|
||||
x: <any>[],
|
||||
y: <any>[],
|
||||
filter: <any>[]
|
||||
},
|
||||
config: {
|
||||
title: "Chart",
|
||||
description: "",
|
||||
show_legends: false,
|
||||
},
|
||||
query: "",
|
||||
},
|
||||
layout: {
|
||||
showCustomQuery: false,
|
||||
loading:false,
|
||||
},
|
||||
meta: {
|
||||
parsedQuery: "",
|
||||
editorValue: "",
|
||||
dateTime: {},
|
||||
filterValue: <any>[],
|
||||
stream: {
|
||||
selectedStreamFields: [],
|
||||
customQueryFields: [],
|
||||
functions: [],
|
||||
streamResults: <any>[],
|
||||
filterField: "",
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
let dashboardPanelData = reactive({ ...getDefaultDashboardPanelData()});
|
||||
|
||||
const useDashboardPanelData = () => {
|
||||
const resetDashboardPanelData = () => {
|
||||
Object.assign(dashboardPanelData, getDefaultDashboardPanelData());
|
||||
console.log("updated...",dashboardPanelData);
|
||||
};
|
||||
|
||||
const addXAxisItem = (name: string) => {
|
||||
if(!dashboardPanelData.data.fields.x) {
|
||||
dashboardPanelData.data.fields.x = []
|
||||
}
|
||||
|
||||
// TODO: condition for all chart type
|
||||
if(dashboardPanelData.data.fields.x.length >= 1){
|
||||
return;
|
||||
}
|
||||
|
||||
// check for existing field
|
||||
if(!dashboardPanelData.data.fields.x.find((it:any) => it.column == name)) {
|
||||
dashboardPanelData.data.fields.x.push({
|
||||
label: 'x_axis_' + dashboardPanelData.data.fields.x.length,
|
||||
column: name,
|
||||
color: null,
|
||||
aggregationFunction: null
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const addYAxisItem = (name: string) => {
|
||||
if(!dashboardPanelData.data.fields.y) {
|
||||
dashboardPanelData.data.fields.y = []
|
||||
}
|
||||
|
||||
if(!dashboardPanelData.data.fields.y.find((it:any) => it.column == name)) {
|
||||
dashboardPanelData.data.fields.y.push({
|
||||
label: 'y_axis_' + dashboardPanelData.data.fields.y.length,
|
||||
column: name,
|
||||
color: '#5960b2',
|
||||
aggregationFunction: 'count'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const removeXAxisItem = (name: string) => {
|
||||
const index = dashboardPanelData.data.fields.x.findIndex((it:any) => it.column == name)
|
||||
if(index >= 0) {
|
||||
dashboardPanelData.data.fields.x.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const removeYAxisItem = (name: string) => {
|
||||
const index = dashboardPanelData.data.fields.y.findIndex((it:any) => it.column == name)
|
||||
if(index >= 0) {
|
||||
dashboardPanelData.data.fields.y.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const removeFilterItem = (name: string) => {
|
||||
const index = dashboardPanelData.data.fields.filter.findIndex((it:any) => it.column == name)
|
||||
if(index >= 0) {
|
||||
dashboardPanelData.data.fields.filter.splice(index, 1)
|
||||
}
|
||||
}
|
||||
return {
|
||||
dashboardPanelData,
|
||||
resetDashboardPanelData,
|
||||
addXAxisItem,
|
||||
addYAxisItem,
|
||||
removeXAxisItem,
|
||||
removeYAxisItem,
|
||||
removeFilterItem
|
||||
};
|
||||
};
|
||||
|
||||
export default useDashboardPanelData;
|
||||
@@ -285,6 +285,11 @@ export default defineComponent({
|
||||
target: "_blank",
|
||||
external: true,
|
||||
},
|
||||
{
|
||||
title: t("menu.dashboard"),
|
||||
icon: "img:" + getImageURL("images/left_nav/dashb2.svg"),
|
||||
link: "/dashboardList",
|
||||
},
|
||||
]);
|
||||
|
||||
const getConfig = async () => {
|
||||
|
||||
@@ -232,7 +232,15 @@
|
||||
"typeDesc": "Type Description of Dashboard",
|
||||
"createdashboard": "Create a New Dashboard",
|
||||
"nameHolder": "Enter Name of New Dashboard",
|
||||
"typeHolder": "Enter Type Description of New Dashboard"
|
||||
"typeHolder": "Enter Type Description of New Dashboard",
|
||||
"identifier": "Identifier",
|
||||
"role": "Role",
|
||||
"description": "Description",
|
||||
"type": "Type",
|
||||
"owner": "Owner",
|
||||
"created": "Created",
|
||||
"actions": "Actions",
|
||||
"nameRequired": "Name is required"
|
||||
},
|
||||
"panel": {
|
||||
"header": "Panel",
|
||||
@@ -246,7 +254,8 @@
|
||||
"createdashboard": "Create a New Panel",
|
||||
"nameHolder": "Enter Name of New Panel",
|
||||
"typeHolder": "Enter Type Description of New Panel",
|
||||
"editPanel": "Edit Panel"
|
||||
"editPanel": "Edit Panel",
|
||||
"addPanel" : "Add Panel"
|
||||
},
|
||||
"alerts": {
|
||||
"header": "Alerts",
|
||||
|
||||
@@ -229,7 +229,15 @@
|
||||
"typeDesc": "Gösterge Tablosunun Tip Açıklaması",
|
||||
"createdashboard": "Yeni Bir Gösterge Tablosu Oluşturun",
|
||||
"nameHolder": "Yeni Gösterge Tablosunun Adını Girin",
|
||||
"typeHolder": "Yeni Gösterge Tablosunun Tip Açıklamasını Girin"
|
||||
"typeHolder": "Yeni Gösterge Tablosunun Tip Açıklamasını Girin",
|
||||
"invite": "Invite Teammate",
|
||||
"identifier": "Identifier",
|
||||
"role": "Role",
|
||||
"description": "Description",
|
||||
"owner": "Owner",
|
||||
"created": "Created",
|
||||
"actions": "Actions",
|
||||
"nameRequired": "Name is required"
|
||||
},
|
||||
"panel": {
|
||||
"header": "Panel",
|
||||
|
||||
@@ -229,7 +229,15 @@
|
||||
"typeDesc": "仪表板的类型描述",
|
||||
"createdashboard": "创建新仪表板",
|
||||
"nameHolder": "输入新仪表板的名称",
|
||||
"typeHolder": "输入新仪表板的类型描述"
|
||||
"typeHolder": "输入新仪表板的类型描述",
|
||||
"invite": "Invite Teammate",
|
||||
"identifier": "Identifier",
|
||||
"role": "Role",
|
||||
"description": "Description",
|
||||
"owner": "Owner",
|
||||
"created": "Created",
|
||||
"actions": "Actions",
|
||||
"nameRequired": "Name is required"
|
||||
},
|
||||
"panel": {
|
||||
"header": "小组",
|
||||
|
||||
@@ -184,7 +184,7 @@ export default defineComponent({
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
.q-menu {
|
||||
box-shadow: 0px 3px 15px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(0.5rem);
|
||||
|
||||
@@ -18,10 +18,8 @@ import Home from "../views/HomeView.vue";
|
||||
import Tickets from "../views/TicketsView.vue";
|
||||
import Users from "../views/User.vue";
|
||||
import About from "../views/About.vue";
|
||||
import Dashboard from "../views/Dashboard.vue";
|
||||
import ViewDashboard from "../views/ViewDashboard.vue";
|
||||
import EditPanel from "../views/EditPanel.vue";
|
||||
|
||||
import ViewDashboard from "../views/Dashboards/ViewDashboard.vue";
|
||||
import AddPanel from "../views/Dashboards/addPanel/AddPanel.vue";
|
||||
import LoginCallback from "../views/LoginCallback.vue";
|
||||
import MemberSubscription from "../views/MemberSubscription.vue";
|
||||
import Search from "../views/Search.vue";
|
||||
@@ -30,6 +28,7 @@ import Functions from "../views/Functions.vue";
|
||||
import Alerts from "../views/Alerts.vue";
|
||||
import Ingestion from "../views/Ingestion.vue";
|
||||
import Error404 from "../views/Error404.vue";
|
||||
import DashboardList from "../views/Dashboards/DashboardList.vue";
|
||||
import {
|
||||
useLocalUserInfo,
|
||||
getDecodedUserInfo,
|
||||
@@ -109,9 +108,9 @@ export default function (store: any) {
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "dashboard",
|
||||
name: "dashboard",
|
||||
component: Dashboard,
|
||||
path: "dashboardList",
|
||||
name: "dashboardList",
|
||||
component: DashboardList,
|
||||
meta: {
|
||||
keepAlive: true,
|
||||
},
|
||||
@@ -126,9 +125,9 @@ export default function (store: any) {
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "editPanel",
|
||||
name: "editPanel",
|
||||
component: EditPanel,
|
||||
path: "addPanel",
|
||||
name: "addPanel",
|
||||
component: AddPanel,
|
||||
props: true,
|
||||
meta: {
|
||||
keepAlive: true,
|
||||
|
||||
@@ -10,20 +10,28 @@
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// limitations under the License.
|
||||
|
||||
import http from "./http";
|
||||
|
||||
const dashboards = {
|
||||
list: (organization: string) => {
|
||||
list: (
|
||||
page_num: number,
|
||||
page_size: number,
|
||||
sort_by: string,
|
||||
desc: boolean,
|
||||
name: string,
|
||||
organization: string
|
||||
) => {
|
||||
return http().get(
|
||||
`/api/${organization}/dashboards`
|
||||
`/api/${organization}/dashboards?page_num=${page_num}&page_size=${page_size}&sort_by=${sort_by}&desc=${desc}&name=${name}`
|
||||
);
|
||||
},
|
||||
create: (organization: string, dashboardID: String, data: any) => {
|
||||
return http().post(`/api/${organization}/dashboards/${dashboardID}`, data, { headers: { 'Content-Type': 'application/json; charset=UTF-8' } });
|
||||
},
|
||||
delete: (organization: string, dashboardID: String) => {
|
||||
console.log(dashboardID)
|
||||
return http().delete(`/api/${organization}/dashboards/${dashboardID}`);
|
||||
},
|
||||
get_Dashboard: (org_identifier: string) => {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2022 Zinc Labs Inc. and Contributors
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http:www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import http from "./http";
|
||||
|
||||
var index = {
|
||||
nameList: (org_identifier: string, type: string, schema: boolean) => {
|
||||
let url = `/api/${org_identifier}/streams`;
|
||||
|
||||
if (type != "") {
|
||||
url += "?type=" + type;
|
||||
}
|
||||
|
||||
if (schema) {
|
||||
url += url.indexOf("?") > 0 ? "&fetchSchema=" + schema : "?fetchSchema=" + schema;
|
||||
}
|
||||
return http().get(url);
|
||||
},
|
||||
schema: (org_identifier: string, stream_name: string, type: string) => {
|
||||
let url = `/api/${org_identifier}/${stream_name}/schema`;
|
||||
|
||||
if (type != "") {
|
||||
url += "?type=" + type;
|
||||
}
|
||||
return http().get(url);
|
||||
},
|
||||
updateSettings: (org_identifier: string, stream_name: string, type: string, data: any) => {
|
||||
let url = `/api/${org_identifier}/${stream_name}/settings`;
|
||||
|
||||
if (type != "") {
|
||||
url += "?type=" + type;
|
||||
}
|
||||
return http().post(url, data);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default index;
|
||||
@@ -28,9 +28,10 @@ export default createStore({
|
||||
currentuser: (useLocalCurrentUser()) ? useLocalCurrentUser() : {},
|
||||
searchCollapsibleSection: 20,
|
||||
organizationPasscode: "",
|
||||
allCurrentDashboards: {},
|
||||
currentSelectedDashboard: {},
|
||||
currentPanelsData: [],
|
||||
// allCurrentDashboards: {},
|
||||
// currentSelectedDashboard: {},
|
||||
// currentPanelsData: [],
|
||||
allDashboardList: [],
|
||||
search: {
|
||||
query: {
|
||||
dateVal: {
|
||||
@@ -84,11 +85,14 @@ export default createStore({
|
||||
setOrganizationPasscode(state, payload) {
|
||||
state.organizationPasscode = payload;
|
||||
},
|
||||
setAllCurrentDashboards(state, payload) {
|
||||
state.allCurrentDashboards = payload;
|
||||
},
|
||||
setCurrentSelectedDashboard(state, payload) {
|
||||
state.currentSelectedDashboard = payload;
|
||||
// setAllCurrentDashboards(state, payload) {
|
||||
// state.allCurrentDashboards = payload;
|
||||
// },
|
||||
// setCurrentSelectedDashboard(state, payload) {
|
||||
// state.currentSelectedDashboard = payload;
|
||||
// },
|
||||
setAllDashboardList(state, payload) {
|
||||
state.allDashboardList = payload
|
||||
},
|
||||
setSearch(state, payload) {
|
||||
state.search = payload;
|
||||
@@ -96,9 +100,9 @@ export default createStore({
|
||||
setStreamFields(state, payload) {
|
||||
state.streamFields = payload;
|
||||
},
|
||||
setCurrentPanelsData(state, payload) {
|
||||
state.currentPanelsData = payload;
|
||||
},
|
||||
// setCurrentPanelsData(state, payload) {
|
||||
// state.currentPanelsData = payload;
|
||||
// },
|
||||
setQuotaThresholdMsg(state, payload) {
|
||||
state.quotaThresholdMsg = payload;
|
||||
},
|
||||
@@ -134,11 +138,14 @@ export default createStore({
|
||||
setOrganizationPasscode(context, payload) {
|
||||
context.commit("setOrganizationPasscode", payload);
|
||||
},
|
||||
setAllCurrentDashboards(context, payload) {
|
||||
context.commit("setAllCurrentDashboards", payload);
|
||||
},
|
||||
setCurrentSelectedDashboard(context, payload) {
|
||||
context.commit("setCurrentSelectedDashboard", payload);
|
||||
// setAllCurrentDashboards(context, payload) {
|
||||
// context.commit('setAllCurrentDashboards', payload);
|
||||
// },
|
||||
// setCurrentSelectedDashboard(context, payload) {
|
||||
// context.commit('setCurrentSelectedDashboard', payload);
|
||||
// },
|
||||
setAllDashboardList(context, payload) {
|
||||
context.commit('setAllDashboardList', payload)
|
||||
},
|
||||
setSearch(context, payload) {
|
||||
context.commit("setSearch", payload);
|
||||
@@ -146,9 +153,9 @@ export default createStore({
|
||||
setStreamFields(context, payload) {
|
||||
context.commit("setStreamFields", payload);
|
||||
},
|
||||
setCurrentPanelsData(context, payload) {
|
||||
context.commit("setCurrentPanelsData", payload);
|
||||
},
|
||||
// setCurrentPanelsData(context, payload) {
|
||||
// context.commit('setCurrentPanelsData', payload);
|
||||
// },
|
||||
setQuotaThresholdMsg(context, payload) {
|
||||
context.commit("setQuotaThresholdMsg", payload);
|
||||
},
|
||||
|
||||
@@ -38,4 +38,9 @@
|
||||
|
||||
.border-right{
|
||||
border-right: 1px solid $border-color;
|
||||
}
|
||||
|
||||
// For Dashboard draggable color change
|
||||
.displayDiv .vue-grid-item.vue-grid-placeholder {
|
||||
background: #B1B5E5 !important;
|
||||
}
|
||||
@@ -84,7 +84,7 @@ export const deletePanelFromDashboard = async (
|
||||
|
||||
export const updateAllDashboardsFromBackend = async (store: any) => {
|
||||
await dashboardService
|
||||
.list(store.state.selectedOrganization.identifier)
|
||||
.list(0, 1000, "name", false, "",store.state.selectedOrganization.identifier)
|
||||
.then((res) => {
|
||||
store.dispatch("setAllCurrentDashboards", res.data.list);
|
||||
});
|
||||
@@ -139,7 +139,10 @@ export const setCurrentPanelToDashboardList = (
|
||||
let found = 0;
|
||||
for (const dashboard of dashboardList) {
|
||||
if (dashboardId === dashboard.name) {
|
||||
console.log("Inside " + counter);
|
||||
let modDashboardObject = JSON.parse(toRaw(dashboard.details));
|
||||
console.log("modDashboardObject---",modDashboardObject);
|
||||
|
||||
if (modDashboardObject.layouts && modDashboardObject.layouts.length > 0) {
|
||||
modDashboardObject.layouts.push(newLayoutObj);
|
||||
} else {
|
||||
@@ -147,6 +150,8 @@ export const setCurrentPanelToDashboardList = (
|
||||
}
|
||||
for (const panel of modDashboardObject.panels) {
|
||||
if (panelId === panel.id) {
|
||||
console.log("Inside Panel match -- " + panelId);
|
||||
console.log("Panel Replace --- " + JSON.stringify(panel));
|
||||
modDashboardObject.panels[counter] = panelObject;
|
||||
found++;
|
||||
}
|
||||
@@ -163,6 +168,7 @@ export const setCurrentPanelToDashboardList = (
|
||||
}
|
||||
dashboardPos++;
|
||||
}
|
||||
console.log("query dashboardList ====",dashboardList);
|
||||
store.dispatch("setAllCurrentDashboards", dashboardList);
|
||||
};
|
||||
|
||||
@@ -175,7 +181,11 @@ export const getDateConsumableDateTime = function (dateVal: {
|
||||
endDate: string;
|
||||
endTime: string;
|
||||
}) {
|
||||
console.log("==dateVal===",dateVal);
|
||||
|
||||
if (dateVal.tab == "relative") {
|
||||
console.log('r');
|
||||
|
||||
let period = "";
|
||||
let periodValue = 0;
|
||||
// quasar does not support arithmetic on weeks. convert to days.
|
||||
@@ -199,6 +209,7 @@ export const getDateConsumableDateTime = function (dateVal: {
|
||||
end_time: endTimeStamp,
|
||||
};
|
||||
} else {
|
||||
console.log('a');
|
||||
let start, end;
|
||||
|
||||
if (dateVal.startDate == "" && dateVal.startTime == "") {
|
||||
@@ -217,11 +228,81 @@ export const getDateConsumableDateTime = function (dateVal: {
|
||||
start_time: start,
|
||||
end_time: end,
|
||||
};
|
||||
console.log(rVal);
|
||||
|
||||
return rVal;
|
||||
}
|
||||
};
|
||||
|
||||
export function getConsumableDateTime(dateObj: any) {
|
||||
if (dateObj.tab == "relative") {
|
||||
let period = "";
|
||||
let periodValue = 0;
|
||||
// quasar does not support arithmetic on weeks. convert to days.
|
||||
|
||||
if (
|
||||
dateObj.relative.period.label.toLowerCase() ==
|
||||
"weeks"
|
||||
) {
|
||||
period = "days";
|
||||
periodValue = dateObj.relative.value * 7;
|
||||
} else {
|
||||
period =
|
||||
dateObj.relative.period.label.toLowerCase();
|
||||
periodValue = dateObj.relative.value;
|
||||
}
|
||||
const subtractObject = '{"' + period + '":' + periodValue + "}";
|
||||
|
||||
let endTimeStamp = new Date();
|
||||
// if (searchObj.data.resultGrid.currentPage > 0) {
|
||||
// endTimeStamp = searchObj.data.resultGrid.currentDateTime;
|
||||
// } else {
|
||||
// searchObj.data.resultGrid.currentDateTime = endTimeStamp;
|
||||
// }
|
||||
|
||||
const startTimeStamp = date.subtractFromDate(
|
||||
endTimeStamp,
|
||||
JSON.parse(subtractObject)
|
||||
);
|
||||
|
||||
return {
|
||||
start_time: startTimeStamp,
|
||||
end_time: endTimeStamp,
|
||||
};
|
||||
} else {
|
||||
let start, end;
|
||||
if (
|
||||
dateObj.absolute.date.from == "" &&
|
||||
dateObj.absolute.startTime == ""
|
||||
) {
|
||||
start = new Date();
|
||||
} else {
|
||||
start = new Date(
|
||||
dateObj.absolute.date.from +
|
||||
" " +
|
||||
dateObj.absolute.startTime
|
||||
);
|
||||
}
|
||||
if (
|
||||
dateObj.absolute.date.to == "" &&
|
||||
dateObj.absolute.endTime == ""
|
||||
) {
|
||||
end = new Date();
|
||||
} else {
|
||||
end = new Date(
|
||||
dateObj.absolute.date.to +
|
||||
" " +
|
||||
dateObj.absolute.endTime
|
||||
);
|
||||
}
|
||||
const rVal = {
|
||||
start_time: start,
|
||||
end_time: end,
|
||||
};
|
||||
return rVal;
|
||||
}
|
||||
}
|
||||
|
||||
export const modifySQLQuery = (currentTimeObj: any, querySQL: String) => {
|
||||
const startTime = moment(String(currentTimeObj.start_time)).format(
|
||||
"YYYY-MM-DDThh:mm:ssZ"
|
||||
@@ -242,3 +323,143 @@ export const modifySQLQuery = (currentTimeObj: any, querySQL: String) => {
|
||||
}
|
||||
return modString;
|
||||
};
|
||||
|
||||
|
||||
// ----------- NEW Methods to retrieve dashboard data
|
||||
export const getAllDashboards = async (store: any) => {
|
||||
// api call
|
||||
return await dashboardService
|
||||
.list(0, 1000, "name", false, "",store.state.selectedOrganization.identifier)
|
||||
.then((res) => {
|
||||
console.log('dashboard list retriveid');
|
||||
console.log(res);
|
||||
// save to store
|
||||
store.dispatch("setAllDashboardList", res.data.list);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
export const addPanel = async (store: any, dashboardId: any, panelData: any) => {
|
||||
// get the object of panel data
|
||||
// find the dashboard and add the panel data to dashboard object
|
||||
// call the update dashboard function
|
||||
|
||||
if(!store.state.allDashboardList || store.state.allDashboardList.length == 0) {
|
||||
await getAllDashboards(store)
|
||||
}
|
||||
const dashboardList = store.state.allDashboardList
|
||||
let currentDashboard = dashboardList.find((it:any)=>it.name === dashboardId )
|
||||
currentDashboard = JSON.parse(currentDashboard.details)
|
||||
if(!currentDashboard.panels){
|
||||
currentDashboard.panels = []
|
||||
}
|
||||
currentDashboard.panels.push(panelData)
|
||||
|
||||
const maxI =
|
||||
currentDashboard.layouts?.length > 0
|
||||
? Math.max(...currentDashboard.layouts?.map((obj:any) => obj.i))
|
||||
: 0;
|
||||
const maxY =
|
||||
currentDashboard.layouts?.length > 0
|
||||
? Math.max(...currentDashboard.layouts?.map((obj:any) => obj.y))
|
||||
: 0;
|
||||
|
||||
const newLayoutObj = {
|
||||
x: 0,
|
||||
y: currentDashboard.layouts?.length > 0 ? maxY + 10 : 0,
|
||||
w: 12,
|
||||
h: 12,
|
||||
i: maxI + 1,
|
||||
panelId: panelData.id,
|
||||
static: false,
|
||||
};
|
||||
|
||||
if(!currentDashboard.layouts){
|
||||
currentDashboard.layouts = []
|
||||
}
|
||||
currentDashboard.layouts.push(newLayoutObj)
|
||||
|
||||
console.log(currentDashboard);
|
||||
|
||||
|
||||
return await updateDashboard(store, store.state.selectedOrganization.identifier, dashboardId, currentDashboard )
|
||||
}
|
||||
|
||||
export const deletePanel = async (store:any, dashboardId:any, panelId:any) => {
|
||||
// get the object of panel id
|
||||
// find the dashboard and remove the panel data to dashboard object
|
||||
// call the update dashboard function
|
||||
const dashboardList = store.state.allDashboardList
|
||||
let currentDashboard = dashboardList.find((it:any)=>it.name === dashboardId )
|
||||
currentDashboard = JSON.parse(currentDashboard.details)
|
||||
|
||||
//remove panel from current dashboard
|
||||
const panelIndex = currentDashboard.panels.findIndex(
|
||||
(panel: any) => panel.id == panelId
|
||||
);
|
||||
currentDashboard.panels.splice(panelIndex, 1);
|
||||
currentDashboard.panels = currentDashboard.panels;
|
||||
|
||||
//remove layout from current dashboard
|
||||
const layoutIndex = currentDashboard.layouts.findIndex(
|
||||
(layout: any) => layout.panelId == panelId
|
||||
);
|
||||
currentDashboard.layouts.splice(layoutIndex, 1);
|
||||
currentDashboard.layouts = currentDashboard.layouts;
|
||||
|
||||
await updateDashboard(store, store.state.selectedOrganization.identifier , dashboardId, currentDashboard )
|
||||
}
|
||||
|
||||
export const updatePanel = async (store:any, dashboardId:any, panelData:any) => {
|
||||
// get the object of panel id
|
||||
// find the dashboard and remove the panel data to dashboard object
|
||||
// call the update dashboard function
|
||||
const dashboardList = store.state.allDashboardList
|
||||
let currentDashboard = dashboardList.find((it:any)=>it.name === dashboardId )
|
||||
currentDashboard = JSON.parse(currentDashboard.details)
|
||||
|
||||
//remove panel from current dashboard
|
||||
const panelIndex = currentDashboard.panels.findIndex(
|
||||
(panel: any) => panel.id == panelData.id
|
||||
);
|
||||
currentDashboard.panels[panelIndex] = panelData;
|
||||
currentDashboard.panels = currentDashboard.panels;
|
||||
|
||||
await updateDashboard(store, store.state.selectedOrganization.identifier , dashboardId, currentDashboard )
|
||||
}
|
||||
|
||||
export const updateDashboard = async (store: any, org:any, dashboardId:any, currentDashboardData: any) => {
|
||||
// make an api call to update the dashboard
|
||||
return await dashboardService
|
||||
.save(
|
||||
org,
|
||||
dashboardId,
|
||||
JSON.stringify(JSON.stringify(currentDashboardData))
|
||||
)
|
||||
.then(async (res) => {
|
||||
// update dashboardList
|
||||
await getAllDashboards(store)
|
||||
});
|
||||
}
|
||||
|
||||
export const getDashboard = async (store: any, dashboardId: any) => {
|
||||
if(!store.state.allDashboardList || store.state.allDashboardList.length == 0) {
|
||||
await getAllDashboards(store)
|
||||
}
|
||||
const dashboardList = store.state.allDashboardList
|
||||
let currentDashboard = dashboardList.find((it:any)=>it.name === dashboardId )
|
||||
currentDashboard = JSON.parse(currentDashboard.details)
|
||||
return currentDashboard
|
||||
}
|
||||
|
||||
export const getPanel = async (store: any, dashboardId: any, panelId: any) => {
|
||||
if(!store.state.allDashboardList || store.state.allDashboardList.length == 0) {
|
||||
await getAllDashboards(store)
|
||||
}
|
||||
const dashboardList = store.state.allDashboardList
|
||||
let currentDashboard = dashboardList.find((it:any)=>it.name === dashboardId )
|
||||
currentDashboard = JSON.parse(currentDashboard.details)
|
||||
return currentDashboard.panels?.find((it: any) => it.id == panelId)
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
<q-page class="aboutPage">
|
||||
<div class="about_head">
|
||||
<img
|
||||
src="~@/assets/images/common/app_logo_zo.png"
|
||||
:src="getImageURL('images/common/app_logo_zo.png')"
|
||||
class="logo"
|
||||
width="177"
|
||||
/>
|
||||
@@ -48,6 +48,7 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { getImageURL } from "../utils/zincutils";
|
||||
|
||||
export default defineComponent({
|
||||
name: "PageAbout",
|
||||
@@ -58,6 +59,7 @@ export default defineComponent({
|
||||
return {
|
||||
store,
|
||||
pageData,
|
||||
getImageURL,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,22 +1,8 @@
|
||||
<!-- Copyright 2022 Zinc Labs Inc. and Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http:www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- eslint-disable vue/v-on-event-hyphenation -->
|
||||
<!-- eslint-disable vue/attribute-hyphenation -->
|
||||
<template>
|
||||
<q-page class="q-pa-none">
|
||||
<!-- add dashboard table -->
|
||||
<q-table
|
||||
ref="qTable"
|
||||
:rows="dashboards"
|
||||
@@ -28,15 +14,17 @@
|
||||
:loading="loading"
|
||||
@row-click="onRowClick"
|
||||
>
|
||||
<!-- if data not available show nodata component -->
|
||||
<template #no-data>
|
||||
<NoData />
|
||||
</template>
|
||||
<!-- add delete icon in actions column -->
|
||||
<template #body-cell-actions="props">
|
||||
<q-td :props="props">
|
||||
<q-btn
|
||||
v-if="props.row.actions == 'true'"
|
||||
:icon="'img:' + getImageURL('images/common/remove_icon.svg')"
|
||||
:title="t('organization.invite')"
|
||||
:title="t('dashboard.delete')"
|
||||
class="iconHoverBtn"
|
||||
padding="sm"
|
||||
unelevated
|
||||
@@ -47,7 +35,7 @@
|
||||
></q-btn>
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
<!-- searchBar at top -->
|
||||
<template #top="scope">
|
||||
<div class="q-table__title">{{ t("dashboard.header") }}</div>
|
||||
<q-input
|
||||
@@ -61,6 +49,7 @@
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
<!-- add dashboard button -->
|
||||
<q-btn
|
||||
class="q-ml-md q-mb-xs text-bold no-border"
|
||||
padding="sm lg"
|
||||
@@ -70,6 +59,7 @@
|
||||
@click="addDashboard"
|
||||
/>
|
||||
|
||||
<!-- table pagination -->
|
||||
<QTablePagination
|
||||
:scope="scope"
|
||||
:pageTitle="t('dashboard.header')"
|
||||
@@ -98,41 +88,11 @@
|
||||
full-height
|
||||
maximized
|
||||
>
|
||||
<add-update-organization @updated="updateDashboardList" />
|
||||
</q-dialog>
|
||||
|
||||
<q-dialog v-model="showOrgAPIKeyDialog">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<div class="text-h6">Organization API Key</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section class="q-pt-none" wrap>
|
||||
<q-item>
|
||||
<q-item-section>
|
||||
<q-item-label lines="3" style="word-wrap: break-word">{{
|
||||
organizationAPIKey
|
||||
}}</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-btn
|
||||
unelevated
|
||||
round
|
||||
flat
|
||||
padding="sm"
|
||||
size="sm"
|
||||
icon="content_copy"
|
||||
@click="copyAPIKey"
|
||||
:title="t('organization.copyapikey')"
|
||||
/>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<AddDashboard @updated="updateDashboardList" />
|
||||
</q-dialog>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
// @ts-nocheck
|
||||
import { computed, defineComponent, onMounted, ref } from "vue";
|
||||
@@ -140,20 +100,18 @@ import { useStore } from "vuex";
|
||||
import { useQuasar, date, copyToClipboard } from "quasar";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
import organizationsService from "../services/organizations";
|
||||
import dashboardService from "../services/dashboards";
|
||||
|
||||
import AddUpdateOrganization from "../components/dashboards/AddUpdateDashboards.vue";
|
||||
import QTablePagination from "../components/shared/grid/Pagination.vue";
|
||||
import NoData from "../components/shared/grid/NoData.vue";
|
||||
import dashboardService from "../../services/dashboards";
|
||||
import AddDashboard from "../../components/dashboards/AddDashboard.vue";
|
||||
import QTablePagination from "../../components/shared/grid/Pagination.vue";
|
||||
import NoData from "../../components/shared/grid/NoData.vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { isProxy, toRaw } from "vue";
|
||||
import { getImageURL } from "../utils/zincutils";
|
||||
import { getImageURL } from "../../utils/zincutils";
|
||||
|
||||
export default defineComponent({
|
||||
name: "PageOrganization",
|
||||
name: "DashboardList",
|
||||
components: {
|
||||
AddUpdateOrganization,
|
||||
AddDashboard,
|
||||
QTablePagination,
|
||||
NoData,
|
||||
},
|
||||
@@ -161,12 +119,12 @@ export default defineComponent({
|
||||
const store = useStore();
|
||||
const { t } = useI18n();
|
||||
const $q = useQuasar();
|
||||
const organization = ref({});
|
||||
const dashboard = ref({});
|
||||
const showAddDashboardDialog = ref(false);
|
||||
const showOrgAPIKeyDialog = ref(false);
|
||||
const organizationAPIKey = ref("");
|
||||
const qTable: any = ref(null);
|
||||
const router = useRouter();
|
||||
const orgData: any = ref(store.state.selectedOrganization);
|
||||
|
||||
const columns = ref<QTableProps["columns"]>([
|
||||
{
|
||||
name: "#",
|
||||
@@ -177,49 +135,49 @@ export default defineComponent({
|
||||
{
|
||||
name: "name",
|
||||
field: "name",
|
||||
label: t("organization.name"),
|
||||
label: t("dashboard.name"),
|
||||
align: "left",
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: "identifier",
|
||||
field: "identifier",
|
||||
label: t("organization.identifier"),
|
||||
label: t("dashboard.identifier"),
|
||||
align: "left",
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: "role",
|
||||
field: "role",
|
||||
label: t("organization.role"),
|
||||
label: t("dashboard.role"),
|
||||
align: "left",
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: "type",
|
||||
field: "type",
|
||||
label: t("organization.type"),
|
||||
name: "description",
|
||||
field: "description",
|
||||
label: t("dashboard.description"),
|
||||
align: "left",
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: "owner",
|
||||
field: "owner",
|
||||
label: t("organization.owner"),
|
||||
label: t("dashboard.owner"),
|
||||
align: "left",
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: "created",
|
||||
field: "created",
|
||||
label: t("organization.created"),
|
||||
label: t("dashboard.created"),
|
||||
align: "left",
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: "actions",
|
||||
field: "actions",
|
||||
label: t("organization.actions"),
|
||||
label: t("dashboard.actions"),
|
||||
align: "center",
|
||||
},
|
||||
]);
|
||||
@@ -255,7 +213,7 @@ export default defineComponent({
|
||||
showAddDashboardDialog.value = true;
|
||||
};
|
||||
const routeToViewD = (row) => {
|
||||
//return router.push( '/viewDashboard' )
|
||||
console.log("row");
|
||||
return router.push({
|
||||
path: "/viewDashboard",
|
||||
query: { dashboard: row.identifier },
|
||||
@@ -266,29 +224,36 @@ export default defineComponent({
|
||||
spinner: true,
|
||||
message: "Please wait while loading dashboards...",
|
||||
});
|
||||
|
||||
await dashboardService
|
||||
.list(store.state.selectedOrganization.identifier)
|
||||
.list(
|
||||
0,
|
||||
1000,
|
||||
"name",
|
||||
false,
|
||||
"",
|
||||
store.state.selectedOrganization.identifier
|
||||
)
|
||||
.then((res) => {
|
||||
store.dispatch("setAllCurrentDashboards", res.data.list);
|
||||
resultTotal.value = res.data.list.length;
|
||||
console.log(res);
|
||||
store.dispatch("setAllDashboardList", res.data.list);
|
||||
|
||||
dismiss();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
const dashboards = computed(function () {
|
||||
const dashboardList = toRaw(store.state.allCurrentDashboards);
|
||||
return dashboardList.map((data: any) => {
|
||||
const dashboardList = toRaw(store.state.allDashboardList);
|
||||
return dashboardList.map((data: any, index) => {
|
||||
const jsonDataOBj = JSON.parse(data.details);
|
||||
return {
|
||||
"#":
|
||||
data.id <= 9
|
||||
? `0${jsonDataOBj.dashboardID}`
|
||||
: jsonDataOBj.dashboardID,
|
||||
"#": index < 9 ? `0${index + 1}` : index + 1,
|
||||
id: jsonDataOBj.dashboardId,
|
||||
name: jsonDataOBj.title,
|
||||
identifier: jsonDataOBj.dashboardId,
|
||||
type: jsonDataOBj.description,
|
||||
description: jsonDataOBj.description,
|
||||
owner: jsonDataOBj.owner,
|
||||
created: date.formatDate(jsonDataOBj.created, "YYYY-MM-DDTHH:mm:ssZ"),
|
||||
role: jsonDataOBj.role,
|
||||
@@ -296,29 +261,19 @@ export default defineComponent({
|
||||
};
|
||||
});
|
||||
});
|
||||
const onAddTeam = (props: any) => {
|
||||
console.log(props);
|
||||
};
|
||||
const inviteTeam = (props: any) => {
|
||||
organization.value = {
|
||||
id: props.row.id,
|
||||
name: props.row.name,
|
||||
role: props.row.role,
|
||||
member_lists: [],
|
||||
};
|
||||
};
|
||||
|
||||
const removeDashboard = async (props: any) => {
|
||||
const dashboardId = props.key;
|
||||
await dashboardService
|
||||
.delete(store.state.selectedOrganization.identifier, dashboardId)
|
||||
.then((res) => {
|
||||
const dashboardList = JSON.parse(
|
||||
JSON.stringify(toRaw(store.state.allCurrentDashboards))
|
||||
JSON.stringify(toRaw(store.state.allDashboardList))
|
||||
);
|
||||
const newDashboardList = dashboardList.filter(
|
||||
(dashboard) => dashboard.name != dashboardId
|
||||
);
|
||||
store.dispatch("setAllCurrentDashboards", newDashboardList);
|
||||
store.dispatch("setAllDashboardList", newDashboardList);
|
||||
$q.notify({
|
||||
type: "positive",
|
||||
message: "Dashboard deleted successfully.",
|
||||
@@ -333,16 +288,15 @@ export default defineComponent({
|
||||
return {
|
||||
t,
|
||||
qTable,
|
||||
store,
|
||||
orgData,
|
||||
router,
|
||||
loading: ref(false),
|
||||
dashboards,
|
||||
organization,
|
||||
dashboard,
|
||||
columns,
|
||||
showAddDashboardDialog,
|
||||
showOrgAPIKeyDialog,
|
||||
organizationAPIKey,
|
||||
addDashboard,
|
||||
inviteTeam,
|
||||
onAddTeam,
|
||||
pagination,
|
||||
resultTotal,
|
||||
perPageOptions,
|
||||
@@ -368,6 +322,7 @@ export default defineComponent({
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
//after adding dashboard need to update the dashboard list
|
||||
updateDashboardList() {
|
||||
this.showAddDashboardDialog = false;
|
||||
this.getDashboards();
|
||||
@@ -380,48 +335,28 @@ export default defineComponent({
|
||||
onRowClick(evt, row) {
|
||||
this.routeToViewD(row);
|
||||
},
|
||||
getAPIKey(org_identifier: string) {
|
||||
const dismiss: any = this.$q.notify({
|
||||
message: "Wait while processing your request...",
|
||||
});
|
||||
this.organizationAPIKey = "";
|
||||
organizationsService
|
||||
.get_organization_passcode(org_identifier)
|
||||
.then((res) => {
|
||||
dismiss();
|
||||
if (res.data.data.token == "") {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
message: "API Key not found.",
|
||||
timeout: 5000,
|
||||
});
|
||||
} else {
|
||||
this.showOrgAPIKeyDialog = true;
|
||||
this.organizationAPIKey = res.data.data.token;
|
||||
}
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
selectedOrg() {
|
||||
return this.store.state.selectedOrganization.identifier;
|
||||
},
|
||||
copyAPIKey() {
|
||||
copyToClipboard(this.organizationAPIKey)
|
||||
.then(() => {
|
||||
this.$q.notify({
|
||||
type: "positive",
|
||||
message: "API Key Copied Successfully!",
|
||||
timeout: 5000,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
message: "Error while copy API Key.",
|
||||
timeout: 5000,
|
||||
});
|
||||
});
|
||||
},
|
||||
watch: {
|
||||
selectedOrg(newVal: any, oldVal: any) {
|
||||
this.orgData.identifier = newVal;
|
||||
if (
|
||||
(newVal != oldVal || this.dashboards.value == undefined) &&
|
||||
this.router.currentRoute.value.name == "dashboardList"
|
||||
) {
|
||||
console.log("inside if");
|
||||
|
||||
this.getDashboards(this.store.state.selectedOrganization.id);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.q-table {
|
||||
&__top {
|
||||
@@ -430,4 +365,3 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,24 +1,9 @@
|
||||
<!-- Copyright 2022 Zinc Labs Inc. and Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http:www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- eslint-disable vue/v-on-event-hyphenation -->
|
||||
<!-- eslint-disable vue/attribute-hyphenation -->
|
||||
<template>
|
||||
<q-page class="q-pa-md">
|
||||
<div class="flex justify-between items-center q-pa-sm">
|
||||
<div class="q-table__title q-mr-md">{{ t("dashboard.header") }}</div>
|
||||
<div class="q-table__title q-mr-md">{{ list[0].title }}</div>
|
||||
<div class="flex items-baseline q-gutter-sm">
|
||||
<q-btn
|
||||
class="q-ml-md q-mb-xs text-bold"
|
||||
@@ -28,13 +13,35 @@
|
||||
text-color="black"
|
||||
no-caps
|
||||
:label="t(`Add Panel`)"
|
||||
@click="addNewPanelOnClick"
|
||||
@click="addPanelData"
|
||||
/>
|
||||
<q-btn
|
||||
class="q-ml-md q-mb-xs text-bold"
|
||||
outline
|
||||
padding="sm lg"
|
||||
color="white"
|
||||
text-color="black"
|
||||
no-caps
|
||||
:label="draggable ? t(`Cancel`) : t(`Edit`)"
|
||||
@click="isDraggableClick"
|
||||
/>
|
||||
<!-- <q-btn
|
||||
class="q-ml-md q-mb-xs text-bold"
|
||||
outline
|
||||
padding="sm lg"
|
||||
color="white"
|
||||
text-color="black"
|
||||
no-caps
|
||||
:label="t(`Edit Panel`)"
|
||||
@click="addNewPanelOnClick"
|
||||
/> -->
|
||||
|
||||
<q-btn
|
||||
class="q-ml-md q-mb-xs text-bold no-border"
|
||||
padding="sm lg"
|
||||
color="secondary"
|
||||
no-caps
|
||||
:disable="!draggable"
|
||||
:label="t(`Save`)"
|
||||
@click="saveDashboardOnClick"
|
||||
/>
|
||||
@@ -55,26 +62,33 @@
|
||||
:label="t(`Go back to Dashboard`)"
|
||||
outline
|
||||
text-color="black"
|
||||
@click="goBacToDashboardList"
|
||||
/>
|
||||
<date-time
|
||||
ref="refDateTime"
|
||||
v-model="dateVal"
|
||||
@update:date="dateChange"
|
||||
@click="goBackToDashboardList"
|
||||
/>
|
||||
<date-time ref="refDateTime" @date-change="dateChange" />
|
||||
<!-- <q-btn
|
||||
class="q-ml-md q-mb-xs text-bold"
|
||||
padding="sm lg"
|
||||
color="white"
|
||||
no-caps
|
||||
:label="t(`show New Panel`)"
|
||||
outline
|
||||
text-color="black"
|
||||
@click="showNewPanel= !showNewPanel"
|
||||
/> -->
|
||||
</div>
|
||||
</div>
|
||||
<q-separator></q-separator>
|
||||
<div class="displayDiv">
|
||||
<grid-layout
|
||||
v-if="list[0].panels.length > 0"
|
||||
v-if="list[0].panels?.length > 0"
|
||||
v-model:layout.sync="list[0].layouts"
|
||||
:col-num="12"
|
||||
:row-height="30"
|
||||
:is-draggable="draggable"
|
||||
:isResizable="true"
|
||||
:vertical-compact="false"
|
||||
:is-resizable="draggable"
|
||||
:vertical-compact="true"
|
||||
:autoSize="true"
|
||||
:restore-on-drag="true"
|
||||
:use-css-transforms="true"
|
||||
@layout-created="layoutCreatedEvent"
|
||||
@layout-before-mount="layoutBeforeMountEvent"
|
||||
@@ -82,10 +96,6 @@
|
||||
@layout-ready="layoutReadyEvent"
|
||||
@layout-updated="layoutUpdatedEvent"
|
||||
>
|
||||
<!-- <li v-for="item in list[0]" :key="item">
|
||||
<PanelContainer :key="item.id" ref="{{item}}" :panelDataElement="item" :dashbordId="getDashboard"
|
||||
@updated:chart="onUpdatePanel">
|
||||
</PanelContainer> -->
|
||||
<grid-item
|
||||
class="plotlyBackground"
|
||||
v-for="item in list[0].panels"
|
||||
@@ -95,26 +105,22 @@
|
||||
:w="getPanelLayout(list[0].layouts, item.id, 'w')"
|
||||
:h="getPanelLayout(list[0].layouts, item.id, 'h')"
|
||||
:i="getPanelLayout(list[0].layouts, item.id, 'i')"
|
||||
:minH="getMinimumHeight(item.type)"
|
||||
@resize="resizeEvent"
|
||||
@move="moveEvent"
|
||||
@resized="resizedEvent"
|
||||
@container-resized="containerResizedEvent"
|
||||
@moved="movedEvent"
|
||||
drag-allow-from=".drag-allow"
|
||||
>
|
||||
<PanelContainer
|
||||
:key="
|
||||
getPanelLayout(list[0].layouts, item.id, 'w') +
|
||||
getPanelLayout(list[0].layouts, item.id, 'h') +
|
||||
getPanelLayout(list[0].layouts, item.id, 'y') +
|
||||
getPanelLayout(list[0].layouts, item.id, 'x')
|
||||
"
|
||||
ref="{{item}}"
|
||||
:panelDataElement="item"
|
||||
:dashboardId="getDashboard"
|
||||
@updated:chart="onUpdatePanel"
|
||||
:selectedTimeDate="currentTimeObj"
|
||||
>
|
||||
</PanelContainer>
|
||||
<div>
|
||||
<PanelContainer
|
||||
@updated:chart="onUpdatePanel"
|
||||
:data="item"
|
||||
:selectedTimeDate="currentTimeObj"
|
||||
>
|
||||
</PanelContainer>
|
||||
</div>
|
||||
</grid-item>
|
||||
</grid-layout>
|
||||
</div>
|
||||
@@ -126,55 +132,54 @@
|
||||
|
||||
<script lang="ts">
|
||||
// @ts-nocheck
|
||||
import { defineComponent, ref, computed, watch, onMounted } from "vue";
|
||||
import {
|
||||
defineComponent,
|
||||
ref,
|
||||
computed,
|
||||
watch,
|
||||
onMounted,
|
||||
onActivated,
|
||||
} from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { useQuasar, date, copyToClipboard } from "quasar";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
import QTablePagination from "../components/shared/grid/Pagination.vue";
|
||||
import NoData from "../components/shared/grid/NoData.vue";
|
||||
import ReactiveBarChart from "../components/shared/plotly/barchart.vue";
|
||||
import PanelContainer from "../components/shared/plotly/panelcontainer.vue";
|
||||
import dashboardService from "../services/dashboards";
|
||||
import DateTime from "../plugins/logs/DateTime.vue";
|
||||
import dashboardService from "../../services/dashboards";
|
||||
import DateTime from "../../plugins/logs/DateTime.vue";
|
||||
import VueGridLayout from "vue3-grid-layout";
|
||||
|
||||
import { useRouter } from "vue-router";
|
||||
import dashboards from "@/services/dashboards";
|
||||
import {
|
||||
getCurrentDashboard,
|
||||
getCurrentPanel,
|
||||
deletePanelFromDashboard,
|
||||
setCurrentPanelToDashboardList,
|
||||
getDateConsumableDateTime,
|
||||
} from "../utils/commons.ts";
|
||||
|
||||
import { isProxy, toRaw, unref } from "vue";
|
||||
import { tsImportEqualsDeclaration } from "@babel/types";
|
||||
getConsumableDateTime,
|
||||
getDashboard,
|
||||
} from "../../utils/commons.ts";
|
||||
import { toRaw, unref, reactive } from "vue";
|
||||
import PanelContainer from "../../components/dashboards/PanelContainer.vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { deletePanel, updateDashboard } from "../../utils/commons";
|
||||
|
||||
export default defineComponent({
|
||||
name: "PageOrganization",
|
||||
name: "ViewDashboard",
|
||||
components: {
|
||||
QTablePagination,
|
||||
ReactiveBarChart,
|
||||
PanelContainer,
|
||||
NoData,
|
||||
GridLayout: VueGridLayout.GridLayout,
|
||||
GridItem: VueGridLayout.GridItem,
|
||||
DateTime,
|
||||
PanelContainer,
|
||||
},
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
const selectedPerPage = ref<number>(20);
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const store = useStore();
|
||||
// let list = ref([])
|
||||
let valueList = [];
|
||||
const currentDashboardData = reactive({
|
||||
data: {},
|
||||
});
|
||||
|
||||
const refDateTime: any = ref(null);
|
||||
const $q = useQuasar();
|
||||
let currentTimeObj: any = ref({});
|
||||
// let showNewPanel = ref(true)
|
||||
|
||||
const dateVal = ref({
|
||||
const initialDateValue = {
|
||||
tab: "relative",
|
||||
startDate: "",
|
||||
startTime: "",
|
||||
@@ -183,42 +188,37 @@ export default defineComponent({
|
||||
selectedRelativePeriod: "Minutes",
|
||||
selectedRelativeValue: 15,
|
||||
selectedFullTime: false,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
dateChange(dateVal);
|
||||
});
|
||||
|
||||
watch(dateVal.value, () => {
|
||||
if (dateVal.value) {
|
||||
dateChange(dateVal);
|
||||
}
|
||||
});
|
||||
const dateChange = (dateValue: any) => {
|
||||
const c = toRaw(unref(dateValue));
|
||||
currentTimeObj.value = getDateConsumableDateTime(c);
|
||||
};
|
||||
|
||||
const pagination: any = ref({
|
||||
rowsPerPage: 20,
|
||||
});
|
||||
// if the date value change, get the Date and time
|
||||
const dateChange = (dateValue: any) => {
|
||||
const c = toRaw(unref(dateValue));
|
||||
currentTimeObj.value = getConsumableDateTime(c);
|
||||
};
|
||||
|
||||
const initialize = () => {};
|
||||
|
||||
const changePagination = (val: { label: string; value: any }) => {
|
||||
selectedPerPage.value = val.value;
|
||||
pagination.value.rowsPerPage = val.value;
|
||||
};
|
||||
|
||||
// back button to render dashboard List page
|
||||
const goBack = () => {
|
||||
return router.push("/dashboard");
|
||||
return router.push("/dashboardList");
|
||||
};
|
||||
|
||||
const goBackToDashboardList = () => {
|
||||
goBack();
|
||||
};
|
||||
|
||||
// delete dashboard remove the data from database and update store variable and redirect to dashboardList page
|
||||
const deleteDashboard = async (dashboardId: String) => {
|
||||
const baseObj = {};
|
||||
await dashboardService
|
||||
.delete(store.state.selectedOrganization.identifier, dashboardId)
|
||||
.then((res) => {
|
||||
const dashboardList = JSON.parse(
|
||||
JSON.stringify(toRaw(store.state.allDashboardList))
|
||||
);
|
||||
const newDashboardList = dashboardList.filter(
|
||||
(dashboard) => dashboard.name != dashboardId
|
||||
);
|
||||
store.dispatch("setAllDashboardList", newDashboardList);
|
||||
$q.notify({
|
||||
type: "positive",
|
||||
message: "Dashboard deleted successfully.",
|
||||
@@ -228,76 +228,93 @@ export default defineComponent({
|
||||
goBack();
|
||||
};
|
||||
|
||||
const saveDashboardOnClick = async (dashboardId: String) => {
|
||||
const currentDashboard = toRaw(store.state.currentSelectedDashboard);
|
||||
await dashboardService
|
||||
.save(
|
||||
store.state.selectedOrganization.identifier,
|
||||
currentDashboard.dashboardId,
|
||||
JSON.stringify(JSON.stringify(currentDashboard))
|
||||
)
|
||||
.then((res) => {
|
||||
$q.notify({
|
||||
type: "positive",
|
||||
message: "Dashboard saved successfully.",
|
||||
timeout: 5000,
|
||||
});
|
||||
});
|
||||
const deleteDashboardOnClick = async () => {
|
||||
await deleteDashboard(route.query.dashboard);
|
||||
};
|
||||
|
||||
// save the dashboard value
|
||||
const saveDashboard = async (dashboardId: String) => {
|
||||
await updateDashboard(
|
||||
store,
|
||||
store.state.selectedOrganization.identifier,
|
||||
route.query.dashboard,
|
||||
currentDashboardData.data
|
||||
);
|
||||
// const currentDashboard = currentDashboardData.data
|
||||
// await dashboardService
|
||||
// .save(
|
||||
// store.state.selectedOrganization.identifier,
|
||||
// currentDashboard.dashboardId,
|
||||
// JSON.stringify(JSON.stringify(currentDashboard))
|
||||
// )
|
||||
// .then((res) => {
|
||||
// $q.notify({
|
||||
// type: "positive",
|
||||
// message: "Dashboard saved successfully.",
|
||||
// timeout: 5000,
|
||||
// });
|
||||
// });
|
||||
currentDashboardData.data = await getDashboard(
|
||||
this.store,
|
||||
this.$route.query.dashboard
|
||||
);
|
||||
|
||||
//goBack()
|
||||
};
|
||||
|
||||
const getDashboard = () => {
|
||||
return currentDashboard.dashboardId;
|
||||
};
|
||||
const refreshAllDashboardsAndGetCurrentDashboard = async (
|
||||
dashId: String
|
||||
) => {
|
||||
const a = await getCurrentDashboard(store, dashId);
|
||||
return a;
|
||||
const saveDashboardOnClick = async () => {
|
||||
await saveDashboard(route.query.dashboard);
|
||||
};
|
||||
|
||||
//get current dashboard Id
|
||||
const getDashboard = () => {
|
||||
return currentDashboardData.data.dashboardId;
|
||||
};
|
||||
|
||||
//add dashboardId
|
||||
const addNewPanel = (dashboardId: String) => {
|
||||
const panelId =
|
||||
"Panel_ID" + Math.floor(Math.random() * (99999 - 10 + 1)) + 10;
|
||||
return router.push({
|
||||
path: "/editPanel",
|
||||
query: { dashboard: dashboardId, panelId: panelId },
|
||||
path: "/addPanel",
|
||||
query: { dashboard: dashboardId },
|
||||
});
|
||||
};
|
||||
|
||||
const addPanelData = () => {
|
||||
addNewPanel(route.query.dashboard);
|
||||
};
|
||||
|
||||
const deleteExistingPanel = async (
|
||||
panelDataElement: any,
|
||||
dashboardId: String,
|
||||
dashboardList: any
|
||||
) => {
|
||||
//const panelId = 'Panel_ID' + Math.floor(Math.random() * (99999 - 10 + 1)) + 10
|
||||
deletePanelFromDashboard(
|
||||
store,
|
||||
dashboardId,
|
||||
panelDataElement.id,
|
||||
dashboardList
|
||||
);
|
||||
// await saveDashboardOnClick(dashboardId)
|
||||
//router.go(0);
|
||||
};
|
||||
|
||||
let list = computed(function () {
|
||||
return [toRaw(store.state.currentSelectedDashboard)];
|
||||
return [toRaw(currentDashboardData.data)];
|
||||
});
|
||||
|
||||
initialize();
|
||||
return {
|
||||
currentDashboardData,
|
||||
goBackToDashboardList,
|
||||
deleteDashboardOnClick,
|
||||
addPanelData,
|
||||
t,
|
||||
list,
|
||||
goBack,
|
||||
getDashboard,
|
||||
valueList,
|
||||
dateVal,
|
||||
dateVal: initialDateValue,
|
||||
deleteDashboard,
|
||||
addNewPanel,
|
||||
saveDashboardOnClick,
|
||||
deleteExistingPanel,
|
||||
refreshAllDashboardsAndGetCurrentDashboard,
|
||||
store,
|
||||
refDateTime,
|
||||
filterQuery: ref(""),
|
||||
@@ -313,80 +330,43 @@ export default defineComponent({
|
||||
},
|
||||
dateChange,
|
||||
currentTimeObj,
|
||||
// showNewPanel,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
computedTimeObj: Date.now(),
|
||||
// layouts: [
|
||||
// { x: 0, y: 0, w: 12, h: 12, i: 0, panelId: "Panel_ID607710", static: false },
|
||||
// { x: 4, y: 4, w: 12, h: 12, i: 1, panelId: "Panel_ID7545110", static: false },
|
||||
// ],
|
||||
// layoutCopy:{
|
||||
// Panel_ID607710 :{ x: 0, y: 0, w: 2, h: 2, i: "Panel_ID607710", static: false },
|
||||
// Panel_ID7545110: { x: 20, y: 40, w: 2, h: 4, i: "Panel_ID7545110", static: false },
|
||||
|
||||
// },
|
||||
// mockedData: {
|
||||
// title: "Time Range Tester Dash",
|
||||
// dashboardId: "DashID_1262352719",
|
||||
// layout: [
|
||||
// { x: 0, y: 0, w: 12, h: 12, i: 0, panelId: "Panel_ID607710", static: false },
|
||||
// { x: 4, y: 4, w: 12, h: 12, i: 1, panelId: "Panel_ID7545110", static: false },
|
||||
// ],
|
||||
// dashboardLayout: {
|
||||
// Panel_ID607710: { x: 0, y: 0, w: 2, h: 2, i: "0", static: false },
|
||||
// Panel_ID7545110: { x: 0, y: 0, w: 2, h: 2, i: "0", static: false },
|
||||
// },
|
||||
// description: "Time Tester D",
|
||||
// role: "User Dashboard",
|
||||
// owner: "Ayon Bhattacharya",
|
||||
// created: "2022-12-13T02:00:17.720Z",
|
||||
// panels: [
|
||||
// {
|
||||
// id: "Panel_ID607710",
|
||||
// query: [
|
||||
// "select count(*) as y_axis, kubernetes.host as x_axis from default WHERE time_range(_timestamp,'2022-12-13T08:06:59+07:00', '2022-12-13T11:06:59+07:00') group by x_axis",
|
||||
// ],
|
||||
// title: "This is always a Description Text",
|
||||
// type: "BarChart",
|
||||
// },
|
||||
// {
|
||||
// id: "Panel_ID7545110",
|
||||
// query: [
|
||||
// "select count(*) as y_axis, kubernetes.host as x_axis from default WHERE time_range(_timestamp,'2022-12-13T08:09:33+07:00', '2022-12-13T11:09:33+07:00') group by x_axis",
|
||||
// ],
|
||||
// title: "This is always a Description Text",
|
||||
// type: "Table",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
|
||||
draggable: true,
|
||||
resizable: true,
|
||||
draggable: false,
|
||||
index: 0,
|
||||
eventLog: [],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
goBacToDashboardList(evt, row) {
|
||||
this.goBack();
|
||||
isDraggableClick(evt, row) {
|
||||
this.draggable = !this.draggable;
|
||||
},
|
||||
deleteDashboardOnClick(evt, row) {
|
||||
this.deleteDashboard(this.$route.query.dashboard);
|
||||
},
|
||||
saveDashboardOnClick(evt, row) {
|
||||
this.saveDashboard(this.$route.query.dashboard);
|
||||
},
|
||||
addNewPanelOnClick(evt, row) {
|
||||
this.addNewPanel(this.$route.query.dashboard);
|
||||
},
|
||||
onUpdatePanel(panelDataElementValue: any) {
|
||||
let dashboardList = toRaw(this.store.state.allCurrentDashboards);
|
||||
this.deleteExistingPanel(
|
||||
panelDataElementValue,
|
||||
async onUpdatePanel(panelDataElementValue: any) {
|
||||
// let dashboardList = toRaw(this.store.state.allDashboardList);
|
||||
// this.deleteExistingPanel(
|
||||
// panelDataElementValue,
|
||||
// this.$route.query.dashboard,
|
||||
// dashboardList
|
||||
// );
|
||||
console.log(
|
||||
"deleting",
|
||||
this.$route.query.dashboard,
|
||||
dashboardList
|
||||
panelDataElementValue,
|
||||
panelDataElementValue.id
|
||||
);
|
||||
|
||||
await deletePanel(
|
||||
this.store,
|
||||
this.$route.query.dashboard,
|
||||
panelDataElementValue.id
|
||||
);
|
||||
this.currentDashboardData.data = await getDashboard(
|
||||
this.store,
|
||||
this.$route.query.dashboard
|
||||
);
|
||||
},
|
||||
updateCurrentDateTimeObj() {
|
||||
@@ -415,6 +395,7 @@ export default defineComponent({
|
||||
this.eventLog.push(msg);
|
||||
},
|
||||
resizedEvent: function (i, newX, newY, newHPx, newWPx) {
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
const msg =
|
||||
"RESIZED i=" +
|
||||
i +
|
||||
@@ -476,25 +457,36 @@ export default defineComponent({
|
||||
|
||||
return 0;
|
||||
},
|
||||
getMinimumHeight(type) {
|
||||
switch (type) {
|
||||
case "area":
|
||||
case "bar":
|
||||
case "h-bar":
|
||||
case "line":
|
||||
case "pie":
|
||||
case "scatter":
|
||||
case "table":
|
||||
return 13;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
},
|
||||
async activated() {
|
||||
let dashboardList = toRaw(this.store.state.allCurrentDashboards);
|
||||
if (Object.keys(dashboardList).length === 0) {
|
||||
await this.refreshAllDashboardsAndGetCurrentDashboard(
|
||||
this.$route.query.dashboard
|
||||
);
|
||||
} else {
|
||||
for (const dashboard of dashboardList) {
|
||||
if (this.$route.query.dashboard === dashboard.name) {
|
||||
this.store.dispatch(
|
||||
"setCurrentSelectedDashboard",
|
||||
JSON.parse(dashboard.details)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
//await this.refreshAllDashboardsAndGetCurrentDashboard(this.$route.query.dashboard);
|
||||
//store.dispatch("setCurrentSelectedDashboard", modDashboardObject);
|
||||
// //get dashboard list from the store
|
||||
// let dashboardList = toRaw(this.store.state.allDashboardList);
|
||||
// //find the dashboard details from the dashboard list using dashboardId
|
||||
// for (const dashboard of dashboardList) {
|
||||
// if (this.$route.query.dashboard === dashboard.name) {
|
||||
// this.currentDashboardData.data = JSON.parse(dashboard.details)
|
||||
// }
|
||||
// }
|
||||
this.currentDashboardData.data = await getDashboard(
|
||||
this.store,
|
||||
this.$route.query.dashboard
|
||||
);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -511,8 +503,12 @@ export default defineComponent({
|
||||
// background: #eee;
|
||||
}
|
||||
|
||||
.vue-grid-item:not(.vue-grid-placeholder) {
|
||||
background: #ccc;
|
||||
// .vue-grid-item:not(.vue-grid-placeholder) {
|
||||
// background: #ccc;
|
||||
// border: 1px solid black;
|
||||
// }
|
||||
|
||||
.vue-grid-item {
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
@@ -584,7 +580,7 @@ export default defineComponent({
|
||||
|
||||
.displayDiv {
|
||||
clear: both;
|
||||
padding: 1.625em 0 0;
|
||||
// padding: 1.625em 0 0;
|
||||
// overflow: auto;
|
||||
}
|
||||
.plotlyBackground {
|
||||
@@ -0,0 +1,218 @@
|
||||
<!-- eslint-disable vue/no-unused-components -->
|
||||
<template>
|
||||
<div style="height: calc(100vh - 57px); overflow-y: auto" class="scroll">
|
||||
<div class="flex justify-between items-center q-pa-sm">
|
||||
<div class="q-table__title q-mr-md">
|
||||
{{ editMode ? t("panel.editPanel") : t("panel.addPanel") }}
|
||||
</div>
|
||||
<div class="flex items-baseline q-gutter-sm">
|
||||
<date-time @date-change="updateDateTime" />
|
||||
<q-btn
|
||||
class="q-ml-md q-mb-xs text-bold"
|
||||
outline
|
||||
padding="sm lg"
|
||||
color="red"
|
||||
no-caps
|
||||
:label="t(`Discard`)"
|
||||
@click="goBackToDashboardList"
|
||||
/>
|
||||
<q-btn
|
||||
class="q-ml-md q-mb-xs text-bold"
|
||||
outline
|
||||
padding="sm lg"
|
||||
color="white"
|
||||
text-color="black"
|
||||
no-caps
|
||||
:label="t(`Save`)"
|
||||
@click="savePanelOnClick"
|
||||
/>
|
||||
<q-btn
|
||||
class="q-ml-md q-mb-xs text-bold no-border"
|
||||
padding="sm lg"
|
||||
color="secondary"
|
||||
no-caps
|
||||
:label="t(`Apply`)"
|
||||
@click="runQuery"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<q-separator></q-separator>
|
||||
<div class="row" style="height: calc(100vh - 118px); overflow-y: auto">
|
||||
<div
|
||||
class="col"
|
||||
style="overflow: hidden; min-width: 75px; max-width: 75px"
|
||||
>
|
||||
<ChartSelection
|
||||
v-model:selectedChartType="dashboardPanelData.data.type"
|
||||
/>
|
||||
</div>
|
||||
<q-separator vertical />
|
||||
<div class="col scroll" style="height: 100%">
|
||||
<GetFields />
|
||||
</div>
|
||||
<q-separator vertical />
|
||||
<div class="col scroll" style="height: 100%">
|
||||
<Layout />
|
||||
</div>
|
||||
<q-separator vertical />
|
||||
<div class="col-7 scroll" style="height: 100%">
|
||||
<SearchBar />
|
||||
<q-separator />
|
||||
<ChartRender
|
||||
:data="chartData"
|
||||
:selectedTimeDate="dashboardPanelData.meta.dateTime"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
defineComponent,
|
||||
ref,
|
||||
computed,
|
||||
toRaw,
|
||||
onActivated,
|
||||
nextTick,
|
||||
} from "vue";
|
||||
import ChartSelection from "../../../components/dashboards/addPanel/ChartSelection.vue";
|
||||
import GetFields from "../../../components/dashboards/addPanel/GetFields.vue";
|
||||
import { useQuasar, date } from "quasar";
|
||||
|
||||
import { useI18n } from "vue-i18n";
|
||||
import {
|
||||
addPanel,
|
||||
getConsumableDateTime,
|
||||
getPanel,
|
||||
updatePanel,
|
||||
} from "../../../utils/commons";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useStore } from "vuex";
|
||||
import Layout from "../../../components/dashboards/addPanel/Layout.vue";
|
||||
import SearchBar from "../../../components/dashboards/SearchBar.vue";
|
||||
import useDashboardPanelData from "../../../composables/useDashboardPanel";
|
||||
import DateTime from "../../../plugins/logs/DateTime.vue";
|
||||
import ChartRender from "../../../components/dashboards/addPanel/ChartRender.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "AddPanel",
|
||||
components: {
|
||||
ChartSelection,
|
||||
GetFields,
|
||||
Layout,
|
||||
SearchBar,
|
||||
DateTime,
|
||||
ChartRender,
|
||||
},
|
||||
setup() {
|
||||
// This will be used to copy the chart data to the chart renderer component
|
||||
// This will deep copy the data object without reactivity and pass it on to the chart renderer
|
||||
const chartData = ref({});
|
||||
const $q = useQuasar();
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const store = useStore();
|
||||
const { dashboardPanelData, resetDashboardPanelData } =
|
||||
useDashboardPanelData();
|
||||
const editMode = ref(false);
|
||||
|
||||
onActivated(async () => {
|
||||
// todo check for the edit more
|
||||
if (route.query.panelId) {
|
||||
editMode.value = true;
|
||||
const panelData = await getPanel(
|
||||
store,
|
||||
route.query.dashboard,
|
||||
route.query.panelId
|
||||
);
|
||||
console.log("panel data", panelData);
|
||||
Object.assign(dashboardPanelData.data, panelData);
|
||||
runQuery();
|
||||
} else {
|
||||
editMode.value = false;
|
||||
resetDashboardPanelData();
|
||||
chartData.value = {};
|
||||
}
|
||||
});
|
||||
|
||||
let list = computed(function () {
|
||||
return [toRaw(store.state.currentSelectedDashboard)];
|
||||
});
|
||||
|
||||
const currentDashboard = toRaw(store.state.currentSelectedDashboard);
|
||||
|
||||
const getDashboard = () => {
|
||||
return currentDashboard.dashboardId;
|
||||
};
|
||||
|
||||
const runQuery = () => {
|
||||
console.log("query change detected to run");
|
||||
|
||||
// copy the data object excluding the reactivity
|
||||
chartData.value = JSON.parse(JSON.stringify(dashboardPanelData.data));
|
||||
};
|
||||
|
||||
const updateDateTime = (value: object) => {
|
||||
dashboardPanelData.meta.dateTime = getConsumableDateTime(value);
|
||||
};
|
||||
const goBack = () => {
|
||||
return router.push({
|
||||
path: "/viewDashboard",
|
||||
query: { dashboard: route.query.dashboard },
|
||||
});
|
||||
};
|
||||
|
||||
const savePanelChangesToDashboard = async (dashId: String) => {
|
||||
if (editMode.value) {
|
||||
await updatePanel(
|
||||
store,
|
||||
dashId,
|
||||
JSON.parse(JSON.stringify(dashboardPanelData.data))
|
||||
);
|
||||
} else {
|
||||
const panelId =
|
||||
"Panel_ID" + Math.floor(Math.random() * (99999 - 10 + 1)) + 10;
|
||||
|
||||
dashboardPanelData.data.id = panelId;
|
||||
await addPanel(
|
||||
store,
|
||||
dashId,
|
||||
JSON.parse(JSON.stringify(dashboardPanelData.data))
|
||||
);
|
||||
}
|
||||
|
||||
await nextTick();
|
||||
return router.push({
|
||||
path: "/viewDashboard?dashbpard=" + dashId,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
t,
|
||||
updateDateTime,
|
||||
goBack,
|
||||
savePanelChangesToDashboard,
|
||||
runQuery,
|
||||
getDashboard,
|
||||
currentDashboard,
|
||||
list,
|
||||
dashboardPanelData,
|
||||
chartData,
|
||||
editMode,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
goBackToDashboardList(evt: any, row: any) {
|
||||
this.goBack();
|
||||
},
|
||||
savePanelOnClick() {
|
||||
this.savePanelChangesToDashboard(this.getDashboard());
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
</style>
|
||||
@@ -1,503 +0,0 @@
|
||||
<!-- Copyright 2022 Zinc Labs Inc. and Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http:www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- eslint-disable vue/v-on-event-hyphenation -->
|
||||
<!-- eslint-disable vue/attribute-hyphenation -->
|
||||
<template>
|
||||
<q-page class="q-pa-md">
|
||||
<div class="flex justify-between items-center q-pa-sm">
|
||||
<div class="q-table__title q-mr-md">{{ t("panel.editPanel") }}</div>
|
||||
<div class="flex items-baseline q-gutter-sm">
|
||||
<date-time
|
||||
ref="refDateTime"
|
||||
v-model="dateVal"
|
||||
@update:date="dateChange"
|
||||
/>
|
||||
<q-btn
|
||||
class="q-ml-md q-mb-xs text-bold"
|
||||
outline
|
||||
padding="sm lg"
|
||||
color="red"
|
||||
no-caps
|
||||
:label="t(`Discard`)"
|
||||
@click="goBacToDashboardList"
|
||||
/>
|
||||
<q-btn
|
||||
class="q-ml-md q-mb-xs text-bold"
|
||||
outline
|
||||
padding="sm lg"
|
||||
color="white"
|
||||
text-color="black"
|
||||
no-caps
|
||||
:label="t(`Save`)"
|
||||
@click="savePanelOnClick"
|
||||
/>
|
||||
<q-btn
|
||||
class="q-ml-md q-mb-xs text-bold no-border"
|
||||
padding="sm lg"
|
||||
color="secondary"
|
||||
no-caps
|
||||
:label="t(`Apply`)"
|
||||
@click="runQuery"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<q-separator></q-separator>
|
||||
<div class="row" style="minheight: 82vh">
|
||||
<div class="col-9">
|
||||
<div style="marginleft: -45px; minheight: 45vh">
|
||||
<div v-if="vizualizationType === 'BarChart'">
|
||||
<ReactiveBarChart
|
||||
ref="plotChart"
|
||||
:data="plotData"
|
||||
@updated:chart="onChartUpdate"
|
||||
></ReactiveBarChart>
|
||||
</div>
|
||||
<div v-else-if="vizualizationType === 'TimeSeries'">
|
||||
<ReactiveLineChart
|
||||
ref="plotChart"
|
||||
:data="plotData"
|
||||
@updated:chart="onChartUpdate"
|
||||
/>
|
||||
</div>
|
||||
<div id="tableCustom1" v-else-if="vizualizationType === 'Table'">
|
||||
<ReactiveTableChart
|
||||
ref="plotChart"
|
||||
:data="plotData"
|
||||
@updated:chart="onChartUpdate"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="vizualizationType === 'Stat'">
|
||||
<ReactiveTableChart
|
||||
ref="plotChart"
|
||||
:data="plotData"
|
||||
@updated:chart="onChartUpdate"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<!-- <ReactiveTableChart
|
||||
ref="plotChart"
|
||||
:data="plotData"
|
||||
@updated:chart="onChartUpdate"
|
||||
/> -->
|
||||
</div>
|
||||
</div>
|
||||
<q-separator></q-separator>
|
||||
|
||||
<q-tabs v-model="panelTab" class="text-teal" align="left">
|
||||
<q-tab name="query" :label="t(`Query`)" no-caps />
|
||||
<q-tab name="transform" :label="t(`Transform`)" no-caps />
|
||||
<q-tab name="alert" :label="t(`Alert`)" no-caps />
|
||||
</q-tabs>
|
||||
|
||||
<q-tab-panels v-model="panelTab" animated>
|
||||
<q-tab-panel name="query">
|
||||
<div class="q-col-gutter-xs" style="width: 100%, height:50%"></div>
|
||||
<div class="q-pa-md" style="max-width: 800px">
|
||||
<q-input
|
||||
v-model="queryText"
|
||||
ref="queryTextA"
|
||||
filled
|
||||
clearable
|
||||
autogrow
|
||||
color="green-8"
|
||||
label="Please provide SQL Query for Visualization"
|
||||
hint="Press Apply Button to complete query preview"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<q-h3 no-caps:label="getDashboard"></q-h3></div
|
||||
></q-tab-panel>
|
||||
<q-tab-panel name="transform"> </q-tab-panel>
|
||||
<q-tab-panel name="alert"> </q-tab-panel>
|
||||
</q-tab-panels>
|
||||
</div>
|
||||
|
||||
<q-separator vertical class="col-shrink" style="width: 1px"></q-separator>
|
||||
|
||||
<div class="col q-pa-md">
|
||||
<q-select
|
||||
dense
|
||||
outlined
|
||||
v-model="vizualizationType"
|
||||
:options="options"
|
||||
label="Type of Visualization"
|
||||
@update:model-value="(value) => selectVizualization(value)"
|
||||
/>
|
||||
|
||||
<q-expansion-item
|
||||
switch-toggle-side
|
||||
:label="t(`Panel Option`)"
|
||||
class="q-mt-md"
|
||||
>
|
||||
<q-form ref="adddashboardForm" class="q-pl-md">
|
||||
<!-- <q-input
|
||||
v-if="beingUpdated"
|
||||
v-model="dashboardData.id"
|
||||
:readonly="beingUpdated"
|
||||
:disabled="beingUpdated"
|
||||
:label="t('panel.id')"
|
||||
/> -->
|
||||
<q-input
|
||||
v-model="panelName"
|
||||
:label="t('panel.name') + '*'"
|
||||
class="q-py-md showLabelOnTop"
|
||||
stack-label
|
||||
filled
|
||||
dense
|
||||
/>
|
||||
<q-input
|
||||
v-model="panelDesc"
|
||||
type="textarea"
|
||||
:label="t('panel.typeDesc') + '*'"
|
||||
class="q-py-md showLabelOnTop"
|
||||
stack-label
|
||||
filled
|
||||
dense
|
||||
/>
|
||||
</q-form>
|
||||
</q-expansion-item>
|
||||
</div>
|
||||
</div>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
// @ts-nocheck
|
||||
import { defineComponent, ref, watch, unref, toRaw, onMounted } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { useQuasar, date } from "quasar";
|
||||
|
||||
import { useI18n } from "vue-i18n";
|
||||
import * as Plotly from "plotly.js";
|
||||
import {
|
||||
getCurrentDashboard,
|
||||
getCurrentPanel,
|
||||
updateAllDashboardsFromBackend,
|
||||
setCurrentPanelToDashboardList,
|
||||
getDateConsumableDateTime,
|
||||
modifySQLQuery,
|
||||
} from "../utils/commons.ts";
|
||||
|
||||
import queryService from "../services/nativequery";
|
||||
import ReactiveLineChart from "../components/shared/plotly/linechart.vue";
|
||||
import ReactiveBarChart from "../components/shared/plotly/barchart.vue";
|
||||
import ReactiveTableChart from "../components/shared/plotly/tablechart.vue";
|
||||
import DateTime from "../plugins/logs/DateTime.vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
export default defineComponent({
|
||||
name: "PageOrganization",
|
||||
components: {
|
||||
ReactiveLineChart,
|
||||
ReactiveBarChart,
|
||||
ReactiveTableChart,
|
||||
DateTime,
|
||||
},
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
const $q = useQuasar();
|
||||
const selectedPerPage = ref<number>(20);
|
||||
const router = useRouter();
|
||||
const plotChart: any = ref({});
|
||||
// let visualizationSelector = "";
|
||||
const store = useStore();
|
||||
const queryTextA: any = "";
|
||||
const plotData: any = ref(null);
|
||||
const refDateTime: any = ref(null);
|
||||
let currentTimeObj = {};
|
||||
const pagination: any = ref({
|
||||
rowsPerPage: 20,
|
||||
});
|
||||
const queryText = ref(null);
|
||||
const vizualizationType = ref("BarChart");
|
||||
|
||||
const dateVal = ref({
|
||||
tab: "relative",
|
||||
startDate: "",
|
||||
startTime: "",
|
||||
endDate: "",
|
||||
endTime: "",
|
||||
|
||||
selectedRelativePeriod: "Minutes",
|
||||
selectedRelativeValue: 15,
|
||||
selectedFullTime: false,
|
||||
});
|
||||
const panelTab = ref("query");
|
||||
const panelName = ref("");
|
||||
const panelDesc = ref("");
|
||||
|
||||
// when the datetime filter changes then update the results
|
||||
watch(dateVal.value, () => {
|
||||
if (dateVal.value) {
|
||||
dateChange(dateVal);
|
||||
}
|
||||
});
|
||||
|
||||
const dateChange = (dateValue: any) => {
|
||||
const c = toRaw(unref(dateValue));
|
||||
currentTimeObj = getDateConsumableDateTime(c);
|
||||
return currentTimeObj;
|
||||
// const b = moment(String(currentTimeObj.start_time)).format('YYYY-MM-DDThh:mm:ssZ')
|
||||
};
|
||||
|
||||
const changePagination = (val: { label: string; value: any }) => {
|
||||
selectedPerPage.value = val.value;
|
||||
pagination.value.rowsPerPage = val.value;
|
||||
};
|
||||
|
||||
const runQuery = () => {
|
||||
const chartParams = {
|
||||
title: "Found " + "2" + " hits in " + "10" + " ms",
|
||||
};
|
||||
|
||||
currentTimeObj = dateChange(dateVal);
|
||||
|
||||
const sqlQueryModified = modifySQLQuery(currentTimeObj, queryText.value);
|
||||
|
||||
const query = { query: { sql: sqlQueryModified, sql_mode: "full" } };
|
||||
queryService
|
||||
.runquery(query, store.state.selectedOrganization.identifier)
|
||||
.then((res) => {
|
||||
if (vizualizationType.value === "Table") {
|
||||
const header_vals = Object.keys(res.data.hits[0]);
|
||||
let data_vals = [];
|
||||
let obj = {};
|
||||
let key = "";
|
||||
for (obj in res.data.hits) {
|
||||
let partobject = [];
|
||||
for (key in header_vals) {
|
||||
partobject.push(res.data.hits[obj][header_vals[key]]);
|
||||
}
|
||||
data_vals.push(partobject);
|
||||
partobject = [];
|
||||
}
|
||||
let output = data_vals[0].map((_, colIndex) =>
|
||||
data_vals.map((row) => row[colIndex])
|
||||
);
|
||||
const styledHeader = header_vals.map(
|
||||
(header) => "<b>" + header + "</b>"
|
||||
);
|
||||
const columnColor = output[0].map((col, index) => {
|
||||
if (index % 2 == 0) {
|
||||
return "#FFF";
|
||||
} else {
|
||||
return "#C9CCE6";
|
||||
}
|
||||
});
|
||||
plotData.value = {
|
||||
id: 1,
|
||||
type: "table",
|
||||
header: {
|
||||
values: styledHeader,
|
||||
align: "center",
|
||||
height: 30,
|
||||
line: { width: 1, color: "black" },
|
||||
fill: { color: "#7A80C2" },
|
||||
font: { family: "Arial", size: 14, color: "white" },
|
||||
},
|
||||
cells: {
|
||||
values: output,
|
||||
align: "center",
|
||||
height: 25,
|
||||
line: { color: "black", width: 1 },
|
||||
fill: { color: [columnColor] },
|
||||
font: { family: "Arial", size: 11, color: ["black"] },
|
||||
},
|
||||
};
|
||||
} else {
|
||||
let x_data = [];
|
||||
let y_data = [];
|
||||
res.data.hits.map((data: any) => {
|
||||
x_data.push(data.x_axis);
|
||||
y_data.push(data.y_axis);
|
||||
});
|
||||
plotData.value = {
|
||||
id: 1,
|
||||
x: x_data,
|
||||
y: y_data,
|
||||
chartParams: chartParams,
|
||||
};
|
||||
}
|
||||
$q.notify({
|
||||
type: "positive",
|
||||
message: "Query applied successfully.",
|
||||
timeout: 5000,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
$q.notify({
|
||||
type: "negative",
|
||||
message: "Something went wrong!",
|
||||
timeout: 5000,
|
||||
});
|
||||
});
|
||||
};
|
||||
const goBack = () => {
|
||||
return router.push("/dashboard");
|
||||
};
|
||||
|
||||
const selectVizualization = (value) => {
|
||||
vizualizationType.value = value;
|
||||
// visualizationSelector = value;
|
||||
};
|
||||
|
||||
const getDashboard = () => {
|
||||
return this.$route.query.dashboard;
|
||||
};
|
||||
|
||||
const renderCurrentPanel = async (currentPanel) => {
|
||||
vizualizationType.value = currentPanel.type || "BarChart";
|
||||
queryText.value = currentPanel?.query?.[0];
|
||||
panelName.value = currentPanel?.name || "";
|
||||
panelDesc.value = currentPanel?.title || "";
|
||||
if (queryText.value) await runQuery();
|
||||
};
|
||||
|
||||
const onChartUpdate = ({ start, end }: { start: any; end: any }) => {
|
||||
emit("updated:dates", { start, end });
|
||||
};
|
||||
|
||||
const savePanelChangesToDashboard = async (
|
||||
dashId: String,
|
||||
panelId: String
|
||||
) => {
|
||||
await updateAllDashboardsFromBackend(store);
|
||||
let modDashboardObject = toRaw(store.state.currentSelectedDashboard);
|
||||
let dashboardList = toRaw(store.state.allCurrentDashboards);
|
||||
|
||||
const currentDashboard = await getCurrentDashboard(store, dashId);
|
||||
const currentPanel = await getCurrentPanel(store, dashId, panelId);
|
||||
|
||||
currentTimeObj = dateChange(dateVal);
|
||||
const sqlQueryModified = modifySQLQuery(currentTimeObj, queryText.value);
|
||||
|
||||
const maxI =
|
||||
currentDashboard.layouts?.length > 0
|
||||
? Math.max(...currentDashboard.layouts?.map((obj) => obj.i))
|
||||
: 0;
|
||||
const maxY =
|
||||
currentDashboard.layouts?.length > 0
|
||||
? Math.max(...currentDashboard.layouts?.map((obj) => obj.y))
|
||||
: 0;
|
||||
|
||||
const newLayoutObj = {
|
||||
x: 0,
|
||||
y: currentDashboard.layouts?.length > 0 ? maxY + 10 : 0,
|
||||
w: 12,
|
||||
h: 12,
|
||||
i: maxI + 1,
|
||||
panelId: panelId,
|
||||
static: false,
|
||||
};
|
||||
const panelObject2 = {
|
||||
id: panelId,
|
||||
query: [sqlQueryModified],
|
||||
title: panelDesc.value,
|
||||
type: vizualizationType.value,
|
||||
name: panelName.value,
|
||||
};
|
||||
setCurrentPanelToDashboardList(
|
||||
dashboardList,
|
||||
dashId,
|
||||
panelId,
|
||||
panelObject2,
|
||||
store,
|
||||
newLayoutObj
|
||||
);
|
||||
return router.push({
|
||||
path: "/viewDashboard",
|
||||
query: { dashboard: dashId },
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
t,
|
||||
goBack,
|
||||
queryText,
|
||||
currentTimeObj,
|
||||
savePanelChangesToDashboard,
|
||||
onChartUpdate,
|
||||
selectVizualization,
|
||||
// getVizualizationSelector,
|
||||
plotChart,
|
||||
plotData,
|
||||
runQuery,
|
||||
store,
|
||||
getDashboard,
|
||||
vizualizationType,
|
||||
queryTextA,
|
||||
// visualizationSelector,
|
||||
filterQuery: ref(""),
|
||||
renderCurrentPanel,
|
||||
refDateTime,
|
||||
dateVal,
|
||||
dateChange,
|
||||
filterData(rows: string | any[], terms: string) {
|
||||
const filtered = [];
|
||||
terms = terms.toLowerCase();
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
if (rows[i]["name"].toLowerCase().includes(terms)) {
|
||||
filtered.push(rows[i]);
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
},
|
||||
model: ref(null),
|
||||
options: ["TimeSeries", "BarChart", "Table"],
|
||||
panelTab,
|
||||
panelName,
|
||||
panelDesc,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
goBacToDashboardList(evt, row) {
|
||||
this.goBack();
|
||||
},
|
||||
selectVizualization() {
|
||||
this.selectVizualization();
|
||||
},
|
||||
savePanelOnClick() {
|
||||
this.savePanelChangesToDashboard(
|
||||
this.$route.query.dashboard,
|
||||
this.$route.query.panelId
|
||||
);
|
||||
},
|
||||
},
|
||||
activated() {
|
||||
const dashboardList = toRaw(this.store.state.allCurrentDashboards);
|
||||
let currentPanel = {};
|
||||
for (const dashboard of dashboardList) {
|
||||
if (this.$route.query.dashboard === dashboard.name) {
|
||||
for (const panel of JSON.parse(toRaw(dashboard.details)).panels) {
|
||||
if (this.$route.query.panelId === panel.id) {
|
||||
currentPanel = panel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.renderCurrentPanel(currentPanel);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.q-table {
|
||||
&__top {
|
||||
border-bottom: 1px solid $border-color;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||