feat: add events

This commit is contained in:
Qiuxia Fan 2022-03-09 22:03:53 +08:00
parent 2a40545f93
commit 0ae27bb63d
12 changed files with 456 additions and 39 deletions

View File

@ -0,0 +1,40 @@
/**
* 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 FetchEvents = {
variable: ["$condition: EventQueryCondition"],
query: `
fetchEvents: queryEvents(condition: $condition) {
events {
uuid
source {
service
serviceInstance
endpoint
}
name
type
message
parameters {
key
value
}
startTime
endTime
}
total
}`,
};

View File

@ -24,6 +24,7 @@ import * as trace from "./query/trace";
import * as log from "./query/log";
import * as profile from "./query/profile";
import * as alarm from "./query/alarm";
import * as event from "./query/event";
const query: { [key: string]: string } = {
...app,
@ -34,6 +35,7 @@ const query: { [key: string]: string } = {
...log,
...profile,
...alarm,
...event,
};
class Graphql {
private queryData = "";

View File

@ -0,0 +1,19 @@
/**
* 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.
*/
import { FetchEvents } from "../fragments/event";
export const queryEvents = `query queryData(${FetchEvents.variable}) {${FetchEvents.query}}`;

View File

@ -33,10 +33,9 @@ export const routesEvent: Array<RouteRecordRaw> = [
path: "/events",
name: "Events",
meta: {
title: "eventList",
exact: false,
},
component: () => import("@/views/Log.vue"),
component: () => import("@/views/Event.vue"),
},
],
},

109
src/store/modules/event.ts Normal file
View File

@ -0,0 +1,109 @@
/**
* 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.
*/
import { defineStore } from "pinia";
import { store } from "@/store";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
import { Event, QueryEventCondition } from "@/types/events";
import { Instance, Endpoint, Service } from "@/types/selector";
import { useAppStoreWithOut } from "@/store/modules/app";
interface eventState {
loading: boolean;
events: Event[];
total: number;
services: Service[];
instances: Instance[];
endpoints: Endpoint[];
condition: QueryEventCondition | any;
}
export const eventStore = defineStore({
id: "event",
state: (): eventState => ({
loading: false,
events: [],
total: 0,
services: [{ value: "", label: "All" }],
instances: [{ value: "", label: "All" }],
endpoints: [{ value: "", label: "All" }],
condition: {
time: useAppStoreWithOut().durationTime,
paging: { pageNum: 1, pageSize: 15, needTotal: true },
},
}),
actions: {
setEventCondition(data: any) {
this.condition = { ...this.condition, ...data };
},
async getServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({
layer,
});
if (res.data.errors) {
return res.data;
}
this.services = res.data.data.services;
return res.data;
},
async getInstances(serviceId: string) {
const res: AxiosResponse = await graphql.query("queryInstances").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
});
if (res.data.errors) {
return res.data;
}
this.instances = [{ value: "", label: "All" }, ...res.data.data.pods] || [
{ value: "", label: "All" },
];
return res.data;
},
async getEndpoints(serviceId: string) {
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
keyword: "",
});
if (res.data.errors) {
return res.data;
}
this.endpoints = [{ value: "", label: "All" }, ...res.data.data.pods] || [
{ value: "", label: "All" },
];
return res.data;
},
async getEvents() {
const res: AxiosResponse = await graphql
.query("queryEvents")
.params({ condition: this.condition });
if (res.data.errors) {
return res.data;
}
if (res.data.data.fetchEvents) {
this.events = res.data.data.fetchEvents.events;
this.total = res.data.data.fetchEvents.total;
}
return res.data;
},
},
});
export function useEventStore(): any {
return eventStore(store);
}

10
src/types/events.d.ts vendored
View File

@ -27,3 +27,13 @@ export type Event = {
checked?: boolean;
scope?: string;
};
export interface QueryEventCondition {
uuid: string;
source: SourceInput;
name: string;
type: EventType;
time: Duration;
order: string;
paging: { pageNum: number; pageSize: number; needTotal: boolean };
}

View File

@ -13,15 +13,22 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="about">{{ props.msg }}</div>
<div class="event flex-v">
<Header />
</div>
</template>
<script lang="ts" setup>
import { useAppStoreWithOut } from "@/store/modules/app";
import Header from "./event/Header.vue";
const appStore = useAppStoreWithOut();
appStore.setPageTitle("Log");
/*global defineProps */
const props = defineProps({
msg: String,
});
appStore.setPageTitle("Events");
</script>
<style lang="scss" scoped>
.event {
flex-grow: 1;
height: 100%;
font-size: 12px;
}
</style>

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

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

@ -0,0 +1,248 @@
<!-- 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.value"
: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.value"
: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.value"
: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="flex-h 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: { id: string; value: string; label: string };
instance: { id: string; value: string; label: string };
endpoint: { id: string; value: string; label: string };
}>({
currentLayer: "",
layers: [],
eventType: "",
service: { id: "", value: "", label: "" },
instance: { id: "", value: "", label: "" },
endpoint: { id: "", value: "", label: "" },
});
getSelectors();
async function getSelectors() {
await getLayers();
if (!state.currentLayer) {
return;
}
await getServices();
if (!state.service.id) {
queryEvents();
return;
}
getEndpoints();
getInstances();
queryEvents();
}
async function getServices() {
const resp = await eventStore.getServices(state.currentLayer);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.service = eventStore.services[0];
}
async function getEndpoints() {
const resp = await eventStore.getEndpoints(state.service.id);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.endpoint = eventStore.endpoints[0];
}
async function getInstances() {
const resp = await eventStore.getInstances(state.service.id);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.instance = eventStore.instances[0];
}
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.value || "",
endpoint: state.endpoint.value || "",
serviceInstance: state.instance.value || "",
},
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();
if (!state.service.id) {
return;
}
getEndpoints();
getInstances();
}
function selectService(opt: any) {
state.service = opt[0];
if (!state.service.id) {
return;
}
getEndpoints();
getInstances();
}
function selectInstance(opt: any) {
state.instance = opt[0];
}
function selectEndpoint(opt: any) {
state.endpoint = opt[0];
}
function selectType(opt: any) {
state.eventType = opt[0].value;
}
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>

View File

@ -14,34 +14,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesLog: Array<RouteRecordRaw> = [
{
path: "",
name: "Logs",
meta: {
title: "logs",
icon: "assignment",
hasGroup: false,
exact: false,
},
component: Layout,
children: [
{
path: "/log",
name: "Logs",
meta: {
title: "log",
exact: false,
},
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
import(/* webpackChunkName: "about" */ "@/views/Log.vue"),
},
],
},
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" },
];