feat: refactor the configuration view and implement the optional config for displaying timestamp in Log widget (#492)

This commit is contained in:
Fine0830
2025-08-20 15:30:01 +07:00
committed by GitHub
parent 7a8ee92bbb
commit a8c5ec8dd2
24 changed files with 267 additions and 357 deletions

View File

@@ -43,7 +43,7 @@ limitations under the License. -->
import { ref, watch } from "vue";
import type { PropType } from "vue";
/*global defineProps, defineEmits, Indexable*/
/*global defineProps, defineEmits, Indexable*/
const emit = defineEmits(["change", "query"]);
const props = defineProps({
options: {

View File

@@ -296,7 +296,7 @@ const msg = {
return: "Return",
isError: "Error",
contentType: "Content Type",
content: "Timestamp - Content",
content: "Content",
level: "Level",
viewLogs: "View Logs",
logsTagsTip: `Only tags defined in the core/default/searchableLogsTags are searchable.

View File

@@ -293,7 +293,7 @@ const msg = {
return: "返回",
isError: "错误",
contentType: "内容类型",
content: "时间戳 - 内容",
content: "内容",
level: "Level",
viewLogs: "查看日志",
logsTagsTip: "只有core/default/searchableLogsTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。",

View File

@@ -33,6 +33,7 @@ interface LogState {
supportQueryLogsByKeywords: boolean;
logs: Recordable[];
loadLogs: boolean;
logHeaderType: string;
}
const { getDurationTime } = useDuration();
@@ -50,6 +51,7 @@ export const logStore = defineStore({
selectorStore: useSelectorStore(),
logs: [],
loadLogs: false,
logHeaderType: localStorage.getItem("log-header-type") || "content",
}),
actions: {
setLogCondition(data: Recordable) {
@@ -62,6 +64,9 @@ export const logStore = defineStore({
paging: { pageNum: 1, pageSize: 15 },
};
},
setLogHeaderType(type: string) {
this.logHeaderType = type;
},
async getServices(layer: string) {
const response = await graphql.query("queryServices").params({
layer,

View File

@@ -24,6 +24,7 @@ import { useSelectorStore } from "@/store/modules/selectors";
import { QueryOrders } from "@/views/dashboard/data";
import { EndpointsTopNDefault } from "../data";
import { useDuration } from "@/hooks/useDuration";
import { LogItem } from "@/types/log";
interface TraceState {
services: Service[];
instances: Instance[];
@@ -32,7 +33,7 @@ interface TraceState {
traceSpans: Span[];
currentTrace: Nullable<Trace>;
conditions: Recordable;
traceSpanLogs: Recordable[];
traceSpanLogs: LogItem[];
selectorStore: Recordable;
selectedSpan: Recordable<Span>;
serviceList: string[];

30
src/types/log.ts Normal file
View File

@@ -0,0 +1,30 @@
/**
* 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.
*/
export interface LogItem {
timestamp: number;
content: string;
tags: { key: string; value: string }[];
serviceId: string;
traceId: string;
spanId: string;
parentSpanId: string;
processId: string;
processName: string;
processTags: { key: string; value: string }[];
processStartTime: string;
processEndTime: string;
}

View File

@@ -15,6 +15,11 @@
* limitations under the License.
*/
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
dayjs.extend(utc);
dayjs.extend(timezone);
export default function dateFormatStep(date: Date, step: string, monthDayDiff?: boolean): string {
const year = date.getFullYear();
const monthTemp = date.getMonth() + 1;
@@ -97,4 +102,10 @@ export const dateFormatTime = (date: Date, step: string): string => {
return "";
};
export const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") => dayjs(new Date(date)).format(pattern);
export const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss", timezone?: string) => {
const dayjsInstance = dayjs(new Date(date));
if (timezone) {
return dayjsInstance.tz(timezone).format(pattern);
}
return dayjsInstance.format(pattern);
};

View File

@@ -11,8 +11,8 @@ 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="item">
<div>{{ t("instanceDashboards") }}</div>
<div class="config-item flex-h">
<div class="config-label flex-h mr-20">{{ t("instanceDashboards") }}</div>
<Selector
:value="instanceDashboardName || ''"
:options="instanceDashboards"
@@ -23,8 +23,8 @@ limitations under the License. -->
:clearable="true"
/>
</div>
<div class="item">
<div>{{ t("processDashboards") }}</div>
<div class="config-item flex-h">
<div class="config-label flex-h mr-20">{{ t("processDashboards") }}</div>
<Selector
:value="processDashboardName || ''"
:options="processDashboards"
@@ -35,14 +35,7 @@ limitations under the License. -->
:clearable="true"
/>
</div>
<div class="footer">
<el-button size="small">
{{ t("cancel") }}
</el-button>
<el-button size="small" type="primary" @click="applyConfig">
{{ t("apply") }}
</el-button>
</div>
<ConfigurationFooter />
</template>
<script lang="ts" setup>
import { ref } from "vue";
@@ -50,6 +43,8 @@ limitations under the License. -->
import { useDashboardStore } from "@/store/modules/dashboard";
import { EntityType } from "../data";
import type { DashboardItem, LayoutConfig } from "@/types/dashboard";
import ConfigurationFooter from "./components/ConfigurationFooter.vue";
import "./style.scss";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
@@ -74,11 +69,6 @@ limitations under the License. -->
}
}
}
function applyConfig() {
dashboardStore.setConfigs(dashboardStore.selectedGrid as LayoutConfig);
dashboardStore.setConfigPanel(false);
}
function changeDashboard(param: { [key: string]: unknown }) {
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
@@ -87,21 +77,6 @@ limitations under the License. -->
}
</script>
<style lang="scss" scoped>
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid $border-color;
padding: 10px;
text-align: right;
width: 100%;
background-color: $theme-background;
}
.item {
margin: 10px 0;
}
.selectors {
width: 500px;
}

View File

@@ -11,28 +11,22 @@ 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>
<span class="label">{{ t("enableAssociate") }}</span>
<div class="config-item flex-h">
<div class="config-label flex-h mr-20">{{ t("enableAssociate") }}</div>
<el-switch v-model="eventAssociate" active-text="Yes" inactive-text="No" @change="updateConfig" />
</div>
<div class="footer">
<el-button size="small" @click="cancelConfig">
{{ t("cancel") }}
</el-button>
<el-button size="small" type="primary" @click="applyConfig">
{{ t("apply") }}
</el-button>
</div>
<ConfigurationFooter />
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { ref } from "vue";
import { useDashboardStore } from "@/store/modules/dashboard";
import type { LayoutConfig } from "@/types/dashboard";
import ConfigurationFooter from "./components/ConfigurationFooter.vue";
import "./style.scss";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const originConfig = dashboardStore.selectedGrid;
const eventAssociate = ref(dashboardStore.selectedGrid?.eventAssociate || false);
function updateConfig() {
@@ -40,37 +34,4 @@ limitations under the License. -->
dashboardStore.selectWidget({ ...selectedGrid, eventAssociate: eventAssociate.value } as LayoutConfig);
}
function applyConfig() {
dashboardStore.setConfigPanel(false);
dashboardStore.setConfigs(dashboardStore.selectedGrid as LayoutConfig);
}
function cancelConfig() {
dashboardStore.selectWidget(originConfig);
dashboardStore.setConfigPanel(false);
}
</script>
<style lang="scss" scoped>
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.item {
margin: 10px 0;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid $border-color-primary;
padding: 10px;
text-align: right;
width: 100%;
background-color: $theme-background;
}
</style>

View File

@@ -11,21 +11,14 @@ 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="item">
<span class="label">{{ t("tabExpressions") }}</span>
<div class="config-item flex-h">
<div class="config-label flex-h mr-20">{{ t("tabExpressions") }}</div>
<div class="mt-10" v-for="(child, index) in widgetTabs || []" :key="index">
<span class="name">{{ child.name }}</span>
<el-input class="input" size="small" v-model="expressions[child.name]" @change="changeExpression(child.name)" />
</div>
</div>
<div class="footer">
<el-button size="small" @click="cancelConfig">
{{ t("cancel") }}
</el-button>
<el-button size="small" type="primary" @click="applyConfig">
{{ t("apply") }}
</el-button>
</div>
<ConfigurationFooter />
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
@@ -34,6 +27,8 @@ limitations under the License. -->
import { ElMessage } from "element-plus";
import { WidgetType, ListEntity } from "@/views/dashboard/data";
import type { LayoutConfig } from "@/types/dashboard";
import ConfigurationFooter from "./components/ConfigurationFooter.vue";
import "./style.scss";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
@@ -67,43 +62,12 @@ limitations under the License. -->
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, children } as LayoutConfig);
}
function applyConfig() {
dashboardStore.setConfigPanel(false);
dashboardStore.setConfigs(dashboardStore.selectedGrid as LayoutConfig);
}
function cancelConfig() {
dashboardStore.selectWidget(originConfig);
dashboardStore.setConfigPanel(false);
}
</script>
<style lang="scss" scoped>
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.item {
margin-bottom: 10px;
}
.input {
width: 500px;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid $border-color-primary;
padding: 10px;
text-align: right;
width: 100%;
background-color: $theme-background;
}
.name {
width: 180px;
display: inline-block;

View File

@@ -11,16 +11,16 @@ 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="item">
<span class="label">{{ t("textUrl") }}</span>
<div class="config-item flex-h">
<div class="config-label flex-h mr-20">{{ t("textUrl") }}</div>
<el-input class="input" v-model="url" size="small" @change="changeConfig({ url })" />
</div>
<div class="item">
<span class="label">{{ t("content") }}</span>
<div class="config-item flex-h">
<div class="config-label flex-h mr-20">{{ t("content") }}</div>
<el-input class="input" v-model="content" size="small" @change="changeConfig({ content })" />
</div>
<div class="item">
<span class="label">{{ t("textAlign") }}</span>
<div class="config-item flex-h">
<div class="config-label flex-h mr-20">{{ t("textAlign") }}</div>
<Selector
:value="textAlign"
:options="AlignStyle"
@@ -30,8 +30,8 @@ limitations under the License. -->
@change="changeConfig({ textAlign: $event[0].value })"
/>
</div>
<div class="item">
<span class="label">{{ t("backgroundColors") }}</span>
<div class="config-item flex-h">
<div class="config-label flex-h mr-20">{{ t("backgroundColors") }}</div>
<Selector
:value="backgroundColor"
:options="Colors"
@@ -41,8 +41,8 @@ limitations under the License. -->
@change="changeConfig({ backgroundColor: $event[0].value })"
/>
</div>
<div class="item">
<span class="label">{{ t("fontSize") }}</span>
<div class="config-item flex-h">
<div class="config-label flex-h mr-20">{{ t("fontSize") }}</div>
<el-slider
class="slider"
v-model="fontSize"
@@ -54,8 +54,8 @@ limitations under the License. -->
@change="changeConfig({ fontSize })"
/>
</div>
<div class="item">
<span class="label">{{ t("fontColors") }}</span>
<div class="config-item flex-h">
<div class="config-label flex-h mr-20">{{ t("fontColors") }}</div>
<Selector
:value="fontColor"
:options="Colors"
@@ -65,20 +65,15 @@ limitations under the License. -->
@change="changeConfig({ fontColor: $event[0].value })"
/>
</div>
<div class="footer">
<el-button size="small" @click="cancelConfig">
{{ t("cancel") }}
</el-button>
<el-button size="small" type="primary" @click="applyConfig">
{{ t("apply") }}
</el-button>
</div>
<ConfigurationFooter />
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { ref } from "vue";
import { useDashboardStore } from "@/store/modules/dashboard";
import type { LayoutConfig, TextConfig } from "@/types/dashboard";
import ConfigurationFooter from "./components/ConfigurationFooter.vue";
import "./style.scss";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
@@ -119,15 +114,6 @@ limitations under the License. -->
};
dashboardStore.selectWidget({ ...selectedGrid, graph } as LayoutConfig);
}
function applyConfig() {
dashboardStore.setConfigPanel(false);
dashboardStore.setConfigs(dashboardStore.selectedGrid as LayoutConfig);
}
function cancelConfig() {
dashboardStore.selectWidget(originConfig);
dashboardStore.setConfigPanel(false);
}
</script>
<style lang="scss" scoped>
.slider {
@@ -135,29 +121,7 @@ limitations under the License. -->
margin-top: -3px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.input {
width: 500px;
}
.item {
margin-bottom: 10px;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid $border-color-primary;
padding: 10px;
text-align: right;
width: 100%;
background-color: $theme-background;
}
</style>

View File

@@ -11,19 +11,12 @@ 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="item">
<span class="label">{{ t("iframeSrc") }}</span>
<div class="config-item flex-h">
<div class="config-label flex-h mr-20">{{ t("iframeSrc") }}</div>
<el-input class="input" v-model="url" size="small" @change="handleUrlChange" :class="{ error: urlError }" />
<div v-if="urlError" class="error-message">{{ urlError }}</div>
</div>
<div class="footer">
<el-button size="small" @click="cancelConfig">
{{ t("cancel") }}
</el-button>
<el-button size="small" type="primary" @click="applyConfig" :disabled="!!urlError">
{{ t("apply") }}
</el-button>
</div>
<ConfigurationFooter />
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
@@ -31,6 +24,8 @@ limitations under the License. -->
import { useDashboardStore } from "@/store/modules/dashboard";
import { validateAndSanitizeUrl } from "@/utils/validateAndSanitizeUrl";
import type { LayoutConfig } from "@/types/dashboard";
import ConfigurationFooter from "./components/ConfigurationFooter.vue";
import "./style.scss";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
@@ -61,19 +56,6 @@ limitations under the License. -->
};
dashboardStore.selectWidget({ ...selectedGrid, widget } as LayoutConfig);
}
function applyConfig() {
if (urlError.value) {
return; // Don't apply if there's a validation error
}
dashboardStore.setConfigPanel(false);
dashboardStore.setConfigs(dashboardStore.selectedGrid as LayoutConfig);
}
function cancelConfig() {
dashboardStore.selectWidget(originConfig);
dashboardStore.setConfigPanel(false);
}
</script>
<style lang="scss" scoped>
.slider {
@@ -81,21 +63,10 @@ limitations under the License. -->
margin-top: -3px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.input {
width: 500px;
}
.item {
margin-bottom: 10px;
}
.url-input.error {
:deep(.el-input__inner) {
border-color: $error-color;
@@ -107,15 +78,4 @@ limitations under the License. -->
font-size: 12px;
margin-top: 4px;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid $border-color-primary;
padding: 10px;
text-align: right;
width: 100%;
background-color: $theme-background;
}
</style>

View File

@@ -11,12 +11,12 @@ 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="item">
<span class="label">{{ t("text") }}</span>
<div class="config-item flex-h">
<div class="config-label flex-h mr-20">{{ t("text") }}</div>
<el-input class="input" v-model="text" size="small" @change="changeConfig({ text })" />
</div>
<div class="item">
<span class="label">{{ t("textAlign") }}</span>
<div class="config-item flex-h">
<div class="config-label flex-h mr-20">{{ t("textAlign") }}</div>
<Selector
:value="textAlign"
:options="AlignStyle"
@@ -26,8 +26,8 @@ limitations under the License. -->
@change="changeConfig({ textAlign: $event[0].value })"
/>
</div>
<div class="item">
<span class="label">{{ t("backgroundColors") }}</span>
<div class="config-item flex-h">
<div class="config-label flex-h mr-20">{{ t("backgroundColors") }}</div>
<Selector
:value="backgroundColor"
:options="Colors"
@@ -37,8 +37,8 @@ limitations under the License. -->
@change="changeConfig({ backgroundColor: $event[0].value })"
/>
</div>
<div class="item">
<span class="label">{{ t("fontSize") }}</span>
<div class="config-item flex-h">
<div class="config-label flex-h mr-20">{{ t("fontSize") }}</div>
<el-slider
class="slider"
v-model="fontSize"
@@ -50,8 +50,8 @@ limitations under the License. -->
@change="changeConfig({ fontSize })"
/>
</div>
<div class="item">
<span class="label">{{ t("fontColors") }}</span>
<div class="config-item flex-h">
<div class="config-label flex-h mr-20">{{ t("fontColors") }}</div>
<Selector
:value="fontColor"
:options="Colors"
@@ -61,20 +61,15 @@ limitations under the License. -->
@change="changeConfig({ fontColor: $event[0].value })"
/>
</div>
<div class="footer">
<el-button size="small" @click="cancelConfig">
{{ t("cancel") }}
</el-button>
<el-button size="small" type="primary" @click="applyConfig">
{{ t("apply") }}
</el-button>
</div>
<ConfigurationFooter />
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { ref } from "vue";
import { useDashboardStore } from "@/store/modules/dashboard";
import type { TimeRangeConfig, LayoutConfig } from "@/types/dashboard";
import ConfigurationFooter from "./components/ConfigurationFooter.vue";
import "./style.scss";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
@@ -113,15 +108,6 @@ limitations under the License. -->
};
dashboardStore.selectWidget({ ...selectedGrid, graph } as LayoutConfig);
}
function applyConfig() {
dashboardStore.setConfigPanel(false);
dashboardStore.setConfigs(dashboardStore.selectedGrid as LayoutConfig);
}
function cancelConfig() {
dashboardStore.selectWidget(originConfig);
dashboardStore.setConfigPanel(false);
}
</script>
<style lang="scss" scoped>
.slider {
@@ -129,29 +115,7 @@ limitations under the License. -->
margin-top: -3px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.input {
width: 500px;
}
.item {
margin-bottom: 10px;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid $border-color-primary;
padding: 10px;
text-align: right;
width: 100%;
background-color: $theme-background;
}
</style>

View File

@@ -11,22 +11,15 @@ 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="item">
<span class="label">{{ t("showDepth") }}</span>
<div class="config-item flex-h">
<div class="config-label flex-h mr-20">{{ t("showDepth") }}</div>
<el-switch v-model="showDepth" active-text="Yes" inactive-text="No" @change="changeConfig({ showDepth })" />
</div>
<div class="item" v-show="showDepth">
<span class="label">{{ t("defaultDepth") }}</span>
<div class="config-item flex-h" v-show="showDepth">
<div class="config-label flex-h mr-20">{{ t("defaultDepth") }}</div>
<Selector class="input" size="small" :value="depth" :options="DepthList" @change="changeDepth($event)" />
</div>
<div class="footer">
<el-button size="small">
{{ t("cancel") }}
</el-button>
<el-button size="small" type="primary" @click="applyConfig">
{{ t("apply") }}
</el-button>
</div>
<ConfigurationFooter />
</template>
<script lang="ts" setup>
import { ref } from "vue";
@@ -35,6 +28,7 @@ limitations under the License. -->
import { DepthList } from "../data";
import type { Option } from "@/types/app";
import type { TopologyConfig, LayoutConfig } from "@/types/dashboard";
import ConfigurationFooter from "./components/ConfigurationFooter.vue";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
@@ -42,10 +36,6 @@ limitations under the License. -->
const showDepth = ref<boolean>(graph?.showDepth || false);
const depth = ref<number>(graph?.depth || 2);
function applyConfig() {
dashboardStore.setConfigs(dashboardStore.selectedGrid as LayoutConfig);
dashboardStore.setConfigPanel(false);
}
function changeConfig(param: { [key: string]: unknown }) {
const { selectedGrid } = dashboardStore;
const graph = {
@@ -60,25 +50,7 @@ limitations under the License. -->
}
</script>
<style lang="scss" scoped>
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid $border-color-primary;
padding: 10px;
text-align: right;
width: 100%;
background-color: $theme-background;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.item {
margin: 10px 0;
.input {
width: 300px;
}
</style>

View File

@@ -70,14 +70,7 @@ limitations under the License. -->
</el-collapse-item>
</el-collapse>
</div>
<div class="footer">
<el-button size="small" @click="cancelConfig">
{{ t("cancel") }}
</el-button>
<el-button size="small" type="primary" @click="applyConfig">
{{ t("apply") }}
</el-button>
</div>
<ConfigurationFooter />
</div>
</template>
<script lang="ts">
@@ -88,13 +81,15 @@ limitations under the License. -->
import type { Option } from "@/types/app";
import graphs from "../graphs";
import CustomOptions from "./widget/index";
import type { LayoutConfig } from "@/types/dashboard";
import ConfigurationFooter from "./components/ConfigurationFooter.vue";
import "./style.scss";
export default defineComponent({
name: "WidgetEdit",
components: {
...graphs,
...CustomOptions,
ConfigurationFooter,
},
setup() {
const configHeight = document.documentElement.clientHeight - 540;
@@ -115,7 +110,6 @@ limitations under the License. -->
index: dashboardStore.selectedGrid?.i,
visType: [],
});
const originConfig = dashboardStore.selectedGrid;
const widget = computed(() => dashboardStore.selectedGrid?.widget || {});
const graph = computed(() => dashboardStore.selectedGrid?.graph || {});
const title = computed(() => encodeURIComponent(widget.value.title || ""));
@@ -137,16 +131,6 @@ limitations under the License. -->
loading.value = load;
}
function applyConfig() {
dashboardStore.setConfigPanel(false);
dashboardStore.setConfigs(dashboardStore.selectedGrid as LayoutConfig);
}
function cancelConfig() {
dashboardStore.selectWidget(originConfig);
dashboardStore.setConfigPanel(false);
}
return {
states,
loading,
@@ -154,8 +138,6 @@ limitations under the License. -->
appStoreWithOut,
configHeight,
dashboardStore,
applyConfig,
cancelConfig,
getSource,
getErrors,
setLoading,
@@ -225,17 +207,6 @@ limitations under the License. -->
line-height: 400px;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid $border-color-primary;
padding: 10px;
text-align: right;
width: 100%;
background-color: $theme-background;
}
.collapse {
margin-top: 10px;
overflow: auto;

View File

@@ -0,0 +1,53 @@
<!-- 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="config-page-footer">
<el-button size="small" @click="cancelConfig">
{{ t("cancel") }}
</el-button>
<el-button size="small" type="primary" @click="applyConfig">
{{ t("apply") }}
</el-button>
</div>
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import type { LayoutConfig } from "@/types/dashboard";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const originConfig = dashboardStore.selectedGrid;
function applyConfig() {
dashboardStore.setConfigPanel(false);
dashboardStore.setConfigs(dashboardStore.selectedGrid as LayoutConfig);
}
function cancelConfig() {
dashboardStore.selectWidget(originConfig);
dashboardStore.setConfigPanel(false);
}
</script>
<style lang="scss" scoped>
.config-page-footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid $border-color-primary;
padding: 10px;
text-align: right;
width: 100%;
background-color: $theme-background;
}
</style>

View File

@@ -0,0 +1,26 @@
/**
* 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.
*/
.config-item {
margin: 10px 0;
}
.config-label {
font-size: 13px;
font-weight: 500;
align-items: center;
width: 200px;
}

View File

@@ -28,7 +28,7 @@ limitations under the License. -->
<Header :needQuery="needQuery" :data="data" />
</div>
<div class="log">
<List />
<List :data="data" />
</div>
</div>
</template>

View File

@@ -438,7 +438,7 @@ limitations under the License. -->
top: 0;
right: 10px;
cursor: pointer;
width: 120px;
width: 80px;
}
.tips {

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div>
<LogTable v-loading="logStore.loadLogs" :tableData="logStore.logs || []" :type="type" :noLink="false">
<LogTable v-loading="logStore.loadLogs" :tableData="logStore.logs || []" :type="type" :noLink="false" :data="data">
<div class="log-tips" v-if="!logStore.logs.length">{{ t("noData") }}</div>
</LogTable>
<div class="mt-5 mb-5">
@@ -34,11 +34,22 @@ limitations under the License. -->
<script lang="ts" setup>
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import type { PropType } from "vue";
import type { LayoutConfig } from "@/types/dashboard";
import LogTable from "./LogTable/Index.vue";
import { useLogStore } from "@/store/modules/log";
import { useDashboardStore } from "@/store/modules/dashboard";
import { ElMessage } from "element-plus";
/*global defineProps*/
defineProps({
needQuery: { type: Boolean, default: false },
data: {
type: Object as PropType<LayoutConfig>,
default: () => ({}),
},
});
const { t } = useI18n();
const logStore = useLogStore();
const dashboardStore = useDashboardStore();

View File

@@ -17,7 +17,19 @@ limitations under the License. -->
<div class="log">
<div class="log-header flex-h" :class="type === 'browser' ? ['browser-header', 'flex-h'] : 'service-header'">
<template v-for="(item, index) in columns" :key="`col${index}`">
<div :class="[item.label, ['message', 'stack'].includes(item.label) ? 'max-item' : '']">
<div v-if="item.label === 'content'" class="content">
<Selector
:options="[
{ label: 'Content', value: 'content' },
{ label: 'Timestamp - Content', value: 'contentTimestamp' },
]"
v-model="headerType"
size="small"
@change="handleHeaderType"
class="content-selector"
/>
</div>
<div v-else :class="[item.label, ['message', 'stack'].includes(item.label) ? 'max-item' : '']">
{{ item.value && t(item.value) }}
</div>
</template>
@@ -27,11 +39,12 @@ limitations under the License. -->
</div>
<div v-else>
<LogService
v-for="(item, index) in tableData"
v-for="(item, index) in logTableData"
:data="item"
:key="'service' + index"
:noLink="noLink"
@select="setCurrentLog"
:config="data"
/>
</div>
<slot></slot>
@@ -47,28 +60,54 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import type { PropType } from "vue";
import type { LayoutConfig } from "@/types/dashboard";
import { ServiceLogConstants, BrowserLogConstants, ServiceLogDetail } from "./data";
import LogBrowser from "./LogBrowser.vue";
import LogService from "./LogService.vue";
import LogDetail from "./LogDetail.vue";
import { useLogStore } from "@/store/modules/log";
import { dateFormat } from "@/utils/dateFormat";
import type { LogItem } from "@/types/log";
/*global defineProps */
const props = defineProps({
type: { type: String, default: "service" },
tableData: { type: Array, default: () => [] },
tableData: { type: Array as PropType<LogItem[]>, default: () => [] },
noLink: { type: Boolean, default: true },
data: {
type: Object as PropType<LayoutConfig>,
default: () => ({}),
},
});
const { t } = useI18n();
const currentLog = ref<any>({});
const logStore = useLogStore();
const currentLog = ref<LogItem>({} as LogItem);
const showDetail = ref<boolean>(false);
const columns: any[] = props.type === "browser" ? BrowserLogConstants : ServiceLogConstants;
const columns: Record<string, string>[] = props.type === "browser" ? BrowserLogConstants : ServiceLogConstants;
const headerType = ref<string>(localStorage.getItem("log-header-type") || "content");
const logTableData = computed(() => {
const logs = props.tableData.map((item: LogItem) => ({ ...item }));
return logs.map((item: LogItem) => {
item.content =
logStore.logHeaderType === "contentTimestamp"
? `${dateFormat(item.timestamp, "YYYY-MM-DD HH:mm:ss", "UTC")} ${item.content}`
: item.content;
return item;
});
});
function setCurrentLog(log: any) {
function setCurrentLog(log: LogItem) {
showDetail.value = true;
currentLog.value = log;
}
function handleHeaderType(param: { value: string }[]) {
localStorage.setItem("log-header-type", param[0].value);
logStore.setLogHeaderType(param[0].value);
}
</script>
<style lang="scss" scoped>
.log {
@@ -127,6 +166,11 @@ limitations under the License. -->
}
.service-header div {
width: 140px;
width: 200px;
padding: 0;
}
.content-selector {
width: 200px;
}
</style>

View File

@@ -21,10 +21,7 @@ limitations under the License. -->
:class="item.label"
@click="selectLog(item.label, data[item.label])"
>
<span v-if="item.label === 'timestamp'">
{{ dateFormat(data.timestamp) }}
</span>
<span v-else-if="item.label === 'tags'" :class="level.toLowerCase()"> > </span>
<span v-if="item.label === 'tags'" :class="level.toLowerCase()"> > </span>
<span class="blue" v-else-if="item.label === 'traceId'">
<el-tooltip content="Trace Link" v-if="!noLink && data[item.label]">
<Icon iconName="merge" />
@@ -36,20 +33,21 @@ limitations under the License. -->
</template>
<script lang="ts" setup>
import { computed, inject } from "vue";
import type { PropType } from "vue";
import { ServiceLogConstants } from "./data";
import getDashboard from "@/hooks/useDashboardsSession";
import { useDashboardStore } from "@/store/modules/dashboard";
import type { LayoutConfig, DashboardItem } from "@/types/dashboard";
import { dateFormat } from "@/utils/dateFormat";
import { WidgetType } from "@/views/dashboard/data";
import { useLogStore } from "@/store/modules/log";
const logStore = useLogStore();
/*global defineProps, defineEmits */
const props = defineProps({
data: { type: Object as any, default: () => ({}) },
noLink: { type: Boolean, default: true },
config: { type: Object as PropType<LayoutConfig>, default: () => ({}) },
});
const logStore = useLogStore();
const dashboardStore = useDashboardStore();
const options: LayoutConfig | null = inject("options") || null;
const emit = defineEmits(["select"]);
@@ -60,10 +58,10 @@ limitations under the License. -->
}
return (props.data.tags.find((d: { key: string; value: string }) => d.key === "level") || {}).value || "";
});
const highlightKeywords = (data: string) => {
const highlightKeywords = (content: string) => {
const keywords = Object.values(logStore.conditions.keywordsOfContent || {});
const regex = new RegExp(keywords.join("|"), "gi");
return data.replace(regex, (match) => `<span style="color: red">${match}</span>`);
return `${content}`.replace(regex, (match) => `<span style="color: red">${match}</span>`);
};
function selectLog(label: string, value: string) {

View File

@@ -26,7 +26,7 @@ export const ServiceLogConstants = [
},
{
label: "content",
value: "content",
value: "contentTimestamp",
},
];
export const ServiceLogDetail = [

View File

@@ -354,7 +354,7 @@ limitations under the License. -->
.search-btn {
cursor: pointer;
width: 120px;
width: 80px;
position: absolute;
top: 0;
right: 10px;