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;


Reply via email to