diff --git a/nezha-fronted/src/assets/stylus/main.scss b/nezha-fronted/src/assets/stylus/main.scss index da620227e..3edfd880f 100644 --- a/nezha-fronted/src/assets/stylus/main.scss +++ b/nezha-fronted/src/assets/stylus/main.scss @@ -902,15 +902,19 @@ li{ } 25%{ opacity: 0; - max-height: 125px; + max-height: 100px; } 50%{ opacity: 0; - max-height: 250px; + max-height: 200px; } - 75%{ - opacity: 0.4; - max-height: 375px; + 66%{ + opacity: 0.2; + max-height: 300px; + } + 83%{ + opacity: 0.5; + max-height: 400px; } 100% { visibility: visible; @@ -924,18 +928,21 @@ li{ max-height: 500px; visibility: visible; } - 25%{ - - opacity: 0.4; - max-height: 375px; + 16% { + opacity: 0.5; + max-height: 400px; + } + 33%{ + opacity: 0.2; + max-height: 300px; } 50%{ opacity: 0; - max-height: 250px; + max-height: 200px; } 75%{ opacity: 0; - max-height: 125px; + max-height: 100px; } 100% { visibility: hidden; diff --git a/nezha-fronted/src/components/charts/chart-detail.vue b/nezha-fronted/src/components/charts/chart-detail.vue new file mode 100644 index 000000000..73a19495d --- /dev/null +++ b/nezha-fronted/src/components/charts/chart-detail.vue @@ -0,0 +1,221 @@ + + + + diff --git a/nezha-fronted/src/components/charts/chart-list.vue b/nezha-fronted/src/components/charts/chart-list.vue index cbc6f2b6c..f233031f9 100644 --- a/nezha-fronted/src/components/charts/chart-list.vue +++ b/nezha-fronted/src/components/charts/chart-list.vue @@ -25,6 +25,7 @@ cursor: se-resize; box-sizing: border-box; user-select: none; + z-index: 2000; } .vue-resizable-handle:after { border-right: 2px solid #555; @@ -52,8 +53,8 @@ scroll:true, filter: '.drag-disabled', scrollFn:function(offsetX,offsetY,originalEvent,touchEvt,hoverTargetEI){}, - animation:150, - handle:'.chartTitle' + animation: 150, + handle: '.chart-title' }" >
+ @on-refresh-data="refreshChart" + @on-search-data="searchData" + @on-remove-chart-block="removeChart" + @on-duplicate-chart-block="duplicateChart" + @on-drag-chart="editChartForDrag" + @on-edit-chart-block="editData" + :panel-id="filter.panelId" + :chart-data="item" + :chart-index="index"> - + >--> + - + >--> - + >--> - + >--> 0 ) { - this.dataList.forEach((item) => { - if (this.$refs['editChart'+item.id] && this.$refs['editChart'+item.id].length > 0) { - this.$refs['editChart'+item.id][0].clearData(); - } - }); - }*/ this.dataList = []; this.chartDataCacheGroup.clear(); }, @@ -549,7 +536,6 @@ }, // 获取panel详情数据,获取panel下所有chart列表 getData(params) { - //param 目前没有用 const param = { panelId: params.panelId, query: params.query, @@ -567,7 +553,10 @@ type: "alertRuleInfo", prev: 0, next: -9, - buildIn: 1 + buildIn: 1, + draggable: false, + resizable: false, + editable: false, }); this.dataList.push({ id: -9, @@ -610,7 +599,10 @@ buildIn: 1, elements: [{ expression: `up{endpoint="${this.additionalInfo.id}"}` - }] + }], + draggable: false, + resizable: false, + editable: false, }); this.dataList.push({ id: -9, @@ -621,7 +613,10 @@ type: "assetInfo", prev: -10, next: -8, - buildIn: 1 + buildIn: 1, + draggable: false, + resizable: false, + editable: false, }); this.dataList.push({ id: -8, @@ -643,6 +638,7 @@ }); return; } + if (!param.query) delete param.query; //根据panelId获得panel下的所有图表 this.$get('panel/'+ params.panelId+'/charts').then(response => { @@ -675,6 +671,17 @@ if (this.dataList.length > 0 ) { this.dataList.forEach((item,index) => { this.setChartSize(item, index);//设置该图表宽度 + if (param.from == "asset") { + if (item.type == "assetInfo") { + this.$set(item, "draggable", true); + this.$set(item, "resizable", true); + } + } else if (param.from == "project") { + if (item.type == "projectInfo") { + this.$set(item, "draggable", true); + this.$set(item, "resizable", true); + } + } if(!item.isLoaded){ //获得当前显示在浏览器的图表,从后台获取数据 let chartBox = document.getElementById('chart-' + item.id); @@ -914,7 +921,7 @@ dpsArr = Object.entries(queryItem.values);//[ ["0",[1577959830.781,"0"]], ["1",[1577959845.781,"0"]] ] dpsArr = dpsArr.map(item => { return [item[0], [item[1][0], Number(item[1][1])]] - }) + }); // 判断是否有数据, && tagsArr.length > 0 if (dpsArr.length > 0 && this.$refs['editChart' + chartItem.id] && this.$refs['editChart' + chartItem.id].length > 0) { tagsArr.forEach((tag, i) => { @@ -1173,18 +1180,6 @@ return [dpsItem[0] * 1000, dpsItem[1]]; }); series.push(seriesItem.theData); - - } else if (chartInfo.elements && chartInfo.elements[0]) { - // 无数据提示 - /* - const currentInfo = chartItem.elements[innerPos]; - const errorMsg = `图表 ${chartItem.title} 中 ${currentInfo.metric},${currentInfo.tags} 无数据`; - this.$message.warning({ - duration: 15, - content: errorMsg, - closable: true, - }); - */ } } if (this.$refs['editChart' + chartInfo.id] && this.$refs['editChart' + chartInfo.id].length > 0) { @@ -1232,90 +1227,71 @@ } }, getEndpointInfoChartData(chartInfo) { + let vm = this; + let detail = []; + chartInfo.title = this.$t("project.chart.endpointInfo"); + detail.push({title: this.$t("project.chart.basicTitle"), data: function() { + let data = {}; + vm.detail.forEach(item => { + data[item.label] = item.value; + }); + data[vm.$t("alert.list.state")] = ""; + return data; + }()}); let endpointId = this.additionalInfo.id; let alertMsg = new Promise((resolve, reject) => { - this.$get('/alert/message?endpointId='+endpointId).then(response=>{ - if(response.code == 200){ - resolve(response.data); - } else { - reject(false); + this.$get('/alert/message?endpointId=' + endpointId).then(response => { + if (response.code == 200) { + let alerts = {}; + response.data && function () { + response.data.list.forEach(item => { + let alertName = item.alertRule.alertName; + let hasAlert = false; + for (let alert in alerts) { + if (alertName == alert) { + hasAlert = true; + } + } + if (hasAlert) { + alerts[alertName]++; + } else { + alerts[alertName] = 1; + } + }); + }(); + detail.push({title: this.$t("overall.alert"), data: alerts}); + resolve(true); } }); }); - /*let endpointState = new Promise((resolve, reject) => { - let query = `up{endpoint="${endpointId}"}`; - this.$get('/prom/api/v1/query_range?query=' + query + "&start=" + this.$stringTimeParseToUnix(this.filter.start_time) + "&end=" + this.$stringTimeParseToUnix(this.filter.end_time) + '&step=' + bus.getStep(this.filter.start_time, this.filter.end_time)).then(response=>{ - if(response.status == "success"){ - resolve(response.data); - } else { - reject(false); - } - }); - });*/ - Promise.all([alertMsg]).then(result => { - //endpointInfo的告警信息 - let alerts = []; - let data = result[0]; - if (data.list) { - data.list.forEach(item => { - let index = -1; - let hasLabel = alerts.some((alert, i) => { - if (alert.label == item.alertRule.alertName) { - index = i; - } - return alert.label == item.alertRule.alertName; - }); - if (hasLabel) { - alerts[index].value++; - } else { - alerts.push({label: item.alertRule.alertName, value: 1}); - } - }); - } - this.$refs['editChart'+chartInfo.id][0].setData(chartInfo, this.detail, alerts); - //endpointInfo的state - /*data = result[1]; - let state = []; - if (data.result && data.result[0] && data.result[0].values) { - state = data.result[0].values; - } - - this.$refs['editChart'+chartInfo.id][0].setData(chartInfo, this.detail, alerts, state);*/ + alertMsg.then(result => { + this.$refs['editChart'+chartInfo.id][0].setData(chartInfo, detail); }, err => { }); - /*req.then(data => { - let alerts = []; - if (data && data.list) { - data.list.forEach(item => { - let index = -1; - let hasLabel = alerts.some((alert, i) => { - if (alert.label == item.alertRule.alertName) { - index = i; - } - return alert.label == item.alertRule.alertName; - }); - if (hasLabel) { - alerts[index].value++; - } else { - alerts.push({label: item.alertRule.alertName, value: 1}); - } - }); - } - this.$refs['editChart'+chartInfo.id][0].setData(chartInfo, this.detail, alerts); - });*/ }, getAssetInfoChartData(chartInfo){ + let vm = this; + chartInfo.title = this.$t("asset.createAssetTab.assetInfo"); + let detail = []; if(!this.isModel){ - this.$refs['editChart'+chartInfo.id][0].showLoad(chartInfo); + this.$refs['editChart'+chartInfo.id][0].showLoad(); let assetId = this.additionalInfo.assetId ? this.additionalInfo.assetId : this.additionalInfo.id; this.$get('/asset/info?id=' + assetId).then(response=>{ if(response.code == 200){ - this.$refs['editChart'+chartInfo.id][0].setData(chartInfo, response.data, this.filter.panelId, this.filter); - }else{ - this.$refs['editChart'+chartInfo.id][0].setData(chartInfo, {}, this.filter.panelId, this.filter, response.msg); + response.data && function() { + response.data.Basic && detail.push({ + title: vm.$t('asset.createAssetTab.basicTitle'), + data: response.data.Basic + }); + response.data.Feature && detail.push({ + title: vm.$t('asset.createAssetTab.featureTitle'), + data: response.data.Feature + }); + }(); } - }) + this.$refs['editChart'+chartInfo.id][0].setData(chartInfo, detail, this.filter.panelId, this.filter); + }); }else { let data={ Basic:{ @@ -1353,62 +1329,112 @@ } }, getProjectInfoChartData(chartInfo){ + let vm = this; + let detail = []; if(!this.isModel){ this.$refs['editChart'+chartInfo.id][0].showLoad(chartInfo); this.$get('/project/info?id='+this.additionalInfo.id).then(response=>{ if(response.code == 200){ - this.$refs['editChart'+chartInfo.id][0].setData(chartInfo, response.data, this.filter.panelId, this.filter); - }else{ - this.$refs['editChart'+chartInfo.id][0].setData(chartInfo, {}, this.filter.panelId, this.filter, response.msg); + response.data && function() { + response.data.basic && detail.push({ + title: vm.$t('project.chart.basicTitle'), + data: response.data.basic + }); + response.data.module && function() { + response.data.module.forEach(d => { + detail.push({ + title: `${vm.$t('project.module.module')}:${d.name}`, + data: d + }); + }); + }(); + }(); } + this.$refs['editChart'+chartInfo.id][0].setData(chartInfo, detail, this.filter.panelId, this.filter, response.msg); }) }else { - let data={ - basic: { + detail.push({ + title: "system", + data: { id: 1, name: "system", remark: "描述信息", alertStat: [1,2,3], - }, - module: [ - { - id: 1, - name: "kafka", - type: "http", - remark: "描述信息", - endpointStat: [3,23], - alertStat: [2,3,4], - }, - { - id: 2, - name: "kafkakafkakafkakafkakafkakafkakafka", - type: "http", - remark: "描述信息", - endpointStat: [3,23], - alertStat: [2,0,4], - }, - { - id: 3, - name: "kafkakafka", - type: "snmp", - remark: "描述信息", - endpointStat: [3,0], - alertStat: [2,3,4], - }, - ] - }; - this.$refs['editChart'+chartInfo.id][0].setData(chartInfo, data, this.filter.panelId, this.filter); + } + }); + detail.push({ + title: `${this.$t("project.module.module")}:kafka`, + data: { + id: 1, + name: "kafka", + type: "http", + remark: "描述信息", + endpointStat: [3,23], + alertStat: [2,3,4], + } + }); + detail.push({ + title: `${this.$t("project.module.module")}:kafkakafkakafkakafkakafkakafkakafka`, + data: { + id: 2, + name: "kafkakafkakafkakafkakafkakafkakafka", + type: "http", + remark: "描述信息", + endpointStat: [3,23], + alertStat: [2,0,4], + } + }); + detail.push({ + title: `${this.$t("project.module.module")}:kafkakafka`, + data: { + id: 3, + name: "kafkakafka", + type: "snmp", + remark: "描述信息", + endpointStat: [3,0], + alertStat: [2,3,4], + } + }); + this.$refs['editChart'+chartInfo.id][0].setData(chartInfo, detail, this.filter.panelId, this.filter); } }, getAlertListChartData:function(chartInfo,filterType){ this.$refs['editChart'+chartInfo.id][0].getAlertList(filterType); }, getAlertRuleChartData(chartInfo) { - this.$get("alert/rule/stat?id=" + this.additionalInfo.id).then(response => { - if (response.code === 200) { - this.$refs['editChart'+chartInfo.id][0].setData(chartInfo, response.data); - } + let vm = this; + let detail = []; + let req = new Promise((resolve, reject) => { + this.$get("alert/rule/stat?id=" + this.additionalInfo.id).then(response => { + if (response.code == 200) { + response.data && function () { + for (let type in response.data) { + if (type == "project" && response.data.project.length > 0) { + detail.push({title: vm.$t("project.project.project"), data: convert(response.data.project)}); + } else if (type == "module" && response.data.module.length > 0) { + detail.push({title: vm.$t("project.module.module"), data: convert(response.data.module)}); + } else if (type == "asset" && response.data.asset.length > 0) { + detail.push({title: vm.$t("asset.asset"), data: convert(response.data.asset)}); + } else if (type == "endpoint" && response.data.endpoint.length > 0) { + detail.push({title: vm.$t("project.endpoint.endpoint"), data: convert(response.data.endpoint)}); + } + } + }(); + resolve(true); + } + }); }); + req.then(result => { + this.$refs['editChart'+chartInfo.id][0].setData(chartInfo, detail); + }, err => {}); + + function convert(d) { + let data = {}; + d.forEach(item => { + data[item.name] = item.nums; + }); + return data; + } }, // 设置图表的尺寸 @@ -1552,7 +1578,7 @@ //1.元素距离页面顶部的距离 var mainOffsetTop = ele.offsetTop;//offsetTop: 当前元素顶部距离最近父元素顶部的距离,和有没有滚动条没有关系。单位px,只读元素。 //2.元素的高度 - var mainHeight = itemHeight//ele.offsetHeight;//itemHeight; + var mainHeight = itemHeight;//ele.offsetHeight;//itemHeight; //3.页面滚动的距离 var windowScrollTop = scrollTop;//document.documentElement.scrollTop || document.body.scrollTop; //4.浏览器可见区域的高度 @@ -1575,7 +1601,7 @@ let chartType = item.type; item.isLoaded = true; if(chartType!=='url'){ - that.getChartDataForSearch(item,index); + that.getChartDataForSearch(item, index); } else { that.$refs['editChart'+item.id][0].showLoad(item); } diff --git a/nezha-fronted/src/components/charts/chart.scss b/nezha-fronted/src/components/charts/chart.scss index 6468bf8b0..0aa69b826 100644 --- a/nezha-fronted/src/components/charts/chart.scss +++ b/nezha-fronted/src/components/charts/chart.scss @@ -198,63 +198,68 @@ } } } - .chart-asset-info, .chart-project-info, .chart-alert-rule-info { - .chart-info-container { + .chart-container { + height: 100%; + position: relative; + background-color: white; + .chart-title:hover { + background-color:#d8dce1; + } + .chart-title:not(.drag-disabled) { + cursor: move; + } + .chart-title { + text-align: center; + width: 100%; + line-height: 30px; + height: 28px; + padding: 1px 3px 0 3px; + box-sizing: border-box; + .nz-chart-top{ + width:100%; + } + .el-dropdown-link { + cursor: move; + } + .el-icon-arrow-down { + font-size: 12px; + } + .chart-title-text { + font-weight: bold; + font-size: 18px; + line-height: 26px; + color: #333; + display:flex; + justify-content:center; + align-items:center; + max-width:calc(100% - 20px); + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + .chart-title-icon{ + display: inline-block; + cursor: pointer; + } + } + } + .chart-info { padding-top: 6px; + width: 100%; + height: calc(100% - 10px); } .active-icon { margin: 0; } - .asset-info-content { - width: 100%; - height: calc(100% - 10px); - } - .asset-info-content-title { + .chart-sub-title { background-color: #efefef; font-size: 13px; color: #505255; padding-left: 2px; height: 25px; line-height: 25px; + user-select: none; } - .basic-content-asset.fold, .feature-content-asset.fold { - animation-name: fold-500; //该动画定义在main.scss里 - animation-duration: 0.3s; - animation-iteration-count:1; - opacity: 0; - max-height: 0; - visibility: hidden; - } - .basic-content-project.init-no-animation { - opacity: 1; - max-height: 200px; - visibility: visible; - } - .basic-content-project.fold, .feature-content-project.fold { - animation-name: fold-200; //该动画定义在main.scss里 - animation-duration: 0.3s; - animation-iteration-count:1; - opacity: 0; - max-height: 0; - visibility: hidden; - } - .basic-content-asset.unfold, .feature-content-asset.unfold { - animation-name: unfold-500; - animation-duration: 0.3s; - animation-iteration-count:1; - opacity: 1; - max-height: 500px; - visibility: visible; - } - .basic-content-project.unfold, .feature-content-project.unfold { - animation-name: unfold-200; - animation-duration: 0.3s; - animation-iteration-count:1; - opacity: 1; - max-height: 200px; - visibility: visible; - } - .basic-content, .feature-content { + .chart-sub-content { opacity: 0; max-height: 0; visibility: hidden; @@ -360,6 +365,43 @@ } } } + .init-no-animation { + opacity: 1; + max-height: 200px; + visibility: visible; + } + .fold-500 { + animation-name: fold-500; //该动画定义在main.scss里 + animation-duration: 0.2s; + animation-iteration-count:1; + opacity: 0; + max-height: 0; + visibility: hidden; + } + .fold-200 { + animation-name: fold-200; //该动画定义在main.scss里 + animation-duration: 0.2s; + animation-iteration-count:1; + opacity: 0; + max-height: 0; + visibility: hidden; + } + .unfold-500 { + animation-name: unfold-500; + animation-duration: 0.2s; + animation-iteration-count:1; + opacity: 1; + max-height: 500px; + visibility: visible; + } + .unfold-200 { + animation-name: unfold-200; + animation-duration: 0.2s; + animation-iteration-count:1; + opacity: 1; + max-height: 200px; + visibility: visible; + } } .chart-url { .url-container {