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

View File

@ -27,7 +27,8 @@ async function query(param: {
{ {
cancelToken: cancelToken(), cancelToken: cancelToken(),
headers: { 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(), cancelToken: cancelToken(),
headers: { 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", contentType: "Content Type",
content: "Content", content: "Content",
viewLogs: "View Logs", viewLogs: "View Logs",
back: "Back",
logsTagsTip: `Only tags defined in the core/default/searchableLogsTags are searchable. logsTagsTip: `Only tags defined in the core/default/searchableLogsTags are searchable.
Check more details on the Configuration Vocabulary page`, Check more details on the Configuration Vocabulary page`,
keywordsOfContentLogTips: keywordsOfContentLogTips:

View File

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

View File

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

View File

@ -1,6 +1,7 @@
// generated by unplugin-vue-components // generated by unplugin-vue-components
// We suggest you to commit this file into source control // We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/vue-next/pull/3399 // Read more: https://github.com/vuejs/vue-next/pull/3399
import '@vue/runtime-core'
declare module '@vue/runtime-core' { declare module '@vue/runtime-core' {
export interface GlobalComponents { 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. --> limitations under the License. -->
<template> <template>
<div class="flex-h" :class="{ light: theme === 'light' }"> <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="sm grey" v-show="theme === 'dark'">{{ t("tags") }}: </span>
<span <span v-if="tagsList.length" class="trace-tags">
class="trace-tags" <!-- :style="type === 'LOG' ? `min-width: 122px;` : ''" -->
:style="type === 'LOG' ? `min-width: 122px;` : ''"
>
<span class="selected" v-for="(item, index) in tagsList" :key="index"> <span class="selected" v-for="(item, index) in tagsList" :key="index">
<span>{{ item }}</span> <span>{{ item }}</span>
<span class="remove-icon" @click="removeTags(index)">×</span> <span class="remove-icon" @click="removeTags(index)">×</span>
@ -34,6 +32,7 @@ limitations under the License. -->
/> />
<span class="tags-tip"> <span class="tags-tip">
<a <a
v-if="false"
target="blank" target="blank"
href="https://github.com/apache/skywalking/blob/master/docs/en/setup/backend/configuration-vocabulary.md" 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" /> <Icon class="icon-help mr-5" iconName="help" size="middle" />
</span> </span>
</el-tooltip> </el-tooltip>
<b v-if="type !== 'LOG'">{{ t("noticeTag") }}</b> <!-- <b v-if="type !== 'LOG'">{{ t("noticeTag") }}</b> -->
</span> </span>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue"; import { ref, defineExpose } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
/*global defineEmits, defineProps */ /*global defineEmits, defineProps */
const emit = defineEmits(["update"]); const emit = defineEmits(["update"]);
defineProps({ defineProps({
type: { type: String, default: "TRACE" }, type: { type: String, default: "TRACE" },
}); });
@ -73,6 +73,13 @@ const theme = ref<string>("dark");
const tags = ref<string>(""); const tags = ref<string>("");
const tagsList = ref<string[]>([]); const tagsList = ref<string[]>([]);
defineExpose({
tagsList,
emptyTags
})
function emptyTags (){
tagsList.value = []
}
function removeTags(index: number) { function removeTags(index: number) {
tagsList.value.splice(index, 1); tagsList.value.splice(index, 1);
updateTags(); updateTags();
@ -97,6 +104,9 @@ function updateTags() {
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.items-center {
align-items: center;
}
.trace-tags { .trace-tags {
padding: 1px 5px 0 0; padding: 1px 5px 0 0;
border-radius: 3px; border-radius: 3px;

View File

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

View File

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

View File

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

View File

@ -15,115 +15,182 @@ limitations under the License. -->
<template> <template>
<div class="dashboard-tool flex-h"> <div class="dashboard-tool flex-h">
<div class="flex-h"> <div class="flex-h">
<div class="selectors-item" v-if="key !== 10"> <div class="flex-h">
<span class="label">$Service</span> <div
<Selector class="selectors-item"
v-model="states.currentService" v-if="key !== 10 && currentTraceView === 'traceList'"
:options="selectorStore.services" >
size="small" <el-tooltip
placeholder="Select a service" class="box-item"
@change="changeService" effect="dark"
class="selectors" 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>
<div class="selectors-item" v-if="key === 3 || key === 4"> <div
<span class="label"> class="flex-h tools"
{{ v-loading="loading"
["EndpointRelation", "Endpoint"].includes(dashboardStore.entity) v-if="$route.query['portal'] !== 'true'"
? "$Endpoint" >
: "$ServiceInstance" <div class="tool-icons flex-h" v-if="dashboardStore.editMode">
}} <el-dropdown content="Controls" placement="bottom">
</span> <i>
<Selector <Icon class="icon-btn" size="sm" iconName="control" />
v-model="states.currentPod" </i>
:options="selectorStore.pods" <template #dropdown>
size="small" <el-dropdown-menu>
placeholder="Select a data" <el-dropdown-item
@change="changePods" @click="clickIcons(t)"
@query="searchPods" v-for="(t, index) in toolIcons"
class="selectorPod" :key="index"
:isRemote=" :title="t.content"
['EndpointRelation', 'Endpoint'].includes(dashboardStore.entity) >
" <Icon class="mr-5" size="middle" :iconName="t.name" />
/> <span>{{ t.content }}</span>
</div> </el-dropdown-item>
<div class="selectors-item" v-if="key === 2 || key === 4"> </el-dropdown-menu>
<span class="label">$DestinationService</span> </template>
<Selector </el-dropdown>
v-model="states.currentDestService" <el-tooltip content="Apply" placement="bottom" effect="light">
:options="selectorStore.destServices" <i @click="applyDashboard">
size="small" <Icon class="icon-btn" size="sm" iconName="save" />
placeholder="Select a service" </i>
@change="changeDestService" </el-tooltip>
class="selectors" </div>
/> <div class="switch">
</div> <el-switch
<div class="selectors-item" v-if="key === 4"> v-model="dashboardStore.editMode"
<span class="label"> active-text="Edit"
{{ inactive-text="View"
dashboardStore.entity === "EndpointRelation" size="small"
? "$DestinationEndpoint" inline-prompt
: "$DestinationServiceInstance" active-color="#409eff"
}} inactive-color="#999"
</span> @change="changeMode"
<Selector />
v-model="states.currentDestPod" </div>
: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> </div>
</div> </div>
<Header v-if="showLogHeader" />
<TraceDetailsTools
v-if="showTraceHeader && currentTraceView === 'traceDetails'"
/>
<Filter v-if="showTraceHeader && currentTraceView === 'traceList'" />
</div> </div>
</template> </template>
<script lang="ts" setup> <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 { reactive, ref, computed, watch } from "vue";
import { useRoute, useRouter } from "vue-router"; import { useRoute } from "vue-router";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { useTraceStore } from "@/store/modules/trace";
import { import {
EntityType, EntityType,
AllTools, AllTools,
@ -138,13 +205,17 @@ import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { Option } from "@/types/app"; import { Option } from "@/types/app";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import TraceDetailsTools from "./component/TraceDetailsTools";
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore(); const selectorStore = useSelectorStore();
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const traceStore = useTraceStore();
const params = useRoute().params; 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(); const { query } = useRoute();
dashboardStore.setViewMode(query["fullview"] === "true"); dashboardStore.setViewMode(query["fullview"] === "true");
@ -176,6 +247,12 @@ const key = computed(() => {
return (type && type.key) || 0; return (type && type.key) || 0;
}); });
function setSelectedSelector(selector: string) {
selectedSelector.value = selector;
}
function closeSelector() {
selectedSelector.value = "";
}
setCurrentDashboard(); setCurrentDashboard();
appStore.setEventStack([initSelector]); appStore.setEventStack([initSelector]);
initSelector(); initSelector();
@ -362,6 +439,7 @@ async function getServices() {
} }
async function changeService(service: any) { async function changeService(service: any) {
selectedSelector.value = "";
if (service[0]) { if (service[0]) {
states.currentService = service[0].value; states.currentService = service[0].value;
selectorStore.setCurrentService(service[0]); selectorStore.setCurrentService(service[0]);
@ -381,6 +459,7 @@ function changeDestService(service: any) {
} }
function changePods(pod: any) { function changePods(pod: any) {
selectedSelector.value = "";
if (pod[0]) { if (pod[0]) {
selectorStore.setCurrentPod(pod[0]); selectorStore.setCurrentPod(pod[0]);
} else { } else {
@ -636,6 +715,7 @@ watch(
background: rgb(240, 242, 245); background: rgb(240, 242, 245);
border-bottom: 1px solid #dfe4e8; border-bottom: 1px solid #dfe4e8;
justify-content: space-between; justify-content: space-between;
align-items: center;
} }
.switch { .switch {
@ -682,4 +762,11 @@ watch(
.selectorPod { .selectorPod {
width: 300px; width: 300px;
} }
.tool-btn {
height: 18px;
}
.el-input__wrapper {
height: 18px !important;
}
</style> </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 See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div class="flex-h row"> <div class="flex-h log-wrapper">
<div class="mr-5" v-if="dashboardStore.entity === EntityType[1].value"> <div v-if="!currentSearchTerm.length" class="flex-h items-center">
<span class="grey mr-5">{{ t("service") }}:</span> <div v-for="(item, index) in arrayOfFilters" :key="index">
<Selector <el-tooltip
size="small" class="box-item"
:value="state.service.value" effect="dark"
:options="logStore.services" :content="item.description"
placeholder="Select a service" placement="bottom-start"
@change="changeField('service', $event)" >
/> <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>
<div class="mr-5" v-if="dashboardStore.entity !== EntityType[3].value"> <div class="flex-h items-center">
<span class="grey mr-5"> <div class="flex-h items-center" v-if="currentSearchTerm === 'service'">
{{ isBrowser ? t("version") : t("instance") }}: <div
</span> class="mr-5 flex-h items-center"
<Selector v-if="dashboardStore.entity === EntityType[1].value"
size="small" >
:value="state.instance.value" <span class="grey mr-5">{{ t("service") }}:</span>
:options="logStore.instances" <Selector
placeholder="Select a instance" size="small"
@change="changeField('instance', $event)" :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>
<div class="mr-5" v-if="dashboardStore.entity !== EntityType[2].value"> <!-- <div class="row tips">
<span class="grey mr-5" <b>{{ t("conditionNotice") }}</b>
>{{ isBrowser ? t("page") : t("endpoint") }}:</span </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 <span class="mr-5 grey">{{ t("keywordsOfContent") }}:</span>
size="small" <span class="log-tags">
:value="state.endpoint.value" <span
:options="logStore.endpoints" class="selected"
placeholder="Select a endpoint" v-for="(item, index) in keywordsOfContent"
@change="changeField('endpoint', $event)" :key="`keywordsOfContent${index}`"
:isRemote="true" >
@query="searchEndpoints" <span>{{ item }}</span>
/> <span class="remove-icon" @click="removeContent(index)">×</span>
</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> </span>
</span> </span>
</span> <el-input
<el-input size="small"
class="inputs-max" class="inputs-max"
size="small" :placeholder="t('addKeywordsOfContent')"
:placeholder="t('addExcludingKeywordsOfContent')" v-model="contentStr"
v-model="excludingContentStr" @change="addLabels('keywordsOfContent')"
@change="addLabels('excludingKeywordsOfContent')" />
/> </div>
<el-tooltip :content="t('keywordsOfContentLogTips')"> <div
<span class="log-tips" v-show="!logStore.supportQueryLogsByKeywords"> class="mr-5 flex-h items-center"
<Icon icon="help" class="mr-5" /> 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> </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> </div>
<el-button
class="search-btn"
size="small"
type="primary"
@click="searchLogs"
>
{{ t("search") }}
</el-button>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, reactive, watch } from "vue"; import { ref, reactive, watch, computed } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { Option } from "@/types/app"; import { Option } from "@/types/app";
import { useLogStore } from "@/store/modules/log"; import { useLogStore } from "@/store/modules/log";
@ -139,6 +217,15 @@ const logStore = useLogStore();
const traceId = ref<string>(""); const traceId = ref<string>("");
const keywordsOfContent = ref<string[]>([]); const keywordsOfContent = ref<string[]>([]);
const excludingKeywordsOfContent = 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 tagsList = ref<string[]>([]);
const tagsMap = ref<Option[]>([]); const tagsMap = ref<Option[]>([]);
const contentStr = ref<string>(""); const contentStr = ref<string>("");
@ -149,7 +236,57 @@ const state = reactive<any>({
endpoint: { value: "0", label: "All" }, endpoint: { value: "0", label: "All" },
service: { value: "", label: "" }, 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(); init();
async function init() { async function init() {
const resp = await logStore.getLogsByKeywords(); const resp = await logStore.getLogsByKeywords();
@ -210,7 +347,46 @@ async function getInstances(id?: string) {
} }
state.instance = logStore.instances[0]; 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() { function searchLogs() {
handleActiveSearchTerms();
currentSearchTerm.value = "";
let endpoint = "", let endpoint = "",
instance = ""; instance = "";
if (dashboardStore.entity === EntityType[2].value) { if (dashboardStore.entity === EntityType[2].value) {
@ -293,6 +469,40 @@ function removeExcludeContent(index: number) {
}); });
excludingContentStr.value = ""; 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( watch(
() => selectorStore.currentService, () => selectorStore.currentService,
() => { () => {
@ -320,6 +530,12 @@ watch(
); );
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
// .log-wrapper {
// width: 600px;
// padding-left: 40px;
// overflow-x: scroll;
// align-items: center;
// }
.inputs { .inputs {
width: 120px; width: 120px;
} }
@ -378,4 +594,22 @@ watch(
margin-left: 3px; margin-left: 3px;
cursor: pointer; 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> </style>

View File

@ -12,130 +12,10 @@ See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div class="trace-detail" v-loading="loading"> <div class="trace-detail" v-loading="loading">
<div <div :class="{ 'full-view': isFullView }" class="trace-chart">
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">
<component <component
v-if="traceStore.currentTrace.endpointNames" v-if="traceStore.currentTrace.endpointNames"
:is="displayMode" :is="traceStore.displayMode"
:data="traceStore.traceSpans" :data="traceStore.traceSpans"
:traceId="traceStore.currentTrace.traceIds[0].value" :traceId="traceStore.currentTrace.traceIds[0].value"
:showBtnDetail="false" :showBtnDetail="false"
@ -146,7 +26,8 @@ limitations under the License. -->
</template> </template>
<script lang="ts"> <script lang="ts">
import dayjs from "dayjs"; 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 { useI18n } from "vue-i18n";
import { useTraceStore } from "@/store/modules/trace"; import { useTraceStore } from "@/store/modules/trace";
import { Option } from "@/types/app"; import { Option } from "@/types/app";
@ -155,7 +36,7 @@ import List from "./components/List.vue";
import graphs from "./components/index"; import graphs from "./components/index";
import LogTable from "@/views/dashboard/related/components/LogTable/Index.vue"; import LogTable from "@/views/dashboard/related/components/LogTable/Index.vue";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
// import TraceDetailsTools from '@/views/dashboard'
export default defineComponent({ export default defineComponent({
name: "TraceDetail", name: "TraceDetail",
components: { components: {
@ -163,18 +44,27 @@ export default defineComponent({
List, List,
LogTable, LogTable,
}, },
setup() { setup(props, ctx) {
const { t } = useI18n(); const { t } = useI18n();
const traceStore = useTraceStore(); const traceStore = useTraceStore();
const loading = ref<boolean>(false); const loading = ref<boolean>(false);
const traceId = ref<string>(""); 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 pageNum = ref<number>(1);
const pageSize = 10; const pageSize = 10;
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") => const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern); dayjs(date).format(pattern);
const showTraceLogs = ref<boolean>(false); const showTraceLogs = ref<boolean>(false);
function showTraceList() {
ctx.emit("show:list");
}
function handleClick(ids: string[] | any) { function handleClick(ids: string[] | any) {
let copyValue = null; let copyValue = null;
if (ids.length === 1) { if (ids.length === 1) {
@ -215,6 +105,8 @@ export default defineComponent({
searchTraceLogs(); searchTraceLogs();
} }
return { return {
isFullView,
showTraceList,
traceStore, traceStore,
displayMode, displayMode,
dateFormat, dateFormat,
@ -233,17 +125,21 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.trace-detail { .trace-detail {
// min-height: 300px;
height: 100%; height: 100%;
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
} }
.trace-chart { .trace-chart {
height: calc(100% - 100px); height: 100%;
// height: calc(100% - 100px);
overflow: auto; overflow: auto;
padding-bottom: 20px; padding-bottom: 20px;
} }
.trace-chart.full-view {
height: calc(100% - 1px) !important;
}
.trace-detail-wrapper { .trace-detail-wrapper {
font-size: 12px; font-size: 12px;
padding: 5px 10px; 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 See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <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="flex-h">
<!-- <div class="mr-5"> <div class="flex-h filter-container">
<span class="grey mr-5">{{ t("timeRange") }}:</span> <div v-for="(filter, index) in arrayOfFilters" :key="index">
<TimePicker <el-tooltip
:value="dateTime" v-if="!activeFilter.length || activeFilter === filter.name"
position="bottom" class="box-item"
format="YYYY-MM-DD HH:mm" effect="dark"
@input="changeTimeRange" :content="filter.description"
/> placement="bottom-start"
</div> --> >
<div class="mr-5"> <el-button
<span class="sm b grey mr-5">{{ t("duration") }}:</span> type="success"
<el-input size="small" class="inputs mr-5" v-model="minTraceDuration" /> :class="[listOfActiveFilters.includes(filter.name) ? 'active-filter' : '']"
<span class="grey mr-5">-</span> class="filter-btn mx-3"
<el-input size="small" class="inputs" v-model="maxTraceDuration" /> @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> </div>
<ConditionTags :type="'TRACE'" @update="updateTags" />
<el-button
class="search-btn"
size="small"
type="primary"
@click="searchTraces"
>
{{ t("search") }}
</el-button>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, reactive, watch } from "vue"; import { ref, reactive, watch, computed } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { Option } from "@/types/app"; import { Option } from "@/types/app";
import { Status } from "../../data"; import { Status } from "../../data";
@ -101,11 +140,60 @@ import ConditionTags from "@/views/components/ConditionTags.vue";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { EntityType } from "../../data"; import { EntityType } from "../../data";
interface filtersObject {
name: string;
iconName: string;
description: string;
}
const { t } = useI18n(); const { t } = useI18n();
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore(); const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const traceStore = useTraceStore(); 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 traceId = ref<string>("");
const minTraceDuration = ref<string>(""); const minTraceDuration = ref<string>("");
const maxTraceDuration = ref<string>(""); const maxTraceDuration = ref<string>("");
@ -118,6 +206,8 @@ const state = reactive<any>({
service: { value: "", label: "" }, service: { value: "", label: "" },
}); });
const traceTagsComponent = ref<InstanceType<typeof ConditionTags> | null>(null);
// const dateTime = computed(() => [ // const dateTime = computed(() => [
// appStore.durationRow.start, // appStore.durationRow.start,
// appStore.durationRow.end, // appStore.durationRow.end,
@ -167,7 +257,92 @@ async function getInstances(id?: string) {
} }
state.instance = traceStore.instances[0]; 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() { function searchTraces() {
handleActiveFilterState();
activeFilter.value = "";
let endpoint = "", let endpoint = "",
instance = ""; instance = "";
if (dashboardStore.entity === EntityType[2].value) { if (dashboardStore.entity === EntityType[2].value) {
@ -185,8 +360,8 @@ function searchTraces() {
serviceInstanceId: instance || state.instance.id || undefined, serviceInstanceId: instance || state.instance.id || undefined,
traceState: state.status.value || "ALL", traceState: state.status.value || "ALL",
queryDuration: appStore.durationTime, queryDuration: appStore.durationTime,
minTraceDuration: appStore.minTraceDuration || undefined, minTraceDuration: minTraceDuration.value || undefined,
maxTraceDuration: appStore.maxTraceDuration || undefined, maxTraceDuration: maxTraceDuration.value || undefined,
queryOrder: "BY_DURATION", queryOrder: "BY_DURATION",
tags: tagsMap.value.length ? tagsMap.value : undefined, tags: tagsMap.value.length ? tagsMap.value : undefined,
paging: { pageNum: 1, pageSize: 15, needTotal: true }, paging: { pageNum: 1, pageSize: 15, needTotal: true },
@ -260,4 +435,28 @@ watch(
margin-left: 20px; margin-left: 20px;
cursor: pointer; 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> </style>

View File

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

View File

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