Merge pull request #8 from pemeraldy/mod/filters-layout

Mod/filters layout
This commit is contained in:
Brandon Fergerson 2022-05-18 17:57:45 +04:00 committed by GitHub
commit 44f3763a54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1254 additions and 443 deletions

View File

@ -37,7 +37,7 @@ limitations under the License. -->
</el-select>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import { ref, watch, onMounted } from "vue";
import type { PropType } from "vue";
interface Option {
@ -95,4 +95,7 @@ watch(
.el-input__inner {
border-radius: unset !important;
}
.el-input.el-input--small.el-input--suffix {
height: 18px !important;
}
</style>

View File

@ -27,7 +27,8 @@ async function query(param: {
{
cancelToken: cancelToken(),
headers: {
Authorization: "Basic c2t5d2Fsa2luZzpza3l3YWxraW5n",
Authorization:
"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJkZXZlbG9wZXJfaWQiOiJzeXN0ZW0iLCJjcmVhdGVkX2F0IjoxNjIyNDIxMzY0ODY4LCJleHBpcmVzX2F0IjoxNjUzOTU3MzY0ODY4LCJpYXQiOjE2MjI0MjEzNjR9.ZVHtxQkfCF7KM_dyDOgawbwpEAsmnCWB4c8I52svPvVc-SlzkEe0SYrNufNPniYZeM3IF0Gbojl_DSk2KleAz9CLRO3zfegciXKeEEvGjsNOqfQjgU5yZtBWmTimVXq5QoZMEGuAojACaf-m4J0H7o4LQNGwrDVA-noXVE0Eu84A5HxkjrRuFlQWv3fzqSRC_-lI0zRKuFGD-JkIfJ9b_wP_OjBWT6nmqkZn_JmK7UwniTUJjocszSA2Ma3XLx2xVPzBcz00QWyjhIyiftxNQzgqLl1XDVkRtzXUIrHnFCR8BcgR_PsqTBn5nH7aCp16zgmkkbOpmJXlNpDSVz9zUY4NOrB1jTzDB190COrfCXddb7JO6fmpet9_Zd3kInJx4XsT3x7JfBSWr9FBqFoUmNkgIWjkbN1TpwMyizXASp1nOmwJ64FDIbSpfpgUAqfSWXKZYhSisfnBLEyHCjMSPzVmDh949w-W1wU9q5nGFtrx6PTOxK_WKOiWU8_oeTjL0pD8pKXqJMaLW-OIzfrl3kzQNuF80YT-nxmNtp5PrcxehprlPmqSB_dyTHccsO3l63d8y9hiIzfRUgUjTJbktFn5t41ADARMs_0WMpIGZJyxcVssstt4J1Gj8WUFOdqPsIKigJZMn3yshC5S-KY-7S0dVd0VXgvpPqmpb9Q9Uho",
},
}
);

View File

@ -56,7 +56,8 @@ class Graphql {
{
cancelToken: cancelToken(),
headers: {
Authorization: "Basic c2t5d2Fsa2luZzpza3l3YWxraW5n",
Authorization:
"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJkZXZlbG9wZXJfaWQiOiJzeXN0ZW0iLCJjcmVhdGVkX2F0IjoxNjIyNDIxMzY0ODY4LCJleHBpcmVzX2F0IjoxNjUzOTU3MzY0ODY4LCJpYXQiOjE2MjI0MjEzNjR9.ZVHtxQkfCF7KM_dyDOgawbwpEAsmnCWB4c8I52svPvVc-SlzkEe0SYrNufNPniYZeM3IF0Gbojl_DSk2KleAz9CLRO3zfegciXKeEEvGjsNOqfQjgU5yZtBWmTimVXq5QoZMEGuAojACaf-m4J0H7o4LQNGwrDVA-noXVE0Eu84A5HxkjrRuFlQWv3fzqSRC_-lI0zRKuFGD-JkIfJ9b_wP_OjBWT6nmqkZn_JmK7UwniTUJjocszSA2Ma3XLx2xVPzBcz00QWyjhIyiftxNQzgqLl1XDVkRtzXUIrHnFCR8BcgR_PsqTBn5nH7aCp16zgmkkbOpmJXlNpDSVz9zUY4NOrB1jTzDB190COrfCXddb7JO6fmpet9_Zd3kInJx4XsT3x7JfBSWr9FBqFoUmNkgIWjkbN1TpwMyizXASp1nOmwJ64FDIbSpfpgUAqfSWXKZYhSisfnBLEyHCjMSPzVmDh949w-W1wU9q5nGFtrx6PTOxK_WKOiWU8_oeTjL0pD8pKXqJMaLW-OIzfrl3kzQNuF80YT-nxmNtp5PrcxehprlPmqSB_dyTHccsO3l63d8y9hiIzfRUgUjTJbktFn5t41ADARMs_0WMpIGZJyxcVssstt4J1Gj8WUFOdqPsIKigJZMn3yshC5S-KY-7S0dVd0VXgvpPqmpb9Q9Uho",
},
}
)

View File

@ -269,6 +269,7 @@ const msg = {
contentType: "Content Type",
content: "Content",
viewLogs: "View Logs",
back: "Back",
logsTagsTip: `Only tags defined in the core/default/searchableLogsTags are searchable.
Check more details on the Configuration Vocabulary page`,
keywordsOfContentLogTips:

View File

@ -40,6 +40,8 @@ interface DashboardState {
showTopology: boolean;
fullView: boolean;
currentTabItems: LayoutConfig[];
showTraceTools: boolean;
showLogTools: boolean;
dashboards: DashboardItem[];
currentDashboard: Nullable<DashboardItem>;
editMode: boolean;
@ -57,6 +59,8 @@ export const dashboardStore = defineStore({
durationTime: useAppStoreWithOut().durationTime,
selectorStore: useSelectorStore(),
showTopology: false,
showLogTools: false,
showTraceTools: false,
fullView: false,
currentTabItems: [],
dashboards: [],
@ -260,6 +264,12 @@ export const dashboardStore = defineStore({
setEntity(type: string) {
this.entity = type;
},
setTraceTools(show: boolean) {
this.showTraceTools = show;
},
setLogTools(show: boolean) {
this.showLogTools = show;
},
setTopology(show: boolean) {
this.showTopology = show;
},

View File

@ -29,6 +29,9 @@ interface TraceState {
instances: Instance[];
endpoints: Endpoint[];
traceList: Trace[];
activeFilter: string;
displayMode: string;
currentView: string;
traceTotal: number;
traceSpans: Span[];
currentTrace: Trace | any;
@ -48,6 +51,9 @@ export const traceStore = defineStore({
services: [{ value: "0", label: "All" }],
instances: [{ value: "0", label: "All" }],
endpoints: [{ value: "0", label: "All" }],
displayMode: "List",
currentView: "traceList",
activeFilter: "",
traceList: [],
traceSpans: [],
traceTotal: 0,
@ -67,6 +73,16 @@ export const traceStore = defineStore({
setTraceCondition(data: any) {
this.condition = { ...this.condition, ...data };
},
setDisplayMode(data: string) {
this.displayMode = data;
},
setCurrentView(data: string) {
this.currentView = data;
},
setActiveFilter(data: string) {
if (!data) this.activeFilter = "";
this.activeFilter = data;
},
setCurrentTrace(trace: Trace) {
this.currentTrace = trace;
},

View File

@ -1,6 +1,7 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/vue-next/pull/3399
import '@vue/runtime-core'
declare module '@vue/runtime-core' {
export interface GlobalComponents {
@ -45,4 +46,4 @@ declare module '@vue/runtime-core' {
}
}
export { }
export {}

View File

@ -14,12 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="flex-h" :class="{ light: theme === 'light' }">
<div class="mr-5">
<div class="flex-h items-center mr-5">
<span class="sm grey" v-show="theme === 'dark'">{{ t("tags") }}: </span>
<span
class="trace-tags"
:style="type === 'LOG' ? `min-width: 122px;` : ''"
>
<span v-if="tagsList.length" class="trace-tags">
<!-- :style="type === 'LOG' ? `min-width: 122px;` : ''" -->
<span class="selected" v-for="(item, index) in tagsList" :key="index">
<span>{{ item }}</span>
<span class="remove-icon" @click="removeTags(index)">×</span>
@ -34,6 +32,7 @@ limitations under the License. -->
/>
<span class="tags-tip">
<a
v-if="false"
target="blank"
href="https://github.com/apache/skywalking/blob/master/docs/en/setup/backend/configuration-vocabulary.md"
>
@ -54,17 +53,18 @@ limitations under the License. -->
<Icon class="icon-help mr-5" iconName="help" size="middle" />
</span>
</el-tooltip>
<b v-if="type !== 'LOG'">{{ t("noticeTag") }}</b>
<!-- <b v-if="type !== 'LOG'">{{ t("noticeTag") }}</b> -->
</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { ref, defineExpose } from "vue";
import { useI18n } from "vue-i18n";
/*global defineEmits, defineProps */
const emit = defineEmits(["update"]);
defineProps({
type: { type: String, default: "TRACE" },
});
@ -73,6 +73,13 @@ const theme = ref<string>("dark");
const tags = ref<string>("");
const tagsList = ref<string[]>([]);
defineExpose({
tagsList,
emptyTags
})
function emptyTags (){
tagsList.value = []
}
function removeTags(index: number) {
tagsList.value.splice(index, 1);
updateTags();
@ -97,6 +104,9 @@ function updateTags() {
}
</script>
<style lang="scss" scoped>
.items-center {
align-items: center;
}
.trace-tags {
padding: 1px 5px 0 0;
border-radius: 3px;

View File

@ -29,18 +29,17 @@ limitations under the License. -->
<span>{{ t("delete") }}</span>
</div>
</el-popover>
<div class="header">
<Header />
</div>
<!-- <div class="header">
</div> -->
<div class="log">
<List />
</div>
</div>
</template>
<script lang="ts" setup>
import type { onBeforeUnmount, onMounted } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/log/Header.vue";
import List from "../related/log/List.vue";
/*global defineProps */
@ -57,6 +56,12 @@ const dashboardStore = useDashboardStore();
function removeWidget() {
dashboardStore.removeControls(props.data);
}
onMounted(() => {
dashboardStore.setLogTools(true);
});
onBeforeUnmount(() => {
dashboardStore.setLogTools(false);
});
</script>
<style lang="scss" scoped>
.log-wrapper {

View File

@ -147,14 +147,7 @@ limitations under the License. -->
</div>
</template>
<script lang="ts">
import {
ref,
watch,
onMounted,
onBeforeUnmount,
defineComponent,
toRefs,
} from "vue";
import { ref, watch, onMounted, onBeforeUnmount, defineComponent, toRefs } from "vue";
import { useI18n } from "vue-i18n";
import type { PropType } from "vue";
import { LayoutConfig } from "@/types/dashboard";
@ -194,9 +187,7 @@ export default defineComponent({
const currentItem = ref<number>(0);
const isScrolling = ref(false);
const l = dashboardStore.layout.findIndex(
(d: LayoutConfig) => d.i === props.data.i
);
const l = dashboardStore.layout.findIndex((d: LayoutConfig) => d.i === props.data.i);
if (dashboardStore.layout[l].children.length) {
dashboardStore.setCurrentTabItems(
dashboardStore.layout[l].children[activeTabIndex.value].children
@ -314,9 +305,7 @@ export default defineComponent({
function clickTabGrid(e: Event, item: LayoutConfig) {
e.stopPropagation();
activeTabWidget.value = item.i;
dashboardStore.activeGridItem(
`${props.data.i}-${activeTabIndex.value}-${item.i}`
);
dashboardStore.activeGridItem(`${props.data.i}-${activeTabIndex.value}-${item.i}`);
handleClick(e);
}
function layoutUpdatedEvent() {
@ -484,7 +473,9 @@ export default defineComponent({
color: #409eff;
}
}
.tab-header .tabs .span.active {
color: red !important;
}
.operations {
color: #aaa;
cursor: pointer;

View File

@ -29,22 +29,19 @@ limitations under the License. -->
<span>{{ t("delete") }}</span>
</div>
</el-popover>
<div class="header">
<Filter />
</div>
<div class="trace flex-h">
<TraceList />
<TraceDetail />
<TraceList @show:trace="showTraceDetails" v-if="traceListActive" />
<TraceDetail @show:list="showTraceList" v-if="!traceListActive" />
</div>
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import Filter from "../related/trace/Filter.vue";
import type { PropType, computed, onMounted, onBeforeUnmount } from "vue";
import TraceList from "../related/trace/TraceList.vue";
import TraceDetail from "../related/trace/Detail.vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useTraceStore } from "@/store/modules/trace";
/*global defineProps */
const props = defineProps({
@ -56,9 +53,26 @@ const props = defineProps({
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const traceStore = useTraceStore();
const traceListActive = computed(() => {
return traceStore.currentView === "traceList";
});
function removeWidget() {
dashboardStore.removeControls(props.data);
}
function showTraceDetails() {
traceStore.currentView === "traceDetails";
}
function showTraceList() {
traceStore.currentView === "traceList";
}
onMounted(() => {
dashboardStore.setTraceTools(true);
});
onBeforeUnmount(() => {
dashboardStore.setTraceTools(false);
});
</script>
<style lang="scss" scoped>
.trace-wrapper {
@ -95,6 +109,7 @@ function removeWidget() {
.trace {
width: 100%;
height: 100%;
overflow: auto;
}
</style>

View File

@ -15,115 +15,182 @@ limitations under the License. -->
<template>
<div class="dashboard-tool flex-h">
<div class="flex-h">
<div class="selectors-item" v-if="key !== 10">
<span class="label">$Service</span>
<Selector
v-model="states.currentService"
:options="selectorStore.services"
size="small"
placeholder="Select a service"
@change="changeService"
class="selectors"
/>
<div class="flex-h">
<div
class="selectors-item"
v-if="key !== 10 && currentTraceView === 'traceList'"
>
<el-tooltip
class="box-item"
effect="dark"
content="Services"
placement="top-start"
>
<el-button
v-if="!selectedSelector.length || selectedSelector === '$service'"
@click="setSelectedSelector('$service')"
class="tool-btn"
size="small"
>
<Icon size="sm" iconName="playlist_add" />
</el-button>
</el-tooltip>
<Selector
v-if="selectedSelector === '$service'"
style="margin-left: 20px"
v-model="states.currentService"
:options="selectorStore.services"
size="small"
placeholder="Select a service"
@change="changeService"
class="selectors"
/>
<el-button
style="margin-left: 4px"
v-if="selectedSelector === '$service'"
class="search-btn tool-btn"
size="small"
type="danger"
@click="closeSelector"
>
<Icon iconSize="sm" iconName="cancel" />
</el-button>
</div>
<div
class="selectors-item"
v-if="(key === 3 || key === 4) && currentTraceView === 'traceList'"
>
<el-tooltip
v-if="!selectedSelector.length || selectedSelector === '$endpoint'"
class="box-item"
effect="dark"
content="Endpoint"
placement="top-start"
>
<el-button
style="margin-left: 4px"
@click="setSelectedSelector('$endpoint')"
class="tool-btn"
>
<Icon size="sm" iconName="view" />
</el-button>
</el-tooltip>
<Selector
v-if="selectedSelector === '$endpoint'"
style="margin-left: 20px"
v-model="states.currentPod"
:options="selectorStore.pods"
size="small"
placeholder="Select a data"
@change="changePods"
@query="searchPods"
class="selectorPod"
:isRemote="
['EndpointRelation', 'Endpoint'].includes(dashboardStore.entity)
"
/>
<el-button
style="margin-left: 4px"
v-if="selectedSelector === '$endpoint'"
class="search-btn"
size="small"
type="danger"
@click="closeSelector"
>
<Icon iconSize="sm" iconName="cancel" />
</el-button>
</div>
<div class="selectors-item" v-if="key === 2 || key === 4">
<span class="label">$DestinationService</span>
<Selector
v-model="states.currentDestService"
:options="selectorStore.destServices"
size="small"
placeholder="Select a service"
@change="changeDestService"
class="selectors"
/>
</div>
<div class="selectors-item" v-if="key === 4">
<span class="label">
{{
dashboardStore.entity === "EndpointRelation"
? "$DestinationEndpoint"
: "$DestinationServiceInstance"
}}
</span>
<Selector
v-model="states.currentDestPod"
:options="selectorStore.destPods"
size="small"
placeholder="Select a data"
@change="changeDestPods"
class="selectorPod"
@query="searchDestPods"
:isRemote="dashboardStore.entity === 'EndpointRelation'"
/>
</div>
</div>
<div class="selectors-item" v-if="key === 3 || key === 4">
<span class="label">
{{
["EndpointRelation", "Endpoint"].includes(dashboardStore.entity)
? "$Endpoint"
: "$ServiceInstance"
}}
</span>
<Selector
v-model="states.currentPod"
:options="selectorStore.pods"
size="small"
placeholder="Select a data"
@change="changePods"
@query="searchPods"
class="selectorPod"
:isRemote="
['EndpointRelation', 'Endpoint'].includes(dashboardStore.entity)
"
/>
</div>
<div class="selectors-item" v-if="key === 2 || key === 4">
<span class="label">$DestinationService</span>
<Selector
v-model="states.currentDestService"
:options="selectorStore.destServices"
size="small"
placeholder="Select a service"
@change="changeDestService"
class="selectors"
/>
</div>
<div class="selectors-item" v-if="key === 4">
<span class="label">
{{
dashboardStore.entity === "EndpointRelation"
? "$DestinationEndpoint"
: "$DestinationServiceInstance"
}}
</span>
<Selector
v-model="states.currentDestPod"
:options="selectorStore.destPods"
size="small"
placeholder="Select a data"
@change="changeDestPods"
class="selectorPod"
@query="searchDestPods"
:isRemote="dashboardStore.entity === 'EndpointRelation'"
/>
</div>
</div>
<div class="flex-h tools" v-loading="loading" v-if="$route.query['portal'] !== 'true'">
<div class="tool-icons flex-h" v-if="dashboardStore.editMode">
<el-dropdown content="Controls" placement="bottom">
<i>
<Icon class="icon-btn" size="sm" iconName="control" />
</i>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
@click="clickIcons(t)"
v-for="(t, index) in toolIcons"
:key="index"
:title="t.content"
>
<Icon class="mr-5" size="middle" :iconName="t.name" />
<span>{{ t.content }}</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-tooltip content="Apply" placement="bottom" effect="light">
<i @click="applyDashboard">
<Icon class="icon-btn" size="sm" iconName="save" />
</i>
</el-tooltip>
</div>
<div class="switch">
<el-switch
v-model="dashboardStore.editMode"
active-text="Edit"
inactive-text="View"
size="small"
inline-prompt
active-color="#409eff"
inactive-color="#999"
@change="changeMode"
/>
<div
class="flex-h tools"
v-loading="loading"
v-if="$route.query['portal'] !== 'true'"
>
<div class="tool-icons flex-h" v-if="dashboardStore.editMode">
<el-dropdown content="Controls" placement="bottom">
<i>
<Icon class="icon-btn" size="sm" iconName="control" />
</i>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
@click="clickIcons(t)"
v-for="(t, index) in toolIcons"
:key="index"
:title="t.content"
>
<Icon class="mr-5" size="middle" :iconName="t.name" />
<span>{{ t.content }}</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-tooltip content="Apply" placement="bottom" effect="light">
<i @click="applyDashboard">
<Icon class="icon-btn" size="sm" iconName="save" />
</i>
</el-tooltip>
</div>
<div class="switch">
<el-switch
v-model="dashboardStore.editMode"
active-text="Edit"
inactive-text="View"
size="small"
inline-prompt
active-color="#409eff"
inactive-color="#999"
@change="changeMode"
/>
</div>
</div>
</div>
<Header v-if="showLogHeader" />
<TraceDetailsTools
v-if="showTraceHeader && currentTraceView === 'traceDetails'"
/>
<Filter v-if="showTraceHeader && currentTraceView === 'traceList'" />
</div>
</template>
<script lang="ts" setup>
import Filter from "../related/trace/Filter.vue";
import Header from "../related/log/Header.vue";
import { reactive, ref, computed, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useRoute } from "vue-router";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useTraceStore } from "@/store/modules/trace";
import {
EntityType,
AllTools,
@ -138,13 +205,17 @@ import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
import { Option } from "@/types/app";
import { useI18n } from "vue-i18n";
import TraceDetailsTools from "./component/TraceDetailsTools";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore();
const appStore = useAppStoreWithOut();
const traceStore = useTraceStore();
const params = useRoute().params;
const selectedSelector = ref<string>("");
const showTraceHeader = computed(() => dashboardStore.showTraceTools);
const showLogHeader = computed(() => dashboardStore.showLogTools);
const currentTraceView = computed(() => traceStore.currentView);
const { query } = useRoute();
dashboardStore.setViewMode(query["fullview"] === "true");
@ -176,6 +247,12 @@ const key = computed(() => {
return (type && type.key) || 0;
});
function setSelectedSelector(selector: string) {
selectedSelector.value = selector;
}
function closeSelector() {
selectedSelector.value = "";
}
setCurrentDashboard();
appStore.setEventStack([initSelector]);
initSelector();
@ -362,6 +439,7 @@ async function getServices() {
}
async function changeService(service: any) {
selectedSelector.value = "";
if (service[0]) {
states.currentService = service[0].value;
selectorStore.setCurrentService(service[0]);
@ -381,6 +459,7 @@ function changeDestService(service: any) {
}
function changePods(pod: any) {
selectedSelector.value = "";
if (pod[0]) {
selectorStore.setCurrentPod(pod[0]);
} else {
@ -636,6 +715,7 @@ watch(
background: rgb(240, 242, 245);
border-bottom: 1px solid #dfe4e8;
justify-content: space-between;
align-items: center;
}
.switch {
@ -682,4 +762,11 @@ watch(
.selectorPod {
width: 300px;
}
.tool-btn {
height: 18px;
}
.el-input__wrapper {
height: 18px !important;
}
</style>

View File

@ -0,0 +1,339 @@
<template>
<div
class="trace-detail-wrapper flex-h"
v-if="traceStore.currentTrace.endpointNames"
>
<div class="mb-0 mt-0">
<Icon
icon="clear"
v-if="traceStore.currentTrace.isError"
class="red mr-5 sm"
/>
<div class="trace-log-btn">
<el-tooltip
class="box-item"
effect="dark"
content="Back"
placement="bottom-start"
>
<el-button
size="small"
class="mr-10 filter-btn"
type="primary"
@click="showTraceList"
>
<Icon iconSize="sm" iconName="chevron-left" />
</el-button>
</el-tooltip>
<el-tooltip
class="box-item"
effect="dark"
:content="t('viewLogs')"
placement="bottom-start"
>
<el-button
size="small"
class="mr-10 filter-btn"
type="primary"
@click="searchTraceLogs"
>
<Icon iconSize="sm" iconName="folder_open" />
</el-button>
</el-tooltip>
</div>
<el-dialog
v-model="showTraceLogs"
:destroy-on-close="true"
fullscreen
@closed="showTraceLogs = false"
>
<div>
<el-pagination
v-model:currentPage="pageNum"
v-model:page-size="pageSize"
:small="true"
:total="traceStore.traceSpanLogsTotal"
@current-change="turnLogsPage"
/>
<LogTable
:tableData="traceStore.traceSpanLogs || []"
:type="`service`"
:noLink="true"
>
<div class="log-tips" v-if="!traceStore.traceSpanLogs.length">
{{ t("noData") }}
</div>
</LogTable>
</div>
</el-dialog>
</div>
<div class="mb- blue sm">
<span class="vm">{{ traceStore.currentTrace.endpointNames[0] }}</span>
<Selector
size="small"
:value="
traceStore.currentTrace.traceIds &&
traceStore.currentTrace.traceIds[0] &&
traceStore.currentTrace.traceIds[0].value
"
:options="traceStore.currentTrace.traceIds"
@change="changeTraceId"
class="trace-detail-ids"
/>
<el-tooltip
class="box-item"
effect="dark"
content="Copy Ids"
placement="bottom-start"
>
<el-button
size="small"
class="mr-10 copy-btn"
type="primary"
@click="handleClick(traceStore.currentTrace.traceIds)"
>
<Icon iconSize="sm" iconName="review-list" />
</el-button>
</el-tooltip>
</div>
<div class="flex-h item">
<div>
<el-tooltip
class="box-item"
effect="dark"
:content="t('list')"
placement="bottom-start"
>
<el-button
class="filter-btn"
size="small"
:class="{ ghost: displayMode === 'List' }"
@click="changeDisplayMode('List')"
>
<Icon class="mr-5" size="sm" iconName="list-bulleted" />
</el-button>
</el-tooltip>
<el-tooltip
class="box-item"
effect="dark"
:content="t('tree')"
placement="bottom-start"
>
<el-button
class="filter-btn"
size="small"
:class="{ ghost: displayMode === 'Tree' }"
@click="changeDisplayMode('Tree')"
>
<Icon class="mr-5" size="sm" iconName="issue-child" />
</el-button>
</el-tooltip>
<el-tooltip
class="box-item"
effect="dark"
:content="t('table')"
placement="bottom-start"
>
<el-button
class="filter-btn"
size="small"
:class="{ ghost: displayMode === 'Table' }"
@click="changeDisplayMode('Table')"
>
<Icon class="mr-5" size="sm" iconName="table" />
</el-button>
</el-tooltip>
<el-tooltip
class="box-item"
effect="dark"
:content="t('statistics')"
placement="bottom-start"
>
<el-button
class="filter-btn"
size="small"
:class="{ ghost: displayMode === 'Statistics' }"
@click="changeDisplayMode('Statistics')"
>
<Icon class="mr-5" size="sm" iconName="statistics-bulleted" />
</el-button>
</el-tooltip>
</div>
</div>
</div>
<div class="no-data" v-else>t("noData")</div>
</template>
<script lang="ts">
import dayjs from "dayjs";
import { ref, computed, defineComponent } from "vue";
import { useI18n } from "vue-i18n";
import { useTraceStore } from "@/store/modules/trace";
import { Option } from "@/types/app";
import copy from "@/utils/copy";
import LogTable from "@/views/dashboard/related/components/LogTable/Index.vue";
import { ElMessage } from "element-plus";
export default defineComponent({
name: "TraceDetailsTools",
components: {
LogTable,
},
setup(props, ctx) {
const { t } = useI18n();
const traceStore = useTraceStore();
const loading = ref<boolean>(false);
const traceId = ref<string>("");
const displayMode = computed(() => {
return traceStore.displayMode;
});
const pageNum = ref<number>(1);
const pageSize = 10;
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern);
const showTraceLogs = ref<boolean>(false);
function showTraceList() {
traceStore.setCurrentView("traceList");
}
function handleClick(ids: string[] | any) {
let copyValue = null;
if (ids.length === 1) {
copyValue = ids[0].value;
} else {
copyValue = ids.map((trace: any) => trace.value).join(",");
}
copy(copyValue);
}
function changeDisplayMode(mode: string) {
traceStore.displayMode = mode;
}
async function changeTraceId(opt: Option[] | any) {
traceId.value = opt[0].value;
loading.value = true;
const res = await traceStore.getTraceSpans({ traceId: opt[0].value });
if (res.errors) {
ElMessage.error(res.errors);
}
loading.value = false;
}
async function searchTraceLogs() {
showTraceLogs.value = true;
const res = await traceStore.getSpanLogs({
condition: {
relatedTrace: {
traceId: traceId.value || traceStore.currentTrace.traceIds[0].value,
},
paging: { pageNum: pageNum.value, pageSize, needTotal: true },
},
});
if (res.errors) {
ElMessage.error(res.errors);
}
}
function turnLogsPage(page: number) {
pageNum.value = page;
searchTraceLogs();
}
return {
showTraceList,
changeDisplayMode,
traceStore,
displayMode,
dateFormat,
changeTraceId,
handleClick,
t,
searchTraceLogs,
showTraceLogs,
turnLogsPage,
pageSize,
pageNum,
loading,
};
},
});
</script>
<style lang="scss" scoped>
.trace-detail {
height: 100%;
width: 100%;
overflow: hidden;
}
.trace-chart {
height: calc(100% - 100px);
overflow: auto;
padding-bottom: 20px;
}
.trace-chart.full-view {
height: calc(100% - 1px) !important;
}
.trace-detail-wrapper {
font-size: 12px;
width: 100%;
justify-content: space-between;
align-items: center;
.grey {
color: #fff;
background-color: #448dfe;
}
.ghost {
cursor: pointer;
background: rgba(4, 147, 114, 1);
}
}
.item {
justify-content: space-between;
}
.trace-detail-ids {
background-color: rgba(0, 0, 0, 0);
outline: 0;
border-style: unset;
color: inherit;
border: 1px solid;
border-radius: 4px;
width: 300px;
}
.trace-log-btn {
float: right;
}
.tag {
display: inline-block;
border-radius: 4px;
padding: 0px 7px;
background-color: #40454e;
color: #eee;
}
.no-data {
padding-top: 50px;
width: 100%;
text-align: center;
}
.vm {
margin-right: 4px;
}
.filter-btn {
height: 18px;
margin: 0 5px;
}
.copy-btn {
height: 18px;
width: 10px;
margin: 0 5px;
}
</style>

View File

@ -13,114 +13,192 @@ 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 class="flex-h row">
<div class="mr-5" v-if="dashboardStore.entity === EntityType[1].value">
<span class="grey mr-5">{{ t("service") }}:</span>
<Selector
size="small"
:value="state.service.value"
:options="logStore.services"
placeholder="Select a service"
@change="changeField('service', $event)"
/>
<div class="flex-h log-wrapper">
<div v-if="!currentSearchTerm.length" class="flex-h items-center">
<div v-for="(item, index) in arrayOfFilters" :key="index">
<el-tooltip
class="box-item"
effect="dark"
:content="item.description"
placement="bottom-start"
>
<el-button
type="success"
:class="[activeTerms.includes(item.name) ? 'active-toggle' : '']"
class="toggle-btn mx-3"
v-show="item.isVisible"
@click="setSearchTerm(item.name)"
>
<Icon iconSize="sm" :iconName="item.iconName" />
</el-button>
</el-tooltip>
</div>
</div>
<div class="mr-5" v-if="dashboardStore.entity !== EntityType[3].value">
<span class="grey mr-5">
{{ isBrowser ? t("version") : t("instance") }}:
</span>
<Selector
size="small"
:value="state.instance.value"
:options="logStore.instances"
placeholder="Select a instance"
@change="changeField('instance', $event)"
/>
<div class="flex-h items-center">
<div class="flex-h items-center" v-if="currentSearchTerm === 'service'">
<div
class="mr-5 flex-h items-center"
v-if="dashboardStore.entity === EntityType[1].value"
>
<span class="grey mr-5">{{ t("service") }}:</span>
<Selector
size="small"
:value="state.service.value"
:options="logStore.services"
placeholder="Select a service"
@change="changeField('service', $event)"
/>
</div>
<b v-else>{{ t("service") }} data not available</b>
</div>
<div class="flex-h items-center" v-if="currentSearchTerm === 'instance'">
<div
class="mr-5 items-center flex-h"
v-if="
dashboardStore.entity !== EntityType[3].value &&
currentSearchTerm === 'instance'
"
>
<span class="grey mr-5">
{{ isBrowser ? t("version") : t("instance") }}:
</span>
<Selector
size="small"
:value="state.instance.value"
:options="logStore.instances"
placeholder="Select a instance"
@change="changeField('instance', $event)"
/>
</div>
<b v-else>{{ t("instance") }} data not available</b>
</div>
<div class="flex-h items-center" v-if="currentSearchTerm === 'endpoints'">
<div
class="mr-5 flex-h items-center"
v-if="
dashboardStore.entity !== EntityType[2].value &&
currentSearchTerm === 'endpoints'
"
>
<span class="grey mr-5"
>{{ isBrowser ? t("page") : t("endpoint") }}:</span
>
<Selector
size="small"
:value="state.endpoint.value"
:options="logStore.endpoints"
placeholder="Select a endpoint"
@change="changeField('endpoint', $event)"
:isRemote="true"
@query="searchEndpoints"
/>
</div>
<b v-else>{{ t("endpoint") }} data not available</b>
</div>
</div>
<div class="mr-5" v-if="dashboardStore.entity !== EntityType[2].value">
<span class="grey mr-5"
>{{ isBrowser ? t("page") : t("endpoint") }}:</span
<!-- <div class="row tips">
<b>{{ t("conditionNotice") }}</b>
</div> -->
<div class="flex-h items-center">
<div class="mr-5 flex-h items-center traceId" v-show="!isBrowser">
<div class="flex-h items-center" v-if="currentSearchTerm === 'traceId'">
<span class="grey mr-5">{{ t("traceID") }}:</span>
<el-input v-model="traceId" class="inputs-max" size="small" />
</div>
</div>
<keep-alive>
<ConditionTags
ref="logTagsComponent"
v-if="currentSearchTerm === 'tags'"
:type="'LOG'"
@update="updateTags"
/>
</keep-alive>
</div>
<div class="flex-h items-center" v-show="!isBrowser">
<div
class="mr-5 flex-h items-center"
v-show="supportQueryLogsByKeywords && currentSearchTerm === 'keywords'"
>
<Selector
size="small"
:value="state.endpoint.value"
:options="logStore.endpoints"
placeholder="Select a endpoint"
@change="changeField('endpoint', $event)"
:isRemote="true"
@query="searchEndpoints"
/>
</div>
</div>
<div class="row tips">
<b>{{ t("conditionNotice") }}</b>
</div>
<div class="flex-h row">
<div class="mr-5 traceId" v-show="!isBrowser">
<span class="grey mr-5">{{ t("traceID") }}:</span>
<el-input v-model="traceId" class="inputs-max" size="small" />
</div>
<ConditionTags :type="'LOG'" @update="updateTags" />
</div>
<div class="flex-h" v-show="!isBrowser">
<div class="mr-5" v-show="logStore.supportQueryLogsByKeywords">
<span class="mr-5 grey">{{ t("keywordsOfContent") }}:</span>
<span class="log-tags">
<span
class="selected"
v-for="(item, index) in keywordsOfContent"
:key="`keywordsOfContent${index}`"
>
<span>{{ item }}</span>
<span class="remove-icon" @click="removeContent(index)">×</span>
</span>
</span>
<el-input
size="small"
class="inputs-max"
:placeholder="t('addKeywordsOfContent')"
v-model="contentStr"
@change="addLabels('keywordsOfContent')"
/>
</div>
<div class="mr-5" v-show="logStore.supportQueryLogsByKeywords">
<span class="grey mr-5"> {{ t("excludingKeywordsOfContent") }}: </span>
<span class="log-tags">
<span
class="selected"
v-for="(item, index) in excludingKeywordsOfContent"
:key="`excludingKeywordsOfContent${index}`"
>
<span>{{ item }}</span>
<span class="remove-icon" @click="removeExcludeContent(index)">
×
<span class="mr-5 grey">{{ t("keywordsOfContent") }}:</span>
<span class="log-tags">
<span
class="selected"
v-for="(item, index) in keywordsOfContent"
:key="`keywordsOfContent${index}`"
>
<span>{{ item }}</span>
<span class="remove-icon" @click="removeContent(index)">×</span>
</span>
</span>
</span>
<el-input
class="inputs-max"
size="small"
:placeholder="t('addExcludingKeywordsOfContent')"
v-model="excludingContentStr"
@change="addLabels('excludingKeywordsOfContent')"
/>
<el-tooltip :content="t('keywordsOfContentLogTips')">
<span class="log-tips" v-show="!logStore.supportQueryLogsByKeywords">
<Icon icon="help" class="mr-5" />
<el-input
size="small"
class="inputs-max"
:placeholder="t('addKeywordsOfContent')"
v-model="contentStr"
@change="addLabels('keywordsOfContent')"
/>
</div>
<div
class="mr-5 flex-h items-center"
v-show="
supportExcludeQueryLogsByKeywords && currentSearchTerm === 'exclude'
"
>
<span class="grey mr-5"> {{ t("excludingKeywordsOfContent") }}: </span>
<span class="log-tags">
<span
class="selected"
v-for="(item, index) in excludingKeywordsOfContent"
:key="`excludingKeywordsOfContent${index}`"
>
<span>{{ item }}</span>
<span class="remove-icon" @click="removeExcludeContent(index)">
×
</span>
</span>
</span>
</el-tooltip>
<el-input
class="inputs-max"
size="small"
:placeholder="t('addExcludingKeywordsOfContent')"
v-model="excludingContentStr"
@change="addLabels('excludingKeywordsOfContent')"
/>
<el-tooltip :content="t('keywordsOfContentLogTips')">
<span class="log-tips" v-show="!logStore.supportQueryLogsByKeywords">
<Icon icon="help" class="mr-5" />
</span>
</el-tooltip>
</div>
<!-- Search&cancel buttons -->
<div v-if="currentSearchTerm.length" class="flex-h items-center">
<el-button
class="search-btn toggle-btn"
size="small"
type="primary"
@click="searchLogs"
>
<Icon iconSize="sm" iconName="search" />
</el-button>
<el-button
class="search-btn toggle-btn"
size="small"
type="primary"
@click="cancelSearchTerm"
>
<Icon iconSize="sm" iconName="cancel" />
</el-button>
</div>
</div>
<el-button
class="search-btn"
size="small"
type="primary"
@click="searchLogs"
>
{{ t("search") }}
</el-button>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from "vue";
import { ref, reactive, watch, computed } from "vue";
import { useI18n } from "vue-i18n";
import { Option } from "@/types/app";
import { useLogStore } from "@/store/modules/log";
@ -139,6 +217,15 @@ const logStore = useLogStore();
const traceId = ref<string>("");
const keywordsOfContent = ref<string[]>([]);
const excludingKeywordsOfContent = ref<string[]>([]);
const supportQueryLogsByKeywords = computed<boolean>(() => {
return logStore.supportQueryLogsByKeywords
})
const supportExcludeQueryLogsByKeywords = computed<boolean>(() => {
return logStore.supportQueryLogsByKeywords
})
const currentSearchTerm = ref<string>("");
const activeTerms = ref<string[]>([]);
const tagsList = ref<string[]>([]);
const tagsMap = ref<Option[]>([]);
const contentStr = ref<string>("");
@ -149,7 +236,57 @@ const state = reactive<any>({
endpoint: { value: "0", label: "All" },
service: { value: "", label: "" },
});
const logTagsComponent = ref<InstanceType<typeof ConditionTags> | null>(null);
interface filtersObject {
name: string;
iconName: string;
description: string;
isVisible?: boolean | unknown; // one of the situations is dependent on an api call
}
const arrayOfFilters = ref<filtersObject[]>([
{
name: "traceId",
iconName: "timeline",
description: "Trace ID",
isVisible: true,
},
{
name: "tags",
iconName: "epic",
description: "Tags",
isVisible: true,
},
{
name: "keywords",
iconName: "library_books",
description: "Keywords",
isVisible: supportQueryLogsByKeywords,
},
{
name: "exclude",
iconName: "issue-child",
description: "Exclude keywords",
isVisible: supportExcludeQueryLogsByKeywords,
},
{
name: "instance",
iconName: "epic",
description: "Instance",
isVisible: dashboardStore.entity !== EntityType[3].value,
},
{
name: "service",
iconName: "settings",
description: "Service",
isVisible: dashboardStore.entity === EntityType[1].value,
},
{
name: "endpoints",
iconName: "timeline",
description: "Endpoints",
isVisible: dashboardStore.entity !== EntityType[2].value,
},
]);
init();
async function init() {
const resp = await logStore.getLogsByKeywords();
@ -210,7 +347,46 @@ async function getInstances(id?: string) {
}
state.instance = logStore.instances[0];
}
function addToActiveTerms() {
activeTerms.value.push(currentSearchTerm.value);
}
function removeFromActiveTerms() {
activeTerms.value = activeTerms.value.filter(
(term) => term !== currentSearchTerm.value
);
}
function handleActiveSearchTerms() {
switch (currentSearchTerm.value) {
case "traceId":
if (!traceId.value.length) return;
addToActiveTerms();
break;
case "tags":
if (!tagsList.value.length) return;
addToActiveTerms();
break;
case "keywords":
if (!keywordsOfContent.value.length) return;
addToActiveTerms();
break;
case "exclude":
if (!excludingKeywordsOfContent.value.length) return;
addToActiveTerms();
break;
case "instance":
addToActiveTerms();
break;
case "service":
addToActiveTerms();
break;
case "endpoints":
addToActiveTerms();
break;
}
}
function searchLogs() {
handleActiveSearchTerms();
currentSearchTerm.value = "";
let endpoint = "",
instance = "";
if (dashboardStore.entity === EntityType[2].value) {
@ -293,6 +469,40 @@ function removeExcludeContent(index: number) {
});
excludingContentStr.value = "";
}
function setSearchTerm(term: string) {
currentSearchTerm.value = term;
}
function cancelSearchTerm() {
switch (currentSearchTerm.value) {
case "traceId":
traceId.value = "";
break;
case "tags":
tagsList.value = [];
tagsMap.value = [];
logTagsComponent.value?.emptyTags();
break;
case "keywords":
keywordsOfContent.value = [];
break;
case "exclude":
excludingKeywordsOfContent.value = [];
break;
case "instance":
state.instance = { value: "0", label: "All" };
break;
case "endpoints":
state.endpoint = { value: "0", label: "All" };
getEndpoints();
break;
case "service":
state.service = { value: "", label: "" };
break;
}
removeFromActiveTerms();
currentSearchTerm.value = "";
searchLogs()
}
watch(
() => selectorStore.currentService,
() => {
@ -320,6 +530,12 @@ watch(
);
</script>
<style lang="scss" scoped>
// .log-wrapper {
// width: 600px;
// padding-left: 40px;
// overflow-x: scroll;
// align-items: center;
// }
.inputs {
width: 120px;
}
@ -378,4 +594,22 @@ watch(
margin-left: 3px;
cursor: pointer;
}
/* buttons*/
.el-button span {
font-size: 10px !important;
}
.toggle-btn {
height: 18px;
margin: 0 5px;
}
.active-toggle.toggle-btn {
background: rgba(4, 147, 114, 1) !important;
span {
color: #275410 !important;
}
}
.items-center {
align-items: center;
}
</style>

View File

@ -12,130 +12,10 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="trace-detail" v-loading="loading">
<div
class="trace-detail-wrapper clear"
v-if="traceStore.currentTrace.endpointNames"
>
<h5 class="mb-5 mt-0">
<Icon
icon="clear"
v-if="traceStore.currentTrace.isError"
class="red mr-5 sm"
/>
<span class="vm">{{ traceStore.currentTrace.endpointNames[0] }}</span>
<div class="trace-log-btn">
<el-button
size="small"
class="mr-10"
type="primary"
@click="searchTraceLogs"
>
{{ t("viewLogs") }}
</el-button>
</div>
<el-dialog
v-model="showTraceLogs"
:destroy-on-close="true"
fullscreen
@closed="showTraceLogs = false"
>
<div>
<el-pagination
v-model:currentPage="pageNum"
v-model:page-size="pageSize"
:small="true"
:total="traceStore.traceSpanLogsTotal"
@current-change="turnLogsPage"
/>
<LogTable
:tableData="traceStore.traceSpanLogs || []"
:type="`service`"
:noLink="true"
>
<div class="log-tips" v-if="!traceStore.traceSpanLogs.length">
{{ t("noData") }}
</div>
</LogTable>
</div>
</el-dialog>
</h5>
<div class="mb-5 blue sm">
<Selector
size="small"
:value="
traceStore.currentTrace.traceIds &&
traceStore.currentTrace.traceIds[0] &&
traceStore.currentTrace.traceIds[0].value
"
:options="traceStore.currentTrace.traceIds"
@change="changeTraceId"
class="trace-detail-ids"
/>
<Icon
size="sm"
class="icon grey link-hover cp ml-5"
iconName="review-list"
@click="handleClick"
/>
</div>
<div class="flex-h item">
<div>
<div class="tag mr-5">{{ t("start") }}</div>
<span class="mr-15 sm">
{{ dateFormat(parseInt(traceStore.currentTrace.start)) }}
</span>
<div class="tag mr-5">{{ t("duration") }}</div>
<span class="mr-15 sm"
>{{ traceStore.currentTrace.duration }} ms</span
>
<div class="tag mr-5">{{ t("spans") }}</div>
<span class="sm">{{ traceStore.traceSpans.length }}</span>
</div>
<div>
<el-button
class="grey"
size="small"
:class="{ ghost: displayMode !== 'List' }"
@click="displayMode = 'List'"
>
<Icon class="mr-5" size="sm" iconName="list-bulleted" />
{{ t("list") }}
</el-button>
<el-button
class="grey"
size="small"
:class="{ ghost: displayMode !== 'Tree' }"
@click="displayMode = 'Tree'"
>
<Icon class="mr-5" size="sm" iconName="issue-child" />
{{ t("tree") }}
</el-button>
<el-button
class="grey"
size="small"
:class="{ ghost: displayMode !== 'Table' }"
@click="displayMode = 'Table'"
>
<Icon class="mr-5" size="sm" iconName="table" />
{{ t("table") }}
</el-button>
<el-button
class="grey"
size="small"
:class="{ ghost: displayMode !== 'Statistics' }"
@click="displayMode = 'Statistics'"
>
<Icon class="mr-5" size="sm" iconName="statistics-bulleted" />
{{ t("statistics") }}
</el-button>
</div>
</div>
</div>
<div class="no-data" v-else>{{ t("noData") }}</div>
<div class="trace-chart">
<div :class="{ 'full-view': isFullView }" class="trace-chart">
<component
v-if="traceStore.currentTrace.endpointNames"
:is="displayMode"
:is="traceStore.displayMode"
:data="traceStore.traceSpans"
:traceId="traceStore.currentTrace.traceIds[0].value"
:showBtnDetail="false"
@ -146,7 +26,8 @@ limitations under the License. -->
</template>
<script lang="ts">
import dayjs from "dayjs";
import { ref, defineComponent } from "vue";
import { ref, computed, defineComponent } from "vue";
import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n";
import { useTraceStore } from "@/store/modules/trace";
import { Option } from "@/types/app";
@ -155,7 +36,7 @@ import List from "./components/List.vue";
import graphs from "./components/index";
import LogTable from "@/views/dashboard/related/components/LogTable/Index.vue";
import { ElMessage } from "element-plus";
// import TraceDetailsTools from '@/views/dashboard'
export default defineComponent({
name: "TraceDetail",
components: {
@ -163,18 +44,27 @@ export default defineComponent({
List,
LogTable,
},
setup() {
setup(props, ctx) {
const { t } = useI18n();
const traceStore = useTraceStore();
const loading = ref<boolean>(false);
const traceId = ref<string>("");
const displayMode = ref<string>("List");
const queries = useRoute().query;
const isFullView = computed(() => {
return queries?.fullview === "true" && queries?.portal === "true";
});
const displayMode = computed(() => {
return traceStore.displayMode;
});
const pageNum = ref<number>(1);
const pageSize = 10;
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern);
const showTraceLogs = ref<boolean>(false);
function showTraceList() {
ctx.emit("show:list");
}
function handleClick(ids: string[] | any) {
let copyValue = null;
if (ids.length === 1) {
@ -215,6 +105,8 @@ export default defineComponent({
searchTraceLogs();
}
return {
isFullView,
showTraceList,
traceStore,
displayMode,
dateFormat,
@ -233,17 +125,21 @@ export default defineComponent({
</script>
<style lang="scss" scoped>
.trace-detail {
// min-height: 300px;
height: 100%;
width: 100%;
overflow: hidden;
}
.trace-chart {
height: calc(100% - 100px);
height: 100%;
// height: calc(100% - 100px);
overflow: auto;
padding-bottom: 20px;
}
.trace-chart.full-view {
height: calc(100% - 1px) !important;
}
.trace-detail-wrapper {
font-size: 12px;
padding: 5px 10px;

View File

@ -13,83 +13,122 @@ 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 class="flex-h row">
<div class="mr-5" v-if="dashboardStore.entity === EntityType[1].value">
<span class="grey mr-5">{{ t("service") }}:</span>
<Selector
size="small"
:value="state.service.value"
:options="traceStore.services"
placeholder="Select a service"
@change="changeField('service', $event)"
/>
</div>
<div class="mr-5" v-if="dashboardStore.entity !== EntityType[3].value">
<span class="grey mr-5">{{ t("instance") }}:</span>
<Selector
size="small"
:value="state.instance.value"
:options="traceStore.instances"
placeholder="Select a instance"
@change="changeField('instance', $event)"
/>
</div>
<div class="mr-5" v-if="dashboardStore.entity !== EntityType[2].value">
<span class="grey mr-5">{{ t("endpoint") }}:</span>
<Selector
size="small"
:value="state.endpoint.value"
:options="traceStore.endpoints"
placeholder="Select a endpoint"
:isRemote="true"
@change="changeField('endpoint', $event)"
@query="searchEndpoints"
/>
</div>
<div class="mr-5">
<span class="grey mr-5">{{ t("status") }}:</span>
<Selector
size="small"
:value="state.status.value"
:options="Status"
placeholder="Select a status"
@change="changeField('status', $event)"
/>
</div>
<div class="mr-5">
<span class="grey mr-5">{{ t("traceID") }}:</span>
<el-input size="small" v-model="traceId" class="traceId" />
</div>
</div>
<div class="flex-h">
<!-- <div class="mr-5">
<span class="grey mr-5">{{ t("timeRange") }}:</span>
<TimePicker
:value="dateTime"
position="bottom"
format="YYYY-MM-DD HH:mm"
@input="changeTimeRange"
/>
</div> -->
<div class="mr-5">
<span class="sm b grey mr-5">{{ t("duration") }}:</span>
<el-input size="small" class="inputs mr-5" v-model="minTraceDuration" />
<span class="grey mr-5">-</span>
<el-input size="small" class="inputs" v-model="maxTraceDuration" />
<div class="flex-h filter-container">
<div v-for="(filter, index) in arrayOfFilters" :key="index">
<el-tooltip
v-if="!activeFilter.length || activeFilter === filter.name"
class="box-item"
effect="dark"
:content="filter.description"
placement="bottom-start"
>
<el-button
type="success"
:class="[listOfActiveFilters.includes(filter.name) ? 'active-filter' : '']"
class="filter-btn mx-3"
@click="setFilter(filter.name)"
>
<Icon size="sm" :iconName="filter.iconName" />
</el-button>
</el-tooltip>
</div>
</div>
<div class="wrap-filters">
<div class="filter" v-if="activeFilter === 'service'">
<span class="grey mr-5">{{ t("service") }}:</span>
<Selector
size="small"
:value="state.service.value"
:options="traceStore.services"
placeholder="Select a service"
@change="changeField('service', $event)"
/>
</div>
<div
class="filter"
v-if="
activeFilter === 'instance' && dashboardStore.entity !== EntityType[3].value
"
>
<span class="grey mr-5">{{ t("instance") }}:</span>
<Selector
size="small"
:value="state.instance.value"
:options="traceStore.instances"
placeholder="Select a instance"
@change="changeField('instance', $event)"
/>
</div>
<div
class="filter"
v-if="
dashboardStore.entity !== EntityType[2].value && activeFilter === 'endpoints'
"
>
<span class="grey mr-5">{{ t("endpoint") }}:</span>
<Selector
size="small"
:value="state.endpoint.value"
:options="traceStore.endpoints"
placeholder="Select a endpoint"
:isRemote="true"
@change="changeField('endpoint', $event)"
@query="searchEndpoints"
/>
</div>
<div v-if="activeFilter === 'status'" class="filter">
<span class="grey mr-5">{{ t("status") }}:</span>
<Selector
size="small"
:value="state.status.value"
:options="Status"
placeholder="Select a status"
@change="changeField('status', $event)"
/>
</div>
<div v-if="activeFilter === 'traceId'" class="filter">
<span class="grey mr-5">{{ t("traceID") }}:</span>
<el-input size="small" v-model="traceId" class="traceId" />
</div>
<div v-if="activeFilter === 'duration'" class="filter">
<span class="sm b grey mr-5">{{ t("duration") }}:</span>
<el-input size="small" class="inputs mr-5" v-model="minTraceDuration" />
<span class="grey mr-5">-</span>
<el-input size="small" class="inputs" v-model="maxTraceDuration" />
</div>
<keep-alive>
<ConditionTags
v-if="activeFilter === 'tags'"
ref="traceTagsComponent"
:type="'TRACE'"
@update="updateTags"
/>
</keep-alive>
<el-button
v-if="activeFilter"
class="search-btn filter-btn"
size="small"
type="primary"
@click="searchTraces"
>
<Icon iconSize="sm" iconName="search" />
</el-button>
<el-button
v-if="activeFilter"
class="search-btn filter-btn"
size="small"
type="danger"
@click="cancelSearch"
>
<Icon iconSize="sm" iconName="cancel" />
</el-button>
</div>
<ConditionTags :type="'TRACE'" @update="updateTags" />
<el-button
class="search-btn"
size="small"
type="primary"
@click="searchTraces"
>
{{ t("search") }}
</el-button>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from "vue";
import { ref, reactive, watch, computed } from "vue";
import { useI18n } from "vue-i18n";
import { Option } from "@/types/app";
import { Status } from "../../data";
@ -101,11 +140,60 @@ import ConditionTags from "@/views/components/ConditionTags.vue";
import { ElMessage } from "element-plus";
import { EntityType } from "../../data";
interface filtersObject {
name: string;
iconName: string;
description: string;
}
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const traceStore = useTraceStore();
const listOfActiveFilters = ref<string[]>([]);
const arrayOfFilters = ref<filtersObject[]>([
{
name: "service",
iconName: "cloud_queue",
description: "Service",
},
{
name: "instance",
iconName: "storage",
description: "Instance",
},
{
name: "status",
iconName: "device_hub",
description: "Status",
},
{
name: "duration",
iconName: "av_timer",
description: "Duration",
},
{
name: "traceId",
iconName: "timeline",
description: "Trace ID",
},
{
name: "tags",
iconName: "epic",
description: "Tags",
},
{
name: "endpoints",
iconName: "device_hub",
description: "Endpoints",
},
]);
const activeFilter = ref<string>("");
function setFilter(filter: string) {
activeFilter.value = filter;
}
const traceId = ref<string>("");
const minTraceDuration = ref<string>("");
const maxTraceDuration = ref<string>("");
@ -118,6 +206,8 @@ const state = reactive<any>({
service: { value: "", label: "" },
});
const traceTagsComponent = ref<InstanceType<typeof ConditionTags> | null>(null);
// const dateTime = computed(() => [
// appStore.durationRow.start,
// appStore.durationRow.end,
@ -167,7 +257,92 @@ async function getInstances(id?: string) {
}
state.instance = traceStore.instances[0];
}
function addToActiveFilterList() {
listOfActiveFilters.value.push(activeFilter.value);
}
function removeFromActiveFilters() {
listOfActiveFilters.value = listOfActiveFilters.value.filter(
(filter) => filter !== activeFilter.value
);
}
function cancelSearch() {
switch (activeFilter.value) {
case "status":
state.status = { label: "All", value: "ALL" };
break;
case "instance":
state.instance = { value: "0", label: "All" };
break;
case "endpoints":
state.endpoint = { value: "0", label: "All" };
break;
case "service":
state.service = { value: "", label: "" };
break;
case "duration":
minTraceDuration.value = "";
maxTraceDuration.value = "";
break;
case "tags":
tagsList.value = [];
tagsMap.value = [];
updateTags({ tagsMap: [], tagsList: [] });
traceTagsComponent.value?.emptyTags();
break;
case "traceId":
traceId.value = "";
break;
}
removeFromActiveFilters();
activeFilter.value = "";
traceStore.activeFilter = "";
searchTraces();
}
function handleActiveFilterState() {
switch (activeFilter.value) {
case "traceId":
if (!traceId.value.length) return;
traceStore.setActiveFilter(activeFilter.value);
addToActiveFilterList();
break;
case "tags":
if (!tagsList.value.length) return;
traceStore.setActiveFilter(activeFilter.value);
addToActiveFilterList();
break;
case "duration":
if (!minTraceDuration.value.length || !maxTraceDuration.value.length) return;
traceStore.setActiveFilter(activeFilter.value);
addToActiveFilterList();
break;
case "service":
traceStore.setActiveFilter(activeFilter.value);
addToActiveFilterList();
break;
case "instance":
traceStore.setActiveFilter(activeFilter.value);
addToActiveFilterList();
break;
case "status":
traceStore.setActiveFilter(activeFilter.value);
addToActiveFilterList();
break;
case "endpoints":
traceStore.setActiveFilter(activeFilter.value);
addToActiveFilterList();
break;
}
}
function searchTraces() {
handleActiveFilterState();
activeFilter.value = "";
let endpoint = "",
instance = "";
if (dashboardStore.entity === EntityType[2].value) {
@ -185,8 +360,8 @@ function searchTraces() {
serviceInstanceId: instance || state.instance.id || undefined,
traceState: state.status.value || "ALL",
queryDuration: appStore.durationTime,
minTraceDuration: appStore.minTraceDuration || undefined,
maxTraceDuration: appStore.maxTraceDuration || undefined,
minTraceDuration: minTraceDuration.value || undefined,
maxTraceDuration: maxTraceDuration.value || undefined,
queryOrder: "BY_DURATION",
tags: tagsMap.value.length ? tagsMap.value : undefined,
paging: { pageNum: 1, pageSize: 15, needTotal: true },
@ -260,4 +435,28 @@ watch(
margin-left: 20px;
cursor: pointer;
}
.filter-container {
align-items: center;
}
.wrap-filters {
padding: 0 10px;
display: flex;
flex-wrap: wrap;
align-items: center;
.filter {
margin: 0;
display: flex;
align-items: center;
}
}
.filter-btn {
height: 18px;
margin: 0 5px;
}
.active-filter.filter-btn {
background: rgba(4, 147, 114, 1) !important;
span {
color: #275410 !important;
}
}
</style>

View File

@ -110,6 +110,7 @@ function changeSort(opt: Option[] | any) {
}
async function selectTrace(i: Trace) {
traceStore.setCurrentView("traceDetails");
traceStore.setCurrentTrace(i);
selectedKey.value = i.key;
if (i.traceIds.length) {
@ -136,6 +137,7 @@ async function queryTraces() {
border-bottom: 1px solid #c1c5ca41;
border-right: 1px solid #c1c5ca41;
height: 35px;
align-items: center;
}
.selectors {
@ -163,11 +165,11 @@ async function queryTraces() {
}
.trace-t {
width: 420px;
width: 100%;
}
.list {
width: 400px;
width: 100%;
}
.trace-tr {

View File

@ -26,7 +26,7 @@ module.exports = {
proxy: {
"/graphql": {
target: `${
process.env.SW_PROXY_TARGET || "http://demo.skywalking.apache.org"
process.env.SW_PROXY_TARGET || "https://demo.sourceplus.plus:12800"
}`,
changeOrigin: true,
},