build: migrate the build tool from vue-cli to vite4 (#208)

This commit is contained in:
Fine0830
2022-12-17 14:07:03 +08:00
committed by GitHub
parent 1e0c253488
commit 44dcb1e7f6
214 changed files with 27014 additions and 54234 deletions

View File

@@ -19,17 +19,17 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { useAppStoreWithOut } from "@/store/modules/app";
import Header from "./alarm/Header.vue";
import Content from "./alarm/Content.vue";
import { useAppStoreWithOut } from "@/store/modules/app";
import Header from "./alarm/Header.vue";
import Content from "./alarm/Content.vue";
const appStore = useAppStoreWithOut();
appStore.setPageTitle("Alerting");
const appStore = useAppStoreWithOut();
appStore.setPageTitle("Alerting");
</script>
<style lang="scss" scoped>
.alarm {
flex-grow: 1;
height: 100%;
font-size: 12px;
}
.alarm {
flex-grow: 1;
height: 100%;
font-size: 12px;
}
</style>

View File

@@ -19,18 +19,18 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { useAppStoreWithOut } from "@/store/modules/app";
import Header from "./event/Header.vue";
import Content from "./event/Content.vue";
import { useAppStoreWithOut } from "@/store/modules/app";
import Header from "./event/Header.vue";
import Content from "./event/Content.vue";
const appStore = useAppStoreWithOut();
const appStore = useAppStoreWithOut();
appStore.setPageTitle("Events");
appStore.setPageTitle("Events");
</script>
<style lang="scss" scoped>
.event {
flex-grow: 1;
height: 100%;
font-size: 12px;
}
.event {
flex-grow: 1;
height: 100%;
font-size: 12px;
}
</style>

View File

@@ -14,54 +14,50 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<Dashboard v-if="dashboardStore.currentDashboard" />
<div v-else class="no-root">
{{ t("noRoot") }} {{ dashboardStore.layerId }}
</div>
<div v-else class="no-root"> {{ t("noRoot") }} {{ dashboardStore.layerId }} </div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useRoute } from "vue-router";
import { EntityType } from "./dashboard/data";
import { useDashboardStore } from "@/store/modules/dashboard";
import Dashboard from "./dashboard/Edit.vue";
import { useI18n } from "vue-i18n";
import { useAppStoreWithOut } from "@/store/modules/app";
import { ref } from "vue";
import { useRoute } from "vue-router";
import { EntityType } from "./dashboard/data";
import { useDashboardStore } from "@/store/modules/dashboard";
import Dashboard from "./dashboard/Edit.vue";
import { useI18n } from "vue-i18n";
import { useAppStoreWithOut } from "@/store/modules/app";
const route = useRoute();
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore();
const layer = ref<string>("GENERAL");
const route = useRoute();
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore();
const layer = ref<string>("GENERAL");
getDashboard();
getDashboard();
async function getDashboard() {
layer.value = String(route.meta.layer);
dashboardStore.setLayer(layer.value);
dashboardStore.setMode(false);
await dashboardStore.setDashboards();
const item = dashboardStore.dashboards.find(
(d: { name: string; isRoot: boolean; layer: string; entity: string }) =>
d.layer === dashboardStore.layerId &&
[EntityType[0].value, EntityType[1].value].includes(d.entity) &&
d.isRoot
);
if (!item) {
appStore.setPageTitle(dashboardStore.layer);
dashboardStore.setCurrentDashboard(null);
dashboardStore.setEntity(EntityType[1].value);
return;
async function getDashboard() {
layer.value = String(route.meta.layer);
dashboardStore.setLayer(layer.value);
dashboardStore.setMode(false);
await dashboardStore.setDashboards();
const item = dashboardStore.dashboards.find(
(d: { name: string; isRoot: boolean; layer: string; entity: string }) =>
d.layer === dashboardStore.layerId && [EntityType[0].value, EntityType[1].value].includes(d.entity) && d.isRoot,
);
if (!item) {
appStore.setPageTitle(dashboardStore.layer);
dashboardStore.setCurrentDashboard(null);
dashboardStore.setEntity(EntityType[1].value);
return;
}
dashboardStore.setEntity(item.entity);
dashboardStore.setCurrentDashboard(item);
}
dashboardStore.setEntity(item.entity);
dashboardStore.setCurrentDashboard(item);
}
</script>
<style lang="scss" scoped>
.no-root {
padding: 15px;
width: 100%;
text-align: center;
color: #888;
}
.no-root {
padding: 15px;
width: 100%;
text-align: center;
color: #888;
}
</style>

View File

@@ -30,24 +30,10 @@ limitations under the License. -->
<div>
<span>UTC</span>
<span class="ml-5 mr-5">{{ utcHour >= 0 ? "+" : "" }}</span>
<input
type="number"
v-model="utcHour"
min="-12"
max="14"
class="utc-input"
@change="setUTCHour"
/>
<input type="number" v-model="utcHour" min="-12" max="14" class="utc-input" @change="setUTCHour" />
<span class="ml-5 mr-5">:</span>
<span class="utc-min">{{ utcMin > 9 || utcMin === 0 ? null : 0 }}</span>
<input
type="number"
v-model="utcMin"
min="0"
max="59"
class="utc-input"
@change="setUTCMin"
/>
<input type="number" v-model="utcMin" min="0" max="59" class="utc-input" @change="setUTCMin" />
</div>
</div>
<div class="flex-h item">
@@ -55,12 +41,7 @@ limitations under the License. -->
<el-switch v-model="auto" @change="handleAuto" style="height: 25px" />
<div class="auto-time ml-5">
<span class="auto-select">
<input
type="number"
v-model="autoTime"
@change="changeAutoTime"
min="1"
/>
<input type="number" v-model="autoTime" @change="changeAutoTime" min="1" />
</span>
{{ t("second") }}
<i class="ml-10">{{ t("timeReload") }}</i>
@@ -69,137 +50,136 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useAppStoreWithOut } from "@/store/modules/app";
import timeFormat from "@/utils/timeFormat";
import { Languages } from "@/constants/data";
import Selector from "@/components/Selector.vue";
import getLocalTime from "@/utils/localtime";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useAppStoreWithOut } from "@/store/modules/app";
import timeFormat from "@/utils/timeFormat";
import { Languages } from "@/constants/data";
import Selector from "@/components/Selector.vue";
import getLocalTime from "@/utils/localtime";
const { t, locale } = useI18n();
const appStore = useAppStoreWithOut();
const lang = ref<string>(locale.value || "en");
const autoTime = ref<number>(6);
const auto = ref<boolean>(appStore.autoRefresh || false);
const utcHour = ref<number>(appStore.utcHour);
const utcMin = ref<number>(appStore.utcMin);
const { t, locale } = useI18n();
const appStore = useAppStoreWithOut();
const lang = ref<string>(locale.value || "en");
const autoTime = ref<number>(6);
const auto = ref<boolean>(appStore.autoRefresh || false);
const utcHour = ref<number>(appStore.utcHour);
const utcMin = ref<number>(appStore.utcMin);
appStore.setPageTitle("Setting");
const handleReload = () => {
const gap =
appStore.duration.end.getTime() - appStore.duration.start.getTime();
const dates: Date[] = [
getLocalTime(appStore.utc, new Date(new Date().getTime() - gap)),
getLocalTime(appStore.utc, new Date()),
];
appStore.setDuration(timeFormat(dates));
};
const handleAuto = () => {
if (autoTime.value < 1) {
return;
}
appStore.setAutoRefresh(auto.value);
if (auto.value) {
handleReload();
appStore.setReloadTimer(setInterval(handleReload, autoTime.value * 1000));
} else {
appStore.setPageTitle("Setting");
const handleReload = () => {
const gap = appStore.duration.end.getTime() - appStore.duration.start.getTime();
const dates: Date[] = [
getLocalTime(appStore.utc, new Date(new Date().getTime() - gap)),
getLocalTime(appStore.utc, new Date()),
];
appStore.setDuration(timeFormat(dates));
};
const handleAuto = () => {
if (autoTime.value < 1) {
return;
}
appStore.setAutoRefresh(auto.value);
if (auto.value) {
handleReload();
appStore.setReloadTimer(setInterval(handleReload, autoTime.value * 1000));
} else {
if (appStore.reloadTimer) {
clearInterval(appStore.reloadTimer);
}
}
};
const changeAutoTime = () => {
if (autoTime.value < 1) {
return;
}
if (appStore.reloadTimer) {
clearInterval(appStore.reloadTimer);
}
}
};
const changeAutoTime = () => {
if (autoTime.value < 1) {
return;
}
if (appStore.reloadTimer) {
clearInterval(appStore.reloadTimer);
}
if (auto.value) {
handleReload();
appStore.setReloadTimer(setInterval(handleReload, autoTime.value * 1000));
}
};
const setLang = (): void => {
locale.value = lang.value;
window.localStorage.setItem("language", lang.value);
};
const setUTCHour = () => {
if (utcHour.value < -12) {
utcHour.value = -12;
}
if (utcHour.value > 14) {
utcHour.value = 14;
}
if (isNaN(utcHour.value)) {
utcHour.value = 0;
}
appStore.setUTC(utcHour.value, utcMin.value);
};
const setUTCMin = () => {
if (utcMin.value < 0) {
utcMin.value = 0;
}
if (utcMin.value > 59) {
utcMin.value = 59;
}
if (isNaN(utcMin.value)) {
utcMin.value = 0;
}
appStore.setUTC(utcHour.value, utcMin.value);
};
if (auto.value) {
handleReload();
appStore.setReloadTimer(setInterval(handleReload, autoTime.value * 1000));
}
};
const setLang = (): void => {
locale.value = lang.value;
window.localStorage.setItem("language", lang.value);
};
const setUTCHour = () => {
if (utcHour.value < -12) {
utcHour.value = -12;
}
if (utcHour.value > 14) {
utcHour.value = 14;
}
if (isNaN(utcHour.value)) {
utcHour.value = 0;
}
appStore.setUTC(utcHour.value, utcMin.value);
};
const setUTCMin = () => {
if (utcMin.value < 0) {
utcMin.value = 0;
}
if (utcMin.value > 59) {
utcMin.value = 59;
}
if (isNaN(utcMin.value)) {
utcMin.value = 0;
}
appStore.setUTC(utcHour.value, utcMin.value);
};
</script>
<style lang="scss" scoped>
.utc-input {
color: inherit;
background: 0;
border: 0;
outline: none;
padding-bottom: 0;
}
.utc-min {
display: inline-block;
padding-top: 2px;
}
.auto-select {
border-radius: 3px;
background-color: #fff;
padding: 1px;
input {
width: 38px;
border-style: unset;
outline: 0;
}
}
.settings {
color: #606266;
font-size: 13px;
padding: 20px;
.item {
margin-top: 10px;
.utc-input {
color: inherit;
background: 0;
border: 0;
outline: none;
padding-bottom: 0;
}
input {
outline: 0;
width: 50px;
border-radius: 3px;
border: 1px solid #ccc;
text-align: center;
height: 25px;
}
.label {
width: 180px;
.utc-min {
display: inline-block;
font-weight: 500;
color: #000;
line-height: 25px;
padding-top: 2px;
}
.auto-select {
border-radius: 3px;
background-color: #fff;
padding: 1px;
input {
width: 38px;
border-style: unset;
outline: 0;
}
}
.settings {
color: #606266;
font-size: 13px;
padding: 20px;
.item {
margin-top: 10px;
}
input {
outline: 0;
width: 50px;
border-radius: 3px;
border: 1px solid #ccc;
text-align: center;
height: 25px;
}
.label {
width: 180px;
display: inline-block;
font-weight: 500;
color: #000;
line-height: 25px;
}
}
}
</style>

View File

@@ -14,11 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="timeline-table clear">
<div
v-for="(i, index) in alarmStore.alarms"
:key="index"
class="clear timeline-item"
>
<div v-for="(i, index) in alarmStore.alarms" :key="index" class="clear timeline-item">
<div class="g-sm-3 grey sm hide-xs time-line tr">
{{ dateFormat(parseInt(i.startTime)) }}
</div>
@@ -50,11 +46,7 @@ limitations under the License. -->
:destroy-on-close="true"
@closed="isShowDetails = false"
>
<div
class="mb-10 clear alarm-detail"
v-for="(item, index) in AlarmDetailCol"
:key="index"
>
<div class="mb-10 clear alarm-detail" v-for="(item, index) in AlarmDetailCol" :key="index">
<span class="g-sm-2 grey">{{ t(item.value) }}:</span>
<span v-if="item.label === 'startTime'">
{{ dateFormat(currentDetail[item.label]) }}
@@ -66,24 +58,12 @@ limitations under the License. -->
<div>
<ul>
<li>
<span
v-for="(i, index) of EventsDetailHeaders"
:class="i.class"
:key="i.class + index"
>
<span v-for="(i, index) of EventsDetailHeaders" :class="i.class" :key="i.class + index">
{{ t(i.text) }}
</span>
</li>
<li
v-for="event in currentEvents"
:key="event.uuid"
@click="viewEventDetail(event)"
>
<span
v-for="(d, index) of EventsDetailHeaders"
:class="d.class"
:key="event.uuid + index"
>
<li v-for="event in currentEvents" :key="event.uuid" @click="viewEventDetail(event)">
<span v-for="(d, index) of EventsDetailHeaders" :class="d.class" :key="event.uuid + index">
<span v-if="d.class === 'startTime' || d.class === 'endTime'">
{{ dateFormat(event[d.class]) }}
</span>
@@ -106,29 +86,16 @@ limitations under the License. -->
@closed="showEventDetails = false"
>
<div class="event-detail">
<div
class="mb-10"
v-for="(eventKey, index) in EventsDetailKeys"
:key="index"
>
<div class="mb-10" v-for="(eventKey, index) in EventsDetailKeys" :key="index">
<span class="keys">{{ t(eventKey.text) }}</span>
<span v-if="eventKey.class === 'parameters'">
<span v-for="(d, index) of currentEvent[eventKey.class]" :key="index">
{{ d.key }}={{ d.value }};
</span>
<span v-for="(d, index) of currentEvent[eventKey.class]" :key="index"> {{ d.key }}={{ d.value }}; </span>
</span>
<span
v-else-if="
eventKey.class === 'startTime' || eventKey.class === 'endTime'
"
>
<span v-else-if="eventKey.class === 'startTime' || eventKey.class === 'endTime'">
{{ dateFormat(currentEvent[eventKey.class]) }}
</span>
<span v-else-if="eventKey.class === 'source'" class="source">
<span
>{{ t("service") }}:
{{ currentEvent[eventKey.class].service }}</span
>
<span>{{ t("service") }}: {{ currentEvent[eventKey.class].service }}</span>
<div v-show="currentEvent[eventKey.class].endpoint">
{{ t("endpoint") }}:
{{ currentEvent[eventKey.class].endpoint }}
@@ -144,45 +111,43 @@ limitations under the License. -->
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { Alarm, Event } from "@/types/alarm";
import { useAlarmStore } from "@/store/modules/alarm";
import { EventsDetailHeaders, AlarmDetailCol, EventsDetailKeys } from "./data";
import { dateFormat } from "@/utils/dateFormat";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import type { Alarm, Event } from "@/types/alarm";
import { useAlarmStore } from "@/store/modules/alarm";
import { EventsDetailHeaders, AlarmDetailCol, EventsDetailKeys } from "./data";
import { dateFormat } from "@/utils/dateFormat";
const { t } = useI18n();
const alarmStore = useAlarmStore();
const isShowDetails = ref<boolean>(false);
const showEventDetails = ref<boolean>(false);
const currentDetail = ref<Alarm | any>({});
const alarmTags = ref<string[]>([]);
const currentEvents = ref<any[]>([]);
const currentEvent = ref<Event | any>({});
const { t } = useI18n();
const alarmStore = useAlarmStore();
const isShowDetails = ref<boolean>(false);
const showEventDetails = ref<boolean>(false);
const currentDetail = ref<Alarm | any>({});
const alarmTags = ref<string[]>([]);
const currentEvents = ref<any[]>([]);
const currentEvent = ref<Event | any>({});
function showDetails(item: Alarm) {
isShowDetails.value = true;
currentDetail.value = item;
currentEvents.value = item.events;
alarmTags.value = currentDetail.value.tags.map(
(d: { key: string; value: string }) => {
function showDetails(item: Alarm) {
isShowDetails.value = true;
currentDetail.value = item;
currentEvents.value = item.events;
alarmTags.value = currentDetail.value.tags.map((d: { key: string; value: string }) => {
return `${d.key} = ${d.value}`;
}
);
}
});
}
function viewEventDetail(event: Event) {
currentEvent.value = event;
showEventDetails.value = true;
}
function viewEventDetail(event: Event) {
currentEvent.value = event;
showEventDetails.value = true;
}
</script>
<style lang="scss" scoped>
@import "../components/style.scss";
@import "../components/style.scss";
.tips {
width: 100%;
margin: 20px 0;
text-align: center;
font-size: 14px;
}
.tips {
width: 100%;
margin: 20px 0;
text-align: center;
font-size: 14px;
}
</style>

View File

@@ -28,12 +28,7 @@ limitations under the License. -->
</div>
<div class="mr-10 ml-10">
<span class="grey">{{ t("searchKeyword") }}: </span>
<el-input
size="small"
v-model="keyword"
class="alarm-tool-input"
@change="refreshAlarms({ pageNum: 1 })"
/>
<el-input size="small" v-model="keyword" class="alarm-tool-input" @change="refreshAlarms({ pageNum: 1 })" />
</div>
<div class="pagination">
<el-pagination
@@ -48,84 +43,79 @@ limitations under the License. -->
/>
</div>
</div>
<ConditionTags
:type="'ALARM'"
@update="(data) => refreshAlarms({ pageNum: 1, tagsMap: data.tagsMap })"
/>
<ConditionTags :type="'ALARM'" @update="(data) => refreshAlarms({ pageNum: 1, tagsMap: data.tagsMap })" />
</nav>
</template>
<script lang="ts" setup>
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import ConditionTags from "@/views/components/ConditionTags.vue";
import { AlarmOptions } from "./data";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useAlarmStore } from "@/store/modules/alarm";
import { ElMessage } from "element-plus";
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import ConditionTags from "@/views/components/ConditionTags.vue";
import { AlarmOptions } from "./data";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useAlarmStore } from "@/store/modules/alarm";
import { ElMessage } from "element-plus";
const appStore = useAppStoreWithOut();
const alarmStore = useAlarmStore();
const { t } = useI18n();
const pageSize = 20;
const entity = ref<string>("");
const keyword = ref<string>("");
const pageNum = ref<number>(1);
const total = computed(() =>
alarmStore.alarms.length === pageSize
? pageSize * pageNum.value + 1
: pageSize * pageNum.value
);
const appStore = useAppStoreWithOut();
const alarmStore = useAlarmStore();
const { t } = useI18n();
const pageSize = 20;
const entity = ref<string>("");
const keyword = ref<string>("");
const pageNum = ref<number>(1);
const total = computed(() =>
alarmStore.alarms.length === pageSize ? pageSize * pageNum.value + 1 : pageSize * pageNum.value,
);
refreshAlarms({ pageNum: 1 });
async function refreshAlarms(param: { pageNum: number; tagsMap?: any }) {
const params: any = {
duration: appStore.durationTime,
paging: {
pageNum: param.pageNum,
pageSize,
},
tags: param.tagsMap,
};
params.scope = entity.value || undefined;
params.keyword = keyword.value || undefined;
const res = await alarmStore.getAlarms(params);
if (res.errors) {
ElMessage.error(res.errors);
}
}
function changeEntity(param: any) {
entity.value = param[0].value;
refreshAlarms({ pageNum: 1 });
}
function changePage(p: number) {
pageNum.value = p;
refreshAlarms({ pageNum: p });
}
async function refreshAlarms(param: { pageNum: number; tagsMap?: any }) {
const params: any = {
duration: appStore.durationTime,
paging: {
pageNum: param.pageNum,
pageSize,
},
tags: param.tagsMap,
};
params.scope = entity.value || undefined;
params.keyword = keyword.value || undefined;
const res = await alarmStore.getAlarms(params);
if (res.errors) {
ElMessage.error(res.errors);
}
}
function changeEntity(param: any) {
entity.value = param[0].value;
refreshAlarms({ pageNum: 1 });
}
function changePage(p: number) {
pageNum.value = p;
refreshAlarms({ pageNum: p });
}
</script>
<style lang="scss" scoped>
.alarm-tool {
font-size: 12px;
border-bottom: 1px solid #c1c5ca41;
background-color: #f0f2f5;
padding: 10px;
position: relative;
}
.alarm-tool {
font-size: 12px;
border-bottom: 1px solid #c1c5ca41;
background-color: #f0f2f5;
padding: 10px;
position: relative;
}
.alarm-tool-input {
border-style: unset;
outline: 0;
padding: 2px 5px;
width: 250px;
border-radius: 3px;
}
.alarm-tool-input {
border-style: unset;
outline: 0;
padding: 2px 5px;
width: 250px;
border-radius: 3px;
}
.pagination {
position: absolute;
top: 10px;
right: 5px;
}
.pagination {
position: absolute;
top: 10px;
right: 5px;
}
</style>

View File

@@ -42,20 +42,12 @@ limitations under the License. -->
/>
</template>
<div class="content">
<span
v-for="(item, index) in tagList"
:key="index"
@click="selectTag(item)"
class="tag-item"
>
<span v-for="(item, index) in tagList" :key="index" @click="selectTag(item)" class="tag-item">
{{ item }}
</span>
</div>
</el-popover>
<span
class="tags-tip"
:class="type !== 'ALARM' && tagArr.length ? 'link-tips' : ''"
>
<span class="tags-tip" :class="type !== 'ALARM' && tagArr.length ? 'link-tips' : ''">
<a
target="blank"
href="https://github.com/apache/skywalking/blob/master/docs/en/setup/backend/configuration-vocabulary.md"
@@ -72,219 +64,219 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useTraceStore } from "@/store/modules/trace";
import { useLogStore } from "@/store/modules/log";
import { ElMessage } from "element-plus";
import { useAppStoreWithOut } from "@/store/modules/app";
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useTraceStore } from "@/store/modules/trace";
import { useLogStore } from "@/store/modules/log";
import { ElMessage } from "element-plus";
import { useAppStoreWithOut } from "@/store/modules/app";
/*global defineEmits, defineProps */
const emit = defineEmits(["update"]);
const props = defineProps({
type: { type: String, default: "TRACE" },
});
const traceStore = useTraceStore();
const logStore = useLogStore();
const appStore = useAppStoreWithOut();
const { t } = useI18n();
const tags = ref<string>("");
const tagsList = ref<string[]>([]);
const tagArr = ref<string[]>([]);
const tagList = ref<string[]>([]);
const tagKeys = ref<string[]>([]);
const keysList = ref<string[]>([]);
const visible = ref<boolean>(false);
const tipsMap = {
LOG: "logTagsTip",
TRACE: "traceTagsTip",
ALARM: "alarmTagsTip",
};
fetchTagKeys();
function removeTags(index: number) {
tagsList.value.splice(index, 1);
updateTags();
}
function addLabels() {
if (!tags.value) {
return;
}
tagsList.value.push(tags.value);
tags.value = "";
updateTags();
}
function updateTags() {
const tagsMap = tagsList.value.map((item: string) => {
const key = item.substring(0, item.indexOf("="));
return {
key,
value: item.substring(item.indexOf("=") + 1, item.length),
};
/*global defineEmits, defineProps */
const emit = defineEmits(["update"]);
const props = defineProps({
type: { type: String, default: "TRACE" },
});
emit("update", { tagsMap, tagsList: tagsList.value });
}
async function fetchTagKeys() {
let resp: any = {};
if (props.type === "TRACE") {
resp = await traceStore.getTagKeys();
} else {
resp = await logStore.getLogTagKeys();
const traceStore = useTraceStore();
const logStore = useLogStore();
const appStore = useAppStoreWithOut();
const { t } = useI18n();
const tags = ref<string>("");
const tagsList = ref<string[]>([]);
const tagArr = ref<string[]>([]);
const tagList = ref<string[]>([]);
const tagKeys = ref<string[]>([]);
const keysList = ref<string[]>([]);
const visible = ref<boolean>(false);
const tipsMap = {
LOG: "logTagsTip",
TRACE: "traceTagsTip",
ALARM: "alarmTagsTip",
};
fetchTagKeys();
function removeTags(index: number) {
tagsList.value.splice(index, 1);
updateTags();
}
function addLabels() {
if (!tags.value) {
return;
}
tagsList.value.push(tags.value);
tags.value = "";
updateTags();
}
function updateTags() {
const tagsMap = tagsList.value.map((item: string) => {
const key = item.substring(0, item.indexOf("="));
return {
key,
value: item.substring(item.indexOf("=") + 1, item.length),
};
});
emit("update", { tagsMap, tagsList: tagsList.value });
}
async function fetchTagKeys() {
let resp: any = {};
if (props.type === "TRACE") {
resp = await traceStore.getTagKeys();
} else {
resp = await logStore.getLogTagKeys();
}
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
tagArr.value = resp.data.tagKeys;
tagKeys.value = resp.data.tagKeys;
keysList.value = resp.data.tagKeys;
searchTags();
}
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
tagArr.value = resp.data.tagKeys;
tagKeys.value = resp.data.tagKeys;
keysList.value = resp.data.tagKeys;
searchTags();
}
async function fetchTagValues() {
const param = tags.value.split("=")[0];
let resp: any = {};
if (props.type === "TRACE") {
resp = await traceStore.getTagValues(param);
} else {
resp = await logStore.getLogTagValues(param);
}
async function fetchTagValues() {
const param = tags.value.split("=")[0];
let resp: any = {};
if (props.type === "TRACE") {
resp = await traceStore.getTagValues(param);
} else {
resp = await logStore.getLogTagValues(param);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
tagArr.value = resp.data.tagValues;
searchTags();
}
if (resp.errors) {
ElMessage.error(resp.errors);
return;
function inputTags() {
if (!tags.value) {
tagArr.value = keysList.value;
tagKeys.value = keysList.value;
tagList.value = tagArr.value;
return;
}
let search = "";
if (tags.value.includes("=")) {
search = tags.value.split("=")[1];
fetchTagValues();
} else {
search = tags.value;
}
tagList.value = tagArr.value.filter((d: string) => d.includes(search));
}
tagArr.value = resp.data.tagValues;
searchTags();
}
function inputTags() {
if (!tags.value) {
tagArr.value = keysList.value;
tagKeys.value = keysList.value;
tagList.value = tagArr.value;
return;
function addTags() {
if (!tags.value.includes("=")) {
return;
}
addLabels();
tagArr.value = tagKeys.value;
searchTags();
}
let search = "";
if (tags.value.includes("=")) {
search = tags.value.split("=")[1];
function selectTag(item: string) {
if (tags.value.includes("=")) {
const key = tags.value.split("=")[0];
tags.value = key + "=" + item;
addTags();
return;
}
tags.value = item + "=";
fetchTagValues();
} else {
search = tags.value;
}
tagList.value = tagArr.value.filter((d: string) => d.includes(search));
}
function addTags() {
if (!tags.value.includes("=")) {
return;
function searchTags() {
let search = "";
if (tags.value.includes("=")) {
search = tags.value.split("=")[1];
} else {
search = tags.value;
}
tagList.value = tagArr.value.filter((d: string) => d.includes(search));
}
addLabels();
tagArr.value = tagKeys.value;
searchTags();
}
function selectTag(item: string) {
if (tags.value.includes("=")) {
const key = tags.value.split("=")[0];
tags.value = key + "=" + item;
addTags();
return;
}
tags.value = item + "=";
fetchTagValues();
}
function searchTags() {
let search = "";
if (tags.value.includes("=")) {
search = tags.value.split("=")[1];
} else {
search = tags.value;
}
tagList.value = tagArr.value.filter((d: string) => d.includes(search));
}
watch(
() => appStore.durationTime,
() => {
fetchTagKeys();
}
);
watch(
() => appStore.durationTime,
() => {
fetchTagKeys();
},
);
</script>
<style lang="scss" scoped>
.trace-tags {
padding: 1px 5px 0 0;
border-radius: 3px;
height: 24px;
display: inline-block;
vertical-align: top;
}
.selected {
display: inline-block;
padding: 0 3px;
border-radius: 3px;
overflow: hidden;
border: 1px dashed #aaa;
font-size: 12px;
margin: 3px 2px 0 2px;
}
.trace-new-tag {
border-style: unset;
outline: 0;
padding: 2px 5px;
border-radius: 3px;
width: 250px;
}
.remove-icon {
display: inline-block;
margin-left: 3px;
cursor: pointer;
}
.tag-item {
display: inline-block;
min-width: 210px;
cursor: pointer;
margin-top: 10px;
&:hover {
color: #409eff;
}
}
.tags-tip {
color: #a7aebb;
}
.link-tips {
display: inline-block;
}
.light {
color: #3d444f;
input {
border: 1px solid #ccc;
.trace-tags {
padding: 1px 5px 0 0;
border-radius: 3px;
height: 24px;
display: inline-block;
vertical-align: top;
}
.selected {
color: #3d444f;
display: inline-block;
padding: 0 3px;
border-radius: 3px;
overflow: hidden;
border: 1px dashed #aaa;
font-size: 12px;
margin: 3px 2px 0 2px;
}
}
.icon-help {
cursor: pointer;
}
.trace-new-tag {
border-style: unset;
outline: 0;
padding: 2px 5px;
border-radius: 3px;
width: 250px;
}
.content {
width: 300px;
max-height: 400px;
overflow: auto;
}
.remove-icon {
display: inline-block;
margin-left: 3px;
cursor: pointer;
}
.tag-item {
display: inline-block;
min-width: 210px;
cursor: pointer;
margin-top: 10px;
&:hover {
color: #409eff;
}
}
.tags-tip {
color: #a7aebb;
}
.link-tips {
display: inline-block;
}
.light {
color: #3d444f;
input {
border: 1px solid #ccc;
}
.selected {
color: #3d444f;
}
}
.icon-help {
cursor: pointer;
}
.content {
width: 300px;
max-height: 400px;
overflow: auto;
}
</style>

View File

@@ -33,89 +33,87 @@ limitations under the License. -->
</div>
</template>
<script lang="ts">
import { ref, defineComponent } from "vue";
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
import GridLayout from "./panel/Layout.vue";
import Tool from "./panel/Tool.vue";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import Configuration from "./configuration";
import { LayoutConfig } from "@/types/dashboard";
import { ref, defineComponent } from "vue";
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
import GridLayout from "./panel/Layout.vue";
import Tool from "./panel/Tool.vue";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import Configuration from "./configuration";
import type { LayoutConfig } from "@/types/dashboard";
export default defineComponent({
name: "Dashboard",
components: { ...Configuration, GridLayout, Tool },
setup() {
const dashboardStore = useDashboardStore();
const appStore = useAppStoreWithOut();
const { t } = useI18n();
const p = useRoute().params;
const layoutKey = ref<string>(`${p.layerId}_${p.entity}_${p.name}`);
setTemplate();
async function setTemplate() {
await dashboardStore.setDashboards();
export default defineComponent({
name: "Dashboard",
components: { ...Configuration, GridLayout, Tool },
setup() {
const dashboardStore = useDashboardStore();
const appStore = useAppStoreWithOut();
const { t } = useI18n();
const p = useRoute().params;
const layoutKey = ref<string>(`${p.layerId}_${p.entity}_${p.name}`);
setTemplate();
async function setTemplate() {
await dashboardStore.setDashboards();
if (!p.entity) {
if (!dashboardStore.currentDashboard) {
return;
if (!p.entity) {
if (!dashboardStore.currentDashboard) {
return;
}
const { layer, entity, name } = dashboardStore.currentDashboard;
layoutKey.value = `${layer}_${entity}_${name}`;
}
const { layer, entity, name } = dashboardStore.currentDashboard;
layoutKey.value = `${layer}_${entity}_${name}`;
}
const c: { configuration: string; id: string } = JSON.parse(
sessionStorage.getItem(layoutKey.value) || "{}"
);
const layout: any = c.configuration || {};
const c: { configuration: string; id: string } = JSON.parse(sessionStorage.getItem(layoutKey.value) || "{}");
const layout: any = c.configuration || {};
dashboardStore.setLayout(setWidgetsID(layout.children || []));
appStore.setPageTitle(layout.name);
if (p.entity) {
dashboardStore.setCurrentDashboard({
layer: p.layerId,
entity: p.entity,
name: p.name,
id: c.id,
isRoot: layout.isRoot,
});
dashboardStore.setLayout(setWidgetsID(layout.children || []));
appStore.setPageTitle(layout.name);
if (p.entity) {
dashboardStore.setCurrentDashboard({
layer: p.layerId,
entity: p.entity,
name: p.name,
id: c.id,
isRoot: layout.isRoot,
});
}
}
}
function setWidgetsID(widgets: LayoutConfig[]) {
for (const item of widgets) {
item.id = item.i;
if (item.type === "Tab") {
if (item.children && item.children.length) {
for (const [index, child] of item.children.entries()) {
if (child.children && child.children.length) {
child.children.map((d: { i: string; index: string } | any) => {
d.id = `${item.i}-${index}-${d.i}`;
return d;
});
function setWidgetsID(widgets: LayoutConfig[]) {
for (const item of widgets) {
item.id = item.i;
if (item.type === "Tab") {
if (item.children && item.children.length) {
for (const [index, child] of item.children.entries()) {
if (child.children && child.children.length) {
child.children.map((d: { i: string; index: string } | any) => {
d.id = `${item.i}-${index}-${d.i}`;
return d;
});
}
}
}
}
}
return widgets;
}
return widgets;
}
function handleClick(e: any) {
e.stopPropagation();
if (e.target.className === "ds-main") {
dashboardStore.activeGridItem("");
dashboardStore.selectWidget(null);
function handleClick(e: any) {
e.stopPropagation();
if (e.target.className === "ds-main") {
dashboardStore.activeGridItem("");
dashboardStore.selectWidget(null);
}
}
}
return {
t,
handleClick,
dashboardStore,
};
},
});
return {
t,
handleClick,
dashboardStore,
};
},
});
</script>
<style lang="scss" scoped>
.ds-main {
overflow: auto;
}
.ds-main {
overflow: auto;
}
</style>

View File

@@ -33,9 +33,7 @@ limitations under the License. -->
{{ t("reloadDashboards") }}
</el-button>
<router-link to="/dashboard/new">
<el-button size="small" type="primary">
+ {{ t("newDashboard") }}
</el-button>
<el-button size="small" type="primary"> + {{ t("newDashboard") }} </el-button>
</router-link>
</div>
<div class="table">
@@ -74,10 +72,7 @@ limitations under the License. -->
<el-button size="small" @click="handleRename(scope.row)">
{{ t("rename") }}
</el-button>
<el-popconfirm
:title="t('deleteTitle')"
@confirm="handleDelete(scope.row)"
>
<el-popconfirm :title="t('deleteTitle')" @confirm="handleDelete(scope.row)">
<template #reference>
<el-button size="small" type="danger">
{{ t("delete") }}
@@ -87,11 +82,7 @@ limitations under the License. -->
<el-popconfirm
:title="t('rootTitle')"
@confirm="setRoot(scope.row)"
v-if="
[EntityType[0].value, EntityType[1].value].includes(
scope.row.entity
)
"
v-if="[EntityType[0].value, EntityType[1].value].includes(scope.row.entity)"
>
<template #reference>
<el-button size="small" style="width: 110px" type="danger">
@@ -140,228 +131,189 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { ElMessageBox, ElMessage } from "element-plus";
import type { ElTable } from "element-plus";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useDashboardStore } from "@/store/modules/dashboard";
import router from "@/router";
import { DashboardItem, LayoutConfig } from "@/types/dashboard";
import { saveFile, readFile } from "@/utils/file";
import { EntityType } from "./data";
import { isEmptyObject } from "@/utils/is";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { ElMessageBox, ElMessage } from "element-plus";
import { ElTable } from "element-plus";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useDashboardStore } from "@/store/modules/dashboard";
import router from "@/router";
import type { DashboardItem, LayoutConfig } from "@/types/dashboard";
import { saveFile, readFile } from "@/utils/file";
import { EntityType } from "./data";
import { isEmptyObject } from "@/utils/is";
/*global Nullable*/
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore();
const pageSize = 20;
const dashboards = ref<DashboardItem[]>([]);
const searchText = ref<string>("");
const loading = ref<boolean>(false);
const currentPage = ref<number>(1);
const total = ref<number>(0);
const multipleTableRef = ref<InstanceType<typeof ElTable>>();
const multipleSelection = ref<DashboardItem[]>([]);
const dashboardFile = ref<Nullable<HTMLDivElement>>(null);
/*global Nullable*/
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore();
const pageSize = 20;
const dashboards = ref<DashboardItem[]>([]);
const searchText = ref<string>("");
const loading = ref<boolean>(false);
const currentPage = ref<number>(1);
const total = ref<number>(0);
const multipleTableRef = ref<InstanceType<typeof ElTable>>();
const multipleSelection = ref<DashboardItem[]>([]);
const dashboardFile = ref<Nullable<HTMLDivElement>>(null);
appStore.setPageTitle("Dashboard List");
const handleSelectionChange = (val: DashboardItem[]) => {
multipleSelection.value = val;
};
setList();
async function setList() {
await dashboardStore.setDashboards();
searchDashboards(1);
}
async function importTemplates(event: any) {
const arr: any = await readFile(event);
for (const item of arr) {
const { layer, name, entity } = item.configuration;
const index = dashboardStore.dashboards.findIndex(
(d: DashboardItem) =>
d.name === name && d.entity === entity && d.layer === layer && !item.id
);
if (index > -1) {
return ElMessage.error(t("nameError"));
appStore.setPageTitle("Dashboard List");
const handleSelectionChange = (val: DashboardItem[]) => {
multipleSelection.value = val;
};
setList();
async function setList() {
await dashboardStore.setDashboards();
searchDashboards(1);
}
async function importTemplates(event: any) {
const arr: any = await readFile(event);
for (const item of arr) {
const { layer, name, entity } = item.configuration;
const index = dashboardStore.dashboards.findIndex(
(d: DashboardItem) => d.name === name && d.entity === entity && d.layer === layer && !item.id,
);
if (index > -1) {
return ElMessage.error(t("nameError"));
}
}
}
loading.value = true;
for (const item of arr) {
const { layer, name, entity, isRoot, children } = item.configuration;
const index = dashboardStore.dashboards.findIndex(
(d: DashboardItem) => d.id === item.id
);
const p: DashboardItem = {
name: name.split(" ").join("-"),
layer: layer,
entity: entity,
isRoot: false,
};
if (index > -1) {
p.id = item.id;
p.isRoot = isRoot;
loading.value = true;
for (const item of arr) {
const { layer, name, entity, isRoot, children } = item.configuration;
const index = dashboardStore.dashboards.findIndex((d: DashboardItem) => d.id === item.id);
const p: DashboardItem = {
name: name.split(" ").join("-"),
layer: layer,
entity: entity,
isRoot: false,
};
if (index > -1) {
p.id = item.id;
p.isRoot = isRoot;
}
dashboardStore.setCurrentDashboard(p);
dashboardStore.setLayout(children);
await dashboardStore.saveDashboard();
}
dashboardStore.setCurrentDashboard(p);
dashboardStore.setLayout(children);
await dashboardStore.saveDashboard();
dashboards.value = dashboardStore.dashboards;
loading.value = false;
dashboardFile.value = null;
}
dashboards.value = dashboardStore.dashboards;
loading.value = false;
dashboardFile.value = null;
}
function exportTemplates() {
if (!multipleSelection.value.length) {
return;
}
const arr = multipleSelection.value.sort(
(a: DashboardItem, b: DashboardItem) => {
function exportTemplates() {
if (!multipleSelection.value.length) {
return;
}
const arr = multipleSelection.value.sort((a: DashboardItem, b: DashboardItem) => {
return a.name.localeCompare(b.name);
}
);
const templates = arr.map((d: DashboardItem) => {
const key = [d.layer, d.entity, d.name].join("_");
const layout = JSON.parse(sessionStorage.getItem(key) || "{}");
return layout;
});
for (const item of templates) {
optimizeTemplate(item.configuration.children);
}
const name = `dashboards.json`;
saveFile(templates, name);
setTimeout(() => {
multipleTableRef.value!.clearSelection();
}, 2000);
}
function optimizeTemplate(
children: (LayoutConfig & {
moved?: boolean;
standard?: unknown;
label?: string;
value?: string;
})[]
) {
for (const child of children || []) {
delete child.moved;
delete child.activedTabIndex;
delete child.standard;
delete child.id;
delete child.label;
delete child.value;
if (isEmptyObject(child.graph)) {
delete child.graph;
}
if (child.widget) {
if (child.widget.title === "") {
delete child.widget.title;
}
if (child.widget.tips === "") {
delete child.widget.tips;
}
}
if (isEmptyObject(child.widget)) {
delete child.widget;
}
if (!(child.metrics && child.metrics.length && child.metrics[0])) {
delete child.metrics;
}
if (
!(child.metricTypes && child.metricTypes.length && child.metricTypes[0])
) {
delete child.metricTypes;
}
if (child.metricConfig && child.metricConfig.length) {
child.metricConfig.forEach((c, index) => {
if (!c.calculation) {
delete c.calculation;
}
if (!c.unit) {
delete c.unit;
}
if (!c.label) {
delete c.label;
}
if (isEmptyObject(c)) {
(child.metricConfig || []).splice(index, 1);
}
});
}
if (!(child.metricConfig && child.metricConfig.length)) {
delete child.metricConfig;
}
if (child.type === "Tab") {
for (const item of child.children || []) {
optimizeTemplate(item.children);
}
}
if (
["Trace", "Topology", "Tab", "Profile", "Ebpf", "Log"].includes(
child.type
)
) {
delete child.widget;
}
}
}
function handleEdit(row: DashboardItem) {
dashboardStore.setMode(true);
dashboardStore.setEntity(row.entity);
dashboardStore.setLayer(row.layer);
dashboardStore.setCurrentDashboard(row);
router.push(`/dashboard/${row.layer}/${row.entity}/${row.name}`);
}
function handleView(row: DashboardItem) {
dashboardStore.setMode(false);
dashboardStore.setEntity(row.entity);
dashboardStore.setLayer(row.layer);
dashboardStore.setCurrentDashboard(row);
router.push(`/dashboard/${row.layer}/${row.entity}/${row.name}`);
}
async function setRoot(row: DashboardItem) {
const items: DashboardItem[] = [];
loading.value = true;
for (const d of dashboardStore.dashboards) {
if (d.id === row.id) {
d.isRoot = !row.isRoot;
});
const templates = arr.map((d: DashboardItem) => {
const key = [d.layer, d.entity, d.name].join("_");
const layout = sessionStorage.getItem(key) || "{}";
const c = {
...JSON.parse(layout).configuration,
...d,
};
delete c.id;
const setting = {
id: d.id,
configuration: JSON.stringify(c),
};
const res = await dashboardStore.updateDashboard(setting);
if (res.data.changeTemplate.id) {
sessionStorage.setItem(
key,
JSON.stringify({
id: d.id,
configuration: c,
})
);
const layout = JSON.parse(sessionStorage.getItem(key) || "{}");
return layout;
});
for (const item of templates) {
optimizeTemplate(item.configuration.children);
}
const name = `dashboards.json`;
saveFile(templates, name);
setTimeout(() => {
multipleTableRef.value!.clearSelection();
}, 2000);
}
function optimizeTemplate(
children: (LayoutConfig & {
moved?: boolean;
standard?: unknown;
label?: string;
value?: string;
})[],
) {
for (const child of children || []) {
delete child.moved;
delete child.activedTabIndex;
delete child.standard;
delete child.id;
delete child.label;
delete child.value;
if (isEmptyObject(child.graph)) {
delete child.graph;
}
} else {
if (
d.layer === row.layer &&
[EntityType[0].value, EntityType[1].value].includes(d.entity) &&
row.isRoot === false &&
d.isRoot === true
) {
d.isRoot = false;
if (child.widget) {
if (child.widget.title === "") {
delete child.widget.title;
}
if (child.widget.tips === "") {
delete child.widget.tips;
}
}
if (isEmptyObject(child.widget)) {
delete child.widget;
}
if (!(child.metrics && child.metrics.length && child.metrics[0])) {
delete child.metrics;
}
if (!(child.metricTypes && child.metricTypes.length && child.metricTypes[0])) {
delete child.metricTypes;
}
if (child.metricConfig && child.metricConfig.length) {
child.metricConfig.forEach((c, index) => {
if (!c.calculation) {
delete c.calculation;
}
if (!c.unit) {
delete c.unit;
}
if (!c.label) {
delete c.label;
}
if (isEmptyObject(c)) {
(child.metricConfig || []).splice(index, 1);
}
});
}
if (!(child.metricConfig && child.metricConfig.length)) {
delete child.metricConfig;
}
if (child.type === "Tab") {
for (const item of child.children || []) {
optimizeTemplate(item.children);
}
}
if (["Trace", "Topology", "Tab", "Profile", "Ebpf", "Log"].includes(child.type)) {
delete child.widget;
}
}
}
function handleEdit(row: DashboardItem) {
dashboardStore.setMode(true);
dashboardStore.setEntity(row.entity);
dashboardStore.setLayer(row.layer);
dashboardStore.setCurrentDashboard(row);
router.push(`/dashboard/${row.layer}/${row.entity}/${row.name}`);
}
function handleView(row: DashboardItem) {
dashboardStore.setMode(false);
dashboardStore.setEntity(row.entity);
dashboardStore.setLayer(row.layer);
dashboardStore.setCurrentDashboard(row);
router.push(`/dashboard/${row.layer}/${row.entity}/${row.name}`);
}
async function setRoot(row: DashboardItem) {
const items: DashboardItem[] = [];
loading.value = true;
for (const d of dashboardStore.dashboards) {
if (d.id === row.id) {
d.isRoot = !row.isRoot;
const key = [d.layer, d.entity, d.name].join("_");
const layout = sessionStorage.getItem(key) || "{}";
const c = {
...JSON.parse(layout).configuration,
...d,
};
delete c.id;
const setting = {
id: d.id,
configuration: JSON.stringify(c),
@@ -373,177 +325,202 @@ async function setRoot(row: DashboardItem) {
JSON.stringify({
id: d.id,
configuration: c,
})
}),
);
}
} else {
if (
d.layer === row.layer &&
[EntityType[0].value, EntityType[1].value].includes(d.entity) &&
row.isRoot === false &&
d.isRoot === true
) {
d.isRoot = false;
const key = [d.layer, d.entity, d.name].join("_");
const layout = sessionStorage.getItem(key) || "{}";
const c = {
...JSON.parse(layout).configuration,
...d,
};
const setting = {
id: d.id,
configuration: JSON.stringify(c),
};
const res = await dashboardStore.updateDashboard(setting);
if (res.data.changeTemplate.id) {
sessionStorage.setItem(
key,
JSON.stringify({
id: d.id,
configuration: c,
}),
);
}
}
}
items.push(d);
}
items.push(d);
dashboardStore.resetDashboards(items);
searchDashboards(1);
loading.value = false;
}
dashboardStore.resetDashboards(items);
searchDashboards(1);
loading.value = false;
}
function handleRename(row: DashboardItem) {
ElMessageBox.prompt("Please input dashboard name", "Edit", {
confirmButtonText: "OK",
cancelButtonText: "Cancel",
inputValue: row.name,
})
.then(({ value }) => {
updateName(row, value);
function handleRename(row: DashboardItem) {
ElMessageBox.prompt("Please input dashboard name", "Edit", {
confirmButtonText: "OK",
cancelButtonText: "Cancel",
inputValue: row.name,
})
.catch(() => {
ElMessage({
type: "info",
message: "Input canceled",
.then(({ value }) => {
updateName(row, value);
})
.catch(() => {
ElMessage({
type: "info",
message: "Input canceled",
});
});
});
}
async function updateName(d: DashboardItem, value: string) {
if (new RegExp(/\s/).test(value)) {
ElMessage.error("The name cannot contain spaces, carriage returns, etc");
return;
}
const key = [d.layer, d.entity, d.name].join("_");
const layout = sessionStorage.getItem(key) || "{}";
const c = {
...JSON.parse(layout).configuration,
...d,
name: value,
};
delete c.id;
delete c.filters;
const setting = {
id: d.id,
configuration: JSON.stringify(c),
};
loading.value = true;
const res = await dashboardStore.updateDashboard(setting);
loading.value = false;
if (!res.data.changeTemplate.id) {
return;
}
dashboardStore.setCurrentDashboard({
...d,
name: value,
});
dashboards.value = dashboardStore.dashboards.map((item: any) => {
if (dashboardStore.currentDashboard.id === item.id) {
item = dashboardStore.currentDashboard;
async function updateName(d: DashboardItem, value: string) {
if (new RegExp(/\s/).test(value)) {
ElMessage.error("The name cannot contain spaces, carriage returns, etc");
return;
}
return item;
});
dashboardStore.resetDashboards(dashboards.value);
sessionStorage.setItem("dashboards", JSON.stringify(dashboards.value));
sessionStorage.removeItem(key);
const str = [
dashboardStore.currentDashboard.layer,
dashboardStore.currentDashboard.entity,
dashboardStore.currentDashboard.name,
].join("_");
sessionStorage.setItem(
str,
JSON.stringify({
const key = [d.layer, d.entity, d.name].join("_");
const layout = sessionStorage.getItem(key) || "{}";
const c = {
...JSON.parse(layout).configuration,
...d,
name: value,
};
delete c.id;
delete c.filters;
const setting = {
id: d.id,
configuration: c,
})
);
searchText.value = "";
}
async function handleDelete(row: DashboardItem) {
dashboardStore.setCurrentDashboard(row);
loading.value = true;
await dashboardStore.deleteDashboard();
dashboards.value = dashboardStore.dashboards;
loading.value = false;
sessionStorage.setItem("dashboards", JSON.stringify(dashboards.value));
sessionStorage.removeItem(`${row.layer}_${row.entity}_${row.name}`);
}
function searchDashboards(pageIndex?: any) {
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
const arr = list.filter((d: { name: string }) =>
d.name.includes(searchText.value)
);
configuration: JSON.stringify(c),
};
loading.value = true;
const res = await dashboardStore.updateDashboard(setting);
loading.value = false;
if (!res.data.changeTemplate.id) {
return;
}
dashboardStore.setCurrentDashboard({
...d,
name: value,
});
dashboards.value = dashboardStore.dashboards.map((item: any) => {
if (dashboardStore.currentDashboard.id === item.id) {
item = dashboardStore.currentDashboard;
}
return item;
});
dashboardStore.resetDashboards(dashboards.value);
sessionStorage.setItem("dashboards", JSON.stringify(dashboards.value));
sessionStorage.removeItem(key);
const str = [
dashboardStore.currentDashboard.layer,
dashboardStore.currentDashboard.entity,
dashboardStore.currentDashboard.name,
].join("_");
sessionStorage.setItem(
str,
JSON.stringify({
id: d.id,
configuration: c,
}),
);
searchText.value = "";
}
async function handleDelete(row: DashboardItem) {
dashboardStore.setCurrentDashboard(row);
loading.value = true;
await dashboardStore.deleteDashboard();
dashboards.value = dashboardStore.dashboards;
loading.value = false;
sessionStorage.setItem("dashboards", JSON.stringify(dashboards.value));
sessionStorage.removeItem(`${row.layer}_${row.entity}_${row.name}`);
}
function searchDashboards(pageIndex?: any) {
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
const arr = list.filter((d: { name: string }) => d.name.includes(searchText.value));
total.value = arr.length;
dashboards.value = arr.filter(
(d: { name: string }, index: number) =>
index < pageIndex * pageSize && index >= (pageIndex - 1) * pageSize
);
currentPage.value = pageIndex;
}
total.value = arr.length;
dashboards.value = arr.filter(
(d: { name: string }, index: number) => index < pageIndex * pageSize && index >= (pageIndex - 1) * pageSize,
);
currentPage.value = pageIndex;
}
async function reloadTemplates() {
loading.value = true;
await dashboardStore.resetTemplates();
loading.value = false;
}
function changePage(pageIndex: number) {
currentPage.value = pageIndex;
searchDashboards(pageIndex);
}
async function reloadTemplates() {
loading.value = true;
await dashboardStore.resetTemplates();
loading.value = false;
}
function changePage(pageIndex: number) {
currentPage.value = pageIndex;
searchDashboards(pageIndex);
}
</script>
<style lang="scss" scoped>
.header {
flex-direction: row-reverse;
}
.header {
flex-direction: row-reverse;
}
.dashboard-list {
padding: 20px;
width: 100%;
overflow: hidden;
}
.dashboard-list {
padding: 20px;
width: 100%;
overflow: hidden;
}
.input-with-search {
width: 250px;
}
.input-with-search {
width: 250px;
}
.table {
padding: 20px 10px;
background-color: #fff;
box-shadow: 0px 1px 4px 0px #00000029;
border-radius: 5px;
width: 100%;
height: 100%;
overflow: auto;
}
.table {
padding: 20px 10px;
background-color: #fff;
box-shadow: 0px 1px 4px 0px #00000029;
border-radius: 5px;
width: 100%;
height: 100%;
overflow: auto;
}
.toggle-selection {
margin-top: 20px;
background-color: #fff;
}
.toggle-selection {
margin-top: 20px;
background-color: #fff;
}
.pagination {
float: right;
}
.pagination {
float: right;
}
.btn {
width: 220px;
font-size: 13px;
}
.btn {
width: 220px;
font-size: 13px;
}
.import-template {
display: none;
}
.import-template {
display: none;
}
.input-label {
line-height: 30px;
height: 30px;
width: 220px;
cursor: pointer;
}
.input-label {
line-height: 30px;
height: 30px;
width: 220px;
cursor: pointer;
}
.name {
color: #409eff;
}
.name {
color: #409eff;
}
.reload {
margin-right: 3px;
}
.reload {
margin-right: 3px;
}
.reload-btn {
display: inline-block;
margin-left: 10px;
}
.reload-btn {
display: inline-block;
margin-left: 10px;
}
</style>

View File

@@ -17,11 +17,7 @@ limitations under the License. -->
<div class="title">{{ t("newDashboard") }}</div>
<div class="item">
<div class="label">{{ t("name") }}</div>
<el-input
size="default"
v-model="states.name"
placeholder="Please input name"
/>
<el-input size="default" v-model="states.name" placeholder="Please input name" />
</div>
<div class="item">
<div class="label">{{ t("layer") }}</div>
@@ -51,95 +47,93 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { reactive } from "vue";
import { useI18n } from "vue-i18n";
import router from "@/router";
import { useSelectorStore } from "@/store/modules/selectors";
import { EntityType } from "./data";
import { ElMessage } from "element-plus";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useDashboardStore } from "@/store/modules/dashboard";
import { reactive } from "vue";
import { useI18n } from "vue-i18n";
import router from "@/router";
import { useSelectorStore } from "@/store/modules/selectors";
import { EntityType } from "./data";
import { ElMessage } from "element-plus";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useDashboardStore } from "@/store/modules/dashboard";
const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore();
appStore.setPageTitle("Dashboard New");
const { t } = useI18n();
const selectorStore = useSelectorStore();
const states = reactive({
name: "",
selectedLayer: "",
entity: EntityType[0].value,
layers: [],
});
setLayers();
dashboardStore.setDashboards();
const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore();
appStore.setPageTitle("Dashboard New");
const { t } = useI18n();
const selectorStore = useSelectorStore();
const states = reactive({
name: "",
selectedLayer: "",
entity: EntityType[0].value,
layers: [],
});
setLayers();
dashboardStore.setDashboards();
const onCreate = () => {
const index = dashboardStore.dashboards.findIndex(
(d: { name: string; entity: string; layer: string }) =>
d.name === states.name &&
states.entity === d.entity &&
states.selectedLayer === d.layer
);
if (!states.name) {
ElMessage.error(t("nameEmptyError"));
return;
const onCreate = () => {
const index = dashboardStore.dashboards.findIndex(
(d: { name: string; entity: string; layer: string }) =>
d.name === states.name && states.entity === d.entity && states.selectedLayer === d.layer,
);
if (!states.name) {
ElMessage.error(t("nameEmptyError"));
return;
}
if (index > -1) {
ElMessage.error(t("nameError"));
return;
}
dashboardStore.setCurrentDashboard({
name: states.name,
entity: states.entity,
layer: states.selectedLayer,
});
const name = states.name;
const path = `/dashboard/${states.selectedLayer}/${states.entity}/${name}`;
router.push(path);
};
async function setLayers() {
const resp = await selectorStore.fetchLayers();
if (resp.errors) {
ElMessage.error(resp.errors);
}
states.selectedLayer = resp.data.layers[0];
states.layers = resp.data.layers.map((d: string) => {
return { label: d, value: d };
});
}
if (index > -1) {
ElMessage.error(t("nameError"));
return;
function changeLayer(opt: { label: string; value: string }[] | any) {
states.selectedLayer = opt[0].value;
}
dashboardStore.setCurrentDashboard({
name: states.name,
entity: states.entity,
layer: states.selectedLayer,
});
const name = states.name;
const path = `/dashboard/${states.selectedLayer}/${states.entity}/${name}`;
router.push(path);
};
async function setLayers() {
const resp = await selectorStore.fetchLayers();
if (resp.errors) {
ElMessage.error(resp.errors);
function changeEntity(opt: { label: string; value: string }[] | any) {
states.entity = opt[0].value;
}
states.selectedLayer = resp.data.layers[0];
states.layers = resp.data.layers.map((d: string) => {
return { label: d, value: d };
});
}
function changeLayer(opt: { label: string; value: string }[] | any) {
states.selectedLayer = opt[0].value;
}
function changeEntity(opt: { label: string; value: string }[] | any) {
states.entity = opt[0].value;
}
</script>
<style lang="scss" scoped>
.title {
font-size: 18px;
font-weight: bold;
padding-top: 20px;
}
.title {
font-size: 18px;
font-weight: bold;
padding-top: 20px;
}
.new-dashboard {
margin: 0 auto;
}
.new-dashboard {
margin: 0 auto;
}
.item {
margin-top: 20px;
}
.item {
margin-top: 20px;
}
.new-dashboard,
.selectors {
width: 600px;
}
.new-dashboard,
.selectors {
width: 600px;
}
.create {
width: 600px;
}
.create {
width: 600px;
}
.btn {
margin-top: 40px;
}
.btn {
margin-top: 40px;
}
</style>

View File

@@ -13,12 +13,7 @@ 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"
/>
<el-switch v-model="eventAssociate" active-text="Yes" inactive-text="No" @change="updateConfig" />
</div>
<div class="footer">
<el-button size="small" @click="cancelConfig">
@@ -30,53 +25,53 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { ref } from "vue";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useI18n } from "vue-i18n";
import { ref } from "vue";
import { useDashboardStore } from "@/store/modules/dashboard";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const originConfig = dashboardStore.selectedGrid;
const eventAssociate = ref(dashboardStore.selectedGrid.eventAssociate || false);
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const originConfig = dashboardStore.selectedGrid;
const eventAssociate = ref(dashboardStore.selectedGrid.eventAssociate || false);
function updateConfig() {
dashboardStore.selectedGrid = {
...dashboardStore.selectedGrid,
eventAssociate,
};
dashboardStore.selectWidget(dashboardStore.selectedGrid);
}
function updateConfig() {
dashboardStore.selectedGrid = {
...dashboardStore.selectedGrid,
eventAssociate,
};
dashboardStore.selectWidget(dashboardStore.selectedGrid);
}
function applyConfig() {
dashboardStore.setConfigPanel(false);
dashboardStore.setConfigs(dashboardStore.selectedGrid);
}
function applyConfig() {
dashboardStore.setConfigPanel(false);
dashboardStore.setConfigs(dashboardStore.selectedGrid);
}
function cancelConfig() {
dashboardStore.selectWidget(originConfig);
dashboardStore.setConfigPanel(false);
}
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;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.item {
margin: 10px 0;
}
.item {
margin: 10px 0;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid #eee;
padding: 10px;
text-align: right;
width: 100%;
background-color: #fff;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid #eee;
padding: 10px;
text-align: right;
width: 100%;
background-color: #fff;
}
</style>

View File

@@ -13,21 +13,11 @@ limitations under the License. -->
<template>
<div class="item">
<span class="label">{{ t("textUrl") }}</span>
<el-input
class="input"
v-model="url"
size="small"
@change="changeConfig({ url })"
/>
<el-input class="input" v-model="url" size="small" @change="changeConfig({ url })" />
</div>
<div class="item">
<span class="label">{{ t("content") }}</span>
<el-input
class="input"
v-model="content"
size="small"
@change="changeConfig({ content })"
/>
<el-input class="input" v-model="content" size="small" @change="changeConfig({ content })" />
</div>
<div class="item">
<span class="label">{{ t("textAlign") }}</span>
@@ -85,86 +75,86 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { ref } from "vue";
import { useDashboardStore } from "@/store/modules/dashboard";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const originConfig = dashboardStore.selectedGrid;
const graph = originConfig.graph || {};
const url = ref(graph.url || "");
const backgroundColor = ref(graph.backgroundColor || "green");
const fontColor = ref(graph.fontColor || "white");
const content = ref<string>(graph.content || "");
const fontSize = ref<number>(graph.fontSize || 12);
const textAlign = ref(graph.textAlign || "left");
const Colors = [
{
label: "Green",
value: "green",
},
{ label: "Blue", value: "blue" },
{ label: "Red", value: "red" },
{ label: "Grey", value: "grey" },
{ label: "White", value: "white" },
{ label: "Black", value: "black" },
{ label: "Orange", value: "orange" },
];
const AlignStyle = [
{
label: "Left",
value: "left",
},
{ label: "Center", value: "center" },
{ label: "Right", value: "right" },
];
function changeConfig(param: { [key: string]: unknown }) {
const { selectedGrid } = dashboardStore;
const graph = {
...selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...selectedGrid, graph });
}
function applyConfig() {
dashboardStore.setConfigPanel(false);
dashboardStore.setConfigs(dashboardStore.selectedGrid);
}
import { useI18n } from "vue-i18n";
import { ref } from "vue";
import { useDashboardStore } from "@/store/modules/dashboard";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const originConfig = dashboardStore.selectedGrid;
const graph = originConfig.graph || {};
const url = ref(graph.url || "");
const backgroundColor = ref(graph.backgroundColor || "green");
const fontColor = ref(graph.fontColor || "white");
const content = ref<string>(graph.content || "");
const fontSize = ref<number>(graph.fontSize || 12);
const textAlign = ref(graph.textAlign || "left");
const Colors = [
{
label: "Green",
value: "green",
},
{ label: "Blue", value: "blue" },
{ label: "Red", value: "red" },
{ label: "Grey", value: "grey" },
{ label: "White", value: "white" },
{ label: "Black", value: "black" },
{ label: "Orange", value: "orange" },
];
const AlignStyle = [
{
label: "Left",
value: "left",
},
{ label: "Center", value: "center" },
{ label: "Right", value: "right" },
];
function changeConfig(param: { [key: string]: unknown }) {
const { selectedGrid } = dashboardStore;
const graph = {
...selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...selectedGrid, graph });
}
function applyConfig() {
dashboardStore.setConfigPanel(false);
dashboardStore.setConfigs(dashboardStore.selectedGrid);
}
function cancelConfig() {
dashboardStore.selectWidget(originConfig);
dashboardStore.setConfigPanel(false);
}
function cancelConfig() {
dashboardStore.selectWidget(originConfig);
dashboardStore.setConfigPanel(false);
}
</script>
<style lang="scss" scoped>
.slider {
width: 500px;
margin-top: -3px;
}
.slider {
width: 500px;
margin-top: -3px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.input {
width: 500px;
}
.input {
width: 500px;
}
.item {
margin-bottom: 10px;
}
.item {
margin-bottom: 10px;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid #eee;
padding: 10px;
text-align: right;
width: 100%;
background-color: #fff;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid #eee;
padding: 10px;
text-align: right;
width: 100%;
background-color: #fff;
}
</style>

View File

@@ -13,12 +13,7 @@ limitations under the License. -->
<template>
<div class="item">
<span class="label">{{ t("text") }}</span>
<el-input
class="input"
v-model="text"
size="small"
@change="changeConfig({ text })"
/>
<el-input class="input" v-model="text" size="small" @change="changeConfig({ text })" />
</div>
<div class="item">
<span class="label">{{ t("textAlign") }}</span>
@@ -76,86 +71,86 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { ref } from "vue";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useI18n } from "vue-i18n";
import { ref } from "vue";
import { useDashboardStore } from "@/store/modules/dashboard";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const originConfig = dashboardStore.selectedGrid;
const graph = originConfig.graph || {};
const backgroundColor = ref(graph.backgroundColor || "green");
const fontColor = ref(graph.fontColor || "white");
const fontSize = ref<number>(graph.fontSize || 12);
const textAlign = ref(graph.textAlign || "left");
const text = ref<string>(graph.text || "");
const Colors = [
{
label: "Green",
value: "green",
},
{ label: "Blue", value: "blue" },
{ label: "Red", value: "red" },
{ label: "Grey", value: "grey" },
{ label: "White", value: "white" },
{ label: "Black", value: "black" },
{ label: "Orange", value: "orange" },
];
const AlignStyle = [
{
label: "Left",
value: "left",
},
{ label: "Center", value: "center" },
{ label: "Right", value: "right" },
];
function changeConfig(param: { [key: string]: unknown }) {
const { selectedGrid } = dashboardStore;
const graph = {
...selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...selectedGrid, graph });
}
function applyConfig() {
dashboardStore.setConfigPanel(false);
dashboardStore.setConfigs(dashboardStore.selectedGrid);
}
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const originConfig = dashboardStore.selectedGrid;
const graph = originConfig.graph || {};
const backgroundColor = ref(graph.backgroundColor || "green");
const fontColor = ref(graph.fontColor || "white");
const fontSize = ref<number>(graph.fontSize || 12);
const textAlign = ref(graph.textAlign || "left");
const text = ref<string>(graph.text || "");
const Colors = [
{
label: "Green",
value: "green",
},
{ label: "Blue", value: "blue" },
{ label: "Red", value: "red" },
{ label: "Grey", value: "grey" },
{ label: "White", value: "white" },
{ label: "Black", value: "black" },
{ label: "Orange", value: "orange" },
];
const AlignStyle = [
{
label: "Left",
value: "left",
},
{ label: "Center", value: "center" },
{ label: "Right", value: "right" },
];
function changeConfig(param: { [key: string]: unknown }) {
const { selectedGrid } = dashboardStore;
const graph = {
...selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...selectedGrid, graph });
}
function applyConfig() {
dashboardStore.setConfigPanel(false);
dashboardStore.setConfigs(dashboardStore.selectedGrid);
}
function cancelConfig() {
dashboardStore.selectWidget(originConfig);
dashboardStore.setConfigPanel(false);
}
function cancelConfig() {
dashboardStore.selectWidget(originConfig);
dashboardStore.setConfigPanel(false);
}
</script>
<style lang="scss" scoped>
.slider {
width: 500px;
margin-top: -3px;
}
.slider {
width: 500px;
margin-top: -3px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.input {
width: 500px;
}
.input {
width: 500px;
}
.item {
margin-bottom: 10px;
}
.item {
margin-bottom: 10px;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid #eee;
padding: 10px;
text-align: right;
width: 100%;
background-color: #fff;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid #eee;
padding: 10px;
text-align: right;
width: 100%;
background-color: #fff;
}
</style>

View File

@@ -13,22 +13,11 @@ limitations under the License. -->
<template>
<div class="item">
<span class="label">{{ t("showDepth") }}</span>
<el-switch
v-model="showDepth"
active-text="Yes"
inactive-text="No"
@change="changeConfig({ showDepth })"
/>
<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>
<Selector
class="input"
size="small"
:value="depth"
:options="DepthList"
@change="changeDepth($event)"
/>
<Selector class="input" size="small" :value="depth" :options="DepthList" @change="changeDepth($event)" />
</div>
<div class="footer">
<el-button size="small">
@@ -40,55 +29,55 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { DepthList } from "../data";
import { Option } from "@/types/app";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { DepthList } from "../data";
import type { Option } from "@/types/app";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = dashboardStore.selectedGrid.graph || {};
const showDepth = ref<boolean>(graph.showDepth);
const depth = ref<number>(graph.depth || 2);
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = dashboardStore.selectedGrid.graph || {};
const showDepth = ref<boolean>(graph.showDepth);
const depth = ref<number>(graph.depth || 2);
function applyConfig() {
dashboardStore.setConfigs(dashboardStore.selectedGrid);
dashboardStore.setConfigPanel(false);
}
function changeConfig(param: { [key: string]: unknown }) {
const { selectedGrid } = dashboardStore;
const graph = {
...selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...selectedGrid, graph });
}
function changeDepth(opt: Option[] | any) {
const val = opt[0].value;
changeConfig({ depth: val });
}
function applyConfig() {
dashboardStore.setConfigs(dashboardStore.selectedGrid);
dashboardStore.setConfigPanel(false);
}
function changeConfig(param: { [key: string]: unknown }) {
const { selectedGrid } = dashboardStore;
const graph = {
...selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...selectedGrid, graph });
}
function changeDepth(opt: Option[] | any) {
const val = opt[0].value;
changeConfig({ depth: val });
}
</script>
<style lang="scss" scoped>
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid #eee;
padding: 10px;
text-align: right;
width: 100%;
background-color: #fff;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid #eee;
padding: 10px;
text-align: right;
width: 100%;
background-color: #fff;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.item {
margin: 10px 0;
}
.item {
margin: 10px 0;
}
</style>

View File

@@ -47,10 +47,7 @@ limitations under the License. -->
</div>
</div>
<div class="collapse" :style="{ height: configHeight + 'px' }">
<el-collapse
v-model="states.activeNames"
:style="{ '--el-collapse-header-font-size': '15px' }"
>
<el-collapse v-model="states.activeNames" :style="{ '--el-collapse-header-font-size': '15px' }">
<el-collapse-item :title="t('selectVisualization')" name="1">
<MetricOptions @update="getSource" @loading="setLoading" />
</el-collapse-item>
@@ -60,18 +57,10 @@ limitations under the License. -->
<el-collapse-item :title="t('widgetOptions')" name="3">
<WidgetOptions />
</el-collapse-item>
<el-collapse-item
:title="t('associateOptions')"
name="4"
v-if="hasAssociate"
>
<el-collapse-item :title="t('associateOptions')" name="4" v-if="hasAssociate">
<AssociateOptions />
</el-collapse-item>
<el-collapse-item
:title="t('relatedTraceOptions')"
name="5"
v-if="hasAssociate"
>
<el-collapse-item :title="t('relatedTraceOptions')" name="5" v-if="hasAssociate">
<RelatedTraceOptions />
</el-collapse-item>
</el-collapse>
@@ -87,164 +76,163 @@ limitations under the License. -->
</div>
</template>
<script lang="ts">
import { reactive, defineComponent, ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { Option } from "@/types/app";
import graphs from "../graphs";
import CustomOptions from "./widget/index";
import { reactive, defineComponent, ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import type { Option } from "@/types/app";
import graphs from "../graphs";
import CustomOptions from "./widget/index";
export default defineComponent({
name: "WidgetEdit",
components: {
...graphs,
...CustomOptions,
},
setup() {
const configHeight = document.documentElement.clientHeight - 540;
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const appStoreWithOut = useAppStoreWithOut();
const loading = ref<boolean>(false);
const states = reactive<{
activeNames: string;
source: unknown;
index: string;
visType: Option[];
}>({
activeNames: "1",
source: {},
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 || ""));
const tips = computed(() => encodeURIComponent(widget.value.tips || ""));
const hasAssociate = computed(() =>
["Bar", "Line", "Area", "TopList"].includes(
dashboardStore.selectedGrid.graph &&
dashboardStore.selectedGrid.graph.type
)
);
export default defineComponent({
name: "WidgetEdit",
components: {
...graphs,
...CustomOptions,
},
setup() {
const configHeight = document.documentElement.clientHeight - 540;
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const appStoreWithOut = useAppStoreWithOut();
const loading = ref<boolean>(false);
const states = reactive<{
activeNames: string;
source: unknown;
index: string;
visType: Option[];
}>({
activeNames: "1",
source: {},
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 || ""));
const tips = computed(() => encodeURIComponent(widget.value.tips || ""));
const hasAssociate = computed(() =>
["Bar", "Line", "Area", "TopList"].includes(
dashboardStore.selectedGrid.graph && dashboardStore.selectedGrid.graph.type,
),
);
function getSource(source: unknown) {
states.source = source;
}
function getSource(source: unknown) {
states.source = source;
}
function setLoading(load: boolean) {
loading.value = load;
}
function setLoading(load: boolean) {
loading.value = load;
}
function applyConfig() {
dashboardStore.setConfigPanel(false);
dashboardStore.setConfigs(dashboardStore.selectedGrid);
}
function applyConfig() {
dashboardStore.setConfigPanel(false);
dashboardStore.setConfigs(dashboardStore.selectedGrid);
}
function cancelConfig() {
dashboardStore.selectWidget(originConfig);
dashboardStore.setConfigPanel(false);
}
function cancelConfig() {
dashboardStore.selectWidget(originConfig);
dashboardStore.setConfigPanel(false);
}
return {
states,
loading,
t,
appStoreWithOut,
configHeight,
dashboardStore,
applyConfig,
cancelConfig,
getSource,
setLoading,
widget,
graph,
title,
tips,
hasAssociate,
};
},
});
return {
states,
loading,
t,
appStoreWithOut,
configHeight,
dashboardStore,
applyConfig,
cancelConfig,
getSource,
setLoading,
widget,
graph,
title,
tips,
hasAssociate,
};
},
});
</script>
<style lang="scss" scoped>
.widget-config {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
.widget-config {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
.graph {
position: relative;
min-width: 1280px;
border: 1px solid #eee;
background-color: #fff;
}
.graph {
position: relative;
min-width: 1280px;
border: 1px solid #eee;
background-color: #fff;
}
.header {
height: 25px;
line-height: 25px;
text-align: center;
background-color: aliceblue;
font-size: 12px;
position: relative;
}
.header {
height: 25px;
line-height: 25px;
text-align: center;
background-color: aliceblue;
font-size: 12px;
position: relative;
}
.tips {
position: absolute;
right: 5px;
top: 0;
}
.tips {
position: absolute;
right: 5px;
top: 0;
}
.render-chart {
padding: 5px;
height: 400px;
width: 100%;
}
.render-chart {
padding: 5px;
height: 400px;
width: 100%;
}
.selectors {
width: 500px;
margin-right: 10px;
}
.selectors {
width: 500px;
margin-right: 10px;
}
.el-collapse-item__header {
font-weight: bold;
}
.el-collapse-item__header {
font-weight: bold;
}
.config {
min-width: 1280px;
}
.config {
min-width: 1280px;
}
.no-data {
font-size: 14px;
text-align: center;
line-height: 400px;
}
.no-data {
font-size: 14px;
text-align: center;
line-height: 400px;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid #eee;
padding: 10px;
text-align: right;
width: 100%;
background-color: #fff;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid #eee;
padding: 10px;
text-align: right;
width: 100%;
background-color: #fff;
}
.collapse {
margin-top: 10px;
overflow: auto;
}
.collapse {
margin-top: 10px;
overflow: auto;
}
.ds-name {
margin-bottom: 10px;
}
.ds-name {
margin-bottom: 10px;
}
.unit {
display: inline-block;
margin-left: 5px;
}
.unit {
display: inline-block;
margin-left: 5px;
}
</style>

View File

@@ -28,86 +28,82 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import getDashboard from "@/hooks/useDashboardsSession";
import { Option } from "@/types/app";
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import getDashboard from "@/hooks/useDashboardsSession";
import type { Option } from "@/types/app";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const associate = dashboardStore.selectedGrid.associate || [];
const widgetIds = ref<string[]>(
associate.map((d: { widgetId: string }) => d.widgetId)
);
const widgets: any = computed(() => {
const widgetList = getDashboard(dashboardStore.currentDashboard).widgets;
const items = [];
for (const d of widgetList) {
const isLinear = ["Bar", "Line", "Area"].includes(
(d.graph && d.graph.type) || ""
);
if (isLinear && d.id && dashboardStore.selectedGrid.id !== d.id) {
items.push({ value: d.id, label: (d.widget && d.widget.name) || d.id });
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const associate = dashboardStore.selectedGrid.associate || [];
const widgetIds = ref<string[]>(associate.map((d: { widgetId: string }) => d.widgetId));
const widgets: any = computed(() => {
const widgetList = getDashboard(dashboardStore.currentDashboard).widgets;
const items = [];
for (const d of widgetList) {
const isLinear = ["Bar", "Line", "Area"].includes((d.graph && d.graph.type) || "");
if (isLinear && d.id && dashboardStore.selectedGrid.id !== d.id) {
items.push({ value: d.id, label: (d.widget && d.widget.name) || d.id });
}
}
}
return items;
});
function updateWidgetConfig(options: Option[]) {
const arr: any = getDashboard(dashboardStore.currentDashboard).widgets;
const opt = options.map((d: Option) => {
return { widgetId: d.value };
return items;
});
const newVal = options.map((d: Option) => d.value);
// add association options in the source widget
const widget = {
...dashboardStore.selectedGrid,
associate: opt,
};
dashboardStore.selectWidget({ ...widget });
function updateWidgetConfig(options: Option[]) {
const arr: any = getDashboard(dashboardStore.currentDashboard).widgets;
const opt = options.map((d: Option) => {
return { widgetId: d.value };
});
const newVal = options.map((d: Option) => d.value);
// add association options in the source widget
const widget = {
...dashboardStore.selectedGrid,
associate: opt,
};
dashboardStore.selectWidget({ ...widget });
// remove unuse association widget option
for (const id of widgetIds.value) {
if (!newVal.includes(id)) {
const w = arr.find((d: { id: string }) => d.id === id);
// remove unuse association widget option
for (const id of widgetIds.value) {
if (!newVal.includes(id)) {
const w = arr.find((d: { id: string }) => d.id === id);
const config = {
...w,
associate: [],
};
dashboardStore.setWidget(config);
}
}
// add association options in target widgets
for (let i = 0; i < opt.length; i++) {
const item = JSON.parse(JSON.stringify(opt));
item[i] = { widgetId: dashboardStore.selectedGrid.id };
const w = arr.find((d: { id: string }) => d.id === opt[i].widgetId);
const config = {
...w,
associate: [],
associate: item,
};
dashboardStore.setWidget(config);
}
widgetIds.value = newVal;
}
// add association options in target widgets
for (let i = 0; i < opt.length; i++) {
const item = JSON.parse(JSON.stringify(opt));
item[i] = { widgetId: dashboardStore.selectedGrid.id };
const w = arr.find((d: { id: string }) => d.id === opt[i].widgetId);
const config = {
...w,
associate: item,
};
dashboardStore.setWidget(config);
}
widgetIds.value = newVal;
}
</script>
<style lang="scss" scoped>
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.input {
width: 500px;
}
.input {
width: 500px;
}
.item {
margin-bottom: 10px;
}
.item {
margin-bottom: 10px;
}
.selectors {
width: 500px;
}
.selectors {
width: 500px;
}
</style>

View File

@@ -26,12 +26,7 @@ limitations under the License. -->
</div>
<div v-else class="item">
<span class="label">{{ t("enableRelatedTrace") }}</span>
<el-switch
v-model="enableRelate"
active-text="Yes"
inactive-text="No"
@change="updateConfig({ enableRelate })"
/>
<el-switch v-model="enableRelate" active-text="Yes" inactive-text="No" @change="updateConfig({ enableRelate })" />
</div>
<div v-if="enableRelate">
<div class="item">
@@ -58,62 +53,57 @@ limitations under the License. -->
</div>
<div class="item">
<span class="label">{{ t("setLatencyDuration") }}</span>
<el-switch
v-model="latency"
active-text="Yes"
inactive-text="No"
@change="updateConfig({ latency })"
/>
<el-switch v-model="latency" active-text="Yes" inactive-text="No" @change="updateConfig({ latency })" />
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { RefIdTypes } from "../../data";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { RefIdTypes } from "../../data";
const QueryOrders = [
{ label: "None", value: "BY_START_TIME" },
{ label: "Duration", value: "BY_DURATION" },
];
const Status = [
{ label: "None", value: "ALL" },
{ label: "Success", value: "SUCCESS" },
{ label: "Error", value: "ERROR" },
];
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const { graph, relatedTrace } = dashboardStore.selectedGrid;
const traceOpt = relatedTrace || {};
const status = ref<string>(traceOpt.status || Status[0].value);
const queryOrder = ref<string>(traceOpt.queryOrder || QueryOrders[0].value);
const latency = ref<boolean>(traceOpt.latency || false);
const enableRelate = ref<boolean>(traceOpt.enableRelate || false);
const type = ref<string>((graph && graph.type) || "");
const refIdType = ref<string>(traceOpt.refIdType || "traceId");
const QueryOrders = [
{ label: "None", value: "BY_START_TIME" },
{ label: "Duration", value: "BY_DURATION" },
];
const Status = [
{ label: "None", value: "ALL" },
{ label: "Success", value: "SUCCESS" },
{ label: "Error", value: "ERROR" },
];
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const { graph, relatedTrace } = dashboardStore.selectedGrid;
const traceOpt = relatedTrace || {};
const status = ref<string>(traceOpt.status || Status[0].value);
const queryOrder = ref<string>(traceOpt.queryOrder || QueryOrders[0].value);
const latency = ref<boolean>(traceOpt.latency || false);
const enableRelate = ref<boolean>(traceOpt.enableRelate || false);
const type = ref<string>((graph && graph.type) || "");
const refIdType = ref<string>(traceOpt.refIdType || "traceId");
function updateConfig(param: { [key: string]: unknown }) {
const relatedTrace = {
...dashboardStore.selectedGrid.relatedTrace,
...param,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, relatedTrace });
}
function updateConfig(param: { [key: string]: unknown }) {
const relatedTrace = {
...dashboardStore.selectedGrid.relatedTrace,
...param,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, relatedTrace });
}
</script>
<style lang="scss" scoped>
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.item {
margin-bottom: 10px;
}
.item {
margin-bottom: 10px;
}
.selector {
width: 500px;
}
.selector {
width: 500px;
}
</style>

View File

@@ -45,64 +45,62 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
import { useDashboardStore } from "@/store/modules/dashboard";
import getDashboard from "@/hooks/useDashboardsSession";
import { LayoutConfig } from "@/types/dashboard";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
import { useDashboardStore } from "@/store/modules/dashboard";
import getDashboard from "@/hooks/useDashboardsSession";
import type { LayoutConfig } from "@/types/dashboard";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const widget = dashboardStore.selectedGrid.widget || {};
const title = ref<string>(widget.title || "");
const tips = ref<string>(widget.tips || "");
const name = ref<string>(widget.name || "");
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const widget = dashboardStore.selectedGrid.widget || {};
const title = ref<string>(widget.title || "");
const tips = ref<string>(widget.tips || "");
const name = ref<string>(widget.name || "");
function updateWidgetConfig(param: { [key: string]: string }) {
const key = Object.keys(param)[0];
if (!key) {
return;
function updateWidgetConfig(param: { [key: string]: string }) {
const key = Object.keys(param)[0];
if (!key) {
return;
}
const { selectedGrid } = dashboardStore;
const widget = {
...dashboardStore.selectedGrid.widget,
[key]: decodeURIComponent(param[key]),
};
dashboardStore.selectWidget({ ...selectedGrid, widget });
}
const { selectedGrid } = dashboardStore;
const widget = {
...dashboardStore.selectedGrid.widget,
[key]: decodeURIComponent(param[key]),
};
dashboardStore.selectWidget({ ...selectedGrid, widget });
}
function updateWidgetName(param: { [key: string]: string }) {
const key = Object.keys(param)[0];
const n = decodeURIComponent(param[key]);
const pattern = /^[A-Za-z0-9-_\u4e00-\u9fa5]{1,300}$/;
if (!pattern.test(n)) {
ElMessage.error(t("nameTip"));
return;
function updateWidgetName(param: { [key: string]: string }) {
const key = Object.keys(param)[0];
const n = decodeURIComponent(param[key]);
const pattern = /^[A-Za-z0-9-_\u4e00-\u9fa5]{1,300}$/;
if (!pattern.test(n)) {
ElMessage.error(t("nameTip"));
return;
}
const { widgets } = getDashboard(dashboardStore.currentDashboard);
const item = widgets.find((d: LayoutConfig) => d.widget && d.widget.name === n);
if (item) {
ElMessage.error(t("duplicateName"));
return;
}
updateWidgetConfig(param);
}
const { widgets } = getDashboard(dashboardStore.currentDashboard);
const item = widgets.find(
(d: LayoutConfig) => d.widget && d.widget.name === n
);
if (item) {
ElMessage.error(t("duplicateName"));
return;
}
updateWidgetConfig(param);
}
</script>
<style lang="scss" scoped>
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.input {
width: 500px;
}
.input {
width: 500px;
}
.item {
margin-bottom: 10px;
}
.item {
margin-bottom: 10px;
}
</style>

View File

@@ -29,35 +29,35 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Legend from "./components/Legend.vue";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Legend from "./components/Legend.vue";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = dashboardStore.selectedGrid.graph || {};
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = dashboardStore.selectedGrid.graph || {};
const opacity = ref(graph.opacity);
const opacity = ref(graph.opacity);
function updateConfig(param: { [key: string]: unknown }) {
const graph = {
...dashboardStore.selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, graph });
}
function updateConfig(param: { [key: string]: unknown }) {
const graph = {
...dashboardStore.selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, graph });
}
</script>
<style lang="scss" scoped>
.bar-width {
width: 500px;
margin-top: -13px;
}
.bar-width {
width: 500px;
margin-top: -13px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
</style>

View File

@@ -25,34 +25,34 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useI18n } from "vue-i18n";
import Legend from "./components/Legend.vue";
import { ref } from "vue";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useI18n } from "vue-i18n";
import Legend from "./components/Legend.vue";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = dashboardStore.selectedGrid.graph;
const showBackground = ref(graph.showBackground || false);
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = dashboardStore.selectedGrid.graph;
const showBackground = ref(graph.showBackground || false);
function changeConfig(param: { [key: string]: unknown }) {
const graph = {
...dashboardStore.selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, graph });
}
function changeConfig(param: { [key: string]: unknown }) {
const graph = {
...dashboardStore.selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, graph });
}
</script>
<style lang="scss" scoped>
.bar-width {
width: 500px;
margin-top: -13px;
}
.bar-width {
width: 500px;
margin-top: -13px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
</style>

View File

@@ -28,43 +28,38 @@ limitations under the License. -->
</div>
<div class="item">
<span class="label">{{ t("showUnit") }}</span>
<el-switch
v-model="showUnit"
active-text="Yes"
inactive-text="No"
@change="updateConfig({ showUnit })"
/>
<el-switch v-model="showUnit" active-text="Yes" inactive-text="No" @change="updateConfig({ showUnit })" />
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = dashboardStore.selectedGrid.graph || {};
const fontSize = ref(graph.fontSize);
const showUnit = ref<boolean>(graph.showUnit);
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = dashboardStore.selectedGrid.graph || {};
const fontSize = ref(graph.fontSize);
const showUnit = ref<boolean>(graph.showUnit);
function updateConfig(param: { [key: string]: unknown }) {
const graph = {
...dashboardStore.selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, graph });
}
function updateConfig(param: { [key: string]: unknown }) {
const graph = {
...dashboardStore.selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, graph });
}
</script>
<style lang="scss" scoped>
.slider {
width: 500px;
margin-top: -13px;
}
.slider {
width: 500px;
margin-top: -13px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
</style>

View File

@@ -28,34 +28,34 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
const dashboardStore = useDashboardStore();
const graph = dashboardStore.selectedGrid.graph;
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = dashboardStore.selectedGrid.graph;
const { t } = useI18n();
const fontSize = ref(graph.fontSize);
const fontSize = ref(graph.fontSize);
function updateConfig(param: { [key: string]: unknown }) {
const graph = {
...dashboardStore.selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, graph });
}
function updateConfig(param: { [key: string]: unknown }) {
const graph = {
...dashboardStore.selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, graph });
}
</script>
<style lang="scss" scoped>
.slider {
width: 500px;
margin-top: -13px;
}
.slider {
width: 500px;
margin-top: -13px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
</style>

View File

@@ -28,33 +28,33 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = dashboardStore.selectedGrid.graph || {};
const fontSize = ref(graph.fontSize);
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = dashboardStore.selectedGrid.graph || {};
const fontSize = ref(graph.fontSize);
function updateConfig(param: { [key: string]: unknown }) {
const graph = {
...dashboardStore.selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, graph });
}
function updateConfig(param: { [key: string]: unknown }) {
const graph = {
...dashboardStore.selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, graph });
}
</script>
<style lang="scss" scoped>
.slider {
width: 500px;
margin-top: -13px;
}
.slider {
width: 500px;
margin-top: -13px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
</style>

View File

@@ -28,33 +28,33 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = dashboardStore.selectedGrid.graph;
const fontSize = ref(graph.fontSize);
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = dashboardStore.selectedGrid.graph;
const fontSize = ref(graph.fontSize);
function updateConfig(param: { [key: string]: unknown }) {
const graph = {
...dashboardStore.selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, graph });
}
function updateConfig(param: { [key: string]: unknown }) {
const graph = {
...dashboardStore.selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, graph });
}
</script>
<style lang="scss" scoped>
.slider {
width: 500px;
margin-top: -13px;
}
.slider {
width: 500px;
margin-top: -13px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
</style>

View File

@@ -34,58 +34,43 @@ limitations under the License. -->
</div>
<div>
<span class="label">{{ t("smooth") }}</span>
<el-switch
v-model="smooth"
active-text="Yes"
inactive-text="No"
@change="updateConfig({ smooth })"
/>
<el-switch v-model="smooth" active-text="Yes" inactive-text="No" @change="updateConfig({ smooth })" />
</div>
<div>
<span class="label">{{ t("showSymbol") }}</span>
<el-switch
v-model="showSymbol"
active-text="Yes"
inactive-text="No"
@change="updateConfig({ showSymbol })"
/>
<el-switch v-model="showSymbol" active-text="Yes" inactive-text="No" @change="updateConfig({ showSymbol })" />
</div>
<div>
<span class="label">{{ t("step") }}</span>
<el-switch
v-model="step"
active-text="Yes"
inactive-text="No"
@change="updateConfig({ step })"
/>
<el-switch v-model="step" active-text="Yes" inactive-text="No" @change="updateConfig({ step })" />
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Legend from "./components/Legend.vue";
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Legend from "./components/Legend.vue";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = computed(() => dashboardStore.selectedGrid.graph || {});
const smooth = ref(graph.value.smooth);
const showSymbol = ref(graph.value.showSymbol);
const step = ref(graph.value.step);
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = computed(() => dashboardStore.selectedGrid.graph || {});
const smooth = ref(graph.value.smooth);
const showSymbol = ref(graph.value.showSymbol);
const step = ref(graph.value.step);
function updateConfig(param: { [key: string]: unknown }) {
const graph = {
...dashboardStore.selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, graph });
}
function updateConfig(param: { [key: string]: unknown }) {
const graph = {
...dashboardStore.selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, graph });
}
</script>
<style lang="scss" scoped>
.label {
font-size: 13px;
display: block;
margin-top: 5px;
margin-bottom: -5px;
}
.label {
font-size: 13px;
display: block;
margin-top: 5px;
margin-bottom: -5px;
}
</style>

View File

@@ -37,38 +37,38 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = computed(() => dashboardStore.selectedGrid.graph || {});
const fontSize = ref(graph.value.fontSize);
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = computed(() => dashboardStore.selectedGrid.graph || {});
const fontSize = ref(graph.value.fontSize);
function updateConfig(param: { [key: string]: unknown }) {
const { selectedGrid } = dashboardStore;
const graph = {
...selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...selectedGrid, graph });
}
function updateConfig(param: { [key: string]: unknown }) {
const { selectedGrid } = dashboardStore;
const graph = {
...selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...selectedGrid, graph });
}
</script>
<style lang="scss" scoped>
.slider {
width: 500px;
margin-top: -13px;
}
.slider {
width: 500px;
margin-top: -13px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.item {
margin-top: 5px;
}
.item {
margin-top: 5px;
}
</style>

View File

@@ -44,39 +44,39 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = dashboardStore.selectedGrid.graph || {};
const showTableValues = ref(graph.showTableValues);
const tableHeaderCol1 = ref(graph.tableHeaderCol1);
const tableHeaderCol2 = ref(graph.tableHeaderCol2);
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = dashboardStore.selectedGrid.graph || {};
const showTableValues = ref(graph.showTableValues);
const tableHeaderCol1 = ref(graph.tableHeaderCol1);
const tableHeaderCol2 = ref(graph.tableHeaderCol2);
function updateConfig(param: { [key: string]: unknown }) {
const graph = {
...dashboardStore.selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, graph });
}
function updateConfig(param: { [key: string]: unknown }) {
const graph = {
...dashboardStore.selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, graph });
}
</script>
<style lang="scss" scoped>
.slider {
width: 500px;
margin-top: -13px;
}
.slider {
width: 500px;
margin-top: -13px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.item {
margin-top: 10px;
}
.item {
margin-top: 10px;
}
</style>

View File

@@ -26,42 +26,42 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = dashboardStore.selectedGrid.graph || {};
const color = ref(graph.color || "purple");
const Colors = [
{ label: "Purple", value: "purple" },
{
label: "Green",
value: "green",
},
{ label: "Blue", value: "blue" },
{ label: "Red", value: "red" },
{ label: "Orange", value: "orange" },
];
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = dashboardStore.selectedGrid.graph || {};
const color = ref(graph.color || "purple");
const Colors = [
{ label: "Purple", value: "purple" },
{
label: "Green",
value: "green",
},
{ label: "Blue", value: "blue" },
{ label: "Red", value: "red" },
{ label: "Orange", value: "orange" },
];
function updateConfig(param: { [key: string]: unknown }) {
const graph = {
...dashboardStore.selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, graph });
}
function updateConfig(param: { [key: string]: unknown }) {
const graph = {
...dashboardStore.selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, graph });
}
</script>
<style lang="scss" scoped>
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.input {
width: 500px;
}
.input {
width: 500px;
}
</style>

View File

@@ -82,57 +82,57 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { computed, reactive } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { LegendOptions } from "@/types/dashboard";
import { computed, reactive } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import type { LegendOptions } from "@/types/dashboard";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = computed(() => dashboardStore.selectedGrid.graph || {});
const legend = reactive<LegendOptions>({
show: true,
total: false,
min: false,
max: false,
mean: false,
asTable: false,
toTheRight: false,
width: 130,
...graph.value.legend,
});
function updateLegendConfig(param: { [key: string]: unknown }) {
const g = {
...dashboardStore.selectedGrid.graph,
legend: {
...dashboardStore.selectedGrid.graph.legend,
...param,
},
};
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
graph: g,
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = computed(() => dashboardStore.selectedGrid.graph || {});
const legend = reactive<LegendOptions>({
show: true,
total: false,
min: false,
max: false,
mean: false,
asTable: false,
toTheRight: false,
width: 130,
...graph.value.legend,
});
}
function updateLegendConfig(param: { [key: string]: unknown }) {
const g = {
...dashboardStore.selectedGrid.graph,
legend: {
...dashboardStore.selectedGrid.graph.legend,
...param,
},
};
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
graph: g,
});
}
</script>
<style lang="scss" scoped>
.label {
font-size: 13px;
display: block;
margin-top: 5px;
margin-bottom: -5px;
}
.label {
font-size: 13px;
display: block;
margin-top: 5px;
margin-bottom: -5px;
}
.title {
font-size: 12px;
display: inline-flex;
height: 32px;
line-height: 34px;
vertical-align: middle;
}
.title {
font-size: 12px;
display: inline-flex;
height: 32px;
line-height: 34px;
vertical-align: middle;
}
.inputs {
width: 120px;
}
.inputs {
width: 120px;
}
</style>

View File

@@ -26,11 +26,7 @@ limitations under the License. -->
/>
</div>
<div>{{ t("metrics") }}</div>
<div
v-for="(metric, index) in states.metrics"
:key="index"
class="metric-item"
>
<div v-for="(metric, index) in states.metrics" :key="index" class="metric-item">
<Selector
:value="metric"
:options="states.metricList"
@@ -53,34 +49,17 @@ limitations under the License. -->
<Icon class="cp mr-5" iconName="mode_edit" size="middle" />
</span>
</template>
<Standard
@update="queryMetrics"
:currentMetricConfig="currentMetricConfig"
:index="index"
/>
<Standard @update="queryMetrics" :currentMetricConfig="currentMetricConfig" :index="index" />
</el-popover>
<span
v-show="
states.isList ||
states.metricTypes[0] === ProtocolTypes.ReadMetricsValues
"
>
<span v-show="states.isList || states.metricTypes[0] === ProtocolTypes.ReadMetricsValues">
<Icon
class="cp mr-5"
v-if="
index === states.metrics.length - 1 &&
states.metrics.length < defaultLen
"
v-if="index === states.metrics.length - 1 && states.metrics.length < defaultLen"
iconName="add_circle_outlinecontrol_point"
size="middle"
@click="addMetric"
/>
<Icon
class="cp"
iconName="remove_circle_outline"
size="middle"
@click="deleteMetric(index)"
/>
<Icon class="cp" iconName="remove_circle_outline" size="middle" @click="deleteMetric(index)" />
</span>
</div>
<div>{{ t("visualization") }}</div>
@@ -98,164 +77,136 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, computed } from "vue";
import { Option } from "@/types/app";
import { useDashboardStore } from "@/store/modules/dashboard";
import {
MetricTypes,
ListChartTypes,
DefaultGraphConfig,
EntityType,
ChartTypes,
PodsChartTypes,
MetricsType,
ProtocolTypes,
} from "../../../data";
import { ElMessage } from "element-plus";
import Icon from "@/components/Icon.vue";
import {
useQueryProcessor,
useSourceProcessor,
useGetMetricEntity,
} from "@/hooks/useMetricsProcessor";
import { useI18n } from "vue-i18n";
import { DashboardItem, MetricConfigOpt } from "@/types/dashboard";
import Standard from "./Standard.vue";
import { reactive, ref, computed } from "vue";
import type { Option } from "@/types/app";
import { useDashboardStore } from "@/store/modules/dashboard";
import {
MetricTypes,
ListChartTypes,
DefaultGraphConfig,
EntityType,
ChartTypes,
PodsChartTypes,
MetricsType,
ProtocolTypes,
} from "../../../data";
import { ElMessage } from "element-plus";
import Icon from "@/components/Icon.vue";
import { useQueryProcessor, useSourceProcessor, useGetMetricEntity } from "@/hooks/useMetricsProcessor";
import { useI18n } from "vue-i18n";
import type { DashboardItem, MetricConfigOpt } from "@/types/dashboard";
import Standard from "./Standard.vue";
/*global defineEmits */
const { t } = useI18n();
const emit = defineEmits(["update", "loading"]);
const dashboardStore = useDashboardStore();
const metrics = computed(() => dashboardStore.selectedGrid.metrics || []);
const graph = computed(() => dashboardStore.selectedGrid.graph || {});
const metricTypes = computed(
() => dashboardStore.selectedGrid.metricTypes || []
);
const states = reactive<{
metrics: string[];
metricTypes: string[];
metricTypeList: Option[][];
isList: boolean;
metricList: (Option & { type: string })[];
dashboardName: string;
dashboardList: ((DashboardItem & { label: string; value: string }) | any)[];
}>({
metrics: metrics.value.length ? metrics.value : [""],
metricTypes: metricTypes.value.length ? metricTypes.value : [""],
metricTypeList: [],
isList: false,
metricList: [],
dashboardName: graph.value.dashboardName,
dashboardList: [{ label: "", value: "" }],
});
const currentMetricConfig = ref<MetricConfigOpt>({
unit: "",
label: "",
labelsIndex: "",
calculation: "",
sortOrder: "DES",
});
/*global defineEmits */
const { t } = useI18n();
const emit = defineEmits(["update", "loading"]);
const dashboardStore = useDashboardStore();
const metrics = computed(() => dashboardStore.selectedGrid.metrics || []);
const graph = computed(() => dashboardStore.selectedGrid.graph || {});
const metricTypes = computed(() => dashboardStore.selectedGrid.metricTypes || []);
const states = reactive<{
metrics: string[];
metricTypes: string[];
metricTypeList: Option[][];
isList: boolean;
metricList: (Option & { type: string })[];
dashboardName: string;
dashboardList: ((DashboardItem & { label: string; value: string }) | any)[];
}>({
metrics: metrics.value.length ? metrics.value : [""],
metricTypes: metricTypes.value.length ? metricTypes.value : [""],
metricTypeList: [],
isList: false,
metricList: [],
dashboardName: graph.value.dashboardName,
dashboardList: [{ label: "", value: "" }],
});
const currentMetricConfig = ref<MetricConfigOpt>({
unit: "",
label: "",
labelsIndex: "",
calculation: "",
sortOrder: "DES",
});
states.isList = ListChartTypes.includes(graph.value.type);
const defaultLen = ref<number>(states.isList ? 5 : 20);
setDashboards();
setMetricType();
states.isList = ListChartTypes.includes(graph.value.type);
const defaultLen = ref<number>(states.isList ? 5 : 20);
setDashboards();
setMetricType();
const setVisTypes = computed(() => {
let graphs = [];
if (dashboardStore.entity === EntityType[0].value) {
graphs = ChartTypes.filter(
(d: Option) =>
![ChartTypes[7].value, ChartTypes[8].value].includes(d.value)
);
} else if (dashboardStore.entity === EntityType[1].value) {
graphs = ChartTypes.filter(
(d: Option) => !PodsChartTypes.includes(d.value)
);
} else {
graphs = ChartTypes.filter(
(d: Option) => !ListChartTypes.includes(d.value)
);
}
return graphs;
});
async function setMetricType(chart?: any) {
const g = chart || dashboardStore.selectedGrid.graph || {};
let arr: any[] = states.metricList;
if (!chart) {
const json = await dashboardStore.fetchMetricList();
if (json.errors) {
ElMessage.error(json.errors);
return;
const setVisTypes = computed(() => {
let graphs = [];
if (dashboardStore.entity === EntityType[0].value) {
graphs = ChartTypes.filter((d: Option) => ![ChartTypes[7].value, ChartTypes[8].value].includes(d.value));
} else if (dashboardStore.entity === EntityType[1].value) {
graphs = ChartTypes.filter((d: Option) => !PodsChartTypes.includes(d.value));
} else {
graphs = ChartTypes.filter((d: Option) => !ListChartTypes.includes(d.value));
}
arr = json.data.metrics;
}
states.metricList = (arr || []).filter(
(d: { catalog: string; type: string }) => {
return graphs;
});
async function setMetricType(chart?: any) {
const g = chart || dashboardStore.selectedGrid.graph || {};
let arr: any[] = states.metricList;
if (!chart) {
const json = await dashboardStore.fetchMetricList();
if (json.errors) {
ElMessage.error(json.errors);
return;
}
arr = json.data.metrics;
}
states.metricList = (arr || []).filter((d: { catalog: string; type: string }) => {
if (states.isList) {
if (
d.type === MetricsType.REGULAR_VALUE ||
d.type === MetricsType.LABELED_VALUE
) {
if (d.type === MetricsType.REGULAR_VALUE || d.type === MetricsType.LABELED_VALUE) {
return d;
}
} else if (g.type === "Table") {
if (
d.type === MetricsType.LABELED_VALUE ||
d.type === MetricsType.REGULAR_VALUE
) {
if (d.type === MetricsType.LABELED_VALUE || d.type === MetricsType.REGULAR_VALUE) {
return d;
}
} else {
return d;
}
}
);
const metrics: any = states.metricList.filter(
(d: { value: string; type: string }) => states.metrics.includes(d.value)
);
});
const metrics: any = states.metricList.filter((d: { value: string; type: string }) =>
states.metrics.includes(d.value),
);
if (metrics.length) {
// keep states.metrics index
const m = metrics.map((d: { value: string }) => d.value);
states.metrics = states.metrics.filter((d) => m.includes(d));
} else {
states.metrics = [""];
states.metricTypes = [""];
}
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
metrics: states.metrics,
metricTypes: states.metricTypes,
graph: g,
});
states.metricTypeList = [];
for (const metric of metrics) {
if (states.metrics.includes(metric.value)) {
const arr = setMetricTypeList(metric.type);
states.metricTypeList.push(arr);
if (metrics.length) {
// keep states.metrics index
const m = metrics.map((d: { value: string }) => d.value);
states.metrics = states.metrics.filter((d) => m.includes(d));
} else {
states.metrics = [""];
states.metricTypes = [""];
}
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
metrics: states.metrics,
metricTypes: states.metricTypes,
graph: g,
});
states.metricTypeList = [];
for (const metric of metrics) {
if (states.metrics.includes(metric.value)) {
const arr = setMetricTypeList(metric.type);
states.metricTypeList.push(arr);
}
}
if (states.metrics && states.metrics[0]) {
queryMetrics();
} else {
emit("update", {});
}
}
if (states.metrics && states.metrics[0]) {
queryMetrics();
} else {
emit("update", {});
}
}
function setDashboards(type?: string) {
const chart =
type ||
(dashboardStore.selectedGrid.graph &&
dashboardStore.selectedGrid.graph.type);
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
const arr = list.reduce(
(
prev: (DashboardItem & { label: string; value: string })[],
d: DashboardItem
) => {
function setDashboards(type?: string) {
const chart = type || (dashboardStore.selectedGrid.graph && dashboardStore.selectedGrid.graph.type);
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
const arr = list.reduce((prev: (DashboardItem & { label: string; value: string })[], d: DashboardItem) => {
if (d.layer === dashboardStore.layerId) {
if (
(d.entity === EntityType[0].value && chart === "ServiceList") ||
@@ -270,221 +221,210 @@ function setDashboards(type?: string) {
}
}
return prev;
},
[]
);
}, []);
states.dashboardList = arr.length ? arr : [{ label: "", value: "" }];
}
function changeChartType(item: Option) {
const chart = DefaultGraphConfig[item.value] || {};
states.isList = ListChartTypes.includes(chart.type);
if (states.isList) {
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
metrics: [""],
metricTypes: [""],
});
states.metrics = [""];
states.metricTypes = [""];
defaultLen.value = 5;
}
setMetricType(chart);
setDashboards(chart.type);
states.dashboardName = "";
defaultLen.value = 10;
}
function changeMetrics(
index: number,
arr: (Option & { type: string })[] | any
) {
if (!arr.length) {
states.metricTypeList = [];
states.metricTypes = [];
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
...{ metricTypes: states.metricTypes, metrics: states.metrics },
});
return;
}
states.metrics[index] = arr[0].value;
const typeOfMetrics = arr[0].type;
states.metricTypeList[index] = setMetricTypeList(typeOfMetrics);
states.metricTypes[index] = MetricTypes[typeOfMetrics][0].value;
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
...{ metricTypes: states.metricTypes, metrics: states.metrics },
});
if (states.isList) {
return;
}
queryMetrics();
}
function changeMetricType(index: number, opt: Option[] | any) {
const metric =
states.metricList.filter(
(d: Option) => states.metrics[index] === d.value
)[0] || {};
const l = setMetricTypeList(metric.type);
if (states.isList) {
states.metricTypes[index] = opt[0].value;
states.metricTypeList[index] = l;
} else {
states.metricTypes = states.metricTypes.map((d: string) => {
d = opt[0].value;
return d;
});
states.metricTypeList = states.metricTypeList.map((d: Option[]) => {
d = l;
return d;
});
}
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
...{ metricTypes: states.metricTypes },
});
if (states.isList) {
return;
}
queryMetrics();
}
async function queryMetrics() {
if (states.isList) {
return;
}
const { metricConfig, metricTypes, metrics } = dashboardStore.selectedGrid;
if (!(metrics && metrics[0] && metricTypes && metricTypes[0])) {
return;
}
const catalog = await useGetMetricEntity(metrics[0], metricTypes[0]);
const params = useQueryProcessor({ ...states, metricConfig, catalog });
if (!params) {
emit("update", {});
return;
states.dashboardList = arr.length ? arr : [{ label: "", value: "" }];
}
emit("loading", true);
const json = await dashboardStore.fetchMetricValue(params);
emit("loading", false);
if (json.errors) {
ElMessage.error(json.errors);
return;
}
const source = useSourceProcessor(json, { ...states, metricConfig });
emit("update", source);
}
function changeDashboard(opt: any) {
if (!opt[0]) {
function changeChartType(item: Option) {
const chart = DefaultGraphConfig[item.value] || {};
states.isList = ListChartTypes.includes(chart.type);
if (states.isList) {
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
metrics: [""],
metricTypes: [""],
});
states.metrics = [""];
states.metricTypes = [""];
defaultLen.value = 5;
}
setMetricType(chart);
setDashboards(chart.type);
states.dashboardName = "";
} else {
states.dashboardName = opt[0].value;
defaultLen.value = 10;
}
const graph = {
...dashboardStore.selectedGrid.graph,
dashboardName: states.dashboardName,
};
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
graph,
});
}
function addMetric() {
states.metrics.push("");
if (!states.isList) {
states.metricTypes.push(states.metricTypes[0]);
states.metricTypeList.push(states.metricTypeList[0]);
return;
}
states.metricTypes.push("");
}
function deleteMetric(index: number) {
if (states.metrics.length === 1) {
states.metrics = [""];
states.metricTypes = [""];
function changeMetrics(index: number, arr: (Option & { type: string })[] | any) {
if (!arr.length) {
states.metricTypeList = [];
states.metricTypes = [];
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
...{ metricTypes: states.metricTypes, metrics: states.metrics },
});
return;
}
states.metrics[index] = arr[0].value;
const typeOfMetrics = arr[0].type;
states.metricTypeList[index] = setMetricTypeList(typeOfMetrics);
states.metricTypes[index] = MetricTypes[typeOfMetrics][0].value;
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
...{ metricTypes: states.metricTypes, metrics: states.metrics },
metricConfig: [],
});
return;
if (states.isList) {
return;
}
queryMetrics();
}
states.metrics.splice(index, 1);
states.metricTypes.splice(index, 1);
const config = dashboardStore.selectedGrid.metricConfig || [];
const metricConfig = config[index] ? config.splice(index, 1) : config;
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
...{ metricTypes: states.metricTypes, metrics: states.metrics },
metricConfig,
});
}
function setMetricTypeList(type: string) {
if (type !== MetricsType.REGULAR_VALUE) {
function changeMetricType(index: number, opt: Option[] | any) {
const metric = states.metricList.filter((d: Option) => states.metrics[index] === d.value)[0] || {};
const l = setMetricTypeList(metric.type);
if (states.isList) {
states.metricTypes[index] = opt[0].value;
states.metricTypeList[index] = l;
} else {
states.metricTypes = states.metricTypes.map((d: string) => {
d = opt[0].value;
return d;
});
states.metricTypeList = states.metricTypeList.map((d: Option[]) => {
d = l;
return d;
});
}
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
...{ metricTypes: states.metricTypes },
});
if (states.isList) {
return;
}
queryMetrics();
}
async function queryMetrics() {
if (states.isList) {
return;
}
const { metricConfig, metricTypes, metrics } = dashboardStore.selectedGrid;
if (!(metrics && metrics[0] && metricTypes && metricTypes[0])) {
return;
}
const catalog = await useGetMetricEntity(metrics[0], metricTypes[0]);
const params = useQueryProcessor({ ...states, metricConfig, catalog });
if (!params) {
emit("update", {});
return;
}
emit("loading", true);
const json = await dashboardStore.fetchMetricValue(params);
emit("loading", false);
if (json.errors) {
ElMessage.error(json.errors);
return;
}
const source = useSourceProcessor(json, { ...states, metricConfig });
emit("update", source);
}
function changeDashboard(opt: any) {
if (!opt[0]) {
states.dashboardName = "";
} else {
states.dashboardName = opt[0].value;
}
const graph = {
...dashboardStore.selectedGrid.graph,
dashboardName: states.dashboardName,
};
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
graph,
});
}
function addMetric() {
states.metrics.push("");
if (!states.isList) {
states.metricTypes.push(states.metricTypes[0]);
states.metricTypeList.push(states.metricTypeList[0]);
return;
}
states.metricTypes.push("");
}
function deleteMetric(index: number) {
if (states.metrics.length === 1) {
states.metrics = [""];
states.metricTypes = [""];
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
...{ metricTypes: states.metricTypes, metrics: states.metrics },
metricConfig: [],
});
return;
}
states.metrics.splice(index, 1);
states.metricTypes.splice(index, 1);
const config = dashboardStore.selectedGrid.metricConfig || [];
const metricConfig = config[index] ? config.splice(index, 1) : config;
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
...{ metricTypes: states.metricTypes, metrics: states.metrics },
metricConfig,
});
}
function setMetricTypeList(type: string) {
if (type !== MetricsType.REGULAR_VALUE) {
return MetricTypes[type];
}
if (states.isList || graph.value.type === "Table") {
return [MetricTypes.REGULAR_VALUE[0], MetricTypes.REGULAR_VALUE[1]];
}
return MetricTypes[type];
}
if (states.isList || graph.value.type === "Table") {
return [MetricTypes.REGULAR_VALUE[0], MetricTypes.REGULAR_VALUE[1]];
function setMetricConfig(index: number) {
const n = {
unit: "",
label: "",
calculation: "",
labelsIndex: "",
sortOrder: "DES",
};
if (!dashboardStore.selectedGrid.metricConfig || !dashboardStore.selectedGrid.metricConfig[index]) {
currentMetricConfig.value = n;
return;
}
currentMetricConfig.value = {
...n,
...dashboardStore.selectedGrid.metricConfig[index],
};
}
return MetricTypes[type];
}
function setMetricConfig(index: number) {
const n = {
unit: "",
label: "",
calculation: "",
labelsIndex: "",
sortOrder: "DES",
};
if (
!dashboardStore.selectedGrid.metricConfig ||
!dashboardStore.selectedGrid.metricConfig[index]
) {
currentMetricConfig.value = n;
return;
}
currentMetricConfig.value = {
...n,
...dashboardStore.selectedGrid.metricConfig[index],
};
}
</script>
<style lang="scss" scoped>
.ds-name {
margin-bottom: 10px;
}
.selectors {
width: 500px;
margin-right: 10px;
}
.metric-item {
margin-bottom: 10px;
}
.chart-types {
span {
display: inline-block;
padding: 2px 10px;
border: 1px solid #ccc;
background-color: #fff;
border-right: 0;
cursor: pointer;
.ds-name {
margin-bottom: 10px;
}
span:nth-last-child(1) {
border-right: 1px solid #ccc;
.selectors {
width: 500px;
margin-right: 10px;
}
}
span.active {
background-color: #409eff;
color: #fff;
}
.metric-item {
margin-bottom: 10px;
}
.chart-types {
span {
display: inline-block;
padding: 2px 10px;
border: 1px solid #ccc;
background-color: #fff;
border-right: 0;
cursor: pointer;
}
span:nth-last-child(1) {
border-right: 1px solid #ccc;
}
}
span.active {
background-color: #409eff;
color: #fff;
}
</style>

View File

@@ -91,99 +91,89 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref, watch, computed } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { SortOrder, CalculationOpts } from "../../../data";
import { useDashboardStore } from "@/store/modules/dashboard";
import { MetricConfigOpt } from "@/types/dashboard";
import { ListChartTypes, ProtocolTypes } from "../../../data";
import { ref, watch, computed } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { SortOrder, CalculationOpts } from "../../../data";
import { useDashboardStore } from "@/store/modules/dashboard";
import type { MetricConfigOpt } from "@/types/dashboard";
import { ListChartTypes, ProtocolTypes } from "../../../data";
/*global defineEmits, defineProps */
const props = defineProps({
currentMetricConfig: {
type: Object as PropType<MetricConfigOpt>,
default: () => ({ unit: "" }),
},
index: { type: Number, default: 0 },
});
const { t } = useI18n();
const emit = defineEmits(["update"]);
const dashboardStore = useDashboardStore();
const currentMetric = ref<MetricConfigOpt>({
...props.currentMetricConfig,
topN: props.currentMetricConfig.topN || 10,
});
const metricTypes = dashboardStore.selectedGrid.metricTypes || [];
const metricType = computed(
() => (dashboardStore.selectedGrid.metricTypes || [])[props.index]
);
const hasLabel = computed(() => {
const graph = dashboardStore.selectedGrid.graph || {};
return (
ListChartTypes.includes(graph.type) ||
[
ProtocolTypes.ReadLabeledMetricsValues,
ProtocolTypes.ReadMetricsValues,
].includes(metricType.value)
);
});
const isTopn = computed(() =>
[
ProtocolTypes.SortMetrics,
ProtocolTypes.ReadSampledRecords,
ProtocolTypes.ReadRecords,
].includes(metricTypes[props.index])
);
function updateConfig(index: number, param: { [key: string]: string }) {
const key = Object.keys(param)[0];
if (!key) {
return;
}
changeConfigs(index, { [key]: decodeURIComponent(param[key]) });
}
function changeConfigs(
index: number,
param: { [key: string]: string | number }
) {
const metricConfig = dashboardStore.selectedGrid.metricConfig || [];
metricConfig[index] = { ...metricConfig[index], ...param };
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
metricConfig,
/*global defineEmits, defineProps */
const props = defineProps({
currentMetricConfig: {
type: Object as PropType<MetricConfigOpt>,
default: () => ({ unit: "" }),
},
index: { type: Number, default: 0 },
});
emit("update");
}
watch(
() => props.currentMetricConfig,
() => {
currentMetric.value = {
...props.currentMetricConfig,
topN: props.currentMetricConfig.topN || 10,
};
const { t } = useI18n();
const emit = defineEmits(["update"]);
const dashboardStore = useDashboardStore();
const currentMetric = ref<MetricConfigOpt>({
...props.currentMetricConfig,
topN: props.currentMetricConfig.topN || 10,
});
const metricTypes = dashboardStore.selectedGrid.metricTypes || [];
const metricType = computed(() => (dashboardStore.selectedGrid.metricTypes || [])[props.index]);
const hasLabel = computed(() => {
const graph = dashboardStore.selectedGrid.graph || {};
return (
ListChartTypes.includes(graph.type) ||
[ProtocolTypes.ReadLabeledMetricsValues, ProtocolTypes.ReadMetricsValues].includes(metricType.value)
);
});
const isTopn = computed(() =>
[ProtocolTypes.SortMetrics, ProtocolTypes.ReadSampledRecords, ProtocolTypes.ReadRecords].includes(
metricTypes[props.index],
),
);
function updateConfig(index: number, param: { [key: string]: string }) {
const key = Object.keys(param)[0];
if (!key) {
return;
}
changeConfigs(index, { [key]: decodeURIComponent(param[key]) });
}
);
function changeConfigs(index: number, param: { [key: string]: string | number }) {
const metricConfig = dashboardStore.selectedGrid.metricConfig || [];
metricConfig[index] = { ...metricConfig[index], ...param };
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
metricConfig,
});
emit("update");
}
watch(
() => props.currentMetricConfig,
() => {
currentMetric.value = {
...props.currentMetricConfig,
topN: props.currentMetricConfig.topN || 10,
};
},
);
</script>
<style lang="scss" scoped>
.config-panel {
padding: 10px 5px;
position: relative;
}
.config-panel {
padding: 10px 5px;
position: relative;
}
.label {
width: 150px;
display: inline-block;
font-size: 12px;
}
.label {
width: 150px;
display: inline-block;
font-size: 12px;
}
.close {
position: absolute;
top: -8px;
right: -15px;
}
.close {
position: absolute;
top: -8px;
right: -15px;
}
.selectors {
width: 365px;
}
.selectors {
width: 365px;
}
</style>

View File

@@ -14,12 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="log-wrapper flex-v">
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="dashboardStore.editMode"
>
<el-popover placement="bottom" trigger="click" :width="100" v-if="dashboardStore.editMode">
<template #reference>
<span class="delete cp">
<Icon iconName="ellipsis_v" size="middle" class="operation" />
@@ -36,58 +31,58 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/demand-log/Header.vue";
import Content from "../related/demand-log/Content.vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/demand-log/Header.vue";
import Content from "../related/demand-log/Content.vue";
/*global defineProps */
const props = defineProps({
data: {
type: Object,
default: () => ({}),
},
activeIndex: { type: String, default: "" },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
/*global defineProps */
const props = defineProps({
data: {
type: Object,
default: () => ({}),
},
activeIndex: { type: String, default: "" },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
function removeWidget() {
dashboardStore.removeControls(props.data);
}
function removeWidget() {
dashboardStore.removeControls(props.data);
}
</script>
<style lang="scss" scoped>
.log-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
overflow: auto;
}
.delete {
position: absolute;
top: 5px;
right: 3px;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
min-width: 1024px;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
.log-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
overflow: auto;
}
.delete {
position: absolute;
top: 5px;
right: 3px;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
min-width: 1024px;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
}
}
}
</style>

View File

@@ -14,12 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="profile-wrapper flex-v">
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="dashboardStore.editMode"
>
<el-popover placement="bottom" trigger="click" :width="100" v-if="dashboardStore.editMode">
<template #reference>
<span class="delete cp">
<Icon iconName="ellipsis_v" size="middle" class="operation" />
@@ -34,63 +29,63 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/ebpf/Header.vue";
import Content from "../related/ebpf/Content.vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/ebpf/Header.vue";
import Content from "../related/ebpf/Content.vue";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
function removeWidget() {
dashboardStore.removeControls(props.data);
}
function removeWidget() {
dashboardStore.removeControls(props.data);
}
</script>
<style lang="scss" scoped>
.profile-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
}
.delete {
position: absolute;
top: 5px;
right: 3px;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
.profile-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
}
}
.trace {
width: 100%;
overflow: auto;
}
.delete {
position: absolute;
top: 5px;
right: 3px;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
}
}
.trace {
width: 100%;
overflow: auto;
}
</style>

View File

@@ -14,12 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="event-wrapper flex-v">
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="dashboardStore.editMode"
>
<el-popover placement="bottom" trigger="click" :width="100" v-if="dashboardStore.editMode">
<template #reference>
<span class="delete cp">
<Icon iconName="ellipsis_v" size="middle" class="operation" />
@@ -41,69 +36,69 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/event/Header.vue";
import Content from "../related/event/Content.vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/event/Header.vue";
import Content from "../related/event/Content.vue";
/*global defineProps */
const props = defineProps({
data: {
type: Object,
default: () => ({}),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
/*global defineProps */
const props = defineProps({
data: {
type: Object,
default: () => ({}),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
function removeWidget() {
dashboardStore.removeControls(props.data);
}
function editConfig() {
dashboardStore.setConfigPanel(true);
dashboardStore.selectWidget(props.data);
}
function removeWidget() {
dashboardStore.removeControls(props.data);
}
function editConfig() {
dashboardStore.setConfigPanel(true);
dashboardStore.selectWidget(props.data);
}
</script>
<style lang="scss" scoped>
.event-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
overflow: auto;
}
.delete {
position: absolute;
top: 5px;
right: 3px;
z-index: 9999;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
min-width: 1024px;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
.event-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
overflow: auto;
}
}
.event {
width: 100%;
height: calc(100% - 80px);
}
.delete {
position: absolute;
top: 5px;
right: 3px;
z-index: 9999;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
min-width: 1024px;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
}
}
.event {
width: 100%;
height: calc(100% - 80px);
}
</style>

View File

@@ -14,12 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="log-wrapper flex-v">
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="dashboardStore.editMode"
>
<el-popover placement="bottom" trigger="click" :width="100" v-if="dashboardStore.editMode">
<template #reference>
<span class="delete cp">
<Icon iconName="ellipsis_v" size="middle" class="operation" />
@@ -38,68 +33,68 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { provide } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/log/Header.vue";
import List from "../related/log/List.vue";
import { LayoutConfig } from "@/types/dashboard";
import type { PropType } from "vue";
import { provide } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/log/Header.vue";
import List from "../related/log/List.vue";
import type { LayoutConfig } from "@/types/dashboard";
import type { PropType } from "vue";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<LayoutConfig>,
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
provide("options", props.data);
const { t } = useI18n();
const dashboardStore = useDashboardStore();
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<LayoutConfig>,
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
provide("options", props.data);
const { t } = useI18n();
const dashboardStore = useDashboardStore();
function removeWidget() {
dashboardStore.removeControls(props.data);
}
function removeWidget() {
dashboardStore.removeControls(props.data);
}
</script>
<style lang="scss" scoped>
.log-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
overflow: auto;
}
.delete {
position: absolute;
top: 5px;
right: 3px;
z-index: 1000;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
min-width: 1024px;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
.log-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
overflow: auto;
}
}
.log {
width: 100%;
}
.delete {
position: absolute;
top: 5px;
right: 3px;
z-index: 1000;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
min-width: 1024px;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
}
}
.log {
width: 100%;
}
</style>

View File

@@ -15,12 +15,7 @@ limitations under the License. -->
<template>
<div class="profile-wrapper flex-v">
<div class="title">Network Profiling</div>
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="dashboardStore.editMode"
>
<el-popover placement="bottom" trigger="click" :width="100" v-if="dashboardStore.editMode">
<template #reference>
<span class="operation cp">
<Icon iconName="ellipsis_v" size="middle" />
@@ -34,64 +29,64 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Content from "../related/network-profiling/Content.vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Content from "../related/network-profiling/Content.vue";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
function removeWidget() {
dashboardStore.removeControls(props.data);
}
function removeWidget() {
dashboardStore.removeControls(props.data);
}
</script>
<style lang="scss" scoped>
.profile-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
}
.operation {
position: absolute;
top: 8px;
right: 3px;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
.profile-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
}
}
.title {
font-weight: bold;
line-height: 40px;
padding: 0 10px;
border-bottom: 1px solid #dcdfe6;
}
.operation {
position: absolute;
top: 8px;
right: 3px;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
}
}
.title {
font-weight: bold;
line-height: 40px;
padding: 0 10px;
border-bottom: 1px solid #dcdfe6;
}
</style>

View File

@@ -14,12 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="profile-wrapper flex-v">
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="dashboardStore.editMode"
>
<el-popover placement="bottom" trigger="click" :width="100" v-if="dashboardStore.editMode">
<template #reference>
<span class="delete cp">
<Icon iconName="ellipsis_v" size="middle" class="operation" />
@@ -34,63 +29,63 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/profile/Header.vue";
import Content from "../related/profile/Content.vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/profile/Header.vue";
import Content from "../related/profile/Content.vue";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
function removeWidget() {
dashboardStore.removeControls(props.data);
}
function removeWidget() {
dashboardStore.removeControls(props.data);
}
</script>
<style lang="scss" scoped>
.profile-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
}
.delete {
position: absolute;
top: 5px;
right: 3px;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
.profile-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
}
}
.trace {
width: 100%;
overflow: auto;
}
.delete {
position: absolute;
top: 5px;
right: 3px;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
}
}
.trace {
width: 100%;
overflow: auto;
}
</style>

View File

@@ -104,158 +104,54 @@ limitations under the License. -->
</div>
</template>
<script lang="ts">
import { ref, watch, defineComponent, toRefs } from "vue";
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
import type { PropType } from "vue";
import { LayoutConfig } from "@/types/dashboard";
import { useDashboardStore } from "@/store/modules/dashboard";
import controls from "./tab";
import { dragIgnoreFrom } from "../data";
import copy from "@/utils/copy";
import { ref, watch, defineComponent, toRefs } from "vue";
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
import type { PropType } from "vue";
import type { LayoutConfig } from "@/types/dashboard";
import { useDashboardStore } from "@/store/modules/dashboard";
import controls from "./tab";
import { dragIgnoreFrom } from "../data";
import copy from "@/utils/copy";
const props = {
data: {
type: Object as PropType<LayoutConfig>,
default: () => ({ children: [] }),
},
active: { type: Boolean, default: false },
};
export default defineComponent({
name: "Tab",
components: {
...controls,
},
props,
setup(props) {
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const route = useRoute();
const activeTabIndex = ref<number>(
Number(route.params.activeTabIndex) || 0
);
const activeTabWidget = ref<string>("");
const editTabIndex = ref<number>(NaN); // edit tab item name
const canEditTabName = ref<boolean>(false);
const needQuery = ref<boolean>(false);
const props = {
data: {
type: Object as PropType<LayoutConfig>,
default: () => ({ children: [] }),
},
active: { type: Boolean, default: false },
};
export default defineComponent({
name: "Tab",
components: {
...controls,
},
props,
setup(props) {
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const route = useRoute();
const activeTabIndex = ref<number>(Number(route.params.activeTabIndex) || 0);
const activeTabWidget = ref<string>("");
const editTabIndex = ref<number>(NaN); // edit tab item name
const canEditTabName = ref<boolean>(false);
const needQuery = ref<boolean>(false);
dashboardStore.setActiveTabIndex(activeTabIndex);
const l = dashboardStore.layout.findIndex(
(d: LayoutConfig) => d.i === props.data.i
);
if (dashboardStore.layout[l].children.length) {
dashboardStore.setCurrentTabItems(
dashboardStore.layout[l].children[activeTabIndex.value].children
);
dashboardStore.setActiveTabIndex(activeTabIndex.value, props.data.i);
}
dashboardStore.setActiveTabIndex(activeTabIndex);
const l = dashboardStore.layout.findIndex((d: LayoutConfig) => d.i === props.data.i);
if (dashboardStore.layout[l].children.length) {
dashboardStore.setCurrentTabItems(dashboardStore.layout[l].children[activeTabIndex.value].children);
dashboardStore.setActiveTabIndex(activeTabIndex.value, props.data.i);
}
function clickTabs(e: Event, idx: number) {
e.stopPropagation();
activeTabIndex.value = idx;
dashboardStore.activeGridItem(props.data.i);
dashboardStore.selectWidget(props.data);
dashboardStore.setActiveTabIndex(idx);
const l = dashboardStore.layout.findIndex(
(d: LayoutConfig) => d.i === props.data.i
);
dashboardStore.setCurrentTabItems(
dashboardStore.layout[l].children[activeTabIndex.value].children
);
needQuery.value = true;
if (route.params.activeTabIndex) {
let p = location.href.split("/tab/")[0];
p = p + "/tab/" + activeTabIndex.value;
history.replaceState({}, "", p);
}
}
function removeTab(e: Event) {
e.stopPropagation();
dashboardStore.removeTab(props.data);
}
function deleteTabItem(e: Event, idx: number) {
e.stopPropagation();
dashboardStore.removeTabItem(props.data, idx);
const kids = dashboardStore.layout[l].children[0];
const arr = (kids && kids.children) || [];
dashboardStore.setCurrentTabItems(arr);
dashboardStore.activeGridItem(0);
activeTabIndex.value = 0;
needQuery.value = true;
}
function addTabItem() {
dashboardStore.addTabItem(props.data);
}
function editTabName(el: Event, index: number) {
if (!canEditTabName.value) {
editTabIndex.value = NaN;
return;
}
editTabIndex.value = index;
}
function handleClick(el: any) {
el.stopPropagation();
needQuery.value = true;
if (["tab-name", "edit-tab"].includes(el.target.className)) {
return;
}
canEditTabName.value = false;
editTabIndex.value = NaN;
}
function clickTabGrid(e: Event, item: LayoutConfig) {
e.stopPropagation();
activeTabWidget.value = item.i;
dashboardStore.activeGridItem(
`${props.data.i}-${activeTabIndex.value}-${item.i}`
);
handleClick(e);
}
function layoutUpdatedEvent() {
const l = dashboardStore.layout.findIndex(
(d: LayoutConfig) => d.i === props.data.i
);
dashboardStore.setCurrentTabItems(
dashboardStore.layout[l].children[activeTabIndex.value].children
);
}
function copyLink() {
let path = "";
if (route.params.activeTabIndex === undefined) {
path = location.href + "/tab/" + activeTabIndex.value;
} else {
const p = location.href.split("/tab/")[0];
path = p + "/tab/" + activeTabIndex.value;
}
copy(path);
}
document.body.addEventListener("click", handleClick, false);
watch(
() => dashboardStore.activedGridItem,
(data) => {
if (!data) {
activeTabWidget.value = "";
return;
}
const i = data.split("-");
if (i[0] === props.data.i && activeTabIndex.value === Number(i[1])) {
activeTabWidget.value = i[2];
} else {
activeTabWidget.value = "";
}
}
);
watch(
() => dashboardStore.currentTabIndex,
() => {
activeTabIndex.value = dashboardStore.currentTabIndex;
function clickTabs(e: Event, idx: number) {
e.stopPropagation();
activeTabIndex.value = idx;
dashboardStore.activeGridItem(props.data.i);
dashboardStore.selectWidget(props.data);
const l = dashboardStore.layout.findIndex(
(d: LayoutConfig) => d.i === props.data.i
);
dashboardStore.setCurrentTabItems(
dashboardStore.layout[l].children[activeTabIndex.value].children
);
dashboardStore.setActiveTabIndex(idx);
const l = dashboardStore.layout.findIndex((d: LayoutConfig) => d.i === props.data.i);
dashboardStore.setCurrentTabItems(dashboardStore.layout[l].children[activeTabIndex.value].children);
needQuery.value = true;
if (route.params.activeTabIndex) {
let p = location.href.split("/tab/")[0];
@@ -263,133 +159,217 @@ export default defineComponent({
history.replaceState({}, "", p);
}
}
);
return {
handleClick,
layoutUpdatedEvent,
clickTabGrid,
editTabName,
addTabItem,
deleteTabItem,
removeTab,
clickTabs,
copyLink,
...toRefs(props),
activeTabWidget,
dashboardStore,
activeTabIndex,
editTabIndex,
needQuery,
canEditTabName,
t,
dragIgnoreFrom,
};
},
});
function removeTab(e: Event) {
e.stopPropagation();
dashboardStore.removeTab(props.data);
}
function deleteTabItem(e: Event, idx: number) {
e.stopPropagation();
dashboardStore.removeTabItem(props.data, idx);
const kids = dashboardStore.layout[l].children[0];
const arr = (kids && kids.children) || [];
dashboardStore.setCurrentTabItems(arr);
dashboardStore.activeGridItem(0);
activeTabIndex.value = 0;
needQuery.value = true;
}
function addTabItem() {
dashboardStore.addTabItem(props.data);
}
function editTabName(el: Event, index: number) {
if (!canEditTabName.value) {
editTabIndex.value = NaN;
return;
}
editTabIndex.value = index;
}
function handleClick(el: any) {
el.stopPropagation();
needQuery.value = true;
if (["tab-name", "edit-tab"].includes(el.target.className)) {
return;
}
canEditTabName.value = false;
editTabIndex.value = NaN;
}
function clickTabGrid(e: Event, item: LayoutConfig) {
e.stopPropagation();
activeTabWidget.value = item.i;
dashboardStore.activeGridItem(`${props.data.i}-${activeTabIndex.value}-${item.i}`);
handleClick(e);
}
function layoutUpdatedEvent() {
const l = dashboardStore.layout.findIndex((d: LayoutConfig) => d.i === props.data.i);
dashboardStore.setCurrentTabItems(dashboardStore.layout[l].children[activeTabIndex.value].children);
}
function copyLink() {
let path = "";
if (route.params.activeTabIndex === undefined) {
path = location.href + "/tab/" + activeTabIndex.value;
} else {
const p = location.href.split("/tab/")[0];
path = p + "/tab/" + activeTabIndex.value;
}
copy(path);
}
document.body.addEventListener("click", handleClick, false);
watch(
() => dashboardStore.activedGridItem,
(data) => {
if (!data) {
activeTabWidget.value = "";
return;
}
const i = data.split("-");
if (i[0] === props.data.i && activeTabIndex.value === Number(i[1])) {
activeTabWidget.value = i[2];
} else {
activeTabWidget.value = "";
}
},
);
watch(
() => dashboardStore.currentTabIndex,
() => {
activeTabIndex.value = dashboardStore.currentTabIndex;
dashboardStore.activeGridItem(props.data.i);
dashboardStore.selectWidget(props.data);
const l = dashboardStore.layout.findIndex((d: LayoutConfig) => d.i === props.data.i);
dashboardStore.setCurrentTabItems(dashboardStore.layout[l].children[activeTabIndex.value].children);
needQuery.value = true;
if (route.params.activeTabIndex) {
let p = location.href.split("/tab/")[0];
p = p + "/tab/" + activeTabIndex.value;
history.replaceState({}, "", p);
}
},
);
return {
handleClick,
layoutUpdatedEvent,
clickTabGrid,
editTabName,
addTabItem,
deleteTabItem,
removeTab,
clickTabs,
copyLink,
...toRefs(props),
activeTabWidget,
dashboardStore,
activeTabIndex,
editTabIndex,
needQuery,
canEditTabName,
t,
dragIgnoreFrom,
};
},
});
</script>
<style lang="scss" scoped>
.tabs {
height: 40px;
color: #ccc;
width: 100%;
overflow-x: auto;
white-space: nowrap;
overflow-y: hidden;
span {
display: inline-block;
.tabs {
height: 40px;
line-height: 40px;
cursor: pointer;
text-align: center;
}
color: #ccc;
width: 100%;
overflow-x: auto;
white-space: nowrap;
overflow-y: hidden;
.tab-name {
max-width: 110px;
height: 20px;
line-height: 20px;
outline: none;
color: #333;
font-style: normal;
margin-right: 5px;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
}
.tab-icons {
color: #333;
i {
margin-right: 3px;
span {
display: inline-block;
height: 40px;
line-height: 40px;
cursor: pointer;
text-align: center;
}
}
.view {
cursor: pointer;
}
input.tab-name {
border: 0;
}
span.active {
border-bottom: 1px solid #409eff;
.tab-name {
color: #409eff;
max-width: 110px;
height: 20px;
line-height: 20px;
outline: none;
color: #333;
font-style: normal;
margin-right: 5px;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
}
.tab-icons {
color: #333;
i {
margin-right: 3px;
}
}
.view {
cursor: pointer;
}
input.tab-name {
border: 0;
}
span.active {
border-bottom: 1px solid #409eff;
.tab-name {
color: #409eff;
}
}
}
}
.operations {
color: #aaa;
cursor: pointer;
height: 40px;
line-height: 40px;
padding-right: 10px;
}
.operations {
color: #aaa;
cursor: pointer;
height: 40px;
line-height: 40px;
padding-right: 10px;
}
.icon-operation {
display: inline-block;
margin-top: 8px;
}
.icon-operation {
display: inline-block;
margin-top: 8px;
}
.tab-header {
justify-content: space-between;
width: 100%;
border-bottom: 1px solid #eee;
}
.tab-header {
justify-content: space-between;
width: 100%;
border-bottom: 1px solid #eee;
}
.vue-grid-layout {
background: #f7f9fa;
height: auto !important;
}
.vue-grid-layout {
background: #f7f9fa;
height: auto !important;
}
.vue-grid-item:not(.vue-grid-placeholder) {
background: #fff;
box-shadow: 0px 1px 4px 0px #00000029;
border-radius: 3px;
}
.vue-grid-item:not(.vue-grid-placeholder) {
background: #fff;
box-shadow: 0px 1px 4px 0px #00000029;
border-radius: 3px;
}
.tab-layout {
height: calc(100% - 55px);
overflow: auto;
}
.tab-layout {
height: calc(100% - 55px);
overflow: auto;
}
.tab-icon {
color: #666;
}
.tab-icon {
color: #666;
}
.vue-grid-item.active {
border: 1px solid #409eff;
}
.vue-grid-item.active {
border: 1px solid #409eff;
}
.no-data-tips {
width: 100%;
text-align: center;
font-size: 14px;
padding-top: 30px;
color: #888;
}
.no-data-tips {
width: 100%;
text-align: center;
font-size: 14px;
padding-top: 30px;
color: #888;
}
</style>

View File

@@ -15,12 +15,7 @@ limitations under the License. -->
<template>
<div class="topology">
<div class="header">
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="dashboardStore.editMode"
>
<el-popover placement="bottom" trigger="click" :width="100" v-if="dashboardStore.editMode">
<template #reference>
<span>
<Icon iconName="ellipsis_v" size="middle" class="operation" />
@@ -55,69 +50,69 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { computed } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { TextColors } from "@/views/dashboard/data";
import { computed } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { TextColors } from "@/views/dashboard/data";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
});
const { t } = useI18n();
const graph = computed(() => props.data.graph || {});
const dashboardStore = useDashboardStore();
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
});
const { t } = useI18n();
const graph = computed(() => props.data.graph || {});
const dashboardStore = useDashboardStore();
function removeTopo() {
dashboardStore.removeControls(props.data);
}
function editConfig() {
dashboardStore.setConfigPanel(true);
dashboardStore.selectWidget(props.data);
}
function removeTopo() {
dashboardStore.removeControls(props.data);
}
function editConfig() {
dashboardStore.setConfigPanel(true);
dashboardStore.selectWidget(props.data);
}
</script>
<style lang="scss" scoped>
.topology {
font-size: 12px;
height: 100%;
position: relative;
}
.operation {
cursor: pointer;
}
.header {
position: absolute;
top: 5px;
right: 5px;
}
.body {
padding: 0 20px 0 10px;
width: 100%;
height: 100%;
cursor: pointer;
display: flex;
align-items: center;
overflow: auto;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
.topology {
font-size: 12px;
height: 100%;
position: relative;
}
.operation {
cursor: pointer;
}
.header {
position: absolute;
top: 5px;
right: 5px;
}
.body {
padding: 0 20px 0 10px;
width: 100%;
height: 100%;
cursor: pointer;
display: flex;
align-items: center;
overflow: auto;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
}
}
}
</style>

View File

@@ -15,12 +15,7 @@ limitations under the License. -->
<template>
<div class="time-range">
<div class="header">
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="dashboardStore.editMode"
>
<el-popover placement="bottom" trigger="click" :width="100" v-if="dashboardStore.editMode">
<template #reference>
<span>
<Icon iconName="ellipsis_v" size="middle" class="operation" />
@@ -62,126 +57,126 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { computed } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { TextColors } from "@/views/dashboard/data";
import { computed } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { TextColors } from "@/views/dashboard/data";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
});
const { t } = useI18n();
const graph = computed(() => props.data.graph || {});
const dashboardStore = useDashboardStore();
const appStore = useAppStoreWithOut();
const content = computed(() => {
const text = [appStore.durationRow.start, appStore.durationRow.end]
.map((date: Date) => tf(date, "YYYY-MM-DD HH:mm"))
.join(` ~ `);
return text;
});
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
});
const { t } = useI18n();
const graph = computed(() => props.data.graph || {});
const dashboardStore = useDashboardStore();
const appStore = useAppStoreWithOut();
const content = computed(() => {
const text = [appStore.durationRow.start, appStore.durationRow.end]
.map((date: Date) => tf(date, "YYYY-MM-DD HH:mm"))
.join(` ~ `);
return text;
});
function removeTopo() {
dashboardStore.removeControls(props.data);
}
function editConfig() {
dashboardStore.setConfigPanel(true);
dashboardStore.selectWidget(props.data);
}
function tf(time: Date, format: any): string {
const local = {
dow: 1, // Monday is the first day of the week
hourTip: t("hourTip"), // tip of select hour
minuteTip: t("minuteTip"), // tip of select minute
secondTip: t("secondTip"), // tip of select second
yearSuffix: t("yearSuffix"), // format of head
monthsHead: t("monthsHead").split("_"), // months of head
months: t("months").split("_"), // months of panel
weeks: t("weeks").split("_"), // weeks
cancelTip: t("cancel"), // default text for cancel button
submitTip: t("confirm"), // default text for submit button
quarterHourCutTip: t("quarterHourCutTip"),
halfHourCutTip: t("halfHourCutTip"),
hourCutTip: t("hourCutTip"),
dayCutTip: t("dayCutTip"),
weekCutTip: t("weekCutTip"),
monthCutTip: t("monthCutTip"),
};
const year = time.getFullYear();
const month = time.getMonth();
const day = time.getDate();
const hours24 = time.getHours();
const hours = hours24 % 12 === 0 ? 12 : hours24 % 12;
const minutes = time.getMinutes();
const seconds = time.getSeconds();
const milliseconds = time.getMilliseconds();
const dd = (t: number) => `0${t}`.slice(-2);
const map: { [key: string]: string | number } = {
YYYY: year,
MM: dd(month + 1),
MMM: local.months[month],
MMMM: local.monthsHead[month],
M: month + 1,
DD: dd(day),
D: day,
HH: dd(hours24),
H: hours24,
hh: dd(hours),
h: hours,
mm: dd(minutes),
m: minutes,
ss: dd(seconds),
s: seconds,
S: milliseconds,
};
return format.replace(/Y+|M+|D+|H+|h+|m+|s+|S+/g, (str: string) => map[str]);
}
function removeTopo() {
dashboardStore.removeControls(props.data);
}
function editConfig() {
dashboardStore.setConfigPanel(true);
dashboardStore.selectWidget(props.data);
}
function tf(time: Date, format: any): string {
const local = {
dow: 1, // Monday is the first day of the week
hourTip: t("hourTip"), // tip of select hour
minuteTip: t("minuteTip"), // tip of select minute
secondTip: t("secondTip"), // tip of select second
yearSuffix: t("yearSuffix"), // format of head
monthsHead: t("monthsHead").split("_"), // months of head
months: t("months").split("_"), // months of panel
weeks: t("weeks").split("_"), // weeks
cancelTip: t("cancel"), // default text for cancel button
submitTip: t("confirm"), // default text for submit button
quarterHourCutTip: t("quarterHourCutTip"),
halfHourCutTip: t("halfHourCutTip"),
hourCutTip: t("hourCutTip"),
dayCutTip: t("dayCutTip"),
weekCutTip: t("weekCutTip"),
monthCutTip: t("monthCutTip"),
};
const year = time.getFullYear();
const month = time.getMonth();
const day = time.getDate();
const hours24 = time.getHours();
const hours = hours24 % 12 === 0 ? 12 : hours24 % 12;
const minutes = time.getMinutes();
const seconds = time.getSeconds();
const milliseconds = time.getMilliseconds();
const dd = (t: number) => `0${t}`.slice(-2);
const map: { [key: string]: string | number } = {
YYYY: year,
MM: dd(month + 1),
MMM: local.months[month],
MMMM: local.monthsHead[month],
M: month + 1,
DD: dd(day),
D: day,
HH: dd(hours24),
H: hours24,
hh: dd(hours),
h: hours,
mm: dd(minutes),
m: minutes,
ss: dd(seconds),
s: seconds,
S: milliseconds,
};
return format.replace(/Y+|M+|D+|H+|h+|m+|s+|S+/g, (str: string) => map[str]);
}
</script>
<style lang="scss" scoped>
.time-range {
font-size: 12px;
height: 100%;
position: relative;
}
.operation {
cursor: pointer;
}
.header {
position: absolute;
top: 5px;
right: 5px;
}
.body {
padding: 0 20px 0 10px;
width: 100%;
height: 100%;
cursor: pointer;
display: flex;
align-items: center;
overflow: auto;
font-weight: bold;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
.time-range {
font-size: 12px;
height: 100%;
position: relative;
}
.operation {
cursor: pointer;
}
.header {
position: absolute;
top: 5px;
right: 5px;
}
.body {
padding: 0 20px 0 10px;
width: 100%;
height: 100%;
cursor: pointer;
display: flex;
align-items: center;
overflow: auto;
font-weight: bold;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
}
}
}
</style>

View File

@@ -15,12 +15,7 @@ limitations under the License. -->
<template>
<div class="topology flex-v">
<div class="operation">
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="dashboardStore.editMode"
>
<el-popover placement="bottom" trigger="click" :width="100" v-if="dashboardStore.editMode">
<template #reference>
<span>
<Icon iconName="ellipsis_v" size="middle" />
@@ -38,64 +33,64 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Topology from "../related/topology/Index.vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Topology from "../related/topology/Index.vue";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
function removeTopo() {
dashboardStore.removeControls(props.data);
}
function editConfig() {
dashboardStore.setConfigPanel(true);
dashboardStore.selectWidget(props.data);
}
function removeTopo() {
dashboardStore.removeControls(props.data);
}
function editConfig() {
dashboardStore.setConfigPanel(true);
dashboardStore.selectWidget(props.data);
}
</script>
<style lang="scss" scoped>
.topology {
background-color: #333840;
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
}
.operation {
position: absolute;
top: 5px;
right: 3px;
cursor: pointer;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
.topology {
background-color: #333840;
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
}
}
.no-data {
font-size: 14px;
color: #888;
width: 100%;
text-align: center;
padding-top: 20px;
}
.operation {
position: absolute;
top: 5px;
right: 3px;
cursor: pointer;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
}
}
.no-data {
font-size: 14px;
color: #888;
width: 100%;
text-align: center;
padding-top: 20px;
}
</style>

View File

@@ -14,12 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="trace-wrapper flex-v">
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="dashboardStore.editMode"
>
<el-popover placement="bottom" trigger="click" :width="100" v-if="dashboardStore.editMode">
<template #reference>
<span class="delete cp">
<Icon iconName="ellipsis_v" size="middle" class="operation" />
@@ -39,68 +34,68 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { provide } from "vue";
import type { PropType } from "vue";
import Filter from "../related/trace/Filter.vue";
import TraceList from "../related/trace/TraceList.vue";
import TraceDetail from "../related/trace/Detail.vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { provide } from "vue";
import type { PropType } from "vue";
import Filter from "../related/trace/Filter.vue";
import TraceList from "../related/trace/TraceList.vue";
import TraceDetail from "../related/trace/Detail.vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
provide("options", props.data);
const { t } = useI18n();
const dashboardStore = useDashboardStore();
function removeWidget() {
dashboardStore.removeControls(props.data);
}
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
provide("options", props.data);
const { t } = useI18n();
const dashboardStore = useDashboardStore();
function removeWidget() {
dashboardStore.removeControls(props.data);
}
</script>
<style lang="scss" scoped>
.trace-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
overflow: auto;
}
.delete {
position: absolute;
top: 5px;
right: 3px;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
min-width: 1200px;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
.trace-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
overflow: auto;
}
}
.trace {
width: 100%;
overflow: auto;
min-width: 1200px;
}
.delete {
position: absolute;
top: 5px;
right: 3px;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
min-width: 1200px;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
}
}
.trace {
width: 100%;
overflow: auto;
min-width: 1200px;
}
</style>

View File

@@ -23,20 +23,10 @@ limitations under the License. -->
<div>
<el-tooltip :content="widget.tips || ''">
<span>
<Icon
iconName="info_outline"
size="sm"
class="operation"
v-show="widget.tips"
/>
<Icon iconName="info_outline" size="sm" class="operation" v-show="widget.tips" />
</span>
</el-tooltip>
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="dashboardStore.editMode"
>
<el-popover placement="bottom" trigger="click" :width="100" v-if="dashboardStore.editMode">
<template #reference>
<span>
<Icon iconName="ellipsis_v" size="middle" class="operation" />
@@ -75,246 +65,227 @@ limitations under the License. -->
</div>
</template>
<script lang="ts">
import { toRefs, reactive, defineComponent, ref, watch, computed } from "vue";
import type { PropType } from "vue";
import { LayoutConfig } from "@/types/dashboard";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import graphs from "../graphs";
import { useI18n } from "vue-i18n";
import {
useQueryProcessor,
useSourceProcessor,
useGetMetricEntity,
} from "@/hooks/useMetricsProcessor";
import { EntityType, ListChartTypes } from "../data";
import { EventParams } from "@/types/dashboard";
import getDashboard from "@/hooks/useDashboardsSession";
import { toRefs, reactive, defineComponent, ref, watch, computed } from "vue";
import type { PropType } from "vue";
import type { LayoutConfig } from "@/types/dashboard";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import graphs from "../graphs";
import { useI18n } from "vue-i18n";
import { useQueryProcessor, useSourceProcessor, useGetMetricEntity } from "@/hooks/useMetricsProcessor";
import { EntityType, ListChartTypes } from "../data";
import type { EventParams } from "@/types/dashboard";
import getDashboard from "@/hooks/useDashboardsSession";
const props = {
data: {
type: Object as PropType<LayoutConfig>,
default: () => ({ widget: {}, graph: {} }),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: false },
};
const props = {
data: {
type: Object as PropType<LayoutConfig>,
default: () => ({ widget: {}, graph: {} }),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: false },
};
export default defineComponent({
name: "Widget",
components: { ...graphs },
props,
setup(props) {
const { t } = useI18n();
const loading = ref<boolean>(false);
const state = reactive<{ source: { [key: string]: unknown } }>({
source: {},
});
const { data } = toRefs(props);
const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore();
const graph = computed(() => props.data.graph || {});
const widget = computed(() => props.data.widget || {});
const isList = computed(() =>
ListChartTypes.includes((props.data.graph && props.data.graph.type) || "")
);
export default defineComponent({
name: "Widget",
components: { ...graphs },
props,
setup(props) {
const { t } = useI18n();
const loading = ref<boolean>(false);
const state = reactive<{ source: { [key: string]: unknown } }>({
source: {},
});
const { data } = toRefs(props);
const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore();
const graph = computed(() => props.data.graph || {});
const widget = computed(() => props.data.widget || {});
const isList = computed(() => ListChartTypes.includes((props.data.graph && props.data.graph.type) || ""));
if (
(props.needQuery || !dashboardStore.currentDashboard.id) &&
!isList.value
) {
queryMetrics();
}
async function queryMetrics() {
const metricTypes = props.data.metricTypes || [];
const metrics = props.data.metrics || [];
const catalog = await useGetMetricEntity(metrics[0], metricTypes[0]);
const params = await useQueryProcessor({ ...props.data, catalog });
if (!params) {
state.source = {};
return;
if ((props.needQuery || !dashboardStore.currentDashboard.id) && !isList.value) {
queryMetrics();
}
loading.value = true;
const json = await dashboardStore.fetchMetricValue(params);
loading.value = false;
if (!json) {
return;
async function queryMetrics() {
const metricTypes = props.data.metricTypes || [];
const metrics = props.data.metrics || [];
const catalog = await useGetMetricEntity(metrics[0], metricTypes[0]);
const params = await useQueryProcessor({ ...props.data, catalog });
if (!params) {
state.source = {};
return;
}
loading.value = true;
const json = await dashboardStore.fetchMetricValue(params);
loading.value = false;
if (!json) {
return;
}
const d = {
metrics: props.data.metrics || [],
metricTypes: props.data.metricTypes || [],
metricConfig: props.data.metricConfig || [],
};
state.source = useSourceProcessor(json, d);
}
const d = {
metrics: props.data.metrics || [],
metricTypes: props.data.metricTypes || [],
metricConfig: props.data.metricConfig || [],
function removeWidget() {
dashboardStore.removeControls(props.data);
}
function editConfig() {
dashboardStore.setConfigPanel(true);
dashboardStore.selectWidget(props.data);
if (props.activeIndex) {
dashboardStore.activeGridItem(props.activeIndex);
} else {
dashboardStore.activeGridItem(props.data.i);
}
}
function clickHandle(params: EventParams | any) {
const { widgets } = getDashboard(dashboardStore.currentDashboard);
const associate = (props.data.associate && props.data.associate) || [];
for (const item of associate) {
const widget = widgets.find((d: LayoutConfig) => d.id === item.widgetId);
if (widget) {
widget.filters = {
dataIndex: params.dataIndex,
sourceId: props.data.id || "",
};
dashboardStore.setWidget(widget);
}
}
}
watch(
() => [props.data.metricTypes, props.data.metrics],
() => {
if (!dashboardStore.selectedGrid) {
return;
}
if (props.data.i !== dashboardStore.selectedGrid.i) {
return;
}
const chart = dashboardStore.selectedGrid.graph || {};
if (ListChartTypes.includes(chart.type) || isList.value) {
return;
}
queryMetrics();
},
);
watch(
() => [selectorStore.currentService, selectorStore.currentDestService],
() => {
if (isList.value) {
return;
}
if (dashboardStore.entity === EntityType[0].value || dashboardStore.entity === EntityType[4].value) {
queryMetrics();
}
},
);
watch(
() => [selectorStore.currentPod, selectorStore.currentDestPod],
() => {
if (dashboardStore.entity === EntityType[0].value || dashboardStore.entity === EntityType[7].value) {
return;
}
if (isList.value) {
return;
}
queryMetrics();
},
);
watch(
() => [selectorStore.currentProcess, selectorStore.currentDestProcess],
() => {
if (!(selectorStore.currentDestProcess && selectorStore.currentProcess)) {
return;
}
if (dashboardStore.entity === EntityType[7].value) {
queryMetrics();
}
},
);
watch(
() => appStore.durationTime,
() => {
if (isList.value) {
return;
}
if (dashboardStore.entity === EntityType[1].value) {
queryMetrics();
}
},
);
return {
state,
appStore,
removeWidget,
editConfig,
data,
loading,
dashboardStore,
t,
graph,
widget,
clickHandle,
};
state.source = useSourceProcessor(json, d);
}
function removeWidget() {
dashboardStore.removeControls(props.data);
}
function editConfig() {
dashboardStore.setConfigPanel(true);
dashboardStore.selectWidget(props.data);
if (props.activeIndex) {
dashboardStore.activeGridItem(props.activeIndex);
} else {
dashboardStore.activeGridItem(props.data.i);
}
}
function clickHandle(params: EventParams | any) {
const { widgets } = getDashboard(dashboardStore.currentDashboard);
const associate = (props.data.associate && props.data.associate) || [];
for (const item of associate) {
const widget = widgets.find(
(d: LayoutConfig) => d.id === item.widgetId
);
if (widget) {
widget.filters = {
dataIndex: params.dataIndex,
sourceId: props.data.id || "",
};
dashboardStore.setWidget(widget);
}
}
}
watch(
() => [props.data.metricTypes, props.data.metrics],
() => {
if (!dashboardStore.selectedGrid) {
return;
}
if (props.data.i !== dashboardStore.selectedGrid.i) {
return;
}
const chart = dashboardStore.selectedGrid.graph || {};
if (ListChartTypes.includes(chart.type) || isList.value) {
return;
}
queryMetrics();
}
);
watch(
() => [selectorStore.currentService, selectorStore.currentDestService],
() => {
if (isList.value) {
return;
}
if (
dashboardStore.entity === EntityType[0].value ||
dashboardStore.entity === EntityType[4].value
) {
queryMetrics();
}
}
);
watch(
() => [selectorStore.currentPod, selectorStore.currentDestPod],
() => {
if (
dashboardStore.entity === EntityType[0].value ||
dashboardStore.entity === EntityType[7].value
) {
return;
}
if (isList.value) {
return;
}
queryMetrics();
}
);
watch(
() => [selectorStore.currentProcess, selectorStore.currentDestProcess],
() => {
if (
!(selectorStore.currentDestProcess && selectorStore.currentProcess)
) {
return;
}
if (dashboardStore.entity === EntityType[7].value) {
queryMetrics();
}
}
);
watch(
() => appStore.durationTime,
() => {
if (isList.value) {
return;
}
if (dashboardStore.entity === EntityType[1].value) {
queryMetrics();
}
}
);
return {
state,
appStore,
removeWidget,
editConfig,
data,
loading,
dashboardStore,
t,
graph,
widget,
clickHandle,
};
},
});
},
});
</script>
<style lang="scss" scoped>
.widget {
font-size: 12px;
height: 100%;
}
.header {
height: 30px;
padding: 5px;
width: 100%;
border-bottom: 1px solid #eee;
justify-content: space-between;
}
.operation {
cursor: pointer;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
.widget {
font-size: 12px;
height: 100%;
}
}
.body {
padding: 5px 10px;
width: 100%;
height: calc(100% - 30px);
}
.header {
height: 30px;
padding: 5px;
width: 100%;
border-bottom: 1px solid #eee;
justify-content: space-between;
}
.no-data {
font-size: 14px;
color: #888;
width: 100%;
text-align: center;
padding-top: 20px;
}
.operation {
cursor: pointer;
}
.unit {
display: inline-block;
margin-left: 5px;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
}
}
.body {
padding: 5px 10px;
width: 100%;
height: calc(100% - 30px);
}
.no-data {
font-size: 14px;
color: #888;
width: 100%;
text-align: center;
padding-top: 20px;
}
.unit {
display: inline-block;
margin-left: 5px;
}
</style>

View File

@@ -140,9 +140,7 @@ export const MetricTypes: {
value: "readLabeledMetricsValues",
},
],
HEATMAP: [
{ label: "read heatmap values in the duration", value: "readHeatMap" },
],
HEATMAP: [{ label: "read heatmap values in the duration", value: "readHeatMap" }],
SAMPLED_RECORD: [{ label: "get sorted topN values", value: "readRecords" }],
};

View File

@@ -14,44 +14,34 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<Line
:data="data"
:intervalTime="intervalTime"
:config="config"
@click="clickEvent"
/>
<Line :data="data" :intervalTime="intervalTime" :config="config" @click="clickEvent" />
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import Line from "./Line.vue";
import {
AreaConfig,
EventParams,
RelatedTrace,
Filters,
} from "@/types/dashboard";
import type { PropType } from "vue";
import Line from "./Line.vue";
import type { AreaConfig, EventParams, RelatedTrace, Filters } from "@/types/dashboard";
/*global defineProps, defineEmits */
const emits = defineEmits(["click"]);
defineProps({
data: {
type: Object as PropType<{ [key: string]: number[] }>,
default: () => ({}),
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
config: {
type: Object as PropType<
AreaConfig & {
filters: Filters;
relatedTrace: RelatedTrace;
id: string;
associate: { widgetId: string }[];
}
>,
default: () => ({}),
},
});
function clickEvent(params: EventParams) {
emits("click", params);
}
/*global defineProps, defineEmits */
const emits = defineEmits(["click"]);
defineProps({
data: {
type: Object as PropType<{ [key: string]: number[] }>,
default: () => ({}),
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
config: {
type: Object as PropType<
AreaConfig & {
filters: Filters;
relatedTrace: RelatedTrace;
id: string;
associate: { widgetId: string }[];
}
>,
default: () => ({}),
},
});
function clickEvent(params: EventParams) {
emits("click", params);
}
</script>

View File

@@ -14,141 +14,124 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="graph" :class="isRight ? 'flex-h' : 'flex-v'">
<Graph
:option="option"
@select="clickEvent"
:filters="config.filters"
:associate="config.associate || []"
/>
<Graph :option="option" @select="clickEvent" :filters="config.filters" :associate="config.associate || []" />
<Legend :config="config.legend" :data="data" :intervalTime="intervalTime" />
</div>
</template>
<script lang="ts" setup>
import { computed } from "vue";
import type { PropType } from "vue";
import {
BarConfig,
EventParams,
RelatedTrace,
Filters,
} from "@/types/dashboard";
import useLegendProcess from "@/hooks/useLegendProcessor";
import { computed } from "vue";
import type { PropType } from "vue";
import type { BarConfig, EventParams, RelatedTrace, Filters } from "@/types/dashboard";
import useLegendProcess from "@/hooks/useLegendProcessor";
/*global defineProps, defineEmits */
const emits = defineEmits(["click"]);
const props = defineProps({
data: {
type: Object as PropType<{ [key: string]: number[] }>,
default: () => ({}),
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
theme: { type: String, default: "light" },
config: {
type: Object as PropType<
BarConfig & {
filters: Filters;
relatedTrace: RelatedTrace;
id: string;
associate: { widgetId: string }[];
}
>,
default: () => ({}),
},
});
const { showEchartsLegend, isRight, chartColors } = useLegendProcess(
props.config.legend
);
const option = computed(() => getOption());
function getOption() {
const keys = Object.keys(props.data || {}).filter(
(i: any) => Array.isArray(props.data[i]) && props.data[i].length
);
const temp = keys.map((i: string) => {
if (!props.intervalTime) {
return;
}
return {
data: props.data[i].map((item: number, itemIndex: number) => [
props.intervalTime[itemIndex],
item,
]),
name: i,
type: "bar",
symbol: "none",
stack: "sum",
lineStyle: {
width: 1.5,
type: "dotted",
},
showBackground: props.config.showBackground,
backgroundStyle: {
color: "rgba(180, 180, 180, 0.1)",
},
};
/*global defineProps, defineEmits */
const emits = defineEmits(["click"]);
const props = defineProps({
data: {
type: Object as PropType<{ [key: string]: number[] }>,
default: () => ({}),
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
theme: { type: String, default: "light" },
config: {
type: Object as PropType<
BarConfig & {
filters: Filters;
relatedTrace: RelatedTrace;
id: string;
associate: { widgetId: string }[];
}
>,
default: () => ({}),
},
});
const color: string[] = chartColors(keys);
return {
color,
tooltip: {
trigger: "none",
const { showEchartsLegend, isRight, chartColors } = useLegendProcess(props.config.legend);
const option = computed(() => getOption());
function getOption() {
const keys = Object.keys(props.data || {}).filter((i: any) => Array.isArray(props.data[i]) && props.data[i].length);
const temp = keys.map((i: string) => {
if (!props.intervalTime) {
return;
}
return {
data: props.data[i].map((item: number, itemIndex: number) => [props.intervalTime[itemIndex], item]),
name: i,
type: "bar",
symbol: "none",
stack: "sum",
lineStyle: {
width: 1.5,
type: "dotted",
},
showBackground: props.config.showBackground,
backgroundStyle: {
color: "rgba(180, 180, 180, 0.1)",
},
};
});
const color: string[] = chartColors(keys);
return {
color,
tooltip: {
trigger: "none",
axisPointer: {
type: "cross",
color: "#333",
backgroundColor: "rgba(255, 255, 255, 0.8)",
},
},
legend: {
type: "scroll",
show: showEchartsLegend(keys),
icon: "circle",
top: 0,
left: 0,
itemWidth: 12,
textStyle: {
color: "#333",
},
},
grid: {
top: keys.length === 1 ? 15 : 40,
left: 0,
right: 10,
bottom: 5,
containLabel: true,
},
axisPointer: {
type: "cross",
color: "#333",
backgroundColor: "rgba(255, 255, 255, 0.8)",
label: {
color: "#333",
backgroundColor: "rgba(255, 255, 255, 0.8)",
},
},
},
legend: {
type: "scroll",
show: showEchartsLegend(keys),
icon: "circle",
top: 0,
left: 0,
itemWidth: 12,
textStyle: {
color: "#333",
xAxis: {
type: "category",
axisTick: {
lineStyle: { color: "#c1c5ca41" },
alignWithLabel: true,
},
splitLine: { show: false },
axisLine: { lineStyle: { color: "rgba(0,0,0,0)" } },
axisLabel: { color: "#9da5b2", fontSize: "11" },
},
},
grid: {
top: keys.length === 1 ? 15 : 40,
left: 0,
right: 10,
bottom: 5,
containLabel: true,
},
axisPointer: {
label: {
color: "#333",
backgroundColor: "rgba(255, 255, 255, 0.8)",
yAxis: {
type: "value",
axisLine: { show: false },
axisTick: { show: false },
splitLine: { lineStyle: { color: "#c1c5ca41", type: "dashed" } },
axisLabel: { color: "#9da5b2", fontSize: "11" },
},
},
xAxis: {
type: "category",
axisTick: {
lineStyle: { color: "#c1c5ca41" },
alignWithLabel: true,
},
splitLine: { show: false },
axisLine: { lineStyle: { color: "rgba(0,0,0,0)" } },
axisLabel: { color: "#9da5b2", fontSize: "11" },
},
yAxis: {
type: "value",
axisLine: { show: false },
axisTick: { show: false },
splitLine: { lineStyle: { color: "#c1c5ca41", type: "dashed" } },
axisLabel: { color: "#9da5b2", fontSize: "11" },
},
series: temp,
};
}
function clickEvent(params: EventParams) {
emits("click", params);
}
series: temp,
};
}
function clickEvent(params: EventParams) {
emits("click", params);
}
</script>
<style lang="scss" scoped>
.graph {
width: 100%;
height: 100%;
}
.graph {
width: 100%;
height: 100%;
}
</style>

View File

@@ -30,52 +30,49 @@ limitations under the License. -->
<div class="center no-data" v-else>{{ t("noData") }}</div>
</template>
<script lang="ts" setup>
import { computed, PropType } from "vue";
import { useI18n } from "vue-i18n";
import { CardConfig, MetricConfigOpt } from "@/types/dashboard";
import { computed } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import type { CardConfig, MetricConfigOpt } from "@/types/dashboard";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<{ [key: string]: number }>,
default: () => ({}),
},
config: {
type: Object as PropType<CardConfig & { metricConfig?: MetricConfigOpt[] }>,
default: () => ({
fontSize: 12,
showUnit: true,
textAlign: "center",
metricConfig: [],
}),
},
});
const { t } = useI18n();
const metricConfig = computed(() => props.config.metricConfig || []);
const key = computed(() => Object.keys(props.data)[0]);
const singleVal = computed(() => Number(props.data[key.value]));
const unit = computed(
() =>
metricConfig.value[0] &&
encodeURIComponent(metricConfig.value[0].unit || "")
);
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<{ [key: string]: number }>,
default: () => ({}),
},
config: {
type: Object as PropType<CardConfig & { metricConfig?: MetricConfigOpt[] }>,
default: () => ({
fontSize: 12,
showUnit: true,
textAlign: "center",
metricConfig: [],
}),
},
});
const { t } = useI18n();
const metricConfig = computed(() => props.config.metricConfig || []);
const key = computed(() => Object.keys(props.data)[0]);
const singleVal = computed(() => Number(props.data[key.value]));
const unit = computed(() => metricConfig.value[0] && encodeURIComponent(metricConfig.value[0].unit || ""));
</script>
<style lang="scss" scoped>
.chart-card {
color: #333;
width: 100%;
height: 100%;
display: flex;
align-items: center;
}
.chart-card {
color: #333;
width: 100%;
height: 100%;
display: flex;
align-items: center;
}
.no-data {
height: 100%;
color: #666;
}
.no-data {
height: 100%;
color: #666;
}
.unit {
display: inline-block;
margin-left: 2px;
}
.unit {
display: inline-block;
margin-left: 2px;
}
</style>

View File

@@ -15,12 +15,7 @@ limitations under the License. -->
<template>
<div class="table">
<div class="search">
<el-input
v-model="searchText"
placeholder="Search for more endpoints"
@change="searchList"
class="inputs"
>
<el-input v-model="searchText" placeholder="Search for more endpoints" @change="searchList" class="inputs">
<template #append>
<el-button @click="searchList" class="btn">
<Icon size="middle" iconName="search" />
@@ -33,11 +28,7 @@ limitations under the License. -->
<el-table v-loading="chartLoading" :data="endpoints" style="width: 100%">
<el-table-column label="Endpoints" fixed min-width="220">
<template #default="scope">
<span
class="link"
@click="clickEndpoint(scope)"
:style="{ fontSize: `${config.fontSize}px` }"
>
<span class="link" @click="clickEndpoint(scope)" :style="{ fontSize: `${config.fontSize}px` }">
{{ scope.row.label }}
</span>
</template>
@@ -58,152 +49,137 @@ limitations under the License. -->
</div>
</template>
<script setup lang="ts">
import { ref, watch } from "vue";
import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
import { useI18n } from "vue-i18n";
import type { PropType } from "vue";
import { EndpointListConfig } from "@/types/dashboard";
import { Endpoint } from "@/types/selector";
import { useDashboardStore } from "@/store/modules/dashboard";
import {
useQueryPodsMetrics,
usePodsSource,
} from "@/hooks/useMetricsProcessor";
import { EntityType } from "../data";
import router from "@/router";
import getDashboard from "@/hooks/useDashboardsSession";
import { MetricConfigOpt } from "@/types/dashboard";
import ColumnGraph from "./components/ColumnGraph.vue";
import { ref, watch } from "vue";
import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
import { useI18n } from "vue-i18n";
import type { PropType } from "vue";
import type { EndpointListConfig } from "@/types/dashboard";
import type { Endpoint } from "@/types/selector";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useQueryPodsMetrics, usePodsSource } from "@/hooks/useMetricsProcessor";
import { EntityType } from "../data";
import router from "@/router";
import getDashboard from "@/hooks/useDashboardsSession";
import type { MetricConfigOpt } from "@/types/dashboard";
import ColumnGraph from "./components/ColumnGraph.vue";
/*global defineProps */
const props = defineProps({
data: {
type: Object,
},
config: {
type: Object as PropType<
EndpointListConfig & {
i: string;
metrics: string[];
metricTypes: string[];
} & { metricConfig: MetricConfigOpt[] }
>,
default: () => ({
metrics: [],
metricTypes: [],
dashboardName: "",
fontSize: 12,
i: "",
}),
},
needQuery: { type: Boolean, default: false },
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
});
const { t } = useI18n();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const chartLoading = ref<boolean>(false);
const endpoints = ref<Endpoint[]>([]);
const searchText = ref<string>("");
const colMetrics = ref<string[]>([]);
const metricConfig = ref<MetricConfigOpt[]>(props.config.metricConfig || []);
const metricTypes = ref<string[]>(props.config.metricTypes || []);
if (props.needQuery) {
queryEndpoints();
}
async function queryEndpoints() {
chartLoading.value = true;
const resp = await selectorStore.getEndpoints({
keyword: searchText.value,
/*global defineProps */
const props = defineProps({
data: {
type: Object,
},
config: {
type: Object as PropType<
EndpointListConfig & {
i: string;
metrics: string[];
metricTypes: string[];
} & { metricConfig: MetricConfigOpt[] }
>,
default: () => ({
metrics: [],
metricTypes: [],
dashboardName: "",
fontSize: 12,
i: "",
}),
},
needQuery: { type: Boolean, default: false },
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
});
chartLoading.value = false;
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
endpoints.value = selectorStore.pods;
queryEndpointMetrics(endpoints.value);
}
async function queryEndpointMetrics(currentPods: Endpoint[]) {
if (!currentPods.length) {
return;
}
const metrics = props.config.metrics || [];
const types = props.config.metricTypes || [];
if (metrics.length && metrics[0] && types.length && types[0]) {
const params = await useQueryPodsMetrics(
currentPods,
props.config,
EntityType[2].value
);
const json = await dashboardStore.fetchMetricValue(params);
const { t } = useI18n();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const chartLoading = ref<boolean>(false);
const endpoints = ref<Endpoint[]>([]);
const searchText = ref<string>("");
const colMetrics = ref<string[]>([]);
const metricConfig = ref<MetricConfigOpt[]>(props.config.metricConfig || []);
const metricTypes = ref<string[]>(props.config.metricTypes || []);
if (json.errors) {
ElMessage.error(json.errors);
return;
}
const { data, names, metricConfigArr, metricTypesArr } = usePodsSource(
currentPods,
json,
{
...props.config,
metricConfig: metricConfig.value,
}
);
endpoints.value = data;
colMetrics.value = names;
metricTypes.value = metricTypesArr;
metricConfig.value = metricConfigArr;
return;
}
endpoints.value = currentPods;
}
function clickEndpoint(scope: any) {
const { dashboard } = getDashboard({
name: props.config.dashboardName,
layer: dashboardStore.layerId,
entity: EntityType[2].value,
});
if (!dashboard) {
ElMessage.error("No this dashboard");
return;
}
router.push(
`/dashboard/${dashboard.layer}/${dashboard.entity}/${selectorStore.currentService.id}/${scope.row.id}/${dashboard.name}`
);
}
async function searchList() {
await queryEndpoints();
}
watch(
() => [
...(props.config.metricTypes || []),
...(props.config.metrics || []),
...(props.config.metricConfig || []),
],
(data, old) => {
if (JSON.stringify(data) === JSON.stringify(old)) {
return;
}
metricConfig.value = props.config.metricConfig;
queryEndpointMetrics(endpoints.value);
}
);
watch(
() => selectorStore.currentService,
() => {
if (props.needQuery) {
queryEndpoints();
}
);
async function queryEndpoints() {
chartLoading.value = true;
const resp = await selectorStore.getEndpoints({
keyword: searchText.value,
});
chartLoading.value = false;
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
endpoints.value = selectorStore.pods;
queryEndpointMetrics(endpoints.value);
}
async function queryEndpointMetrics(currentPods: Endpoint[]) {
if (!currentPods.length) {
return;
}
const metrics = props.config.metrics || [];
const types = props.config.metricTypes || [];
if (metrics.length && metrics[0] && types.length && types[0]) {
const params = await useQueryPodsMetrics(currentPods, props.config, EntityType[2].value);
const json = await dashboardStore.fetchMetricValue(params);
if (json.errors) {
ElMessage.error(json.errors);
return;
}
const { data, names, metricConfigArr, metricTypesArr } = usePodsSource(currentPods, json, {
...props.config,
metricConfig: metricConfig.value,
});
endpoints.value = data;
colMetrics.value = names;
metricTypes.value = metricTypesArr;
metricConfig.value = metricConfigArr;
return;
}
endpoints.value = currentPods;
}
function clickEndpoint(scope: any) {
const { dashboard } = getDashboard({
name: props.config.dashboardName,
layer: dashboardStore.layerId,
entity: EntityType[2].value,
});
if (!dashboard) {
ElMessage.error("No this dashboard");
return;
}
router.push(
`/dashboard/${dashboard.layer}/${dashboard.entity}/${selectorStore.currentService.id}/${scope.row.id}/${dashboard.name}`,
);
}
async function searchList() {
await queryEndpoints();
}
watch(
() => [...(props.config.metricTypes || []), ...(props.config.metrics || []), ...(props.config.metricConfig || [])],
(data, old) => {
if (JSON.stringify(data) === JSON.stringify(old)) {
return;
}
metricConfig.value = props.config.metricConfig;
queryEndpointMetrics(endpoints.value);
},
);
watch(
() => selectorStore.currentService,
() => {
queryEndpoints();
},
);
</script>
<style lang="scss" scoped>
@import "./style.scss";
@import "./style.scss";
.tips {
color: rgba(255, 0, 0, 0.5);
}
.tips {
color: rgba(255, 0, 0, 0.5);
}
</style>

View File

@@ -16,147 +16,147 @@ limitations under the License. -->
<Graph :option="option" />
</template>
<script lang="ts" setup>
import { computed } from "vue";
import type { PropType } from "vue";
import { computed } from "vue";
import type { PropType } from "vue";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<{
[key: string]: { nodes: number[][]; buckets: number[][] };
}>,
default: () => ({}),
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
config: {
type: Object as PropType<any>,
default: () => ({ metrics: [] }),
},
});
const option = computed(() => getOption());
function getOption() {
const metric = props.config.metrics && props.config.metrics[0];
let nodes: any = [];
if (metric) {
nodes = (props.data[metric] && props.data[metric].nodes) || [];
}
const source = (nodes || []).map((d: number[]) => d[2]);
const maxItem = Math.max(...source);
const minItem = Math.min(...source);
const colorBox = [
"#fff",
"#FDF0F0",
"#FAE2E2",
"#F8D3D3",
"#F6C4C4",
"#F4B5B5",
"#F1A7A7",
"#EF9898",
"#E86C6C",
"#E44E4E",
"#E23F3F",
"#DF3131",
"#DD2222",
"#CE2020",
"#C01D1D",
"#B11B1B",
"#A21919",
"#851414",
"#761212",
"#671010",
];
return {
tooltip: {
position: "top",
// formatter: (a: any) =>
// `${a.data[1] * 100}${props.standard.unit} [ ${a.data[2]} ]`,
// textStyle: {
// fontSize: 13,
// color: "#ccc",
// },
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<{
[key: string]: { nodes: number[][]; buckets: number[][] };
}>,
default: () => ({}),
},
grid: {
top: 15,
left: 0,
right: 10,
bottom: 5,
containLabel: true,
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
config: {
type: Object as PropType<any>,
default: () => ({ metrics: [] }),
},
xAxis: {
type: "category",
data: props.intervalTime,
axisTick: {
lineStyle: { color: "#c1c5ca" },
alignWithLabel: true,
});
const option = computed(() => getOption());
function getOption() {
const metric = props.config.metrics && props.config.metrics[0];
let nodes: any = [];
if (metric) {
nodes = (props.data[metric] && props.data[metric].nodes) || [];
}
const source = (nodes || []).map((d: number[]) => d[2]);
const maxItem = Math.max(...source);
const minItem = Math.min(...source);
const colorBox = [
"#fff",
"#FDF0F0",
"#FAE2E2",
"#F8D3D3",
"#F6C4C4",
"#F4B5B5",
"#F1A7A7",
"#EF9898",
"#E86C6C",
"#E44E4E",
"#E23F3F",
"#DF3131",
"#DD2222",
"#CE2020",
"#C01D1D",
"#B11B1B",
"#A21919",
"#851414",
"#761212",
"#671010",
];
return {
tooltip: {
position: "top",
// formatter: (a: any) =>
// `${a.data[1] * 100}${props.standard.unit} [ ${a.data[2]} ]`,
// textStyle: {
// fontSize: 13,
// color: "#ccc",
// },
},
splitLine: { show: false },
axisLine: { lineStyle: { color: "rgba(0,0,0,0)" } },
axisLabel: { color: "#9da5b2", fontSize: "11" },
},
visualMap: [
{
min: minItem,
max: maxItem,
show: false,
type: "piecewise",
calculable: true,
pieces: generatePieces(maxItem, colorBox, minItem),
grid: {
top: 15,
left: 0,
right: 10,
bottom: 5,
containLabel: true,
},
],
yAxis: {
type: "category",
axisLine: { show: false },
axisTick: { show: false },
splitLine: { lineStyle: { color: "#c1c5ca", type: "dashed" } },
axisLabel: { color: "#9da5b2", fontSize: "11" },
data: props.data.buckets,
},
series: [
{
type: "heatmap",
data: nodes || [],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowColor: "rgba(0, 0, 0, 0.5)",
xAxis: {
type: "category",
data: props.intervalTime,
axisTick: {
lineStyle: { color: "#c1c5ca" },
alignWithLabel: true,
},
splitLine: { show: false },
axisLine: { lineStyle: { color: "rgba(0,0,0,0)" } },
axisLabel: { color: "#9da5b2", fontSize: "11" },
},
visualMap: [
{
min: minItem,
max: maxItem,
show: false,
type: "piecewise",
calculable: true,
pieces: generatePieces(maxItem, colorBox, minItem),
},
],
yAxis: {
type: "category",
axisLine: { show: false },
axisTick: { show: false },
splitLine: { lineStyle: { color: "#c1c5ca", type: "dashed" } },
axisLabel: { color: "#9da5b2", fontSize: "11" },
data: props.data.buckets,
},
series: [
{
type: "heatmap",
data: nodes || [],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowColor: "rgba(0, 0, 0, 0.5)",
},
},
},
},
],
};
}
function generatePieces(maxValue: number, colorBox: string[], minItem: number) {
if (maxValue < minItem) {
return [];
],
};
}
const pieces = [];
let quotient = 1;
let temp = {} as { min: number; max: number; color: string };
temp.max = minItem;
temp.min = minItem;
temp.color = colorBox[0];
pieces.push(temp);
if (maxValue && maxValue >= 19) {
quotient = Math.floor(maxValue / 19);
for (let i = 1; i < 20; i++) {
temp = {} as any;
if (i === 1) {
temp.min = minItem;
} else {
temp.min = quotient * (i - 1);
}
temp.max = quotient * i;
temp.color = colorBox[i];
pieces.push(temp);
function generatePieces(maxValue: number, colorBox: string[], minItem: number) {
if (maxValue < minItem) {
return [];
}
const pieces = [];
let quotient = 1;
let temp = {} as { min: number; max: number; color: string };
temp.max = minItem;
temp.min = minItem;
temp.color = colorBox[0];
pieces.push(temp);
if (maxValue && maxValue >= 19) {
quotient = Math.floor(maxValue / 19);
for (let i = 1; i < 20; i++) {
temp = {} as any;
if (i === 1) {
temp.min = minItem;
} else {
temp.min = quotient * (i - 1);
}
temp.max = quotient * i;
temp.color = colorBox[i];
pieces.push(temp);
}
}
const length = pieces.length;
if (length) {
const item = pieces[length - 1];
item.max = maxValue;
}
return pieces;
}
const length = pieces.length;
if (length) {
const item = pieces[length - 1];
item.max = maxValue;
}
return pieces;
}
</script>

View File

@@ -15,12 +15,7 @@ limitations under the License. -->
<template>
<div class="table">
<div class="search">
<el-input
v-model="searchText"
placeholder="Please input instance name"
@change="searchList"
class="inputs"
>
<el-input v-model="searchText" placeholder="Please input instance name" @change="searchList" class="inputs">
<template #append>
<el-button class="btn" @click="searchList">
<Icon size="sm" iconName="search" />
@@ -32,11 +27,7 @@ limitations under the License. -->
<el-table v-loading="chartLoading" :data="instances" style="width: 100%">
<el-table-column label="Service Instances" fixed min-width="320">
<template #default="scope">
<span
class="link"
@click="clickInstance(scope)"
:style="{ fontSize: `${config.fontSize}px` }"
>
<span class="link" @click="clickInstance(scope)" :style="{ fontSize: `${config.fontSize}px` }">
{{ scope.row.label }}
</span>
</template>
@@ -87,174 +78,153 @@ limitations under the License. -->
</div>
</template>
<script setup lang="ts">
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
import type { PropType } from "vue";
import { useSelectorStore } from "@/store/modules/selectors";
import { useDashboardStore } from "@/store/modules/dashboard";
import { InstanceListConfig } from "@/types/dashboard";
import { Instance } from "@/types/selector";
import {
useQueryPodsMetrics,
usePodsSource,
} from "@/hooks/useMetricsProcessor";
import { EntityType } from "../data";
import router from "@/router";
import getDashboard from "@/hooks/useDashboardsSession";
import { MetricConfigOpt } from "@/types/dashboard";
import ColumnGraph from "./components/ColumnGraph.vue";
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
import type { PropType } from "vue";
import { useSelectorStore } from "@/store/modules/selectors";
import { useDashboardStore } from "@/store/modules/dashboard";
import type { InstanceListConfig } from "@/types/dashboard";
import type { Instance } from "@/types/selector";
import { useQueryPodsMetrics, usePodsSource } from "@/hooks/useMetricsProcessor";
import { EntityType } from "../data";
import router from "@/router";
import getDashboard from "@/hooks/useDashboardsSession";
import type { MetricConfigOpt } from "@/types/dashboard";
import ColumnGraph from "./components/ColumnGraph.vue";
/*global defineProps */
const props = defineProps({
config: {
type: Object as PropType<
InstanceListConfig & {
i: string;
metrics: string[];
metricTypes: string[];
isEdit: boolean;
} & { metricConfig: MetricConfigOpt[] }
>,
default: () => ({
dashboardName: "",
fontSize: 12,
i: "",
metrics: [],
metricTypes: [],
}),
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
needQuery: { type: Boolean, default: false },
});
const { t } = useI18n();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const chartLoading = ref<boolean>(false);
const instances = ref<Instance[]>([]); // current instances
const pageSize = 10;
const searchText = ref<string>("");
const colMetrics = ref<string[]>([]);
const metricConfig = ref<MetricConfigOpt[]>(props.config.metricConfig || []);
const metricTypes = ref<string[]>(props.config.metricTypes || []);
if (props.needQuery) {
queryInstance();
}
async function queryInstance() {
chartLoading.value = true;
const resp = await selectorStore.getServiceInstances();
chartLoading.value = false;
if (resp && resp.errors) {
ElMessage.error(resp.errors);
instances.value = [];
return;
}
instances.value = selectorStore.pods.filter(
(d: unknown, index: number) => index < pageSize
);
queryInstanceMetrics(instances.value);
}
async function queryInstanceMetrics(currentInstances: Instance[]) {
if (!currentInstances.length) {
return;
}
const metrics = props.config.metrics || [];
const types = props.config.metricTypes || [];
if (metrics.length && metrics[0] && types.length && types[0]) {
const params = await useQueryPodsMetrics(
currentInstances,
props.config,
EntityType[3].value
);
const json = await dashboardStore.fetchMetricValue(params);
if (json.errors) {
ElMessage.error(json.errors);
return;
}
const { data, names, metricConfigArr, metricTypesArr } = usePodsSource(
currentInstances,
json,
{
...props.config,
metricConfig: metricConfig.value,
}
);
instances.value = data;
colMetrics.value = names;
metricTypes.value = metricTypesArr;
metricConfig.value = metricConfigArr;
return;
}
instances.value = currentInstances;
}
function clickInstance(scope: any) {
const { dashboard } = getDashboard({
name: props.config.dashboardName,
layer: dashboardStore.layerId,
entity: EntityType[3].value,
/*global defineProps */
const props = defineProps({
config: {
type: Object as PropType<
InstanceListConfig & {
i: string;
metrics: string[];
metricTypes: string[];
isEdit: boolean;
} & { metricConfig: MetricConfigOpt[] }
>,
default: () => ({
dashboardName: "",
fontSize: 12,
i: "",
metrics: [],
metricTypes: [],
}),
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
needQuery: { type: Boolean, default: false },
});
if (!dashboard) {
ElMessage.error("No this dashboard");
return;
}
router.push(
`/dashboard/${dashboard.layer}/${dashboard.entity}/${
selectorStore.currentService.id
}/${scope.row.id}/${dashboard.name.split(" ").join("-")}`
);
}
function changePage(pageIndex: number) {
instances.value = selectorStore.pods.filter((d: unknown, index: number) => {
if (index >= (pageIndex - 1) * pageSize && index < pageIndex * pageSize) {
return d;
}
});
queryInstanceMetrics(instances.value);
}
function searchList() {
const searchInstances = selectorStore.pods.filter((d: { label: string }) =>
d.label.includes(searchText.value)
);
instances.value = searchInstances.filter(
(d: unknown, index: number) => index < pageSize
);
queryInstanceMetrics(instances.value);
}
watch(
() => [
...(props.config.metricTypes || []),
...(props.config.metrics || []),
...(props.config.metricConfig || []),
],
(data, old) => {
if (JSON.stringify(data) === JSON.stringify(old)) {
return;
}
metricConfig.value = props.config.metricConfig;
queryInstanceMetrics(instances.value);
}
);
watch(
() => selectorStore.currentService,
() => {
const { t } = useI18n();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const chartLoading = ref<boolean>(false);
const instances = ref<Instance[]>([]); // current instances
const pageSize = 10;
const searchText = ref<string>("");
const colMetrics = ref<string[]>([]);
const metricConfig = ref<MetricConfigOpt[]>(props.config.metricConfig || []);
const metricTypes = ref<string[]>(props.config.metricTypes || []);
if (props.needQuery) {
queryInstance();
}
);
async function queryInstance() {
chartLoading.value = true;
const resp = await selectorStore.getServiceInstances();
chartLoading.value = false;
if (resp && resp.errors) {
ElMessage.error(resp.errors);
instances.value = [];
return;
}
instances.value = selectorStore.pods.filter((d: unknown, index: number) => index < pageSize);
queryInstanceMetrics(instances.value);
}
async function queryInstanceMetrics(currentInstances: Instance[]) {
if (!currentInstances.length) {
return;
}
const metrics = props.config.metrics || [];
const types = props.config.metricTypes || [];
if (metrics.length && metrics[0] && types.length && types[0]) {
const params = await useQueryPodsMetrics(currentInstances, props.config, EntityType[3].value);
const json = await dashboardStore.fetchMetricValue(params);
if (json.errors) {
ElMessage.error(json.errors);
return;
}
const { data, names, metricConfigArr, metricTypesArr } = usePodsSource(currentInstances, json, {
...props.config,
metricConfig: metricConfig.value,
});
instances.value = data;
colMetrics.value = names;
metricTypes.value = metricTypesArr;
metricConfig.value = metricConfigArr;
return;
}
instances.value = currentInstances;
}
function clickInstance(scope: any) {
const { dashboard } = getDashboard({
name: props.config.dashboardName,
layer: dashboardStore.layerId,
entity: EntityType[3].value,
});
if (!dashboard) {
ElMessage.error("No this dashboard");
return;
}
router.push(
`/dashboard/${dashboard.layer}/${dashboard.entity}/${selectorStore.currentService.id}/${
scope.row.id
}/${dashboard.name.split(" ").join("-")}`,
);
}
function changePage(pageIndex: number) {
instances.value = selectorStore.pods.filter((d: unknown, index: number) => {
if (index >= (pageIndex - 1) * pageSize && index < pageIndex * pageSize) {
return d;
}
});
queryInstanceMetrics(instances.value);
}
function searchList() {
const searchInstances = selectorStore.pods.filter((d: { label: string }) => d.label.includes(searchText.value));
instances.value = searchInstances.filter((d: unknown, index: number) => index < pageSize);
queryInstanceMetrics(instances.value);
}
watch(
() => [...(props.config.metricTypes || []), ...(props.config.metrics || []), ...(props.config.metricConfig || [])],
(data, old) => {
if (JSON.stringify(data) === JSON.stringify(old)) {
return;
}
metricConfig.value = props.config.metricConfig;
queryInstanceMetrics(instances.value);
},
);
watch(
() => selectorStore.currentService,
() => {
queryInstance();
},
);
</script>
<style lang="scss" scoped>
@import "./style.scss";
@import "./style.scss";
.attributes {
max-height: 400px;
overflow: auto;
}
.attributes {
max-height: 400px;
overflow: auto;
}
</style>

View File

@@ -25,174 +25,161 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { computed, ref } from "vue";
import type { PropType } from "vue";
import {
LineConfig,
EventParams,
RelatedTrace,
Filters,
} from "@/types/dashboard";
import Legend from "./components/Legend.vue";
import useLegendProcess from "@/hooks/useLegendProcessor";
import { computed, ref } from "vue";
import type { PropType } from "vue";
import type { LineConfig, EventParams, RelatedTrace, Filters } from "@/types/dashboard";
import Legend from "./components/Legend.vue";
import useLegendProcess from "@/hooks/useLegendProcessor";
/*global defineProps, defineEmits */
const emits = defineEmits(["click"]);
const props = defineProps({
data: {
type: Object as PropType<{ [key: string]: number[] }>,
default: () => ({}),
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
theme: { type: String, default: "light" },
config: {
type: Object as PropType<
LineConfig & {
filters?: Filters;
relatedTrace?: RelatedTrace;
id?: string;
associate: { widgetId: string }[];
}
>,
default: () => ({
step: false,
smooth: false,
showSymbol: false,
opacity: 0.4,
showXAxis: true,
showYAxis: true,
smallTips: false,
showlabels: true,
}),
},
});
const setRight = ref<boolean>(false);
const option = computed(() => getOption());
function getOption() {
const { showEchartsLegend, isRight, chartColors } = useLegendProcess(
props.config.legend
);
setRight.value = isRight;
const keys = Object.keys(props.data || {}).filter(
(i: any) => Array.isArray(props.data[i]) && props.data[i].length
);
const temp = keys.map((i: any) => {
const serie: any = {
data: props.data[i].map((item: any, itemIndex: number) => [
props.intervalTime[itemIndex],
item,
]),
name: i,
type: "line",
symbol: "circle",
symbolSize: 8,
showSymbol: props.config.showSymbol,
step: props.config.step,
smooth: props.config.smooth,
lineStyle: {
width: 1.5,
type: "solid",
},
};
if (props.config.type === "Area") {
serie.areaStyle = {
opacity: props.config.opacity || 0.4,
};
}
return serie;
/*global defineProps, defineEmits */
const emits = defineEmits(["click"]);
const props = defineProps({
data: {
type: Object as PropType<{ [key: string]: number[] }>,
default: () => ({}),
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
theme: { type: String, default: "light" },
config: {
type: Object as PropType<
LineConfig & {
filters?: Filters;
relatedTrace?: RelatedTrace;
id?: string;
associate?: { widgetId: string }[];
}
>,
default: () => ({
step: false,
smooth: false,
showSymbol: false,
opacity: 0.4,
showXAxis: true,
showYAxis: true,
smallTips: false,
showlabels: true,
}),
},
});
const color: string[] = chartColors(keys);
const tooltip = {
trigger: "none",
axisPointer: {
type: "cross",
color: "#333",
backgroundColor: "rgba(255, 255, 255, 0.8)",
},
// trigger: "axis",
// textStyle: {
// fontSize: 12,
// color: "#333",
// },
// enterable: true,
// confine: true,
extraCssText: "max-height: 300px; overflow: auto; border: none;",
};
const tips = {
formatter(params: any) {
return `${params[0].value[1]}`;
},
confine: true,
extraCssText: `height: 20px; padding:0 2px;`,
trigger: "axis",
textStyle: {
fontSize: 12,
color: "#333",
},
};
return {
color,
tooltip: props.config.smallTips ? tips : tooltip,
legend: {
type: "scroll",
show: showEchartsLegend(keys),
icon: "circle",
top: 0,
left: 0,
itemWidth: 12,
textStyle: {
color: props.theme === "dark" ? "#fff" : "#333",
},
},
axisPointer: {
label: {
const setRight = ref<boolean>(false);
const option = computed(() => getOption());
function getOption() {
const { showEchartsLegend, isRight, chartColors } = useLegendProcess(props.config.legend);
setRight.value = isRight;
const keys = Object.keys(props.data || {}).filter((i: any) => Array.isArray(props.data[i]) && props.data[i].length);
const temp = keys.map((i: any) => {
const serie: any = {
data: props.data[i].map((item: any, itemIndex: number) => [props.intervalTime[itemIndex], item]),
name: i,
type: "line",
symbol: "circle",
symbolSize: 8,
showSymbol: props.config.showSymbol,
step: props.config.step,
smooth: props.config.smooth,
lineStyle: {
width: 1.5,
type: "solid",
},
};
if (props.config.type === "Area") {
serie.areaStyle = {
opacity: props.config.opacity || 0.4,
};
}
return serie;
});
const color: string[] = chartColors(keys);
const tooltip = {
trigger: "none",
axisPointer: {
type: "cross",
color: "#333",
backgroundColor: "rgba(255, 255, 255, 0.8)",
},
},
grid: {
top: showEchartsLegend(keys) ? 35 : 10,
left: 0,
right: 10,
bottom: 5,
containLabel:
props.config.showlabels === undefined ? true : props.config.showlabels,
},
xAxis: {
type: "category",
show: props.config.showXAxis,
axisTick: {
lineStyle: { color: "#c1c5ca41" },
alignWithLabel: true,
// trigger: "axis",
// textStyle: {
// fontSize: 12,
// color: "#333",
// },
// enterable: true,
// confine: true,
extraCssText: "max-height: 300px; overflow: auto; border: none;",
};
const tips = {
formatter(params: any) {
return `${params[0].value[1]}`;
},
splitLine: { show: false },
axisLine: { lineStyle: { color: "rgba(0,0,0,0)" } },
axisLabel: { color: "#9da5b2", fontSize: "11" },
},
yAxis: {
show: props.config.showYAxis,
type: "value",
axisLine: { show: false },
axisTick: { show: false },
splitLine: { lineStyle: { color: "#c1c5ca41", type: "dashed" } },
axisLabel: {
color: "#9da5b2",
fontSize: "11",
show: props.config.showYAxis,
confine: true,
extraCssText: `height: 20px; padding:0 2px;`,
trigger: "axis",
textStyle: {
fontSize: 12,
color: "#333",
},
},
series: temp,
};
}
};
function clickEvent(params: EventParams) {
emits("click", params);
}
return {
color,
tooltip: props.config.smallTips ? tips : tooltip,
legend: {
type: "scroll",
show: showEchartsLegend(keys),
icon: "circle",
top: 0,
left: 0,
itemWidth: 12,
textStyle: {
color: props.theme === "dark" ? "#fff" : "#333",
},
},
axisPointer: {
label: {
color: "#333",
backgroundColor: "rgba(255, 255, 255, 0.8)",
},
},
grid: {
top: showEchartsLegend(keys) ? 35 : 10,
left: 0,
right: 10,
bottom: 5,
containLabel: props.config.showlabels === undefined ? true : props.config.showlabels,
},
xAxis: {
type: "category",
show: props.config.showXAxis,
axisTick: {
lineStyle: { color: "#c1c5ca41" },
alignWithLabel: true,
},
splitLine: { show: false },
axisLine: { lineStyle: { color: "rgba(0,0,0,0)" } },
axisLabel: { color: "#9da5b2", fontSize: "11" },
},
yAxis: {
show: props.config.showYAxis,
type: "value",
axisLine: { show: false },
axisTick: { show: false },
splitLine: { lineStyle: { color: "#c1c5ca41", type: "dashed" } },
axisLabel: {
color: "#9da5b2",
fontSize: "11",
show: props.config.showYAxis,
},
},
series: temp,
};
}
function clickEvent(params: EventParams) {
emits("click", params);
}
</script>
<style lang="scss" scoped>
.graph {
width: 100%;
height: 100%;
}
.graph {
width: 100%;
height: 100%;
}
</style>

View File

@@ -15,12 +15,7 @@ limitations under the License. -->
<template>
<div class="table">
<div class="search">
<el-input
v-model="searchText"
placeholder="Please input service name"
@change="searchList"
class="inputs mt-5"
>
<el-input v-model="searchText" placeholder="Please input service name" @change="searchList" class="inputs mt-5">
<template #append>
<el-button class="btn" @click="searchList">
<Icon size="sm" iconName="search" />
@@ -37,23 +32,14 @@ limitations under the License. -->
:border="true"
:style="{ fontSize: '14px' }"
>
<el-table-column
fixed
label="Service Groups"
v-if="config.showGroup"
min-width="150"
>
<el-table-column fixed label="Service Groups" v-if="config.showGroup" min-width="150">
<template #default="scope">
{{ scope.row.group }}
</template>
</el-table-column>
<el-table-column fixed label="Service Names" min-width="220">
<template #default="scope">
<span
class="link"
:style="{ fontSize: `${config.fontSize}px` }"
@click="clickService(scope)"
>
<span class="link" :style="{ fontSize: `${config.fontSize}px` }" @click="clickService(scope)">
{{ scope.row.label }}
</span>
</template>
@@ -85,89 +71,83 @@ limitations under the License. -->
</div>
</template>
<script setup lang="ts">
import { watch, ref } from "vue";
import { ElMessage } from "element-plus";
import type { PropType } from "vue";
import { ServiceListConfig } from "@/types/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { Service } from "@/types/selector";
import {
useQueryPodsMetrics,
usePodsSource,
} from "@/hooks/useMetricsProcessor";
import { EntityType } from "../data";
import router from "@/router";
import getDashboard from "@/hooks/useDashboardsSession";
import { MetricConfigOpt } from "@/types/dashboard";
import ColumnGraph from "./components/ColumnGraph.vue";
import { watch, ref } from "vue";
import { ElMessage } from "element-plus";
import type { PropType } from "vue";
import type { ServiceListConfig } from "@/types/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import type { Service } from "@/types/selector";
import { useQueryPodsMetrics, usePodsSource } from "@/hooks/useMetricsProcessor";
import { EntityType } from "../data";
import router from "@/router";
import getDashboard from "@/hooks/useDashboardsSession";
import type { MetricConfigOpt } from "@/types/dashboard";
import ColumnGraph from "./components/ColumnGraph.vue";
/*global defineProps */
const props = defineProps({
data: {
type: Object,
},
config: {
type: Object as PropType<
ServiceListConfig & {
i: string;
metrics: string[];
metricTypes: string[];
isEdit: boolean;
names: string[];
metricConfig: MetricConfigOpt[];
}
>,
default: () => ({ dashboardName: "", fontSize: 12 }),
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
isEdit: { type: Boolean, default: false },
});
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const appStore = useAppStoreWithOut();
const chartLoading = ref<boolean>(false);
const pageSize = 10;
const services = ref<Service[]>([]);
const colMetrics = ref<string[]>([]);
const searchText = ref<string>("");
const groups = ref<any>({});
const sortServices = ref<(Service & { merge: boolean })[]>([]);
const metricConfig = ref<MetricConfigOpt[]>(props.config.metricConfig || []);
const metricTypes = ref<string[]>(props.config.metricTypes || []);
queryServices();
async function queryServices() {
chartLoading.value = true;
const resp = await selectorStore.fetchServices(dashboardStore.layerId);
chartLoading.value = false;
if (resp.errors) {
ElMessage.error(resp.errors);
}
sortServices.value = selectorStore.services.sort((a: any, b: any) => {
const groupA = a.group.toUpperCase();
const groupB = b.group.toUpperCase();
if (groupA < groupB) {
return -1;
}
if (groupA > groupB) {
return 1;
}
return 0;
/*global defineProps */
const props = defineProps({
data: {
type: Object,
},
config: {
type: Object as PropType<
ServiceListConfig & {
i: string;
metrics: string[];
metricTypes: string[];
isEdit: boolean;
names: string[];
metricConfig: MetricConfigOpt[];
}
>,
default: () => ({ dashboardName: "", fontSize: 12 }),
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
isEdit: { type: Boolean, default: false },
});
const s = sortServices.value.filter(
(d: Service, index: number) => index < pageSize
);
setServices(s);
}
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const appStore = useAppStoreWithOut();
const chartLoading = ref<boolean>(false);
const pageSize = 10;
const services = ref<Service[]>([]);
const colMetrics = ref<string[]>([]);
const searchText = ref<string>("");
const groups = ref<any>({});
const sortServices = ref<(Service & { merge: boolean })[]>([]);
const metricConfig = ref<MetricConfigOpt[]>(props.config.metricConfig || []);
const metricTypes = ref<string[]>(props.config.metricTypes || []);
function setServices(arr: (Service & { merge: boolean })[]) {
groups.value = {};
const map: { [key: string]: any[] } = arr.reduce(
(result: { [key: string]: any[] }, item: any) => {
queryServices();
async function queryServices() {
chartLoading.value = true;
const resp = await selectorStore.fetchServices(dashboardStore.layerId);
chartLoading.value = false;
if (resp.errors) {
ElMessage.error(resp.errors);
}
sortServices.value = selectorStore.services.sort((a: any, b: any) => {
const groupA = a.group.toUpperCase();
const groupB = b.group.toUpperCase();
if (groupA < groupB) {
return -1;
}
if (groupA > groupB) {
return 1;
}
return 0;
});
const s = sortServices.value.filter((d: Service, index: number) => index < pageSize);
setServices(s);
}
function setServices(arr: (Service & { merge: boolean })[]) {
groups.value = {};
const map: { [key: string]: any[] } = arr.reduce((result: { [key: string]: any[] }, item: any) => {
item.group = item.group || "";
if (result[item.group]) {
item.merge = true;
@@ -177,132 +157,118 @@ function setServices(arr: (Service & { merge: boolean })[]) {
}
result[item.group].push(item);
return result;
},
{}
);
const list = Object.values(map).flat(1);
const obj = {} as any;
for (const s of list) {
s.group = s.group || "";
if (!obj[s.group]) {
obj[s.group] = 1;
} else {
obj[s.group]++;
}
groups.value[s.group] = obj[s.group];
}
services.value = list;
queryServiceMetrics(services.value);
}
function clickService(scope: any) {
const { dashboard } = getDashboard({
name: props.config.dashboardName,
layer: dashboardStore.layerId,
entity: EntityType[0].value,
});
if (!dashboard) {
ElMessage.error("No this dashboard");
return;
}
const path = `/dashboard/${dashboard.layer}/${dashboard.entity}/${scope.row.id}/${dashboard.name}`;
router.push(path);
}
async function queryServiceMetrics(currentServices: Service[]) {
if (!currentServices.length) {
return;
}
const metrics = props.config.metrics || [];
const types = props.config.metricTypes || [];
if (metrics.length && metrics[0] && types.length && types[0]) {
const params = await useQueryPodsMetrics(
currentServices,
{ ...props.config, metricConfig: metricConfig.value || [] },
EntityType[0].value
);
const json = await dashboardStore.fetchMetricValue(params);
if (json.errors) {
ElMessage.error(json.errors);
return;
}
const { data, names, metricConfigArr, metricTypesArr } = usePodsSource(
currentServices,
json,
{
...props.config,
metricConfig: metricConfig.value || [],
}, {});
const list = Object.values(map).flat(1);
const obj = {} as any;
for (const s of list) {
s.group = s.group || "";
if (!obj[s.group]) {
obj[s.group] = 1;
} else {
obj[s.group]++;
}
);
services.value = data;
colMetrics.value = names;
metricTypes.value = metricTypesArr;
metricConfig.value = metricConfigArr;
return;
}
services.value = currentServices;
}
function objectSpanMethod(param: any): any {
if (!props.config.showGroup) {
return;
}
if (param.columnIndex !== 0) {
return;
}
if (param.row.merge) {
return {
rowspan: 0,
colspan: 0,
};
}
return { rowspan: groups.value[param.row.group], colspan: 1 };
}
function changePage(pageIndex: number) {
const arr = sortServices.value.filter((d: Service, index: number) => {
if (index >= (pageIndex - 1) * pageSize && index < pageSize * pageIndex) {
return d;
groups.value[s.group] = obj[s.group];
}
});
setServices(arr);
}
function searchList() {
const searchServices = sortServices.value.filter((d: { label: string }) =>
d.label.includes(searchText.value)
);
const services = searchServices.filter(
(d: unknown, index: number) => index < pageSize
);
setServices(services);
}
watch(
() => [
...(props.config.metricTypes || []),
...(props.config.metrics || []),
...(props.config.metricConfig || []),
],
(data, old) => {
if (JSON.stringify(data) === JSON.stringify(old)) {
return;
}
metricConfig.value = props.config.metricConfig;
services.value = list;
queryServiceMetrics(services.value);
}
);
watch(
() => appStore.durationTime,
() => {
if (dashboardStore.entity === EntityType[1].value) {
queryServices();
function clickService(scope: any) {
const { dashboard } = getDashboard({
name: props.config.dashboardName,
layer: dashboardStore.layerId,
entity: EntityType[0].value,
});
if (!dashboard) {
ElMessage.error("No this dashboard");
return;
}
const path = `/dashboard/${dashboard.layer}/${dashboard.entity}/${scope.row.id}/${dashboard.name}`;
router.push(path);
}
);
async function queryServiceMetrics(currentServices: Service[]) {
if (!currentServices.length) {
return;
}
const metrics = props.config.metrics || [];
const types = props.config.metricTypes || [];
if (metrics.length && metrics[0] && types.length && types[0]) {
const params = await useQueryPodsMetrics(
currentServices,
{ ...props.config, metricConfig: metricConfig.value || [] },
EntityType[0].value,
);
const json = await dashboardStore.fetchMetricValue(params);
if (json.errors) {
ElMessage.error(json.errors);
return;
}
const { data, names, metricConfigArr, metricTypesArr } = usePodsSource(currentServices, json, {
...props.config,
metricConfig: metricConfig.value || [],
});
services.value = data;
colMetrics.value = names;
metricTypes.value = metricTypesArr;
metricConfig.value = metricConfigArr;
return;
}
services.value = currentServices;
}
function objectSpanMethod(param: any): any {
if (!props.config.showGroup) {
return;
}
if (param.columnIndex !== 0) {
return;
}
if (param.row.merge) {
return {
rowspan: 0,
colspan: 0,
};
}
return { rowspan: groups.value[param.row.group], colspan: 1 };
}
function changePage(pageIndex: number) {
const arr = sortServices.value.filter((d: Service, index: number) => {
if (index >= (pageIndex - 1) * pageSize && index < pageSize * pageIndex) {
return d;
}
});
setServices(arr);
}
function searchList() {
const searchServices = sortServices.value.filter((d: { label: string }) => d.label.includes(searchText.value));
const services = searchServices.filter((d: unknown, index: number) => index < pageSize);
setServices(services);
}
watch(
() => [...(props.config.metricTypes || []), ...(props.config.metrics || []), ...(props.config.metricConfig || [])],
(data, old) => {
if (JSON.stringify(data) === JSON.stringify(old)) {
return;
}
metricConfig.value = props.config.metricConfig;
queryServiceMetrics(services.value);
},
);
watch(
() => appStore.durationTime,
() => {
if (dashboardStore.entity === EntityType[1].value) {
queryServices();
}
},
);
</script>
<style lang="scss" scoped>
@import "./style.scss";
@import "./style.scss";
</style>

View File

@@ -26,103 +26,97 @@ limitations under the License. -->
<div class="row flex-h" v-for="key in dataKeys" :key="key">
<div class="name" :style="`width: ${nameWidth}`">{{ key }}</div>
<div class="value-col" v-if="config.showTableValues">
{{
config.metricTypes[0] === "readMetricsValue"
? data[key]
: data[key][data[key].length - 1 || 0]
}}
{{ config.metricTypes[0] === "readMetricsValue" ? data[key] : data[key][data[key].length - 1 || 0] }}
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<{ [key: string]: number[] }>,
default: () => ({}),
},
config: {
type: Object as PropType<{
showTableValues: boolean;
tableHeaderCol2: string;
tableHeaderCol1: string;
metricTypes: string[];
}>,
default: () => ({ showTableValues: true }),
},
});
import { computed } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<{ [key: string]: number[] }>,
default: () => ({}),
},
config: {
type: Object as PropType<{
showTableValues: boolean;
tableHeaderCol2: string;
tableHeaderCol1: string;
metricTypes: string[];
}>,
default: () => ({ showTableValues: true }),
},
});
const { t } = useI18n();
const nameWidth = computed(() =>
props.config.showTableValues ? "80%" : "100%"
);
const dataKeys = computed(() => {
if (props.config.metricTypes[0] === "readMetricsValue") {
const keys = Object.keys(props.data || {});
const { t } = useI18n();
const nameWidth = computed(() => (props.config.showTableValues ? "80%" : "100%"));
const dataKeys = computed(() => {
if (props.config.metricTypes[0] === "readMetricsValue") {
const keys = Object.keys(props.data || {});
return keys;
}
const keys = Object.keys(props.data || {}).filter(
(i: string) => Array.isArray(props.data[i]) && props.data[i].length,
);
return keys;
}
const keys = Object.keys(props.data || {}).filter(
(i: string) => Array.isArray(props.data[i]) && props.data[i].length
);
return keys;
});
});
</script>
<style lang="scss" scoped>
.chart-table {
height: 100%;
width: 100%;
overflow: auto;
.name {
padding-left: 15px;
}
.row {
border-left: 1px solid #ccc;
height: 20px;
.chart-table {
height: 100%;
width: 100%;
overflow: auto;
div {
overflow: hidden;
text-overflow: ellipsis;
border-right: 1px solid #ccc;
text-align: center;
.name {
padding-left: 15px;
}
.row {
border-left: 1px solid #ccc;
height: 20px;
line-height: 20px;
display: inline-block;
width: 100%;
div {
overflow: hidden;
text-overflow: ellipsis;
border-right: 1px solid #ccc;
text-align: center;
height: 20px;
line-height: 20px;
display: inline-block;
}
div:last-child {
border-bottom: 1px solid #ccc;
}
div:nth-last-child(2) {
border-bottom: 1px solid #ccc;
}
}
div:last-child {
border-bottom: 1px solid #ccc;
.dark {
color: #eee;
}
div:nth-last-child(2) {
border-bottom: 1px solid #ccc;
.row:first-child {
div {
border-top: 1px solid #ccc;
background: #eee;
}
}
.header {
color: #000;
font-weight: bold;
}
.value-col {
width: 50%;
}
}
.dark {
color: #eee;
}
.row:first-child {
div {
border-top: 1px solid #ccc;
background: #eee;
}
}
.header {
color: #000;
font-weight: bold;
}
.value-col {
width: 50%;
}
}
</style>

View File

@@ -32,22 +32,14 @@ limitations under the License. -->
<div class="operation" @click="handleClick(i.name)">
<span>{{ t("copy") }}</span>
</div>
<div
class="operation"
@click="viewTrace(i)"
v-show="refIdType === RefIdTypes[0].value"
>
<div class="operation" @click="viewTrace(i)" v-show="refIdType === RefIdTypes[0].value">
<span>{{ t("viewTrace") }}</span>
</div>
</el-popover>
</div>
<el-progress
:stroke-width="6"
:percentage="
isNaN(Number(i.value) / maxValue)
? 0
: (Number(i.value) / maxValue) * 100
"
:percentage="isNaN(Number(i.value) / maxValue) ? 0 : (Number(i.value) / maxValue) * 100"
:color="TextColors[config.color || 'purple']"
:show-text="false"
/>
@@ -66,149 +58,142 @@ limitations under the License. -->
<div class="center no-data" v-else>No Data</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { computed, ref } from "vue";
import copy from "@/utils/copy";
import { TextColors } from "@/views/dashboard/data";
import Trace from "@/views/dashboard/related/trace/Index.vue";
import { QueryOrders, Status, RefIdTypes } from "../data";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<{
[key: string]: { name: string; value: number; id: string }[];
}>,
default: () => ({}),
},
config: {
type: Object as PropType<{
color: string;
metrics: string[];
relatedTrace: any;
}>,
default: () => ({ color: "purple" }),
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
});
const { t } = useI18n();
const showTrace = ref<boolean>(false);
const traceOptions = ref<{ type: string; filters?: unknown }>({
type: "Trace",
});
const refIdType = computed(
() =>
(props.config.relatedTrace && props.config.relatedTrace.refIdType) ||
RefIdTypes[0].value
);
const key = computed(() => Object.keys(props.data)[0] || "");
const available = computed(
() =>
Array.isArray(props.data[key.value]) &&
props.data[key.value][0] &&
props.data[key.value][0].value
);
const maxValue = computed(() => {
if (!(props.data[key.value] && props.data[key.value].length)) {
return 0;
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { computed, ref } from "vue";
import copy from "@/utils/copy";
import { TextColors } from "@/views/dashboard/data";
import Trace from "@/views/dashboard/related/trace/Index.vue";
import { QueryOrders, Status, RefIdTypes } from "../data";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<{
[key: string]: { name: string; value: number; id: string }[];
}>,
default: () => ({}),
},
config: {
type: Object as PropType<{
color: string;
metrics: string[];
relatedTrace: any;
}>,
default: () => ({ color: "purple" }),
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
});
const { t } = useI18n();
const showTrace = ref<boolean>(false);
const traceOptions = ref<{ type: string; filters?: unknown }>({
type: "Trace",
});
const refIdType = computed(
() => (props.config.relatedTrace && props.config.relatedTrace.refIdType) || RefIdTypes[0].value,
);
const key = computed(() => Object.keys(props.data)[0] || "");
const available = computed(
() => Array.isArray(props.data[key.value]) && props.data[key.value][0] && props.data[key.value][0].value,
);
const maxValue = computed(() => {
if (!(props.data[key.value] && props.data[key.value].length)) {
return 0;
}
const temp: number[] = props.data[key.value].map((i: any) => i.value);
return Math.max.apply(null, temp);
});
function handleClick(i: string) {
copy(i);
}
function viewTrace(item: { name: string; id: string; value: unknown }) {
const filters = {
...item,
queryOrder: QueryOrders[1].value,
status: Status[2].value,
id: item.id || item.name,
metricValue: [{ label: props.config.metrics[0], data: item.value, value: item.name }],
};
traceOptions.value = {
...traceOptions.value,
filters,
};
showTrace.value = true;
}
const temp: number[] = props.data[key.value].map((i: any) => i.value);
return Math.max.apply(null, temp);
});
function handleClick(i: string) {
copy(i);
}
function viewTrace(item: { name: string; id: string; value: unknown }) {
const filters = {
...item,
queryOrder: QueryOrders[1].value,
status: Status[2].value,
id: item.id || item.name,
metricValue: [
{ label: props.config.metrics[0], data: item.value, value: item.name },
],
};
traceOptions.value = {
...traceOptions.value,
filters,
};
showTrace.value = true;
}
</script>
<style lang="scss" scoped>
.top-list {
height: 100%;
overflow: auto;
padding: 10px;
}
.tools {
justify-content: space-between;
}
.progress-bar {
font-size: 12px;
color: #333;
}
.chart-slow-i {
padding: 6px 0;
}
.chart-slow {
height: 100%;
}
.desc {
flex-grow: 2;
overflow: hidden;
text-overflow: ellipsis;
}
.calls {
font-size: 12px;
padding: 0 5px;
display: inline-block;
background-color: #40454e;
color: #eee;
border-radius: 4px;
}
.chart-slow-link {
padding: 4px 10px 7px 10px;
border-radius: 4px;
border: 1px solid #ddd;
color: #333;
background-color: #fff;
will-change: opacity, background-color;
transition: opacity 0.3s, background-color 0.3s;
}
.no-data {
height: 100%;
color: #666;
box-sizing: border-box;
display: -webkit-box;
-webkit-box-orient: horizontal;
-webkit-box-pack: center;
-webkit-box-align: center;
}
.operation-icon {
color: #333;
}
.operation {
padding: 5px 0;
color: #333;
cursor: pointer;
position: relative;
text-align: center;
font-size: 12px;
&:hover {
color: #409eff;
background-color: #eee;
.top-list {
height: 100%;
overflow: auto;
padding: 10px;
}
.tools {
justify-content: space-between;
}
.progress-bar {
font-size: 12px;
color: #333;
}
.chart-slow-i {
padding: 6px 0;
}
.chart-slow {
height: 100%;
}
.desc {
flex-grow: 2;
overflow: hidden;
text-overflow: ellipsis;
}
.calls {
font-size: 12px;
padding: 0 5px;
display: inline-block;
background-color: #40454e;
color: #eee;
border-radius: 4px;
}
.chart-slow-link {
padding: 4px 10px 7px 10px;
border-radius: 4px;
border: 1px solid #ddd;
color: #333;
background-color: #fff;
will-change: opacity, background-color;
transition: opacity 0.3s, background-color 0.3s;
}
.no-data {
height: 100%;
color: #666;
box-sizing: border-box;
display: -webkit-box;
-webkit-box-orient: horizontal;
-webkit-box-pack: center;
-webkit-box-align: center;
}
.operation-icon {
color: #333;
}
.operation {
padding: 5px 0;
color: #333;
cursor: pointer;
position: relative;
text-align: center;
font-size: 12px;
&:hover {
color: #409eff;
background-color: #eee;
}
}
}
</style>

View File

@@ -16,9 +16,7 @@ limitations under the License. -->
<template>
<el-table-column
v-for="(metric, index) in colMetrics"
:label="`${decodeURIComponent(
getLabel(metric, index)
)} ${decodeURIComponent(getUnit(index))}`"
:label="`${decodeURIComponent(getLabel(metric, index))} ${decodeURIComponent(getUnit(index))}`"
:key="metric + index"
min-width="150"
>
@@ -37,18 +35,11 @@ limitations under the License. -->
showlabels: false,
}"
/>
<span
class="item flex-h"
v-else-if="useListConfig(config, index).isAvg"
>
<span class="item flex-h" v-else-if="useListConfig(config, index).isAvg">
<el-popover placement="left" :width="400" trigger="click">
<template #reference>
<span class="trend">
<Icon
iconName="timeline"
size="middle"
style="color: #409eff"
/>
<Icon iconName="timeline" size="middle" style="color: #409eff" />
</span>
</template>
<div class="view-line">
@@ -75,99 +66,85 @@ limitations under the License. -->
/>
</span>
</span>
<Card
v-else
:data="{ [metric]: scope.row[metric] }"
:config="{ textAlign: 'left' }"
/>
<Card v-else :data="{ [metric]: scope.row[metric] }" :config="{ textAlign: 'left' }" />
</div>
</template>
</el-table-column>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { MetricConfigOpt } from "@/types/dashboard";
import { useListConfig } from "@/hooks/useListConfig";
import Line from "../Line.vue";
import Card from "../Card.vue";
import { MetricQueryTypes } from "@/hooks/data";
import type { PropType } from "vue";
import type { MetricConfigOpt } from "@/types/dashboard";
import { useListConfig } from "@/hooks/useListConfig";
import Line from "../Line.vue";
import Card from "../Card.vue";
import { MetricQueryTypes } from "@/hooks/data";
/*global defineProps */
const props = defineProps({
colMetrics: { type: Object },
config: {
type: Object as PropType<{
i: string;
metrics: string[];
metricTypes: string[];
metricConfig: MetricConfigOpt[];
}>,
default: () => ({}),
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
});
/*global defineProps */
const props = defineProps({
colMetrics: { type: Object },
config: {
type: Object as PropType<{
i: string;
metrics: string[];
metricTypes: string[];
metricConfig: MetricConfigOpt[];
}>,
default: () => ({}),
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
});
function getUnit(index: string) {
const i = Number(index);
const u =
props.config.metricConfig &&
props.config.metricConfig[i] &&
props.config.metricConfig[i].unit;
if (u) {
return `(${encodeURIComponent(u)})`;
}
return encodeURIComponent("");
}
function getLabel(metric: string, index: string) {
const i = Number(index);
const label =
props.config.metricConfig &&
props.config.metricConfig[i] &&
props.config.metricConfig[i].label;
if (label) {
if (
props.config.metricTypes[i] === MetricQueryTypes.ReadLabeledMetricsValues
) {
const name = (label || "")
.split(",")
.map((item: string) => item.replace(/^\s*|\s*$/g, ""))[
props.config.metricConfig[i].index || 0
];
return encodeURIComponent(name || "");
function getUnit(index: string) {
const i = Number(index);
const u = props.config.metricConfig && props.config.metricConfig[i] && props.config.metricConfig[i].unit;
if (u) {
return `(${encodeURIComponent(u)})`;
}
return encodeURIComponent(label);
return encodeURIComponent("");
}
function getLabel(metric: string, index: string) {
const i = Number(index);
const label = props.config.metricConfig && props.config.metricConfig[i] && props.config.metricConfig[i].label;
if (label) {
if (props.config.metricTypes[i] === MetricQueryTypes.ReadLabeledMetricsValues) {
const name = (label || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""))[
props.config.metricConfig[i].index || 0
];
return encodeURIComponent(name || "");
}
return encodeURIComponent(label);
}
return encodeURIComponent(metric);
}
return encodeURIComponent(metric);
}
</script>
<style lang="scss" scoped>
.chart {
height: 40px;
}
.chart {
height: 40px;
}
.view-line {
width: 380px;
height: 200px;
}
.view-line {
width: 380px;
height: 200px;
}
.item {
display: inline-block;
width: 100%;
height: 100%;
}
.item {
display: inline-block;
width: 100%;
height: 100%;
}
.trend {
width: 30px;
display: inline-block;
height: 100%;
cursor: pointer;
}
.trend {
width: 30px;
display: inline-block;
height: 100%;
cursor: pointer;
}
.value {
display: inline-block;
flex-grow: 2;
height: 100%;
width: calc(100% - 30px);
}
.value {
display: inline-block;
flex-grow: 2;
height: 100%;
width: calc(100% - 30px);
}
</style>

View File

@@ -81,115 +81,107 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { computed } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { LegendOptions } from "@/types/dashboard";
import useLegendProcess from "@/hooks/useLegendProcessor";
import { computed } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import type { LegendOptions } from "@/types/dashboard";
import useLegendProcess from "@/hooks/useLegendProcessor";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<{ [key: string]: number[] }>,
default: () => ({}),
},
config: {
type: Object as PropType<LegendOptions>,
default: () => ({}),
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
});
const { t } = useI18n();
const tableData: any = computed(() => {
const { aggregations } = useLegendProcess(props.config);
return aggregations(props.data, props.intervalTime).source;
});
const headerRow = computed(() => {
const { aggregations } = useLegendProcess(props.config);
return aggregations(props.data, props.intervalTime).headers;
});
const isRight = computed(() => useLegendProcess(props.config).isRight);
const width = computed(() =>
props.config.width
? props.config.width + "px"
: isRight.value
? "150px"
: "100%"
);
const colors = computed(() => {
const keys = Object.keys(props.data || {}).filter(
(i: any) => Array.isArray(props.data[i]) && props.data[i].length
);
const { chartColors } = useLegendProcess(props.config);
return chartColors(keys);
});
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<{ [key: string]: number[] }>,
default: () => ({}),
},
config: {
type: Object as PropType<LegendOptions>,
default: () => ({}),
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
});
const { t } = useI18n();
const tableData: any = computed(() => {
const { aggregations } = useLegendProcess(props.config);
return aggregations(props.data, props.intervalTime).source;
});
const headerRow = computed(() => {
const { aggregations } = useLegendProcess(props.config);
return aggregations(props.data, props.intervalTime).headers;
});
const isRight = computed(() => useLegendProcess(props.config).isRight);
const width = computed(() => (props.config.width ? props.config.width + "px" : isRight.value ? "150px" : "100%"));
const colors = computed(() => {
const keys = Object.keys(props.data || {}).filter((i: any) => Array.isArray(props.data[i]) && props.data[i].length);
const { chartColors } = useLegendProcess(props.config);
return chartColors(keys);
});
</script>
<style lang="scss" scoped>
table {
font-size: 12px;
white-space: nowrap;
margin: 0;
border: none;
border-collapse: separate;
border-spacing: 0;
table-layout: fixed;
}
table th {
padding: 5px;
}
table thead th {
position: sticky;
top: 0;
z-index: 1;
width: 25vw;
background: #fff;
text-align: left;
}
.name {
cursor: pointer;
}
table td {
padding: 5px;
}
table thead th:first-child {
position: sticky;
left: 0;
z-index: 2;
}
table tbody th {
font-weight: bold;
font-style: normal;
text-align: left;
background: #fff;
position: sticky;
left: 0;
z-index: 1;
}
[role="region"][aria-labelledby][tabindex] {
overflow: auto;
}
i {
font-style: normal;
}
.value {
span {
display: inline-block;
padding: 5px;
width: 80px;
table {
font-size: 12px;
white-space: nowrap;
margin: 0;
border: none;
border-collapse: separate;
border-spacing: 0;
table-layout: fixed;
}
}
.list {
height: 360px;
overflow: auto;
}
table th {
padding: 5px;
}
table thead th {
position: sticky;
top: 0;
z-index: 1;
width: 25vw;
background: #fff;
text-align: left;
}
.name {
cursor: pointer;
}
table td {
padding: 5px;
}
table thead th:first-child {
position: sticky;
left: 0;
z-index: 2;
}
table tbody th {
font-weight: bold;
font-style: normal;
text-align: left;
background: #fff;
position: sticky;
left: 0;
z-index: 1;
}
[role="region"][aria-labelledby][tabindex] {
overflow: auto;
}
i {
font-style: normal;
}
.value {
span {
display: inline-block;
padding: 5px;
width: 80px;
}
}
.list {
height: 360px;
overflow: auto;
}
</style>

View File

@@ -39,69 +39,66 @@ limitations under the License. -->
<div class="no-data-tips" v-else>{{ t("noWidget") }}</div>
</template>
<script lang="ts">
import { defineComponent, onBeforeUnmount } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { LayoutConfig } from "@/types/dashboard";
import controls from "../controls/index";
import { dragIgnoreFrom } from "../data";
import { defineComponent, onBeforeUnmount } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import type { LayoutConfig } from "@/types/dashboard";
import controls from "../controls/index";
import { dragIgnoreFrom } from "../data";
export default defineComponent({
name: "Layout",
components: { ...controls },
setup() {
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore();
export default defineComponent({
name: "Layout",
components: { ...controls },
setup() {
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore();
function clickGrid(item: LayoutConfig, event: Event) {
dashboardStore.activeGridItem(item.i);
dashboardStore.selectWidget(item);
if (
item.type === "Tab" &&
(event.target as HTMLDivElement)?.className !== "tab-layout"
) {
dashboardStore.setActiveTabIndex(0);
function clickGrid(item: LayoutConfig, event: Event) {
dashboardStore.activeGridItem(item.i);
dashboardStore.selectWidget(item);
if (item.type === "Tab" && (event.target as HTMLDivElement)?.className !== "tab-layout") {
dashboardStore.setActiveTabIndex(0);
}
}
}
onBeforeUnmount(() => {
dashboardStore.setLayout([]);
selectorStore.setCurrentService(null);
selectorStore.setCurrentPod(null);
dashboardStore.setEntity("");
dashboardStore.setConfigPanel(false);
});
return {
dashboardStore,
clickGrid,
t,
dragIgnoreFrom,
};
},
});
onBeforeUnmount(() => {
dashboardStore.setLayout([]);
selectorStore.setCurrentService(null);
selectorStore.setCurrentPod(null);
dashboardStore.setEntity("");
dashboardStore.setConfigPanel(false);
});
return {
dashboardStore,
clickGrid,
t,
dragIgnoreFrom,
};
},
});
</script>
<style lang="scss" scoped>
.vue-grid-layout {
background: #f7f9fa;
height: auto !important;
}
.vue-grid-layout {
background: #f7f9fa;
height: auto !important;
}
.vue-grid-item:not(.vue-grid-placeholder) {
background: #fff;
box-shadow: 0px 1px 4px 0px #00000029;
border-radius: 3px;
}
.vue-grid-item:not(.vue-grid-placeholder) {
background: #fff;
box-shadow: 0px 1px 4px 0px #00000029;
border-radius: 3px;
}
.vue-grid-item.active {
border: 1px solid #409eff;
}
.vue-grid-item.active {
border: 1px solid #409eff;
}
.no-data-tips {
width: 100%;
text-align: center;
font-size: 14px;
padding-top: 30px;
color: #888;
}
.no-data-tips {
width: 100%;
text-align: center;
font-size: 14px;
padding-top: 30px;
color: #888;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -42,9 +42,7 @@ limitations under the License. -->
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("monitorDuration") }}:</span>
<span class="g-sm-8 wba">
{{ details.fixedTriggerDuration / 60 }} min
</span>
<span class="g-sm-8 wba"> {{ details.fixedTriggerDuration / 60 }} min </span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("triggerType") }}:</span>
@@ -58,24 +56,23 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
import { EBPFTaskList } from "@/types/ebpf";
import type { PropType } from "vue";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
import type { EBPFTaskList } from "@/types/ebpf";
/*global defineProps */
defineProps({
details: {
type: Object as PropType<EBPFTaskList>,
default: () => ({}),
},
});
const { t } = useI18n();
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern);
/*global defineProps */
defineProps({
details: {
type: Object as PropType<EBPFTaskList>,
default: () => ({}),
},
});
const { t } = useI18n();
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") => dayjs(date).format(pattern);
</script>
<style lang="scss" scoped>
.item span {
height: 21px;
}
.item span {
height: 21px;
}
</style>

View File

@@ -22,8 +22,6 @@ export default (d3: any, graph: any, diff: number[]) =>
.on("zoom", (d: any) => {
graph.attr(
"transform",
`translate(${d.transform.x + diff[0]},${
d.transform.y + diff[1]
})scale(${d.transform.k})`
`translate(${d.transform.x + diff[0]},${d.transform.y + diff[1]})scale(${d.transform.k})`,
);
});

View File

@@ -23,77 +23,77 @@ limitations under the License. -->
></div>
</template>
<script lang="ts" setup>
import { onMounted, ref, onUnmounted, watch, toRaw } from "vue";
import { useDemandLogStore } from "@/store/modules/demand-log";
import { onMounted, ref, onUnmounted, watch, toRaw } from "vue";
import { useDemandLogStore } from "@/store/modules/demand-log";
/*global Nullable */
const demandLogStore = useDemandLogStore();
const monacoInstance = ref();
const logContent = ref<Nullable<HTMLDivElement>>(null);
/*global Nullable */
const demandLogStore = useDemandLogStore();
const monacoInstance = ref();
const logContent = ref<Nullable<HTMLDivElement>>(null);
onMounted(() => {
init();
});
async function init() {
const monaco = await import("monaco-editor");
setTimeout(() => {
monacoInstanceGen(monaco);
}, 500);
window.addEventListener("resize", () => {
onMounted(() => {
init();
});
async function init() {
const monaco = await import("monaco-editor");
setTimeout(() => {
monacoInstanceGen(monaco);
}, 500);
window.addEventListener("resize", () => {
editorLayout();
});
}
function monacoInstanceGen(monaco: any) {
monacoInstance.value = monaco.editor.create(logContent.value, {
value: "",
language: "text",
wordWrap: true,
minimap: { enabled: false },
readonly: true,
});
toRaw(monacoInstance.value).updateOptions({ readOnly: true });
editorLayout();
});
}
function monacoInstanceGen(monaco: any) {
monacoInstance.value = monaco.editor.create(logContent.value, {
value: "",
language: "text",
wordWrap: true,
minimap: { enabled: false },
readonly: true,
});
toRaw(monacoInstance.value).updateOptions({ readOnly: true });
editorLayout();
}
function editorLayout() {
if (!logContent.value) {
return;
}
const { width, height } = logContent.value.getBoundingClientRect();
toRaw(monacoInstance.value).layout({
height: height,
width: width,
});
}
onUnmounted(() => {
if (!toRaw(monacoInstance.value)) {
return;
function editorLayout() {
if (!logContent.value) {
return;
}
const { width, height } = logContent.value.getBoundingClientRect();
toRaw(monacoInstance.value).layout({
height: height,
width: width,
});
}
toRaw(monacoInstance.value).dispose();
monacoInstance.value = null;
demandLogStore.setLogs("");
});
watch(
() => demandLogStore.logs,
() => {
onUnmounted(() => {
if (!toRaw(monacoInstance.value)) {
return;
}
toRaw(monacoInstance.value).setValue(demandLogStore.logs);
if (!demandLogStore.logs) {
return;
}
setTimeout(() => {
toRaw(monacoInstance.value).revealPosition({
column: 1,
lineNumber: demandLogStore.total,
});
}, 1000);
}
);
toRaw(monacoInstance.value).dispose();
monacoInstance.value = null;
demandLogStore.setLogs("");
});
watch(
() => demandLogStore.logs,
() => {
if (!toRaw(monacoInstance.value)) {
return;
}
toRaw(monacoInstance.value).setValue(demandLogStore.logs);
if (!demandLogStore.logs) {
return;
}
setTimeout(() => {
toRaw(monacoInstance.value).revealPosition({
column: 1,
lineNumber: demandLogStore.total,
});
}, 1000);
},
);
</script>
<style lang="scss" scoped>
.log-content {
min-width: 600px;
min-height: 400px;
}
.log-content {
min-width: 600px;
min-height: 400px;
}
</style>

View File

@@ -72,11 +72,7 @@ limitations under the License. -->
<div class="mr-5">
<span class="mr-5 grey">{{ t("keywordsOfContent") }}:</span>
<span class="log-tags">
<span
class="selected"
v-for="(item, index) in keywordsOfContent"
:key="`keywordsOfContent${index}`"
>
<span class="selected" v-for="(item, index) in keywordsOfContent" :key="`keywordsOfContent${index}`">
<span>{{ item }}</span>
<span class="remove-icon" @click="removeContent(index)">×</span>
</span>
@@ -98,9 +94,7 @@ limitations under the License. -->
:key="`excludingKeywordsOfContent${index}`"
>
<span>{{ item }}</span>
<span class="remove-icon" @click="removeExcludeContent(index)">
×
</span>
<span class="remove-icon" @click="removeExcludeContent(index)"> × </span>
</span>
</span>
<el-input
@@ -113,320 +107,296 @@ limitations under the License. -->
</div>
</div>
<div class="flex-h row btn-row">
<el-button
class="search-btn mt-10"
size="small"
type="primary"
@click="runInterval"
:disabled="disabled"
>
<Icon
size="middle"
iconName="retry"
:loading="!!intervalFn"
class="mr-5"
/>
<el-button class="search-btn mt-10" size="small" type="primary" @click="runInterval" :disabled="disabled">
<Icon size="middle" iconName="retry" :loading="!!intervalFn" class="mr-5" />
{{ intervalFn ? t("pause") : t("start") }}
</el-button>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, watch, onMounted, onUnmounted } from "vue";
import { useI18n } from "vue-i18n";
import { useDemandLogStore } from "@/store/modules/demand-log";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
import { EntityType } from "../../data";
import { TimeRanges, IntervalOpts } from "./data";
import getLocalTime from "@/utils/localtime";
import dateFormatStep from "@/utils/dateFormat";
import { ref, reactive, watch, onMounted, onUnmounted } from "vue";
import { useI18n } from "vue-i18n";
import { useDemandLogStore } from "@/store/modules/demand-log";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
import { EntityType } from "../../data";
import { TimeRanges, IntervalOpts } from "./data";
import getLocalTime from "@/utils/localtime";
import dateFormatStep from "@/utils/dateFormat";
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const demandLogStore = useDemandLogStore();
const keywordsOfContent = ref<string[]>([]);
const excludingKeywordsOfContent = ref<string[]>([]);
const contentStr = ref<string>("");
const excludingContentStr = ref<string>("");
const state = reactive<any>({
instance: { value: "", label: "" },
container: { value: "", label: "" },
duration: { label: "From 30 minutes ago", value: 1800 },
interval: { label: "30 seconds", value: 30 },
});
const disabled = ref<boolean>(true);
/*global Nullable */
const intervalFn = ref<Nullable<any>>(null);
onMounted(() => {
fetchSelectors();
});
async function fetchSelectors() {
if (dashboardStore.entity !== EntityType[3].value) {
await getInstances();
}
getContainers();
if (intervalFn.value) {
clearTimer();
}
}
async function getContainers() {
if (
!(
state.instance.id ||
(selectorStore.currentPod && selectorStore.currentPod.id)
)
) {
return;
}
const resp = await demandLogStore.getContainers(
state.instance.id || selectorStore.currentPod.id
);
if (resp.errors) {
disabled.value = true;
ElMessage.error(resp.errors);
return;
}
if (resp.data.containers.errorReason) {
disabled.value = true;
ElMessage.warning(resp.data.containers.errorReason);
return;
}
if (demandLogStore.containers.length) {
state.container = demandLogStore.containers[0];
disabled.value = false;
}
}
async function getInstances() {
const resp = await demandLogStore.getInstances();
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.instance = demandLogStore.instances[0];
}
function runInterval() {
if (intervalFn.value) {
clearTimer();
return;
}
searchLogs();
if (state.interval.value === 0) {
return;
}
intervalFn.value = setInterval(searchLogs, state.interval.value * 1000);
setTimeout(() => {
clearTimer();
}, state.duration.value * 1000);
}
function searchLogs() {
let instance = "";
if (dashboardStore.entity === EntityType[3].value) {
instance = selectorStore.currentPod.id;
}
const serviceInstanceId =
instance || (state.instance && state.instance.id) || "";
demandLogStore.setLogCondition({
serviceInstanceId,
container: state.container.value,
duration: rangeTime(),
keywordsOfContent: keywordsOfContent.value.length
? keywordsOfContent.value
: undefined,
excludingKeywordsOfContent: excludingKeywordsOfContent.value.length
? excludingKeywordsOfContent.value
: undefined,
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const demandLogStore = useDemandLogStore();
const keywordsOfContent = ref<string[]>([]);
const excludingKeywordsOfContent = ref<string[]>([]);
const contentStr = ref<string>("");
const excludingContentStr = ref<string>("");
const state = reactive<any>({
instance: { value: "", label: "" },
container: { value: "", label: "" },
duration: { label: "From 30 minutes ago", value: 1800 },
interval: { label: "30 seconds", value: 30 },
});
if (!serviceInstanceId) {
return;
}
queryLogs();
}
const disabled = ref<boolean>(true);
/*global Nullable */
const intervalFn = ref<Nullable<any>>(null);
function rangeTime() {
{
const times = {
start: getLocalTime(
appStore.utc,
new Date(new Date().getTime() - state.duration.value * 1000)
),
end: getLocalTime(appStore.utc, new Date()),
step: "SECOND",
};
return {
start: dateFormatStep(times.start, times.step, false),
end: dateFormatStep(times.end, times.step, false),
step: times.step,
};
}
}
onMounted(() => {
fetchSelectors();
});
async function queryLogs() {
const res = await demandLogStore.getDemandLogs();
if (res && res.errors) {
ElMessage.error(res.errors);
}
}
function changeField(type: string, opt: any) {
clearTimer();
// if (["limit"].includes(type)) {
// state[type] = opt;
// return;
// }
state[type] = opt[0];
if (type === "instance") {
async function fetchSelectors() {
if (dashboardStore.entity !== EntityType[3].value) {
await getInstances();
}
getContainers();
if (intervalFn.value) {
clearTimer();
}
}
}
function removeContent(index: number) {
const keywordsOfContentList = keywordsOfContent.value || [];
keywordsOfContentList.splice(index, 1);
demandLogStore.setLogCondition({
keywordsOfContent: keywordsOfContentList,
});
contentStr.value = "";
clearTimer();
}
function addLabels(type: string) {
if (type === "keywordsOfContent" && !contentStr.value) {
return;
async function getContainers() {
if (!(state.instance.id || (selectorStore.currentPod && selectorStore.currentPod.id))) {
return;
}
const resp = await demandLogStore.getContainers(state.instance.id || selectorStore.currentPod.id);
if (resp.errors) {
disabled.value = true;
ElMessage.error(resp.errors);
return;
}
if (resp.data.containers.errorReason) {
disabled.value = true;
ElMessage.warning(resp.data.containers.errorReason);
return;
}
if (demandLogStore.containers.length) {
state.container = demandLogStore.containers[0];
disabled.value = false;
}
}
if (type === "excludingKeywordsOfContent" && !excludingContentStr.value) {
return;
async function getInstances() {
const resp = await demandLogStore.getInstances();
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.instance = demandLogStore.instances[0];
}
if (type === "keywordsOfContent") {
keywordsOfContent.value.push(contentStr.value);
function runInterval() {
if (intervalFn.value) {
clearTimer();
return;
}
searchLogs();
if (state.interval.value === 0) {
return;
}
intervalFn.value = setInterval(searchLogs, state.interval.value * 1000);
setTimeout(() => {
clearTimer();
}, state.duration.value * 1000);
}
function searchLogs() {
let instance = "";
if (dashboardStore.entity === EntityType[3].value) {
instance = selectorStore.currentPod.id;
}
const serviceInstanceId = instance || (state.instance && state.instance.id) || "";
demandLogStore.setLogCondition({
[type]: keywordsOfContent.value,
serviceInstanceId,
container: state.container.value,
duration: rangeTime(),
keywordsOfContent: keywordsOfContent.value.length ? keywordsOfContent.value : undefined,
excludingKeywordsOfContent: excludingKeywordsOfContent.value.length
? excludingKeywordsOfContent.value
: undefined,
});
if (!serviceInstanceId) {
return;
}
queryLogs();
}
function rangeTime() {
{
const times = {
start: getLocalTime(appStore.utc, new Date(new Date().getTime() - state.duration.value * 1000)),
end: getLocalTime(appStore.utc, new Date()),
step: "SECOND",
};
return {
start: dateFormatStep(times.start, times.step, false),
end: dateFormatStep(times.end, times.step, false),
step: times.step,
};
}
}
async function queryLogs() {
const res = await demandLogStore.getDemandLogs();
if (res && res.errors) {
ElMessage.error(res.errors);
}
}
function changeField(type: string, opt: any) {
clearTimer();
// if (["limit"].includes(type)) {
// state[type] = opt;
// return;
// }
state[type] = opt[0];
if (type === "instance") {
getContainers();
}
}
function removeContent(index: number) {
const keywordsOfContentList = keywordsOfContent.value || [];
keywordsOfContentList.splice(index, 1);
demandLogStore.setLogCondition({
keywordsOfContent: keywordsOfContentList,
});
contentStr.value = "";
} else if (type === "excludingKeywordsOfContent") {
excludingKeywordsOfContent.value.push(excludingContentStr.value);
clearTimer();
}
function addLabels(type: string) {
if (type === "keywordsOfContent" && !contentStr.value) {
return;
}
if (type === "excludingKeywordsOfContent" && !excludingContentStr.value) {
return;
}
if (type === "keywordsOfContent") {
keywordsOfContent.value.push(contentStr.value);
demandLogStore.setLogCondition({
[type]: keywordsOfContent.value,
});
contentStr.value = "";
} else if (type === "excludingKeywordsOfContent") {
excludingKeywordsOfContent.value.push(excludingContentStr.value);
demandLogStore.setLogCondition({
[type]: excludingKeywordsOfContent.value,
});
excludingContentStr.value = "";
}
clearTimer();
}
function removeExcludeContent(index: number) {
excludingKeywordsOfContent.value.splice(index, 1);
demandLogStore.setLogCondition({
[type]: excludingKeywordsOfContent.value,
excludingKeywordsOfContent: excludingKeywordsOfContent.value,
});
excludingContentStr.value = "";
clearTimer();
}
clearTimer();
}
function removeExcludeContent(index: number) {
excludingKeywordsOfContent.value.splice(index, 1);
demandLogStore.setLogCondition({
excludingKeywordsOfContent: excludingKeywordsOfContent.value,
function clearTimer() {
if (!intervalFn.value) {
return;
}
clearInterval(intervalFn.value);
intervalFn.value = null;
}
onUnmounted(() => {
clearTimer();
});
excludingContentStr.value = "";
clearTimer();
}
function clearTimer() {
if (!intervalFn.value) {
return;
}
clearInterval(intervalFn.value);
intervalFn.value = null;
}
onUnmounted(() => {
clearTimer();
});
watch(
() => selectorStore.currentService,
() => {
if (dashboardStore.entity === EntityType[0].value) {
fetchSelectors();
demandLogStore.setLogs("");
}
}
);
watch(
() => [selectorStore.currentPod],
() => {
if (dashboardStore.entity === EntityType[3].value) {
fetchSelectors();
demandLogStore.setLogs("");
}
}
);
watch(
() => selectorStore.currentService,
() => {
if (dashboardStore.entity === EntityType[0].value) {
fetchSelectors();
demandLogStore.setLogs("");
}
},
);
watch(
() => [selectorStore.currentPod],
() => {
if (dashboardStore.entity === EntityType[3].value) {
fetchSelectors();
demandLogStore.setLogs("");
}
},
);
</script>
<style lang="scss" scoped>
.inputs {
width: 120px;
}
.inputs {
width: 120px;
}
.row {
margin-bottom: 5px;
position: relative;
flex-wrap: wrap;
}
.row {
margin-bottom: 5px;
position: relative;
flex-wrap: wrap;
}
.inputs-max {
width: 270px;
}
.inputs-max {
width: 270px;
}
.traceId {
margin-top: 2px;
}
.traceId {
margin-top: 2px;
}
.search-btn {
cursor: pointer;
width: 120px;
}
.search-btn {
cursor: pointer;
width: 120px;
}
.tips {
color: #888;
}
.tips {
color: #888;
}
.log-tag {
width: 30%;
border-style: unset;
outline: 0;
border: 1px solid #ccc;
height: 30px;
padding: 0 5px;
}
.log-tag {
width: 30%;
border-style: unset;
outline: 0;
border: 1px solid #ccc;
height: 30px;
padding: 0 5px;
}
.log-tags {
padding: 1px 5px 0 0;
border-radius: 3px;
height: 24px;
display: inline-block;
vertical-align: top;
}
.log-tags {
padding: 1px 5px 0 0;
border-radius: 3px;
height: 24px;
display: inline-block;
vertical-align: top;
}
.selected {
display: inline-block;
padding: 0 3px;
border-radius: 3px;
overflow: hidden;
color: #3d444f;
border: 1px dashed #aaa;
font-size: 12px;
margin: 0 2px;
}
.selected {
display: inline-block;
padding: 0 3px;
border-radius: 3px;
overflow: hidden;
color: #3d444f;
border: 1px dashed #aaa;
font-size: 12px;
margin: 0 2px;
}
.remove-icon {
display: inline-block;
margin-left: 3px;
cursor: pointer;
}
.remove-icon {
display: inline-block;
margin-left: 3px;
cursor: pointer;
}
.selectors {
width: 250px;
}
.selectors {
width: 250px;
}
.duration-range {
width: 210px;
}
.duration-range {
width: 210px;
}
.btn-row {
justify-content: flex-end;
}
.btn-row {
justify-content: flex-end;
}
.help {
color: #999;
cursor: pointer;
}
.help {
color: #999;
cursor: pointer;
}
</style>

View File

@@ -26,33 +26,33 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import TaskList from "./components/TaskList.vue";
import EBPFSchedules from "./components/EBPFSchedules.vue";
import EBPFStack from "./components/EBPFStack.vue";
import TaskList from "./components/TaskList.vue";
import EBPFSchedules from "./components/EBPFSchedules.vue";
import EBPFStack from "./components/EBPFStack.vue";
</script>
<style lang="scss" scoped>
.content {
height: calc(100% - 30px);
width: 100%;
}
.content {
height: calc(100% - 30px);
width: 100%;
}
.vis-graph {
height: 100%;
flex-grow: 2;
min-width: 700px;
overflow: auto;
}
.vis-graph {
height: 100%;
flex-grow: 2;
min-width: 700px;
overflow: auto;
}
.item {
width: 100%;
overflow: auto;
height: calc(100% - 100px);
padding-bottom: 10px;
}
.item {
width: 100%;
overflow: auto;
height: calc(100% - 100px);
padding-bottom: 10px;
}
.schedules {
height: 90px;
border-bottom: 1px solid #ccc;
padding-right: 10px;
}
.schedules {
height: 90px;
border-bottom: 1px solid #ccc;
padding-right: 10px;
}
</style>

View File

@@ -19,95 +19,89 @@ limitations under the License. -->
{{ t("newTask") }}
</el-button>
</div>
<el-dialog
v-model="newTask"
:destroy-on-close="true"
fullscreen
@closed="newTask = false"
>
<el-dialog v-model="newTask" :destroy-on-close="true" fullscreen @closed="newTask = false">
<NewTask @close="newTask = false" />
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useEbpfStore } from "@/store/modules/ebpf";
import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
import NewTask from "./components/NewTask.vue";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { EntityType } from "../../data";
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useEbpfStore } from "@/store/modules/ebpf";
import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
import NewTask from "./components/NewTask.vue";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { EntityType } from "../../data";
/*global defineProps */
const props = defineProps({
needQuery: { type: Boolean, default: true },
});
const ebpfStore = useEbpfStore();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const { t } = useI18n();
const newTask = ref<boolean>(false);
if (props.needQuery) {
searchTasks();
}
async function searchTasks() {
const serviceId =
(selectorStore.currentService && selectorStore.currentService.id) || "";
const res = await ebpfStore.getTaskList({
serviceId,
targets: ["ON_CPU", "OFF_CPU"],
/*global defineProps */
const props = defineProps({
needQuery: { type: Boolean, default: true },
});
const ebpfStore = useEbpfStore();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const { t } = useI18n();
const newTask = ref<boolean>(false);
if (res.errors) {
ElMessage.error(res.errors);
}
}
async function createTask() {
if (!selectorStore.currentService) {
return;
}
newTask.value = true;
ebpfStore.getCreateTaskData(selectorStore.currentService.id);
}
watch(
() => selectorStore.currentService,
() => {
if (props.needQuery) {
searchTasks();
}
);
watch(
() => appStore.durationTime,
() => {
if (dashboardStore.entity === EntityType[1].value) {
searchTasks();
async function searchTasks() {
const serviceId = (selectorStore.currentService && selectorStore.currentService.id) || "";
const res = await ebpfStore.getTaskList({
serviceId,
targets: ["ON_CPU", "OFF_CPU"],
});
if (res.errors) {
ElMessage.error(res.errors);
}
}
);
async function createTask() {
if (!selectorStore.currentService) {
return;
}
newTask.value = true;
ebpfStore.getCreateTaskData(selectorStore.currentService.id);
}
watch(
() => selectorStore.currentService,
() => {
searchTasks();
},
);
watch(
() => appStore.durationTime,
() => {
if (dashboardStore.entity === EntityType[1].value) {
searchTasks();
}
},
);
</script>
<style lang="scss" scoped>
.header {
padding: 5px 20px 5px 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
justify-content: space-between;
}
.header {
padding: 5px 20px 5px 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
justify-content: space-between;
}
.name {
width: 270px;
}
.name {
width: 270px;
}
.new-btn {
float: right;
}
.new-btn {
float: right;
}
.title {
font-weight: bold;
line-height: 24px;
}
.title {
font-weight: bold;
line-height: 24px;
}
</style>

View File

@@ -40,12 +40,7 @@ limitations under the License. -->
@change="changeAggregateType"
class="selector mr-10"
/>
<el-popover
placement="bottom"
:width="680"
trigger="click"
:persistent="false"
>
<el-popover placement="bottom" :width="680" trigger="click" :persistent="false">
<template #reference>
<el-button type="primary" size="small">
{{ t("processSelect") }}
@@ -64,11 +59,7 @@ limitations under the License. -->
</el-button>
</template>
</el-input>
<el-table
:data="currentProcesses"
ref="multipleTableRef"
@selection-change="handleSelectionChange"
>
<el-table :data="currentProcesses" ref="multipleTableRef" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column
v-for="(h, index) of TableHeader"
@@ -102,55 +93,52 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { Option } from "@/types/app";
import { TableHeader, AggregateTypes } from "./data";
import { useEbpfStore } from "@/store/modules/ebpf";
import { EBPFProfilingSchedule, Process } from "@/types/ebpf";
import { ElMessage, ElTable } from "element-plus";
import { dateFormat } from "@/utils/dateFormat";
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import type { Option } from "@/types/app";
import { TableHeader, AggregateTypes } from "./data";
import { useEbpfStore } from "@/store/modules/ebpf";
import type { EBPFProfilingSchedule, Process } from "@/types/ebpf";
import { ElMessage, ElTable } from "element-plus";
import { dateFormat } from "@/utils/dateFormat";
const { t } = useI18n();
const ebpfStore = useEbpfStore();
const pageSize = 5;
const multipleTableRef = ref<InstanceType<typeof ElTable>>();
const selectedProcesses = ref<string[]>([]);
const labels = ref<Option[]>([{ label: "All", value: "0" }]);
const processes = ref<Process[]>([]);
const currentProcesses = ref<Process[]>([]);
const selectedLabels = ref<string[]>(["0"]);
const searchText = ref<string>("");
const aggregateType = ref<string>(AggregateTypes[0].value);
const duration = ref<string[]>([]);
const attributes = (attr: { name: string; value: string }[]) => {
return attr
.map((d: { name: string; value: string }) => `${d.name}=${d.value}`)
.join("; ");
};
const { t } = useI18n();
const ebpfStore = useEbpfStore();
const pageSize = 5;
const multipleTableRef = ref<InstanceType<typeof ElTable>>();
const selectedProcesses = ref<string[]>([]);
const labels = ref<Option[]>([{ label: "All", value: "0" }]);
const processes = ref<Process[]>([]);
const currentProcesses = ref<Process[]>([]);
const selectedLabels = ref<string[]>(["0"]);
const searchText = ref<string>("");
const aggregateType = ref<string>(AggregateTypes[0].value);
const duration = ref<string[]>([]);
const attributes = (attr: { name: string; value: string }[]) => {
return attr.map((d: { name: string; value: string }) => `${d.name}=${d.value}`).join("; ");
};
function changeLabels(opt: any[]) {
const arr = opt.map((d) => d.value);
selectedLabels.value = arr;
}
function changeAggregateType(opt: any[]) {
aggregateType.value = opt[0].value;
ebpfStore.setAnalyzeTrees([]);
}
const handleSelectionChange = (arr: Process[]) => {
selectedProcesses.value = arr.map((d: Process) => d.id);
};
async function analyzeEBPF() {
let arr: string[] = selectedLabels.value;
if (selectedLabels.value.includes("0")) {
arr = labels.value.map((d: Option) => d.value);
function changeLabels(opt: any[]) {
const arr = opt.map((d) => d.value);
selectedLabels.value = arr;
}
const ranges: { start: number; end: number }[] = [];
const scheduleIdList = ebpfStore.eBPFSchedules.flatMap(
(d: EBPFProfilingSchedule) => {
function changeAggregateType(opt: any[]) {
aggregateType.value = opt[0].value;
ebpfStore.setAnalyzeTrees([]);
}
const handleSelectionChange = (arr: Process[]) => {
selectedProcesses.value = arr.map((d: Process) => d.id);
};
async function analyzeEBPF() {
let arr: string[] = selectedLabels.value;
if (selectedLabels.value.includes("0")) {
arr = labels.value.map((d: Option) => d.value);
}
const ranges: { start: number; end: number }[] = [];
const scheduleIdList = ebpfStore.eBPFSchedules.flatMap((d: EBPFProfilingSchedule) => {
const l = d.process.labels.find((d: string) => arr.includes(d));
const i = selectedProcesses.value.includes(d.process.id);
if (l || i) {
@@ -160,124 +148,112 @@ async function analyzeEBPF() {
});
return d.scheduleId;
}
}
);
let timeRanges: { start: number; end: number }[] = [];
for (const r of ranges) {
if (timeRanges.length) {
for (const t of timeRanges) {
if (r.start > t.start && r.start < t.end) {
if (r.end > t.end) {
t.end = r.end;
});
let timeRanges: { start: number; end: number }[] = [];
for (const r of ranges) {
if (timeRanges.length) {
for (const t of timeRanges) {
if (r.start > t.start && r.start < t.end) {
if (r.end > t.end) {
t.end = r.end;
}
}
}
} else {
timeRanges.push(r);
}
}
const res = await ebpfStore.getEBPFAnalyze({
scheduleIdList,
timeRanges,
aggregateType: aggregateType.value,
});
if (res.data && res.data.errors) {
ElMessage.error(res.data.errors);
return;
}
}
function getSchedules() {
labels.value = [{ label: "All", value: "0" }];
selectedLabels.value = ["0"];
processes.value = [];
const ranges = ebpfStore.eBPFSchedules.map((d: EBPFProfilingSchedule) => {
for (const l of d.process.labels) {
labels.value.push({ label: l, value: l });
}
processes.value.push(d.process);
return [d.startTime / 10000, d.endTime / 10000];
});
if (ranges.length) {
const arr = ranges.flat(1);
const min = Math.min(...arr);
const max = Math.max(...arr);
duration.value = [dateFormat(min * 10000), dateFormat(max * 10000)];
} else {
timeRanges.push(r);
duration.value = [];
}
searchProcesses(0);
analyzeEBPF();
}
const res = await ebpfStore.getEBPFAnalyze({
scheduleIdList,
timeRanges,
aggregateType: aggregateType.value,
});
if (res.data && res.data.errors) {
ElMessage.error(res.data.errors);
return;
}
}
function getSchedules() {
labels.value = [{ label: "All", value: "0" }];
selectedLabels.value = ["0"];
processes.value = [];
const ranges = ebpfStore.eBPFSchedules.map((d: EBPFProfilingSchedule) => {
for (const l of d.process.labels) {
labels.value.push({ label: l, value: l });
function changePage(pageIndex: number) {
searchProcesses(pageIndex);
}
function searchProcesses(pageIndex: number) {
const arr = processes.value.filter(
(d: { name: string; instanceName: string; attributes: { name: string; value: string }[] }) =>
d.name.includes(searchText.value) ||
d.instanceName.includes(searchText.value) ||
searchAttribute(d.attributes, searchText.value),
);
currentProcesses.value = arr.filter(
(d, index: number) => (pageIndex - 1 || 0) * pageSize <= index && pageSize * (pageIndex || 1) > index,
);
}
function searchAttribute(attributes: { name: string; value: string }[], text: string) {
const item = attributes.find((d: { name: string; value: string }) => d.name === "command_line");
if (!item) {
return false;
}
processes.value.push(d.process);
return [d.startTime / 10000, d.endTime / 10000];
});
if (ranges.length) {
const arr = ranges.flat(1);
const min = Math.min(...arr);
const max = Math.max(...arr);
duration.value = [dateFormat(min * 10000), dateFormat(max * 10000)];
} else {
duration.value = [];
return item.value.includes(text);
}
searchProcesses(0);
analyzeEBPF();
}
function changePage(pageIndex: number) {
searchProcesses(pageIndex);
}
function searchProcesses(pageIndex: number) {
const arr = processes.value.filter(
(d: {
name: string;
instanceName: string;
attributes: { name: string; value: string }[];
}) =>
d.name.includes(searchText.value) ||
d.instanceName.includes(searchText.value) ||
searchAttribute(d.attributes, searchText.value)
watch(
() => ebpfStore.eBPFSchedules,
() => {
getSchedules();
},
);
currentProcesses.value = arr.filter(
(d, index: number) =>
(pageIndex - 1 || 0) * pageSize <= index &&
pageSize * (pageIndex || 1) > index
);
}
function searchAttribute(
attributes: { name: string; value: string }[],
text: string
) {
const item = attributes.find(
(d: { name: string; value: string }) => d.name === "command_line"
);
if (!item) {
return false;
}
return item.value.includes(text);
}
watch(
() => ebpfStore.eBPFSchedules,
() => {
getSchedules();
}
);
</script>
<style lang="scss" scoped>
.filters {
margin: 5px 0;
width: 100%;
min-width: 560px;
}
.filters {
margin: 5px 0;
width: 100%;
min-width: 560px;
}
.inputs {
width: 400px;
}
.inputs {
width: 400px;
}
.input-with-search {
width: 650px;
margin-bottom: 5px;
}
.input-with-search {
width: 650px;
margin-bottom: 5px;
}
.pagination {
margin-top: 10px;
}
.pagination {
margin-top: 10px;
}
.selector {
width: 120px;
}
.selector {
width: 120px;
}
.duration {
line-height: 30px;
}
.duration {
line-height: 30px;
}
</style>

View File

@@ -18,195 +18,191 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import * as d3 from "d3";
import d3tip from "d3-tip";
import { flamegraph } from "d3-flame-graph";
import { useEbpfStore } from "@/store/modules/ebpf";
import { StackElement } from "@/types/ebpf";
import { AggregateTypes } from "./data";
import "d3-flame-graph/dist/d3-flamegraph.css";
import { ref, watch } from "vue";
import * as d3 from "d3";
import d3tip from "d3-tip";
import { flamegraph } from "d3-flame-graph";
import { useEbpfStore } from "@/store/modules/ebpf";
import type { StackElement } from "@/types/ebpf";
import { AggregateTypes } from "./data";
import "d3-flame-graph/dist/d3-flamegraph.css";
/*global Nullable*/
const ebpfStore = useEbpfStore();
const stackTree = ref<Nullable<StackElement>>(null);
const selectStack = ref<Nullable<StackElement>>(null);
const graph = ref<Nullable<HTMLDivElement>>(null);
const flameChart = ref<any>(null);
const min = ref<number>(1);
const max = ref<number>(1);
/*global Nullable*/
const ebpfStore = useEbpfStore();
const stackTree = ref<Nullable<StackElement>>(null);
const selectStack = ref<Nullable<StackElement>>(null);
const graph = ref<Nullable<HTMLDivElement>>(null);
const flameChart = ref<any>(null);
const min = ref<number>(1);
const max = ref<number>(1);
function drawGraph() {
if (flameChart.value) {
flameChart.value.destroy();
}
if (!ebpfStore.analyzeTrees.length) {
return (stackTree.value = null);
}
const root: StackElement = {
parentId: "0",
originId: "1",
name: "Virtual Root",
children: [],
value: 0,
id: "1",
symbol: "Virtual Root",
dumpCount: 0,
stackType: "",
rateOfRoot: "",
rateOfParent: "",
};
countRange();
for (const tree of ebpfStore.analyzeTrees) {
const ele = processTree(tree.elements);
root.children && root.children.push(ele);
}
const param = (root.children || []).reduce(
(prev: number[], curr: StackElement) => {
prev[0] += curr.value;
prev[1] += curr.dumpCount;
return prev;
},
[0, 0]
);
root.value = param[0];
root.dumpCount = param[1];
stackTree.value = root;
const width = (graph.value && graph.value.getBoundingClientRect().width) || 0;
const w = width < 800 ? 802 : width;
flameChart.value = flamegraph()
.width(w - 15)
.cellHeight(18)
.transitionDuration(750)
.minFrameSize(1)
.transitionEase(d3.easeCubic as any)
.sort(true)
.title("")
.selfValue(false)
.inverted(true)
.onClick((d: { data: StackElement }) => {
selectStack.value = d.data;
})
.setColorMapper((d, originalColor) =>
d.highlight ? "#6aff8f" : originalColor
function drawGraph() {
if (flameChart.value) {
flameChart.value.destroy();
}
if (!ebpfStore.analyzeTrees.length) {
return (stackTree.value = null);
}
const root: StackElement = {
parentId: "0",
originId: "1",
name: "Virtual Root",
children: [],
value: 0,
id: "1",
symbol: "Virtual Root",
dumpCount: 0,
stackType: "",
rateOfRoot: "",
rateOfParent: "",
};
countRange();
for (const tree of ebpfStore.analyzeTrees) {
const ele = processTree(tree.elements);
root.children && root.children.push(ele);
}
const param = (root.children || []).reduce(
(prev: number[], curr: StackElement) => {
prev[0] += curr.value;
prev[1] += curr.dumpCount;
return prev;
},
[0, 0],
);
const tip = (d3tip as any)()
.attr("class", "d3-tip")
.direction("w")
.html((d: { data: StackElement } & { parent: { data: StackElement } }) => {
const name = d.data.name.replace("<", "&lt;").replace(">", "&gt;");
const valStr =
ebpfStore.aggregateType === AggregateTypes[0].value
? `<div class="mb-5">Dump Count: ${d.data.dumpCount}</div>`
: `<div class="mb-5">Duration: ${d.data.dumpCount} ns</div>`;
const rateOfParent =
(d.parent &&
`<div class="mb-5">Percentage Of Selected: ${
(
(d.data.dumpCount /
((selectStack.value && selectStack.value.dumpCount) ||
root.dumpCount)) *
100
).toFixed(3) + "%"
}</div>`) ||
"";
const rateOfRoot = `<div class="mb-5">Percentage Of Root: ${
((d.data.dumpCount / root.dumpCount) * 100).toFixed(3) + "%"
}</div>`;
return `<div class="mb-5 name">Symbol: ${name}</div>${valStr}${rateOfParent}${rateOfRoot}`;
})
.style("max-width", "500px");
flameChart.value.tooltip(tip);
d3.select("#graph-stack").datum(stackTree.value).call(flameChart.value);
}
function countRange() {
const list = [];
for (const tree of ebpfStore.analyzeTrees) {
for (const ele of tree.elements) {
list.push(ele.dumpCount);
}
root.value = param[0];
root.dumpCount = param[1];
stackTree.value = root;
const width = (graph.value && graph.value.getBoundingClientRect().width) || 0;
const w = width < 800 ? 802 : width;
flameChart.value = flamegraph()
.width(w - 15)
.cellHeight(18)
.transitionDuration(750)
.minFrameSize(1)
.transitionEase(d3.easeCubic as any)
.sort(true)
.title("")
.selfValue(false)
.inverted(true)
.onClick((d: { data: StackElement }) => {
selectStack.value = d.data;
})
.setColorMapper((d, originalColor) => (d.highlight ? "#6aff8f" : originalColor));
const tip = (d3tip as any)()
.attr("class", "d3-tip")
.direction("w")
.html((d: { data: StackElement } & { parent: { data: StackElement } }) => {
const name = d.data.name.replace("<", "&lt;").replace(">", "&gt;");
const valStr =
ebpfStore.aggregateType === AggregateTypes[0].value
? `<div class="mb-5">Dump Count: ${d.data.dumpCount}</div>`
: `<div class="mb-5">Duration: ${d.data.dumpCount} ns</div>`;
const rateOfParent =
(d.parent &&
`<div class="mb-5">Percentage Of Selected: ${
(
(d.data.dumpCount / ((selectStack.value && selectStack.value.dumpCount) || root.dumpCount)) *
100
).toFixed(3) + "%"
}</div>`) ||
"";
const rateOfRoot = `<div class="mb-5">Percentage Of Root: ${
((d.data.dumpCount / root.dumpCount) * 100).toFixed(3) + "%"
}</div>`;
return `<div class="mb-5 name">Symbol: ${name}</div>${valStr}${rateOfParent}${rateOfRoot}`;
})
.style("max-width", "500px");
flameChart.value.tooltip(tip);
d3.select("#graph-stack").datum(stackTree.value).call(flameChart.value);
}
max.value = Math.max(...list);
min.value = Math.min(...list);
}
function processTree(arr: StackElement[]) {
const copyArr = (window as any).structuredClone(arr);
const obj: any = {};
let res = null;
for (const item of copyArr) {
item.parentId = String(Number(item.parentId) + 1);
item.originId = String(Number(item.id) + 1);
item.name = item.symbol;
delete item.id;
obj[item.originId] = item;
}
const scale = d3.scaleLinear().domain([min.value, max.value]).range([1, 200]);
for (const item of copyArr) {
if (item.parentId === "1") {
const val = Number(scale(item.dumpCount).toFixed(4));
res = item;
res.value = val;
function countRange() {
const list = [];
for (const tree of ebpfStore.analyzeTrees) {
for (const ele of tree.elements) {
list.push(ele.dumpCount);
}
}
for (const key in obj) {
if (item.originId === obj[key].parentId) {
const val = Number(scale(obj[key].dumpCount).toFixed(4));
max.value = Math.max(...list);
min.value = Math.min(...list);
}
obj[key].value = val;
if (item.children) {
item.children.push(obj[key]);
} else {
item.children = [obj[key]];
function processTree(arr: StackElement[]) {
const copyArr = (window as any).structuredClone(arr);
const obj: any = {};
let res = null;
for (const item of copyArr) {
item.parentId = String(Number(item.parentId) + 1);
item.originId = String(Number(item.id) + 1);
item.name = item.symbol;
delete item.id;
obj[item.originId] = item;
}
const scale = d3.scaleLinear().domain([min.value, max.value]).range([1, 200]);
for (const item of copyArr) {
if (item.parentId === "1") {
const val = Number(scale(item.dumpCount).toFixed(4));
res = item;
res.value = val;
}
for (const key in obj) {
if (item.originId === obj[key].parentId) {
const val = Number(scale(obj[key].dumpCount).toFixed(4));
obj[key].value = val;
if (item.children) {
item.children.push(obj[key]);
} else {
item.children = [obj[key]];
}
}
}
}
}
treeForeach([res], (node: StackElement) => {
if (node.children) {
let val = 0;
for (const child of node.children) {
val = child.value + val;
treeForeach([res], (node: StackElement) => {
if (node.children) {
let val = 0;
for (const child of node.children) {
val = child.value + val;
}
node.value = node.value < val ? val : node.value;
}
node.value = node.value < val ? val : node.value;
});
return res;
}
function treeForeach(tree: StackElement[], func: (node: StackElement) => void) {
for (const data of tree) {
data.children && treeForeach(data.children, func);
func(data);
}
});
return res;
}
function treeForeach(tree: StackElement[], func: (node: StackElement) => void) {
for (const data of tree) {
data.children && treeForeach(data.children, func);
func(data);
return tree;
}
return tree;
}
watch(
() => ebpfStore.analyzeTrees,
() => {
drawGraph();
}
);
watch(
() => ebpfStore.analyzeTrees,
() => {
drawGraph();
},
);
</script>
<style>
#graph-stack {
width: 100%;
height: 100%;
cursor: pointer;
}
#graph-stack {
width: 100%;
height: 100%;
cursor: pointer;
}
.tip {
display: inline-block;
width: 100%;
text-align: center;
color: red;
margin-top: 20px;
}
.tip {
display: inline-block;
width: 100%;
text-align: center;
color: red;
margin-top: 20px;
}
.name {
word-wrap: break-word;
}
.name {
word-wrap: break-word;
}
</style>

View File

@@ -42,18 +42,9 @@ limitations under the License. -->
<div>
<div class="label">{{ t("monitorTime") }}</div>
<div>
<Radio
:value="monitorTime"
:options="InitTaskField.monitorTimeEn"
@change="changeMonitorTime"
/>
<Radio :value="monitorTime" :options="InitTaskField.monitorTimeEn" @change="changeMonitorTime" />
<span class="date">
<TimePicker
:value="time"
position="bottom"
format="YYYY-MM-DD HH:mm:ss"
@input="changeTimeRange"
/>
<TimePicker :value="time" position="bottom" format="YYYY-MM-DD HH:mm:ss" @input="changeTimeRange" />
</span>
</div>
</div>
@@ -79,90 +70,90 @@ limitations under the License. -->
<div v-else>{{ t("ebpfTip") }}</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useEbpfStore } from "@/store/modules/ebpf";
import { useSelectorStore } from "@/store/modules/selectors";
import { useAppStoreWithOut } from "@/store/modules/app";
import { ElMessage } from "element-plus";
import { InitTaskField, TargetTypes } from "./data";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useEbpfStore } from "@/store/modules/ebpf";
import { useSelectorStore } from "@/store/modules/selectors";
import { useAppStoreWithOut } from "@/store/modules/app";
import { ElMessage } from "element-plus";
import { InitTaskField, TargetTypes } from "./data";
/* global defineEmits */
const emits = defineEmits(["close"]);
const eBPFStore = useEbpfStore();
const selectorStore = useSelectorStore();
const appStore = useAppStoreWithOut();
const { t } = useI18n();
const labels = ref<string[]>([]);
const type = ref<string>(TargetTypes[0].value);
const monitorTime = ref<string>(InitTaskField.monitorTimeEn[0].value);
const monitorDuration = ref<number>(10);
const time = ref<Date>(appStore.durationRow.start);
const disabled = ref<boolean>(false);
/* global defineEmits */
const emits = defineEmits(["close"]);
const eBPFStore = useEbpfStore();
const selectorStore = useSelectorStore();
const appStore = useAppStoreWithOut();
const { t } = useI18n();
const labels = ref<string[]>([]);
const type = ref<string>(TargetTypes[0].value);
const monitorTime = ref<string>(InitTaskField.monitorTimeEn[0].value);
const monitorDuration = ref<number>(10);
const time = ref<Date>(appStore.durationRow.start);
const disabled = ref<boolean>(false);
function changeMonitorTime(opt: string) {
monitorTime.value = opt;
}
function changeLabel(opt: any[]) {
labels.value = opt.map((d) => d.value);
}
function changeType(opt: any[]) {
type.value = opt[0].value;
}
async function createTask() {
if (disabled.value) {
return;
function changeMonitorTime(opt: string) {
monitorTime.value = opt;
}
disabled.value = true;
const date = monitorTime.value === "0" ? new Date() : time.value;
const params = {
serviceId: selectorStore.currentService.id,
processLabels: labels.value,
startTime: date.getTime(),
duration: monitorDuration.value * 60,
targetType: type.value,
};
const res = await eBPFStore.createTask(params);
if (res.errors) {
ElMessage.error(res.errors);
return;
function changeLabel(opt: any[]) {
labels.value = opt.map((d) => d.value);
}
disabled.value = false;
if (!res.data.createTaskData.status) {
ElMessage.error(res.data.createTaskData.errorReason);
return;
function changeType(opt: any[]) {
type.value = opt[0].value;
}
async function createTask() {
if (disabled.value) {
return;
}
disabled.value = true;
const date = monitorTime.value === "0" ? new Date() : time.value;
const params = {
serviceId: selectorStore.currentService.id,
processLabels: labels.value,
startTime: date.getTime(),
duration: monitorDuration.value * 60,
targetType: type.value,
};
const res = await eBPFStore.createTask(params);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
disabled.value = false;
if (!res.data.createTaskData.status) {
ElMessage.error(res.data.createTaskData.errorReason);
return;
}
ElMessage.success("Task created successfully");
emits("close");
}
function changeTimeRange(val: Date) {
time.value = val;
}
ElMessage.success("Task created successfully");
emits("close");
}
function changeTimeRange(val: Date) {
time.value = val;
}
</script>
<style lang="scss" scoped>
.ebpf-task {
margin: 0 auto;
width: 400px;
}
.ebpf-task {
margin: 0 auto;
width: 400px;
}
.date {
font-size: 12px;
}
.date {
font-size: 12px;
}
.label {
margin-top: 10px;
font-size: 14px;
}
.label {
margin-top: 10px;
font-size: 14px;
}
.profile-input {
width: 300px;
}
.profile-input {
width: 300px;
}
.create-task-btn {
width: 300px;
margin-top: 50px;
}
.create-task-btn {
width: 300px;
margin-top: 50px;
}
</style>

View File

@@ -21,12 +21,7 @@ limitations under the License. -->
{{ t("noData") }}
</div>
<table class="profile-t">
<tr
class="profile-tr cp"
v-for="(i, index) in ebpfStore.taskList"
@click="changeTask(i)"
:key="index"
>
<tr class="profile-tr cp" v-for="(i, index) in ebpfStore.taskList" @click="changeTask(i)" :key="index">
<td
class="profile-td"
:class="{
@@ -35,13 +30,7 @@ limitations under the License. -->
>
<div class="ell">
<span>
{{
i.targetType +
": " +
(i.processLabels.length
? i.processLabels.join(" ")
: `All Processes`)
}}
{{ i.targetType + ": " + (i.processLabels.length ? i.processLabels.join(" ") : `All Processes`) }}
</span>
<a class="profile-btn r" @click="viewDetail = true">
<Icon iconName="view" size="middle" />
@@ -50,9 +39,7 @@ limitations under the License. -->
<div class="grey ell sm">
<span class="mr-10 sm">{{ dateFormat(i.taskStartTime) }}</span>
<span class="mr-10 sm">
{{
dateFormat(i.taskStartTime + i.fixedTriggerDuration * 1000)
}}
{{ dateFormat(i.taskStartTime + i.fixedTriggerDuration * 1000) }}
</span>
</div>
</td>
@@ -61,97 +48,92 @@ limitations under the License. -->
</div>
</div>
</div>
<el-dialog
v-model="viewDetail"
:destroy-on-close="true"
fullscreen
@closed="viewDetail = false"
>
<el-dialog v-model="viewDetail" :destroy-on-close="true" fullscreen @closed="viewDetail = false">
<TaskDetails :details="ebpfStore.selectedTask" />
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useEbpfStore } from "@/store/modules/ebpf";
import { EBPFTaskList } from "@/types/ebpf";
import { ElMessage } from "element-plus";
import TaskDetails from "../../components/TaskDetails.vue";
import { dateFormat } from "@/utils/dateFormat";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useEbpfStore } from "@/store/modules/ebpf";
import type { EBPFTaskList } from "@/types/ebpf";
import { ElMessage } from "element-plus";
import TaskDetails from "../../components/TaskDetails.vue";
import { dateFormat } from "@/utils/dateFormat";
const { t } = useI18n();
const ebpfStore = useEbpfStore();
const viewDetail = ref<boolean>(false);
const { t } = useI18n();
const ebpfStore = useEbpfStore();
const viewDetail = ref<boolean>(false);
async function changeTask(item: EBPFTaskList) {
ebpfStore.setSelectedTask(item);
const res = await ebpfStore.getEBPFSchedules({
taskId: item.taskId,
});
if (res.errors) {
ElMessage.error(res.errors);
async function changeTask(item: EBPFTaskList) {
ebpfStore.setSelectedTask(item);
const res = await ebpfStore.getEBPFSchedules({
taskId: item.taskId,
});
if (res.errors) {
ElMessage.error(res.errors);
}
}
}
</script>
<style lang="scss" scoped>
.profile-task-list {
width: 300px;
height: calc(100% - 10px);
overflow: auto;
border-right: 1px solid rgba(0, 0, 0, 0.1);
}
.item span {
height: 21px;
}
.profile-td {
padding: 5px 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
&.selected {
background-color: #ededed;
.profile-task-list {
width: 300px;
height: calc(100% - 10px);
overflow: auto;
border-right: 1px solid rgba(0, 0, 0, 0.1);
}
}
.no-data {
text-align: center;
margin-top: 10px;
}
.profile-t-wrapper {
overflow: auto;
flex-grow: 1;
}
.profile-t {
width: 100%;
border-spacing: 0;
table-layout: fixed;
flex-grow: 1;
position: relative;
border: none;
}
.profile-tr {
&:hover {
background-color: rgba(0, 0, 0, 0.04);
.item span {
height: 21px;
}
}
.profile-t-tool {
padding: 5px 10px;
font-weight: bold;
border-right: 1px solid rgba(0, 0, 0, 0.07);
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
background: #f3f4f9;
}
.profile-td {
padding: 5px 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
.profile-btn {
color: #3d444f;
padding: 1px 3px;
border-radius: 2px;
font-size: 12px;
float: right;
}
&.selected {
background-color: #ededed;
}
}
.no-data {
text-align: center;
margin-top: 10px;
}
.profile-t-wrapper {
overflow: auto;
flex-grow: 1;
}
.profile-t {
width: 100%;
border-spacing: 0;
table-layout: fixed;
flex-grow: 1;
position: relative;
border: none;
}
.profile-tr {
&:hover {
background-color: rgba(0, 0, 0, 0.04);
}
}
.profile-t-tool {
padding: 5px 10px;
font-weight: bold;
border-right: 1px solid rgba(0, 0, 0, 0.07);
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
background: #f3f4f9;
}
.profile-btn {
color: #3d444f;
padding: 1px 3px;
border-radius: 2px;
font-size: 12px;
float: right;
}
</style>

View File

@@ -16,272 +16,257 @@ limitations under the License. -->
<div ref="timeline" class="events"></div>
</template>
<script lang="ts" setup>
import { ref, watch, onMounted } from "vue";
import dayjs from "dayjs";
import { useThrottleFn } from "@vueuse/core";
import { Event } from "@/types/events";
import { LayoutConfig } from "@/types/dashboard";
import { useEventStore } from "@/store/modules/event";
import { DataSet, Timeline } from "vis-timeline/standalone";
import "vis-timeline/styles/vis-timeline-graph2d.css";
import { useDashboardStore } from "@/store/modules/dashboard";
import getDashboard from "@/hooks/useDashboardsSession";
import { useAppStoreWithOut } from "@/store/modules/app";
import dateFormatStep, { dateFormatTime } from "@/utils/dateFormat";
import getLocalTime from "@/utils/localtime";
import { ref, watch, onMounted } from "vue";
import dayjs from "dayjs";
import { useThrottleFn } from "@vueuse/core";
import type { Event } from "@/types/events";
import type { LayoutConfig } from "@/types/dashboard";
import { useEventStore } from "@/store/modules/event";
import { DataSet, Timeline } from "vis-timeline/standalone";
import "vis-timeline/styles/vis-timeline-graph2d.css";
import { useDashboardStore } from "@/store/modules/dashboard";
import getDashboard from "@/hooks/useDashboardsSession";
import { useAppStoreWithOut } from "@/store/modules/app";
import dateFormatStep, { dateFormatTime } from "@/utils/dateFormat";
import getLocalTime from "@/utils/localtime";
const eventStore = useEventStore();
/*global defineProps, Nullable */
const props = defineProps({
data: {
type: Object,
default: () => ({}),
},
});
const timeline = ref<Nullable<HTMLDivElement>>(null);
const visGraph = ref<Nullable<any>>(null);
const oldVal = ref<{ width: number; height: number }>({ width: 0, height: 0 });
const dashboardStore = useDashboardStore();
const appStore = useAppStoreWithOut();
const visDate = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern);
onMounted(() => {
oldVal.value = (timeline.value && timeline.value.getBoundingClientRect()) || {
width: 0,
height: 0,
};
useThrottleFn(resize, 500)();
});
function visTimeline() {
if (!timeline.value) {
return;
}
if (visGraph.value) {
visGraph.value.destroy();
}
const h = timeline.value.getBoundingClientRect().height;
const events = eventStore.events.map((d: Event, index: number) => {
return {
id: index + 1,
content: d.name,
start: new Date(Number(d.startTime)),
end: new Date(Number(d.endTime)),
data: d,
className: d.type,
};
const eventStore = useEventStore();
/*global defineProps, Nullable */
const props = defineProps({
data: {
type: Object,
default: () => ({}),
},
});
const items: any = new DataSet(events);
const options: any = {
height: h,
width: "100%",
locale: "en",
groupHeightMode: "fitItems",
autoResize: false,
tooltip: {
overflowMethod: "cap",
template(item: Event | any) {
const data = item.data || {};
let tmp = `<div>ID: ${data.uuid || ""}</div>
const timeline = ref<Nullable<HTMLDivElement>>(null);
const visGraph = ref<Nullable<any>>(null);
const oldVal = ref<{ width: number; height: number }>({ width: 0, height: 0 });
const dashboardStore = useDashboardStore();
const appStore = useAppStoreWithOut();
const visDate = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") => dayjs(date).format(pattern);
onMounted(() => {
oldVal.value = (timeline.value && timeline.value.getBoundingClientRect()) || {
width: 0,
height: 0,
};
useThrottleFn(resize, 500)();
});
function visTimeline() {
if (!timeline.value) {
return;
}
if (visGraph.value) {
visGraph.value.destroy();
}
const h = timeline.value.getBoundingClientRect().height;
const events = eventStore.events.map((d: Event, index: number) => {
return {
id: index + 1,
content: d.name,
start: new Date(Number(d.startTime)),
end: new Date(Number(d.endTime)),
data: d,
className: d.type,
};
});
const items: any = new DataSet(events);
const options: any = {
height: h,
width: "100%",
locale: "en",
groupHeightMode: "fitItems",
autoResize: false,
tooltip: {
overflowMethod: "cap",
template(item: Event | any) {
const data = item.data || {};
let tmp = `<div>ID: ${data.uuid || ""}</div>
<div>Name: ${data.name || ""}</div>
<div>Event Type: ${data.type || ""}</div>
<div>Start Time: ${data.startTime ? visDate(data.startTime) : ""}</div>
<div>End Time: ${data.endTime ? visDate(data.endTime) : ""}</div>
<div>Message: ${data.message || ""}</div>
<div>Service: ${data.source.service || ""}</div>`;
if (data.source.endpoint) {
tmp += `<div>Endpoint: ${data.source.endpoint}</div>`;
}
if (data.source.instance) {
tmp += `<div>Service Instance: ${data.source.instance}</div>`;
}
return tmp;
if (data.source.endpoint) {
tmp += `<div>Endpoint: ${data.source.endpoint}</div>`;
}
if (data.source.instance) {
tmp += `<div>Service Instance: ${data.source.instance}</div>`;
}
return tmp;
},
},
},
};
visGraph.value = new Timeline(timeline.value, items, options);
visGraph.value.on("select", (properties: { items: number[] }) => {
if (!props.data.eventAssociate) {
return;
}
dashboardStore.selectWidget(props.data);
const dashboard = getDashboard(dashboardStore.currentDashboard).widgets;
associateMetrics(properties.items, events, dashboard);
associateTraceLog(properties.items, events, dashboard);
});
}
function associateTraceLog(
items: number[],
events: {
id: number;
content: string;
start: Date;
end: Date;
data: unknown;
className: string;
}[],
dashboard: LayoutConfig[]
) {
const widgets = dashboard.filter((d: { type: string }) =>
["Trace", "Log"].includes(d.type)
);
const index = items[0];
const i = events[index - 1 || 0];
for (const widget of widgets) {
if (isNaN(index)) {
const item = {
...widget,
filters: {
sourceId: props.data.id || "",
duration: null,
},
};
dashboardStore.setWidget(item);
} else {
let step = appStore.duration.step;
let start = i.start;
let end = i.end;
if (
appStore.duration.step === "MINUTE" &&
i.end.getTime() - i.start.getTime() < 60000
) {
step = "SECOND";
} else {
const times = setEndTime(i.start, i.end);
start = times.start;
end = times.end;
};
visGraph.value = new Timeline(timeline.value, items, options);
visGraph.value.on("select", (properties: { items: number[] }) => {
if (!props.data.eventAssociate) {
return;
}
const item = {
...widget,
filters: {
sourceId: props.data.id || "",
duration: {
start: dateFormatStep(
getLocalTime(appStore.utc, start),
dashboardStore.selectWidget(props.data);
const dashboard = getDashboard(dashboardStore.currentDashboard).widgets;
associateMetrics(properties.items, events, dashboard);
associateTraceLog(properties.items, events, dashboard);
});
}
function associateTraceLog(
items: number[],
events: {
id: number;
content: string;
start: Date;
end: Date;
data: unknown;
className: string;
}[],
dashboard: LayoutConfig[],
) {
const widgets = dashboard.filter((d: { type: string }) => ["Trace", "Log"].includes(d.type));
const index = items[0];
const i = events[index - 1 || 0];
for (const widget of widgets) {
if (isNaN(index)) {
const item = {
...widget,
filters: {
sourceId: props.data.id || "",
duration: null,
},
};
dashboardStore.setWidget(item);
} else {
let step = appStore.duration.step;
let start = i.start;
let end = i.end;
if (appStore.duration.step === "MINUTE" && i.end.getTime() - i.start.getTime() < 60000) {
step = "SECOND";
} else {
const times = setEndTime(i.start, i.end);
start = times.start;
end = times.end;
}
const item = {
...widget,
filters: {
sourceId: props.data.id || "",
duration: {
start: dateFormatStep(getLocalTime(appStore.utc, start), step, true),
end: dateFormatStep(getLocalTime(appStore.utc, end), step, true),
step,
true
),
end: dateFormatStep(getLocalTime(appStore.utc, end), step, true),
step,
},
},
},
};
dashboardStore.setWidget(item);
};
dashboardStore.setWidget(item);
}
}
}
}
function associateMetrics(
items: number[],
events: {
id: number;
content: string;
start: Date;
end: Date;
data: unknown;
className: string;
}[],
dashboard: LayoutConfig[]
) {
const widgets = dashboard.filter((d: LayoutConfig) => {
const isLinear = ["Bar", "Line", "Area"].includes(
(d.graph && d.graph.type) || ""
);
if (isLinear) {
return d;
}
});
const index = items[0];
const i = events[index - 1 || 0];
function associateMetrics(
items: number[],
events: {
id: number;
content: string;
start: Date;
end: Date;
data: unknown;
className: string;
}[],
dashboard: LayoutConfig[],
) {
const widgets = dashboard.filter((d: LayoutConfig) => {
const isLinear = ["Bar", "Line", "Area"].includes((d.graph && d.graph.type) || "");
if (isLinear) {
return d;
}
});
const index = items[0];
const i = events[index - 1 || 0];
for (const widget of widgets) {
if (isNaN(index)) {
const item = {
...widget,
filters: {
sourceId: dashboardStore.selectedGrid.id || "",
isRange: true,
duration: {
startTime: null,
endTime: null,
for (const widget of widgets) {
if (isNaN(index)) {
const item = {
...widget,
filters: {
sourceId: dashboardStore.selectedGrid.id || "",
isRange: true,
duration: {
startTime: null,
endTime: null,
},
},
},
};
dashboardStore.setWidget(item);
} else {
const { start, end } = setEndTime(i.start, i.end);
const startTime = dateFormatTime(start, appStore.duration.step);
const endTime = dateFormatTime(end, appStore.duration.step);
const item = {
...widget,
filters: {
sourceId: dashboardStore.selectedGrid.id || "",
isRange: true,
duration: {
startTime,
endTime,
};
dashboardStore.setWidget(item);
} else {
const { start, end } = setEndTime(i.start, i.end);
const startTime = dateFormatTime(start, appStore.duration.step);
const endTime = dateFormatTime(end, appStore.duration.step);
const item = {
...widget,
filters: {
sourceId: dashboardStore.selectedGrid.id || "",
isRange: true,
duration: {
startTime,
endTime,
},
},
},
};
dashboardStore.setWidget(item);
};
dashboardStore.setWidget(item);
}
}
}
}
function setEndTime(start: Date, end: Date) {
let time: Date | number = end;
let diff = 60000;
switch (appStore.duration.step) {
case "MINUTE":
diff = 60000;
break;
case "HOUR":
diff = 3600000;
break;
case "DAY":
diff = 3600000 * 24;
break;
function setEndTime(start: Date, end: Date) {
let time: Date | number = end;
let diff = 60000;
switch (appStore.duration.step) {
case "MINUTE":
diff = 60000;
break;
case "HOUR":
diff = 3600000;
break;
case "DAY":
diff = 3600000 * 24;
break;
}
if (!end || end.getTime() - start.getTime() < diff) {
time = start.getTime() + diff;
}
return { start, end: new Date(time) };
}
if (!end || end.getTime() - start.getTime() < diff) {
time = start.getTime() + diff;
}
return { start, end: new Date(time) };
}
function resize() {
const observer = new ResizeObserver((entries) => {
const entry = entries[0];
const cr = entry.contentRect;
if (
Math.abs(cr.width - oldVal.value.width) < 3 &&
Math.abs(cr.height - oldVal.value.height) < 3
) {
return;
function resize() {
const observer = new ResizeObserver((entries) => {
const entry = entries[0];
const cr = entry.contentRect;
if (Math.abs(cr.width - oldVal.value.width) < 3 && Math.abs(cr.height - oldVal.value.height) < 3) {
return;
}
visTimeline();
oldVal.value = { width: cr.width, height: cr.height };
});
if (timeline.value) {
observer.observe(timeline.value);
}
visTimeline();
oldVal.value = { width: cr.width, height: cr.height };
});
if (timeline.value) {
observer.observe(timeline.value);
}
}
watch(
() => eventStore.events,
() => {
visTimeline();
}
);
watch(
() => eventStore.events,
() => {
visTimeline();
},
);
</script>
<style lang="scss" scoped>
.events {
width: calc(100% - 5px);
margin: 0 5px 5px 0;
height: 100%;
min-height: 150px;
}
.events {
width: calc(100% - 5px);
margin: 0 5px 5px 0;
height: 100%;
min-height: 150px;
}
.message {
max-width: 400px;
text-overflow: ellipsis;
overflow: hidden;
}
.message {
max-width: 400px;
text-overflow: ellipsis;
overflow: hidden;
}
</style>

View File

@@ -56,186 +56,179 @@ limitations under the License. -->
:pager-count="5"
small
/>
<el-button
class="search-btn"
size="small"
type="primary"
@click="queryEvents"
>
<el-button class="search-btn" size="small" type="primary" @click="queryEvents">
{{ t("search") }}
</el-button>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, reactive, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useEventStore } from "@/store/modules/event";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
import { EntityType } from "../../data";
import { EventTypes } from "./data";
import { ref, computed, reactive, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useEventStore } from "@/store/modules/event";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
import { EntityType } from "../../data";
import { EventTypes } from "./data";
/*global defineProps */
const props = defineProps({
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const eventStore = useEventStore();
const pageSize = 20;
const pageNum = ref<number>(1);
const state = reactive<any>({
instance: { value: "", label: "All", id: "" },
endpoint: { value: "", label: "All", id: "" },
eventType: { value: "", label: "All" },
});
const total = computed(() =>
eventStore.events.length === pageSize
? pageSize * pageNum.value + 1
: pageSize * pageNum.value
);
if (props.needQuery) {
init();
}
async function init() {
fetchSelectors();
await queryEvents();
state.instance = { value: "", label: "All" };
state.endpoint = { value: "", label: "All" };
}
function fetchSelectors() {
if (dashboardStore.entity === EntityType[2].value) {
getInstances();
return;
}
if (dashboardStore.entity === EntityType[3].value) {
getEndpoints();
return;
}
if (dashboardStore.entity === EntityType[0].value) {
getInstances();
getEndpoints();
}
}
async function getEndpoints(id?: string) {
const resp = await eventStore.getEndpoints(id);
if (!resp) {
return;
}
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.endpoint = eventStore.endpoints[0];
}
async function getInstances(id?: string) {
const resp = await eventStore.getInstances(id);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.instance = eventStore.instances[0];
}
async function queryEvents() {
let endpoint = state.endpoint.value,
instance = state.instance.value;
if (dashboardStore.entity === EntityType[2].value) {
endpoint = selectorStore.currentPod && selectorStore.currentPod.id;
}
if (dashboardStore.entity === EntityType[3].value) {
instance = selectorStore.currentPod && selectorStore.currentPod.id;
}
if (!selectorStore.currentService) {
return;
}
eventStore.setEventCondition({
// layer: dashboardStore.layerId,
paging: {
pageNum: pageNum.value,
pageSize: pageSize,
},
source: {
service: selectorStore.currentService.value || "",
endpoint: endpoint || "",
serviceInstance: instance || "",
},
type: state.eventType.value || undefined,
/*global defineProps */
const props = defineProps({
needQuery: { type: Boolean, default: true },
});
const res = await eventStore.getEvents();
if (res && res.errors) {
ElMessage.error(res.errors);
}
}
function changeField(type: string, opt: any[]) {
state[type] = opt[0];
}
async function searchEndpoints(keyword: string) {
const resp = await eventStore.getEndpoints(keyword);
if (resp.errors) {
ElMessage.error(resp.errors);
}
}
function updatePage(p: number) {
pageNum.value = p;
queryEvents();
}
watch(
() => [selectorStore.currentService],
() => {
if (dashboardStore.entity === EntityType[0].value) {
init();
}
}
);
watch(
() => [selectorStore.currentPod],
() => {
if (dashboardStore.entity === EntityType[0].value) {
return;
}
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const eventStore = useEventStore();
const pageSize = 20;
const pageNum = ref<number>(1);
const state = reactive<any>({
instance: { value: "", label: "All", id: "" },
endpoint: { value: "", label: "All", id: "" },
eventType: { value: "", label: "All" },
});
const total = computed(() =>
eventStore.events.length === pageSize ? pageSize * pageNum.value + 1 : pageSize * pageNum.value,
);
if (props.needQuery) {
init();
}
);
watch(
() => appStore.durationTime,
() => {
if (dashboardStore.entity === EntityType[1].value) {
init();
async function init() {
fetchSelectors();
await queryEvents();
state.instance = { value: "", label: "All" };
state.endpoint = { value: "", label: "All" };
}
function fetchSelectors() {
if (dashboardStore.entity === EntityType[2].value) {
getInstances();
return;
}
if (dashboardStore.entity === EntityType[3].value) {
getEndpoints();
return;
}
if (dashboardStore.entity === EntityType[0].value) {
getInstances();
getEndpoints();
}
}
);
async function getEndpoints(id?: string) {
const resp = await eventStore.getEndpoints(id);
if (!resp) {
return;
}
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.endpoint = eventStore.endpoints[0];
}
async function getInstances(id?: string) {
const resp = await eventStore.getInstances(id);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.instance = eventStore.instances[0];
}
async function queryEvents() {
let endpoint = state.endpoint.value,
instance = state.instance.value;
if (dashboardStore.entity === EntityType[2].value) {
endpoint = selectorStore.currentPod && selectorStore.currentPod.id;
}
if (dashboardStore.entity === EntityType[3].value) {
instance = selectorStore.currentPod && selectorStore.currentPod.id;
}
if (!selectorStore.currentService) {
return;
}
eventStore.setEventCondition({
// layer: dashboardStore.layerId,
paging: {
pageNum: pageNum.value,
pageSize: pageSize,
},
source: {
service: selectorStore.currentService.value || "",
endpoint: endpoint || "",
serviceInstance: instance || "",
},
type: state.eventType.value || undefined,
});
const res = await eventStore.getEvents();
if (res && res.errors) {
ElMessage.error(res.errors);
}
}
function changeField(type: string, opt: any[]) {
state[type] = opt[0];
}
async function searchEndpoints(keyword: string) {
const resp = await eventStore.getEndpoints(keyword);
if (resp.errors) {
ElMessage.error(resp.errors);
}
}
function updatePage(p: number) {
pageNum.value = p;
queryEvents();
}
watch(
() => [selectorStore.currentService],
() => {
if (dashboardStore.entity === EntityType[0].value) {
init();
}
},
);
watch(
() => [selectorStore.currentPod],
() => {
if (dashboardStore.entity === EntityType[0].value) {
return;
}
init();
},
);
watch(
() => appStore.durationTime,
() => {
if (dashboardStore.entity === EntityType[1].value) {
init();
}
},
);
</script>
<style lang="scss" scoped>
.inputs {
width: 120px;
}
.inputs {
width: 120px;
}
.inputs-max {
width: 270px;
}
.inputs-max {
width: 270px;
}
.search-btn {
cursor: pointer;
width: 120px;
}
.search-btn {
cursor: pointer;
width: 120px;
}
.selected {
display: inline-block;
padding: 0 3px;
border-radius: 3px;
overflow: hidden;
color: #3d444f;
border: 1px dashed #aaa;
font-size: 12px;
margin: 0 2px;
}
.selected {
display: inline-block;
padding: 0 3px;
border-radius: 3px;
overflow: hidden;
color: #3d444f;
border: 1px dashed #aaa;
font-size: 12px;
margin: 0 2px;
}
</style>

View File

@@ -25,9 +25,7 @@ limitations under the License. -->
/>
</div>
<div class="mr-5" v-if="dashboardStore.entity !== EntityType[3].value">
<span class="grey mr-5">
{{ isBrowser ? t("version") : t("instance") }}:
</span>
<span class="grey mr-5"> {{ isBrowser ? t("version") : t("instance") }}: </span>
<Selector
size="small"
:value="state.instance.value"
@@ -37,9 +35,7 @@ limitations under the License. -->
/>
</div>
<div class="mr-5" v-if="dashboardStore.entity !== EntityType[2].value">
<span class="grey mr-5">
{{ isBrowser ? t("page") : t("endpoint") }}:
</span>
<span class="grey mr-5"> {{ isBrowser ? t("page") : t("endpoint") }}: </span>
<Selector
size="small"
:value="state.endpoint.value"
@@ -60,12 +56,7 @@ limitations under the License. -->
@change="changeField('category', $event)"
/>
</div>
<el-button
class="search-btn"
size="small"
type="primary"
@click="searchLogs"
>
<el-button class="search-btn" size="small" type="primary" @click="searchLogs">
{{ t("search") }}
</el-button>
</div>
@@ -83,11 +74,7 @@ limitations under the License. -->
<div class="mr-5" v-show="logStore.supportQueryLogsByKeywords">
<span class="mr-5 grey">{{ t("keywordsOfContent") }}:</span>
<span class="log-tags">
<span
class="selected"
v-for="(item, index) in keywordsOfContent"
:key="`keywordsOfContent${index}`"
>
<span class="selected" v-for="(item, index) in keywordsOfContent" :key="`keywordsOfContent${index}`">
<span>{{ item }}</span>
<span class="remove-icon" @click="removeContent(index)">×</span>
</span>
@@ -109,9 +96,7 @@ limitations under the License. -->
:key="`excludingKeywordsOfContent${index}`"
>
<span>{{ item }}</span>
<span class="remove-icon" @click="removeExcludeContent(index)">
×
</span>
<span class="remove-icon" @click="removeExcludeContent(index)"> × </span>
</span>
</span>
<el-input
@@ -130,321 +115,311 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, watch, onUnmounted } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { Option } from "@/types/app";
import { useLogStore } from "@/store/modules/log";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import ConditionTags from "@/views/components/ConditionTags.vue";
import { ElMessage } from "element-plus";
import { EntityType } from "../../data";
import { ErrorCategory } from "./data";
import { LayoutConfig } from "@/types/dashboard";
import { DurationTime } from "@/types/app";
import { ref, reactive, watch, onUnmounted } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import type { Option } from "@/types/app";
import { useLogStore } from "@/store/modules/log";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import ConditionTags from "@/views/components/ConditionTags.vue";
import { ElMessage } from "element-plus";
import { EntityType } from "../../data";
import { ErrorCategory } from "./data";
import type { LayoutConfig } from "@/types/dashboard";
import type { DurationTime } from "@/types/app";
/*global defineProps, Recordable */
const props = defineProps({
needQuery: { type: Boolean, default: true },
data: {
type: Object as PropType<LayoutConfig>,
default: () => ({ graph: {} }),
},
});
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const logStore = useLogStore();
const traceId = ref<string>(
(props.data.filters && props.data.filters.traceId) || ""
);
const duration = ref<DurationTime>(
(props.data.filters && props.data.filters.duration) || appStore.durationTime
);
const keywordsOfContent = ref<string[]>([]);
const excludingKeywordsOfContent = ref<string[]>([]);
const tagsList = ref<string[]>([]);
const tagsMap = ref<Option[]>([]);
const contentStr = ref<string>("");
const excludingContentStr = ref<string>("");
const isBrowser = ref<boolean>(dashboardStore.layerId === "BROWSER");
const state = reactive<Recordable>({
instance: { value: "0", label: "All" },
endpoint: { value: "0", label: "All" },
service: { value: "", label: "" },
category: { value: "ALL", label: "All" },
});
if (props.needQuery) {
init();
}
async function init() {
const resp = await logStore.getLogsByKeywords();
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
await fetchSelectors();
await searchLogs();
state.instance = { value: "0", label: "All" };
state.endpoint = { value: "0", label: "All" };
}
async function fetchSelectors() {
if (dashboardStore.entity === EntityType[1].value) {
await getServices();
return;
}
if (dashboardStore.entity === EntityType[2].value) {
await getInstances();
return;
}
if (dashboardStore.entity === EntityType[3].value) {
await getEndpoints();
return;
}
if (dashboardStore.entity === EntityType[0].value) {
await getInstances();
await getEndpoints();
}
}
async function getServices() {
const resp = await logStore.getServices(dashboardStore.layerId);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.service = logStore.services[0];
getInstances(state.service.id);
getEndpoints(state.service.id);
}
async function getEndpoints(id?: string) {
const resp = await logStore.getEndpoints(id);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.endpoint = logStore.endpoints[0];
}
async function getInstances(id?: string) {
const resp = await logStore.getInstances(id);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.instance = logStore.instances[0];
}
function searchLogs() {
let endpoint = "",
instance = "";
if (dashboardStore.entity === EntityType[2].value) {
endpoint = selectorStore.currentPod.id;
}
if (dashboardStore.entity === EntityType[3].value) {
instance = selectorStore.currentPod.id;
}
if (dashboardStore.layerId === "BROWSER") {
logStore.setLogCondition({
serviceId: selectorStore.currentService
? selectorStore.currentService.id
: state.service.id,
pagePathId: endpoint || state.endpoint.id || undefined,
serviceVersionId: instance || state.instance.id || undefined,
paging: { pageNum: 1, pageSize: 15 },
queryDuration: duration,
category: state.category.value,
});
} else {
let segmentId, spanId;
if (props.data.filters) {
segmentId = props.data.filters.segmentId;
spanId = props.data.filters.spanId;
}
logStore.setLogCondition({
serviceId: selectorStore.currentService
? selectorStore.currentService.id
: state.service.id,
endpointId: endpoint || state.endpoint.id || undefined,
serviceInstanceId: instance || state.instance.id || undefined,
queryDuration: duration.value,
keywordsOfContent: keywordsOfContent.value,
excludingKeywordsOfContent: excludingKeywordsOfContent.value,
tags: tagsMap.value.length ? tagsMap.value : undefined,
paging: { pageNum: 1, pageSize: 15 },
relatedTrace: traceId.value
? { traceId: traceId.value, segmentId, spanId }
: undefined,
});
}
queryLogs();
}
async function queryLogs() {
const res = await logStore.getLogs();
if (res && res.errors) {
ElMessage.error(res.errors);
}
}
function changeField(type: string, opt: any) {
state[type] = opt[0];
if (type === "service") {
getEndpoints(state.service.id);
getInstances(state.service.id);
}
}
async function searchEndpoints(keyword: string) {
const resp = await logStore.getEndpoints(state.service.id, keyword);
if (resp.errors) {
ElMessage.error(resp.errors);
}
}
function updateTags(data: { tagsMap: Array<Option>; tagsList: string[] }) {
tagsList.value = data.tagsList;
tagsMap.value = data.tagsMap;
}
function removeContent(index: number) {
const keywordsOfContentList = keywordsOfContent.value || [];
keywordsOfContentList.splice(index, 1);
logStore.setLogCondition({
keywordsOfContent: keywordsOfContentList,
/*global defineProps, Recordable */
const props = defineProps({
needQuery: { type: Boolean, default: true },
data: {
type: Object as PropType<LayoutConfig>,
default: () => ({ graph: {} }),
},
});
contentStr.value = "";
}
function addLabels(type: string) {
if (type === "keywordsOfContent" && !contentStr.value) {
return;
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const logStore = useLogStore();
const traceId = ref<string>((props.data.filters && props.data.filters.traceId) || "");
const duration = ref<DurationTime>((props.data.filters && props.data.filters.duration) || appStore.durationTime);
const keywordsOfContent = ref<string[]>([]);
const excludingKeywordsOfContent = ref<string[]>([]);
const tagsList = ref<string[]>([]);
const tagsMap = ref<Option[]>([]);
const contentStr = ref<string>("");
const excludingContentStr = ref<string>("");
const isBrowser = ref<boolean>(dashboardStore.layerId === "BROWSER");
const state = reactive<Recordable>({
instance: { value: "0", label: "All" },
endpoint: { value: "0", label: "All" },
service: { value: "", label: "" },
category: { value: "ALL", label: "All" },
});
if (props.needQuery) {
init();
}
if (type === "excludingKeywordsOfContent" && !excludingContentStr.value) {
return;
async function init() {
const resp = await logStore.getLogsByKeywords();
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
await fetchSelectors();
await searchLogs();
state.instance = { value: "0", label: "All" };
state.endpoint = { value: "0", label: "All" };
}
if (type === "keywordsOfContent") {
keywordsOfContent.value.push(contentStr.value);
async function fetchSelectors() {
if (dashboardStore.entity === EntityType[1].value) {
await getServices();
return;
}
if (dashboardStore.entity === EntityType[2].value) {
await getInstances();
return;
}
if (dashboardStore.entity === EntityType[3].value) {
await getEndpoints();
return;
}
if (dashboardStore.entity === EntityType[0].value) {
await getInstances();
await getEndpoints();
}
}
async function getServices() {
const resp = await logStore.getServices(dashboardStore.layerId);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.service = logStore.services[0];
getInstances(state.service.id);
getEndpoints(state.service.id);
}
async function getEndpoints(id?: string) {
const resp = await logStore.getEndpoints(id);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.endpoint = logStore.endpoints[0];
}
async function getInstances(id?: string) {
const resp = await logStore.getInstances(id);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.instance = logStore.instances[0];
}
function searchLogs() {
let endpoint = "",
instance = "";
if (dashboardStore.entity === EntityType[2].value) {
endpoint = selectorStore.currentPod.id;
}
if (dashboardStore.entity === EntityType[3].value) {
instance = selectorStore.currentPod.id;
}
if (dashboardStore.layerId === "BROWSER") {
logStore.setLogCondition({
serviceId: selectorStore.currentService ? selectorStore.currentService.id : state.service.id,
pagePathId: endpoint || state.endpoint.id || undefined,
serviceVersionId: instance || state.instance.id || undefined,
paging: { pageNum: 1, pageSize: 15 },
queryDuration: duration,
category: state.category.value,
});
} else {
let segmentId, spanId;
if (props.data.filters) {
segmentId = props.data.filters.segmentId;
spanId = props.data.filters.spanId;
}
logStore.setLogCondition({
serviceId: selectorStore.currentService ? selectorStore.currentService.id : state.service.id,
endpointId: endpoint || state.endpoint.id || undefined,
serviceInstanceId: instance || state.instance.id || undefined,
queryDuration: duration.value,
keywordsOfContent: keywordsOfContent.value,
excludingKeywordsOfContent: excludingKeywordsOfContent.value,
tags: tagsMap.value.length ? tagsMap.value : undefined,
paging: { pageNum: 1, pageSize: 15 },
relatedTrace: traceId.value ? { traceId: traceId.value, segmentId, spanId } : undefined,
});
}
queryLogs();
}
async function queryLogs() {
const res = await logStore.getLogs();
if (res && res.errors) {
ElMessage.error(res.errors);
}
}
function changeField(type: string, opt: any) {
state[type] = opt[0];
if (type === "service") {
getEndpoints(state.service.id);
getInstances(state.service.id);
}
}
async function searchEndpoints(keyword: string) {
const resp = await logStore.getEndpoints(state.service.id, keyword);
if (resp.errors) {
ElMessage.error(resp.errors);
}
}
function updateTags(data: { tagsMap: Array<Option>; tagsList: string[] }) {
tagsList.value = data.tagsList;
tagsMap.value = data.tagsMap;
}
function removeContent(index: number) {
const keywordsOfContentList = keywordsOfContent.value || [];
keywordsOfContentList.splice(index, 1);
logStore.setLogCondition({
[type]: keywordsOfContent.value,
keywordsOfContent: keywordsOfContentList,
});
contentStr.value = "";
} else if (type === "excludingKeywordsOfContent") {
excludingKeywordsOfContent.value.push(excludingContentStr.value);
}
function addLabels(type: string) {
if (type === "keywordsOfContent" && !contentStr.value) {
return;
}
if (type === "excludingKeywordsOfContent" && !excludingContentStr.value) {
return;
}
if (type === "keywordsOfContent") {
keywordsOfContent.value.push(contentStr.value);
logStore.setLogCondition({
[type]: keywordsOfContent.value,
});
contentStr.value = "";
} else if (type === "excludingKeywordsOfContent") {
excludingKeywordsOfContent.value.push(excludingContentStr.value);
logStore.setLogCondition({
[type]: excludingKeywordsOfContent.value,
});
excludingContentStr.value = "";
}
}
function removeExcludeContent(index: number) {
excludingKeywordsOfContent.value.splice(index, 1);
logStore.setLogCondition({
[type]: excludingKeywordsOfContent.value,
excludingKeywordsOfContent: excludingKeywordsOfContent.value,
});
excludingContentStr.value = "";
}
}
function removeExcludeContent(index: number) {
excludingKeywordsOfContent.value.splice(index, 1);
logStore.setLogCondition({
excludingKeywordsOfContent: excludingKeywordsOfContent.value,
onUnmounted(() => {
logStore.resetState();
});
excludingContentStr.value = "";
}
onUnmounted(() => {
logStore.resetState();
});
watch(
() => selectorStore.currentService,
() => {
if (dashboardStore.entity === EntityType[0].value) {
init();
}
}
);
watch(
() => [selectorStore.currentPod],
() => {
if (dashboardStore.entity === EntityType[0].value) {
return;
}
init();
}
);
watch(
() => appStore.durationTime,
() => {
if (dashboardStore.entity === EntityType[1].value) {
init();
}
}
);
watch(
() => props.data.filters,
(newJson, oldJson) => {
if (props.data.filters) {
if (JSON.stringify(newJson) === JSON.stringify(oldJson)) {
watch(
() => selectorStore.currentService,
() => {
if (dashboardStore.entity === EntityType[0].value) {
init();
}
},
);
watch(
() => [selectorStore.currentPod],
() => {
if (dashboardStore.entity === EntityType[0].value) {
return;
}
traceId.value = props.data.filters.traceId || "";
duration.value = props.data.filters.duration || appStore.durationTime;
init();
}
}
);
},
);
watch(
() => appStore.durationTime,
() => {
if (dashboardStore.entity === EntityType[1].value) {
init();
}
},
);
watch(
() => props.data.filters,
(newJson, oldJson) => {
if (props.data.filters) {
if (JSON.stringify(newJson) === JSON.stringify(oldJson)) {
return;
}
traceId.value = props.data.filters.traceId || "";
duration.value = props.data.filters.duration || appStore.durationTime;
init();
}
},
);
</script>
<style lang="scss" scoped>
.inputs {
width: 120px;
}
.inputs {
width: 120px;
}
.row {
margin-bottom: 5px;
position: relative;
}
.row {
margin-bottom: 5px;
position: relative;
}
.inputs-max {
width: 270px;
}
.inputs-max {
width: 270px;
}
.traceId {
margin-top: 2px;
}
.traceId {
margin-top: 2px;
}
.search-btn {
position: absolute;
top: 0;
right: 10px;
cursor: pointer;
width: 120px;
}
.search-btn {
position: absolute;
top: 0;
right: 10px;
cursor: pointer;
width: 120px;
}
.tips {
color: #888;
}
.tips {
color: #888;
}
.log-tag {
width: 30%;
border-style: unset;
outline: 0;
border: 1px solid #ccc;
height: 30px;
padding: 0 5px;
}
.log-tag {
width: 30%;
border-style: unset;
outline: 0;
border: 1px solid #ccc;
height: 30px;
padding: 0 5px;
}
.log-tags {
padding: 1px 5px 0 0;
border-radius: 3px;
height: 24px;
display: inline-block;
vertical-align: top;
}
.log-tags {
padding: 1px 5px 0 0;
border-radius: 3px;
height: 24px;
display: inline-block;
vertical-align: top;
}
.selected {
display: inline-block;
padding: 0 3px;
border-radius: 3px;
overflow: hidden;
color: #3d444f;
border: 1px dashed #aaa;
font-size: 12px;
margin: 0 2px;
}
.selected {
display: inline-block;
padding: 0 3px;
border-radius: 3px;
overflow: hidden;
color: #3d444f;
border: 1px dashed #aaa;
font-size: 12px;
margin: 0 2px;
}
.remove-icon {
display: inline-block;
margin-left: 3px;
cursor: pointer;
}
.remove-icon {
display: inline-block;
margin-left: 3px;
cursor: pointer;
}
</style>

View File

@@ -14,12 +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">
<div class="log-tips" v-if="!logStore.logs.length">{{ t("noData") }}</div>
</LogTable>
<div class="mt-5 mb-5">
@@ -37,42 +32,40 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import LogTable from "./LogTable/Index.vue";
import { useLogStore } from "@/store/modules/log";
import { useDashboardStore } from "@/store/modules/dashboard";
import { ElMessage } from "element-plus";
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import LogTable from "./LogTable/Index.vue";
import { useLogStore } from "@/store/modules/log";
import { useDashboardStore } from "@/store/modules/dashboard";
import { ElMessage } from "element-plus";
const { t } = useI18n();
const logStore = useLogStore();
const dashboardStore = useDashboardStore();
const type = ref<string>(
dashboardStore.layerId === "BROWSER" ? "browser" : "service"
);
const pageSize = ref<number>(15);
const total = computed(() =>
logStore.logs.length === pageSize.value
? pageSize.value * logStore.conditions.paging.pageNum + 1
: pageSize.value * logStore.conditions.paging.pageNum
);
function updatePage(p: number) {
logStore.setLogCondition({
paging: { pageNum: p, pageSize: pageSize.value },
});
queryLogs();
}
async function queryLogs() {
const res = await logStore.getLogs();
if (res && res.errors) {
ElMessage.error(res.errors);
const { t } = useI18n();
const logStore = useLogStore();
const dashboardStore = useDashboardStore();
const type = ref<string>(dashboardStore.layerId === "BROWSER" ? "browser" : "service");
const pageSize = ref<number>(15);
const total = computed(() =>
logStore.logs.length === pageSize.value
? pageSize.value * logStore.conditions.paging.pageNum + 1
: pageSize.value * logStore.conditions.paging.pageNum,
);
function updatePage(p: number) {
logStore.setLogCondition({
paging: { pageNum: p, pageSize: pageSize.value },
});
queryLogs();
}
async function queryLogs() {
const res = await logStore.getLogs();
if (res && res.errors) {
ElMessage.error(res.errors);
}
}
}
</script>
<style lang="scss" scoped>
.log-tips {
width: 100%;
text-align: center;
margin: 50px 0;
}
.log-tips {
width: 100%;
text-align: center;
margin: 50px 0;
}
</style>

View File

@@ -15,30 +15,15 @@ limitations under the License. -->
<template>
<div class="log">
<div
class="log-header"
:class="
type === 'browser' ? ['browser-header', 'flex-h'] : 'service-header'
"
>
<div class="log-header" :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 :class="[item.label, ['message', 'stack'].includes(item.label) ? 'max-item' : '']">
{{ t(item.value) }}
</div>
</template>
</div>
<div v-if="type === 'browser'">
<LogBrowser
v-for="(item, index) in tableData"
:data="item"
:key="'browser' + index"
@select="setCurrentLog"
/>
<LogBrowser v-for="(item, index) in tableData" :data="item" :key="'browser' + index" @select="setCurrentLog" />
</div>
<div v-else>
<LogService
@@ -62,86 +47,85 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { ServiceLogConstants, BrowserLogConstants } from "./data";
import LogBrowser from "./LogBrowser.vue";
import LogService from "./LogService.vue";
import LogDetail from "./LogDetail.vue";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { ServiceLogConstants, BrowserLogConstants } from "./data";
import LogBrowser from "./LogBrowser.vue";
import LogService from "./LogService.vue";
import LogDetail from "./LogDetail.vue";
/*global defineProps */
const props = defineProps({
type: { type: String, default: "service" },
tableData: { type: Array, default: () => [] },
noLink: { type: Boolean, default: true },
});
const { t } = useI18n();
const currentLog = ref<any>({});
const showDetail = ref<boolean>(false);
const columns: any[] =
props.type === "browser" ? BrowserLogConstants : ServiceLogConstants;
/*global defineProps */
const props = defineProps({
type: { type: String, default: "service" },
tableData: { type: Array, default: () => [] },
noLink: { type: Boolean, default: true },
});
const { t } = useI18n();
const currentLog = ref<any>({});
const showDetail = ref<boolean>(false);
const columns: any[] = props.type === "browser" ? BrowserLogConstants : ServiceLogConstants;
function setCurrentLog(log: any) {
showDetail.value = true;
currentLog.value = log;
}
function setCurrentLog(log: any) {
showDetail.value = true;
currentLog.value = log;
}
</script>
<style lang="scss" scoped>
.log {
font-size: 12px;
height: 100%;
border-bottom: 1px solid #eee;
width: 100%;
overflow: auto;
}
.log-header {
white-space: nowrap;
user-select: none;
border-left: 0;
border-right: 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
.traceId {
width: 390px;
.log {
font-size: 12px;
height: 100%;
border-bottom: 1px solid #eee;
width: 100%;
overflow: auto;
}
.content,
.tags {
width: 300px;
.log-header {
white-space: nowrap;
user-select: none;
border-left: 0;
border-right: 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
.traceId {
width: 390px;
}
.content,
.tags {
width: 300px;
}
.serviceInstanceName,
.endpointName,
.serviceName {
width: 200px;
}
}
.serviceInstanceName,
.endpointName,
.serviceName {
width: 200px;
}
}
.log-header div {
display: inline-block;
padding: 0 5px;
border: 1px solid transparent;
border-right: 1px dotted silver;
line-height: 30px;
background-color: #f3f4f9;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.browser-header {
div {
min-width: 140px;
width: 10%;
.log-header div {
display: inline-block;
padding: 0 5px;
border: 1px solid transparent;
border-right: 1px dotted silver;
line-height: 30px;
background-color: #f3f4f9;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.max-item {
width: 20%;
}
}
.browser-header {
div {
min-width: 140px;
width: 10%;
}
.service-header div {
width: 140px;
}
.max-item {
width: 20%;
}
}
.service-header div {
width: 140px;
}
</style>

View File

@@ -14,18 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div
@click="showSelectSpan"
:class="['log-item', 'clearfix', 'flex-h']"
ref="logItem"
>
<div @click="showSelectSpan" :class="['log-item', 'clearfix', 'flex-h']" ref="logItem">
<div
v-for="(item, index) in columns"
:key="index"
:class="[
'log',
['message', 'stack'].includes(item.label) ? 'max-item' : '',
]"
:class="['log', ['message', 'stack'].includes(item.label) ? 'max-item' : '']"
>
<span v-if="item.label === 'time'">{{ dateFormat(data.time) }}</span>
<span v-else-if="item.label === 'errorUrl'">{{ data.pagePath }}</span>
@@ -38,74 +31,74 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { BrowserLogConstants } from "./data";
import { dateFormat } from "@/utils/dateFormat";
import { ref } from "vue";
import { BrowserLogConstants } from "./data";
import { dateFormat } from "@/utils/dateFormat";
/*global defineProps, defineEmits, NodeListOf */
const props = defineProps({
data: { type: Object as any, default: () => ({}) },
});
const columns = BrowserLogConstants;
const emit = defineEmits(["select"]);
const logItem = ref<any>(null);
/*global defineProps, defineEmits, NodeListOf */
const props = defineProps({
data: { type: Object as any, default: () => ({}) },
});
const columns = BrowserLogConstants;
const emit = defineEmits(["select"]);
const logItem = ref<any>(null);
function showSelectSpan() {
const items: NodeListOf<any> = document.querySelectorAll(".log-item");
function showSelectSpan() {
const items: NodeListOf<any> = document.querySelectorAll(".log-item");
for (const item of items) {
item.style.background = "#fff";
for (const item of items) {
item.style.background = "#fff";
}
logItem.value.style.background = "rgba(0, 0, 0, 0.1)";
emit("select", props.data);
}
logItem.value.style.background = "rgba(0, 0, 0, 0.1)";
emit("select", props.data);
}
</script>
<style lang="scss" scoped>
.log-item {
white-space: nowrap;
position: relative;
cursor: pointer;
}
.log-item {
white-space: nowrap;
position: relative;
cursor: pointer;
}
.log-item.selected {
background: rgba(0, 0, 0, 0.04);
}
.log-item.selected {
background: rgba(0, 0, 0, 0.04);
}
.log-item:not(.level0):hover {
background: rgba(0, 0, 0, 0.04);
}
.log-item:not(.level0):hover {
background: rgba(0, 0, 0, 0.04);
}
.log-item:hover {
background: rgba(0, 0, 0, 0.04) !important;
}
.log-item:hover {
background: rgba(0, 0, 0, 0.04) !important;
}
.log-item > div {
width: 10%;
min-width: 140px;
padding: 0 5px;
display: inline-block;
border: 1px solid transparent;
border-right: 1px dotted silver;
overflow: hidden;
line-height: 30px;
text-overflow: ellipsis;
white-space: nowrap;
}
.log-item > div {
width: 10%;
min-width: 140px;
padding: 0 5px;
display: inline-block;
border: 1px solid transparent;
border-right: 1px dotted silver;
overflow: hidden;
line-height: 30px;
text-overflow: ellipsis;
white-space: nowrap;
}
.max-item.log {
width: 20%;
}
.max-item.log {
width: 20%;
}
.log-item .text {
width: 100% !important;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.log-item .text {
width: 100% !important;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.log-item > div.log {
line-height: 30px;
}
.log-item > div.log {
line-height: 30px;
}
</style>

View File

@@ -14,16 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="log-detail">
<div
class="mb-10 clear rk-flex"
v-for="(item, index) in columns"
:key="index"
>
<div class="mb-10 clear rk-flex" v-for="(item, index) in columns" :key="index">
<span class="g-sm-4 grey">{{ t(item.value) }}:</span>
<span
v-if="['timestamp', 'time'].includes(item.label)"
class="g-sm-8 mb-10"
>
<span v-if="['timestamp', 'time'].includes(item.label)" class="g-sm-8 mb-10">
{{ dateFormat(currentLog[item.label]) }}
</span>
<textarea
@@ -40,35 +33,35 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { computed } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { Option } from "@/types/app";
import { dateFormat } from "@/utils/dateFormat";
import { computed } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import type { Option } from "@/types/app";
import { dateFormat } from "@/utils/dateFormat";
/*global defineProps */
const props = defineProps({
currentLog: { type: Object as PropType<any>, default: () => ({}) },
columns: { type: Array as PropType<Option[]>, default: () => [] },
});
const { t } = useI18n();
const logTags = computed(() => {
if (!props.currentLog.tags) {
return [];
}
return props.currentLog.tags.map((d: { key: string; value: string }) => {
return `${d.key} = ${d.value}`;
/*global defineProps */
const props = defineProps({
currentLog: { type: Object as PropType<any>, default: () => ({}) },
columns: { type: Array as PropType<Option[]>, default: () => [] },
});
const { t } = useI18n();
const logTags = computed(() => {
if (!props.currentLog.tags) {
return [];
}
return props.currentLog.tags.map((d: { key: string; value: string }) => {
return `${d.key} = ${d.value}`;
});
});
});
</script>
<style lang="scss" scoped>
.content {
max-width: 700px;
min-width: 500px;
min-height: 500px;
border: none;
outline: none;
color: #3d444f;
overflow: auto;
}
.content {
max-width: 700px;
min-width: 500px;
min-height: 500px;
border: none;
outline: none;
color: #3d444f;
overflow: auto;
}
</style>

View File

@@ -27,10 +27,7 @@ limitations under the License. -->
<span v-else-if="item.label === 'tags'">
{{ tags }}
</span>
<span
v-else-if="item.label === 'traceId' && !noLink"
:class="noLink ? '' : 'blue'"
>
<span v-else-if="item.label === 'traceId' && !noLink" :class="noLink ? '' : 'blue'">
{{ data[item.label] }}
</span>
<span v-else>{{ data[item.label] }}</span>
@@ -38,116 +35,112 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { computed, inject } from "vue";
import { ServiceLogConstants } from "./data";
import getDashboard from "@/hooks/useDashboardsSession";
import { useDashboardStore } from "@/store/modules/dashboard";
import { LayoutConfig } from "@/types/dashboard";
import { dateFormat } from "@/utils/dateFormat";
import { computed, inject } from "vue";
import { ServiceLogConstants } from "./data";
import getDashboard from "@/hooks/useDashboardsSession";
import { useDashboardStore } from "@/store/modules/dashboard";
import type { LayoutConfig } from "@/types/dashboard";
import { dateFormat } from "@/utils/dateFormat";
/*global defineProps, defineEmits, Recordable */
const props = defineProps({
data: { type: Object as any, default: () => ({}) },
noLink: { type: Boolean, default: true },
});
const dashboardStore = useDashboardStore();
const options: Recordable<LayoutConfig> = inject("options") || {};
const emit = defineEmits(["select"]);
const columns = ServiceLogConstants;
const tags = computed(() => {
if (!props.data.tags) {
return "";
}
return String(
props.data.tags.map(
(d: { key: string; value: string }) => `${d.key}=${d.value}`
)
);
});
/*global defineProps, defineEmits, Recordable */
const props = defineProps({
data: { type: Object as any, default: () => ({}) },
noLink: { type: Boolean, default: true },
});
const dashboardStore = useDashboardStore();
const options: Recordable<LayoutConfig> = inject("options") || {};
const emit = defineEmits(["select"]);
const columns = ServiceLogConstants;
const tags = computed(() => {
if (!props.data.tags) {
return "";
}
return String(props.data.tags.map((d: { key: string; value: string }) => `${d.key}=${d.value}`));
});
function selectLog(label: string, value: string) {
if (label === "traceId") {
if (!value) {
emit("select", props.data);
function selectLog(label: string, value: string) {
if (label === "traceId") {
if (!value) {
emit("select", props.data);
return;
}
linkTrace(value);
return;
}
linkTrace(value);
return;
emit("select", props.data);
}
function linkTrace(id: string) {
const { associationWidget } = getDashboard(dashboardStore.currentDashboard);
associationWidget(
(options.id as any) || "",
{
sourceId: options.id || "",
traceId: id,
},
"Trace",
);
}
emit("select", props.data);
}
function linkTrace(id: string) {
const { associationWidget } = getDashboard(dashboardStore.currentDashboard);
associationWidget(
(options.id as any) || "",
{
sourceId: options.id || "",
traceId: id,
},
"Trace"
);
}
</script>
<style lang="scss" scoped>
.log-item {
white-space: nowrap;
position: relative;
cursor: pointer;
.traceId {
width: 390px;
.log-item {
white-space: nowrap;
position: relative;
cursor: pointer;
span {
display: inline-block;
width: 100%;
line-height: 30px;
.traceId {
width: 390px;
cursor: pointer;
span {
display: inline-block;
width: 100%;
line-height: 30px;
}
.blue {
color: #448dfe;
}
}
.blue {
color: #448dfe;
.content,
.tags {
width: 300px;
}
.serviceInstanceName,
.endpointName,
.serviceName {
width: 200px;
}
}
.content,
.tags {
width: 300px;
.log-item:hover {
background: rgba(0, 0, 0, 0.04);
}
.serviceInstanceName,
.endpointName,
.serviceName {
width: 200px;
.log-item > div {
width: 140px;
padding: 0 5px;
display: inline-block;
border: 1px solid transparent;
border-right: 1px dotted silver;
overflow: hidden;
height: 30px;
line-height: 30px;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.log-item:hover {
background: rgba(0, 0, 0, 0.04);
}
.log-item .text {
width: 100%;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.log-item > div {
width: 140px;
padding: 0 5px;
display: inline-block;
border: 1px solid transparent;
border-right: 1px dotted silver;
overflow: hidden;
height: 30px;
line-height: 30px;
text-overflow: ellipsis;
white-space: nowrap;
}
.log-item .text {
width: 100%;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.log-item > div.method {
height: 100%;
padding: 3px 8px;
}
.log-item > div.method {
height: 100%;
padding: 3px 8px;
}
</style>

View File

@@ -16,10 +16,7 @@ limitations under the License. -->
<div class="flex-h content">
<Tasks />
<div class="vis-graph ml-5" v-loading="networkProfilingStore.loadNodes">
<process-topology
v-if="networkProfilingStore.nodes.length"
:config="config"
/>
<process-topology v-if="networkProfilingStore.nodes.length" :config="config" />
<div class="text" v-else>
{{ t("noData") }}
</div>
@@ -27,40 +24,40 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import Tasks from "./components/Tasks.vue";
import ProcessTopology from "./components/ProcessTopology.vue";
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import Tasks from "./components/Tasks.vue";
import ProcessTopology from "./components/ProcessTopology.vue";
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
/*global defineProps */
defineProps({
config: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
});
const networkProfilingStore = useNetworkProfilingStore();
const { t } = useI18n();
/*global defineProps */
defineProps({
config: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
});
const networkProfilingStore = useNetworkProfilingStore();
const { t } = useI18n();
</script>
<style lang="scss" scoped>
.content {
height: calc(100% - 30px);
width: 100%;
}
.content {
height: calc(100% - 30px);
width: 100%;
}
.vis-graph {
height: 100%;
flex-grow: 2;
min-width: 700px;
overflow: hidden;
position: relative;
width: calc(100% - 330px);
}
.vis-graph {
height: 100%;
flex-grow: 2;
min-width: 700px;
overflow: hidden;
position: relative;
width: calc(100% - 330px);
}
.text {
width: 100%;
text-align: center;
margin-top: 30px;
}
.text {
width: 100%;
text-align: center;
margin-top: 30px;
}
</style>

View File

@@ -33,7 +33,7 @@ class Orientation {
b1: number,
b2: number,
b3: number,
start_angle: number
start_angle: number,
) {
this.f0 = f0;
this.f1 = f1;
@@ -49,33 +49,11 @@ class Orientation {
const SQRT3 = Math.sqrt(3.0);
class Layout {
static Pointy = new Orientation(
SQRT3,
SQRT3 / 2.0,
0.0,
3.0 / 2.0,
SQRT3 / 3.0,
-1.0 / 3.0,
0.0,
2.0 / 3.0,
0.5
);
static Flat = new Orientation(
3.0 / 2.0,
0.0,
SQRT3 / 2.0,
SQRT3,
2.0 / 3.0,
0.0,
-1.0 / 3.0,
SQRT3 / 3.0,
0.0
);
static Pointy = new Orientation(SQRT3, SQRT3 / 2.0, 0.0, 3.0 / 2.0, SQRT3 / 3.0, -1.0 / 3.0, 0.0, 2.0 / 3.0, 0.5);
static Flat = new Orientation(3.0 / 2.0, 0.0, SQRT3 / 2.0, SQRT3, 2.0 / 3.0, 0.0, -1.0 / 3.0, SQRT3 / 3.0, 0.0);
static spacing(radius: number, isPointy = false): number[] {
return isPointy
? [SQRT3 * radius, 2 * radius * (3 / 4)]
: [2 * radius * (3 / 4), SQRT3 * radius];
return isPointy ? [SQRT3 * radius, 2 * radius * (3 / 4)] : [2 * radius * (3 / 4), SQRT3 * radius];
}
private radius = 1;
@@ -135,9 +113,7 @@ class Hex extends Int16Array {
}
get len(): number {
return Math.floor(
(Math.abs(this[0]) + Math.abs(this[1]) + Math.abs(this[2])) / 2
);
return Math.floor((Math.abs(this[0]) + Math.abs(this[1]) + Math.abs(this[2])) / 2);
}
}

View File

@@ -15,7 +15,7 @@
* limitations under the License.
*/
import icons from "@/assets/img/icons";
import { Call } from "@/types/topology";
import type { Call } from "@/types/topology";
// Control Point coordinates of quadratic Bezier curve
function computeControlPoint(ps: number[], pe: number[], arc = 0.5) {
@@ -24,10 +24,7 @@ function computeControlPoint(ps: number[], pe: number[], arc = 0.5) {
const theta = Math.atan(deltaY / deltaX);
const len = (Math.sqrt(deltaX * deltaX + deltaY * deltaY) / 2) * arc;
const newTheta = theta - Math.PI / 2;
return [
(ps[0] + pe[0]) / 2 + len * Math.cos(newTheta),
(ps[1] + pe[1]) / 2 + len * Math.sin(newTheta),
];
return [(ps[0] + pe[0]) / 2 + len * Math.cos(newTheta), (ps[1] + pe[1]) / 2 + len * Math.sin(newTheta)];
}
// Point coordinates of quadratic Bezier curve
/**
@@ -41,7 +38,7 @@ function quadraticBezier(
t: number,
ps: { x: number; y: number },
pc: { x: number; y: number },
pe: { x: number; y: number }
pe: { x: number; y: number },
) {
const x = (1 - t) * (1 - t) * ps.x + 2 * t * (1 - t) * pc.x + t * t * pe.x;
const y = (1 - t) * (1 - t) * ps.y + 2 * t * (1 - t) * pc.y + t * t * pe.y;
@@ -54,11 +51,7 @@ export function getMidpoint(d: Call) {
if (isNaN(d.target.x) || isNaN(d.target.y)) {
return [0, 0];
}
const controlPos = computeControlPoint(
[d.source.x, d.source.y],
[d.target.x, d.target.y],
0.5
);
const controlPos = computeControlPoint([d.source.x, d.source.y], [d.target.x, d.target.y], 0.5);
if (d.lowerArc) {
controlPos[1] = -controlPos[1];
}
@@ -66,7 +59,7 @@ export function getMidpoint(d: Call) {
0.5,
{ x: d.source.x, y: d.source.y },
{ x: controlPos[0], y: controlPos[1] },
{ x: d.target.x, y: d.target.y }
{ x: d.target.x, y: d.target.y },
);
return p;
}
@@ -77,11 +70,7 @@ export function linkPath(d: Call) {
if (isNaN(d.target.x) || isNaN(d.target.y)) {
return;
}
const controlPos = computeControlPoint(
[d.source.x, d.source.y - 5],
[d.target.x, d.target.y - 5],
0.5
);
const controlPos = computeControlPoint([d.source.x, d.source.y - 5], [d.target.x, d.target.y - 5], 0.5);
if (d.lowerArc) {
controlPos[1] = -controlPos[1] - 10;
}

View File

@@ -21,76 +21,60 @@ limitations under the License. -->
</div>
<div>
<div class="label">{{ t("minDuration") }} (ms)</div>
<el-input
size="small"
class="profile-input"
:min="0"
v-model="states.minDuration"
type="number"
/>
<el-input size="small" class="profile-input" :min="0" v-model="states.minDuration" type="number" />
</div>
<div>
<div class="label">{{ t("when4xx") }}</div>
<Radio
class="mb-5"
:value="states.when4xx"
:options="InitTaskField.Whenxx"
@change="changeWhen4xx"
/>
<Radio class="mb-5" :value="states.when4xx" :options="InitTaskField.Whenxx" @change="changeWhen4xx" />
</div>
<div>
<div class="label">{{ t("when5xx") }}</div>
<Radio
class="mb-5"
:value="states.when5xx"
:options="InitTaskField.Whenxx"
@change="changeWhen5xx"
/>
<Radio class="mb-5" :value="states.when5xx" :options="InitTaskField.Whenxx" @change="changeWhen5xx" />
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive } from "vue";
import { useI18n } from "vue-i18n";
import type { PropType } from "vue";
import { InitTaskField } from "./data";
import { NetworkProfilingRequest } from "@/types/ebpf";
import { reactive } from "vue";
import { useI18n } from "vue-i18n";
import type { PropType } from "vue";
import { InitTaskField } from "./data";
import type { NetworkProfilingRequest } from "@/types/ebpf";
/* global defineProps, defineEmits */
const emits = defineEmits(["change"]);
const props = defineProps({
condition: {
type: Object as PropType<NetworkProfilingRequest>,
default: () => ({ children: [] }),
},
key: {
type: Number,
default: () => 0,
},
});
const { t } = useI18n();
const states = reactive<NetworkProfilingRequest>(props.condition);
/* global defineEmits, defineProps */
const emits = defineEmits(["change"]);
const props = defineProps({
condition: {
type: Object as PropType<NetworkProfilingRequest>,
default: () => ({ children: [] }),
},
index: {
type: Number,
default: () => 0,
},
});
const { t } = useI18n();
const states = reactive<NetworkProfilingRequest>(props.condition);
function changeWhen5xx(value: string) {
states.when5xx = value;
emits("change", states, props.key);
}
function changeWhen4xx(value: string) {
states.when4xx = value;
emits("change", states, props.key);
}
function changeWhen5xx(value: string) {
states.when5xx = value;
emits("change", states, props.index);
}
function changeWhen4xx(value: string) {
states.when4xx = value;
emits("change", states, props.index);
}
</script>
<style lang="scss" scoped>
.date {
font-size: 12px;
}
.date {
font-size: 12px;
}
.label {
margin-top: 10px;
font-size: 14px;
}
.label {
margin-top: 10px;
font-size: 14px;
}
.profile-input {
width: 300px;
}
.profile-input {
width: 300px;
}
</style>

View File

@@ -16,11 +16,7 @@ limitations under the License. -->
<template>
<div class="profile-task">
<el-collapse v-model="activeNames">
<el-collapse-item
v-for="(item, index) in conditionsList"
:key="index"
:name="index"
>
<el-collapse-item v-for="(item, index) in conditionsList" :key="index" :name="index">
<template #title>
<div>
<span class="title">{{ `Rule - ${index + 1}` }}</span>
@@ -40,12 +36,7 @@ limitations under the License. -->
/>
</div>
</template>
<NewCondition
:name="index"
:condition="item"
:key="index"
@change="changeConfig"
/>
<NewCondition :name="index" :condition="item" :index="index" @change="changeConfig" />
</el-collapse-item>
</el-collapse>
<div>
@@ -56,84 +47,77 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { InitTaskField } from "./data";
import NewCondition from "./NewConditions.vue";
import { NetworkProfilingRequest } from "@/types/ebpf";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { InitTaskField } from "./data";
import NewCondition from "./NewConditions.vue";
import type { NetworkProfilingRequest } from "@/types/ebpf";
/* global defineEmits */
const emits = defineEmits(["create"]);
const { t } = useI18n();
const activeNames = ref([0]);
const conditionsList = ref<NetworkProfilingRequest[]>([
{
uriRegex: "",
when4xx: InitTaskField.Whenxx[1].value,
when5xx: InitTaskField.Whenxx[0].value,
minDuration: NaN,
},
]);
/* global defineEmits */
const emits = defineEmits(["create"]);
const { t } = useI18n();
const activeNames = ref([0]);
const conditionsList = ref<NetworkProfilingRequest[]>([
{
uriRegex: "",
when4xx: InitTaskField.Whenxx[1].value,
when5xx: InitTaskField.Whenxx[0].value,
minDuration: NaN,
},
]);
function changeConfig(
params: { [key: string]: number | string },
index: number
) {
const key: string = Object.keys(params)[0];
(conditionsList.value[index] as any)[key] = params[key];
}
function createTask() {
const list = conditionsList.value.map((d: NetworkProfilingRequest) => {
return {
uriRegex: d.uriRegex || undefined,
when4xx: d.when4xx === InitTaskField.Whenxx[0].value ? true : false,
when5xx: d.when5xx === InitTaskField.Whenxx[0].value ? true : false,
minDuration: isNaN(Number(d.minDuration))
? undefined
: Number(d.minDuration),
settings: {
requireCompleteRequest: true,
requireCompleteResponse: true,
},
};
});
emits("create", list);
}
function createConditions(e: any) {
e.stopPropagation();
conditionsList.value.push({
uriRegex: "",
when4xx: InitTaskField.Whenxx[0].value,
when5xx: InitTaskField.Whenxx[1].value,
minDuration: NaN,
});
activeNames.value = [conditionsList.value.length - 1];
}
function removeConditions(e: any, key: number) {
e.stopPropagation();
if (conditionsList.value.length === 1) {
return;
function changeConfig(params: { [key: string]: number | string }, index: number) {
const key: string = Object.keys(params)[0];
(conditionsList.value[index] as any)[key] = params[key];
}
function createTask() {
const list = conditionsList.value.map((d: NetworkProfilingRequest) => {
return {
uriRegex: d.uriRegex || undefined,
when4xx: d.when4xx === InitTaskField.Whenxx[0].value ? true : false,
when5xx: d.when5xx === InitTaskField.Whenxx[0].value ? true : false,
minDuration: isNaN(Number(d.minDuration)) ? undefined : Number(d.minDuration),
settings: {
requireCompleteRequest: true,
requireCompleteResponse: true,
},
};
});
emits("create", list);
}
function createConditions(e: any) {
e.stopPropagation();
conditionsList.value.push({
uriRegex: "",
when4xx: InitTaskField.Whenxx[0].value,
when5xx: InitTaskField.Whenxx[1].value,
minDuration: NaN,
});
activeNames.value = [conditionsList.value.length - 1];
}
function removeConditions(e: any, key: number) {
e.stopPropagation();
if (conditionsList.value.length === 1) {
return;
}
conditionsList.value = conditionsList.value.filter((_, index: number) => index !== key);
}
conditionsList.value = conditionsList.value.filter(
(_, index: number) => index !== key
);
}
</script>
<style lang="scss" scoped>
.profile-task {
width: 100%;
}
.profile-task {
width: 100%;
}
.create-task-btn {
width: 300px;
margin-top: 50px;
}
.create-task-btn {
width: 300px;
margin-top: 50px;
}
.title {
display: inline-block;
margin-right: 5px;
}
.title {
display: inline-block;
margin-right: 5px;
}
</style>

View File

@@ -14,20 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div ref="chart" class="process-topo">
<svg
class="process-svg"
:width="width"
:height="height"
@click="clickTopology"
>
<svg class="process-svg" :width="width" :height="height" @click="clickTopology">
<g class="svg-graph" :transform="`translate(${diff[0]}, ${diff[1]})`">
<g class="hex-polygon">
<path
:d="getHexPolygonVertices()"
stroke="#D5DDF6"
stroke-width="2"
fill="none"
/>
<path :d="getHexPolygonVertices()" stroke="#D5DDF6" stroke-width="2" fill="none" />
<text :x="0" :y="radius - 15" fill="#000" text-anchor="middle">
{{ selectorStore.currentPod.label }}
</text>
@@ -50,17 +40,8 @@ limitations under the License. -->
:x="(node.x || 0) - 15"
:y="(node.y || 0) - 15"
/>
<text
:x="node.x"
:y="(node.y || 0) + 28"
fill="#000"
text-anchor="middle"
>
{{
node.name.length > 10
? `${node.name.substring(0, 10)}...`
: node.name
}}
<text :x="node.x" :y="(node.y || 0) + 28" fill="#000" text-anchor="middle">
{{ node.name.length > 10 ? `${node.name.substring(0, 10)}...` : node.name }}
</text>
</g>
</g>
@@ -120,491 +101,473 @@ limitations under the License. -->
<TimeLine @get="getDates" />
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { ref, onMounted, watch } from "vue";
import * as d3 from "d3";
import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
import router from "@/router";
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { linkPath, getAnchor, getMidpoint } from "./Graph/linkProcess";
import { Call } from "@/types/topology";
import zoom from "../../components/utils/zoom";
import { ProcessNode } from "@/types/ebpf";
import { useThrottleFn } from "@vueuse/core";
import Settings from "./Settings.vue";
import { EntityType } from "@/views/dashboard/data";
import getDashboard from "@/hooks/useDashboardsSession";
import { Layout } from "./Graph/layout";
import TimeLine from "./TimeLine.vue";
import { useAppStoreWithOut } from "@/store/modules/app";
import icons from "@/assets/img/icons";
import type { PropType } from "vue";
import { ref, onMounted, watch } from "vue";
import * as d3 from "d3";
import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
import router from "@/router";
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { linkPath, getAnchor, getMidpoint } from "./Graph/linkProcess";
import type { Call } from "@/types/topology";
import zoom from "../../components/utils/zoom";
import type { ProcessNode } from "@/types/ebpf";
import { useThrottleFn } from "@vueuse/core";
import Settings from "./Settings.vue";
import { EntityType } from "@/views/dashboard/data";
import getDashboard from "@/hooks/useDashboardsSession";
import { Layout } from "./Graph/layout";
import TimeLine from "./TimeLine.vue";
import { useAppStoreWithOut } from "@/store/modules/app";
import icons from "@/assets/img/icons";
/*global Nullable, defineProps */
const props = defineProps({
config: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
});
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore();
const networkProfilingStore = useNetworkProfilingStore();
const height = ref<number>(100);
const width = ref<number>(100);
const chart = ref<Nullable<HTMLDivElement>>(null);
const tooltip = ref<Nullable<any>>(null);
const svg = ref<Nullable<any>>(null);
const graph = ref<Nullable<any>>(null);
const oldVal = ref<{ width: number; height: number }>({ width: 0, height: 0 });
const config = ref<any>(props.config || {});
const diff = ref<number[]>([220, 200]);
const radius = 210;
const dates = ref<Nullable<{ start: number; end: number }>>(null);
const nodeList = ref<ProcessNode[]>([]);
const currentNode = ref<Nullable<ProcessNode>>(null);
const origin = [0, 0];
onMounted(() => {
init();
oldVal.value = (chart.value && chart.value.getBoundingClientRect()) || {
width: 0,
height: 0,
};
});
async function init() {
if (!networkProfilingStore.nodes.length) {
return;
}
svg.value = d3.select(".process-svg");
graph.value = d3.select(".svg-graph");
tooltip.value = d3.select("#tooltip");
freshNodes();
useThrottleFn(resize, 500)();
}
function drawGraph() {
const dom = chart.value?.getBoundingClientRect() || {
height: 20,
width: 0,
};
height.value = (dom.height || 40) - 20;
width.value = dom.width;
diff.value[0] = (dom.width - radius * 2) / 2 + radius;
svg.value.call(zoom(d3, graph.value, diff.value));
}
function clickTopology(event: MouseEvent) {
event.stopPropagation();
event.preventDefault();
networkProfilingStore.setNode(null);
networkProfilingStore.setLink(null);
dashboardStore.selectWidget(props.config);
}
function hexGrid(n = 1, radius = 1, origin = [0, 0]) {
let x, y, yn, p;
const gLayout = new Layout(radius, origin);
const pos = [];
for (x = -n; x <= n; x++) {
y = Math.max(-n, -x - n); // 0
yn = Math.min(n, -x + n); // 1
for (y; y <= yn; y++) {
p = gLayout.axialToPixel(x, y);
pos.push(p);
}
}
return pos;
}
function createPolygon(radius: number, sides = 6, offset = 0) {
const poly: number[][] = [];
let i, rad;
for (i = 0; i < sides; i++) {
rad = Math.PI * 2 * (i / sides);
poly.push([
Math.cos(rad + offset) * radius,
Math.sin(rad + offset) * radius,
]);
}
return poly;
}
function getCirclePoint(radius: number, p = 1) {
const data = [];
for (let index = 0; index < 360; index = index + p) {
if (index < 230 || index > 310) {
let x = radius * Math.cos((Math.PI * 2 * index) / 360);
let y = radius * Math.sin((Math.PI * 2 * index) / 360);
data.push([x + origin[0], y + origin[1]]);
}
}
return data;
}
function getHexPolygonVertices() {
const p = {
count: 1,
radius, // layout hexagons radius 300
};
const polygon = createPolygon(p.radius, 6, 0);
const vertices: any = []; // a hexagon vertices
for (let v = 0; v < polygon.length; v++) {
vertices.push([origin[0] + polygon[v][0], origin[1] + polygon[v][1]]);
}
const linePath = d3.line();
linePath.curve(d3.curveLinearClosed);
return linePath(vertices) || "";
}
function createLayout() {
const dom: any = (chart.value && chart.value.getBoundingClientRect()) || {
width: 0,
height: 0,
};
if (isNaN(dom.width) || dom.width < 1) {
return;
}
const p = {
count: 1,
radius, // layout hexagons radius 300
};
const nodeArr = networkProfilingStore.nodes.filter(
(d: ProcessNode) => d.isReal || d.name === "UNKNOWN_LOCAL"
);
const count = nodeArr.length;
// layout
const centers = hexGrid(p.count, 68, origin); // cube centers
const cubeCenters = [];
if (count > 7) {
for (let i = 0; i < centers.length; i++) {
// const polygon = createPolygon(68, 6, 0);
// const vertices: any = []; // a hexagon vertices
// for (let v = 0; v < polygon.length; v++) {
// vertices.push([
// centers[i][0] + polygon[v][0],
// centers[i][1] + polygon[v][1],
// ]);
// }
// const linePath = d3.line();
// linePath.curve(d3.curveLinearClosed);
// graph.value
// .append("path")
// .attr("d", linePath(vertices))
// .attr("stroke", "#ccc")
// .attr("stroke-width", 1)
// .style("fill", "none");
let c = hexGrid(1, 20, centers[i]);
if (count < 15) {
c = [c[0], c[5]];
} else if (count < 22) {
c = [c[0], c[2], c[5]];
}
cubeCenters.push(...c);
}
shuffleArray(cubeCenters);
}
// for (let i = 0; i < cubeCenters.length; i++) {
// const polygon = createPolygon(20, 6, 0);
// const vertices: any = []; // a hexagon vertices
// for (let v = 0; v < polygon.length; v++) {
// vertices.push([
// cubeCenters[i][0] + polygon[v][0],
// cubeCenters[i][1] + polygon[v][1],
// ]);
// }
// const linePath = d3.line();
// linePath.curve(d3.curveLinearClosed);
// graph.value
// .append("path")
// .attr("d", linePath(vertices))
// .attr("stroke", "#ccc")
// .attr("stroke-width", 1)
// .style("fill", "none");
// }
let cubes = count > 7 ? cubeCenters : centers;
for (let v = 0; v < count; v++) {
const x = cubes[v][0];
const y = cubes[v][1];
nodeArr[v].x = x;
nodeArr[v].y = y;
}
const outNodes = networkProfilingStore.nodes.filter(
(d: ProcessNode) => !(d.isReal || d.name === "UNKNOWN_LOCAL")
);
const interval = 30;
let r = 250;
let pointArr = getCirclePoint(r, interval);
for (let v = 0; v < outNodes.length; v++) {
if (!pointArr[v]) {
r = r + 80;
pointArr = [...pointArr, ...getCirclePoint(r, interval)];
}
outNodes[v].x = pointArr[v][0];
outNodes[v].y = pointArr[v][1];
}
nodeList.value = [...nodeArr, ...outNodes];
const drag: any = d3.drag().on("drag", (d: ProcessNode) => {
moveNode(d);
/*global Nullable, defineProps */
const props = defineProps({
config: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
});
d3.selectAll(".node").call(drag);
}
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore();
const networkProfilingStore = useNetworkProfilingStore();
const height = ref<number>(100);
const width = ref<number>(100);
const chart = ref<Nullable<HTMLDivElement>>(null);
const tooltip = ref<Nullable<any>>(null);
const svg = ref<Nullable<any>>(null);
const graph = ref<Nullable<any>>(null);
const oldVal = ref<{ width: number; height: number }>({ width: 0, height: 0 });
const config = ref<any>(props.config || {});
const diff = ref<number[]>([220, 200]);
const radius = 210;
const dates = ref<Nullable<{ start: number; end: number }>>(null);
const nodeList = ref<ProcessNode[]>([]);
const currentNode = ref<Nullable<ProcessNode>>(null);
const origin = [0, 0];
function shuffleArray(array: number[][]) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
function handleLinkClick(event: any, d: Call) {
event.stopPropagation();
networkProfilingStore.setNode(null);
networkProfilingStore.setLink(d);
if (!config.value.linkDashboard) {
return;
}
const { dashboard } = getDashboard({
name: config.value.linkDashboard,
layer: dashboardStore.layerId,
entity: EntityType[7].value,
});
if (!dashboard) {
ElMessage.error(
`The dashboard named ${config.value.linkDashboard} doesn't exist`
);
return;
}
let times: any = {};
if (dates.value) {
times = dates.value;
} else {
const { taskStartTime, fixedTriggerDuration } =
networkProfilingStore.selectedNetworkTask;
const startTime =
fixedTriggerDuration > 1800
? taskStartTime + fixedTriggerDuration * 1000 - 30 * 60 * 1000
: taskStartTime;
times = {
start: startTime,
end: taskStartTime + fixedTriggerDuration * 1000,
onMounted(() => {
init();
oldVal.value = (chart.value && chart.value.getBoundingClientRect()) || {
width: 0,
height: 0,
};
}
const param = JSON.stringify({
...times,
step: appStore.duration.step,
utc: appStore.utc,
});
const path = `/dashboard/${dashboard.layer}/${EntityType[7].value}/${d.source.serviceId}/${d.source.serviceInstanceId}/${d.source.id}/${d.target.serviceId}/${d.target.serviceInstanceId}/${d.target.id}/${dashboard.name}/duration/${param}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
}
function updateSettings(param: unknown) {
config.value = param;
}
function setConfig() {
dashboardStore.selectWidget(props.config);
}
function getDates(times: any) {
dates.value = times;
}
function resize() {
const observer = new ResizeObserver((entries) => {
const entry = entries[0];
const cr = entry.contentRect;
if (
Math.abs(cr.width - oldVal.value.width) < 5 &&
Math.abs(cr.height - oldVal.value.height) < 5
) {
async function init() {
if (!networkProfilingStore.nodes.length) {
return;
}
svg.value = d3.select(".process-svg");
graph.value = d3.select(".svg-graph");
tooltip.value = d3.select("#tooltip");
freshNodes();
oldVal.value = { width: cr.width, height: cr.height };
});
if (chart.value) {
observer.observe(chart.value);
useThrottleFn(resize, 500)();
}
}
async function freshNodes() {
if (!networkProfilingStore.nodes.length) {
return;
function drawGraph() {
const dom = chart.value?.getBoundingClientRect() || {
height: 20,
width: 0,
};
height.value = (dom.height || 40) - 20;
width.value = dom.width;
diff.value[0] = (dom.width - radius * 2) / 2 + radius;
svg.value.call(zoom(d3, graph.value, diff.value));
}
drawGraph();
createLayout();
}
function startMoveNode(event: MouseEvent, d: ProcessNode) {
event.stopPropagation();
currentNode.value = d;
}
function stopMoveNode(event: MouseEvent) {
event.stopPropagation();
currentNode.value = null;
}
function moveNode(d: ProcessNode) {
if (!currentNode.value) {
return;
function clickTopology(event: MouseEvent) {
event.stopPropagation();
event.preventDefault();
networkProfilingStore.setNode(null);
networkProfilingStore.setLink(null);
dashboardStore.selectWidget(props.config);
}
const inNode =
currentNode.value.isReal || currentNode.value.name === "UNKNOWN_LOCAL";
const diff = inNode ? -20 : 20;
const inside = posInHex(d.x || 0, d.y || 0, diff);
if (inNode) {
if (!inside) {
function hexGrid(n = 1, radius = 1, origin = [0, 0]) {
let x, y, yn, p;
const gLayout = new Layout(radius, origin);
const pos = [];
for (x = -n; x <= n; x++) {
y = Math.max(-n, -x - n); // 0
yn = Math.min(n, -x + n); // 1
for (y; y <= yn; y++) {
p = gLayout.axialToPixel(x, y);
pos.push(p);
}
}
return pos;
}
function createPolygon(radius: number, sides = 6, offset = 0) {
const poly: number[][] = [];
let i, rad;
for (i = 0; i < sides; i++) {
rad = Math.PI * 2 * (i / sides);
poly.push([Math.cos(rad + offset) * radius, Math.sin(rad + offset) * radius]);
}
return poly;
}
function getCirclePoint(radius: number, p = 1) {
const data = [];
for (let index = 0; index < 360; index = index + p) {
if (index < 230 || index > 310) {
let x = radius * Math.cos((Math.PI * 2 * index) / 360);
let y = radius * Math.sin((Math.PI * 2 * index) / 360);
data.push([x + origin[0], y + origin[1]]);
}
}
return data;
}
function getHexPolygonVertices() {
const p = {
count: 1,
radius, // layout hexagons radius 300
};
const polygon = createPolygon(p.radius, 6, 0);
const vertices: any = []; // a hexagon vertices
for (let v = 0; v < polygon.length; v++) {
vertices.push([origin[0] + polygon[v][0], origin[1] + polygon[v][1]]);
}
const linePath = d3.line();
linePath.curve(d3.curveLinearClosed);
return linePath(vertices) || "";
}
function createLayout() {
const dom: any = (chart.value && chart.value.getBoundingClientRect()) || {
width: 0,
height: 0,
};
if (isNaN(dom.width) || dom.width < 1) {
return;
}
} else {
if (inside) {
const p = {
count: 1,
radius, // layout hexagons radius 300
};
const nodeArr = networkProfilingStore.nodes.filter((d: ProcessNode) => d.isReal || d.name === "UNKNOWN_LOCAL");
const count = nodeArr.length;
// layout
const centers = hexGrid(p.count, 68, origin); // cube centers
const cubeCenters = [];
if (count > 7) {
for (let i = 0; i < centers.length; i++) {
// const polygon = createPolygon(68, 6, 0);
// const vertices: any = []; // a hexagon vertices
// for (let v = 0; v < polygon.length; v++) {
// vertices.push([
// centers[i][0] + polygon[v][0],
// centers[i][1] + polygon[v][1],
// ]);
// }
// const linePath = d3.line();
// linePath.curve(d3.curveLinearClosed);
// graph.value
// .append("path")
// .attr("d", linePath(vertices))
// .attr("stroke", "#ccc")
// .attr("stroke-width", 1)
// .style("fill", "none");
let c = hexGrid(1, 20, centers[i]);
if (count < 15) {
c = [c[0], c[5]];
} else if (count < 22) {
c = [c[0], c[2], c[5]];
}
cubeCenters.push(...c);
}
shuffleArray(cubeCenters);
}
// for (let i = 0; i < cubeCenters.length; i++) {
// const polygon = createPolygon(20, 6, 0);
// const vertices: any = []; // a hexagon vertices
// for (let v = 0; v < polygon.length; v++) {
// vertices.push([
// cubeCenters[i][0] + polygon[v][0],
// cubeCenters[i][1] + polygon[v][1],
// ]);
// }
// const linePath = d3.line();
// linePath.curve(d3.curveLinearClosed);
// graph.value
// .append("path")
// .attr("d", linePath(vertices))
// .attr("stroke", "#ccc")
// .attr("stroke-width", 1)
// .style("fill", "none");
// }
let cubes = count > 7 ? cubeCenters : centers;
for (let v = 0; v < count; v++) {
const x = cubes[v][0];
const y = cubes[v][1];
nodeArr[v].x = x;
nodeArr[v].y = y;
}
const outNodes = networkProfilingStore.nodes.filter((d: ProcessNode) => !(d.isReal || d.name === "UNKNOWN_LOCAL"));
const interval = 30;
let r = 250;
let pointArr = getCirclePoint(r, interval);
for (let v = 0; v < outNodes.length; v++) {
if (!pointArr[v]) {
r = r + 80;
pointArr = [...pointArr, ...getCirclePoint(r, interval)];
}
outNodes[v].x = pointArr[v][0];
outNodes[v].y = pointArr[v][1];
}
nodeList.value = [...nodeArr, ...outNodes];
const drag: any = d3.drag().on("drag", (d: ProcessNode) => {
moveNode(d);
});
d3.selectAll(".node").call(drag);
}
function shuffleArray(array: number[][]) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
function handleLinkClick(event: any, d: Call) {
event.stopPropagation();
networkProfilingStore.setNode(null);
networkProfilingStore.setLink(d);
if (!config.value.linkDashboard) {
return;
}
}
nodeList.value = nodeList.value.map((node: ProcessNode) => {
if (currentNode.value && node.id === currentNode.value.id) {
node.x = d.x;
node.y = d.y;
const { dashboard } = getDashboard({
name: config.value.linkDashboard,
layer: dashboardStore.layerId,
entity: EntityType[7].value,
});
if (!dashboard) {
ElMessage.error(`The dashboard named ${config.value.linkDashboard} doesn't exist`);
return;
}
return node;
});
}
function posInHex(posX: number, posY: number, diff: number) {
const halfSideLen = (radius + diff) / 2;
const mathSqrt3 = Math.sqrt(3);
const dx = Math.abs(origin[0] - posX);
const dy = Math.abs(origin[1] - posY);
let times: any = {};
if (dates.value) {
times = dates.value;
} else {
const { taskStartTime, fixedTriggerDuration } = networkProfilingStore.selectedNetworkTask;
const startTime =
fixedTriggerDuration > 1800 ? taskStartTime + fixedTriggerDuration * 1000 - 30 * 60 * 1000 : taskStartTime;
times = {
start: startTime,
end: taskStartTime + fixedTriggerDuration * 1000,
};
}
const param = JSON.stringify({
...times,
step: appStore.duration.step,
utc: appStore.utc,
});
const path = `/dashboard/${dashboard.layer}/${EntityType[7].value}/${d.source.serviceId}/${d.source.serviceInstanceId}/${d.source.id}/${d.target.serviceId}/${d.target.serviceInstanceId}/${d.target.id}/${dashboard.name}/duration/${param}`;
const routeUrl = router.resolve({ path });
if (dx < halfSideLen) {
return dy <= halfSideLen * mathSqrt3;
} else {
const maxY = -mathSqrt3 * (dx - halfSideLen) + halfSideLen * mathSqrt3;
return dy < maxY;
window.open(routeUrl.href, "_blank");
}
}
function showNodeTip(d: ProcessNode, event: MouseEvent) {
const tipHtml = ` <div class="mb-5"><span class="grey">name: </span>${d.name}</div>`;
tooltip.value
.style("top", event.offsetY + "px")
.style("left", event.offsetX + "px")
.style("visibility", "visible")
.html(tipHtml);
}
function hideNodeTip() {
tooltip.value.style("visibility", "hidden");
}
function showLinkTip(link: Call, event: MouseEvent) {
const types = [...link.sourceComponents, ...link.targetComponents];
let l = "TCP";
if (types.includes("https")) {
l = "HTTPS";
function updateSettings(param: unknown) {
config.value = param;
}
if (types.includes("http")) {
l = "HTTP";
function setConfig() {
dashboardStore.selectWidget(props.config);
}
if (types.includes("tls")) {
l = "TLS";
function getDates(times: any) {
dates.value = times;
}
const tipHtml = `<div><span class="grey">${t(
"detectPoint"
)}: </span>${link.detectPoints.join(" | ")}</div>
function resize() {
const observer = new ResizeObserver((entries) => {
const entry = entries[0];
const cr = entry.contentRect;
if (Math.abs(cr.width - oldVal.value.width) < 5 && Math.abs(cr.height - oldVal.value.height) < 5) {
return;
}
freshNodes();
oldVal.value = { width: cr.width, height: cr.height };
});
if (chart.value) {
observer.observe(chart.value);
}
}
async function freshNodes() {
if (!networkProfilingStore.nodes.length) {
return;
}
drawGraph();
createLayout();
}
function startMoveNode(event: MouseEvent, d: ProcessNode) {
event.stopPropagation();
currentNode.value = d;
}
function stopMoveNode(event: MouseEvent) {
event.stopPropagation();
currentNode.value = null;
}
function moveNode(d: ProcessNode) {
if (!currentNode.value) {
return;
}
const inNode = currentNode.value.isReal || currentNode.value.name === "UNKNOWN_LOCAL";
const diff = inNode ? -20 : 20;
const inside = posInHex(d.x || 0, d.y || 0, diff);
if (inNode) {
if (!inside) {
return;
}
} else {
if (inside) {
return;
}
}
nodeList.value = nodeList.value.map((node: ProcessNode) => {
if (currentNode.value && node.id === currentNode.value.id) {
node.x = d.x;
node.y = d.y;
}
return node;
});
}
function posInHex(posX: number, posY: number, diff: number) {
const halfSideLen = (radius + diff) / 2;
const mathSqrt3 = Math.sqrt(3);
const dx = Math.abs(origin[0] - posX);
const dy = Math.abs(origin[1] - posY);
if (dx < halfSideLen) {
return dy <= halfSideLen * mathSqrt3;
} else {
const maxY = -mathSqrt3 * (dx - halfSideLen) + halfSideLen * mathSqrt3;
return dy < maxY;
}
}
function showNodeTip(d: ProcessNode, event: MouseEvent) {
const tipHtml = ` <div class="mb-5"><span class="grey">name: </span>${d.name}</div>`;
tooltip.value
.style("top", event.offsetY + "px")
.style("left", event.offsetX + "px")
.style("visibility", "visible")
.html(tipHtml);
}
function hideNodeTip() {
tooltip.value.style("visibility", "hidden");
}
function showLinkTip(link: Call, event: MouseEvent) {
const types = [...link.sourceComponents, ...link.targetComponents];
let l = "TCP";
if (types.includes("https")) {
l = "HTTPS";
}
if (types.includes("http")) {
l = "HTTP";
}
if (types.includes("tls")) {
l = "TLS";
}
const tipHtml = `<div><span class="grey">${t("detectPoint")}: </span>${link.detectPoints.join(" | ")}</div>
<div><span class="grey">Type: </span>${l}</div>`;
tooltip.value
.style("top", event.offsetY + "px")
.style("left", event.offsetX + "px")
.style("visibility", "visible")
.html(tipHtml);
}
function hideLinkTip() {
tooltip.value.style("visibility", "hidden");
}
watch(
() => networkProfilingStore.nodes,
() => {
freshNodes();
tooltip.value
.style("top", event.offsetY + "px")
.style("left", event.offsetX + "px")
.style("visibility", "visible")
.html(tipHtml);
}
);
function hideLinkTip() {
tooltip.value.style("visibility", "hidden");
}
watch(
() => networkProfilingStore.nodes,
() => {
freshNodes();
},
);
</script>
<style lang="scss">
.process-topo {
width: 100%;
height: 100%;
min-height: 150px;
min-width: 300px;
overflow: auto;
}
.process-svg {
width: 100%;
height: calc(100% - 10px);
cursor: move;
}
.topo-line-anchor {
cursor: pointer;
}
.switch-icon-edit {
cursor: pointer;
transition: all 0.5ms linear;
border: 1px solid #ccc;
color: #666;
display: inline-block;
padding: 5px;
border-radius: 3px;
position: absolute;
top: 10px;
right: 10px;
}
.range {
right: 50px;
}
.topo-call {
stroke-linecap: round;
stroke-width: 2px;
stroke-dasharray: 13 7;
fill: none;
animation: topo-dash 0.5s linear infinite;
}
@keyframes topo-dash {
from {
stroke-dashoffset: 20;
.process-topo {
width: 100%;
height: 100%;
min-height: 150px;
min-width: 300px;
overflow: auto;
}
to {
stroke-dashoffset: 0;
.process-svg {
width: 100%;
height: calc(100% - 10px);
cursor: move;
}
}
.time-ranges {
width: 100%;
padding: 10px;
}
.topo-line-anchor {
cursor: pointer;
}
.query {
margin-left: 510px;
}
.switch-icon-edit {
cursor: pointer;
transition: all 0.5ms linear;
border: 1px solid #ccc;
color: #666;
display: inline-block;
padding: 5px;
border-radius: 3px;
position: absolute;
top: 10px;
right: 10px;
}
#tooltip {
position: absolute;
visibility: hidden;
padding: 5px;
border: 1px solid #000;
border-radius: 3px;
background-color: #fff;
}
.range {
right: 50px;
}
.topo-call {
stroke-linecap: round;
stroke-width: 2px;
stroke-dasharray: 13 7;
fill: none;
animation: topo-dash 0.5s linear infinite;
}
@keyframes topo-dash {
from {
stroke-dashoffset: 20;
}
to {
stroke-dashoffset: 0;
}
}
.time-ranges {
width: 100%;
padding: 10px;
}
.query {
margin-left: 510px;
}
#tooltip {
position: absolute;
visibility: hidden;
padding: 5px;
border: 1px solid #000;
border-radius: 3px;
background-color: #fff;
}
</style>

View File

@@ -25,62 +25,54 @@ limitations under the License. -->
/>
</template>
<script lang="ts" setup>
import { ref, onMounted } from "vue";
import { useI18n } from "vue-i18n";
import { DashboardItem } from "@/types/dashboard";
import { useDashboardStore } from "@/store/modules/dashboard";
import { EntityType } from "@/views/dashboard/data";
/*global defineEmits */
const emits = defineEmits(["update"]);
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const linkDashboards = ref<
(DashboardItem & { label: string; value: string })[]
>([]);
import { ref, onMounted } from "vue";
import { useI18n } from "vue-i18n";
import type { DashboardItem } from "@/types/dashboard";
import { useDashboardStore } from "@/store/modules/dashboard";
import { EntityType } from "@/views/dashboard/data";
/*global defineEmits */
const emits = defineEmits(["update"]);
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const linkDashboards = ref<(DashboardItem & { label: string; value: string })[]>([]);
onMounted(() => {
getDashboards();
});
onMounted(() => {
getDashboards();
});
function getDashboards() {
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
linkDashboards.value = list.reduce(
(
prev: (DashboardItem & { label: string; value: string })[],
d: DashboardItem
) => {
if (
d.layer === dashboardStore.layerId &&
d.entity === EntityType[7].value
) {
prev.push({ ...d, label: d.name, value: d.name });
}
return prev;
},
[]
);
}
function getDashboards() {
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
linkDashboards.value = list.reduce(
(prev: (DashboardItem & { label: string; value: string })[], d: DashboardItem) => {
if (d.layer === dashboardStore.layerId && d.entity === EntityType[7].value) {
prev.push({ ...d, label: d.name, value: d.name });
}
return prev;
},
[],
);
}
function changeLinkDashboard(opt: { value: string }[]) {
const linkDashboard = opt.length ? opt[0].value : "";
const p = {
...dashboardStore.selectedGrid,
linkDashboard,
};
dashboardStore.selectWidget(p);
dashboardStore.setConfigs(p);
emits("update", p);
}
function changeLinkDashboard(opt: { value: string }[]) {
const linkDashboard = opt.length ? opt[0].value : "";
const p = {
...dashboardStore.selectedGrid,
linkDashboard,
};
dashboardStore.selectWidget(p);
dashboardStore.setConfigs(p);
emits("update", p);
}
</script>
<style lang="scss" scoped>
.label {
font-size: 12px;
margin-top: 10px;
}
.label {
font-size: 12px;
margin-top: 10px;
}
.inputs {
margin-top: 8px;
width: 270px;
margin-bottom: 30px;
}
.inputs {
margin-top: 8px;
width: 270px;
margin-bottom: 30px;
}
</style>

View File

@@ -18,18 +18,11 @@ limitations under the License. -->
<div class="profile-t-tool">
<span>{{ t("taskList") }}</span>
<span class="new-task cp" @click="createTask">
<Icon
:style="{ color: inProcess ? '#ccc' : '#000' }"
iconName="library_add"
size="middle"
/>
<Icon :style="{ color: inProcess ? '#ccc' : '#000' }" iconName="library_add" size="middle" />
</span>
</div>
<div class="profile-t-wrapper">
<div
class="no-data"
v-show="!networkProfilingStore.networkTasks.length"
>
<div class="no-data" v-show="!networkProfilingStore.networkTasks.length">
{{ t("noData") }}
</div>
<table class="profile-t">
@@ -42,8 +35,7 @@ limitations under the License. -->
<td
class="profile-td"
:class="{
selected:
networkProfilingStore.selectedNetworkTask.taskId === i.taskId,
selected: networkProfilingStore.selectedNetworkTask.taskId === i.taskId,
}"
>
<div class="ell">
@@ -51,9 +43,7 @@ limitations under the License. -->
{{ dateFormat(i.taskStartTime) }}
</span>
<span class="mr-10 sm">
{{
dateFormat(i.taskStartTime + i.fixedTriggerDuration * 1000)
}}
{{ dateFormat(i.taskStartTime + i.fixedTriggerDuration * 1000) }}
</span>
<span class="new-task" @click="viewDetail = true">
<Icon iconName="view" size="middle" />
@@ -68,247 +58,215 @@ limitations under the License. -->
</div>
</div>
</div>
<el-dialog
v-model="viewDetail"
:destroy-on-close="true"
fullscreen
@closed="viewDetail = false"
>
<el-dialog v-model="viewDetail" :destroy-on-close="true" fullscreen @closed="viewDetail = false">
<TaskDetails :details="networkProfilingStore.selectedNetworkTask" />
</el-dialog>
<el-dialog
v-model="newTask"
:title="t('taskTitle')"
:destroy-on-close="true"
fullscreen
@closed="newTask = false"
>
<el-dialog v-model="newTask" :title="t('taskTitle')" :destroy-on-close="true" fullscreen @closed="newTask = false">
<NewTask @create="saveNewTask" />
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
import { useSelectorStore } from "@/store/modules/selectors";
import { EBPFTaskList } from "@/types/ebpf";
import { ElMessage } from "element-plus";
import TaskDetails from "../../components/TaskDetails.vue";
import dateFormatStep, { dateFormat } from "@/utils/dateFormat";
import getLocalTime from "@/utils/localtime";
import { useAppStoreWithOut } from "@/store/modules/app";
import NewTask from "./NewTask.vue";
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
import { useSelectorStore } from "@/store/modules/selectors";
import type { EBPFTaskList } from "@/types/ebpf";
import { ElMessage } from "element-plus";
import TaskDetails from "../../components/TaskDetails.vue";
import dateFormatStep, { dateFormat } from "@/utils/dateFormat";
import getLocalTime from "@/utils/localtime";
import { useAppStoreWithOut } from "@/store/modules/app";
import NewTask from "./NewTask.vue";
/*global Nullable */
const { t } = useI18n();
const selectorStore = useSelectorStore();
const networkProfilingStore = useNetworkProfilingStore();
const appStore = useAppStoreWithOut();
const viewDetail = ref<boolean>(false);
const newTask = ref<boolean>(false);
const intervalFn = ref<Nullable<any>>(null);
const intervalKeepAlive = ref<Nullable<any>>(null);
const inProcess = ref<boolean>(false);
/*global Nullable */
const { t } = useI18n();
const selectorStore = useSelectorStore();
const networkProfilingStore = useNetworkProfilingStore();
const appStore = useAppStoreWithOut();
const viewDetail = ref<boolean>(false);
const newTask = ref<boolean>(false);
const intervalFn = ref<Nullable<any>>(null);
const intervalKeepAlive = ref<Nullable<any>>(null);
const inProcess = ref<boolean>(false);
fetchTasks();
fetchTasks();
async function changeTask(item: EBPFTaskList) {
networkProfilingStore.setSelectedNetworkTask(item);
intervalFn.value && clearInterval(intervalFn.value);
getTopology();
}
async function getTopology() {
const { taskStartTime, fixedTriggerDuration, taskId } =
networkProfilingStore.selectedNetworkTask;
const serviceInstanceId =
(selectorStore.currentPod && selectorStore.currentPod.id) || "";
const startTime =
fixedTriggerDuration > 1800
? taskStartTime + fixedTriggerDuration * 1000 - 30 * 60 * 1000
: taskStartTime;
let endTime = taskStartTime + fixedTriggerDuration * 1000;
if (taskStartTime + fixedTriggerDuration * 1000 > new Date().getTime()) {
endTime = new Date().getTime();
async function changeTask(item: EBPFTaskList) {
networkProfilingStore.setSelectedNetworkTask(item);
intervalFn.value && clearInterval(intervalFn.value);
getTopology();
}
const resp = await networkProfilingStore.getProcessTopology({
serviceInstanceId,
duration: {
start: dateFormatStep(
getLocalTime(appStore.utc, new Date(startTime)),
appStore.duration.step,
true
),
end: dateFormatStep(
getLocalTime(appStore.utc, new Date(endTime)),
appStore.duration.step,
true
),
step: appStore.duration.step,
async function getTopology() {
const { taskStartTime, fixedTriggerDuration, taskId } = networkProfilingStore.selectedNetworkTask;
const serviceInstanceId = (selectorStore.currentPod && selectorStore.currentPod.id) || "";
const startTime =
fixedTriggerDuration > 1800 ? taskStartTime + fixedTriggerDuration * 1000 - 30 * 60 * 1000 : taskStartTime;
let endTime = taskStartTime + fixedTriggerDuration * 1000;
if (taskStartTime + fixedTriggerDuration * 1000 > new Date().getTime()) {
endTime = new Date().getTime();
}
const resp = await networkProfilingStore.getProcessTopology({
serviceInstanceId,
duration: {
start: dateFormatStep(getLocalTime(appStore.utc, new Date(startTime)), appStore.duration.step, true),
end: dateFormatStep(getLocalTime(appStore.utc, new Date(endTime)), appStore.duration.step, true),
step: appStore.duration.step,
},
});
if (resp.errors) {
ElMessage.error(resp.errors);
}
const task = networkProfilingStore.networkTasks[0] || {};
if (task.taskId === taskId) {
inProcess.value = task.taskStartTime + task.fixedTriggerDuration * 1000 > new Date().getTime() ? true : false;
}
if (!inProcess.value) {
intervalFn.value && clearInterval(intervalFn.value);
intervalKeepAlive.value && clearInterval(intervalKeepAlive.value);
}
return resp;
}
function createTask() {
if (inProcess.value) {
return;
}
newTask.value = true;
}
async function saveNewTask(
params: {
uriRegex: string;
when4xx: string;
when5xx: string;
minDuration: number;
}[],
) {
const instanceId = (selectorStore.currentPod && selectorStore.currentPod.id) || "";
if (!instanceId) {
return ElMessage.error("No Instance ID");
}
const res = await networkProfilingStore.createNetworkTask(instanceId, params);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
if (!res.data.createEBPFNetworkProfiling.status) {
ElMessage.error(res.data.createEBPFNetworkProfiling.errorReason);
return;
}
newTask.value = false;
await fetchTasks();
}
function enableInterval() {
intervalFn.value = setInterval(getTopology, 30000);
}
function networkInterval() {
intervalKeepAlive.value = setInterval(keepAliveNetwork, 60000);
}
async function keepAliveNetwork() {
const res = await networkProfilingStore.keepNetworkProfiling(networkProfilingStore.selectedNetworkTask.taskId);
if (res.errors) {
intervalKeepAlive.value && clearInterval(intervalKeepAlive.value);
return ElMessage.error(res.errors);
}
if (!networkProfilingStore.aliveNetwork) {
intervalFn.value && clearInterval(intervalFn.value);
intervalKeepAlive.value && clearInterval(intervalKeepAlive.value);
}
}
async function fetchTasks() {
intervalFn.value && clearInterval(intervalFn.value);
intervalKeepAlive.value && clearInterval(intervalKeepAlive.value);
const serviceId = (selectorStore.currentService && selectorStore.currentService.id) || "";
const serviceInstanceId = (selectorStore.currentPod && selectorStore.currentPod.id) || "";
const res = await networkProfilingStore.getTaskList({
serviceId,
serviceInstanceId,
targets: ["NETWORK"],
});
if (res.errors) {
return ElMessage.error(res.errors);
}
if (!networkProfilingStore.networkTasks.length) {
return;
}
await getTopology();
if (inProcess.value) {
enableInterval();
networkInterval();
keepAliveNetwork();
}
}
watch(
() => selectorStore.currentPod,
() => {
inProcess.value = false;
fetchTasks();
},
});
if (resp.errors) {
ElMessage.error(resp.errors);
}
const task = networkProfilingStore.networkTasks[0] || {};
if (task.taskId === taskId) {
inProcess.value =
task.taskStartTime + task.fixedTriggerDuration * 1000 >
new Date().getTime()
? true
: false;
}
if (!inProcess.value) {
intervalFn.value && clearInterval(intervalFn.value);
intervalKeepAlive.value && clearInterval(intervalKeepAlive.value);
}
return resp;
}
function createTask() {
if (inProcess.value) {
return;
}
newTask.value = true;
}
async function saveNewTask(
params: {
uriRegex: string;
when4xx: string;
when5xx: string;
minDuration: number;
}[]
) {
const instanceId =
(selectorStore.currentPod && selectorStore.currentPod.id) || "";
if (!instanceId) {
return ElMessage.error("No Instance ID");
}
const res = await networkProfilingStore.createNetworkTask(instanceId, params);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
if (!res.data.createEBPFNetworkProfiling.status) {
ElMessage.error(res.data.createEBPFNetworkProfiling.errorReason);
return;
}
newTask.value = false;
await fetchTasks();
}
function enableInterval() {
intervalFn.value = setInterval(getTopology, 30000);
}
function networkInterval() {
intervalKeepAlive.value = setInterval(keepAliveNetwork, 60000);
}
async function keepAliveNetwork() {
const res = await networkProfilingStore.keepNetworkProfiling(
networkProfilingStore.selectedNetworkTask.taskId
);
if (res.errors) {
intervalKeepAlive.value && clearInterval(intervalKeepAlive.value);
return ElMessage.error(res.errors);
}
if (!networkProfilingStore.aliveNetwork) {
intervalFn.value && clearInterval(intervalFn.value);
intervalKeepAlive.value && clearInterval(intervalKeepAlive.value);
}
}
async function fetchTasks() {
intervalFn.value && clearInterval(intervalFn.value);
intervalKeepAlive.value && clearInterval(intervalKeepAlive.value);
const serviceId =
(selectorStore.currentService && selectorStore.currentService.id) || "";
const serviceInstanceId =
(selectorStore.currentPod && selectorStore.currentPod.id) || "";
const res = await networkProfilingStore.getTaskList({
serviceId,
serviceInstanceId,
targets: ["NETWORK"],
});
if (res.errors) {
return ElMessage.error(res.errors);
}
if (!networkProfilingStore.networkTasks.length) {
return;
}
await getTopology();
if (inProcess.value) {
enableInterval();
networkInterval();
keepAliveNetwork();
}
}
watch(
() => selectorStore.currentPod,
() => {
inProcess.value = false;
fetchTasks();
}
);
</script>
<style lang="scss" scoped>
.profile-task-list {
width: 330px;
height: calc(100% - 10px);
overflow: auto;
border-right: 1px solid rgba(0, 0, 0, 0.1);
}
.item span {
height: 21px;
}
.profile-td {
padding: 10px 5px 10px 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
&.selected {
background-color: #ededed;
.profile-task-list {
width: 330px;
height: calc(100% - 10px);
overflow: auto;
border-right: 1px solid rgba(0, 0, 0, 0.1);
}
}
.no-data {
text-align: center;
margin-top: 10px;
}
.profile-t-wrapper {
overflow: auto;
flex-grow: 1;
}
.profile-t {
width: 100%;
border-spacing: 0;
table-layout: fixed;
flex-grow: 1;
position: relative;
border: none;
}
.profile-tr {
&:hover {
background-color: rgba(0, 0, 0, 0.04);
.item span {
height: 21px;
}
}
.profile-t-tool {
padding: 10px 5px 10px 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
background: #f3f4f9;
width: 100%;
}
.profile-td {
padding: 10px 5px 10px 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
.new-task {
float: right;
}
&.selected {
background-color: #ededed;
}
}
.reload {
margin-left: 30px;
}
.no-data {
text-align: center;
margin-top: 10px;
}
.profile-t-wrapper {
overflow: auto;
flex-grow: 1;
}
.profile-t {
width: 100%;
border-spacing: 0;
table-layout: fixed;
flex-grow: 1;
position: relative;
border: none;
}
.profile-tr {
&:hover {
background-color: rgba(0, 0, 0, 0.04);
}
}
.profile-t-tool {
padding: 10px 5px 10px 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
background: #f3f4f9;
width: 100%;
}
.new-task {
float: right;
}
.reload {
margin-left: 30px;
}
</style>

View File

@@ -13,166 +13,141 @@ 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>
<el-popover
placement="bottom"
:width="600"
trigger="click"
@after-enter="showTimeLine"
>
<el-popover placement="bottom" :width="600" trigger="click" @after-enter="showTimeLine">
<template #reference>
<div class="switch-icon-edit">
<Icon size="middle" iconName="time_range" />
</div>
</template>
<div ref="timeRange" class="time-ranges"></div>
<el-button
class="query"
size="small"
type="primary"
@click="updateTopology"
>
<el-button class="query" size="small" type="primary" @click="updateTopology">
{{ t("query") }}
</el-button>
</el-popover>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
import { useSelectorStore } from "@/store/modules/selectors";
import { DataSet, Timeline } from "vis-timeline/standalone";
import "vis-timeline/styles/vis-timeline-graph2d.css";
import dateFormatStep from "@/utils/dateFormat";
import getLocalTime from "@/utils/localtime";
import { useAppStoreWithOut } from "@/store/modules/app";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
import { useSelectorStore } from "@/store/modules/selectors";
import { DataSet, Timeline } from "vis-timeline/standalone";
import "vis-timeline/styles/vis-timeline-graph2d.css";
import dateFormatStep from "@/utils/dateFormat";
import getLocalTime from "@/utils/localtime";
import { useAppStoreWithOut } from "@/store/modules/app";
/*global Nullable, defineEmits */
const emits = defineEmits(["get"]);
const { t } = useI18n();
const selectorStore = useSelectorStore();
const appStore = useAppStoreWithOut();
const networkProfilingStore = useNetworkProfilingStore();
const timeRange = ref<Nullable<HTMLDivElement>>(null);
const visGraph = ref<Nullable<any>>(null);
const task = ref<any[]>([]);
const isUpdate = ref<boolean>(false);
/*global Nullable, defineEmits */
const emits = defineEmits(["get"]);
const { t } = useI18n();
const selectorStore = useSelectorStore();
const appStore = useAppStoreWithOut();
const networkProfilingStore = useNetworkProfilingStore();
const timeRange = ref<Nullable<HTMLDivElement>>(null);
const visGraph = ref<Nullable<any>>(null);
const task = ref<any[]>([]);
const isUpdate = ref<boolean>(false);
function showTimeLine() {
visTimeline();
}
function visTimeline() {
if (!timeRange.value) {
return;
function showTimeLine() {
visTimeline();
}
if (!networkProfilingStore.selectedNetworkTask.taskId) {
return;
}
const { taskStartTime, fixedTriggerDuration, targetType, taskId } =
networkProfilingStore.selectedNetworkTask;
if (task.value[0] && task.value[0].data.taskId === taskId) {
if (isUpdate.value) {
function visTimeline() {
if (!timeRange.value) {
return;
}
if (!networkProfilingStore.selectedNetworkTask.taskId) {
return;
}
const { taskStartTime, fixedTriggerDuration, targetType, taskId } = networkProfilingStore.selectedNetworkTask;
if (task.value[0] && task.value[0].data.taskId === taskId) {
if (isUpdate.value) {
return;
}
}
if (visGraph.value) {
visGraph.value.destroy();
}
isUpdate.value = false;
let startTime = taskStartTime;
if (fixedTriggerDuration > 1800) {
startTime = taskStartTime + fixedTriggerDuration * 1000 - 30 * 60 * 1000;
}
const d = networkProfilingStore.networkTasks[0] || {};
let endTime = taskStartTime + fixedTriggerDuration * 1000;
if (taskStartTime + fixedTriggerDuration * 1000 > new Date().getTime() && taskId === d.taskId) {
endTime = new Date().getTime();
}
task.value = [
{
id: 1,
content: "",
start: new Date(startTime),
end: new Date(endTime),
data: networkProfilingStore.selectedNetworkTask,
className: targetType,
},
];
const items: any = new DataSet(task.value);
items.on("update", (event: string, properties: any) => {
task.value = properties.data;
});
const itemsAlwaysDraggable =
fixedTriggerDuration > 1800
? {
item: true,
range: true,
}
: undefined;
const editable =
fixedTriggerDuration > 1800
? {
updateTime: true,
}
: false;
const options = {
height: 150,
width: "100%",
locale: "en",
editable,
zoomMin: 1000 * 60,
zoomMax: 1000 * 60 * 60 * 24,
};
const opt = itemsAlwaysDraggable ? { ...options, itemsAlwaysDraggable } : options;
visGraph.value = new Timeline(timeRange.value, items, opt);
}
if (visGraph.value) {
visGraph.value.destroy();
}
isUpdate.value = false;
let startTime = taskStartTime;
if (fixedTriggerDuration > 1800) {
startTime = taskStartTime + fixedTriggerDuration * 1000 - 30 * 60 * 1000;
}
const d = networkProfilingStore.networkTasks[0] || {};
let endTime = taskStartTime + fixedTriggerDuration * 1000;
if (
taskStartTime + fixedTriggerDuration * 1000 > new Date().getTime() &&
taskId === d.taskId
) {
endTime = new Date().getTime();
}
task.value = [
{
id: 1,
content: "",
start: new Date(startTime),
end: new Date(endTime),
data: networkProfilingStore.selectedNetworkTask,
className: targetType,
},
];
const items: any = new DataSet(task.value);
items.on("update", (event: string, properties: any) => {
task.value = properties.data;
});
const itemsAlwaysDraggable =
fixedTriggerDuration > 1800
? {
item: true,
range: true,
}
: undefined;
const editable =
fixedTriggerDuration > 1800
? {
updateTime: true,
}
: false;
const options = {
height: 150,
width: "100%",
locale: "en",
editable,
zoomMin: 1000 * 60,
zoomMax: 1000 * 60 * 60 * 24,
};
const opt = itemsAlwaysDraggable
? { ...options, itemsAlwaysDraggable }
: options;
visGraph.value = new Timeline(timeRange.value, items, opt);
}
async function updateTopology() {
isUpdate.value = true;
emits("get", {
start: task.value[0].start.getTime(),
end: task.value[0].end.getTime(),
});
const serviceInstanceId =
(selectorStore.currentPod && selectorStore.currentPod.id) || "";
async function updateTopology() {
isUpdate.value = true;
emits("get", {
start: task.value[0].start.getTime(),
end: task.value[0].end.getTime(),
});
const serviceInstanceId = (selectorStore.currentPod && selectorStore.currentPod.id) || "";
const resp = await networkProfilingStore.getProcessTopology({
serviceInstanceId,
duration: {
start: dateFormatStep(
getLocalTime(appStore.utc, new Date(task.value[0].start)),
appStore.duration.step,
true
),
end: dateFormatStep(
getLocalTime(appStore.utc, new Date(task.value[0].end)),
appStore.duration.step,
true
),
step: appStore.duration.step,
},
});
if (resp.errors) {
ElMessage.error(resp.errors);
const resp = await networkProfilingStore.getProcessTopology({
serviceInstanceId,
duration: {
start: dateFormatStep(getLocalTime(appStore.utc, new Date(task.value[0].start)), appStore.duration.step, true),
end: dateFormatStep(getLocalTime(appStore.utc, new Date(task.value[0].end)), appStore.duration.step, true),
step: appStore.duration.step,
},
});
if (resp.errors) {
ElMessage.error(resp.errors);
}
return resp;
}
return resp;
}
</script>
<style lang="scss" scoped>
.switch-icon-edit {
cursor: pointer;
transition: all 0.5ms linear;
border: 1px solid #ccc;
color: #666;
display: inline-block;
padding: 5px;
border-radius: 3px;
position: absolute;
top: 10px;
right: 50px;
}
.switch-icon-edit {
cursor: pointer;
transition: all 0.5ms linear;
border: 1px solid #ccc;
color: #666;
display: inline-block;
padding: 5px;
border-radius: 3px;
position: absolute;
top: 10px;
right: 50px;
}
</style>

View File

@@ -34,48 +34,48 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import TaskList from "./components/TaskList.vue";
import SegmentList from "./components/SegmentList.vue";
import SpanTree from "./components/SpanTree.vue";
import StackTable from "./components/Stack/Index.vue";
import { useProfileStore } from "@/store/modules/profile";
import { ref } from "vue";
import TaskList from "./components/TaskList.vue";
import SegmentList from "./components/SegmentList.vue";
import SpanTree from "./components/SpanTree.vue";
import StackTable from "./components/Stack/Index.vue";
import { useProfileStore } from "@/store/modules/profile";
const loading = ref<boolean>(false);
const profileStore = useProfileStore();
const loading = ref<boolean>(false);
const profileStore = useProfileStore();
function loadTrees(l: boolean) {
loading.value = l;
}
function loadTrees(l: boolean) {
loading.value = l;
}
</script>
<style lang="scss" scoped>
.content {
height: calc(100% - 30px);
width: 100%;
}
.content {
height: calc(100% - 30px);
width: 100%;
}
.item {
height: 100%;
flex-grow: 2;
overflow: auto;
}
.item {
height: 100%;
flex-grow: 2;
overflow: auto;
}
.list {
width: 300px;
height: 100%;
}
.list {
width: 300px;
height: 100%;
}
.thread-stack {
padding: 5px;
height: calc(50% - 20px);
overflow: auto;
width: 100%;
}
.thread-stack {
padding: 5px;
height: calc(50% - 20px);
overflow: auto;
width: 100%;
}
.t-loading {
text-align: center;
width: 100%;
overflow: hidden;
height: calc(50% - 20px);
}
.t-loading {
text-align: center;
width: 100%;
overflow: hidden;
height: calc(50% - 20px);
}
</style>

View File

@@ -27,114 +27,103 @@ limitations under the License. -->
@query="searchEndpoints"
/>
</div>
<el-button
class="search-btn"
size="small"
type="primary"
@click="searchTasks"
>
<el-button class="search-btn" size="small" type="primary" @click="searchTasks">
{{ t("search") }}
</el-button>
<el-button class="search-btn" size="small" @click="createTask">
{{ t("newTask") }}
</el-button>
</div>
<el-dialog
v-model="newTask"
:destroy-on-close="true"
fullscreen
@closed="newTask = false"
>
<el-dialog v-model="newTask" :destroy-on-close="true" fullscreen @closed="newTask = false">
<NewTask @close="newTask = false" />
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useProfileStore } from "@/store/modules/profile";
import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
import NewTask from "./components/NewTask.vue";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { EntityType } from "../../data";
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useProfileStore } from "@/store/modules/profile";
import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
import NewTask from "./components/NewTask.vue";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { EntityType } from "../../data";
/*global defineProps */
const props = defineProps({
needQuery: { type: Boolean, default: true },
});
const profileStore = useProfileStore();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const { t } = useI18n();
const endpointName = ref<string>("");
const newTask = ref<boolean>(false);
if (props.needQuery) {
searchTasks();
searchEndpoints("");
}
async function searchEndpoints(keyword: string) {
if (!selectorStore.currentService) {
return;
}
const service = selectorStore.currentService.id;
const res = await profileStore.getEndpoints(service, keyword);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
endpointName.value = profileStore.endpoints[0].value;
}
function changeEndpoint(opt: any[]) {
endpointName.value = opt[0].value;
}
async function searchTasks() {
profileStore.setConditions({
serviceId:
(selectorStore.currentService && selectorStore.currentService.id) || "",
endpointName: endpointName.value,
/*global defineProps */
const props = defineProps({
needQuery: { type: Boolean, default: true },
});
const res = await profileStore.getTaskList();
const profileStore = useProfileStore();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const { t } = useI18n();
const endpointName = ref<string>("");
const newTask = ref<boolean>(false);
if (res.errors) {
ElMessage.error(res.errors);
}
}
function createTask() {
newTask.value = true;
}
watch(
() => selectorStore.currentService,
() => {
if (props.needQuery) {
searchTasks();
console.log("service");
searchEndpoints("");
}
);
watch(
() => appStore.durationTime,
() => {
if (dashboardStore.entity === EntityType[1].value) {
searchTasks();
async function searchEndpoints(keyword: string) {
if (!selectorStore.currentService) {
return;
}
const service = selectorStore.currentService.id;
const res = await profileStore.getEndpoints(service, keyword);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
endpointName.value = profileStore.endpoints[0].value;
}
function changeEndpoint(opt: any[]) {
endpointName.value = opt[0].value;
}
async function searchTasks() {
profileStore.setConditions({
serviceId: (selectorStore.currentService && selectorStore.currentService.id) || "",
endpointName: endpointName.value,
});
const res = await profileStore.getTaskList();
if (res.errors) {
ElMessage.error(res.errors);
}
}
);
function createTask() {
newTask.value = true;
}
watch(
() => selectorStore.currentService,
() => {
searchTasks();
console.log("service");
},
);
watch(
() => appStore.durationTime,
() => {
if (dashboardStore.entity === EntityType[1].value) {
searchTasks();
}
},
);
</script>
<style lang="scss" scoped>
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
}
.name {
width: 270px;
}
.name {
width: 270px;
}
</style>

View File

@@ -31,19 +31,9 @@ limitations under the License. -->
<div>
<div class="label">{{ t("monitorTime") }}</div>
<div>
<Radio
class="mb-5"
:value="monitorTime"
:options="InitTaskField.monitorTimeEn"
@change="changeMonitorTime"
/>
<Radio class="mb-5" :value="monitorTime" :options="InitTaskField.monitorTimeEn" @change="changeMonitorTime" />
<span class="date">
<TimePicker
:value="time"
position="bottom"
format="YYYY-MM-DD HH:mm:ss"
@input="changeTimeRange"
/>
<TimePicker :value="time" position="bottom" format="YYYY-MM-DD HH:mm:ss" @input="changeTimeRange" />
</span>
</div>
</div>
@@ -58,21 +48,11 @@ limitations under the License. -->
</div>
<div>
<div class="label">{{ t("minThreshold") }} (ms)</div>
<el-input-number
size="small"
class="profile-input"
:min="0"
v-model="minThreshold"
/>
<el-input-number size="small" class="profile-input" :min="0" v-model="minThreshold" />
</div>
<div>
<div class="label">{{ t("dumpPeriod") }}</div>
<Radio
class="mb-5"
:value="dumpPeriod"
:options="InitTaskField.dumpPeriod"
@change="changeDumpPeriod"
/>
<Radio class="mb-5" :value="dumpPeriod" :options="InitTaskField.dumpPeriod" @change="changeDumpPeriod" />
</div>
<div>
<div class="label">{{ t("maxSamplingCount") }}</div>
@@ -93,110 +73,110 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useProfileStore } from "@/store/modules/profile";
import { useSelectorStore } from "@/store/modules/selectors";
import { useAppStoreWithOut } from "@/store/modules/app";
import { ElMessage } from "element-plus";
import { InitTaskField } from "./data";
/* global defineEmits */
const emits = defineEmits(["close"]);
const profileStore = useProfileStore();
const selectorStore = useSelectorStore();
const appStore = useAppStoreWithOut();
const { t } = useI18n();
const endpointName = ref<string>("");
const monitorTime = ref<string>(InitTaskField.monitorTimeEn[0].value);
const monitorDuration = ref<string>(InitTaskField.monitorDuration[0].value);
const time = ref<Date>(appStore.durationRow.start);
const minThreshold = ref<number>(0);
const dumpPeriod = ref<string>(InitTaskField.dumpPeriod[0].value);
const maxSamplingCount = ref<string>(InitTaskField.maxSamplingCount[0].value);
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useProfileStore } from "@/store/modules/profile";
import { useSelectorStore } from "@/store/modules/selectors";
import { useAppStoreWithOut } from "@/store/modules/app";
import { ElMessage } from "element-plus";
import { InitTaskField } from "./data";
/* global defineEmits */
const emits = defineEmits(["close"]);
const profileStore = useProfileStore();
const selectorStore = useSelectorStore();
const appStore = useAppStoreWithOut();
const { t } = useI18n();
const endpointName = ref<string>("");
const monitorTime = ref<string>(InitTaskField.monitorTimeEn[0].value);
const monitorDuration = ref<string>(InitTaskField.monitorDuration[0].value);
const time = ref<Date>(appStore.durationRow.start);
const minThreshold = ref<number>(0);
const dumpPeriod = ref<string>(InitTaskField.dumpPeriod[0].value);
const maxSamplingCount = ref<string>(InitTaskField.maxSamplingCount[0].value);
async function searchEndpoints(keyword: string) {
if (!selectorStore.currentService) {
return;
async function searchEndpoints(keyword: string) {
if (!selectorStore.currentService) {
return;
}
const service = selectorStore.currentService.id;
const res = await profileStore.getEndpoints(service, keyword);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
endpointName.value = profileStore.taskEndpoints[0].value;
}
const service = selectorStore.currentService.id;
const res = await profileStore.getEndpoints(service, keyword);
if (res.errors) {
ElMessage.error(res.errors);
return;
function changeMonitorTime(opt: string) {
monitorTime.value = opt;
}
endpointName.value = profileStore.taskEndpoints[0].value;
}
function changeMonitorTime(opt: string) {
monitorTime.value = opt;
}
function changeMonitorDuration(val: string) {
monitorDuration.value = val;
}
function changeDumpPeriod(val: string) {
dumpPeriod.value = val;
}
function changeMaxSamplingCount(opt: any[]) {
maxSamplingCount.value = opt[0].value;
}
function changeEndpoint(opt: any[]) {
endpointName.value = opt[0].value;
}
async function createTask() {
emits("close");
const date = monitorTime.value === "0" ? new Date() : time.value;
const params = {
serviceId: selectorStore.currentService.id,
endpointName: endpointName.value,
startTime: date.getTime(),
duration: Number(monitorDuration.value),
minDurationThreshold: Number(minThreshold.value),
dumpPeriod: Number(dumpPeriod.value),
maxSamplingCount: Number(maxSamplingCount.value),
};
const res = await profileStore.createTask(params);
if (res.errors) {
ElMessage.error(res.errors);
return;
function changeMonitorDuration(val: string) {
monitorDuration.value = val;
}
const { tip } = res.data;
if (tip) {
ElMessage.error(tip);
return;
function changeDumpPeriod(val: string) {
dumpPeriod.value = val;
}
function changeMaxSamplingCount(opt: any[]) {
maxSamplingCount.value = opt[0].value;
}
function changeEndpoint(opt: any[]) {
endpointName.value = opt[0].value;
}
async function createTask() {
emits("close");
const date = monitorTime.value === "0" ? new Date() : time.value;
const params = {
serviceId: selectorStore.currentService.id,
endpointName: endpointName.value,
startTime: date.getTime(),
duration: Number(monitorDuration.value),
minDurationThreshold: Number(minThreshold.value),
dumpPeriod: Number(dumpPeriod.value),
maxSamplingCount: Number(maxSamplingCount.value),
};
const res = await profileStore.createTask(params);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
const { tip } = res.data;
if (tip) {
ElMessage.error(tip);
return;
}
ElMessage.success("Task created successfully");
}
function changeTimeRange(val: Date) {
time.value = val;
}
ElMessage.success("Task created successfully");
}
function changeTimeRange(val: Date) {
time.value = val;
}
</script>
<style lang="scss" scoped>
.profile-task {
margin: 0 auto;
width: 400px;
}
.profile-task {
margin: 0 auto;
width: 400px;
}
.date {
font-size: 12px;
}
.date {
font-size: 12px;
}
.label {
margin-top: 10px;
font-size: 14px;
}
.label {
margin-top: 10px;
font-size: 14px;
}
.profile-input {
width: 300px;
}
.profile-input {
width: 300px;
}
.create-task-btn {
width: 300px;
margin-top: 50px;
}
.create-task-btn {
width: 300px;
margin-top: 50px;
}
</style>

View File

@@ -20,12 +20,7 @@ limitations under the License. -->
{{ t("noData") }}
</div>
<table class="profile-t">
<tr
class="profile-tr cp"
v-for="(i, index) in profileStore.segmentList"
@click="selectTrace(i)"
:key="index"
>
<tr class="profile-tr cp" v-for="(i, index) in profileStore.segmentList" @click="selectTrace(i)" :key="index">
<td
class="profile-td"
:class="{
@@ -52,103 +47,103 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useProfileStore } from "@/store/modules/profile";
import { Trace } from "@/types/trace";
import { ElMessage } from "element-plus";
import { dateFormat } from "@/utils/dateFormat";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useProfileStore } from "@/store/modules/profile";
import type { Trace } from "@/types/trace";
import { ElMessage } from "element-plus";
import { dateFormat } from "@/utils/dateFormat";
const { t } = useI18n();
const profileStore = useProfileStore();
const selectedKey = ref<string>("");
const { t } = useI18n();
const profileStore = useProfileStore();
const selectedKey = ref<string>("");
async function selectTrace(item: Trace) {
profileStore.setCurrentSegment(item);
selectedKey.value = item.segmentId;
const res = await profileStore.getSegmentSpans({ segmentId: item.segmentId });
async function selectTrace(item: Trace) {
profileStore.setCurrentSegment(item);
selectedKey.value = item.segmentId;
const res = await profileStore.getSegmentSpans({ segmentId: item.segmentId });
if (res.errors) {
ElMessage.error(res.errors);
if (res.errors) {
ElMessage.error(res.errors);
}
}
}
</script>
<style lang="scss" scoped>
.profile-trace-wrapper {
width: 300px;
height: 50%;
overflow: auto;
.no-data {
text-align: center;
margin-top: 10px;
}
.profile-t-wrapper {
.profile-trace-wrapper {
width: 300px;
height: 50%;
overflow: auto;
flex-grow: 1;
border-right: 1px solid rgba(0, 0, 0, 0.1);
}
.profile-t-loading {
text-align: center;
position: absolute;
width: 100%;
height: 70px;
margin-top: 40px;
line-height: 88px;
overflow: hidden;
.no-data {
text-align: center;
margin-top: 10px;
}
.icon {
width: 30px;
height: 30px;
.profile-t-wrapper {
overflow: auto;
flex-grow: 1;
border-right: 1px solid rgba(0, 0, 0, 0.1);
}
.profile-t-loading {
text-align: center;
position: absolute;
width: 100%;
height: 70px;
margin-top: 40px;
line-height: 88px;
overflow: hidden;
.icon {
width: 30px;
height: 30px;
}
}
.profile-t {
width: 100%;
border-spacing: 0;
table-layout: fixed;
flex-grow: 1;
position: relative;
}
.profile-tr {
&:hover {
background-color: rgba(0, 0, 0, 0.04);
}
}
.profile-td {
padding: 5px 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
&.selected {
background-color: #ededed;
}
}
.profile-t-tool {
padding: 5px 10px;
font-weight: bold;
border-right: 1px solid rgba(0, 0, 0, 0.07);
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
background: #f3f4f9;
}
.log-item {
margin-top: 20px;
}
.profile-btn {
color: #3d444f;
padding: 1px 3px;
border-radius: 2px;
font-size: 12px;
}
}
.profile-t {
width: 100%;
border-spacing: 0;
table-layout: fixed;
flex-grow: 1;
position: relative;
.profile-segment {
border-top: 1px solid rgba(0, 0, 0, 0.07);
}
.profile-tr {
&:hover {
background-color: rgba(0, 0, 0, 0.04);
}
}
.profile-td {
padding: 5px 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
&.selected {
background-color: #ededed;
}
}
.profile-t-tool {
padding: 5px 10px;
font-weight: bold;
border-right: 1px solid rgba(0, 0, 0, 0.07);
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
background: #f3f4f9;
}
.log-item {
margin-top: 20px;
}
.profile-btn {
color: #3d444f;
padding: 1px 3px;
border-radius: 2px;
font-size: 12px;
}
}
.profile-segment {
border-top: 1px solid rgba(0, 0, 0, 0.07);
}
</style>

View File

@@ -38,10 +38,7 @@ limitations under the License. -->
<div class="profile-table">
<Table
:data="profileStore.segmentSpans"
:traceId="
profileStore.currentSegment.traceIds &&
profileStore.currentSegment.traceIds[0]
"
:traceId="profileStore.currentSegment.traceIds && profileStore.currentSegment.traceIds[0]"
:showBtnDetail="true"
headerType="profile"
@select="selectSpan"
@@ -50,132 +47,132 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import Table from "../../trace/components/Table/Index.vue";
import { useProfileStore } from "@/store/modules/profile";
import Selector from "@/components/Selector.vue";
import { Span } from "@/types/trace";
import { Option } from "@/types/app";
import { ElMessage } from "element-plus";
import { ProfileMode } from "./data";
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import Table from "../../trace/components/Table/Index.vue";
import { useProfileStore } from "@/store/modules/profile";
import Selector from "@/components/Selector.vue";
import type { Span } from "@/types/trace";
import type { Option } from "@/types/app";
import { ElMessage } from "element-plus";
import { ProfileMode } from "./data";
/* global defineEmits*/
const emits = defineEmits(["loading"]);
const { t } = useI18n();
const profileStore = useProfileStore();
const mode = ref<string>("include");
const message = ref<string>("");
const timeRange = ref<Array<{ start: number; end: number }>>([]);
const traceId = ref<string>("");
const traceIds = computed(() =>
(profileStore.currentSegment.traceIds || []).map((id: string) => ({
label: id,
value: id,
}))
);
/* global defineEmits*/
const emits = defineEmits(["loading"]);
const { t } = useI18n();
const profileStore = useProfileStore();
const mode = ref<string>("include");
const message = ref<string>("");
const timeRange = ref<Array<{ start: number; end: number }>>([]);
const traceId = ref<string>("");
const traceIds = computed(() =>
(profileStore.currentSegment.traceIds || []).map((id: string) => ({
label: id,
value: id,
})),
);
function selectSpan(span: Span) {
profileStore.setCurrentSpan(span);
}
function spanModeChange(item: Option[]) {
mode.value = item[0].value;
updateTimeRange();
}
function changeTraceId(opt: Option[]) {
traceId.value = opt[0].value;
}
async function analyzeProfile() {
emits("loading", true);
updateTimeRange();
const res = await profileStore.getProfileAnalyze({
segmentId: profileStore.currentSegment.segmentId,
timeRanges: timeRange.value,
});
emits("loading", false);
if (res.errors) {
ElMessage.error(res.errors);
function selectSpan(span: Span) {
profileStore.setCurrentSpan(span);
}
if (res.tip) {
message.value = res.tip;
function spanModeChange(item: Option[]) {
mode.value = item[0].value;
updateTimeRange();
}
}
function updateTimeRange() {
if (mode.value === "include") {
timeRange.value = [
{
start: profileStore.currentSpan.startTime,
end: profileStore.currentSpan.endTime,
},
];
} else {
const { children, startTime, endTime } = profileStore.currentSpan;
let dateRange = [];
function changeTraceId(opt: Option[]) {
traceId.value = opt[0].value;
}
if (!children || !children.length) {
async function analyzeProfile() {
emits("loading", true);
updateTimeRange();
const res = await profileStore.getProfileAnalyze({
segmentId: profileStore.currentSegment.segmentId,
timeRanges: timeRange.value,
});
emits("loading", false);
if (res.errors) {
ElMessage.error(res.errors);
}
if (res.tip) {
message.value = res.tip;
}
}
function updateTimeRange() {
if (mode.value === "include") {
timeRange.value = [
{
start: startTime,
end: endTime,
start: profileStore.currentSpan.startTime,
end: profileStore.currentSpan.endTime,
},
];
return;
}
for (const item of children) {
dateRange.push(
{
start: startTime,
end: item.startTime,
},
{
start: item.endTime,
end: endTime,
}
);
}
dateRange = dateRange.reduce((prev: any[], cur) => {
let isUpdate = false;
for (const item of prev) {
if (cur.start <= item.end && item.start <= cur.start) {
isUpdate = true;
item.start = item.start < cur.start ? cur.start : item.start;
item.end = item.end < cur.end ? item.end : cur.end;
}
} else {
const { children, startTime, endTime } = profileStore.currentSpan;
let dateRange = [];
if (!children || !children.length) {
timeRange.value = [
{
start: startTime,
end: endTime,
},
];
return;
}
if (!isUpdate) {
prev.push(cur);
for (const item of children) {
dateRange.push(
{
start: startTime,
end: item.startTime,
},
{
start: item.endTime,
end: endTime,
},
);
}
return prev;
}, []);
timeRange.value = dateRange.filter((item: any) => item.start !== item.end);
dateRange = dateRange.reduce((prev: any[], cur) => {
let isUpdate = false;
for (const item of prev) {
if (cur.start <= item.end && item.start <= cur.start) {
isUpdate = true;
item.start = item.start < cur.start ? cur.start : item.start;
item.end = item.end < cur.end ? item.end : cur.end;
}
}
if (!isUpdate) {
prev.push(cur);
}
return prev;
}, []);
timeRange.value = dateRange.filter((item: any) => item.start !== item.end);
}
}
}
</script>
<style lang="scss" scoped>
.profile-trace-dashboard {
padding: 5px;
flex-shrink: 0;
height: 50%;
width: 100%;
min-width: 800px;
}
.profile-trace-dashboard {
padding: 5px;
flex-shrink: 0;
height: 50%;
width: 100%;
min-width: 800px;
}
.profile-table {
height: calc(100% - 30px);
overflow: auto;
}
.profile-table {
height: calc(100% - 30px);
overflow: auto;
}
.profile-trace-detail-wrapper {
padding: 5px 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
width: 100%;
}
.profile-trace-detail-wrapper {
padding: 5px 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
width: 100%;
}
.profile-trace-detail-ids {
width: 300px;
}
.profile-trace-detail-ids {
width: 300px;
}
</style>

View File

@@ -36,93 +36,88 @@ limitations under the License. -->
</div>
<div class="dump-count">Dump Count</div>
</div>
<TableItem
:thread="thread"
v-for="(item, index) in tableData"
:data="item"
:key="'key' + index"
/>
<TableItem :thread="thread" v-for="(item, index) in tableData" :data="item" :key="'key' + index" />
<slot></slot>
</div>
</template>
<script lang="ts" setup>
import { useProfileStore } from "@/store/modules/profile";
import { ref, onMounted } from "vue";
import type { PropType } from "vue";
import TableItem from "./Item.vue";
import { useProfileStore } from "@/store/modules/profile";
import { ref, onMounted } from "vue";
import type { PropType } from "vue";
import TableItem from "./Item.vue";
/* global defineProps */
defineProps({
tableData: { type: Array as PropType<any>, default: () => [] },
highlightTop: { type: Boolean, default: false },
});
const dragger = ref<any>(null);
const thread = ref<number>(500);
const profileStore = useProfileStore();
/* global defineProps */
defineProps({
tableData: { type: Array as PropType<any>, default: () => [] },
highlightTop: { type: Boolean, default: false },
});
const dragger = ref<any>(null);
const thread = ref<number>(500);
const profileStore = useProfileStore();
onMounted(() => {
dragger.value.onmousedown = (event: any) => {
const diffX = event.clientX;
const copy = thread.value;
document.onmousemove = (documentEvent) => {
const moveX = documentEvent.clientX - diffX;
thread.value = copy + moveX;
onMounted(() => {
dragger.value.onmousedown = (event: any) => {
const diffX = event.clientX;
const copy = thread.value;
document.onmousemove = (documentEvent) => {
const moveX = documentEvent.clientX - diffX;
thread.value = copy + moveX;
};
document.onmouseup = () => {
document.onmousemove = null;
document.onmouseup = null;
};
};
document.onmouseup = () => {
document.onmousemove = null;
document.onmouseup = null;
};
};
});
});
function updateHighlightTop() {
profileStore.setHighlightTop();
}
function updateHighlightTop() {
profileStore.setHighlightTop();
}
</script>
<style lang="scss" scoped>
@import "./profile.scss";
@import "./profile.scss";
.dragger {
float: right;
}
.profile {
font-size: 12px;
height: 100%;
.profile-set-btn {
font-size: 12px;
border: 1px solid #ccc;
border-radius: 3px;
text-align: center;
width: 57px;
overflow: hidden;
display: inline-block;
height: 20px;
line-height: 20px;
position: absolute;
top: 4px;
right: 3px;
padding: 0 3px;
.dragger {
float: right;
}
}
.profile-header {
white-space: nowrap;
user-select: none;
border-left: 0;
border-right: 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.profile {
font-size: 12px;
height: 100%;
.profile-header div {
display: inline-block;
padding: 0 4px;
border-right: 1px dotted silver;
line-height: 30px;
background-color: #f3f4f9;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.profile-set-btn {
font-size: 12px;
border: 1px solid #ccc;
border-radius: 3px;
text-align: center;
width: 57px;
overflow: hidden;
display: inline-block;
height: 20px;
line-height: 20px;
position: absolute;
top: 4px;
right: 3px;
padding: 0 3px;
}
}
.profile-header {
white-space: nowrap;
user-select: none;
border-left: 0;
border-right: 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.profile-header div {
display: inline-block;
padding: 0 4px;
border-right: 1px dotted silver;
line-height: 30px;
background-color: #f3f4f9;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>

View File

@@ -21,89 +21,86 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { ref, onMounted, watch } from "vue";
import type { PropType } from "vue";
import Container from "./Container.vue";
import { useI18n } from "vue-i18n";
import { ref, onMounted, watch } from "vue";
import type { PropType } from "vue";
import Container from "./Container.vue";
const { t } = useI18n();
/* global defineProps */
const props = defineProps({
data: { type: Array as PropType<any>, default: () => [] },
highlightTop: { type: Boolean, default: false },
});
const tableData = ref<any>([]);
const { t } = useI18n();
/* global defineProps */
const props = defineProps({
data: { type: Array as PropType<any>, default: () => [] },
highlightTop: { type: Boolean, default: false },
});
const tableData = ref<any>([]);
onMounted(() => {
tableData.value = processTree();
});
function processTree() {
if (!props.data.length) {
return [];
}
const durationChildExcluded = props.data
.map((d: any) => {
return d.elements.map((item: any) => item.durationChildExcluded);
})
.flat(1);
function compare(val: number, val1: number) {
return val1 - val;
}
const topDur = durationChildExcluded
.sort(compare)
.filter((item: any, index: number) => index < 10 && item !== 0);
const trees = [];
for (const item of props.data) {
const newArr = sortArr(item.elements, topDur);
trees.push(...newArr);
}
return trees;
}
function sortArr(arr: any[], topDur: any) {
const copyArr = JSON.parse(JSON.stringify(arr));
const obj: any = {};
const res = [];
for (const item of copyArr) {
obj[item.id] = item;
}
for (const item of copyArr) {
item.topDur =
topDur.includes(item.durationChildExcluded) && props.highlightTop;
if (item.parentId === "0") {
res.push(item);
onMounted(() => {
tableData.value = processTree();
});
function processTree() {
if (!props.data.length) {
return [];
}
for (const key in obj) {
if (item.id === obj[key].parentId) {
if (item.children) {
item.children.push(obj[key]);
} else {
item.children = [obj[key]];
const durationChildExcluded = props.data
.map((d: any) => {
return d.elements.map((item: any) => item.durationChildExcluded);
})
.flat(1);
function compare(val: number, val1: number) {
return val1 - val;
}
const topDur = durationChildExcluded.sort(compare).filter((item: any, index: number) => index < 10 && item !== 0);
const trees = [];
for (const item of props.data) {
const newArr = sortArr(item.elements, topDur);
trees.push(...newArr);
}
return trees;
}
function sortArr(arr: any[], topDur: any) {
const copyArr = JSON.parse(JSON.stringify(arr));
const obj: any = {};
const res = [];
for (const item of copyArr) {
obj[item.id] = item;
}
for (const item of copyArr) {
item.topDur = topDur.includes(item.durationChildExcluded) && props.highlightTop;
if (item.parentId === "0") {
res.push(item);
}
for (const key in obj) {
if (item.id === obj[key].parentId) {
if (item.children) {
item.children.push(obj[key]);
} else {
item.children = [obj[key]];
}
}
}
}
return res;
}
return res;
}
watch(
() => [props.data, props.highlightTop],
() => {
if (!props.data.length) {
tableData.value = [];
return;
}
tableData.value = processTree();
}
);
watch(
() => [props.data, props.highlightTop],
() => {
if (!props.data.length) {
tableData.value = [];
return;
}
tableData.value = processTree();
},
);
</script>
<style lang="scss" scoped>
.profile-detail-chart-table {
height: 100%;
overflow: auto;
}
.profile-detail-chart-table {
height: 100%;
overflow: auto;
}
</style>

View File

@@ -14,10 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div>
<div
:class="['profile-item', 'level' + data.parentId]"
:style="{ color: data.topDur ? '#448dfe' : '#3d444f' }"
>
<div :class="['profile-item', 'level' + data.parentId]" :style="{ color: data.topDur ? '#448dfe' : '#3d444f' }">
<div
:class="['thread', 'level' + data.parentId]"
:style="{
@@ -42,112 +39,104 @@ limitations under the License. -->
<div class="exec-ms">{{ data.durationChildExcluded }}</div>
<div class="dump-count">{{ data.count }}</div>
</div>
<div
v-show="data.children && data.children.length && displayChildren"
class="children-trace"
>
<table-item
:thread="thread"
v-for="(item, index) in data.children"
:key="index"
:data="item"
/>
<div v-show="data.children && data.children.length && displayChildren" class="children-trace">
<table-item :thread="thread" v-for="(item, index) in data.children" :key="index" :data="item" />
</div>
</div>
</template>
<script lang="ts">
import { ref, defineComponent, toRefs } from "vue";
import type { PropType } from "vue";
import { ref, defineComponent, toRefs } from "vue";
import type { PropType } from "vue";
const props = {
data: { type: Object as PropType<any>, default: () => ({}) },
thread: { type: Number, default: 0 },
};
export default defineComponent({
name: "TableItem",
props,
setup(props) {
const displayChildren = ref<boolean>(true);
function toggle() {
displayChildren.value = !displayChildren.value;
}
return { toggle, displayChildren, ...toRefs(props) };
},
});
const props = {
data: { type: Object as PropType<any>, default: () => ({}) },
thread: { type: Number, default: 0 },
};
export default defineComponent({
name: "TableItem",
props,
setup(props) {
const displayChildren = ref<boolean>(true);
function toggle() {
displayChildren.value = !displayChildren.value;
}
return { toggle, displayChildren, ...toRefs(props) };
},
});
</script>
<style lang="scss" scoped>
@import "./profile.scss";
@import "./profile.scss";
.profile-item.level0 {
background: rgba(0, 0, 0, 0.04);
color: #448dfe;
&:hover {
.profile-item.level0 {
background: rgba(0, 0, 0, 0.04);
color: #448dfe;
&:hover {
background: rgba(0, 0, 0, 0.04);
color: #448dfe;
}
&::before {
position: absolute;
content: "";
width: 5px;
height: 100%;
background: #448dfe;
left: 0;
}
}
&::before {
position: absolute;
content: "";
width: 5px;
height: 100%;
background: #448dfe;
left: 0;
}
}
.profile-item {
position: relative;
white-space: nowrap;
}
.profile-item.selected {
background: rgba(0, 0, 0, 0.04);
}
.profile-item:not(.level0):hover {
background: rgba(0, 0, 0, 0.04);
}
.profile-item > div {
display: inline-block;
padding: 0 5px;
border: 1px solid transparent;
border-right: 1px dotted silver;
overflow: hidden;
line-height: 30px;
text-overflow: ellipsis;
white-space: nowrap;
}
.profile-item > div.method {
padding-left: 10px;
}
.profile-item div.exec-percent {
width: 10%;
height: 30px;
padding: 0 8px;
.outer-progress_bar {
width: 100%;
height: 6px;
border-radius: 3px;
background: rgb(63, 177, 227);
.profile-item {
position: relative;
margin-top: 11px;
border: none;
white-space: nowrap;
}
.inner-progress_bar {
position: absolute;
background: rgb(110, 64, 170);
height: 4px;
border-radius: 2px;
left: 0;
border: none;
top: 1px;
.profile-item.selected {
background: rgba(0, 0, 0, 0.04);
}
.profile-item:not(.level0):hover {
background: rgba(0, 0, 0, 0.04);
}
.profile-item > div {
display: inline-block;
padding: 0 5px;
border: 1px solid transparent;
border-right: 1px dotted silver;
overflow: hidden;
line-height: 30px;
text-overflow: ellipsis;
white-space: nowrap;
}
.profile-item > div.method {
padding-left: 10px;
}
.profile-item div.exec-percent {
width: 10%;
height: 30px;
padding: 0 8px;
.outer-progress_bar {
width: 100%;
height: 6px;
border-radius: 3px;
background: rgb(63, 177, 227);
position: relative;
margin-top: 11px;
border: none;
}
.inner-progress_bar {
position: absolute;
background: rgb(110, 64, 170);
height: 4px;
border-radius: 2px;
left: 0;
border: none;
top: 1px;
}
}
}
</style>

View File

@@ -21,12 +21,7 @@ limitations under the License. -->
{{ t("noData") }}
</div>
<table class="profile-t">
<tr
class="profile-tr cp"
v-for="(i, index) in profileStore.taskList"
@click="changeTask(i)"
:key="index"
>
<tr class="profile-tr cp" v-for="(i, index) in profileStore.taskList" @click="changeTask(i)" :key="index">
<td
class="profile-td"
:class="{
@@ -53,12 +48,7 @@ limitations under the License. -->
</div>
</div>
</div>
<el-dialog
v-model="viewDetail"
:destroy-on-close="true"
fullscreen
@closed="viewDetail = false"
>
<el-dialog v-model="viewDetail" :destroy-on-close="true" fullscreen @closed="viewDetail = false">
<div class="profile-detail flex-v">
<div>
<h5 class="mb-10">{{ t("task") }}.</h5>
@@ -82,9 +72,7 @@ limitations under the License. -->
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("minThreshold") }}:</span>
<span class="g-sm-8 wba">
{{ selectedTask.minDurationThreshold }} ms
</span>
<span class="g-sm-8 wba"> {{ selectedTask.minDurationThreshold }} ms </span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("dumpPeriod") }}:</span>
@@ -96,17 +84,8 @@ limitations under the License. -->
</div>
</div>
<div>
<h5
class="mb-10 mt-10"
v-show="selectedTask.logs && selectedTask.logs.length"
>
{{ t("logs") }}.
</h5>
<div
class="log-item"
v-for="(i, index) in Object.keys(instanceLogs)"
:key="index"
>
<h5 class="mb-10 mt-10" v-show="selectedTask.logs && selectedTask.logs.length"> {{ t("logs") }}. </h5>
<div class="log-item" v-for="(i, index) in Object.keys(instanceLogs)" :key="index">
<div class="mb-10 sm">
<span class="mr-10 grey">{{ t("instance") }}:</span>
<span>{{ i }}</span>
@@ -123,127 +102,123 @@ limitations under the License. -->
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useSelectorStore } from "@/store/modules/selectors";
import { useProfileStore } from "@/store/modules/profile";
import { TaskLog, TaskListItem } from "@/types/profile";
import { ElMessage } from "element-plus";
import { dateFormat } from "@/utils/dateFormat";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useSelectorStore } from "@/store/modules/selectors";
import { useProfileStore } from "@/store/modules/profile";
import type { TaskLog, TaskListItem } from "@/types/profile";
import { ElMessage } from "element-plus";
import { dateFormat } from "@/utils/dateFormat";
const { t } = useI18n();
const profileStore = useProfileStore();
const selectorStore = useSelectorStore();
const viewDetail = ref<boolean>(false);
const service = ref<string>("");
const selectedTask = ref<TaskListItem | Record<string, never>>({});
const instanceLogs = ref<TaskLog | any>({});
const { t } = useI18n();
const profileStore = useProfileStore();
const selectorStore = useSelectorStore();
const viewDetail = ref<boolean>(false);
const service = ref<string>("");
const selectedTask = ref<TaskListItem | Record<string, never>>({});
const instanceLogs = ref<TaskLog | any>({});
async function changeTask(item: TaskListItem) {
profileStore.setCurrentSegment({});
selectedTask.value = item;
const res = await profileStore.getSegmentList({ taskID: item.id });
if (res.errors) {
ElMessage.error(res.errors);
}
}
async function viewTask(e: Event, item: TaskListItem) {
window.event ? (window.event.cancelBubble = true) : e.stopPropagation();
viewDetail.value = true;
selectedTask.value = item;
service.value = (
selectorStore.services.filter((s: any) => s.id === item.serviceId)[0] || {}
).label;
const res = await profileStore.getTaskLogs({ taskID: item.id });
if (res.errors) {
ElMessage.error(res.errors);
return;
}
item.logs = profileStore.taskLogs;
instanceLogs.value = {};
for (const d of item.logs) {
if (instanceLogs.value[d.instanceName]) {
instanceLogs.value[d.instanceName].push({
operationType: d.operationType,
operationTime: d.operationTime,
});
} else {
instanceLogs.value[d.instanceName] = [
{ operationType: d.operationType, operationTime: d.operationTime },
];
async function changeTask(item: TaskListItem) {
profileStore.setCurrentSegment({});
selectedTask.value = item;
const res = await profileStore.getSegmentList({ taskID: item.id });
if (res.errors) {
ElMessage.error(res.errors);
}
}
selectedTask.value = item;
}
async function viewTask(e: Event, item: TaskListItem) {
window.event ? (window.event.cancelBubble = true) : e.stopPropagation();
viewDetail.value = true;
selectedTask.value = item;
service.value = (selectorStore.services.filter((s: any) => s.id === item.serviceId)[0] || {}).label;
const res = await profileStore.getTaskLogs({ taskID: item.id });
if (res.errors) {
ElMessage.error(res.errors);
return;
}
item.logs = profileStore.taskLogs;
instanceLogs.value = {};
for (const d of item.logs) {
if (instanceLogs.value[d.instanceName]) {
instanceLogs.value[d.instanceName].push({
operationType: d.operationType,
operationTime: d.operationTime,
});
} else {
instanceLogs.value[d.instanceName] = [{ operationType: d.operationType, operationTime: d.operationTime }];
}
}
selectedTask.value = item;
}
</script>
<style lang="scss" scoped>
.profile-task-list {
width: 300px;
height: calc((100% - 60px) / 2);
overflow: auto;
}
.item span {
height: 21px;
}
.profile-td {
padding: 5px 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
&.selected {
background-color: #ededed;
.profile-task-list {
width: 300px;
height: calc((100% - 60px) / 2);
overflow: auto;
}
}
.no-data {
text-align: center;
margin-top: 10px;
}
.profile-t-wrapper {
overflow: auto;
flex-grow: 1;
border-right: 1px solid rgba(0, 0, 0, 0.1);
}
.profile-t {
width: 100%;
border-spacing: 0;
table-layout: fixed;
flex-grow: 1;
position: relative;
}
.profile-tr {
&:hover {
background-color: rgba(0, 0, 0, 0.04);
.item span {
height: 21px;
}
}
.profile-segment {
border-top: 1px solid rgba(0, 0, 0, 0.07);
}
.profile-td {
padding: 5px 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
.profile-t-tool {
padding: 5px 10px;
font-weight: bold;
border-right: 1px solid rgba(0, 0, 0, 0.07);
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
background: #f3f4f9;
}
&.selected {
background-color: #ededed;
}
}
.log-item {
margin-top: 20px;
}
.no-data {
text-align: center;
margin-top: 10px;
}
.profile-btn {
color: #3d444f;
padding: 1px 3px;
border-radius: 2px;
font-size: 12px;
float: right;
}
.profile-t-wrapper {
overflow: auto;
flex-grow: 1;
border-right: 1px solid rgba(0, 0, 0, 0.1);
}
.profile-t {
width: 100%;
border-spacing: 0;
table-layout: fixed;
flex-grow: 1;
position: relative;
}
.profile-tr {
&:hover {
background-color: rgba(0, 0, 0, 0.04);
}
}
.profile-segment {
border-top: 1px solid rgba(0, 0, 0, 0.07);
}
.profile-t-tool {
padding: 5px 10px;
font-weight: bold;
border-right: 1px solid rgba(0, 0, 0, 0.07);
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
background: #f3f4f9;
}
.log-item {
margin-top: 20px;
}
.profile-btn {
color: #3d444f;
padding: 1px 3px;
border-radius: 2px;
font-size: 12px;
float: right;
}
</style>

View File

@@ -17,21 +17,19 @@ limitations under the License. -->
<PodTopology :config="config" v-else />
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import Graph from "./components/Graph.vue";
import PodTopology from "./components/PodTopology.vue";
import { EntityType } from "../../data";
import { useDashboardStore } from "@/store/modules/dashboard";
import type { PropType } from "vue";
import Graph from "./components/Graph.vue";
import PodTopology from "./components/PodTopology.vue";
import { EntityType } from "../../data";
import { useDashboardStore } from "@/store/modules/dashboard";
/*global defineProps */
defineProps({
config: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
});
const dashboardStore = useDashboardStore();
const isService = [EntityType[0].value, EntityType[1].value].includes(
dashboardStore.entity
);
/*global defineProps */
defineProps({
config: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
});
const dashboardStore = useDashboardStore();
const isService = [EntityType[0].value, EntityType[1].value].includes(dashboardStore.entity);
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -16,12 +16,7 @@ limitations under the License. -->
<div class="config-panel">
<div class="item mb-10">
<span class="label">{{ t("metrics") }}</span>
<SelectSingle
:value="currentMetric"
:options="metrics"
@change="changeMetric"
class="selectors"
/>
<SelectSingle :value="currentMetric" :options="metrics" @change="changeMetric" class="selectors" />
</div>
<div class="item mb-10">
<span class="label">{{ t("unit") }}</span>
@@ -56,116 +51,114 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref, computed, watch } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { CalculationOpts } from "../../../data";
import { useDashboardStore } from "@/store/modules/dashboard";
import { MetricConfigOpt } from "@/types/dashboard";
import { Option } from "element-plus/es/components/select-v2/src/select.types";
import { ref, computed, watch } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { CalculationOpts } from "../../../data";
import { useDashboardStore } from "@/store/modules/dashboard";
import type { MetricConfigOpt } from "@/types/dashboard";
import type { Option } from "element-plus/es/components/select-v2/src/select.types";
/*global defineEmits, defineProps */
const props = defineProps({
currentMetricConfig: {
type: Object as PropType<MetricConfigOpt>,
default: () => ({ unit: "" }),
},
type: { type: String, default: "" },
metrics: { type: Array as PropType<string[]>, default: () => [] },
});
const { t } = useI18n();
const emit = defineEmits(["update"]);
const dashboardStore = useDashboardStore();
const m = props.metrics.map((d: string) => {
return { label: d, value: d };
});
const metrics = ref<Option[]>(m.length ? m : [{ label: "", value: "" }]);
const currentMetric = ref<string>(metrics.value[0].value);
const currentConfig = ref<{ unit: string; calculation: string; label: string }>(
{
/*global defineEmits, defineProps */
const props = defineProps({
currentMetricConfig: {
type: Object as PropType<MetricConfigOpt>,
default: () => ({ unit: "" }),
},
type: { type: String, default: "" },
metrics: { type: Array as PropType<string[]>, default: () => [] },
});
const { t } = useI18n();
const emit = defineEmits(["update"]);
const dashboardStore = useDashboardStore();
const m = props.metrics.map((d: string) => {
return { label: d, value: d };
});
const metrics = ref<Option[]>(m.length ? m : [{ label: "", value: "" }]);
const currentMetric = ref<string>(metrics.value[0].value);
const currentConfig = ref<{ unit: string; calculation: string; label: string }>({
unit: "",
calculation: "",
label: "",
}
);
const currentIndex = ref<number>(0);
const getMetricConfig = computed(() => {
let config = [];
switch (props.type) {
case "linkServerMetricConfig":
config = dashboardStore.selectedGrid.linkServerMetricConfig;
break;
case "linkClientMetricConfig":
config = dashboardStore.selectedGrid.linkClientMetricConfig;
break;
case "nodeMetricConfig":
config = dashboardStore.selectedGrid.nodeMetricConfig;
break;
}
return config || [];
});
});
const currentIndex = ref<number>(0);
const getMetricConfig = computed(() => {
let config = [];
switch (props.type) {
case "linkServerMetricConfig":
config = dashboardStore.selectedGrid.linkServerMetricConfig;
break;
case "linkClientMetricConfig":
config = dashboardStore.selectedGrid.linkClientMetricConfig;
break;
case "nodeMetricConfig":
config = dashboardStore.selectedGrid.nodeMetricConfig;
break;
}
return config || [];
});
function changeConfigs(param: { [key: string]: string }) {
const metricConfig = getMetricConfig.value || [];
metricConfig[currentIndex.value] = {
...metricConfig[currentIndex.value],
...param,
};
currentConfig.value = metricConfig[currentIndex.value];
emit("update", { [props.type]: metricConfig });
}
function changeMetric(val: string) {
currentMetric.value = val;
const index = metrics.value.findIndex((d: Option) => d.value === val);
currentIndex.value = index || 0;
const config = getMetricConfig.value || [];
currentConfig.value = {
unit: "",
label: "",
calculation: "",
...config[index],
};
}
watch(
() => props.type,
() => {
const m = props.metrics.map((d: string) => {
return { label: d, value: d };
});
metrics.value = m.length ? m : [{ label: "", value: "" }];
currentMetric.value = metrics.value[0].value;
function changeConfigs(param: { [key: string]: string }) {
const metricConfig = getMetricConfig.value || [];
metricConfig[currentIndex.value] = {
...metricConfig[currentIndex.value],
...param,
};
currentConfig.value = metricConfig[currentIndex.value];
emit("update", { [props.type]: metricConfig });
}
function changeMetric(val: string) {
currentMetric.value = val;
const index = metrics.value.findIndex((d: Option) => d.value === val);
currentIndex.value = index || 0;
const config = getMetricConfig.value || [];
currentIndex.value = 0;
currentConfig.value = {
unit: "",
label: "",
calculation: "",
...config[0],
...config[index],
};
}
);
watch(
() => props.type,
() => {
const m = props.metrics.map((d: string) => {
return { label: d, value: d };
});
metrics.value = m.length ? m : [{ label: "", value: "" }];
currentMetric.value = metrics.value[0].value;
const config = getMetricConfig.value || [];
currentIndex.value = 0;
currentConfig.value = {
unit: "",
label: "",
calculation: "",
...config[0],
};
},
);
</script>
<style lang="scss" scoped>
.config-panel {
padding: 10px 5px;
position: relative;
min-height: 300px;
}
.config-panel {
padding: 10px 5px;
position: relative;
min-height: 300px;
}
.label {
width: 150px;
display: inline-block;
font-size: 12px;
}
.label {
width: 150px;
display: inline-block;
font-size: 12px;
}
.close {
position: absolute;
top: -8px;
right: -15px;
}
.close {
position: absolute;
top: -8px;
right: -15px;
}
.selectors {
width: 365px;
}
.selectors {
width: 365px;
}
</style>

View File

@@ -14,13 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="tool">
<span
v-show="
dashboardStore.entity === EntityType[2].value &&
config.graph &&
config.graph.showDepth
"
>
<span v-show="dashboardStore.entity === EntityType[2].value && config.graph && config.graph.showDepth">
<span class="label">{{ t("currentDepth") }}</span>
<Selector
class="inputs"
@@ -30,19 +24,10 @@ limitations under the License. -->
@change="changeDepth"
/>
</span>
<span
class="switch-icon ml-5"
title="Settings"
@click="setConfig"
v-if="dashboardStore.editMode"
>
<span class="switch-icon ml-5" title="Settings" @click="setConfig" v-if="dashboardStore.editMode">
<Icon size="middle" iconName="settings" />
</span>
<span
class="switch-icon ml-5"
title="Back to overview topology"
@click="backToTopology"
>
<span class="switch-icon ml-5" title="Back to overview topology" @click="backToTopology">
<Icon size="middle" iconName="keyboard_backspace" />
</span>
<div class="settings" v-if="showSettings">
@@ -67,298 +52,278 @@ limitations under the License. -->
}"
>
<i v-for="(item, index) of items" :key="index" @click="item.func">
<span
v-if="
['alarm', 'inspect'].includes(item.id) ||
(item.id === 'dashboard' && settings.nodeDashboard)
"
>
<span v-if="['alarm', 'inspect'].includes(item.id) || (item.id === 'dashboard' && settings.nodeDashboard)">
{{ item.title }}
</span>
</i>
</div>
</template>
<script lang="ts" setup>
import { watch } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { ref, onMounted, reactive } from "vue";
import { Option } from "@/types/app";
import { useTopologyStore } from "@/store/modules/topology";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { useAppStoreWithOut } from "@/store/modules/app";
import { EntityType, DepthList } from "../../../data";
import { ElMessage } from "element-plus";
import Sankey from "./Sankey.vue";
import Settings from "./Settings.vue";
import router from "@/router";
import getDashboard from "@/hooks/useDashboardsSession";
import { watch } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { ref, onMounted, reactive } from "vue";
import type { Option } from "@/types/app";
import { useTopologyStore } from "@/store/modules/topology";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { useAppStoreWithOut } from "@/store/modules/app";
import { EntityType, DepthList } from "../../../data";
import { ElMessage } from "element-plus";
import Sankey from "./Sankey.vue";
import Settings from "./Settings.vue";
import router from "@/router";
import getDashboard from "@/hooks/useDashboardsSession";
/*global defineProps */
const props = defineProps({
config: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore();
const topologyStore = useTopologyStore();
const appStore = useAppStoreWithOut();
const loading = ref<boolean>(false);
const height = ref<number>(100);
const width = ref<number>(100);
const showSettings = ref<boolean>(false);
const settings = ref<any>(props.config);
const operationsPos = reactive<{ x: number; y: number }>({ x: NaN, y: NaN });
const depth = ref<number>(
(props.config.graph && props.config.graph.depth) || 3
);
const items = [
{ id: "inspect", title: "Inspect", func: inspect },
{ id: "dashboard", title: "View Dashboard", func: goDashboard },
{ id: "alarm", title: "View Alarm", func: goAlarm },
];
onMounted(() => {
loadTopology(selectorStore.currentPod && selectorStore.currentPod.id);
window.addEventListener("resize", resize);
});
async function loadTopology(id: string) {
loading.value = true;
const resp = await getTopology(id);
loading.value = false;
if (resp && resp.errors) {
ElMessage.error(resp.errors);
}
const dom = document.querySelector(".topology")?.getBoundingClientRect() || {
height: 70,
width: 5,
};
height.value = dom.height - 70;
width.value = dom.width - 5;
topologyStore.getLinkClientMetrics(settings.value.linkClientMetrics || []);
topologyStore.getLinkServerMetrics(settings.value.linkServerMetrics || []);
topologyStore.queryNodeMetrics(settings.value.nodeMetrics || []);
}
function resize() {
const dom = document.querySelector(".topology")?.getBoundingClientRect() || {
height: 40,
width: 0,
};
height.value = dom.height - 40;
width.value = dom.width;
}
function inspect() {
const id = topologyStore.node.id;
topologyStore.setNode(null);
topologyStore.setLink(null);
loadTopology(id);
}
function goAlarm() {
const path = `/alarm`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
topologyStore.setNode(null);
}
function goDashboard() {
const entity =
dashboardStore.entity === EntityType[2].value
? EntityType[2].value
: EntityType[3].value;
const { dashboard } = getDashboard({
name: settings.value.nodeDashboard,
layer: dashboardStore.layerId,
entity,
/*global defineProps */
const props = defineProps({
config: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
});
if (!dashboard) {
ElMessage.error(
`The dashboard named ${settings.value.nodeDashboard} doesn't exist`
);
return;
}
const path = `/dashboard/${dashboard.layer}/${entity}/${topologyStore.node.serviceId}/${topologyStore.node.id}/${dashboard.name}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
topologyStore.setNode(null);
}
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore();
const topologyStore = useTopologyStore();
const appStore = useAppStoreWithOut();
const loading = ref<boolean>(false);
const height = ref<number>(100);
const width = ref<number>(100);
const showSettings = ref<boolean>(false);
const settings = ref<any>(props.config);
const operationsPos = reactive<{ x: number; y: number }>({ x: NaN, y: NaN });
const depth = ref<number>((props.config.graph && props.config.graph.depth) || 3);
const items = [
{ id: "inspect", title: "Inspect", func: inspect },
{ id: "dashboard", title: "View Dashboard", func: goDashboard },
{ id: "alarm", title: "View Alarm", func: goAlarm },
];
function setConfig() {
topologyStore.setNode(null);
showSettings.value = !showSettings.value;
dashboardStore.selectWidget(props.config);
}
onMounted(() => {
loadTopology(selectorStore.currentPod && selectorStore.currentPod.id);
window.addEventListener("resize", resize);
});
function updateConfig(config: any) {
settings.value = config;
}
function backToTopology() {
loadTopology(selectorStore.currentPod.id);
topologyStore.setNode(null);
}
function selectNodeLink(d: any) {
if (d.dataType === "edge") {
topologyStore.setNode(null);
topologyStore.setLink(d.data);
if (!settings.value.linkDashboard) {
return;
async function loadTopology(id: string) {
loading.value = true;
const resp = await getTopology(id);
loading.value = false;
if (resp && resp.errors) {
ElMessage.error(resp.errors);
}
const { sourceObj, targetObj } = d.data;
const entity =
dashboardStore.entity === EntityType[2].value
? EntityType[6].value
: EntityType[5].value;
const dom = document.querySelector(".topology")?.getBoundingClientRect() || {
height: 70,
width: 5,
};
height.value = dom.height - 70;
width.value = dom.width - 5;
topologyStore.getLinkClientMetrics(settings.value.linkClientMetrics || []);
topologyStore.getLinkServerMetrics(settings.value.linkServerMetrics || []);
topologyStore.queryNodeMetrics(settings.value.nodeMetrics || []);
}
function resize() {
const dom = document.querySelector(".topology")?.getBoundingClientRect() || {
height: 40,
width: 0,
};
height.value = dom.height - 40;
width.value = dom.width;
}
function inspect() {
const id = topologyStore.node.id;
topologyStore.setNode(null);
topologyStore.setLink(null);
loadTopology(id);
}
function goAlarm() {
const path = `/alarm`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
topologyStore.setNode(null);
}
function goDashboard() {
const entity = dashboardStore.entity === EntityType[2].value ? EntityType[2].value : EntityType[3].value;
const { dashboard } = getDashboard({
name: settings.value.linkDashboard,
name: settings.value.nodeDashboard,
layer: dashboardStore.layerId,
entity,
});
if (!dashboard) {
ElMessage.error(
`The dashboard named ${settings.value.linkDashboard} doesn't exist`
);
ElMessage.error(`The dashboard named ${settings.value.nodeDashboard} doesn't exist`);
return;
}
const path = `/dashboard/${dashboard.layer}/${entity}/${sourceObj.serviceId}/${sourceObj.id}/${targetObj.serviceId}/${targetObj.id}/${dashboard.name}`;
const path = `/dashboard/${dashboard.layer}/${entity}/${topologyStore.node.serviceId}/${topologyStore.node.id}/${dashboard.name}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
return;
}
topologyStore.setNode(d.data);
topologyStore.setLink(null);
operationsPos.x = d.event.event.clientX - 200;
operationsPos.y = d.event.event.clientY - 150;
}
async function changeDepth(opt: Option[] | any) {
depth.value = opt[0].value;
loadTopology(selectorStore.currentPod.id);
}
async function getTopology(id: string) {
let resp;
switch (dashboardStore.entity) {
case EntityType[2].value:
resp = await topologyStore.updateEndpointTopology(
[id],
Number(depth.value)
);
break;
case EntityType[5].value:
resp = await topologyStore.getInstanceTopology();
break;
case EntityType[4].value:
resp = await topologyStore.getInstanceTopology();
break;
}
return resp;
}
function handleClick(event: any) {
if (event.target.nodeName === "svg") {
topologyStore.setNode(null);
topologyStore.setLink(null);
}
function setConfig() {
topologyStore.setNode(null);
showSettings.value = !showSettings.value;
dashboardStore.selectWidget(props.config);
}
}
watch(
() => [selectorStore.currentPod],
() => {
function updateConfig(config: any) {
settings.value = config;
}
function backToTopology() {
loadTopology(selectorStore.currentPod.id);
topologyStore.setNode(null);
topologyStore.setLink(null);
}
);
watch(
() => appStore.durationTime,
() => {
function selectNodeLink(d: any) {
if (d.dataType === "edge") {
topologyStore.setNode(null);
topologyStore.setLink(d.data);
if (!settings.value.linkDashboard) {
return;
}
const { sourceObj, targetObj } = d.data;
const entity = dashboardStore.entity === EntityType[2].value ? EntityType[6].value : EntityType[5].value;
const { dashboard } = getDashboard({
name: settings.value.linkDashboard,
layer: dashboardStore.layerId,
entity,
});
if (!dashboard) {
ElMessage.error(`The dashboard named ${settings.value.linkDashboard} doesn't exist`);
return;
}
const path = `/dashboard/${dashboard.layer}/${entity}/${sourceObj.serviceId}/${sourceObj.id}/${targetObj.serviceId}/${targetObj.id}/${dashboard.name}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
return;
}
topologyStore.setNode(d.data);
topologyStore.setLink(null);
operationsPos.x = d.event.event.clientX - 200;
operationsPos.y = d.event.event.clientY - 150;
}
async function changeDepth(opt: Option[] | any) {
depth.value = opt[0].value;
loadTopology(selectorStore.currentPod.id);
topologyStore.setNode(null);
topologyStore.setLink(null);
}
);
async function getTopology(id: string) {
let resp;
switch (dashboardStore.entity) {
case EntityType[2].value:
resp = await topologyStore.updateEndpointTopology([id], Number(depth.value));
break;
case EntityType[5].value:
resp = await topologyStore.getInstanceTopology();
break;
case EntityType[4].value:
resp = await topologyStore.getInstanceTopology();
break;
}
return resp;
}
function handleClick(event: any) {
if (event.target.nodeName === "svg") {
topologyStore.setNode(null);
topologyStore.setLink(null);
dashboardStore.selectWidget(props.config);
}
}
watch(
() => [selectorStore.currentPod],
() => {
loadTopology(selectorStore.currentPod.id);
topologyStore.setNode(null);
topologyStore.setLink(null);
},
);
watch(
() => appStore.durationTime,
() => {
loadTopology(selectorStore.currentPod.id);
topologyStore.setNode(null);
topologyStore.setLink(null);
},
);
</script>
<style lang="scss" scoped>
.sankey {
margin-top: 10px;
background-color: #333840 !important;
color: #ddd;
}
.sankey {
margin-top: 10px;
background-color: #333840 !important;
color: #ddd;
}
.settings {
position: absolute;
top: 60px;
right: 10px;
width: 400px;
height: 600px;
background-color: #2b3037;
overflow: auto;
padding: 10px 15px;
border-radius: 3px;
color: #ccc;
transition: all 0.5ms linear;
z-index: 99;
text-align: left;
}
.tool {
text-align: right;
margin-top: 40px;
margin-right: 10px;
position: relative;
}
.switch-icon {
width: 30px;
height: 30px;
line-height: 30px;
text-align: center;
cursor: pointer;
transition: all 0.5ms linear;
background-color: #252a2f99;
color: #ddd;
display: inline-block;
border-radius: 3px;
}
.label {
color: #ccc;
display: inline-block;
margin-right: 5px;
}
.operations-list {
position: absolute;
padding: 10px 0;
color: #333;
cursor: pointer;
background-color: #fff;
border-radius: 3px;
span {
display: block;
height: 30px;
width: 140px;
line-height: 30px;
.settings {
position: absolute;
top: 60px;
right: 10px;
width: 400px;
height: 600px;
background-color: #2b3037;
overflow: auto;
padding: 10px 15px;
border-radius: 3px;
color: #ccc;
transition: all 0.5ms linear;
z-index: 99;
text-align: left;
padding: 0 15px;
}
span:hover {
color: #409eff;
background-color: #eee;
.tool {
text-align: right;
margin-top: 40px;
margin-right: 10px;
position: relative;
}
i {
font-style: normal;
.switch-icon {
width: 30px;
height: 30px;
line-height: 30px;
text-align: center;
cursor: pointer;
transition: all 0.5ms linear;
background-color: #252a2f99;
color: #ddd;
display: inline-block;
border-radius: 3px;
}
.label {
color: #ccc;
display: inline-block;
margin-right: 5px;
}
.operations-list {
position: absolute;
padding: 10px 0;
color: #333;
cursor: pointer;
background-color: #fff;
border-radius: 3px;
span {
display: block;
height: 30px;
width: 140px;
line-height: 30px;
text-align: left;
padding: 0 15px;
}
span:hover {
color: #409eff;
background-color: #eee;
}
i {
font-style: normal;
}
}
}
</style>

View File

@@ -17,139 +17,116 @@ limitations under the License. -->
<Graph :option="option" @select="clickChart" />
</template>
<script lang="ts" setup>
import { computed, PropType } from "vue";
import { useTopologyStore } from "@/store/modules/topology";
import { Node, Call } from "@/types/topology";
import { MetricConfigOpt } from "@/types/dashboard";
import { aggregation } from "@/hooks/useMetricsProcessor";
import type { PropType } from "vue";
import { computed } from "vue";
import { useTopologyStore } from "@/store/modules/topology";
import type { Node, Call } from "@/types/topology";
import type { MetricConfigOpt } from "@/types/dashboard";
import { aggregation } from "@/hooks/useMetricsProcessor";
/*global defineEmits, defineProps */
const props = defineProps({
settings: {
type: Object as PropType<any>,
default: () => ({}),
},
});
const emit = defineEmits(["click"]);
const topologyStore = useTopologyStore();
const option = computed(() => getOption());
function getOption() {
return {
tooltip: {
trigger: "item",
confine: true,
/*global defineEmits, defineProps */
const props = defineProps({
settings: {
type: Object as PropType<any>,
default: () => ({}),
},
series: {
type: "sankey",
left: 40,
top: 20,
right: 300,
bottom: 40,
emphasis: { focus: "adjacency" },
data: topologyStore.nodes,
links: topologyStore.calls,
label: {
color: "#fff",
formatter: (param: any) => param.data.name,
},
color: [
"#3fe1da",
"#6be6c1",
"#3fcfdc",
"#626c91",
"#3fbcde",
"#a0a7e6",
"#3fa9e1",
"#96dee8",
"#bf99f8",
],
itemStyle: {
borderWidth: 0,
},
lineStyle: {
color: "source",
opacity: 0.12,
},
});
const emit = defineEmits(["click"]);
const topologyStore = useTopologyStore();
const option = computed(() => getOption());
function getOption() {
return {
tooltip: {
position: "bottom",
formatter: (param: { data: any; dataType: string }) => {
if (param.dataType === "edge") {
return linkTooltip(param.data);
}
return nodeTooltip(param.data);
trigger: "item",
confine: true,
},
series: {
type: "sankey",
left: 40,
top: 20,
right: 300,
bottom: 40,
emphasis: { focus: "adjacency" },
data: topologyStore.nodes,
links: topologyStore.calls,
label: {
color: "#fff",
formatter: (param: any) => param.data.name,
},
color: ["#3fe1da", "#6be6c1", "#3fcfdc", "#626c91", "#3fbcde", "#a0a7e6", "#3fa9e1", "#96dee8", "#bf99f8"],
itemStyle: {
borderWidth: 0,
},
lineStyle: {
color: "source",
opacity: 0.12,
},
tooltip: {
position: "bottom",
formatter: (param: { data: any; dataType: string }) => {
if (param.dataType === "edge") {
return linkTooltip(param.data);
}
return nodeTooltip(param.data);
},
},
},
},
};
}
function linkTooltip(data: Call) {
const clientMetrics: string[] = Object.keys(topologyStore.linkClientMetrics);
const serverMetrics: string[] = Object.keys(topologyStore.linkServerMetrics);
const linkServerMetricConfig: MetricConfigOpt[] =
props.settings.linkServerMetricConfig || [];
const linkClientMetricConfig: MetricConfigOpt[] =
props.settings.linkClientMetricConfig || [];
};
}
function linkTooltip(data: Call) {
const clientMetrics: string[] = Object.keys(topologyStore.linkClientMetrics);
const serverMetrics: string[] = Object.keys(topologyStore.linkServerMetrics);
const linkServerMetricConfig: MetricConfigOpt[] = props.settings.linkServerMetricConfig || [];
const linkClientMetricConfig: MetricConfigOpt[] = props.settings.linkClientMetricConfig || [];
const htmlServer = serverMetrics.map((m, index) => {
const metric =
topologyStore.linkServerMetrics[m].values.find(
(val: { id: string; value: unknown }) => val.id === data.id
) || {};
if (metric) {
const opt: MetricConfigOpt = linkServerMetricConfig[index] || {};
const htmlServer = serverMetrics.map((m, index) => {
const metric =
topologyStore.linkServerMetrics[m].values.find((val: { id: string; value: unknown }) => val.id === data.id) ||
{};
if (metric) {
const opt: MetricConfigOpt = linkServerMetricConfig[index] || {};
const v = aggregation(metric.value, opt);
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${v} ${opt.unit || ""}</div>`;
}
});
const htmlClient = clientMetrics.map((m, index) => {
const opt: MetricConfigOpt = linkClientMetricConfig[index] || {};
const metric =
topologyStore.linkClientMetrics[m].values.find((val: { id: string; value: unknown }) => val.id === data.id) ||
{};
const v = aggregation(metric.value, opt);
return ` <div class="mb-5"><span class="grey">${
opt.label || m
}: </span>${v} ${opt.unit || ""}</div>`;
}
});
const htmlClient = clientMetrics.map((m, index) => {
const opt: MetricConfigOpt = linkClientMetricConfig[index] || {};
const metric =
topologyStore.linkClientMetrics[m].values.find(
(val: { id: string; value: unknown }) => val.id === data.id
) || {};
const v = aggregation(metric.value, opt);
return ` <div class="mb-5"><span class="grey">${
opt.label || m
}: </span>${v} ${opt.unit || ""}</div>`;
});
const html = [
`<div>${data.sourceObj.serviceName} -> ${data.targetObj.serviceName}</div>`,
...htmlServer,
...htmlClient,
].join(" ");
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${v} ${opt.unit || ""}</div>`;
});
const html = [
`<div>${data.sourceObj.serviceName} -> ${data.targetObj.serviceName}</div>`,
...htmlServer,
...htmlClient,
].join(" ");
return html;
}
return html;
}
function nodeTooltip(data: Node) {
const nodeMetrics: string[] = Object.keys(topologyStore.nodeMetricValue);
const nodeMetricConfig = props.settings.nodeMetricConfig || [];
const html = nodeMetrics.map((m, index) => {
const metric =
topologyStore.nodeMetricValue[m].values.find(
(val: { id: string; value: unknown }) => val.id === data.id
) || {};
const opt: MetricConfigOpt = nodeMetricConfig[index] || {};
const v = aggregation(metric.value, opt);
return ` <div class="mb-5"><span class="grey">${
opt.label || m
}: </span>${v} ${opt.unit || ""}</div>`;
});
return [` <div><span>name: </span>${data.serviceName}</div>`, ...html].join(
" "
);
}
function nodeTooltip(data: Node) {
const nodeMetrics: string[] = Object.keys(topologyStore.nodeMetricValue);
const nodeMetricConfig = props.settings.nodeMetricConfig || [];
const html = nodeMetrics.map((m, index) => {
const metric =
topologyStore.nodeMetricValue[m].values.find((val: { id: string; value: unknown }) => val.id === data.id) || {};
const opt: MetricConfigOpt = nodeMetricConfig[index] || {};
const v = aggregation(metric.value, opt);
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${v} ${opt.unit || ""}</div>`;
});
return [` <div><span>name: </span>${data.serviceName}</div>`, ...html].join(" ");
}
function clickChart(param: any) {
emit("click", param);
}
function clickChart(param: any) {
emit("click", param);
}
</script>
<style lang="scss" scoped>
.sankey {
width: 100%;
height: 100%;
}
.sankey {
width: 100%;
height: 100%;
}
</style>

View File

@@ -27,23 +27,13 @@ limitations under the License. -->
/>
<div class="label">
<span>{{ t("linkServerMetrics") }}</span>
<el-popover
placement="left"
:width="400"
trigger="click"
effect="dark"
v-if="states.linkServerMetrics.length"
>
<el-popover placement="left" :width="400" trigger="click" effect="dark" v-if="states.linkServerMetrics.length">
<template #reference>
<span @click="setConfigType('linkServerMetricConfig')">
<Icon class="cp ml-5" iconName="mode_edit" size="middle" />
</span>
</template>
<Metrics
:type="configType"
:metrics="states.linkServerMetrics"
@update="changeLinkServerMetrics"
/>
<Metrics :type="configType" :metrics="states.linkServerMetrics" @update="changeLinkServerMetrics" />
</el-popover>
</div>
<Selector
@@ -58,23 +48,13 @@ limitations under the License. -->
<span v-show="dashboardStore.entity !== EntityType[2].value">
<div class="label">
<span>{{ t("linkClientMetrics") }}</span>
<el-popover
placement="left"
:width="400"
trigger="click"
effect="dark"
v-if="states.linkClientMetrics.length"
>
<el-popover placement="left" :width="400" trigger="click" effect="dark" v-if="states.linkClientMetrics.length">
<template #reference>
<span @click="setConfigType('linkClientMetricConfig')">
<Icon class="cp ml-5" iconName="mode_edit" size="middle" />
</span>
</template>
<Metrics
:type="configType"
:metrics="states.linkClientMetrics"
@update="changeLinkClientMetrics"
/>
<Metrics :type="configType" :metrics="states.linkClientMetrics" @update="changeLinkClientMetrics" />
</el-popover>
</div>
<Selector
@@ -100,12 +80,7 @@ limitations under the License. -->
@change="changeNodeDashboard"
class="inputs"
/>
<div
v-show="isService"
v-for="(item, index) in items"
:key="index"
class="metric-item"
>
<div v-show="isService" v-for="(item, index) in items" :key="index" class="metric-item">
<Selector
:value="item.scope"
:options="ScopeType"
@@ -123,12 +98,7 @@ limitations under the License. -->
class="item mr-5"
/>
<span>
<Icon
class="cp mr-5"
iconName="remove_circle_outline"
size="middle"
@click="deleteItem(index)"
/>
<Icon class="cp mr-5" iconName="remove_circle_outline" size="middle" @click="deleteItem(index)" />
<Icon
class="cp"
v-show="index === items.length - 1 && items.length < 5"
@@ -140,23 +110,13 @@ limitations under the License. -->
</div>
<div class="label">
<span>{{ t("nodeMetrics") }}</span>
<el-popover
placement="left"
:width="400"
trigger="click"
effect="dark"
v-if="states.nodeMetrics.length"
>
<el-popover placement="left" :width="400" trigger="click" effect="dark" v-if="states.nodeMetrics.length">
<template #reference>
<span @click="setConfigType('nodeMetricConfig')">
<Icon class="cp ml-5" iconName="mode_edit" size="middle" />
</span>
</template>
<Metrics
:type="configType"
:metrics="states.nodeMetrics"
@update="changeNodeMetrics"
/>
<Metrics :type="configType" :metrics="states.nodeMetrics" @update="changeNodeMetrics" />
</el-popover>
</div>
<Selector
@@ -198,366 +158,315 @@ limitations under the License. -->
class="item"
/>
<span>
<Icon
class="cp delete"
iconName="remove_circle_outline"
size="middle"
@click="deleteMetric(index)"
/>
<Icon class="cp delete" iconName="remove_circle_outline" size="middle" @click="deleteMetric(index)" />
<Icon
class="cp"
iconName="add_circle_outlinecontrol_point"
size="middle"
v-show="
index === legend.metric.length - 1 && legend.metric.length < 5
"
v-show="index === legend.metric.length - 1 && legend.metric.length < 5"
@click="addMetric"
/>
</span>
<div v-show="index !== legend.metric.length - 1">&&</div>
</div>
<div class="label">Healthy Description</div>
<el-input
v-model="description.healthy"
placeholder="Please input description"
size="small"
class="mt-5"
/>
<el-input v-model="description.healthy" placeholder="Please input description" size="small" class="mt-5" />
<div class="label">Unhealthy Description</div>
<el-input
v-model="description.unhealthy"
placeholder="Please input description"
size="small"
class="mt-5"
/>
<el-button
@click="setLegend"
class="legend-btn"
size="small"
type="primary"
>
<el-input v-model="description.unhealthy" placeholder="Please input description" size="small" class="mt-5" />
<el-button @click="setLegend" class="legend-btn" size="small" type="primary">
{{ t("setLegend") }}
</el-button>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useTopologyStore } from "@/store/modules/topology";
import { ElMessage } from "element-plus";
import { MetricCatalog, ScopeType, MetricConditions } from "../../../data";
import { Option } from "@/types/app";
import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
import { Node } from "@/types/topology";
import { DashboardItem, MetricConfigOpt } from "@/types/dashboard";
import { EntityType, LegendOpt, MetricsType } from "../../../data";
import Metrics from "./Metrics.vue";
import { reactive, ref } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useTopologyStore } from "@/store/modules/topology";
import { ElMessage } from "element-plus";
import { MetricCatalog, ScopeType, MetricConditions } from "../../../data";
import type { Option } from "@/types/app";
import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
import type { Node } from "@/types/topology";
import type { DashboardItem, MetricConfigOpt } from "@/types/dashboard";
import { EntityType, LegendOpt, MetricsType } from "../../../data";
import Metrics from "./Metrics.vue";
/*global defineEmits */
const emit = defineEmits(["update", "updateNodes"]);
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const topologyStore = useTopologyStore();
const { selectedGrid } = dashboardStore;
const nodeDashboard =
selectedGrid.nodeDashboard && selectedGrid.nodeDashboard.length
? selectedGrid.nodeDashboard
: "";
const isService = [EntityType[0].value, EntityType[1].value].includes(
dashboardStore.entity
);
const items = reactive<
{
scope: string;
dashboard: string;
}[]
>(isService && nodeDashboard ? nodeDashboard : [{ scope: "", dashboard: "" }]);
const states = reactive<{
linkDashboard: string;
nodeDashboard: {
scope: string;
dashboard: string;
}[];
linkServerMetrics: string[];
linkClientMetrics: string[];
nodeMetrics: string[];
nodeMetricList: Option[];
linkMetricList: Option[];
linkDashboards: (DashboardItem & { label: string; value: string })[];
nodeDashboards: (DashboardItem & { label: string; value: string })[];
}>({
linkDashboard: selectedGrid.linkDashboard || "",
nodeDashboard: selectedGrid.nodeDashboard || [],
linkServerMetrics: selectedGrid.linkServerMetrics || [],
linkClientMetrics: selectedGrid.linkClientMetrics || [],
nodeMetrics: selectedGrid.nodeMetrics || [],
nodeMetricList: [],
linkMetricList: [],
linkDashboards: [],
nodeDashboards: [],
});
const l = selectedGrid.legend && selectedGrid.legend.length;
const legend = reactive<{
metric: { name: string; condition: string; value: string }[];
}>({
metric: l ? selectedGrid.legend : [{ name: "", condition: "", value: "" }],
});
const configType = ref<string>("");
const description = reactive<any>(selectedGrid.description || {});
/*global defineEmits */
const emit = defineEmits(["update", "updateNodes"]);
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const topologyStore = useTopologyStore();
const { selectedGrid } = dashboardStore;
const nodeDashboard =
selectedGrid.nodeDashboard && selectedGrid.nodeDashboard.length ? selectedGrid.nodeDashboard : "";
const isService = [EntityType[0].value, EntityType[1].value].includes(dashboardStore.entity);
const items = reactive<
{
scope: string;
dashboard: string;
}[]
>(isService && nodeDashboard ? nodeDashboard : [{ scope: "", dashboard: "" }]);
const states = reactive<{
linkDashboard: string;
nodeDashboard: {
scope: string;
dashboard: string;
}[];
linkServerMetrics: string[];
linkClientMetrics: string[];
nodeMetrics: string[];
nodeMetricList: Option[];
linkMetricList: Option[];
linkDashboards: (DashboardItem & { label: string; value: string })[];
nodeDashboards: (DashboardItem & { label: string; value: string })[];
}>({
linkDashboard: selectedGrid.linkDashboard || "",
nodeDashboard: selectedGrid.nodeDashboard || [],
linkServerMetrics: selectedGrid.linkServerMetrics || [],
linkClientMetrics: selectedGrid.linkClientMetrics || [],
nodeMetrics: selectedGrid.nodeMetrics || [],
nodeMetricList: [],
linkMetricList: [],
linkDashboards: [],
nodeDashboards: [],
});
const l = selectedGrid.legend && selectedGrid.legend.length;
const legend = reactive<{
metric: { name: string; condition: string; value: string }[];
}>({
metric: l ? selectedGrid.legend : [{ name: "", condition: "", value: "" }],
});
const configType = ref<string>("");
const description = reactive<any>(selectedGrid.description || {});
getMetricList();
async function getMetricList() {
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
const json = await dashboardStore.fetchMetricList();
if (json.errors) {
ElMessage.error(json.errors);
return;
getMetricList();
async function getMetricList() {
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
const json = await dashboardStore.fetchMetricList();
if (json.errors) {
ElMessage.error(json.errors);
return;
}
const entity =
dashboardStore.entity === EntityType[1].value
? EntityType[0].value
: dashboardStore.entity === EntityType[4].value
? EntityType[3].value
: dashboardStore.entity;
states.linkDashboards = list.reduce(
(prev: (DashboardItem & { label: string; value: string })[], d: DashboardItem) => {
if (d.layer === dashboardStore.layerId && d.entity === entity + "Relation") {
prev.push({ ...d, label: d.name, value: d.name });
}
return prev;
},
[],
);
states.nodeMetricList = (json.data.metrics || []).filter(
(d: { type: string }) => d.type === MetricsType.REGULAR_VALUE,
);
states.linkMetricList = (json.data.metrics || []).filter(
(d: { catalog: string; type: string }) =>
entity + "Relation" === (MetricCatalog as any)[d.catalog] && d.type === MetricsType.REGULAR_VALUE,
);
if (isService) {
return;
}
states.nodeDashboards = list.reduce(
(prev: (DashboardItem & { label: string; value: string })[], d: DashboardItem) => {
if (d.layer === dashboardStore.layerId && d.entity === entity) {
prev.push({ ...d, label: d.name, value: d.name });
}
return prev;
},
[],
);
}
const entity =
dashboardStore.entity === EntityType[1].value
? EntityType[0].value
: dashboardStore.entity === EntityType[4].value
? EntityType[3].value
: dashboardStore.entity;
states.linkDashboards = list.reduce(
(
prev: (DashboardItem & { label: string; value: string })[],
d: DashboardItem
) => {
if (
d.layer === dashboardStore.layerId &&
d.entity === entity + "Relation"
) {
prev.push({ ...d, label: d.name, value: d.name });
}
return prev;
},
[]
);
states.nodeMetricList = (json.data.metrics || []).filter(
(d: { type: string }) => d.type === MetricsType.REGULAR_VALUE
);
states.linkMetricList = (json.data.metrics || []).filter(
(d: { catalog: string; type: string }) =>
entity + "Relation" === (MetricCatalog as any)[d.catalog] &&
d.type === MetricsType.REGULAR_VALUE
);
if (isService) {
return;
}
states.nodeDashboards = list.reduce(
(
prev: (DashboardItem & { label: string; value: string })[],
d: DashboardItem
) => {
if (d.layer === dashboardStore.layerId && d.entity === entity) {
prev.push({ ...d, label: d.name, value: d.name });
}
return prev;
},
[]
);
}
async function setLegend() {
updateSettings();
const ids = topologyStore.nodes.map((d: Node) => d.id);
const names = dashboardStore.selectedGrid.legend.map((d: any) => d.name);
if (!names.length) {
async function setLegend() {
updateSettings();
const ids = topologyStore.nodes.map((d: Node) => d.id);
const names = dashboardStore.selectedGrid.legend.map((d: any) => d.name);
if (!names.length) {
emit("updateNodes");
return;
}
const param = await useQueryTopologyMetrics(names, ids);
const res = await topologyStore.getLegendMetrics(param);
if (res.errors) {
ElMessage.error(res.errors);
}
emit("updateNodes");
return;
}
const param = await useQueryTopologyMetrics(names, ids);
const res = await topologyStore.getLegendMetrics(param);
if (res.errors) {
ElMessage.error(res.errors);
function changeNodeDashboard(opt: any) {
states.nodeDashboard = opt[0].value;
updateSettings();
}
emit("updateNodes");
}
function changeNodeDashboard(opt: any) {
states.nodeDashboard = opt[0].value;
updateSettings();
}
function changeLinkDashboard(opt: any) {
states.linkDashboard = opt[0].value;
updateSettings();
}
function changeLegend(type: string, opt: any, index: number) {
(legend.metric[index] as any)[type] = opt[0].value || opt;
}
function changeScope(index: number, opt: Option[] | any) {
items[index].scope = opt[0].value;
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
states.nodeDashboards = list.reduce(
(
prev: (DashboardItem & { label: string; value: string })[],
d: DashboardItem
) => {
if (d.layer === dashboardStore.layerId && d.entity === opt[0].value) {
prev.push({ ...d, label: d.name, value: d.name });
}
return prev;
},
[]
);
items[index].dashboard = states.nodeDashboards[0].value;
updateSettings();
}
function updateNodeDashboards(index: number, content: Option[] | any) {
items[index].dashboard = content[0].value;
updateSettings();
}
function addItem() {
items.push({ scope: "", dashboard: "" });
}
function deleteItem(index: number) {
if (items.length === 1) {
function changeLinkDashboard(opt: any) {
states.linkDashboard = opt[0].value;
updateSettings();
}
function changeLegend(type: string, opt: any, index: number) {
(legend.metric[index] as any)[type] = opt[0].value || opt;
}
function changeScope(index: number, opt: Option[] | any) {
items[index].scope = opt[0].value;
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
states.nodeDashboards = list.reduce(
(prev: (DashboardItem & { label: string; value: string })[], d: DashboardItem) => {
if (d.layer === dashboardStore.layerId && d.entity === opt[0].value) {
prev.push({ ...d, label: d.name, value: d.name });
}
return prev;
},
[],
);
items[index].dashboard = states.nodeDashboards[0].value;
updateSettings();
}
function updateNodeDashboards(index: number, content: Option[] | any) {
items[index].dashboard = content[0].value;
updateSettings();
}
function addItem() {
items.push({ scope: "", dashboard: "" });
}
items.splice(index, 1);
updateSettings();
}
function updateSettings(metricConfig?: { [key: string]: MetricConfigOpt[] }) {
const metrics = legend.metric.filter(
(d: any) => d.name && d.value && d.condition
);
const param = {
...dashboardStore.selectedGrid,
linkDashboard: states.linkDashboard,
nodeDashboard: isService
? items.filter((d: { scope: string; dashboard: string }) => d.dashboard)
: states.nodeDashboard,
linkServerMetrics: states.linkServerMetrics,
linkClientMetrics: states.linkClientMetrics,
nodeMetrics: states.nodeMetrics,
legend: metrics,
...metricConfig,
description,
};
dashboardStore.selectWidget(param);
dashboardStore.setConfigs(param);
emit("update", param);
}
function updateLinkServerMetrics(options: Option[] | any) {
const opt = options.map((d: Option) => d.value);
const index = states.linkServerMetrics.findIndex(
(d: any) => !opt.includes(d)
);
states.linkServerMetrics = opt;
if (index < 0) {
changeLinkServerMetrics();
return;
function deleteItem(index: number) {
if (items.length === 1) {
items.push({ scope: "", dashboard: "" });
}
items.splice(index, 1);
updateSettings();
}
const origin = dashboardStore.selectedGrid.linkServerMetricConfig || [];
const config = origin.length === 1 ? [] : origin.splice(index, 1);
changeLinkServerMetrics({ linkServerMetricConfig: config });
}
async function changeLinkServerMetrics(config?: {
[key: string]: MetricConfigOpt[];
}) {
updateSettings(config);
if (!states.linkServerMetrics.length) {
topologyStore.setLinkServerMetrics({});
return;
function updateSettings(metricConfig?: { [key: string]: MetricConfigOpt[] }) {
const metrics = legend.metric.filter((d: any) => d.name && d.value && d.condition);
const param = {
...dashboardStore.selectedGrid,
linkDashboard: states.linkDashboard,
nodeDashboard: isService
? items.filter((d: { scope: string; dashboard: string }) => d.dashboard)
: states.nodeDashboard,
linkServerMetrics: states.linkServerMetrics,
linkClientMetrics: states.linkClientMetrics,
nodeMetrics: states.nodeMetrics,
legend: metrics,
...metricConfig,
description,
};
dashboardStore.selectWidget(param);
dashboardStore.setConfigs(param);
emit("update", param);
}
topologyStore.getLinkServerMetrics(states.linkServerMetrics);
}
function updateLinkClientMetrics(options: Option[] | any) {
const opt = options.map((d: Option) => d.value);
const index = states.linkClientMetrics.findIndex(
(d: any) => !opt.includes(d)
);
states.linkClientMetrics = opt;
if (index < 0) {
changeLinkClientMetrics();
return;
function updateLinkServerMetrics(options: Option[] | any) {
const opt = options.map((d: Option) => d.value);
const index = states.linkServerMetrics.findIndex((d: any) => !opt.includes(d));
states.linkServerMetrics = opt;
if (index < 0) {
changeLinkServerMetrics();
return;
}
const origin = dashboardStore.selectedGrid.linkServerMetricConfig || [];
const config = origin.length === 1 ? [] : origin.splice(index, 1);
changeLinkServerMetrics({ linkServerMetricConfig: config });
}
const origin = dashboardStore.selectedGrid.linkClientMetricConfig || [];
const config = origin.length === 1 ? [] : origin.splice(index, 1);
changeLinkClientMetrics({ linkClientMetricConfig: config });
}
async function changeLinkClientMetrics(config?: {
[key: string]: MetricConfigOpt[];
}) {
updateSettings(config);
if (!states.linkClientMetrics.length) {
topologyStore.setLinkClientMetrics({});
return;
async function changeLinkServerMetrics(config?: { [key: string]: MetricConfigOpt[] }) {
updateSettings(config);
if (!states.linkServerMetrics.length) {
topologyStore.setLinkServerMetrics({});
return;
}
topologyStore.getLinkServerMetrics(states.linkServerMetrics);
}
topologyStore.getLinkClientMetrics(states.linkClientMetrics);
}
function updateNodeMetrics(options: Option[] | any) {
const opt = options.map((d: Option) => d.value);
const index = states.nodeMetrics.findIndex((d: any) => !opt.includes(d));
states.nodeMetrics = opt;
if (index < 0) {
changeNodeMetrics();
return;
function updateLinkClientMetrics(options: Option[] | any) {
const opt = options.map((d: Option) => d.value);
const index = states.linkClientMetrics.findIndex((d: any) => !opt.includes(d));
states.linkClientMetrics = opt;
if (index < 0) {
changeLinkClientMetrics();
return;
}
const origin = dashboardStore.selectedGrid.linkClientMetricConfig || [];
const config = origin.length === 1 ? [] : origin.splice(index, 1);
changeLinkClientMetrics({ linkClientMetricConfig: config });
}
const origin = dashboardStore.selectedGrid.nodeMetricConfig || [];
const config = origin.length === 1 ? [] : origin.splice(index, 1);
changeNodeMetrics({ nodeMetricConfig: config });
}
async function changeNodeMetrics(config?: {
[key: string]: MetricConfigOpt[];
}) {
updateSettings(config);
if (!states.nodeMetrics.length) {
topologyStore.setNodeMetricValue({});
return;
async function changeLinkClientMetrics(config?: { [key: string]: MetricConfigOpt[] }) {
updateSettings(config);
if (!states.linkClientMetrics.length) {
topologyStore.setLinkClientMetrics({});
return;
}
topologyStore.getLinkClientMetrics(states.linkClientMetrics);
}
topologyStore.queryNodeMetrics(states.nodeMetrics);
}
function deleteMetric(index: number) {
if (legend.metric.length === 1) {
legend.metric = [{ name: "", condition: "", value: "" }];
return;
function updateNodeMetrics(options: Option[] | any) {
const opt = options.map((d: Option) => d.value);
const index = states.nodeMetrics.findIndex((d: any) => !opt.includes(d));
states.nodeMetrics = opt;
if (index < 0) {
changeNodeMetrics();
return;
}
const origin = dashboardStore.selectedGrid.nodeMetricConfig || [];
const config = origin.length === 1 ? [] : origin.splice(index, 1);
changeNodeMetrics({ nodeMetricConfig: config });
}
async function changeNodeMetrics(config?: { [key: string]: MetricConfigOpt[] }) {
updateSettings(config);
if (!states.nodeMetrics.length) {
topologyStore.setNodeMetricValue({});
return;
}
topologyStore.queryNodeMetrics(states.nodeMetrics);
}
function deleteMetric(index: number) {
if (legend.metric.length === 1) {
legend.metric = [{ name: "", condition: "", value: "" }];
return;
}
legend.metric.splice(index, 1);
}
function addMetric() {
legend.metric.push({ name: "", condition: "", value: "" });
}
function setConfigType(type: string) {
configType.value = type;
}
legend.metric.splice(index, 1);
}
function addMetric() {
legend.metric.push({ name: "", condition: "", value: "" });
}
function setConfigType(type: string) {
configType.value = type;
}
</script>
<style lang="scss" scoped>
.link-settings {
margin-bottom: 20px;
}
.link-settings {
margin-bottom: 20px;
}
.inputs {
margin-top: 8px;
width: 355px;
}
.inputs {
margin-top: 8px;
width: 355px;
}
.item {
width: 130px;
margin-top: 5px;
}
.item {
width: 130px;
margin-top: 5px;
}
.input-small {
width: 45px;
margin: 0 3px;
}
.input-small {
width: 45px;
margin: 0 3px;
}
.title {
margin-bottom: 0;
}
.title {
margin-bottom: 0;
}
.label {
font-size: 12px;
margin-top: 10px;
}
.label {
font-size: 12px;
margin-top: 10px;
}
.legend-btn {
margin: 20px 0;
cursor: pointer;
}
.legend-btn {
margin: 20px 0;
cursor: pointer;
}
.delete {
margin: 0 3px;
}
.delete {
margin: 0 3px;
}
</style>

Some files were not shown because too many files have changed in this diff Show More