feat: add filters

This commit is contained in:
Qiuxia Fan 2022-03-07 14:50:33 +08:00
parent 04e26d3550
commit bdc08f42e4
10 changed files with 581 additions and 11 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: "不",

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

@ -0,0 +1,142 @@
/**
* 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";
interface LogState {
services: Service[];
instances: Instance[];
endpoints: Endpoint[];
conditions: any;
durationTime: Duration;
selectorStore: any;
supportQueryLogsByKeywords: boolean;
logs: any[];
logsTotal: number;
}
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,
}),
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 res: AxiosResponse = await graphql
.query("queryServiceLogs")
.params({ condition: this.conditions });
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() {
const res: AxiosResponse = await graphql
.query("queryBrowserErrorLogs")
.params({ condition: this.conditions });
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

@ -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,9 +29,11 @@ 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>

View File

@ -13,5 +13,79 @@ 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">Log</div>
<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 flex-h">log list</div>
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/log/Header.vue";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
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

@ -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

@ -0,0 +1,343 @@
<!-- 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">{{ 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">{{ 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">
<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">
<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"
@keyup="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/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 state = reactive<any>({
status: { label: "All", value: "ALL" },
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;
}
searchLogs();
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) {
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

@ -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();