WidgetsBundles fetch feature
This commit is contained in:
parent
d34c54b31c
commit
5af7eb8221
@ -25,6 +25,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.thingsboard.server.common.data.audit.ActionType;
|
||||||
import org.thingsboard.server.common.data.exception.ThingsboardException;
|
import org.thingsboard.server.common.data.exception.ThingsboardException;
|
||||||
import org.thingsboard.server.common.data.id.TenantId;
|
import org.thingsboard.server.common.data.id.TenantId;
|
||||||
import org.thingsboard.server.common.data.id.WidgetsBundleId;
|
import org.thingsboard.server.common.data.id.WidgetsBundleId;
|
||||||
@ -68,7 +69,11 @@ public class WidgetsBundleController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
checkEntity(widgetsBundle.getId(), widgetsBundle, Resource.WIDGETS_BUNDLE);
|
checkEntity(widgetsBundle.getId(), widgetsBundle, Resource.WIDGETS_BUNDLE);
|
||||||
return checkNotNull(widgetsBundleService.saveWidgetsBundle(widgetsBundle));
|
WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
|
||||||
|
|
||||||
|
sendNotificationMsgToEdgeService(savedWidgetsBundle.getTenantId(), savedWidgetsBundle.getId(), ActionType.UPDATED);
|
||||||
|
|
||||||
|
return checkNotNull(savedWidgetsBundle);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw handleException(e);
|
throw handleException(e);
|
||||||
}
|
}
|
||||||
@ -83,6 +88,9 @@ public class WidgetsBundleController extends BaseController {
|
|||||||
WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId));
|
WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId));
|
||||||
checkWidgetsBundleId(widgetsBundleId, Operation.DELETE);
|
checkWidgetsBundleId(widgetsBundleId, Operation.DELETE);
|
||||||
widgetsBundleService.deleteWidgetsBundle(getTenantId(), widgetsBundleId);
|
widgetsBundleService.deleteWidgetsBundle(getTenantId(), widgetsBundleId);
|
||||||
|
|
||||||
|
sendNotificationMsgToEdgeService(getTenantId(), widgetsBundleId, ActionType.DELETED);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw handleException(e);
|
throw handleException(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -157,6 +157,7 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
|
|||||||
case ENTITY_VIEW:
|
case ENTITY_VIEW:
|
||||||
case DASHBOARD:
|
case DASHBOARD:
|
||||||
case RULE_CHAIN:
|
case RULE_CHAIN:
|
||||||
|
case WIDGETS_BUNDLE:
|
||||||
processEntity(tenantId, edgeNotificationMsg);
|
processEntity(tenantId, edgeNotificationMsg);
|
||||||
break;
|
break;
|
||||||
case ALARM:
|
case ALARM:
|
||||||
@ -185,20 +186,29 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
|
|||||||
// case ADDED:
|
// case ADDED:
|
||||||
case UPDATED:
|
case UPDATED:
|
||||||
case CREDENTIALS_UPDATED:
|
case CREDENTIALS_UPDATED:
|
||||||
ListenableFuture<List<EdgeId>> edgeIdsFuture = findRelatedEdgeIdsByEntityId(tenantId, entityId);
|
if (edgeEventType.equals(EdgeEventType.WIDGETS_BUNDLE)) {
|
||||||
Futures.transform(edgeIdsFuture, edgeIds -> {
|
TextPageData<Edge> edgesByTenantId = edgeService.findEdgesByTenantId(tenantId, new TextPageLink(Integer.MAX_VALUE));
|
||||||
if (edgeIds != null && !edgeIds.isEmpty()) {
|
if (edgesByTenantId != null && edgesByTenantId.getData() != null && !edgesByTenantId.getData().isEmpty()) {
|
||||||
for (EdgeId edgeId : edgeIds) {
|
for (Edge edge : edgesByTenantId.getData()) {
|
||||||
try {
|
saveEdgeEvent(tenantId, edge.getId(), edgeEventType, edgeEventActionType, entityId, null);
|
||||||
saveEdgeEvent(tenantId, edgeId, edgeEventType, edgeEventActionType, entityId, null);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("[{}] Failed to push event to edge, edgeId [{}], edgeEventType [{}], edgeEventActionType [{}], entityId [{}]",
|
|
||||||
tenantId, edgeId, edgeEventType, edgeEventActionType, entityId, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
} else {
|
||||||
}, dbCallbackExecutorService);
|
ListenableFuture<List<EdgeId>> edgeIdsFuture = findRelatedEdgeIdsByEntityId(tenantId, entityId);
|
||||||
|
Futures.transform(edgeIdsFuture, edgeIds -> {
|
||||||
|
if (edgeIds != null && !edgeIds.isEmpty()) {
|
||||||
|
for (EdgeId edgeId : edgeIds) {
|
||||||
|
try {
|
||||||
|
saveEdgeEvent(tenantId, edgeId, edgeEventType, edgeEventActionType, entityId, null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[{}] Failed to push event to edge, edgeId [{}], edgeEventType [{}], edgeEventActionType [{}], entityId [{}]",
|
||||||
|
tenantId, edgeId, edgeEventType, edgeEventActionType, entityId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, dbCallbackExecutorService);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case DELETED:
|
case DELETED:
|
||||||
TextPageData<Edge> edgesByTenantId = edgeService.findEdgesByTenantId(tenantId, new TextPageLink(Integer.MAX_VALUE));
|
TextPageData<Edge> edgesByTenantId = edgeService.findEdgesByTenantId(tenantId, new TextPageLink(Integer.MAX_VALUE));
|
||||||
|
|||||||
@ -33,19 +33,12 @@ import org.thingsboard.server.dao.entityview.EntityViewService;
|
|||||||
import org.thingsboard.server.dao.relation.RelationService;
|
import org.thingsboard.server.dao.relation.RelationService;
|
||||||
import org.thingsboard.server.dao.rule.RuleChainService;
|
import org.thingsboard.server.dao.rule.RuleChainService;
|
||||||
import org.thingsboard.server.dao.user.UserService;
|
import org.thingsboard.server.dao.user.UserService;
|
||||||
|
import org.thingsboard.server.dao.widget.WidgetsBundleService;
|
||||||
import org.thingsboard.server.queue.discovery.PartitionService;
|
import org.thingsboard.server.queue.discovery.PartitionService;
|
||||||
import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
|
import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
|
||||||
import org.thingsboard.server.queue.util.TbCoreComponent;
|
import org.thingsboard.server.queue.util.TbCoreComponent;
|
||||||
import org.thingsboard.server.service.edge.rpc.EdgeEventStorageSettings;
|
import org.thingsboard.server.service.edge.rpc.EdgeEventStorageSettings;
|
||||||
import org.thingsboard.server.service.edge.rpc.constructor.AlarmUpdateMsgConstructor;
|
import org.thingsboard.server.service.edge.rpc.constructor.*;
|
||||||
import org.thingsboard.server.service.edge.rpc.constructor.AssetUpdateMsgConstructor;
|
|
||||||
import org.thingsboard.server.service.edge.rpc.constructor.DashboardUpdateMsgConstructor;
|
|
||||||
import org.thingsboard.server.service.edge.rpc.constructor.DeviceUpdateMsgConstructor;
|
|
||||||
import org.thingsboard.server.service.edge.rpc.constructor.EntityDataMsgConstructor;
|
|
||||||
import org.thingsboard.server.service.edge.rpc.constructor.EntityViewUpdateMsgConstructor;
|
|
||||||
import org.thingsboard.server.service.edge.rpc.constructor.RelationUpdateMsgConstructor;
|
|
||||||
import org.thingsboard.server.service.edge.rpc.constructor.RuleChainUpdateMsgConstructor;
|
|
||||||
import org.thingsboard.server.service.edge.rpc.constructor.UserUpdateMsgConstructor;
|
|
||||||
import org.thingsboard.server.service.edge.rpc.init.SyncEdgeService;
|
import org.thingsboard.server.service.edge.rpc.init.SyncEdgeService;
|
||||||
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
|
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
|
||||||
import org.thingsboard.server.service.queue.TbClusterService;
|
import org.thingsboard.server.service.queue.TbClusterService;
|
||||||
@ -119,6 +112,10 @@ public class EdgeContextComponent {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ActorService actorService;
|
private ActorService actorService;
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
@Autowired
|
||||||
|
private WidgetsBundleService widgetsBundleService;
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
@Autowired
|
@Autowired
|
||||||
private DeviceStateService deviceStateService;
|
private DeviceStateService deviceStateService;
|
||||||
@ -163,6 +160,10 @@ public class EdgeContextComponent {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private RelationUpdateMsgConstructor relationUpdateMsgConstructor;
|
private RelationUpdateMsgConstructor relationUpdateMsgConstructor;
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
@Autowired
|
||||||
|
private WidgetsBundleUpdateMsgConstructor widgetsBundleUpdateMsgConstructor;
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
@Autowired
|
@Autowired
|
||||||
private EntityDataMsgConstructor entityDataMsgConstructor;
|
private EntityDataMsgConstructor entityDataMsgConstructor;
|
||||||
|
|||||||
@ -54,6 +54,7 @@ import org.thingsboard.server.common.data.id.EntityViewId;
|
|||||||
import org.thingsboard.server.common.data.id.RuleChainId;
|
import org.thingsboard.server.common.data.id.RuleChainId;
|
||||||
import org.thingsboard.server.common.data.id.TenantId;
|
import org.thingsboard.server.common.data.id.TenantId;
|
||||||
import org.thingsboard.server.common.data.id.UserId;
|
import org.thingsboard.server.common.data.id.UserId;
|
||||||
|
import org.thingsboard.server.common.data.id.WidgetsBundleId;
|
||||||
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
|
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
|
||||||
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
|
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
|
||||||
import org.thingsboard.server.common.data.kv.LongDataEntry;
|
import org.thingsboard.server.common.data.kv.LongDataEntry;
|
||||||
@ -66,6 +67,7 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData;
|
|||||||
import org.thingsboard.server.common.data.security.DeviceCredentials;
|
import org.thingsboard.server.common.data.security.DeviceCredentials;
|
||||||
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
|
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
|
||||||
import org.thingsboard.server.common.data.security.UserCredentials;
|
import org.thingsboard.server.common.data.security.UserCredentials;
|
||||||
|
import org.thingsboard.server.common.data.widget.WidgetsBundle;
|
||||||
import org.thingsboard.server.common.msg.TbMsg;
|
import org.thingsboard.server.common.msg.TbMsg;
|
||||||
import org.thingsboard.server.common.msg.TbMsgMetaData;
|
import org.thingsboard.server.common.msg.TbMsgMetaData;
|
||||||
import org.thingsboard.server.common.msg.queue.ServiceType;
|
import org.thingsboard.server.common.msg.queue.ServiceType;
|
||||||
@ -99,6 +101,7 @@ import org.thingsboard.server.gen.edge.UplinkMsg;
|
|||||||
import org.thingsboard.server.gen.edge.UplinkResponseMsg;
|
import org.thingsboard.server.gen.edge.UplinkResponseMsg;
|
||||||
import org.thingsboard.server.gen.edge.UserCredentialsRequestMsg;
|
import org.thingsboard.server.gen.edge.UserCredentialsRequestMsg;
|
||||||
import org.thingsboard.server.gen.edge.UserCredentialsUpdateMsg;
|
import org.thingsboard.server.gen.edge.UserCredentialsUpdateMsg;
|
||||||
|
import org.thingsboard.server.gen.edge.WidgetsBundleUpdateMsg;
|
||||||
import org.thingsboard.server.gen.transport.TransportProtos;
|
import org.thingsboard.server.gen.transport.TransportProtos;
|
||||||
import org.thingsboard.server.queue.TbQueueCallback;
|
import org.thingsboard.server.queue.TbQueueCallback;
|
||||||
import org.thingsboard.server.queue.TbQueueMsgMetadata;
|
import org.thingsboard.server.queue.TbQueueMsgMetadata;
|
||||||
@ -335,6 +338,9 @@ public final class EdgeGrpcSession implements Closeable {
|
|||||||
case RELATION:
|
case RELATION:
|
||||||
processRelation(edgeEvent, msgType);
|
processRelation(edgeEvent, msgType);
|
||||||
break;
|
break;
|
||||||
|
case WIDGETS_BUNDLE:
|
||||||
|
processWidgetsBundle(edgeEvent, msgType, edgeEventAction);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -588,6 +594,36 @@ public final class EdgeGrpcSession implements Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void processWidgetsBundle(EdgeEvent edgeEvent, UpdateMsgType msgType, ActionType edgeActionType) {
|
||||||
|
WidgetsBundleId widgetsBundleId = new WidgetsBundleId(edgeEvent.getEntityId());
|
||||||
|
EntityUpdateMsg entityUpdateMsg = null;
|
||||||
|
switch (edgeActionType) {
|
||||||
|
case ADDED:
|
||||||
|
case UPDATED:
|
||||||
|
WidgetsBundle widgetsBundle = ctx.getWidgetsBundleService().findWidgetsBundleById(edgeEvent.getTenantId(), widgetsBundleId);
|
||||||
|
if (widgetsBundle != null) {
|
||||||
|
WidgetsBundleUpdateMsg widgetsBundleUpdateMsg =
|
||||||
|
ctx.getWidgetsBundleUpdateMsgConstructor().constructWidgetsBundleUpdateMsg(msgType, widgetsBundle);
|
||||||
|
entityUpdateMsg = EntityUpdateMsg.newBuilder()
|
||||||
|
.setWidgetsBundleUpdateMsg(widgetsBundleUpdateMsg)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DELETED:
|
||||||
|
WidgetsBundleUpdateMsg widgetsBundleUpdateMsg =
|
||||||
|
ctx.getWidgetsBundleUpdateMsgConstructor().constructWidgetsBundleDeleteMsg(widgetsBundleId);
|
||||||
|
entityUpdateMsg = EntityUpdateMsg.newBuilder()
|
||||||
|
.setWidgetsBundleUpdateMsg(widgetsBundleUpdateMsg)
|
||||||
|
.build();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (entityUpdateMsg != null) {
|
||||||
|
outputStream.onNext(ResponseMsg.newBuilder()
|
||||||
|
.setEntityUpdateMsg(entityUpdateMsg)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private UpdateMsgType getResponseMsgType(ActionType actionType) {
|
private UpdateMsgType getResponseMsgType(ActionType actionType) {
|
||||||
switch (actionType) {
|
switch (actionType) {
|
||||||
case UPDATED:
|
case UPDATED:
|
||||||
|
|||||||
@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* Copyright © 2016-2020 The Thingsboard Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.thingsboard.server.service.edge.rpc.constructor;
|
||||||
|
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.thingsboard.server.common.data.id.WidgetsBundleId;
|
||||||
|
import org.thingsboard.server.common.data.widget.WidgetsBundle;
|
||||||
|
import org.thingsboard.server.gen.edge.UpdateMsgType;
|
||||||
|
import org.thingsboard.server.gen.edge.WidgetsBundleUpdateMsg;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class WidgetsBundleUpdateMsgConstructor {
|
||||||
|
|
||||||
|
public WidgetsBundleUpdateMsg constructWidgetsBundleUpdateMsg(UpdateMsgType msgType, WidgetsBundle widgetsBundle) {
|
||||||
|
WidgetsBundleUpdateMsg.Builder builder = WidgetsBundleUpdateMsg.newBuilder()
|
||||||
|
.setMsgType(msgType)
|
||||||
|
.setIdMSB(widgetsBundle.getId().getId().getMostSignificantBits())
|
||||||
|
.setIdLSB(widgetsBundle.getId().getId().getLeastSignificantBits())
|
||||||
|
.setTitle(widgetsBundle.getTitle())
|
||||||
|
.setAlias(widgetsBundle.getAlias());
|
||||||
|
if (widgetsBundle.getImage() != null) {
|
||||||
|
builder.setImage(ByteString.copyFrom(widgetsBundle.getImage()));
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public WidgetsBundleUpdateMsg constructWidgetsBundleDeleteMsg(WidgetsBundleId widgetsBundleId) {
|
||||||
|
return WidgetsBundleUpdateMsg.newBuilder()
|
||||||
|
.setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE)
|
||||||
|
.setIdMSB(widgetsBundleId.getId().getMostSignificantBits())
|
||||||
|
.setIdLSB(widgetsBundleId.getId().getLeastSignificantBits())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,9 +16,6 @@
|
|||||||
package org.thingsboard.server.common.data;
|
package org.thingsboard.server.common.data;
|
||||||
|
|
||||||
import org.thingsboard.server.common.data.edge.EdgeEventType;
|
import org.thingsboard.server.common.data.edge.EdgeEventType;
|
||||||
import org.thingsboard.server.common.data.id.EdgeId;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public final class EdgeUtils {
|
public final class EdgeUtils {
|
||||||
|
|
||||||
@ -39,6 +36,8 @@ public final class EdgeUtils {
|
|||||||
return EdgeEventType.USER;
|
return EdgeEventType.USER;
|
||||||
case ALARM:
|
case ALARM:
|
||||||
return EdgeEventType.ALARM;
|
return EdgeEventType.ALARM;
|
||||||
|
case WIDGETS_BUNDLE:
|
||||||
|
return EdgeEventType.WIDGETS_BUNDLE;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,5 +26,6 @@ public enum EdgeEventType {
|
|||||||
EDGE,
|
EDGE,
|
||||||
USER,
|
USER,
|
||||||
CUSTOMER,
|
CUSTOMER,
|
||||||
RELATION
|
RELATION,
|
||||||
|
WIDGETS_BUNDLE
|
||||||
}
|
}
|
||||||
|
|||||||
@ -87,6 +87,8 @@ public class EntityIdFactory {
|
|||||||
return new RuleChainId(uuid);
|
return new RuleChainId(uuid);
|
||||||
case ENTITY_VIEW:
|
case ENTITY_VIEW:
|
||||||
return new EntityViewId(uuid);
|
return new EntityViewId(uuid);
|
||||||
|
case WIDGETS_BUNDLE:
|
||||||
|
return new WidgetsBundleId(uuid);
|
||||||
case EDGE:
|
case EDGE:
|
||||||
return new EdgeId(uuid);
|
return new EdgeId(uuid);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,6 +59,7 @@ message EntityUpdateMsg {
|
|||||||
UserCredentialsUpdateMsg userCredentialsUpdateMsg = 10;
|
UserCredentialsUpdateMsg userCredentialsUpdateMsg = 10;
|
||||||
CustomerUpdateMsg customerUpdateMsg = 11;
|
CustomerUpdateMsg customerUpdateMsg = 11;
|
||||||
RelationUpdateMsg relationUpdateMsg = 12;
|
RelationUpdateMsg relationUpdateMsg = 12;
|
||||||
|
WidgetsBundleUpdateMsg widgetsBundleUpdateMsg = 13;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RequestMsgType {
|
enum RequestMsgType {
|
||||||
@ -266,6 +267,15 @@ message UserUpdateMsg {
|
|||||||
string additionalInfo = 8;
|
string additionalInfo = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message WidgetsBundleUpdateMsg {
|
||||||
|
UpdateMsgType msgType = 1;
|
||||||
|
int64 idMSB = 2;
|
||||||
|
int64 idLSB = 3;
|
||||||
|
string title = 4;
|
||||||
|
string alias = 5;
|
||||||
|
bytes image = 6;
|
||||||
|
}
|
||||||
|
|
||||||
message UserCredentialsUpdateMsg {
|
message UserCredentialsUpdateMsg {
|
||||||
int64 userIdMSB = 1;
|
int64 userIdMSB = 1;
|
||||||
int64 userIdLSB = 2;
|
int64 userIdLSB = 2;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user