Merge pull request #3 from Fine0830/feat/dashboard

feat: add a page for creating dashboards
This commit is contained in:
Fine0830 2021-12-16 11:39:28 +08:00 committed by GitHub
commit 820abda5a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 494 additions and 14 deletions

View File

@ -18,7 +18,6 @@ limitations under the License. -->
<style>
#app {
text-align: center;
color: #2c3e50;
height: 100%;
}

View File

@ -0,0 +1,32 @@
/**
* 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 Services = {
variable: ["$layer: String!"],
query: `
services: listServices(layer: $layer) {
value: id
label: name
group
layer
}
`,
};
export const Layers = {
query: `
layers: listLayers
`,
};

View File

@ -16,9 +16,10 @@
*/
import axios, { AxiosPromise, AxiosResponse } from "axios";
import { cancelToken } from "@/utils/cancelToken";
import * as global from "./query/global";
import * as app from "./query/app";
import * as selector from "./query/selector";
const query: { [key: string]: string } = { ...global };
const query: { [key: string]: string } = { ...app, ...selector };
class Graph {
private queryData = "";
public query(queryData: string) {

View File

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { OAPTimeInfo, OAPVersion } from "../fragments/global";
import { OAPTimeInfo, OAPVersion } from "../fragments/app";
export const queryOAPTimeInfo = `query queryOAPTimeInfo {${OAPTimeInfo.query}}`;

View File

@ -0,0 +1,20 @@
/**
* 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 { Services, Layers } from "../fragments/selector";
export const queryServices = `query queryServices(${Services.variable}) {${Services.query}}`;
export const queryLayers = `query ${Layers.query}`;

View File

@ -91,6 +91,7 @@ watch(
background-color: #fafbfc;
border-bottom: 1px solid #dfe4e8;
color: #222;
font-size: 12px;
}
.nav-bar.dark {
background-color: #333840;

View File

@ -43,6 +43,8 @@ const msg = {
eventList: "Event List",
databasePanel: "Database Panel",
meshServicePanel: "Service Panel",
newDashboard: "New a dashboard",
dashboardEdit: "Edit the dashboard",
hourTip: "Select Hour",
minuteTip: "Select Minute",
secondTip: "Select Second",

View File

@ -43,6 +43,8 @@ const msg = {
eventList: "事件列表",
databasePanel: "数据库面板",
meshServicePanel: "服务面板",
newDashboard: "新增仪表盘",
dashboardEdit: "编辑仪表盘",
hourTip: "选择小时",
minuteTip: "选择分钟",
secondTip: "选择秒数",

View File

@ -47,6 +47,36 @@ export const routesDashboard: Array<RouteRecordRaw> = [
exact: false,
},
},
{
path: "/dashboard/edit/service/:serviceId",
component: () => import("@/views/dashboard/Edit.vue"),
name: "serviceEdit",
meta: {
title: "dashboardEdit",
exact: false,
notShow: true,
},
},
{
path: "/dashboard/edit/endpoint/:serviceId/:endpointId",
component: () => import("@/views/dashboard/Edit.vue"),
name: "endpointEdit",
meta: {
title: "dashboardEdit",
exact: false,
notShow: true,
},
},
{
path: "/dashboard/edit/instance/:serviceId/:instanceId",
component: () => import("@/views/dashboard/Edit.vue"),
name: "instanceEdit",
meta: {
title: "dashboardEdit",
exact: false,
notShow: true,
},
},
],
},
];

View File

@ -43,4 +43,16 @@ const router = createRouter({
routes,
});
(window as any).axiosCancel = [];
router.beforeEach((to, from, next) => {
// const token = window.localStorage.getItem("skywalking-authority");
if ((window as any).axiosCancel.length !== 0) {
for (const func of (window as any).axiosCancel) {
setTimeout(func(), 0);
}
(window as any).axiosCancel = [];
}
next();
});
export default router;

8
src/shims-vue.d.ts vendored
View File

@ -19,3 +19,11 @@ declare module "*.vue" {
const Component: ReturnType<typeof defineComponent>;
export default Component;
}
declare global {
namespace JSX {}
interface Window {
Promise: any;
moment: any;
axiosCancel: any;
}
}

View File

@ -26,6 +26,8 @@ interface AppState {
utc: string;
utcHour: number;
utcMin: number;
eventStack: (() => unknown)[];
timer: ReturnType<typeof setInterval> | null;
}
export const appStore = defineStore({
@ -35,6 +37,8 @@ export const appStore = defineStore({
utc: "",
utcHour: 0,
utcMin: 0,
eventStack: [],
timer: null,
}),
getters: {
duration(): Duration {
@ -92,15 +96,38 @@ export const appStore = defineStore({
},
},
actions: {
setDuration(data: Duration) {
setDuration(data: Duration): void {
this.durationRow = data;
if ((window as any).axiosCancel.length !== 0) {
for (const event of (window as any).axiosCancel) {
setTimeout(event(), 0);
}
(window as any).axiosCancel = [];
}
this.runEventStack();
},
setUTC(utcHour: number, utcMin: number) {
setUTC(utcHour: number, utcMin: number): void {
this.runEventStack();
this.utcMin = utcMin;
this.utcHour = utcHour;
this.utc = `${utcHour}:${utcMin}`;
localStorage.setItem("utc", this.utc);
},
setEventStack(funcs: (() => void)[]): void {
this.eventStack = funcs;
},
runEventStack() {
if (this.timer) {
clearTimeout(this.timer);
}
this.timer = setTimeout(
() =>
this.eventStack.forEach((event: any) => {
setTimeout(event(), 0);
}),
500
);
},
},
});
export function useAppStoreWithOut(): any {

View File

@ -0,0 +1,54 @@
/**
* 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 { Option } from "@/types/app";
import { store } from "@/store";
import graph from "@/graph";
import { AxiosResponse } from "axios";
interface SelectorState {
services: Option[];
}
export const selectorStore = defineStore({
id: "selector",
state: (): SelectorState => ({
services: [],
}),
getters: {},
actions: {
async fetchLayers() {
const res: AxiosResponse = await graph.query("queryLayers").params({});
return res;
},
async fetchServices(layer: string): Promise<AxiosResponse> {
const res: AxiosResponse = await graph
.query("queryServices")
.params({ layer });
if (!res.data.errors) {
this.services = res.data.data.services;
}
return res;
},
},
});
export function useSelectorStore(): any {
return selectorStore(store);
}

View File

@ -18,7 +18,7 @@
body {
margin: 0;
line-height: 1.5;
font-size: 13px;
font-size: 14px;
color: #3d444f;
font-family: 'Helvetica', 'Arial', 'Source Han Sans CN', 'Microsoft YaHei', 'sans-serif';
text-rendering: optimizeLegibility;

View File

@ -14,11 +14,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable */
import axios from "axios";
const CancelToken = axios.CancelToken;
export const cancelToken = () =>
export const cancelToken = (): any =>
new CancelToken(function executor(c) {
const w = window as any;
w.axiosCancel.push(c);

View File

@ -0,0 +1,15 @@
<!-- 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>dashboard edit</template>

View File

@ -28,7 +28,9 @@ limitations under the License. -->
</template>
</el-input>
<router-link to="/dashboard/new">
<el-button size="small" type="primary">+ New Dashboard</el-button>
<el-button size="small" type="primary">
+ {{ t("newDashboard") }}
</el-button>
</router-link>
</div>
<el-table :data="tableData" style="width: 100%" max-height="550">
@ -57,6 +59,7 @@ limitations under the License. -->
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { ElTable, ElTableColumn, ElButton, ElInput } from "element-plus";
// # - os-linux
// # - k8s
@ -69,6 +72,7 @@ import { ElTable, ElTableColumn, ElButton, ElInput } from "element-plus";
// # - cache
// # - browser
// # - skywalking
const { t } = useI18n();
const searchText = ref<string>("");
const tableData = [
{

View File

@ -13,11 +13,197 @@ 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="new-dashboard">{{ props.msg }}</div>
<div class="new-dashboard">
<h4>Create a new dashboard</h4>
<div class="item">
<div class="label">Name</div>
<el-input
size="small"
v-model="state.name"
placeholder="Please input name"
/>
</div>
<div class="item">
<div class="label">Layer</div>
<el-select
size="small"
v-model="state.layer"
placeholder="Select a layer"
class="selectors"
>
<el-option
v-for="item in Options"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</div>
<div class="item">
<div class="label">Entity</div>
<el-select
size="small"
v-model="state.entity"
placeholder="Select a entity"
class="selectors"
>
<el-option
v-for="item in EntityType"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</div>
<div class="item" v-show="state.entity === EntityType[0].value">
<div class="label">Service</div>
<el-select
size="small"
v-model="state.service"
placeholder="Select a service"
class="selectors"
>
<el-option
v-for="item in Options"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</div>
<div class="item" v-show="state.entity === EntityType[2].value">
<div class="label">Service / Endpoint</div>
<el-cascader
v-model="state.serviceEndpoint"
:options="SelectOpt"
:props="props"
size="small"
@change="handleChange"
:style="{ width: '600px' }"
></el-cascader>
</div>
<div class="item" v-show="state.entity === EntityType[3].value">
<div class="label">Service / Instance</div>
<el-cascader
v-model="state.serviceInstance"
:options="SelectOpt"
:props="props"
size="small"
@change="handleChange"
:style="{ width: '600px' }"
></el-cascader>
</div>
<div class="item" v-show="state.entity === EntityType[4].value">
<div class="label">Destination Service</div>
<el-select
size="small"
v-model="state.destService"
placeholder="Select"
class="selectors"
>
<el-option
v-for="item in Options"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</div>
<div class="item" v-show="state.entity === EntityType[5].value">
<span class="label">Destination Service / Endpoint</span>
<el-cascader
v-model="state.destServiceEndpoint"
:options="SelectOpt"
:props="props"
@change="handleChange"
:style="{ width: '600px' }"
></el-cascader>
</div>
<div class="item" v-show="state.entity === EntityType[6].value">
<span class="label">Destination Service / Instance</span>
<el-cascader
v-model="state.destServiceInstance"
:options="SelectOpt"
:props="props"
@change="handleChange"
:style="{ width: '600px' }"
></el-cascader>
</div>
<div class="btn">
<el-button class="create" size="small" type="primary" @click="onCreate">
Create
</el-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { defineProps } from "vue";
const props = defineProps({
msg: String,
import { reactive } from "vue";
import router from "@/router";
import {
ElSelect,
ElOption,
ElCascader,
ElInput,
ElButton,
} from "element-plus";
import { useSelectorStore } from "@/store/modules/selectors";
import { EntityType, SelectOpt, Options } from "./data";
const selectorStore = useSelectorStore();
const props = {
expandTrigger: "hover",
};
const state = reactive({
name: "",
layer: "",
entity: EntityType[0].value,
service: "",
serviceEndpoint: "",
serviceInstance: "",
destService: "",
destServiceEndpoint: "",
destServiceInstance: "",
});
const handleChange = (value: any) => {
console.log(value);
};
const onCreate = () => {
let path = `/dashboard/edit/${state.entity}/`;
switch (state.entity) {
case EntityType[0].value:
path += state.service;
break;
case EntityType[2].value:
path += `${state.service}/${state.serviceEndpoint}`;
break;
case EntityType[3].value:
path += `${state.service}/${state.serviceInstance}`;
break;
}
router.push(path);
};
selectorStore.fetchServices("general");
</script>
<style lang="scss" scoped>
.new-dashboard {
margin: 0 auto;
}
.item {
margin-top: 20px;
}
.new-dashboard,
.selectors,
.el-cascader-menu {
width: 600px;
}
.create {
width: 600px;
}
.btn {
margin-top: 40px;
}
</style>

View File

@ -0,0 +1,87 @@
/**
* 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 EntityType = [
{ value: "service", label: "Service" },
{ value: "all", label: "All" },
{ value: "endpoint", label: "Service Endpoint" },
{ value: "serviceInstance", label: "Service Instance" },
{ value: "serviceRelation", label: "Service Relation" },
{ value: "serviceInstanceRelation", label: "Service Instance Relation" },
{ value: "endpointRelation", label: "Endpoint Relation" },
];
export const SelectOpt = [
{
value: "guide",
label: "Guide",
children: [
{
value: "disciplines",
label: "Disciplines",
children: [
{
value: "consistency",
label: "Consistency",
},
{
value: "feedback",
label: "Feedback",
},
{
value: "efficiency",
label: "Efficiency",
},
],
},
{
value: "navigation",
label: "Navigation",
children: [
{
value: "side nav",
label: "Side Navigation",
},
{
value: "top nav",
label: "Top Navigation",
},
],
},
],
},
];
export const Options = [
{
value: "Option1",
label: "Option1",
},
{
value: "Option2",
label: "Option2",
},
{
value: "Option3",
label: "Option3",
},
{
value: "Option4",
label: "Option4",
},
{
value: "Option5",
label: "Option5",
},
];