feat: Add Log widget in dashboards (#22)

This commit is contained in:
Fine0830 2022-03-08 14:39:42 +08:00 committed by GitHub
parent 1377703185
commit 8ad1c91082
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 760 additions and 99 deletions

View File

@ -324,11 +324,12 @@ const msg = {
logContentEmpty: "The content of the log should not be empty.",
debug: "Debug",
addTraceID: "Please input a trace ID",
addTags: "Please input a tag",
addKeywordsOfContent: "Please input a keyword of content",
addExcludingKeywordsOfContent: "Please input a keyword of excluding content",
noticeTag: "Please press Enter after inputting a tag(key=value).",
conditionNotice:
"Notice: Please press enter after inputting a tag, key of content, exclude key of content.",
"Notice: Please press Enter after inputting a tag, key of content, exclude key of content(key=value).",
cacheModalTitle: "Clear cache reminder",
yes: "Yes",
no: "No",

View File

@ -325,10 +325,12 @@ const msg = {
logContentEmpty: "日志数据的内容不应该是空。",
debug: "调试",
addTraceID: "请输入一个Trace ID",
addTags: "请输入一个标签",
addKeywordsOfContent: "请输入一个内容关键词",
addExcludingKeywordsOfContent: "请输入一个内容不包含的关键词",
noticeTag: "请输入一个标签(key=value)之后回车",
conditionNotice: "请输入一个标签、内容关键词或者内容不包含的关键词之后回车",
conditionNotice:
"请输入一个标签、内容关键词或者内容不包含的关键词(key=value)之后回车",
cacheModalTitle: "清除缓存提醒",
yes: "是的",
no: "不",

View File

@ -1,45 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesAlert: Array<RouteRecordRaw> = [
{
path: "",
name: "Alerts",
meta: {
title: "alerts",
icon: "notification_important",
hasGroup: false,
exact: false,
},
component: Layout,
children: [
{
path: "/alerts",
name: "Alerts",
meta: {
title: "alerts",
icon: "notification_important",
hasGroup: false,
exact: false,
},
component: () => import("@/views/Log.vue"),
},
],
},
];

View File

@ -21,9 +21,8 @@ import { routesMesh } from "./serviceMesh";
import { routesDatabase } from "./database";
import { routesInfra } from "./infrastructure";
import { routesDashboard } from "./dashboard";
import { routesLog } from "./log";
// import { routesLog } from "./log";
import { routesEvent } from "./event";
import { routesAlert } from "./alert";
import { routesSetting } from "./setting";
import { routesAlarm } from "./alarm";
@ -33,9 +32,7 @@ const routes: Array<RouteRecordRaw> = [
...routesDatabase,
...routesInfra,
...routesDashboard,
...routesLog,
...routesEvent,
...routesAlert,
...routesSetting,
...routesAlarm,
];

View File

@ -98,8 +98,8 @@ export const dashboardStore = defineStore({
showDepth: true,
};
}
if (type === "Trace" || type === "Profile") {
newItem.h = 24;
if (type === "Trace" || type === "Profile" || type === "Log") {
newItem.h = 36;
}
this.activedGridItem = newItem.i;
this.selectedGrid = newItem;
@ -150,7 +150,7 @@ export const dashboardStore = defineStore({
showDepth: true,
};
}
if (type === "Trace" || type === "Profile") {
if (type === "Trace" || type === "Profile" || type === "Log") {
newItem.h = 24;
}
if (this.layout[idx].children) {

156
src/store/modules/log.ts Normal file
View File

@ -0,0 +1,156 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineStore } from "pinia";
import { Duration } from "@/types/app";
import { Instance, Endpoint, Service } from "@/types/selector";
import { store } from "@/store";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { useDashboardStore } from "@/store/modules/dashboard";
interface LogState {
services: Service[];
instances: Instance[];
endpoints: Endpoint[];
conditions: any;
durationTime: Duration;
selectorStore: any;
supportQueryLogsByKeywords: boolean;
logs: any[];
logsTotal: number;
loadLogs: boolean;
}
export const logStore = defineStore({
id: "trace",
state: (): LogState => ({
services: [{ value: "0", label: "All" }],
instances: [{ value: "0", label: "All" }],
endpoints: [{ value: "0", label: "All" }],
conditions: {
queryDuration: useAppStoreWithOut().durationTime,
paging: { pageNum: 1, pageSize: 15, needTotal: true },
},
supportQueryLogsByKeywords: true,
durationTime: useAppStoreWithOut().durationTime,
selectorStore: useSelectorStore(),
logs: [],
logsTotal: 0,
loadLogs: false,
}),
actions: {
setLogCondition(data: any) {
this.conditions = { ...this.conditions, ...data };
},
async getServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({
layer,
});
if (res.data.errors) {
return res.data;
}
this.services = [
{ value: "0", label: "All" },
...res.data.data.services,
] || [{ value: "0", label: "All" }];
return res.data;
},
async getInstances() {
const res: AxiosResponse = await graphql.query("queryInstances").params({
serviceId: this.selectorStore.currentService.id,
duration: this.durationTime,
});
if (res.data.errors) {
return res.data;
}
this.instances = [
{ value: "0", label: "All" },
...res.data.data.pods,
] || [{ value: " 0", label: "All" }];
return res.data;
},
async getEndpoints() {
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
serviceId: this.selectorStore.currentService.id,
duration: this.durationTime,
keyword: "",
});
if (res.data.errors) {
return res.data;
}
this.endpoints = [
{ value: "0", label: "All" },
...res.data.data.pods,
] || [{ value: "0", label: "All" }];
return res.data;
},
async queryLogsByKeywords() {
const res: AxiosResponse = await graphql
.query("queryLogsByKeywords")
.params({});
if (res.data.errors) {
return res.data;
}
this.supportQueryLogsByKeywords = res.data.data.support;
return res.data;
},
async getLogs() {
const dashboardStore = useDashboardStore();
if (dashboardStore.layerId === "BROWSER") {
return this.getBrowserLogs();
}
return this.getServiceLogs();
},
async getServiceLogs() {
this.loadLogs = true;
const res: AxiosResponse = await graphql
.query("queryServiceLogs")
.params({ condition: this.conditions });
this.loadLogs = false;
if (res.data.errors) {
return res.data;
}
this.logs = res.data.data.queryLogs.logs;
this.logsTotal = res.data.data.queryLogs.total;
return res.data;
},
async getBrowserLogs() {
this.loadLogs = true;
const res: AxiosResponse = await graphql
.query("queryBrowserErrorLogs")
.params({ condition: this.conditions });
this.loadLogs = false;
if (res.data.errors) {
return res.data;
}
this.logs = res.data.data.queryBrowserErrorLogs.logs;
this.logsTotal = res.data.data.queryBrowserErrorLogs.total;
return res.data;
},
},
});
export function useLogStore(): any {
return logStore(store);
}

View File

@ -107,6 +107,10 @@
margin-bottom: 5px;
}
.mt-5 {
margin-top: 5px;
}
.ml-5 {
margin-left: 5px;
}
@ -123,6 +127,10 @@
margin-left: 20px;
}
.mb-10 {
margin-bottom: 10px;
}
.mr-5 {
margin-right: 5px;
}

View File

@ -0,0 +1,93 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="log-wrapper flex-v">
<el-popover placement="bottom" trigger="click" :width="100">
<template #reference>
<span class="delete cp">
<Icon iconName="ellipsis_v" size="middle" class="operation" />
</span>
</template>
<div class="tools" @click="removeWidget">
<span>{{ t("delete") }}</span>
</div>
</el-popover>
<div class="header">
<Header />
</div>
<div class="log">
<List />
</div>
</div>
</template>
<script lang="ts" setup>
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 */
const props = defineProps({
data: {
type: Object,
default: () => ({}),
},
activeIndex: { type: String, default: "" },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
function removeWidget() {
dashboardStore.removeControls(props.data);
}
</script>
<style lang="scss" scoped>
.log-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
}
.delete {
position: absolute;
top: 5px;
right: 3px;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
}
}
.log {
width: 100%;
overflow: auto;
}
</style>

View File

@ -119,6 +119,7 @@ import Topology from "./Topology.vue";
import Widget from "./Widget.vue";
import Trace from "./Trace.vue";
import Profile from "./Profile.vue";
import Log from "./Log.vue";
const props = {
data: {
@ -129,7 +130,7 @@ const props = {
};
export default defineComponent({
name: "Tab",
components: { Topology, Widget, Trace, Profile },
components: { Topology, Widget, Trace, Profile, Log },
props,
setup(props) {
const { t } = useI18n();

View File

@ -19,5 +19,6 @@ import Tab from "./Tab.vue";
import Widget from "./Widget.vue";
import Trace from "./Trace.vue";
import Profile from "./Profile.vue";
import Log from "./Log.vue";
export default { Tab, Widget, Trace, Topology, Profile };
export default { Tab, Widget, Trace, Topology, Profile, Log };

View File

@ -168,6 +168,7 @@ export const ToolIcons = [
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "timeline", content: "Add Profile", id: "addProfile" },
{ name: "assignment", content: "Add Log", id: "addLog" },
// { name: "save_alt", content: "Export", id: "export" },
// { name: "folder_open", content: "Import", id: "import" },
// { name: "settings", content: "Settings", id: "settings" },

View File

@ -309,6 +309,9 @@ function setTabControls(id: string) {
case "addTrace":
dashboardStore.addTabControls("Trace");
break;
case "addLog":
dashboardStore.addTabControls("Log");
break;
case "addProfile":
dashboardStore.addTabControls("Profile");
break;
@ -335,6 +338,9 @@ function setControls(id: string) {
case "addProfile":
dashboardStore.addControl("Profile");
break;
case "addLog":
dashboardStore.addControl("Log");
break;
case "addTopology":
dashboardStore.addControl("Topology");
break;

View File

@ -14,7 +14,7 @@ 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-10 pt-5">
<div class="mr-5">
<span class="sm grey" v-show="theme === 'dark'">{{ t("tags") }}: </span>
<span
class="trace-tags"
@ -25,7 +25,12 @@ limitations under the License. -->
<span class="remove-icon" @click="removeTags(index)">×</span>
</span>
</span>
<el-input v-model="tags" class="trace-new-tag" @change="addLabels" />
<el-input
v-model="tags"
class="trace-new-tag"
@change="addLabels"
:placeholder="t('addTags')"
/>
<span class="tags-tip">
<a
target="blank"
@ -38,7 +43,7 @@ limitations under the License. -->
<Icon class="icon-help mr-5" iconName="help" size="middle" />
</span>
</el-tooltip>
<b>{{ t("noticeTag") }}</b>
<b v-if="type === 'TRACE'">{{ t("noticeTag") }}</b>
</span>
</div>
</div>

View File

@ -29,13 +29,16 @@ limitations under the License. -->
>
<span v-if="item.label === 'time'">{{ dateFormat(data.time) }}</span>
<span v-else-if="item.label === 'errorUrl'">{{ data.pagePath }}</span>
<span v-else v-tooltip:bottom="data[item.label] || '-'">{{
data[item.label] || "-"
}}</span>
<el-tooltip v-else :content="data[item.label] || '-'">
<span>
{{ data[item.label] || "-" }}
</span>
</el-tooltip>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import dayjs from "dayjs";
import { BrowserLogConstants } from "./data";
@ -45,6 +48,7 @@ const props = defineProps({
});
const columns = BrowserLogConstants;
const emit = defineEmits(["select"]);
const logItem = ref<any>(null);
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern);
@ -55,9 +59,8 @@ function showSelectSpan() {
for (const item of items) {
item.style.background = "#fff";
}
const logItem: any = this.$refs.logItem;
logItem.style.background = "rgba(0, 0, 0, 0.1)";
logItem.value.style.background = "rgba(0, 0, 0, 0.1)";
emit("select", props.data);
}
</script>

View File

@ -19,22 +19,20 @@ limitations under the License. -->
v-for="(item, index) in columns"
:key="index"
>
<template>
<span class="g-sm-4 grey">{{ t(item.value) }}:</span>
<span v-if="item.label === 'timestamp'" class="g-sm-8">
{{ dateFormat(currentLog[item.label]) }}
</span>
<textarea
class="content"
:readonly="true"
v-else-if="item.label === 'content'"
:value="currentLog[item.label]"
/>
<span v-else-if="item.label === 'tags'" class="g-sm-8">
<div v-for="(d, index) in logTags" :key="index">{{ d }}</div>
</span>
<span v-else class="g-sm-8">{{ currentLog[item.label] }}</span>
</template>
<span class="g-sm-4 grey">{{ t(item.value) }}:</span>
<span v-if="item.label === 'timestamp'" class="g-sm-8 mb-10">
{{ dateFormat(currentLog[item.label]) }}
</span>
<textarea
class="content mb-10"
:readonly="true"
v-else-if="item.label === 'content'"
:value="currentLog[item.label]"
/>
<span v-else-if="item.label === 'tags'" class="g-sm-8 mb-10">
<div v-for="(d, index) in logTags" :key="index">{{ d }}</div>
</span>
<span v-else class="g-sm-8 mb-10">{{ currentLog[item.label] }}</span>
</div>
</div>
</template>
@ -43,7 +41,7 @@ import { computed } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import dayjs from "dayjs";
import { ServiceLogDetail } from "@/views/components/LogTable/data";
import { ServiceLogDetail } from "./data";
/*global defineProps */
const props = defineProps({

View File

@ -26,7 +26,7 @@ limitations under the License. -->
v-else-if="item.label === 'traceId' && !noLink"
:to="{ name: 'trace', query: { traceid: data[item.label] } }"
>
<span>{{ data[item.label] }}</span>
<span :class="noLink ? '' : 'blue'">{{ data[item.label] }}</span>
</router-link>
<span v-else>{{ data[item.label] }}</span>
</div>
@ -38,7 +38,7 @@ import dayjs from "dayjs";
import { ServiceLogConstants } from "./data";
/*global defineProps, defineEmits */
const props = defineProps({
data: { type: Array as any, default: () => [] },
data: { type: Object as any, default: () => ({}) },
noLink: { type: Boolean, default: true },
});
const emit = defineEmits(["select"]);
@ -63,7 +63,6 @@ function showSelectSpan() {
.traceId {
width: 390px;
color: #448dfe;
cursor: pointer;
span {
@ -71,6 +70,10 @@ function showSelectSpan() {
width: 100%;
line-height: 30px;
}
.blue {
color: #448dfe;
}
}
.content,

View File

@ -48,11 +48,11 @@ export const ServiceLogConstants = [
export const ServiceLogDetail = [
{
label: "serviceName",
value: "currentService",
value: "service",
},
{
label: "serviceInstanceName",
value: "currentInstance",
value: "instance",
},
{
label: "timestamp",
@ -96,19 +96,15 @@ export const BrowserLogConstants = [
{
label: "message",
value: "message",
drag: true,
// drag: true,
method: 350,
},
{
label: "stack",
value: "stack",
drag: true,
// drag: true,
method: 350,
},
// {
// label: 'pagePath',
// value: 'Page Path',
// },
{
label: "category",
value: "category",

View File

@ -0,0 +1,362 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div 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>
<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>
<div class="mr-5" v-if="dashboardStore.entity !== EntityType[2].value">
<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)"
/>
</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" />
</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
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>
<el-input
class="inputs-max"
: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>
<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 { useI18n } from "vue-i18n";
import { Option } from "@/types/app";
import { useLogStore } from "@/store/modules/log";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import ConditionTags from "@/views/dashboard/related/components/ConditionTags.vue";
import { ElMessage } from "element-plus";
import { EntityType } from "../../data";
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const logStore = useLogStore();
const traceId = ref<string>("");
const keywordsOfContent = ref<string[]>([]);
const excludingKeywordsOfContent = ref<string[]>([]);
const tagsList = ref<string[]>([]);
const tagsMap = ref<Option[]>([]);
const contentStr = ref<string>("");
const excludingContentStr = ref<string>("");
const isBrowser = ref<boolean>(dashboardStore.layerId === "BROWSER");
const state = reactive<any>({
instance: { value: "0", label: "All" },
endpoint: { value: "0", label: "All" },
service: { value: "0", label: "All" },
});
init();
async function init() {
const resp = await logStore.queryLogsByKeywords();
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.instance = { value: "0", label: "All" };
state.endpoint = { value: "0", label: "All" };
state.service = { value: "0", label: "All" };
searchLogs();
fetchSelectors();
}
function fetchSelectors() {
if (dashboardStore.entity === EntityType[1].value) {
getServices();
return;
}
if (dashboardStore.entity === EntityType[2].value) {
getInstances();
return;
}
if (dashboardStore.entity === EntityType[3].value) {
getEndpoints();
return;
}
if (dashboardStore.entity === EntityType[0].value) {
getInstances();
getEndpoints();
}
}
async function getServices() {
const resp = await logStore.getServices(dashboardStore.layerId);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.service = logStore.services[0];
}
async function getEndpoints() {
const resp = await logStore.getEndpoints();
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.endpoint = logStore.endpoints[0];
}
async function getInstances() {
const resp = await logStore.getInstances();
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.instance = logStore.instances[0];
}
function searchLogs() {
let endpoint = "",
instance = "";
if (dashboardStore.entity === EntityType[2].value) {
endpoint = selectorStore.currentPod.id;
}
if (dashboardStore.entity === EntityType[3].value) {
instance = selectorStore.currentPod.id;
}
logStore.setLogCondition({
serviceId: selectorStore.currentService
? selectorStore.currentService.id
: state.service.id,
endpointId: endpoint || state.endpoint.id || undefined,
serviceInstanceId: instance || state.instance.id || undefined,
queryDuration: appStore.durationTime,
keywordsOfContent: keywordsOfContent.value,
excludingKeywordsOfContent: excludingKeywordsOfContent.value,
tags: tagsMap.value.length ? tagsMap.value : undefined,
paging: { pageNum: 1, pageSize: 15, needTotal: true },
relatedTrace: traceId.value ? { traceId: traceId.value } : undefined,
});
queryLogs();
}
async function queryLogs() {
const res = await logStore.getLogs();
if (res && res.errors) {
ElMessage.error(res.errors);
}
}
function changeField(type: string, opt: any) {
state[type] = opt[0];
if (type === "service") {
getEndpoints();
getInstances();
}
}
function updateTags(data: { tagsMap: Array<Option>; tagsList: string[] }) {
tagsList.value = data.tagsList;
tagsMap.value = data.tagsMap;
}
function removeContent(index: number) {
const keywordsOfContentList = keywordsOfContent.value || [];
keywordsOfContentList.splice(index, 1);
logStore.setLogCondition({
keywordsOfContent: keywordsOfContentList,
});
contentStr.value = "";
}
function addLabels(type: string) {
if (type === "keywordsOfContent" && !contentStr.value) {
return;
}
if (type === "excludingKeywordsOfContent" && !excludingContentStr.value) {
return;
}
if (type === "keywordsOfContent") {
keywordsOfContent.value.push(contentStr.value);
logStore.setLogCondition({
[type]: keywordsOfContent.value,
});
contentStr.value = "";
} else if (type === "excludingKeywordsOfContent") {
excludingKeywordsOfContent.value.push(excludingContentStr.value);
logStore.setLogCondition({
[type]: excludingKeywordsOfContent.value,
});
excludingContentStr.value = "";
}
}
function removeExcludeContent(index: number) {
excludingKeywordsOfContent.value.splice(index, 1);
logStore.setLogCondition({
excludingKeywordsOfContent: excludingKeywordsOfContent.value,
});
excludingContentStr.value = "";
}
watch(
() => selectorStore.currentService,
() => {
if (dashboardStore.entity === EntityType[0].value) {
init();
}
}
);
watch(
() => [selectorStore.currentPod],
() => {
if (dashboardStore.entity === EntityType[0].value) {
return;
}
init();
}
);
</script>
<style lang="scss" scoped>
.inputs {
width: 120px;
}
.row {
margin-bottom: 5px;
}
.inputs-max {
width: 270px;
}
.traceId {
margin-top: 2px;
}
.search-btn {
margin-left: 20px;
cursor: pointer;
}
.tips {
color: #888;
}
.log-tag {
width: 30%;
border-style: unset;
outline: 0;
border: 1px solid #ccc;
height: 30px;
padding: 0 5px;
}
.log-tags {
padding: 1px 5px 0 0;
border-radius: 3px;
height: 24px;
display: inline-block;
vertical-align: top;
}
.selected {
display: inline-block;
padding: 0 3px;
border-radius: 3px;
overflow: hidden;
color: #3d444f;
border: 1px dashed #aaa;
font-size: 12px;
margin: 0 2px;
}
.remove-icon {
display: inline-block;
margin-left: 3px;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,73 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div>
<div class="log-t-loading" v-show="logStore.loadLogs">
<Icon iconName="spinner" size="lg" />
</div>
<LogTable :tableData="logStore.logs || []" :type="type" :noLink="true">
<div class="log-tips" v-if="!logStore.logs.length">{{ t("noData") }}</div>
</LogTable>
<div class="mt-5 mb-5">
<el-pagination
v-model:currentPage="logStore.conditions.paging.pageNum"
v-model:page-size="pageSize"
layout="prev, pager, next, jumper"
:total="logStore.logsTotal"
@current-change="updatePage"
:style="`float: right`"
/>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import LogTable from "@/views/dashboard/related/components/LogTable/Index.vue";
import { useLogStore } from "@/store/modules/log";
import { useDashboardStore } from "@/store/modules/dashboard";
import { ElMessage } from "element-plus";
const { t } = useI18n();
const logStore = useLogStore();
const dashboardStore = useDashboardStore();
const type = ref<string>(
dashboardStore.layerId === "BROWSER" ? "browser" : "service"
);
const pageSize = ref<number>(15);
function updatePage(p: number) {
logStore.setLogCondition({
paging: { pageNum: p, pageSize: pageSize.value, needTotal: true },
});
queryLogs();
}
async function queryLogs() {
const res = await logStore.getLogs();
if (res && res.errors) {
ElMessage.error(res.errors);
}
}
</script>
<style lang="scss" scoped>
.log-tips {
width: 100%;
text-align: center;
margin: 50px 0;
}
.log-t-loading {
text-align: center;
}
</style>

View File

@ -144,7 +144,7 @@ import { Option } from "@/types/app";
import copy from "@/utils/copy";
import List from "./components/List.vue";
import graphs from "./components/index";
import LogTable from "@/views/components/LogTable/Index.vue";
import LogTable from "@/views/dashboard/related/components/LogTable/Index.vue";
import { ElMessage } from "element-plus";
export default defineComponent({
@ -166,7 +166,7 @@ export default defineComponent({
dayjs(date).format(pattern);
const showTraceLogs = ref<boolean>(false);
function handleClick(ids: string[]) {
function handleClick(ids: string[] | any) {
let copyValue = null;
if (ids.length === 1) {
copyValue = ids[0];
@ -176,7 +176,7 @@ export default defineComponent({
copy(copyValue);
}
async function changeTraceId(opt: Option[]) {
async function changeTraceId(opt: Option[] | any) {
traceId.value = opt[0].value;
loading.value = true;
const res = await traceStore.getTraceSpans({ traceId: opt[0].value });

View File

@ -95,7 +95,7 @@ import { useTraceStore } from "@/store/modules/trace";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import ConditionTags from "@/views/components/ConditionTags.vue";
import ConditionTags from "@/views/dashboard/related/components/ConditionTags.vue";
import { ElMessage } from "element-plus";
import { EntityType } from "../../data";
@ -198,7 +198,7 @@ async function queryTraces() {
ElMessage.error(res.errors);
}
}
function changeField(type: string, opt: any[]) {
function changeField(type: string, opt: any) {
state[type] = opt[0];
if (type === "service") {
getEndpoints();

View File

@ -101,7 +101,7 @@ function updatePage(p: number) {
searchTrace();
}
function changeSort(opt: Option[]) {
function changeSort(opt: Option[] | any) {
traceStore.setTraceCondition({
queryOrder: opt[0].value,
paging: { pageNum: 1, pageSize: pageSize.value, needTotal: true },

View File

@ -113,7 +113,7 @@ import dayjs from "dayjs";
import { useTraceStore } from "@/store/modules/trace";
import copy from "@/utils/copy";
import { ElMessage } from "element-plus";
import LogTable from "@/views/components/LogTable/Index.vue";
import LogTable from "@/views/dashboard/related/components/LogTable/Index.vue";
/* global defineProps */
const props = defineProps({