Compare commits

..

12 Commits

Author SHA1 Message Date
tomsun28
7ca1ae88fd [monitor]feature:remove duplicate port monitor yml 2022-04-05 22:23:44 +08:00
tomsun28
63d1257275 [monitor]feature:all monitors set advanced params 2022-04-05 22:14:39 +08:00
tomsun28
442d4d1dfb [monitor]feature:support hide advanced params define 2022-04-05 22:01:23 +08:00
tomsun28
51266aab87 [collector,alerter]bugfix:monitors always timeout alert (#67) 2022-04-05 20:50:33 +08:00
tomsun28
e99dd2e870 [script]feature:support win bat service (#65)
* [script]feature:support win bat service

* add sponsor
2022-04-04 20:55:09 +08:00
tomsun28
95c36fd9a3 [webapp,home]change tancloud brand to hertzbeat (#63) 2022-04-04 12:35:16 +08:00
tomsun28
2553c6f61e [warehouse]feature:make tdengine optional, not required (#62) 2022-04-04 11:45:41 +08:00
tomsun28
cc92f82472 [webapp]feature:http port follows ssl change (#61) 2022-04-04 11:02:12 +08:00
tomsun28
8dd2c1e47f [manager]feature:[api, website] support basic auth, digest auth (#60) 2022-04-04 09:15:03 +08:00
tomsun28
6a9d65ba5d [manager,collector]feature:api monitor support query params (#59) 2022-04-04 08:40:23 +08:00
tomsun28
3c48b4c71f [manager,webapp]feature:api monitor support http headers (#58) 2022-04-03 22:12:57 +08:00
tomsun28
636303021f [monitor]feature:param yml support key-value map (#57) 2022-04-03 21:35:56 +08:00
37 changed files with 640 additions and 117 deletions

View File

@@ -110,7 +110,7 @@ public class CalculateAlarm {
} else {
// 其他异常
alertBuilder.target(CommonConstants.AVAILABLE)
.content("监控紧急可用性告警: " + metricsData.getCode().name());
.content("监控可用性告警: " + metricsData.getCode().name() + " : " + metricsData.getMsg());
triggeredMonitorStateAlertMap.put(monitorId, metricsData.getCode());
dataQueue.addAlertData(alertBuilder.build());
}

View File

@@ -105,9 +105,13 @@ public class CommonDispatcher implements MetricsTaskDispatch, CollectDataDispatc
.setId(timerJob.getJob().getMonitorId())
.setApp(timerJob.getJob().getApp())
.setMetrics(metricsTime.getMetrics().getName())
.setPriority(metricsTime.getMetrics().getPriority())
.setTime(System.currentTimeMillis())
.setCode(CollectRep.Code.TIMEOUT).setMsg("collect timeout").build();
log.error("[Collect Timeout]: \n{}", metricsData);
if (metricsData.getPriority() == 0) {
dispatchCollectData(metricsTime.timeout, metricsTime.getMetrics(), metricsData);
}
metricsTimeoutMonitorMap.remove(entry.getKey());
}
}
@@ -165,8 +169,8 @@ public class CommonDispatcher implements MetricsTaskDispatch, CollectDataDispatc
metricsSet.forEach(metricItem -> {
MetricsCollect metricsCollect = new MetricsCollect(metricItem, timeout, this);
jobRequestQueue.addJob(metricsCollect);
metricsTimeoutMonitorMap.put(job.getId() + "-" + metrics.getName(),
new MetricsTime(System.currentTimeMillis(), metrics, timeout));
metricsTimeoutMonitorMap.put(job.getId() + "-" + metricItem.getName(),
new MetricsTime(System.currentTimeMillis(), metricItem, timeout));
});
} else {
// 当前执行级别的指标组列表未全执行完成,
@@ -185,8 +189,8 @@ public class CommonDispatcher implements MetricsTaskDispatch, CollectDataDispatc
metricsSet.forEach(metricItem -> {
MetricsCollect metricsCollect = new MetricsCollect(metricItem, timeout, this);
jobRequestQueue.addJob(metricsCollect);
metricsTimeoutMonitorMap.put(job.getId() + "-" + metrics.getName(),
new MetricsTime(System.currentTimeMillis(), metrics, timeout));
metricsTimeoutMonitorMap.put(job.getId() + "-" + metricItem.getName(),
new MetricsTime(System.currentTimeMillis(), metricItem, timeout));
});
} else {
// 当前执行级别的指标组列表未全执行完成,

View File

@@ -34,6 +34,10 @@ import java.util.stream.Collectors;
@Slf4j
@Data
public class MetricsCollect implements Runnable, Comparable<MetricsCollect> {
/**
* 调度告警阈值时间 100ms
*/
private static final long WARN_DISPATCH_TIME = 100;
/**
* 监控ID
*/
@@ -267,11 +271,15 @@ public class MetricsCollect implements Runnable, Comparable<MetricsCollect> {
private CollectRep.MetricsData validateResponse(CollectRep.MetricsData.Builder builder) {
long endTime = System.currentTimeMillis();
builder.setTime(endTime);
log.debug("[Collect]: newTime: {}, startTime: {}, spendTime: {}.", newTime, startTime, endTime - startTime);
long runningTime = endTime - startTime;
long allTime = endTime - newTime;
if (startTime - newTime >= WARN_DISPATCH_TIME) {
log.warn("[Collector Dispatch Warn, Dispatch Use {}ms.", startTime - newTime);
}
if (builder.getCode() != CollectRep.Code.SUCCESS) {
log.info("[Collect Fail] Reason: {}", builder.getMsg());
log.info("[Collect Failed, Run {}ms, All {}ms] Reason: {}", runningTime, allTime, builder.getMsg());
} else {
log.info("[Collect Success].");
log.info("[Collect Success, Run {}ms, All {}ms].", runningTime, allTime);
}
return builder.build();
}

View File

@@ -90,7 +90,7 @@ public class WheelTimerTask implements TimerTask {
if (key != null && key.startsWith("^_^") && key.endsWith("^_^")) {
key = key.replaceAll("\\^_\\^", "");
Configmap param = configmap.get(key);
if (param.getType() == (byte) 3) {
if (param != null && param.getType() == (byte) 3) {
String jsonValue = (String) param.getValue();
Map<String, String> map = GsonUtil.fromJson(jsonValue, Map.class);
if (map != null) {

View File

@@ -122,6 +122,12 @@ public class ParamDefine {
@ApiModelProperty(value = "当type为key-value时有效,表示value的别名描述", example = "Value", accessMode = READ_WRITE, position = 10)
private String valueAlias;
/**
* 是否是高级隐藏参数 true-是 false-否
*/
@ApiModelProperty(value = "是否是高级隐藏参数 true-是 false-否", example = "true", accessMode = READ_WRITE, position = 11)
private boolean hide = false;
/**
* 此条记录创建者
*/

View File

@@ -12,7 +12,8 @@ sidebar_label: 赞助
![wechat-alipay](https://cdn.jsdelivr.net/gh/dromara/hertzbeat@gh-pages/img/docs/pay.png)
感谢[吉实信息(构建全新的微波+光交易网络)](https://www.flarespeed.com)赞助服务器采集节点
感谢[吉实信息(构建全新的微波+光交易网络)](https://www.flarespeed.com) 赞助服务器采集节点
感谢[天上云计算(全新智慧上云)](https://www.tsyvps.com/aff/BZBEGYLX) 赞助服务器采集节点

View File

@@ -7,8 +7,8 @@ const repoUrl = `https://github.com/dromara/${projectName}`
const cdnUrl = 'https://cdn.jsdelivr.net/gh/dromara/hertzbeat@gh-pages/'
module.exports = {
title: 'TANCLOUD探云',
tagline: '易用友好的高性能监控云',
title: 'HertzBeat',
tagline: '易用友好的云监控系统',
url: 'https://hertzbeat.com',
baseUrl: '/',
onBrokenLinks: 'throw',

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@@ -0,0 +1,4 @@
<svg width="199" height="53" viewBox="0 0 199 53" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M60.0112 15.3828V36H57.3208V15.3828H60.0112ZM66.6382 15.3828V17.6201H50.708V15.3828H66.6382ZM76.1963 17.2095L69.3711 36H66.5815L74.4404 15.3828H76.2388L76.1963 17.2095ZM81.917 36L75.0776 17.2095L75.0352 15.3828H76.8335L84.7207 36H81.917ZM81.563 28.3677V30.605H69.98V28.3677H81.563ZM103.341 15.3828V36H100.594L90.2148 20.0981V36H87.4819V15.3828H90.2148L100.637 31.3271V15.3828H103.341ZM120.631 29.4438H123.35C123.208 30.7466 122.835 31.9124 122.231 32.9414C121.627 33.9704 120.772 34.7869 119.668 35.3911C118.563 35.9858 117.185 36.2832 115.533 36.2832C114.325 36.2832 113.225 36.0566 112.234 35.6035C111.252 35.1504 110.407 34.5085 109.699 33.6777C108.991 32.8376 108.444 31.8322 108.057 30.6616C107.679 29.4816 107.49 28.1694 107.49 26.7251V24.6719C107.49 23.2275 107.679 21.9201 108.057 20.7495C108.444 19.5695 108.996 18.5594 109.713 17.7192C110.44 16.8791 111.313 16.2324 112.333 15.7793C113.353 15.3262 114.5 15.0996 115.774 15.0996C117.332 15.0996 118.648 15.3923 119.725 15.9775C120.801 16.5628 121.636 17.3747 122.231 18.4131C122.835 19.4421 123.208 20.6362 123.35 21.9956H120.631C120.499 21.0327 120.253 20.2067 119.895 19.5176C119.536 18.819 119.026 18.2809 118.365 17.9033C117.704 17.5257 116.841 17.3369 115.774 17.3369C114.858 17.3369 114.051 17.5116 113.353 17.8608C112.663 18.2101 112.083 18.7057 111.611 19.3477C111.148 19.9896 110.799 20.759 110.563 21.6558C110.327 22.5526 110.209 23.5485 110.209 24.6436V26.7251C110.209 27.7352 110.313 28.6839 110.521 29.5713C110.738 30.4587 111.063 31.2375 111.498 31.9077C111.932 32.578 112.484 33.1066 113.154 33.4937C113.825 33.8713 114.618 34.0601 115.533 34.0601C116.694 34.0601 117.619 33.876 118.309 33.5078C118.998 33.1396 119.517 32.611 119.866 31.9219C120.225 31.2327 120.48 30.4067 120.631 29.4438ZM139.577 33.7769V36H129.269V33.7769H139.577ZM129.807 15.3828V36H127.074V15.3828H129.807ZM157.646 25.04V26.3428C157.646 27.891 157.452 29.2786 157.065 30.5059C156.678 31.7331 156.121 32.7762 155.394 33.6353C154.667 34.4943 153.794 35.1504 152.774 35.6035C151.764 36.0566 150.632 36.2832 149.376 36.2832C148.158 36.2832 147.04 36.0566 146.02 35.6035C145.01 35.1504 144.132 34.4943 143.386 33.6353C142.65 32.7762 142.079 31.7331 141.673 30.5059C141.267 29.2786 141.064 27.891 141.064 26.3428V25.04C141.064 23.4919 141.262 22.1089 141.659 20.8911C142.065 19.6639 142.636 18.6208 143.372 17.7617C144.108 16.8932 144.982 16.2324 145.992 15.7793C147.011 15.3262 148.13 15.0996 149.348 15.0996C150.603 15.0996 151.736 15.3262 152.746 15.7793C153.766 16.2324 154.639 16.8932 155.366 17.7617C156.102 18.6208 156.664 19.6639 157.051 20.8911C157.447 22.1089 157.646 23.4919 157.646 25.04ZM154.941 26.3428V25.0117C154.941 23.7845 154.813 22.6989 154.559 21.7549C154.313 20.8109 153.95 20.0179 153.468 19.376C152.987 18.734 152.397 18.2479 151.698 17.9175C151.009 17.5871 150.226 17.4219 149.348 17.4219C148.498 17.4219 147.729 17.5871 147.04 17.9175C146.36 18.2479 145.775 18.734 145.284 19.376C144.802 20.0179 144.429 20.8109 144.165 21.7549C143.901 22.6989 143.769 23.7845 143.769 25.0117V26.3428C143.769 27.5794 143.901 28.6745 144.165 29.6279C144.429 30.5719 144.807 31.3696 145.298 32.021C145.798 32.6629 146.388 33.1491 147.068 33.4795C147.757 33.8099 148.526 33.9751 149.376 33.9751C150.263 33.9751 151.052 33.8099 151.741 33.4795C152.43 33.1491 153.01 32.6629 153.482 32.021C153.964 31.3696 154.327 30.5719 154.573 29.6279C154.818 28.6745 154.941 27.5794 154.941 26.3428ZM173.533 15.3828H176.252V29.3306C176.252 30.8787 175.907 32.1673 175.218 33.1963C174.529 34.2253 173.613 34.9993 172.471 35.5186C171.338 36.0283 170.106 36.2832 168.775 36.2832C167.378 36.2832 166.113 36.0283 164.98 35.5186C163.857 34.9993 162.965 34.2253 162.304 33.1963C161.653 32.1673 161.327 30.8787 161.327 29.3306V15.3828H164.032V29.3306C164.032 30.4067 164.23 31.2941 164.626 31.9927C165.023 32.6912 165.575 33.2104 166.283 33.5503C167.001 33.8901 167.831 34.0601 168.775 34.0601C169.729 34.0601 170.56 33.8901 171.268 33.5503C171.985 33.2104 172.542 32.6912 172.938 31.9927C173.335 31.2941 173.533 30.4067 173.533 29.3306V15.3828ZM186.122 36H181.817L181.845 33.7769H186.122C187.594 33.7769 188.821 33.4701 189.803 32.8564C190.785 32.2334 191.521 31.3649 192.012 30.251C192.513 29.1276 192.763 27.8154 192.763 26.3145V25.0542C192.763 23.8742 192.621 22.8263 192.338 21.9106C192.055 20.9855 191.639 20.2067 191.092 19.5742C190.544 18.9323 189.874 18.4461 189.081 18.1157C188.298 17.7853 187.396 17.6201 186.376 17.6201H181.732V15.3828H186.376C187.726 15.3828 188.958 15.6094 190.072 16.0625C191.186 16.5062 192.144 17.1528 192.947 18.0024C193.759 18.8426 194.382 19.8621 194.816 21.061C195.25 22.2505 195.467 23.591 195.467 25.0825V26.3145C195.467 27.806 195.25 29.1512 194.816 30.3501C194.382 31.5396 193.754 32.5544 192.933 33.3945C192.121 34.2347 191.139 34.8813 189.987 35.3345C188.845 35.7782 187.556 36 186.122 36ZM183.275 15.3828V36H180.542V15.3828H183.275Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.001 0C10.748 0 0 10.745 0 24.001C0 33.826 5.90999 42.271 14.369 45.982C14.301 44.308 14.357 42.293 14.784 40.47C15.246 38.522 17.106 27.5 17.106 27.5C17.106 27.5 17.106 25.861 17.106 23.595C17.106 20.039 19.434 21.819 22 21.819L25.5 23C32 23 42.5 23 42.5 23C42.5 23 32.943 15.5 26.5 15.5C19.221 15.5 18.5 15.5 18.5 15.5C18.5 17.594 13.5 18 13.5 18L11.5 15.5L9 14.5C9 7.684 13.52 8.069 24.922 8.069C34.086 8.069 48 14.5 42.5 23C42.5 23 33.219 23 25.5 23C22.912 23 28.336 31.09 27.5 29.5C27.5 29.5 28 33 22 37C21.492 38.85 18.111 45.575 17.2 47.015C19.359 47.653 21.64 48 24.001 48C37.255 48 48 37.255 48 24.001C48 10.745 37.255 0 24.001 0Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@@ -26,6 +26,12 @@ configmap:
type: 1
- key: payload
type: 1
- key: authType
type: 1
- key: headers
type: 3
- key: params
type: 3
# 指标组列表
metrics:
# 第一个监控指标组 cpu
@@ -58,12 +64,18 @@ metrics:
# 请求头内容
headers:
content-type: ^_^contentType^_^
^_^headers^_^: ^_^headers^_^
# 请求参数内容
params:
^_^params^_^: ^_^params^_^
# 认证
authorization:
# 认证方式: Basic Auth, Digest Auth, Bearer Token
type: Basic Auth
type: ^_^authType^_^
basicAuthUsername: ^_^username^_^
basicAuthPassword: ^_^password^_^
digestAuthUsername: ^_^username^_^
digestAuthPassword: ^_^password^_^
# 响应数据解析方式: default-系统规则,jsonPath-jsonPath脚本,website-网站可用性指标监控
# todo xmlPath-xmlPath脚本,prometheus-Prometheus数据规则
parseType: website

View File

@@ -1,25 +0,0 @@
category: service
app: telnet
name:
zh-CN: TELNET端口可用性
en-US: PORT TELNET
configmap:
- key: host
type: 1
- key: port
type: 0
- key: timeout
type: 0
metrics:
- name: summary
priority: 0
fields:
- field: responseTime
type: 0
unit: ms
protocol: telnet
# 当protocol为telnet协议时具体的采集配置
telnet:
host: ^_^host^_^
port: ^_^port^_^
timeout: ^_^timeout^_^

View File

@@ -12,6 +12,12 @@ configmap:
type: 1
- key: ssl
type: 1
- key: authType
type: 1
- key: username
type: 1
- key: password
type: 2
# 指标组列表
metrics:
# 第一个监控指标组 cpu
@@ -40,6 +46,13 @@ metrics:
method: GET
# 是否启用ssl/tls,即是http还是https,默认false
ssl: ^_^ssl^_^
authorization:
# 认证方式: Basic Auth, Digest Auth, Bearer Token
type: ^_^authType^_^
basicAuthUsername: ^_^username^_^
basicAuthPassword: ^_^password^_^
digestAuthUsername: ^_^username^_^
digestAuthPassword: ^_^password^_^
# 响应数据解析方式: default-系统规则,jsonPath-jsonPath脚本,website-网站可用性指标监控
# todo xmlPath-xmlPath脚本,prometheus-Prometheus数据规则
parseType: website

View File

@@ -26,10 +26,12 @@ param:
# 当type为text时,用limit表示字符串限制大小
limit: 20
required: false
hide: true
- field: password
name: 密码
type: password
required: false
hide: true
- field: ssl
name: 启动SSL
# 当type为boolean时,前端用switch展示开关
@@ -50,7 +52,7 @@ param:
- label: DELETE请求
value: DELETE
- field: headers
name: 请求
name: 请求Headers
type: key-value
required: false
keyAlias: Header Name

View File

@@ -17,13 +17,6 @@ param:
range: '[0,65535]'
required: true
defaultValue: 80
- field: uri
name: 相对路径
type: text
# 当type为text时,用limit表示字符串限制大小
limit: 200
required: false
placeholder: 'API地址除IP端口外的路径 例如:/v2/book/bar'
- field: method
name: 请求方式
type: radio
@@ -38,28 +31,62 @@ param:
value: PUT
- label: DELETE请求
value: DELETE
- field: uri
name: 相对路径
type: text
# 当type为text时,用limit表示字符串限制大小
limit: 200
required: false
placeholder: 'API地址除IP端口外的路径 例如:/v2/book/bar'
- field: ssl
name: 启用HTTPS
# 当type为boolean时,前端用switch展示开关
type: boolean
required: true
- field: headers
name: 请求Headers
type: key-value
required: false
keyAlias: Header Name
valueAlias: Header Value
- field: params
name: 查询Params
type: key-value
required: false
keyAlias: Param Key
valueAlias: Param Value
- field: contentType
name: Content-Type
type: text
placeholder: '请求BODY资源类型'
required: false
hide: true
- field: payload
name: 请求BODY
type: textarea
placeholder: 'POST PUT请求时有效'
required: false
hide: true
- field: authType
name: 认证方式
type: radio
required: false
hide: true
# 当type为radio单选框,checkbox复选框时,option表示可选项值列表 {name1:value1,name2:value2}
options:
- label: Basic Auth
value: Basic Auth
- label: Digest Auth
value: Digest Auth
- field: username
name: 用户名
type: text
# 当type为text时,用limit表示字符串限制大小
limit: 20
required: false
hide: true
- field: password
name: 密码
type: password
required: false
- field: contentType
name: Content-Type
type: text
placeholder: '请求BODY资源类型'
required: false
- field: payload
name: 请求BODY
type: textarea
placeholder: 'POST PUT请求时有效'
required: false
hide: true

View File

@@ -17,6 +17,7 @@ param:
required: false
defaultValue: 6000
placeholder: '查询超时时间'
hide: true
- field: database
name: 数据库名称
type: text
@@ -34,3 +35,4 @@ param:
name: URL
type: text
required: false
hide: true

View File

@@ -15,6 +15,7 @@ param:
name: 查询超时时间
type: number
required: false
hide: true
defaultValue: 6000
placeholder: '查询超时时间'
- field: database
@@ -34,3 +35,4 @@ param:
name: URL
type: text
required: false
hide: true

View File

@@ -15,6 +15,7 @@ param:
name: 查询超时时间
type: number
required: false
hide: true
defaultValue: 6000
placeholder: '查询超时时间'
- field: database
@@ -34,3 +35,4 @@ param:
name: URL
type: text
required: false
hide: true

View File

@@ -15,6 +15,7 @@ param:
name: 查询超时时间
type: number
required: false
hide: true
defaultValue: 6000
placeholder: '查询超时时间'
- field: database
@@ -34,3 +35,4 @@ param:
name: URL
type: text
required: false
hide: true

View File

@@ -15,6 +15,7 @@ param:
name: 查询超时时间
type: number
required: false
hide: true
defaultValue: 6000
placeholder: '查询超时时间'
- field: database
@@ -34,3 +35,4 @@ param:
name: URL
type: text
required: false
hide: true

View File

@@ -1,27 +0,0 @@
# 监控应用类型名称(与文件名保持一致) eg: linux windows tomcat mysql aws...
app: telnet
# 强制固定必须参数 - host(ipv4,ipv6,域名)
param:
# field-字段名称标识符
- field: host
# name-参数字段显示名称
name: 主机Host
# type-字段类型,样式(大部分映射input标签type属性)
type: host
# 是否是必输项 true-必填 false-可选
required: true
- field: port
name: 端口
type: number
# 当type为number时,用range表示范围
range: '[0,65535]'
required: true
defaultValue: 80
- field: timeout
name: Telnet超时时间
type: number
# 当type为number时,用range表示范围
range: '[0,100000]'
required: true
placeholder: '请输入超时时间,单位毫秒'
defaultValue: 6000

View File

@@ -29,3 +29,26 @@ param:
# 当type为boolean时,前端用switch展示开关
type: boolean
required: true
- field: authType
name: 认证方式
type: radio
required: false
hide: true
# 当type为radio单选框,checkbox复选框时,option表示可选项值列表 {name1:value1,name2:value2}
options:
- label: Basic Auth
value: Basic Auth
- label: Digest Auth
value: Digest Auth
- field: username
name: 用户名
type: text
# 当type为text时,用limit表示字符串限制大小
limit: 20
required: false
hide: true
- field: password
name: 密码
type: password
required: false
hide: true

View File

@@ -0,0 +1,23 @@
@title HertzBeat
@echo off
setlocal enabledelayedexpansion
rem 项目名称
set SERVER_NAME="${project.artifactId}"
rem 应用的端口号
set SERVER_PORT=1157
echo Start shutdown HertzBeat %SERVER_NAME%
for /f "tokens=1-5" %%i in ('netstat -ano^|findstr ":%SERVER_PORT%"') do (
echo kill the process %%m who use the port
taskkill /pid %%m -t -f
echo Shutdown HertzBeat %SERVER_NAME% Success!
goto q
)
echo Faild shutdown HertzBeat %SERVER_NAME%
:q
pause

View File

@@ -0,0 +1,56 @@
@title HertzBeat
@echo off
setlocal enabledelayedexpansion
rem 项目名称
set SERVER_NAME=${project.artifactId}
rem jar名称
set JAR_NAME=${project.build.finalName}.jar
rem 进入bin目录
cd /d %~dp0
rem 返回到上一级项目根目录路径
cd ..
rem 打印项目安装根目录绝对路径
set DEPLOY_DIR=%~dp0..
echo %DEPLOY_DIR%
rem 外部配置文件绝对目录,如果是目录需要/结尾,也可以直接指定文件
rem 如果指定的是目录,spring则会读取目录中的所有配置文件
set CONF_DIR=%DEPLOY_DIR%\config
echo %CONF_DIR%
rem 应用的端口号
set SERVER_PORT=1157
for /f "tokens=1-5" %%i in ('netstat -ano^|findstr "0.0.0.0:%SERVER_PORT%"') do (
echo The HertzBeat %SERVER_NAME% port %SERVER_PORT% already used!
echo exit!
goto q
)
rem 项目日志输出绝对路径
set LOGS_DIR=%DEPLOY_DIR%\logs
rem JVM Configuration
set JAVA_OPTS= -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Duser.timezone=Asia/Shanghai
set JAVA_MEM_OPTS= -server -Xms256m -Xmx1024m -XX:SurvivorRatio=2 -XX:+UseParallelGC
rem 加载外部log文件的配置
set LOGGING_CONFIG=-Dlogging.config=%CONF_DIR%\logback-spring.xml
rem 注意配置文件目录最后的后缀需为 / 而不是 windows \
set CONFIG_FILES= -Dlogging.path=%LOGS_DIR% %LOGGING_CONFIG% -Dspring.config.location=%CONF_DIR%/
echo Starting the %SERVER_NAME% ...
start javaw %JAVA_OPTS% %JAVA_MEM_OPTS% %CONFIG_FILES% -jar %DEPLOY_DIR%\%JAR_NAME%
echo "Service starting OK!"
for /f "tokens=1-5" %%i in ('netstat -ano^|findstr ":%SERVER_PORT%"') do (
echo Service PID: %%m , Port %SERVER_PORT%
goto q
)
:q
pause

View File

@@ -61,11 +61,11 @@ public class TdEngineDataStorage implements DisposableBean {
log.error("init error, please config Warehouse TdEngine props in application.yml");
throw new IllegalArgumentException("please config Warehouse TdEngine props");
}
initTdEngineDatasource(properties.getStore().getTdEngine());
startStorageData();
boolean success = initTdEngineDatasource(properties.getStore().getTdEngine());
startStorageData(success);
}
private void initTdEngineDatasource(WarehouseProperties.StoreProperties.TdEngineProperties tdEngineProperties) {
private boolean initTdEngineDatasource(WarehouseProperties.StoreProperties.TdEngineProperties tdEngineProperties) {
HikariConfig config = new HikariConfig();
// jdbc properties
config.setJdbcUrl(tdEngineProperties.getUrl());
@@ -84,16 +84,28 @@ public class TdEngineDataStorage implements DisposableBean {
config.setIdleTimeout(0);
//validation query
config.setConnectionTestQuery("select server_status()");
try {
this.hikariDataSource = new HikariDataSource(config);
} catch (Exception e) {
log.error("\n\t------------------WARN WARN WARN------------------\n" +
"\t---------------Init TdEngine Failed---------------\n" +
"\t---------------Init TdEngine Failed---------------\n" +
"\t--------------Please Config Tdengine--------------\n" +
"\t----------Can Not Use Metric History Now----------\n" +
"\t----------Can Not Use Metric History Now----------\n" +
"\t----------Can Not Use Metric History Now----------\n");
return false;
}
return true;
}
private void startStorageData() {
private void startStorageData(boolean consume) {
Runnable runnable = () -> {
Thread.currentThread().setName("warehouse-tdEngine-data-storage");
while (!Thread.currentThread().isInterrupted()) {
try {
CollectRep.MetricsData metricsData = dataExporter.pollPersistentStorageMetricsData();
if (metricsData != null) {
if (consume && metricsData != null) {
saveData(metricsData);
}
} catch (InterruptedException e) {
@@ -232,6 +244,12 @@ public class TdEngineDataStorage implements DisposableBean {
Connection connection = null;
Map<String, List<Value>> instanceValuesMap = new HashMap<>(8);
try {
if (hikariDataSource == null) {
log.error("\n\t---------------TdEngine Init Failed---------------\n" +
"\t--------------Please Config Tdengine--------------\n" +
"\t----------Can Not Use Metric History Now----------\n");
return instanceValuesMap;
}
connection = hikariDataSource.getConnection();
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(selectSql);

View File

@@ -12,7 +12,7 @@
<global-footer [links]="links">
Copyright
<i nz-icon nzType="copyright" nzTheme="outline"></i>
2021
2022
<a href="https://tancloud.cn" target="_blank">探云 tancloud.cn | </a>
<a href="https://hertzbeat.com" target="_blank">赫兹跳动 hertzbeat.com</a>
</global-footer>

View File

@@ -12,4 +12,6 @@ export class ParamDefine {
// 当type为key-value时有效,表示别名描述
keyAlias!: string;
valueAlias!: string;
// 此参数是否隐藏 即默认不显示, 在高级设置区显示
hide: boolean = false;
}

View File

@@ -125,6 +125,7 @@
<nz-form-control *ngIf="paramDefine.type === 'boolean'" nzSpan="8" [nzErrorTip]="'validation.required' | i18n">
<nz-switch
[(ngModel)]="params[i].value"
(ngModelChange)="onParamBooleanChanged($event, paramDefine.field)"
[required]="paramDefine.required"
[name]="paramDefine.field"
[id]="paramDefine.field"
@@ -161,11 +162,151 @@
</nz-form-control>
</nz-form-item>
<nz-collapse [nzGhost]="true">
<nz-collapse-panel nzHeader="高级" [nzHeader]="extraColHeader" [nzShowArrow]="false">
<nz-form-item *ngFor="let paramDefine of advancedParamDefines; let i = index">
<nz-form-label
*ngIf="paramDefine.field !== 'host' && paramDefine.type === 'text'"
nzSpan="7"
[nzRequired]="paramDefine.required"
[nzFor]="paramDefine.field"
>{{ paramDefine.name }}
</nz-form-label>
<nz-form-control
*ngIf="paramDefine.field !== 'host' && paramDefine.type === 'text'"
nzSpan="8"
[nzErrorTip]="'validation.required' | i18n"
>
<input
nz-input
[(ngModel)]="advancedParams[i].value"
[required]="paramDefine.required"
[name]="paramDefine.field"
[type]="paramDefine.type"
[id]="paramDefine.field"
[placeholder]="paramDefine.placeholder ? paramDefine.placeholder : ''"
/>
</nz-form-control>
<nz-form-label
*ngIf="paramDefine.type === 'textarea'"
nzSpan="7"
[nzRequired]="paramDefine.required"
[nzFor]="paramDefine.field"
>{{ paramDefine.name }}
</nz-form-label>
<nz-form-control *ngIf="paramDefine.type === 'textarea'" nzSpan="8" [nzErrorTip]="'validation.required' | i18n">
<textarea
nz-input
[(ngModel)]="advancedParams[i].value"
[required]="paramDefine.required"
[name]="paramDefine.field"
[id]="paramDefine.field"
[placeholder]="paramDefine.placeholder ? paramDefine.placeholder : ''"
rows="3"
></textarea>
</nz-form-control>
<nz-form-label
*ngIf="paramDefine.type === 'password'"
nzSpan="7"
[nzRequired]="paramDefine.required"
[nzFor]="paramDefine.field"
>{{ paramDefine.name }}
</nz-form-label>
<nz-form-control *ngIf="paramDefine.type === 'password'" nzSpan="8" [nzErrorTip]="'validation.required' | i18n">
<nz-input-group [nzSuffix]="suffixTemplate" style="width: 100%">
<input
[type]="passwordVisible ? 'text' : 'password'"
nz-input
placeholder="input password"
[required]="paramDefine.required"
[(ngModel)]="advancedParams[i].value"
[id]="paramDefine.field"
[name]="paramDefine.field"
[placeholder]="paramDefine.placeholder ? paramDefine.placeholder : ''"
/>
</nz-input-group>
<ng-template #suffixTemplate>
<i nz-icon [nzType]="passwordVisible ? 'eye-invisible' : 'eye'" (click)="passwordVisible = !passwordVisible"></i>
</ng-template>
</nz-form-control>
<nz-form-label *ngIf="paramDefine.type === 'number'" nzSpan="7" [nzRequired]="paramDefine.required" [nzFor]="paramDefine.field"
>{{ paramDefine.name }}
</nz-form-label>
<nz-form-control *ngIf="paramDefine.type === 'number'" nzSpan="8" [nzErrorTip]="'validation.required' | i18n">
<nz-input-number
[(ngModel)]="advancedParams[i].value"
[required]="paramDefine.required"
[nzMin]="-1000"
[nzMax]="65535"
[nzStep]="1"
[nzPlaceHolder]="paramDefine.placeholder ? paramDefine.placeholder : ''"
[name]="paramDefine.field"
[id]="paramDefine.field"
></nz-input-number>
</nz-form-control>
<nz-form-label *ngIf="paramDefine.type === 'boolean'" nzSpan="7" [nzRequired]="paramDefine.required" [nzFor]="paramDefine.field"
>{{ paramDefine.name }}
</nz-form-label>
<nz-form-control *ngIf="paramDefine.type === 'boolean'" nzSpan="8" [nzErrorTip]="'validation.required' | i18n">
<nz-switch
[(ngModel)]="advancedParams[i].value"
[required]="paramDefine.required"
[name]="paramDefine.field"
[id]="paramDefine.field"
></nz-switch>
</nz-form-control>
<nz-form-label *ngIf="paramDefine.type === 'radio'" nzSpan="7" [nzRequired]="paramDefine.required" [nzFor]="paramDefine.field"
>{{ paramDefine.name }}
</nz-form-label>
<nz-form-control *ngIf="paramDefine.type === 'radio'" nzSpan="8" [nzErrorTip]="'validation.required' | i18n">
<nz-radio-group
[(ngModel)]="advancedParams[i].value"
[required]="paramDefine.required"
nzButtonStyle="solid"
[name]="paramDefine.field"
[id]="paramDefine.field"
>
<label nz-radio-button [nzValue]="optionItem.value" *ngFor="let optionItem of paramDefine.options">
{{ optionItem.label }}
</label>
</nz-radio-group>
</nz-form-control>
<nz-form-label
*ngIf="paramDefine.type === 'key-value'"
nzSpan="7"
[nzRequired]="paramDefine.required"
[nzFor]="paramDefine.field"
>{{ paramDefine.name }}
</nz-form-label>
<nz-form-control *ngIf="paramDefine.type === 'key-value'" nzSpan="8" [nzErrorTip]="'validation.required' | i18n">
<app-key-value-input
[(value)]="advancedParams[i].value"
[id]="paramDefine.field"
keyAlias="Header Name"
valueAlias="Header Value"
></app-key-value-input>
</nz-form-control>
</nz-form-item>
</nz-collapse-panel>
</nz-collapse>
<ng-template #extraColHeader>
<button style="top: -10px; margin-left: 40%" nz-button nzType="dashed" nz-tooltip nzTooltipTitle="设置高级可选参数">
<span>高级设置</span>
<i nz-icon nzType="down-circle" nzTheme="outline"></i>
</button>
</ng-template>
<nz-divider></nz-divider>
<nz-form-item>
<nz-form-label nzSpan="7" nzFor="intervals" nzTooltipTitle="监控周期性采集数据间隔时间,单位秒"> 采集间隔 </nz-form-label>
<nz-form-control nzSpan="10">
<nz-form-control nzSpan="8">
<nz-input-number [(ngModel)]="monitor.intervals" [nzMin]="10" [nzMax]="604800" [nzStep]="60" name="intervals" id="intervals">
</nz-input-number>
</nz-form-control>

View File

@@ -0,0 +1,8 @@
.dynamic-button {
font-size: 20px;
margin-left: 45%;
}
.dynamic-button:hover {
font-size: 30px;
}

View File

@@ -16,7 +16,7 @@ import { MonitorService } from '../../../service/monitor.service';
@Component({
selector: 'app-monitor-modify',
templateUrl: './monitor-edit.component.html',
styles: []
styleUrls: ['./monitor-edit.component.less']
})
export class MonitorEditComponent implements OnInit {
constructor(
@@ -30,6 +30,8 @@ export class MonitorEditComponent implements OnInit {
paramDefines!: ParamDefine[];
params!: Param[];
advancedParamDefines!: ParamDefine[];
advancedParams!: Param[];
paramValueMap = new Map<String, Param>();
monitor = new Monitor();
profileForm: FormGroup = new FormGroup({});
@@ -59,7 +61,6 @@ export class MonitorEditComponent implements OnInit {
this.paramValueMap.set(item.field, item);
});
}
this.params = message.data.params;
this.detected = message.data.detected ? message.data.detected : true;
} else {
console.warn(message.msg);
@@ -73,7 +74,10 @@ export class MonitorEditComponent implements OnInit {
if (message.code === 0) {
this.paramDefines = message.data;
this.params = [];
this.paramDefines.forEach(define => {
this.advancedParams = [];
this.paramDefines = [];
this.advancedParamDefines = [];
message.data.forEach(define => {
let param = this.paramValueMap.get(define.field);
if (param === undefined) {
param = new Param();
@@ -100,7 +104,13 @@ export class MonitorEditComponent implements OnInit {
}
}
}
if (define.hide) {
this.advancedParams.push(param);
this.advancedParamDefines.push(define);
} else {
this.params.push(param);
this.paramDefines.push(define);
}
});
} else {
console.warn(message.msg);
@@ -108,6 +118,21 @@ export class MonitorEditComponent implements OnInit {
});
}
onParamBooleanChanged(booleanValue: boolean, field: string) {
// 对SSL的端口联动处理, 不开启SSL默认80端口开启SSL默认443
if (field === 'ssl') {
this.params.forEach(param => {
if (param.field === 'port') {
if (booleanValue) {
param.value = '443';
} else {
param.value = '80';
}
}
});
}
}
onSubmit(formGroup: FormGroup) {
if (formGroup.invalid) {
Object.values(formGroup.controls).forEach(control => {
@@ -129,10 +154,15 @@ export class MonitorEditComponent implements OnInit {
param.value = (param.value as string).trim();
}
});
this.advancedParams.forEach(param => {
if (param.value != null && typeof param.value == 'string') {
param.value = (param.value as string).trim();
}
});
let addMonitor = {
detected: this.detected,
monitor: this.monitor,
params: this.params
params: this.params.concat(this.advancedParams)
};
this.isSpinning = true;
this.monitorSvc.editMonitor(addMonitor).subscribe(
@@ -173,10 +203,15 @@ export class MonitorEditComponent implements OnInit {
param.value = (param.value as string).trim();
}
});
this.advancedParams.forEach(param => {
if (param.value != null && typeof param.value == 'string') {
param.value = (param.value as string).trim();
}
});
let detectMonitor = {
detected: this.detected,
monitor: this.monitor,
params: this.params
params: this.params.concat(this.advancedParams)
};
this.isSpinning = true;
this.monitorSvc.detectMonitor(detectMonitor).subscribe(

View File

@@ -134,6 +134,7 @@
<nz-form-control *ngIf="paramDefine.type === 'boolean'" nzSpan="8" [nzErrorTip]="'validation.required' | i18n">
<nz-switch
[(ngModel)]="params[i].value"
(ngModelChange)="onParamBooleanChanged($event, paramDefine.field)"
[required]="paramDefine.required"
[name]="paramDefine.field"
[id]="paramDefine.field"
@@ -170,6 +171,146 @@
</nz-form-control>
</nz-form-item>
<nz-collapse [nzGhost]="true">
<nz-collapse-panel nzHeader="高级" [nzHeader]="extraColHeader" [nzShowArrow]="false">
<nz-form-item *ngFor="let paramDefine of advancedParamDefines; let i = index">
<nz-form-label
*ngIf="paramDefine.field !== 'host' && paramDefine.type === 'text'"
nzSpan="7"
[nzRequired]="paramDefine.required"
[nzFor]="paramDefine.field"
>{{ paramDefine.name }}
</nz-form-label>
<nz-form-control
*ngIf="paramDefine.field !== 'host' && paramDefine.type === 'text'"
nzSpan="8"
[nzErrorTip]="'validation.required' | i18n"
>
<input
nz-input
[(ngModel)]="advancedParams[i].value"
[required]="paramDefine.required"
[name]="paramDefine.field"
[type]="paramDefine.type"
[id]="paramDefine.field"
[placeholder]="paramDefine.placeholder ? paramDefine.placeholder : ''"
/>
</nz-form-control>
<nz-form-label
*ngIf="paramDefine.type === 'textarea'"
nzSpan="7"
[nzRequired]="paramDefine.required"
[nzFor]="paramDefine.field"
>{{ paramDefine.name }}
</nz-form-label>
<nz-form-control *ngIf="paramDefine.type === 'textarea'" nzSpan="8" [nzErrorTip]="'validation.required' | i18n">
<textarea
nz-input
[(ngModel)]="advancedParams[i].value"
[required]="paramDefine.required"
[name]="paramDefine.field"
[id]="paramDefine.field"
[placeholder]="paramDefine.placeholder ? paramDefine.placeholder : ''"
rows="3"
></textarea>
</nz-form-control>
<nz-form-label
*ngIf="paramDefine.type === 'password'"
nzSpan="7"
[nzRequired]="paramDefine.required"
[nzFor]="paramDefine.field"
>{{ paramDefine.name }}
</nz-form-label>
<nz-form-control *ngIf="paramDefine.type === 'password'" nzSpan="8" [nzErrorTip]="'validation.required' | i18n">
<nz-input-group [nzSuffix]="suffixTemplate" style="width: 100%">
<input
[type]="passwordVisible ? 'text' : 'password'"
nz-input
placeholder="input password"
[required]="paramDefine.required"
[(ngModel)]="advancedParams[i].value"
[id]="paramDefine.field"
[name]="paramDefine.field"
[placeholder]="paramDefine.placeholder ? paramDefine.placeholder : ''"
/>
</nz-input-group>
<ng-template #suffixTemplate>
<i nz-icon [nzType]="passwordVisible ? 'eye-invisible' : 'eye'" (click)="passwordVisible = !passwordVisible"></i>
</ng-template>
</nz-form-control>
<nz-form-label *ngIf="paramDefine.type === 'number'" nzSpan="7" [nzRequired]="paramDefine.required" [nzFor]="paramDefine.field"
>{{ paramDefine.name }}
</nz-form-label>
<nz-form-control *ngIf="paramDefine.type === 'number'" nzSpan="8" [nzErrorTip]="'validation.required' | i18n">
<nz-input-number
[(ngModel)]="advancedParams[i].value"
[required]="paramDefine.required"
[nzMin]="-1000"
[nzMax]="65535"
[nzStep]="1"
[nzPlaceHolder]="paramDefine.placeholder ? paramDefine.placeholder : ''"
[name]="paramDefine.field"
[id]="paramDefine.field"
></nz-input-number>
</nz-form-control>
<nz-form-label *ngIf="paramDefine.type === 'boolean'" nzSpan="7" [nzRequired]="paramDefine.required" [nzFor]="paramDefine.field"
>{{ paramDefine.name }}
</nz-form-label>
<nz-form-control *ngIf="paramDefine.type === 'boolean'" nzSpan="8" [nzErrorTip]="'validation.required' | i18n">
<nz-switch
[(ngModel)]="advancedParams[i].value"
[required]="paramDefine.required"
[name]="paramDefine.field"
[id]="paramDefine.field"
></nz-switch>
</nz-form-control>
<nz-form-label *ngIf="paramDefine.type === 'radio'" nzSpan="7" [nzRequired]="paramDefine.required" [nzFor]="paramDefine.field"
>{{ paramDefine.name }}
</nz-form-label>
<nz-form-control *ngIf="paramDefine.type === 'radio'" nzSpan="8" [nzErrorTip]="'validation.required' | i18n">
<nz-radio-group
[(ngModel)]="advancedParams[i].value"
[required]="paramDefine.required"
nzButtonStyle="solid"
[name]="paramDefine.field"
[id]="paramDefine.field"
>
<label nz-radio-button [nzValue]="optionItem.value" *ngFor="let optionItem of paramDefine.options">
{{ optionItem.label }}
</label>
</nz-radio-group>
</nz-form-control>
<nz-form-label
*ngIf="paramDefine.type === 'key-value'"
nzSpan="7"
[nzRequired]="paramDefine.required"
[nzFor]="paramDefine.field"
>{{ paramDefine.name }}
</nz-form-label>
<nz-form-control *ngIf="paramDefine.type === 'key-value'" nzSpan="8" [nzErrorTip]="'validation.required' | i18n">
<app-key-value-input
[(value)]="advancedParams[i].value"
[id]="paramDefine.field"
keyAlias="Header Name"
valueAlias="Header Value"
></app-key-value-input>
</nz-form-control>
</nz-form-item>
</nz-collapse-panel>
</nz-collapse>
<ng-template #extraColHeader>
<button style="top: -10px; margin-left: 40%" nz-button nzType="dashed" nz-tooltip nzTooltipTitle="设置高级可选参数">
<span>高级设置</span>
<i nz-icon nzType="down-circle" nzTheme="outline"></i>
</button>
</ng-template>
<nz-divider></nz-divider>
<nz-form-item>

View File

@@ -20,6 +20,8 @@ import { MonitorService } from '../../../service/monitor.service';
export class MonitorNewComponent implements OnInit {
paramDefines!: ParamDefine[];
params!: Param[];
advancedParamDefines!: ParamDefine[];
advancedParams!: Param[];
monitor!: Monitor;
detected: boolean = true;
passwordVisible: boolean = false;
@@ -53,9 +55,11 @@ export class MonitorNewComponent implements OnInit {
)
.subscribe(message => {
if (message.code === 0) {
this.paramDefines = message.data;
this.params = [];
this.paramDefines.forEach(define => {
this.advancedParams = [];
this.paramDefines = [];
this.advancedParamDefines = [];
message.data.forEach(define => {
let param = new Param();
param.field = define.field;
if (define.type === 'number') {
@@ -77,7 +81,13 @@ export class MonitorNewComponent implements OnInit {
param.value = define.defaultValue;
}
}
if (define.hide) {
this.advancedParams.push(param);
this.advancedParamDefines.push(define);
} else {
this.params.push(param);
this.paramDefines.push(define);
}
});
} else {
console.warn(message.msg);
@@ -89,6 +99,21 @@ export class MonitorNewComponent implements OnInit {
this.monitor.name = `${this.monitor.app.toUpperCase()}_${hostValue}`;
}
onParamBooleanChanged(booleanValue: boolean, field: string) {
// 对SSL的端口联动处理, 不开启SSL默认80端口开启SSL默认443
if (field === 'ssl') {
this.params.forEach(param => {
if (param.field === 'port') {
if (booleanValue) {
param.value = '443';
} else {
param.value = '80';
}
}
});
}
}
onSubmit(formGroup: FormGroup) {
if (formGroup.invalid) {
Object.values(formGroup.controls).forEach(control => {
@@ -110,10 +135,15 @@ export class MonitorNewComponent implements OnInit {
param.value = (param.value as string).trim();
}
});
this.advancedParams.forEach(param => {
if (param.value != null && typeof param.value == 'string') {
param.value = (param.value as string).trim();
}
});
let addMonitor = {
detected: this.detected,
monitor: this.monitor,
params: this.params
params: this.params.concat(this.advancedParams)
};
this.isSpinning = true;
this.monitorSvc.newMonitor(addMonitor).subscribe(
@@ -154,10 +184,15 @@ export class MonitorNewComponent implements OnInit {
param.value = (param.value as string).trim();
}
});
this.advancedParams.forEach(param => {
if (param.value != null && typeof param.value == 'string') {
param.value = (param.value as string).trim();
}
});
let detectMonitor = {
detected: true,
monitor: this.monitor,
params: this.params
params: this.params.concat(this.advancedParams)
};
this.isSpinning = true;
this.monitorSvc.detectMonitor(detectMonitor).subscribe(

View File

@@ -16,6 +16,7 @@ import { MonitorEditComponent } from './monitor-edit/monitor-edit.component';
import { MonitorListComponent } from './monitor-list/monitor-list.component';
import { MonitorNewComponent } from './monitor-new/monitor-new.component';
import { MonitorRoutingModule } from './monitor-routing.module';
import { NzCollapseModule } from 'ng-zorro-antd/collapse';
const COMPONENTS: Array<Type<void>> = [
MonitorNewComponent,
@@ -37,7 +38,8 @@ const COMPONENTS: Array<Type<void>> = [
NzRadioModule,
NgxEchartsModule,
NzLayoutModule,
NzSpaceModule
NzSpaceModule,
NzCollapseModule
],
declarations: COMPONENTS
})

View File

@@ -1,10 +1,10 @@
.dynamic-button {
cursor: pointer;
position: relative;
top: 4px;
top: 20%;
font-size: 15px;
transition: all 0.3s;
margin-left: 6%;
margin-left: 12%;
}
.dynamic-button:hover {

View File

@@ -1,6 +1,6 @@
{
"app": {
"name": "TANCLOUD",
"name": "HertzBeat",
"description": "易用友好的高性能监控云"
},
"user": {

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8">
<title>TanCloud</title>
<title>HertzBeat</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">