Commit d45de8fb authored by gaozhentao's avatar gaozhentao

告警表增加字段

修改首页区域模块,新增区域
parent 1a414050
......@@ -120,5 +120,7 @@
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
......@@ -24,6 +24,9 @@ public class AlarmLog {
/* */
@Column("alarm_message")
private String alarmMessage;
@Column("cluster_name")
private String clusterName;
/* 告警时间 */
private String alatime;
/* 恢复时间 */
......@@ -94,4 +97,12 @@ public class AlarmLog {
public void setState(String state) {
this.state = state;
}
public String getClusterName() {
return clusterName;
}
public void setClusterName(String clusterName) {
this.clusterName = clusterName;
}
}
......@@ -68,9 +68,10 @@ public class AlarmTask {
String uuid = UUID.randomUUID().toString();
AlarmLog alarmLog = new AlarmLog();
alarmLog.setSerialnum(uuid);
alarmLog.setClusterName(hostInfo.getCluster_name());
alarmLog.setIp(hostInfo.getHost_ip());
alarmLog.setPort(String.valueOf(hostInfo.getHost_port()));
AlarmLog alarmCheck = cassandraDao.checkAlarm(hostInfo.getHost_ip(), String.valueOf(hostInfo.getHost_port()));
AlarmLog alarmCheck = cassandraDao.checkAlarm(hostInfo.getCluster_name(),hostInfo.getHost_ip(), String.valueOf(hostInfo.getHost_port()));
String msg = "";
boolean flag = true;
if (faultCount5Min != 0) {
......@@ -97,15 +98,20 @@ public class AlarmTask {
flag = false;
}
if(flag){
if(alarmCheck != null){
/* 如果存在告警 则恢复 */
cassandraDao.updateAlarm(alarmCheck.getSerialnum());
cassandraDao.updateAlarm(alarmCheck.getSerialnum(),TimeUtil.getDate());
}
}else{
if(alarmCheck == null){
/* 如果不存在告警 新增数据 */
cassandraDao.insertAlarm(alarmLog);
}else {
/* 如果告警发生变化 回复时间变成告警时间 */
if(!alarmCheck.getAlarmLevel().equals(alarmLog.getAlarmLevel())) {
cassandraDao.updateAlarm(alarmCheck.getSerialnum(),"------");
cassandraDao.insertAlarm(alarmLog);
}
}
}
} catch (IOException | DataFormatException e) {
......@@ -123,7 +129,4 @@ public class AlarmTask {
return queryBuilder;
}
public static void main(String[] args) {
}
}
......@@ -30,8 +30,8 @@ public class HttpTask extends BasicTask{
@Autowired
public HttpTask(HttpClient httpClient) {
SimpleClientHttpRequestFactory httpRequestFactory = new SimpleClientHttpRequestFactory();
httpRequestFactory.setConnectTimeout(500);
httpRequestFactory.setReadTimeout(500);
httpRequestFactory.setConnectTimeout(5000);
httpRequestFactory.setReadTimeout(5000);
restTemplate = new RestTemplate(httpRequestFactory);
this.httpClient = httpClient;
}
......@@ -66,23 +66,20 @@ public class HttpTask extends BasicTask{
logger.info(e.getMessage());
return;
}
if (responseEntity != null) {
int statusCodeValue = responseEntity.getStatusCodeValue();
insertToKairos(httpClient, hostInfo.getHost_ip(), hostInfo.getHost_port(), dur, statusCodeValue == 200);
logger.info("httpTask to {}|{}", hostInfo.getHost_ip(), statusCodeValue == 200);
boolean flag = false;
switch (hostInfo.getCluster_name()){
case "VPN方式接入服务":
flag = chenkStatusCode(responseEntity);
break;
case "token方式接入服务":
flag = checkCode(responseEntity);
flag = checkErrorCode(responseEntity);
break;
case "日志采集服务":
flag = chenkStatusCode(responseEntity);
break;
case "核心调度服务":
flag = checkCode(responseEntity);
flag = checkErrorCode(responseEntity);
break;
case "token核验服务":
flag = checkCode(responseEntity);
......@@ -106,7 +103,7 @@ public class HttpTask extends BasicTask{
flag = checkCode(responseEntity);
break;
case "1比N比对(特征)":
flag = checkCode(responseEntity);
flag = chenkStatusCode(responseEntity);
break;
case "1比N比对(图片)":
flag = checkCode(responseEntity);
......@@ -115,6 +112,9 @@ public class HttpTask extends BasicTask{
flag = checkCode(responseEntity);
break;
}
if (responseEntity != null) {
insertToKairos(httpClient, hostInfo.getHost_ip(), hostInfo.getHost_port(), dur, flag);
logger.info("httpTask to {}|{}", hostInfo.getHost_ip(), flag);
if (flag) {
alarmHost.remove(hostInfo.getService_path());
}
......@@ -140,6 +140,15 @@ public class HttpTask extends BasicTask{
return false;
}
private boolean checkErrorCode(ResponseEntity<String> responseEntity) {
JSONObject jsonObject = JSONObject.fromObject(responseEntity.getBody());
String code = jsonObject.getString("error_code");
if(code.equals("0")){
return true;
}
return false;
}
private boolean chenkStatus(ResponseEntity<String> responseEntity) {
JSONObject jsonObject = JSONObject.fromObject(responseEntity.getBody());
String status = jsonObject.getString("status");
......
......@@ -96,9 +96,13 @@ public class Manager {
List<Server_Host_Info> host_info_List = authModeAndHostList.getClusterBeMonitoredList();
List<SingleHostStatus> accessList = new ArrayList<>();
List<SingleHostStatus> serviceList = new ArrayList<>();
List<SingleHostStatus> dataList = new ArrayList<>();
List<SingleHostStatus> passportList = new ArrayList<>();
List<Future> accessFuture = new ArrayList<>();
List<Future> serviceFuture = new ArrayList<>();
List<Future> dataFuture = new ArrayList<>();
List<Future> passportFuture = new ArrayList<>();
List<Future> futureList = new ArrayList<>();
List<SingleHostStatus> hostStatusList = new ArrayList<>();
/*线程池*/
......@@ -109,8 +113,12 @@ public class Manager {
futureList.add(result);
if (hostInfo.getLayer().equals("接入区")){
accessFuture.add(result);
}else {
}else if(hostInfo.getLayer().equals("服务区")){
serviceFuture.add(result);
}else if(hostInfo.getLayer().equals("算法区")){
dataFuture.add(result);
}else if(hostInfo.getLayer().equals("护照柜")){
passportFuture.add(result);
}
}
service.shutdown();
......@@ -128,6 +136,14 @@ public class Manager {
SingleHostStatus singleHostStatus = (SingleHostStatus) future.get();
serviceList.add(singleHostStatus);
}
for (Future future : dataFuture) {
SingleHostStatus singleHostStatus = (SingleHostStatus) future.get();
dataList.add(singleHostStatus);
}
for (Future future : passportFuture) {
SingleHostStatus singleHostStatus = (SingleHostStatus) future.get();
passportList.add(singleHostStatus);
}
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
......@@ -135,9 +151,13 @@ public class Manager {
Map accessMap = selectClusterStatusBylayer(accessList);
Map serviceMap = selectClusterStatusBylayer(serviceList);
Map dataMap = selectClusterStatusBylayer(dataList);
Map passportMap = selectClusterStatusBylayer(passportList);
Map map = new HashMap();
map.put("接入区",accessMap);
map.put("服务区",serviceMap);
map.put("算法区",dataMap);
map.put("护照柜",passportMap);
return map;
}
public Map selectClusterStatusBylayer(List<SingleHostStatus> hostStatusList){
......
......@@ -67,13 +67,13 @@ public class CassandraDaoImpl {
return template.select(cql, AlarmLog.class);
}
public AlarmLog checkAlarm(String ip,String port) {
String cql="select * from ctid_prof.alarm_log where ip = '"+ip+"' and port = '"+port+"' and state = '" +Constant.ALARM_ING+"' allow filtering";
public AlarmLog checkAlarm(String name,String ip,String port) {
String cql="select * from ctid_prof.alarm_log where cluster_name = '"+name+"' and ip = '"+ip+"' and port = '"+port+"' and state = '" +Constant.ALARM_ING+"' allow filtering";
return template.selectOne(cql, AlarmLog.class);
}
public boolean updateAlarm(String serialnum) {
String cql="update from ctid_prof.alarm_log set state = '" +Constant.ALARM_RECOVERY +"' and alarm_level = "+Constant.ALARM_OK+" where serialnum = "+serialnum ;
public boolean updateAlarm(String serialnum,String time) {
String cql="update ctid_prof.alarm_log set rectime = '"+time+"' , state = '" +Constant.ALARM_RECOVERY +"', alarm_level = '"+Constant.ALARM_OK+"' where serialnum = '"+serialnum +"'";
return template.getCqlOperations().execute(cql);
}
public void insertAlarm(AlarmLog alarmLog) {
......
......@@ -6,7 +6,8 @@ spring:
keyspace-name: ctid_prof
# contact-points: 192.168.189.130
contact-points: 172.16.15.7
contact-points: 200.102.10.11
# contact-points: 172.16.15.7
port: 9042
security:
user:
......@@ -26,8 +27,9 @@ jwt:
demo:
kairosdb:
url: "http://172.16.15.7:8080"
# url: "http://172.16.15.7:8080"
# url: "http://192.168.189.130:8080"
url: "http://200.102.10.11:8080"
maxConnTotal: 50
maxConnPerRoute: 30
......
<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>可靠性监控</title><link href=/css/app.2c6ca10a90e1bdf5cc8fc7b8ff67c7d8.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=/js/manifest.1766e3c7586055624ca0.js></script><script type=text/javascript src=/js/vendor.1a62fd43a60a688ba80d.js></script><script type=text/javascript src=/js/app.905200718f7bb353ecaf.js></script></body></html>
\ No newline at end of file
<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>可靠性监控</title><link href=/css/app.bdbe29a4117aecac5061f5ea89d4940c.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=/js/manifest.1766e3c7586055624ca0.js></script><script type=text/javascript src=/js/vendor.1a62fd43a60a688ba80d.js></script><script type=text/javascript src=/js/app.6d5a3dee66e7439ccbbf.js></script></body></html>
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
{"version":3,"sources":["webpack:///webpack/bootstrap 6abb185472d924e24f25"],"names":["parentJsonpFunction","window","chunkIds","moreModules","executeModules","moduleId","chunkId","result","i","resolves","length","installedChunks","push","Object","prototype","hasOwnProperty","call","modules","shift","__webpack_require__","s","installedModules","2","exports","module","l","m","c","d","name","getter","o","defineProperty","configurable","enumerable","get","n","__esModule","object","property","p","oe","err","console","error"],"mappings":"aACA,IAAAA,EAAAC,OAAA,aACAA,OAAA,sBAAAC,EAAAC,EAAAC,GAIA,IADA,IAAAC,EAAAC,EAAAC,EAAAC,EAAA,EAAAC,KACQD,EAAAN,EAAAQ,OAAoBF,IAC5BF,EAAAJ,EAAAM,GACAG,EAAAL,IACAG,EAAAG,KAAAD,EAAAL,GAAA,IAEAK,EAAAL,GAAA,EAEA,IAAAD,KAAAF,EACAU,OAAAC,UAAAC,eAAAC,KAAAb,EAAAE,KACAY,EAAAZ,GAAAF,EAAAE,IAIA,IADAL,KAAAE,EAAAC,EAAAC,GACAK,EAAAC,QACAD,EAAAS,OAAAT,GAEA,GAAAL,EACA,IAAAI,EAAA,EAAYA,EAAAJ,EAAAM,OAA2BF,IACvCD,EAAAY,IAAAC,EAAAhB,EAAAI,IAGA,OAAAD,GAIA,IAAAc,KAGAV,GACAW,EAAA,GAIA,SAAAH,EAAAd,GAGA,GAAAgB,EAAAhB,GACA,OAAAgB,EAAAhB,GAAAkB,QAGA,IAAAC,EAAAH,EAAAhB,IACAG,EAAAH,EACAoB,GAAA,EACAF,YAUA,OANAN,EAAAZ,GAAAW,KAAAQ,EAAAD,QAAAC,IAAAD,QAAAJ,GAGAK,EAAAC,GAAA,EAGAD,EAAAD,QAKAJ,EAAAO,EAAAT,EAGAE,EAAAQ,EAAAN,EAGAF,EAAAS,EAAA,SAAAL,EAAAM,EAAAC,GACAX,EAAAY,EAAAR,EAAAM,IACAhB,OAAAmB,eAAAT,EAAAM,GACAI,cAAA,EACAC,YAAA,EACAC,IAAAL,KAMAX,EAAAiB,EAAA,SAAAZ,GACA,IAAAM,EAAAN,KAAAa,WACA,WAA2B,OAAAb,EAAA,SAC3B,WAAiC,OAAAA,GAEjC,OADAL,EAAAS,EAAAE,EAAA,IAAAA,GACAA,GAIAX,EAAAY,EAAA,SAAAO,EAAAC,GAAsD,OAAA1B,OAAAC,UAAAC,eAAAC,KAAAsB,EAAAC,IAGtDpB,EAAAqB,EAAA,IAGArB,EAAAsB,GAAA,SAAAC,GAA8D,MAApBC,QAAAC,MAAAF,GAAoBA","file":"js/manifest.1766e3c7586055624ca0.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [], result;\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(installedChunks[chunkId]) {\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\t}\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);\n \t\twhile(resolves.length) {\n \t\t\tresolves.shift()();\n \t\t}\n \t\tif(executeModules) {\n \t\t\tfor(i=0; i < executeModules.length; i++) {\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = executeModules[i]);\n \t\t\t}\n \t\t}\n \t\treturn result;\n \t};\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// objects to store loaded and loading chunks\n \tvar installedChunks = {\n \t\t2: 0\n \t};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/\";\n\n \t// on error function for async loading\n \t__webpack_require__.oe = function(err) { console.error(err); throw err; };\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 6abb185472d924e24f25"],"sourceRoot":""}
\ No newline at end of file
{"version":3,"sources":["webpack:///webpack/bootstrap 6022d6934a7799fc90bb"],"names":["parentJsonpFunction","window","chunkIds","moreModules","executeModules","moduleId","chunkId","result","i","resolves","length","installedChunks","push","Object","prototype","hasOwnProperty","call","modules","shift","__webpack_require__","s","installedModules","2","exports","module","l","m","c","d","name","getter","o","defineProperty","configurable","enumerable","get","n","__esModule","object","property","p","oe","err","console","error"],"mappings":"aACA,IAAAA,EAAAC,OAAA,aACAA,OAAA,sBAAAC,EAAAC,EAAAC,GAIA,IADA,IAAAC,EAAAC,EAAAC,EAAAC,EAAA,EAAAC,KACQD,EAAAN,EAAAQ,OAAoBF,IAC5BF,EAAAJ,EAAAM,GACAG,EAAAL,IACAG,EAAAG,KAAAD,EAAAL,GAAA,IAEAK,EAAAL,GAAA,EAEA,IAAAD,KAAAF,EACAU,OAAAC,UAAAC,eAAAC,KAAAb,EAAAE,KACAY,EAAAZ,GAAAF,EAAAE,IAIA,IADAL,KAAAE,EAAAC,EAAAC,GACAK,EAAAC,QACAD,EAAAS,OAAAT,GAEA,GAAAL,EACA,IAAAI,EAAA,EAAYA,EAAAJ,EAAAM,OAA2BF,IACvCD,EAAAY,IAAAC,EAAAhB,EAAAI,IAGA,OAAAD,GAIA,IAAAc,KAGAV,GACAW,EAAA,GAIA,SAAAH,EAAAd,GAGA,GAAAgB,EAAAhB,GACA,OAAAgB,EAAAhB,GAAAkB,QAGA,IAAAC,EAAAH,EAAAhB,IACAG,EAAAH,EACAoB,GAAA,EACAF,YAUA,OANAN,EAAAZ,GAAAW,KAAAQ,EAAAD,QAAAC,IAAAD,QAAAJ,GAGAK,EAAAC,GAAA,EAGAD,EAAAD,QAKAJ,EAAAO,EAAAT,EAGAE,EAAAQ,EAAAN,EAGAF,EAAAS,EAAA,SAAAL,EAAAM,EAAAC,GACAX,EAAAY,EAAAR,EAAAM,IACAhB,OAAAmB,eAAAT,EAAAM,GACAI,cAAA,EACAC,YAAA,EACAC,IAAAL,KAMAX,EAAAiB,EAAA,SAAAZ,GACA,IAAAM,EAAAN,KAAAa,WACA,WAA2B,OAAAb,EAAA,SAC3B,WAAiC,OAAAA,GAEjC,OADAL,EAAAS,EAAAE,EAAA,IAAAA,GACAA,GAIAX,EAAAY,EAAA,SAAAO,EAAAC,GAAsD,OAAA1B,OAAAC,UAAAC,eAAAC,KAAAsB,EAAAC,IAGtDpB,EAAAqB,EAAA,IAGArB,EAAAsB,GAAA,SAAAC,GAA8D,MAApBC,QAAAC,MAAAF,GAAoBA","file":"js/manifest.1766e3c7586055624ca0.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [], result;\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(installedChunks[chunkId]) {\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\t}\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);\n \t\twhile(resolves.length) {\n \t\t\tresolves.shift()();\n \t\t}\n \t\tif(executeModules) {\n \t\t\tfor(i=0; i < executeModules.length; i++) {\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = executeModules[i]);\n \t\t\t}\n \t\t}\n \t\treturn result;\n \t};\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// objects to store loaded and loading chunks\n \tvar installedChunks = {\n \t\t2: 0\n \t};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/\";\n\n \t// on error function for async loading\n \t__webpack_require__.oe = function(err) { console.error(err); throw err; };\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 6022d6934a7799fc90bb"],"sourceRoot":""}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment