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 a8c5ec8d feat: refactor the configuration view and implement the optional config for displaying `timestamp` in Log widget (#492) a8c5ec8d is described below commit a8c5ec8dd26f4eaacbbac7e8edd2ac0773c94343 Author: Fine0830 <fanxue0...@gmail.com> AuthorDate: Wed Aug 20 15:30:01 2025 +0700 feat: refactor the configuration view and implement the optional config for displaying `timestamp` in Log widget (#492) --- src/components/Selector.vue | 2 +- src/locales/lang/en.ts | 2 +- src/locales/lang/zh.ts | 2 +- src/store/modules/log.ts | 5 ++ src/store/modules/trace.ts | 3 +- src/types/log.ts | 30 ++++++++++ src/utils/dateFormat.ts | 13 ++++- .../configuration/ContinuousProfiling.vue | 39 +++---------- src/views/dashboard/configuration/Event.vue | 49 ++-------------- src/views/dashboard/configuration/Tab.vue | 46 ++------------- src/views/dashboard/configuration/Text.vue | 66 +++++----------------- .../dashboard/configuration/ThirdPartyApp.vue | 50 ++-------------- src/views/dashboard/configuration/TimeRange.vue | 62 +++++--------------- src/views/dashboard/configuration/Topology.vue | 44 +++------------ src/views/dashboard/configuration/Widget.vue | 37 ++---------- .../ConfigurationFooter.vue} | 27 +-------- src/views/dashboard/configuration/style.scss | 26 +++++++++ src/views/dashboard/controls/Log.vue | 2 +- src/views/dashboard/related/log/Header.vue | 2 +- src/views/dashboard/related/log/List.vue | 13 ++++- src/views/dashboard/related/log/LogTable/Index.vue | 60 +++++++++++++++++--- .../dashboard/related/log/LogTable/LogService.vue | 14 ++--- src/views/dashboard/related/log/LogTable/data.ts | 2 +- src/views/dashboard/related/trace/Filter.vue | 2 +- 24 files changed, 216 insertions(+), 382 deletions(-) diff --git a/src/components/Selector.vue b/src/components/Selector.vue index 92a19d22..3ec91704 100644 --- a/src/components/Selector.vue +++ b/src/components/Selector.vue @@ -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: { diff --git a/src/locales/lang/en.ts b/src/locales/lang/en.ts index 78029437..4ac42de4 100644 --- a/src/locales/lang/en.ts +++ b/src/locales/lang/en.ts @@ -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. diff --git a/src/locales/lang/zh.ts b/src/locales/lang/zh.ts index a3b079b7..5bc9dfde 100644 --- a/src/locales/lang/zh.ts +++ b/src/locales/lang/zh.ts @@ -293,7 +293,7 @@ const msg = { return: "返回", isError: "错误", contentType: "内容类型", - content: "时间戳 - 内容", + content: "内容", level: "Level", viewLogs: "查看日志", logsTagsTip: "只有core/default/searchableLogsTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。", diff --git a/src/store/modules/log.ts b/src/store/modules/log.ts index d105cb2a..9500c1fb 100644 --- a/src/store/modules/log.ts +++ b/src/store/modules/log.ts @@ -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, diff --git a/src/store/modules/trace.ts b/src/store/modules/trace.ts index 2173a133..05d60c9e 100644 --- a/src/store/modules/trace.ts +++ b/src/store/modules/trace.ts @@ -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[]; diff --git a/src/types/log.ts b/src/types/log.ts new file mode 100644 index 00000000..9255eb10 --- /dev/null +++ b/src/types/log.ts @@ -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; +} diff --git a/src/utils/dateFormat.ts b/src/utils/dateFormat.ts index 7347f19b..f65ebd7b 100644 --- a/src/utils/dateFormat.ts +++ b/src/utils/dateFormat.ts @@ -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); +}; diff --git a/src/views/dashboard/configuration/ContinuousProfiling.vue b/src/views/dashboard/configuration/ContinuousProfiling.vue index e91e865c..ace70c7d 100644 --- a/src/views/dashboard/configuration/ContinuousProfiling.vue +++ b/src/views/dashboard/configuration/ContinuousProfiling.vue @@ -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; } diff --git a/src/views/dashboard/configuration/Event.vue b/src/views/dashboard/configuration/Event.vue index 9291e5ec..a4489476 100644 --- a/src/views/dashboard/configuration/Event.vue +++ b/src/views/dashboard/configuration/Event.vue @@ -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> diff --git a/src/views/dashboard/configuration/Tab.vue b/src/views/dashboard/configuration/Tab.vue index 5ce6dd6f..a7558592 100644 --- a/src/views/dashboard/configuration/Tab.vue +++ b/src/views/dashboard/configuration/Tab.vue @@ -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; diff --git a/src/views/dashboard/configuration/Text.vue b/src/views/dashboard/configuration/Text.vue index 3c4694e7..b55d27e3 100644 --- a/src/views/dashboard/configuration/Text.vue +++ b/src/views/dashboard/configuration/Text.vue @@ -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> diff --git a/src/views/dashboard/configuration/ThirdPartyApp.vue b/src/views/dashboard/configuration/ThirdPartyApp.vue index e9f9063f..8e52d152 100644 --- a/src/views/dashboard/configuration/ThirdPartyApp.vue +++ b/src/views/dashboard/configuration/ThirdPartyApp.vue @@ -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> diff --git a/src/views/dashboard/configuration/TimeRange.vue b/src/views/dashboard/configuration/TimeRange.vue index 14a8a551..06394738 100644 --- a/src/views/dashboard/configuration/TimeRange.vue +++ b/src/views/dashboard/configuration/TimeRange.vue @@ -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> diff --git a/src/views/dashboard/configuration/Topology.vue b/src/views/dashboard/configuration/Topology.vue index 9df56cfb..761c20fa 100644 --- a/src/views/dashboard/configuration/Topology.vue +++ b/src/views/dashboard/configuration/Topology.vue @@ -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> diff --git a/src/views/dashboard/configuration/Widget.vue b/src/views/dashboard/configuration/Widget.vue index a128074a..bf42a488 100644 --- a/src/views/dashboard/configuration/Widget.vue +++ b/src/views/dashboard/configuration/Widget.vue @@ -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; diff --git a/src/views/dashboard/configuration/Event.vue b/src/views/dashboard/configuration/components/ConfigurationFooter.vue similarity index 73% copy from src/views/dashboard/configuration/Event.vue copy to src/views/dashboard/configuration/components/ConfigurationFooter.vue index 9291e5ec..17f90fbf 100644 --- a/src/views/dashboard/configuration/Event.vue +++ b/src/views/dashboard/configuration/components/ConfigurationFooter.vue @@ -11,11 +11,7 @@ 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> - <el-switch v-model="eventAssociate" active-text="Yes" inactive-text="No" @change="updateConfig" /> - </div> - <div class="footer"> + <div class="config-page-footer"> <el-button size="small" @click="cancelConfig"> {{ t("cancel") }} </el-button> @@ -26,20 +22,12 @@ limitations under the License. --> </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"; const { t } = useI18n(); const dashboardStore = useDashboardStore(); const originConfig = dashboardStore.selectedGrid; - const eventAssociate = ref(dashboardStore.selectedGrid?.eventAssociate || false); - - function updateConfig() { - const { selectedGrid } = dashboardStore; - - dashboardStore.selectWidget({ ...selectedGrid, eventAssociate: eventAssociate.value } as LayoutConfig); - } function applyConfig() { dashboardStore.setConfigPanel(false); @@ -52,18 +40,7 @@ limitations under the License. --> } </script> <style lang="scss" scoped> - .label { - font-size: 13px; - font-weight: 500; - display: block; - margin-bottom: 5px; - } - - .item { - margin: 10px 0; - } - - .footer { + .config-page-footer { position: fixed; bottom: 0; right: 0; diff --git a/src/views/dashboard/configuration/style.scss b/src/views/dashboard/configuration/style.scss new file mode 100644 index 00000000..f67c8be2 --- /dev/null +++ b/src/views/dashboard/configuration/style.scss @@ -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; +} diff --git a/src/views/dashboard/controls/Log.vue b/src/views/dashboard/controls/Log.vue index 2f7a8c1b..194f602f 100644 --- a/src/views/dashboard/controls/Log.vue +++ b/src/views/dashboard/controls/Log.vue @@ -28,7 +28,7 @@ limitations under the License. --> <Header :needQuery="needQuery" :data="data" /> </div> <div class="log"> - <List /> + <List :data="data" /> </div> </div> </template> diff --git a/src/views/dashboard/related/log/Header.vue b/src/views/dashboard/related/log/Header.vue index 26b7ee2a..c0a013be 100644 --- a/src/views/dashboard/related/log/Header.vue +++ b/src/views/dashboard/related/log/Header.vue @@ -438,7 +438,7 @@ limitations under the License. --> top: 0; right: 10px; cursor: pointer; - width: 120px; + width: 80px; } .tips { diff --git a/src/views/dashboard/related/log/List.vue b/src/views/dashboard/related/log/List.vue index 8a5b5b4d..8307cf7a 100644 --- a/src/views/dashboard/related/log/List.vue +++ b/src/views/dashboard/related/log/List.vue @@ -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(); diff --git a/src/views/dashboard/related/log/LogTable/Index.vue b/src/views/dashboard/related/log/LogTable/Index.vue index fec460ff..376370a8 100644 --- a/src/views/dashboard/related/log/LogTable/Index.vue +++ b/src/views/dashboard/related/log/LogTable/Index.vue @@ -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> diff --git a/src/views/dashboard/related/log/LogTable/LogService.vue b/src/views/dashboard/related/log/LogTable/LogService.vue index 28119c0f..ce1fc908 100644 --- a/src/views/dashboard/related/log/LogTable/LogService.vue +++ b/src/views/dashboard/related/log/LogTable/LogService.vue @@ -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) { diff --git a/src/views/dashboard/related/log/LogTable/data.ts b/src/views/dashboard/related/log/LogTable/data.ts index 607ddcf4..6773d3d6 100644 --- a/src/views/dashboard/related/log/LogTable/data.ts +++ b/src/views/dashboard/related/log/LogTable/data.ts @@ -26,7 +26,7 @@ export const ServiceLogConstants = [ }, { label: "content", - value: "content", + value: "contentTimestamp", }, ]; export const ServiceLogDetail = [ diff --git a/src/views/dashboard/related/trace/Filter.vue b/src/views/dashboard/related/trace/Filter.vue index f703057c..5f65ea56 100644 --- a/src/views/dashboard/related/trace/Filter.vue +++ b/src/views/dashboard/related/trace/Filter.vue @@ -354,7 +354,7 @@ limitations under the License. --> .search-btn { cursor: pointer; - width: 120px; + width: 80px; position: absolute; top: 0; right: 10px;