feat: Implement visualizing events with a timeline. (#25)

This commit is contained in:
Fine0830
2022-03-10 10:53:02 +08:00
committed by GitHub
parent 2a40545f93
commit 0f667d967e
14 changed files with 625 additions and 44 deletions

36
src/views/Event.vue Normal file
View File

@@ -0,0 +1,36 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="event flex-v">
<Header />
<Content />
</div>
</template>
<script lang="ts" setup>
import { useAppStoreWithOut } from "@/store/modules/app";
import Header from "./event/Header.vue";
import Content from "./event/Content.vue";
const appStore = useAppStoreWithOut();
appStore.setPageTitle("Events");
</script>
<style lang="scss" scoped>
.event {
flex-grow: 1;
height: 100%;
font-size: 12px;
}
</style>

View File

@@ -4,9 +4,7 @@ this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -17,7 +15,6 @@ limitations under the License. -->
</template>
<script lang="ts" setup>
import { useAppStoreWithOut } from "@/store/modules/app";
const appStore = useAppStoreWithOut();
appStore.setPageTitle("Log");
/*global defineProps */

View File

@@ -179,7 +179,7 @@ function viewEventDetail(event: Event) {
}
</script>
<style lang="scss" scoped>
@import "./index.scss";
@import "../components/style.scss";
.tips {
width: 100%;

View File

@@ -14,10 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div>
<div class="log-t-loading" v-show="logStore.loadLogs">
<Icon iconName="spinner" size="lg" />
</div>
<LogTable :tableData="logStore.logs || []" :type="type" :noLink="true">
<LogTable
v-loading="logStore.loadLogs"
:tableData="logStore.logs || []"
:type="type"
:noLink="true"
>
<div class="log-tips" v-if="!logStore.logs.length">{{ t("noData") }}</div>
</LogTable>
<div class="mt-5 mb-5">
@@ -66,8 +68,4 @@ async function queryLogs() {
text-align: center;
margin: 50px 0;
}
.log-t-loading {
text-align: center;
}
</style>

121
src/views/event/Content.vue Normal file
View File

@@ -0,0 +1,121 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="timeline-table clear">
<div
v-for="(i, index) in eventStore.events"
:key="index"
class="mb-10 clear timeline-item"
@click="showEventDetails(i)"
>
<div class="g-sm-3 grey sm hide-xs time-line tr">
{{ dateFormat(parseInt(i.startTime)) }}
</div>
<div class="timeline-table-i g-sm-9">
<div class="message mb-5 b">
{{ i.message }}
</div>
<div
class="timeline-table-i-scope mr-10 l sm"
:class="{
blue: i.scope === 'Service',
green: i.scope === 'Endpoint',
yellow: i.scope === 'ServiceInstance',
}"
>
{{ i.scope }}
</div>
<div class="grey sm show-xs">
{{ dateFormat(parseInt(i.startTime)) }}
</div>
</div>
</div>
<div v-if="!eventStore.events.length" class="tips">{{ t("noData") }}</div>
</div>
<el-dialog
:title="t('eventDetail')"
v-model="showDetails"
fullscreen
:destroy-on-close="true"
@closed="showDetails = false"
>
<div>
<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>
<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
>
<div v-show="currentEvent[eventKey.class].endpoint">
{{ t("endpoint") }}:
{{ currentEvent[eventKey.class].endpoint }}
</div>
<div v-show="currentEvent[eventKey.class].serviceInstance">
{{ t("instance") }}:
{{ currentEvent[eventKey.class].serviceInstance }}
</div>
</span>
<span v-else>{{ currentEvent[eventKey.class] }}</span>
</div>
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import dayjs from "dayjs";
import { useEventStore } from "@/store/modules/event";
import { EventsDetailKeys } from "./data";
import { Event } from "@/types/events";
const { t } = useI18n();
const eventStore = useEventStore();
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern);
const showDetails = ref<boolean>(false);
const currentEvent = ref<any>({});
function showEventDetails(item: Event) {
showDetails.value = true;
currentEvent.value = item;
}
</script>
<style lang="scss" scoped>
@import "../components/style.scss";
.tips {
width: 100%;
margin: 20px 0;
text-align: center;
font-size: 14px;
}
</style>

247
src/views/event/Header.vue Normal file
View File

@@ -0,0 +1,247 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<nav class="event-tool flex-v">
<div class="flex-h">
<div class="mr-5">
<span class="grey">{{ t("layer") }}: </span>
<Selector
v-model="state.currentLayer"
:options="state.layers"
placeholder="Select a layer"
@change="selectLayer"
class="event-tool-input"
size="small"
/>
</div>
<div class="mr-5">
<span class="grey">{{ t("service") }}: </span>
<Selector
v-model="state.service"
:options="eventStore.services"
placeholder="Select a service"
@change="selectService"
class="event-tool-input"
size="small"
/>
</div>
<div class="mr-5">
<span class="grey mr-5">{{ t("instance") }}: </span>
<Selector
v-model="state.instance"
:options="eventStore.instances"
placeholder="Select a instance"
@change="selectInstance"
class="event-tool-input"
size="small"
/>
</div>
<div class="mr-5">
<span class="grey mr-5">{{ t("endpoint") }}: </span>
<Selector
v-model="state.endpoint"
:options="eventStore.endpoints"
placeholder="Select a endpoint"
@change="selectEndpoint"
class="event-tool-input"
size="small"
/>
</div>
<div class="mr-5">
<span class="grey">{{ t("eventsType") }}: </span>
<Selector
v-model="state.eventType"
:options="EventTypes"
placeholder="Select a type"
@change="selectType"
class="event-tool-input"
size="small"
/>
</div>
</div>
<div class="mt-5">
<el-pagination
v-model:currentPage="pageNum"
v-model:page-size="pageSize"
layout="prev, jumper, total, next"
:total="eventStore.total"
@current-change="updatePage"
:pager-count="5"
small
:style="`--el-pagination-bg-color: #f0f2f5; --el-pagination-button-disabled-bg-color: #f0f2f5;`"
/>
<!-- <div>
<el-button class="search" type="primary" @click="queryEvents">
<Icon iconName="search" class="mr-5" />
<span class="vm">{{ t("search") }}</span>
</el-button>
</div> -->
</div>
</nav>
</template>
<script lang="ts" setup>
import { ref, reactive } from "vue";
import { useI18n } from "vue-i18n";
import { EventTypes } from "./data";
import { useEventStore } from "@/store/modules/event";
import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
const { t } = useI18n();
const eventStore = useEventStore();
const selectorStore = useSelectorStore();
const pageSize = 20;
const pageNum = ref<number>(1);
const state = reactive<{
currentLayer: string;
layers: string[];
eventType: string;
service: string;
instance: string;
endpoint: string;
}>({
currentLayer: "",
layers: [],
eventType: "",
service: "",
instance: "",
endpoint: "",
});
getSelectors();
async function getSelectors() {
await getLayers();
if (!state.currentLayer) {
return;
}
getServices();
}
async function getServices() {
const resp = await eventStore.getServices(state.currentLayer);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.service = eventStore.services[0].value;
if (!eventStore.services[0].id) {
queryEvents();
return;
}
getEndpoints(eventStore.services[0].id);
getInstances(eventStore.services[0].id);
queryEvents();
}
async function getEndpoints(id: string) {
const resp = await eventStore.getEndpoints(id);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.endpoint = eventStore.endpoints[0].value;
}
async function getInstances(id: string) {
const resp = await eventStore.getInstances(id);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.instance = eventStore.instances[0].value;
}
async function getLayers() {
const resp = await selectorStore.fetchLayers();
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.currentLayer = resp.data.layers[0] || "";
state.layers = resp.data.layers.map((d: string) => {
return { label: d, value: d };
});
}
async function queryEvents() {
eventStore.setEventCondition({
paging: {
pageNum: pageNum.value,
pageSize: pageSize,
needTotal: true,
},
source: {
service: state.service || "",
endpoint: state.endpoint || "",
serviceInstance: state.instance || "",
},
type: state.eventType || undefined,
});
const resp = await eventStore.getEvents();
if (resp.errors) {
ElMessage.error(resp.errors);
}
}
async function selectLayer(opt: any) {
state.currentLayer = opt[0].value;
await getServices();
}
function selectService(opt: any) {
state.service = opt[0].value;
queryEvents();
if (!opt[0].id) {
return;
}
getEndpoints(opt[0].id);
getInstances(opt[0].id);
}
function selectInstance(opt: any) {
state.instance = opt[0].value;
queryEvents();
}
function selectEndpoint(opt: any) {
state.endpoint = opt[0].value;
queryEvents();
}
function selectType(opt: any) {
state.eventType = opt[0].value;
queryEvents();
}
function updatePage(p: number) {
pageNum.value = p;
queryEvents();
}
</script>
<style lang="scss" scoped>
.event-tool {
background-color: #f0f2f5;
width: 100%;
padding: 10px;
}
.event-tool-input {
width: 200px;
}
.search {
margin-left: 20px;
}
</style>

30
src/views/event/data.ts Normal file
View File

@@ -0,0 +1,30 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const EventsDetailKeys = [
{ text: "eventID", class: "uuid" },
{ text: "eventName", class: "name" },
{ text: "eventsType", class: "type" },
{ text: "startTime", class: "startTime" },
{ text: "endTime", class: "endTime" },
{ text: "eventsMessage", class: "message" },
{ text: "eventSource", class: "source" },
];
export const EventTypes = [
{ label: "All", value: "" },
{ label: "Normal", value: "Normal" },
{ label: "Error", value: "Error" },
];