Browse Source
# Conflicts: # hzims-service/operational/src/main/java/com/hnac/hzims/operational/duty/service/impl/ImsDutyGroupServiceImpl.javazhongwei
ty
8 months ago
107 changed files with 828 additions and 927 deletions
@ -1,59 +0,0 @@ |
|||||||
package com.hnac.hzims.alarm.ws.level; |
|
||||||
|
|
||||||
import com.hnac.hzims.alarm.source.service.LevelAlarmService; |
|
||||||
import lombok.extern.slf4j.Slf4j; |
|
||||||
import org.springblade.core.tool.utils.ObjectUtil; |
|
||||||
import org.springblade.core.tool.utils.StringUtil; |
|
||||||
import org.springframework.beans.factory.annotation.Autowired; |
|
||||||
import org.springframework.beans.factory.annotation.Value; |
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling; |
|
||||||
import org.springframework.scheduling.annotation.Scheduled; |
|
||||||
import org.springframework.stereotype.Component; |
|
||||||
|
|
||||||
import java.net.URI; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author ysj |
|
||||||
*/ |
|
||||||
@Slf4j |
|
||||||
@Component |
|
||||||
@EnableScheduling |
|
||||||
public class LevelAlarmRegular { |
|
||||||
|
|
||||||
@Autowired |
|
||||||
private LevelAlarmService levelService; |
|
||||||
|
|
||||||
private LevelAlarmWebSocket socket; |
|
||||||
|
|
||||||
@Value("${hzims.level.wss-url}") |
|
||||||
private String level_wss_url; |
|
||||||
|
|
||||||
// 两分钟进行一次检查链接是否存活,不存活了重新建立链接
|
|
||||||
@Scheduled(cron = "0 0/2 * * * ?") |
|
||||||
private void regular(){ |
|
||||||
// 检查链接存活状态
|
|
||||||
if(ObjectUtil.isEmpty(socket) || !socket.isOpen()){ |
|
||||||
log.error("level websocket survival check : {}","死亡"); |
|
||||||
this.createSocket(); |
|
||||||
if(ObjectUtil.isNotEmpty(socket) && socket.isOpen()){ |
|
||||||
String message = levelService.message(); |
|
||||||
if(!StringUtil.isEmpty(message)){ |
|
||||||
socket.send(message); |
|
||||||
} |
|
||||||
} |
|
||||||
log.error("level websocket survival check : {}","重新建立链接"); |
|
||||||
}else{ |
|
||||||
log.error("level websocket survival check : {}","存活"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// 创建websocket链接
|
|
||||||
private void createSocket() { |
|
||||||
try{ |
|
||||||
socket = new LevelAlarmWebSocket(new URI(level_wss_url)); |
|
||||||
socket.connectBlocking(); |
|
||||||
}catch (Exception e){ |
|
||||||
log.error("level create error : {}",e.getMessage()); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,139 +0,0 @@ |
|||||||
package com.hnac.hzims.alarm.ws.level; |
|
||||||
|
|
||||||
import com.hnac.hzims.alarm.config.entity.AlarmEntity; |
|
||||||
import com.hnac.hzims.alarm.source.service.LevelAlarmService; |
|
||||||
import com.hnac.hzims.alarm.monitor.service.AlarmSaveService; |
|
||||||
import lombok.extern.slf4j.Slf4j; |
|
||||||
import org.java_websocket.client.WebSocketClient; |
|
||||||
import org.java_websocket.handshake.ServerHandshake; |
|
||||||
import org.springblade.core.log.exception.ServiceException; |
|
||||||
import org.springblade.core.tool.utils.SpringUtil; |
|
||||||
import org.springblade.core.tool.utils.StringUtil; |
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext; |
|
||||||
import javax.net.ssl.TrustManager; |
|
||||||
import javax.net.ssl.X509TrustManager; |
|
||||||
import java.net.Socket; |
|
||||||
import java.net.URI; |
|
||||||
import java.security.SecureRandom; |
|
||||||
import java.security.cert.X509Certificate; |
|
||||||
import java.util.List; |
|
||||||
import java.util.Optional; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author ysj |
|
||||||
*/ |
|
||||||
@Slf4j |
|
||||||
public class LevelAlarmWebSocket extends WebSocketClient { |
|
||||||
|
|
||||||
|
|
||||||
private final LevelAlarmService levelService; |
|
||||||
|
|
||||||
private AlarmSaveService alarmSaveService; |
|
||||||
|
|
||||||
/** |
|
||||||
* 构造等级告警websocket |
|
||||||
* @param uri |
|
||||||
*/ |
|
||||||
public LevelAlarmWebSocket(URI uri) { |
|
||||||
super(uri); |
|
||||||
levelService = SpringUtil.getBean(LevelAlarmService.class); |
|
||||||
connection(this); |
|
||||||
} |
|
||||||
|
|
||||||
// 链接到服务器回调接口
|
|
||||||
@Override |
|
||||||
public void onOpen(ServerHandshake handshakedata) { |
|
||||||
log.error("systemAlarm websocket open"); |
|
||||||
} |
|
||||||
|
|
||||||
// 接收到服务器消息回调接口
|
|
||||||
@Override |
|
||||||
public void onMessage(String message) { |
|
||||||
if(StringUtil.isEmpty(message)){ |
|
||||||
log.error("level alarm on message is null"); |
|
||||||
}else{ |
|
||||||
|
|
||||||
// 告警数据转化
|
|
||||||
List<AlarmEntity> alarmEntities = levelService.receiveMessage(message); |
|
||||||
// 等级告警数据处理
|
|
||||||
try { |
|
||||||
//websocket 消息推送保存
|
|
||||||
alarmSaveService.save(alarmEntities); |
|
||||||
}catch (Exception e){ |
|
||||||
throw new ServiceException("集中监控告警数据处理报错(levelAlarm):"+e); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// 与服务器链接中断回调接口
|
|
||||||
@Override |
|
||||||
public void onClose(int code, String reason, boolean remote) { |
|
||||||
log.error("level alarm websocket close"); |
|
||||||
} |
|
||||||
|
|
||||||
// 与服务器通讯异常触发
|
|
||||||
@Override |
|
||||||
public void onError(Exception e) { |
|
||||||
log.error("level alarm websocket error : {}",e.getMessage()); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 建立链接 |
|
||||||
* @param webSocket |
|
||||||
*/ |
|
||||||
private void connection(LevelAlarmWebSocket webSocket) { |
|
||||||
SSLContext context = init(); |
|
||||||
if(Optional.ofNullable(context).isPresent()){ |
|
||||||
Socket socket = create(context); |
|
||||||
if(Optional.ofNullable(socket).isPresent()){ |
|
||||||
webSocket.setSocket(socket); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 创建Socket |
|
||||||
* @param context |
|
||||||
* @return |
|
||||||
*/ |
|
||||||
private Socket create(SSLContext context) { |
|
||||||
Socket socket = null; |
|
||||||
try{ |
|
||||||
socket = context.getSocketFactory().createSocket(); |
|
||||||
}catch (Exception e){ |
|
||||||
log.error("level alarm socket create error : {}",e.getMessage()); |
|
||||||
} |
|
||||||
return socket; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 协议初始化 |
|
||||||
* @return SSLContext |
|
||||||
*/ |
|
||||||
private SSLContext init() { |
|
||||||
SSLContext SSL = null; |
|
||||||
try{ |
|
||||||
SSL = SSLContext.getInstance("TLS"); |
|
||||||
SSL.init(null, new TrustManager[]{new X509TrustManager() { |
|
||||||
@Override |
|
||||||
public void checkClientTrusted(X509Certificate[] chain, |
|
||||||
String authType) { |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void checkServerTrusted(X509Certificate[] chain, String authType) { |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public X509Certificate[] getAcceptedIssuers() { |
|
||||||
return new X509Certificate[0]; |
|
||||||
} |
|
||||||
}}, new SecureRandom()); |
|
||||||
}catch (Exception e){ |
|
||||||
log.error("level alarm SSL init error : {}",e.getMessage()); |
|
||||||
} |
|
||||||
return SSL; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,13 @@ |
|||||||
|
package com.hnac.hzims.scheduled.service.alarm; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author ysj |
||||||
|
*/ |
||||||
|
public interface AlarmService { |
||||||
|
|
||||||
|
void startStopAlarm(String param); |
||||||
|
|
||||||
|
void clearHistoryAlarm(String param); |
||||||
|
|
||||||
|
void interruption(String param); |
||||||
|
} |
@ -0,0 +1,244 @@ |
|||||||
|
package com.hnac.hzims.scheduled.service.alarm.impl; |
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
||||||
|
import com.hnac.hzims.alarm.config.constants.AlarmConstants; |
||||||
|
import com.hnac.hzims.alarm.config.entity.AlarmEntity; |
||||||
|
import com.hnac.hzims.business.interruption.constants.InterruptionConstants; |
||||||
|
import com.hnac.hzims.business.interruption.entity.InterruptionEntity; |
||||||
|
import com.hnac.hzims.operational.main.constant.HomePageConstant; |
||||||
|
import com.hnac.hzims.operational.main.vo.HydropowerUnitRealVo; |
||||||
|
import com.hnac.hzims.operational.station.entity.StationEntity; |
||||||
|
import com.hnac.hzims.scheduled.service.alarm.AlarmQueryService; |
||||||
|
import com.hnac.hzims.scheduled.service.alarm.AlarmService; |
||||||
|
import com.hnac.hzims.scheduled.service.operation.station.StationService; |
||||||
|
import com.hnac.hzinfo.datasearch.soe.ISoeClient; |
||||||
|
import com.hnac.hzinfo.datasearch.soe.domian.SoeData; |
||||||
|
import com.hnac.hzinfo.datasearch.soe.domian.SoeQueryConditionByStation; |
||||||
|
import com.hnac.hzinfo.sdk.core.response.HzPage; |
||||||
|
import com.hnac.hzinfo.sdk.core.response.Result; |
||||||
|
import lombok.AllArgsConstructor; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springblade.core.tool.utils.CollectionUtil; |
||||||
|
import org.springblade.core.tool.utils.DateUtil; |
||||||
|
import org.springblade.core.tool.utils.Func; |
||||||
|
import org.springblade.core.tool.utils.ObjectUtil; |
||||||
|
import org.springframework.data.redis.core.RedisTemplate; |
||||||
|
import org.springframework.stereotype.Service; |
||||||
|
|
||||||
|
import java.time.LocalDateTime; |
||||||
|
import java.util.*; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author ysj |
||||||
|
*/ |
||||||
|
@AllArgsConstructor |
||||||
|
@Service |
||||||
|
@Slf4j |
||||||
|
public class AlarmServiceImpl implements AlarmService { |
||||||
|
|
||||||
|
private final StationService stationService; |
||||||
|
|
||||||
|
private final AlarmQueryService alarmQueryService; |
||||||
|
|
||||||
|
private final InterruptionAlarmServiceImpl interruptionAlarmService; |
||||||
|
|
||||||
|
private final ISoeClient soeClient; |
||||||
|
|
||||||
|
private final RedisTemplate redisTemplate; |
||||||
|
|
||||||
|
private final static String start_stop_cache_final = "hzims:operation:start:stop:key"; |
||||||
|
private final static String load_hydropower_unit_real_key = "hzims:operation:loadhydropowerunit:real:key"; |
||||||
|
|
||||||
|
/** |
||||||
|
* 开停机告警 |
||||||
|
* @param param |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void startStopAlarm(String param) { |
||||||
|
// 获取站点开关机状态
|
||||||
|
Map<String,Integer> startStopMap = (HashMap<String, Integer>) redisTemplate.opsForValue().get(start_stop_cache_final); |
||||||
|
|
||||||
|
// 查询接入水电站点
|
||||||
|
List<StationEntity> stations = stationService.list(new LambdaQueryWrapper<StationEntity>() |
||||||
|
.eq(StationEntity::getDataOrigin,0) |
||||||
|
.eq(StationEntity::getType, HomePageConstant.HYDROPOWER) |
||||||
|
); |
||||||
|
if(CollectionUtil.isEmpty(stations)){ |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// 获取站点设备实时数据
|
||||||
|
List<HydropowerUnitRealVo> reals = (List<HydropowerUnitRealVo>) redisTemplate.opsForValue().get(load_hydropower_unit_real_key); |
||||||
|
if(CollectionUtil.isEmpty(reals)){ |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
Map<String,Integer> refresh = new HashMap<>(); |
||||||
|
// 实时设备遍历
|
||||||
|
reals.forEach(real->{ |
||||||
|
// 过滤站点
|
||||||
|
List<StationEntity> list = stations.stream().filter(item -> item.getRefDept().equals(real.getDeptId())).collect(Collectors.toList()); |
||||||
|
if(CollectionUtil.isEmpty(list)){ |
||||||
|
return; |
||||||
|
} |
||||||
|
// 确定站点
|
||||||
|
StationEntity station = list.get(0); |
||||||
|
|
||||||
|
// 数据初始化记录
|
||||||
|
if(CollectionUtil.isEmpty(startStopMap)){ |
||||||
|
if(Math.abs(real.getActivePower()) > 0){ |
||||||
|
refresh.put(real.getDeviceCode(),1); |
||||||
|
}else{ |
||||||
|
refresh.put(real.getDeviceCode(),0); |
||||||
|
} |
||||||
|
return; |
||||||
|
} |
||||||
|
// 比对开机状态
|
||||||
|
if(Math.abs(real.getActivePower()) > 0){ |
||||||
|
if(startStopMap.get(real.getDeviceCode()) == 0){ |
||||||
|
refresh.put(real.getDeviceCode(),1); |
||||||
|
// 记录开机告警
|
||||||
|
this.saveStartStopAlarm(station,real,1); |
||||||
|
}else{ |
||||||
|
refresh.put(real.getDeviceCode(),0); |
||||||
|
} |
||||||
|
}else{ |
||||||
|
if(startStopMap.get(real.getDeviceCode()) == 1){ |
||||||
|
refresh.put(real.getDeviceCode(),0); |
||||||
|
// 记录关机告警
|
||||||
|
this.saveStartStopAlarm(station,real,0); |
||||||
|
}else{ |
||||||
|
refresh.put(real.getDeviceCode(),1); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
redisTemplate.opsForValue().set(start_stop_cache_final,refresh); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 清理历史告警数据 |
||||||
|
* @param param |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void clearHistoryAlarm(String param) { |
||||||
|
this.alarmQueryService.clear(param); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 保存站点设备开关机告警 |
||||||
|
* @param station |
||||||
|
* @param real |
||||||
|
* @param state |
||||||
|
*/ |
||||||
|
private void saveStartStopAlarm(StationEntity station, HydropowerUnitRealVo real, int state) { |
||||||
|
AlarmEntity alarm = new AlarmEntity(); |
||||||
|
alarm.setStationId(station.getCode()); |
||||||
|
alarm.setStationName(station.getName()); |
||||||
|
alarm.setDeviceCode(real.getDeviceCode()); |
||||||
|
alarm.setDeviceName(real.getDeviceName()); |
||||||
|
alarm.setAlarmId(station.getCode() + "_" + Func.randomUUID()); |
||||||
|
alarm.setAlarmContext(station.getName() + "_" + real.getDeviceName() + "_关机"); |
||||||
|
alarm.setAlarmSource(AlarmConstants.START_STOP_WARNING); |
||||||
|
alarm.setAlarmType(AlarmConstants.STOP); |
||||||
|
if(state == 1){ |
||||||
|
alarm.setAlarmType(AlarmConstants.START); |
||||||
|
alarm.setAlarmContext(station.getName() + "_" + real.getDeviceName() + "_开机"); |
||||||
|
} |
||||||
|
alarm.setStatus(0); |
||||||
|
alarm.setAlarmTime(new Date()); |
||||||
|
alarm.setIsRightTabulation(0); |
||||||
|
alarm.setIsShowAlert(1); |
||||||
|
alarm.setIsSmallBell(1); |
||||||
|
alarm.setIsMask(1); |
||||||
|
alarm.setIsBroadcast(0); |
||||||
|
alarm.setIsPlatformMessage(1); |
||||||
|
alarm.setIsShortMessage(1); |
||||||
|
alarm.setIsWxMessage(1); |
||||||
|
alarm.setTenantId(station.getTenantId()); |
||||||
|
alarm.setCreateDept(station.getRefDept()); |
||||||
|
alarm.setCreateUser(station.getCreateUser()); |
||||||
|
alarm.setUpdateUser(station.getUpdateUser()); |
||||||
|
alarm.setUpdateTime(station.getUpdateTime()); |
||||||
|
this.alarmQueryService.save(alarm); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 数据中断告警 |
||||||
|
* @param param |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void interruption(String param) { |
||||||
|
// 查询接入站点
|
||||||
|
List<StationEntity> stations = stationService.list(Wrappers.<StationEntity>lambdaQuery() |
||||||
|
.eq(StationEntity::getDataOrigin,"0") |
||||||
|
); |
||||||
|
if(CollectionUtil.isEmpty(stations)){ |
||||||
|
return; |
||||||
|
} |
||||||
|
// 查询告警数据: 间隔10分钟
|
||||||
|
SoeQueryConditionByStation query = new SoeQueryConditionByStation(); |
||||||
|
query.setTypes(InterruptionConstants.INTERRUPTION_TYPE); |
||||||
|
query.setStationIds(stations.stream().map(StationEntity::getCode).collect(Collectors.toList())); |
||||||
|
Calendar calendar = Calendar.getInstance(); |
||||||
|
query.setEndTime(LocalDateTime.parse(DateUtil.format(calendar.getTime(), DateUtil.PATTERN_DATETIME),DateUtil.DATETIME_FORMATTER)); |
||||||
|
calendar.add(Calendar.MINUTE,-360); |
||||||
|
query.setBeginTime(LocalDateTime.parse(DateUtil.format(calendar.getTime() , DateUtil.PATTERN_DATETIME),DateUtil.DATETIME_FORMATTER)); |
||||||
|
query.setNeedPage(false); |
||||||
|
query.setPage(1); |
||||||
|
query.setLimit(1000); |
||||||
|
Result<HzPage<SoeData>> result = soeClient.getByStationsAndTime(query); |
||||||
|
// 未查询到告警信息
|
||||||
|
if(!result.isSuccess() || ObjectUtil.isEmpty(result.getData()) || CollectionUtil.isEmpty(result.getData().getRecords())) { |
||||||
|
return; |
||||||
|
} |
||||||
|
// 中断告警数据
|
||||||
|
List<SoeData> soes = result.getData().getRecords(); |
||||||
|
// 查询当天数据中断告警记录
|
||||||
|
List<InterruptionEntity> saves = this.saveInterruptions(stations.stream().map(StationEntity::getCode).collect(Collectors.toList())); |
||||||
|
// 批量保存中断告警数据
|
||||||
|
this.interruptionAlarmService.saveBatch(soes.stream().filter(o-> CollectionUtil.isEmpty(saves) || !saves.stream().map(InterruptionEntity::getAlarmId).collect(Collectors.toList()).contains(o.getId())).map(soe->{ |
||||||
|
InterruptionEntity entity = new InterruptionEntity(); |
||||||
|
List<StationEntity> soeStations = stations.stream().filter(o->o.getCode().equals(soe.getStation())).collect(Collectors.toList()); |
||||||
|
if(!CollectionUtil.isEmpty(soeStations)){ |
||||||
|
entity.setStationId(soeStations.get(0).getCode()); |
||||||
|
entity.setStationName(soeStations.get(0).getName()); |
||||||
|
entity.setCreateDept(soeStations.get(0).getRefDept()); |
||||||
|
entity.setTenantId(soeStations.get(0).getTenantId()); |
||||||
|
} |
||||||
|
entity.setAlarmId(soe.getId()); |
||||||
|
entity.setRealId(soe.getRealId()); |
||||||
|
entity.setSoeExplain(soe.getSoeExplain()); |
||||||
|
entity.setType(soe.getSoeType()); |
||||||
|
entity.setAlarmTime(soe.getTs()); |
||||||
|
entity.setStatus(0); |
||||||
|
// 通讯中断恢复
|
||||||
|
if(InterruptionConstants.ABNORMAL_STATUS.equals(soe.getSoeAlarmType())){ |
||||||
|
entity.setStatus(1); |
||||||
|
} |
||||||
|
return entity; |
||||||
|
}).collect(Collectors.toList())); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 查询当天数据中断告警记录 |
||||||
|
* @param stations |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private List<InterruptionEntity> saveInterruptions(List<String> stations) { |
||||||
|
Calendar calendar = Calendar.getInstance(); |
||||||
|
String start = DateUtil.format(calendar.getTime(),DateUtil.PATTERN_DATETIME); |
||||||
|
calendar.add(Calendar.HOUR_OF_DAY, -calendar.get(Calendar.HOUR_OF_DAY)); |
||||||
|
calendar.add(Calendar.MINUTE, -calendar.get(Calendar.MINUTE)); |
||||||
|
calendar.add(Calendar.SECOND, -calendar.get(Calendar.SECOND)); |
||||||
|
String end = DateUtil.format(calendar.getTime(),DateUtil.PATTERN_DATETIME); |
||||||
|
return this.interruptionAlarmService.list(Wrappers.<InterruptionEntity>lambdaQuery() |
||||||
|
.in(InterruptionEntity::getStationId,stations) |
||||||
|
.ge(InterruptionEntity::getCreateTime,start) |
||||||
|
.le(InterruptionEntity::getCreateTime,end) |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -1,5 +1,10 @@ |
|||||||
<?xml version="1.0" encoding="UTF-8"?> |
<?xml version="1.0" encoding="UTF-8"?> |
||||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
||||||
<mapper namespace="com.hnac.hzims.alarm.show.mapper.AlarmMapper"> |
<mapper namespace="com.hnac.hzims.scheduled.mapper.alarm.AlarmQueryMapper"> |
||||||
|
|
||||||
|
|
||||||
|
<delete id="clear"> |
||||||
|
delete from hzims_alarm |
||||||
|
where alarm_time < #{alarmTime} |
||||||
|
</delete> |
||||||
</mapper> |
</mapper> |
||||||
|
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue