This is an automated email from the ASF dual-hosted git repository.
wusheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-booster-ui.git
The following commit(s) were added to refs/heads/main by this push:
new 8ad1c91 feat: Add Log widget in dashboards (#22)
8ad1c91 is described below
commit 8ad1c91082b5c58a7be39cd78ecef783e60d50c2
Author: Fine0830 <[email protected]>
AuthorDate: Tue Mar 8 14:39:42 2022 +0800
feat: Add Log widget in dashboards (#22)
---
src/locales/lang/en.ts | 3 +-
src/locales/lang/zh.ts | 4 +-
src/router/alert.ts | 45 ---
src/router/index.ts | 5 +-
src/store/modules/dashboard.ts | 6 +-
src/store/modules/log.ts | 156 +++++++++
src/styles/lib.scss | 8 +
src/views/dashboard/controls/Log.vue | 93 ++++++
src/views/dashboard/controls/Tab.vue | 3 +-
src/views/dashboard/controls/index.ts | 3 +-
src/views/dashboard/data.ts | 1 +
src/views/dashboard/panel/Tool.vue | 6 +
.../related}/components/ConditionTags.vue | 11 +-
.../related}/components/LogTable/Index.vue | 0
.../related}/components/LogTable/LogBrowser.vue | 13 +-
.../related}/components/LogTable/LogDetail.vue | 32 +-
.../related}/components/LogTable/LogService.vue | 9 +-
.../related}/components/LogTable/data.ts | 12 +-
src/views/dashboard/related/log/Header.vue | 362 +++++++++++++++++++++
src/views/dashboard/related/log/List.vue | 73 +++++
src/views/dashboard/related/trace/Detail.vue | 6 +-
src/views/dashboard/related/trace/Filter.vue | 4 +-
src/views/dashboard/related/trace/TraceList.vue | 2 +-
.../trace/components/D3Graph/SpanDetail.vue | 2 +-
24 files changed, 760 insertions(+), 99 deletions(-)
diff --git a/src/locales/lang/en.ts b/src/locales/lang/en.ts
index b364919..ac45f3e 100644
--- a/src/locales/lang/en.ts
+++ b/src/locales/lang/en.ts
@@ -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",
diff --git a/src/locales/lang/zh.ts b/src/locales/lang/zh.ts
index d60322c..0b60578 100644
--- a/src/locales/lang/zh.ts
+++ b/src/locales/lang/zh.ts
@@ -325,10 +325,12 @@ const msg = {
logContentEmpty: "日志数据的内容不应该是空。",
debug: "调试",
addTraceID: "请输入一个Trace ID",
+ addTags: "请输入一个标签",
addKeywordsOfContent: "请输入一个内容关键词",
addExcludingKeywordsOfContent: "请输入一个内容不包含的关键词",
noticeTag: "请输入一个标签(key=value)之后回车",
- conditionNotice: "请输入一个标签、内容关键词或者内容不包含的关键词之后回车",
+ conditionNotice:
+ "请输入一个标签、内容关键词或者内容不包含的关键词(key=value)之后回车",
cacheModalTitle: "清除缓存提醒",
yes: "是的",
no: "不",
diff --git a/src/router/alert.ts b/src/router/alert.ts
deleted file mode 100644
index c0e27fb..0000000
--- a/src/router/alert.ts
+++ /dev/null
@@ -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"),
- },
- ],
- },
-];
diff --git a/src/router/index.ts b/src/router/index.ts
index 4ab9d19..aa6e470 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -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,
];
diff --git a/src/store/modules/dashboard.ts b/src/store/modules/dashboard.ts
index ad15f25..84af4f5 100644
--- a/src/store/modules/dashboard.ts
+++ b/src/store/modules/dashboard.ts
@@ -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) {
diff --git a/src/store/modules/log.ts b/src/store/modules/log.ts
new file mode 100644
index 0000000..f7846d6
--- /dev/null
+++ b/src/store/modules/log.ts
@@ -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);
+}
diff --git a/src/styles/lib.scss b/src/styles/lib.scss
index 3462512..a589ae1 100644
--- a/src/styles/lib.scss
+++ b/src/styles/lib.scss
@@ -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;
}
diff --git a/src/views/dashboard/controls/Log.vue
b/src/views/dashboard/controls/Log.vue
new file mode 100644
index 0000000..d0a2240
--- /dev/null
+++ b/src/views/dashboard/controls/Log.vue
@@ -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>
diff --git a/src/views/dashboard/controls/Tab.vue
b/src/views/dashboard/controls/Tab.vue
index 20c106d..78f4586 100644
--- a/src/views/dashboard/controls/Tab.vue
+++ b/src/views/dashboard/controls/Tab.vue
@@ -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();
diff --git a/src/views/dashboard/controls/index.ts
b/src/views/dashboard/controls/index.ts
index 4f2ee20..7fdcd85 100644
--- a/src/views/dashboard/controls/index.ts
+++ b/src/views/dashboard/controls/index.ts
@@ -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 };
diff --git a/src/views/dashboard/data.ts b/src/views/dashboard/data.ts
index 396de2c..6917c97 100644
--- a/src/views/dashboard/data.ts
+++ b/src/views/dashboard/data.ts
@@ -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" },
diff --git a/src/views/dashboard/panel/Tool.vue
b/src/views/dashboard/panel/Tool.vue
index b052257..ce05edd 100644
--- a/src/views/dashboard/panel/Tool.vue
+++ b/src/views/dashboard/panel/Tool.vue
@@ -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;
diff --git a/src/views/components/ConditionTags.vue
b/src/views/dashboard/related/components/ConditionTags.vue
similarity index 93%
rename from src/views/components/ConditionTags.vue
rename to src/views/dashboard/related/components/ConditionTags.vue
index b73ae6a..f755910 100644
--- a/src/views/components/ConditionTags.vue
+++ b/src/views/dashboard/related/components/ConditionTags.vue
@@ -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>
diff --git a/src/views/components/LogTable/Index.vue
b/src/views/dashboard/related/components/LogTable/Index.vue
similarity index 100%
rename from src/views/components/LogTable/Index.vue
rename to src/views/dashboard/related/components/LogTable/Index.vue
diff --git a/src/views/components/LogTable/LogBrowser.vue
b/src/views/dashboard/related/components/LogTable/LogBrowser.vue
similarity index 91%
rename from src/views/components/LogTable/LogBrowser.vue
rename to src/views/dashboard/related/components/LogTable/LogBrowser.vue
index 20fbb7d..6a1159e 100644
--- a/src/views/components/LogTable/LogBrowser.vue
+++ b/src/views/dashboard/related/components/LogTable/LogBrowser.vue
@@ -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>
diff --git a/src/views/components/LogTable/LogDetail.vue
b/src/views/dashboard/related/components/LogTable/LogDetail.vue
similarity index 71%
rename from src/views/components/LogTable/LogDetail.vue
rename to src/views/dashboard/related/components/LogTable/LogDetail.vue
index 4588132..75104fb 100644
--- a/src/views/components/LogTable/LogDetail.vue
+++ b/src/views/dashboard/related/components/LogTable/LogDetail.vue
@@ -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({
diff --git a/src/views/components/LogTable/LogService.vue
b/src/views/dashboard/related/components/LogTable/LogService.vue
similarity index 94%
rename from src/views/components/LogTable/LogService.vue
rename to src/views/dashboard/related/components/LogTable/LogService.vue
index 59943d4..3f94af0 100644
--- a/src/views/components/LogTable/LogService.vue
+++ b/src/views/dashboard/related/components/LogTable/LogService.vue
@@ -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,
diff --git a/src/views/components/LogTable/data.ts
b/src/views/dashboard/related/components/LogTable/data.ts
similarity index 93%
rename from src/views/components/LogTable/data.ts
rename to src/views/dashboard/related/components/LogTable/data.ts
index 9765e01..9ddd3c5 100644
--- a/src/views/components/LogTable/data.ts
+++ b/src/views/dashboard/related/components/LogTable/data.ts
@@ -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",
diff --git a/src/views/dashboard/related/log/Header.vue
b/src/views/dashboard/related/log/Header.vue
new file mode 100644
index 0000000..42a7e63
--- /dev/null
+++ b/src/views/dashboard/related/log/Header.vue
@@ -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>
diff --git a/src/views/dashboard/related/log/List.vue
b/src/views/dashboard/related/log/List.vue
new file mode 100644
index 0000000..8cbcb50
--- /dev/null
+++ b/src/views/dashboard/related/log/List.vue
@@ -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>
diff --git a/src/views/dashboard/related/trace/Detail.vue
b/src/views/dashboard/related/trace/Detail.vue
index ade0f74..c622efb 100644
--- a/src/views/dashboard/related/trace/Detail.vue
+++ b/src/views/dashboard/related/trace/Detail.vue
@@ -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 });
diff --git a/src/views/dashboard/related/trace/Filter.vue
b/src/views/dashboard/related/trace/Filter.vue
index 236697b..342a225 100644
--- a/src/views/dashboard/related/trace/Filter.vue
+++ b/src/views/dashboard/related/trace/Filter.vue
@@ -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();
diff --git a/src/views/dashboard/related/trace/TraceList.vue
b/src/views/dashboard/related/trace/TraceList.vue
index 8f863e5..9240e5d 100644
--- a/src/views/dashboard/related/trace/TraceList.vue
+++ b/src/views/dashboard/related/trace/TraceList.vue
@@ -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 },
diff --git
a/src/views/dashboard/related/trace/components/D3Graph/SpanDetail.vue
b/src/views/dashboard/related/trace/components/D3Graph/SpanDetail.vue
index 69f154c..e6390ff 100644
--- a/src/views/dashboard/related/trace/components/D3Graph/SpanDetail.vue
+++ b/src/views/dashboard/related/trace/components/D3Graph/SpanDetail.vue
@@ -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({