|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 113 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 113 KiB |
|
After Width: | Height: | Size: 17 KiB |
@ -0,0 +1,14 @@ |
|||
{ |
|||
"widgetsBundle": { |
|||
"alias": "scada_symbols", |
|||
"title": "SCADA symbols", |
|||
"scada": true, |
|||
"image": null, |
|||
"description": "Bundle with SCADA symbols", |
|||
"order": 9200, |
|||
"name": "SCADA symbols" |
|||
}, |
|||
"widgetTypeFqns": [ |
|||
"scada_symbol" |
|||
] |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
{ |
|||
"widgetsBundle": { |
|||
"alias": "scada_water_system_symbols", |
|||
"title": "SCADA water system symbols", |
|||
"scada": true, |
|||
"image": null, |
|||
"description": "Bundle with SCADA symbols for water system", |
|||
"order": 9300, |
|||
"name": "SCADA water system symbols" |
|||
}, |
|||
"widgetTypeFqns": [ |
|||
"horizontal_pipe", |
|||
"long_horizontal_pipe", |
|||
"vertical_pipe", |
|||
"long_vertical_pipe", |
|||
"left_bottom_elbow_pipe", |
|||
"bottom_right_elbow_pipe", |
|||
"top_right_elbow_pipe", |
|||
"left_top_elbow_pipe", |
|||
"cross_pipe", |
|||
"left_tee_pipe", |
|||
"bottom_tee_pipe", |
|||
"right_tee_pipe", |
|||
"top_tee_pipe", |
|||
"right_elbow_drain_pipe", |
|||
"left_elbow_drain_pipe", |
|||
"left_drain_pipe", |
|||
"right_drain_pipe", |
|||
"short_left_drain_pipe", |
|||
"short_right_drain_pipe", |
|||
"top_flow_meter", |
|||
"right_flow_meter", |
|||
"bottom_flow_meter", |
|||
"left_flow_meter", |
|||
"horizontal_inline_flow_meter", |
|||
"vertical_inline_flow_meter", |
|||
"centrifugal_pump", |
|||
"small_right_motor_pump", |
|||
"small_left_motor_pump", |
|||
"right_motor_pump", |
|||
"left_motor_pump", |
|||
"right_heat_pump", |
|||
"left_heat_pump", |
|||
"short_bottom_filter", |
|||
"long_bottom_filter", |
|||
"short_top_filter", |
|||
"long_top_filter", |
|||
"stand_filter", |
|||
"horizontal_wheel_valve", |
|||
"vertical_wheel_valve", |
|||
"horizontal_ball_valve", |
|||
"vertical_ball_valve", |
|||
"vertical_tank", |
|||
"stand_vertical_tank", |
|||
"cylindrical_tank", |
|||
"stand_cylindrical_tank", |
|||
"vertical_short_tank", |
|||
"stand_vertical_short_tank", |
|||
"large_cylindrical_tank", |
|||
"large_stand_cylindrical_tank", |
|||
"large_vertical_tank", |
|||
"large_stand_vertical_tank", |
|||
"horizontal_tank", |
|||
"stand_horizontal_tank", |
|||
"spherical_tank", |
|||
"small_spherical_tank", |
|||
"elevated_tank", |
|||
"pool" |
|||
] |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
{ |
|||
"fqn": "scada_symbol", |
|||
"name": "SCADA symbol", |
|||
"deprecated": false, |
|||
"scada": true, |
|||
"image": null, |
|||
"description": "", |
|||
"descriptor": { |
|||
"type": "rpc", |
|||
"sizeX": 3, |
|||
"sizeY": 3, |
|||
"resources": [], |
|||
"templateHtml": "<tb-scada-symbol-widget\n [ctx]='ctx'\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-scada-symbol-widget>", |
|||
"templateCss": "", |
|||
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.actionWidget.onInit();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '300px',\n previewHeight: '320px',\n embedTitlePanel: true,\n targetDeviceOptional: true,\n displayRpcMessageToast: false\n };\n};\n\nself.onDestroy = function() {\n}\n", |
|||
"settingsSchema": "", |
|||
"dataKeySettingsSchema": "{}\n", |
|||
"settingsDirective": "tb-scada-symbol-widget-settings", |
|||
"hasBasicMode": true, |
|||
"basicModeDirective": "tb-scada-symbol-basic-config", |
|||
"defaultConfig": "{\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"background\":{\"type\":\"color\",\"imageUrl\":null,\"color\":\"rgb(255, 255, 255)\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"padding\":\"12px\",\"scadaSymbolUrl\":\"\",\"scadaSymbolObjectSettings\":{\"behavior\":{},\"properties\":{}}},\"title\":\"SCADA symbol\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"actions\":{},\"widgetCss\":\"\",\"noDataDisplayMessage\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":null,\"lineHeight\":\"1.6\"},\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleStyle\":null,\"pageSize\":1024,\"titleIcon\":\"mdi:lightbulb-outline\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"configMode\":\"basic\",\"targetDevice\":null,\"titleColor\":null,\"borderRadius\":\"0px\",\"margin\":\"0px\"}" |
|||
}, |
|||
"tags": null |
|||
} |
|||
@ -1,78 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2024 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.config; |
|||
|
|||
import jakarta.servlet.FilterChain; |
|||
import jakarta.servlet.ServletException; |
|||
import jakarta.servlet.http.HttpServletRequest; |
|||
import jakarta.servlet.http.HttpServletResponse; |
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.stereotype.Component; |
|||
import org.springframework.util.AntPathMatcher; |
|||
import org.springframework.web.filter.OncePerRequestFilter; |
|||
import org.thingsboard.server.common.msg.tools.MaxPayloadSizeExceededException; |
|||
import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.List; |
|||
|
|||
@Slf4j |
|||
@Component |
|||
@RequiredArgsConstructor |
|||
public class RequestSizeFilter extends OncePerRequestFilter { |
|||
|
|||
private final List<String> urls = List.of("/api/plugins/rpc/**", "/api/rpc/**"); |
|||
private final AntPathMatcher pathMatcher = new AntPathMatcher(); |
|||
private final ThingsboardErrorResponseHandler errorResponseHandler; |
|||
|
|||
@Value("${transport.http.max_payload_size:65536}") |
|||
private int maxPayloadSize; |
|||
|
|||
@Override |
|||
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { |
|||
if (request.getContentLength() > maxPayloadSize) { |
|||
if (log.isDebugEnabled()) { |
|||
log.debug("Too large payload size. Url: {}, client ip: {}, content length: {}", request.getRequestURL(), |
|||
request.getRemoteAddr(), request.getContentLength()); |
|||
} |
|||
errorResponseHandler.handle(new MaxPayloadSizeExceededException(), response); |
|||
return; |
|||
} |
|||
chain.doFilter(request, response); |
|||
} |
|||
|
|||
@Override |
|||
protected boolean shouldNotFilter(HttpServletRequest request) { |
|||
for (String url : urls) { |
|||
if (pathMatcher.match(url, request.getRequestURI())) { |
|||
return false; |
|||
} |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
@Override |
|||
protected boolean shouldNotFilterAsyncDispatch() { |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
protected boolean shouldNotFilterErrorDispatch() { |
|||
return false; |
|||
} |
|||
} |
|||
@ -0,0 +1,132 @@ |
|||
/** |
|||
* Copyright © 2016-2024 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.controller; |
|||
|
|||
import io.swagger.v3.oas.annotations.Parameter; |
|||
import io.swagger.v3.oas.annotations.media.ArraySchema; |
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import jakarta.validation.Valid; |
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.security.access.prepost.PreAuthorize; |
|||
import org.springframework.web.bind.annotation.DeleteMapping; |
|||
import org.springframework.web.bind.annotation.GetMapping; |
|||
import org.springframework.web.bind.annotation.PathVariable; |
|||
import org.springframework.web.bind.annotation.PostMapping; |
|||
import org.springframework.web.bind.annotation.PutMapping; |
|||
import org.springframework.web.bind.annotation.RequestBody; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RequestParam; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
import org.thingsboard.server.common.data.domain.Domain; |
|||
import org.thingsboard.server.common.data.domain.DomainInfo; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.DomainId; |
|||
import org.thingsboard.server.common.data.id.OAuth2ClientId; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
import org.thingsboard.server.config.annotations.ApiOperation; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.entitiy.domain.TbDomainService; |
|||
import org.thingsboard.server.service.security.permission.Operation; |
|||
import org.thingsboard.server.service.security.permission.Resource; |
|||
|
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; |
|||
import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK; |
|||
|
|||
@RestController |
|||
@TbCoreComponent |
|||
@RequestMapping("/api") |
|||
@RequiredArgsConstructor |
|||
@Slf4j |
|||
public class DomainController extends BaseController { |
|||
|
|||
private final TbDomainService tbDomainService; |
|||
|
|||
@ApiOperation(value = "Save or Update Domain (saveDomain)", |
|||
notes = "Create or update the Domain. When creating domain, platform generates Domain Id as " + UUID_WIKI_LINK + |
|||
"The newly created Domain Id will be present in the response. " + |
|||
"Specify existing Domain Id to update the domain. " + |
|||
"Referencing non-existing Domain Id will cause 'Not Found' error." + |
|||
"\n\nDomain name is unique for entire platform setup.\n\n" + SYSTEM_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')") |
|||
@PostMapping(value = "/domain") |
|||
public Domain saveDomain( |
|||
@Parameter(description = "A JSON value representing the Domain.", required = true) |
|||
@RequestBody @Valid Domain domain, |
|||
@Parameter(description = "A list of oauth2 client registration ids, separated by comma ','", array = @ArraySchema(schema = @Schema(type = "string"))) |
|||
@RequestParam(name = "oauth2ClientIds", required = false) UUID[] ids) throws Exception { |
|||
domain.setTenantId(getTenantId()); |
|||
checkEntity(domain.getId(), domain, Resource.DOMAIN); |
|||
return tbDomainService.save(domain, getOAuth2ClientIds(ids), getCurrentUser()); |
|||
} |
|||
|
|||
@ApiOperation(value = "Update oauth2 clients (updateOauth2Clients)", |
|||
notes = "Update oauth2 clients for the specified domain. ") |
|||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')") |
|||
@PutMapping(value = "/domain/{id}/oauth2Clients") |
|||
public void updateOauth2Clients(@PathVariable UUID id, |
|||
@RequestBody UUID[] clientIds) throws ThingsboardException { |
|||
DomainId domainId = new DomainId(id); |
|||
Domain domain = checkDomainId(domainId, Operation.WRITE); |
|||
List<OAuth2ClientId> oAuth2ClientIds = getOAuth2ClientIds(clientIds); |
|||
tbDomainService.updateOauth2Clients(domain, oAuth2ClientIds, getCurrentUser()); |
|||
} |
|||
|
|||
@ApiOperation(value = "Get Domain infos (getTenantDomainInfos)", notes = SYSTEM_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')") |
|||
@GetMapping(value = "/domain/infos") |
|||
public PageData<DomainInfo> getTenantDomainInfos(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) |
|||
@RequestParam int pageSize, |
|||
@Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) |
|||
@RequestParam int page, |
|||
@Parameter(description = "Case-insensitive 'substring' filter based on domain's name") |
|||
@RequestParam(required = false) String textSearch, |
|||
@Parameter(description = SORT_PROPERTY_DESCRIPTION) |
|||
@RequestParam(required = false) String sortProperty, |
|||
@Parameter(description = SORT_ORDER_DESCRIPTION) |
|||
@RequestParam(required = false) String sortOrder) throws ThingsboardException { |
|||
accessControlService.checkPermission(getCurrentUser(), Resource.DOMAIN, Operation.READ); |
|||
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
|||
return domainService.findDomainInfosByTenantId(getTenantId(), pageLink); |
|||
} |
|||
|
|||
@ApiOperation(value = "Get Domain info by Id (getDomainInfoById)", notes = SYSTEM_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')") |
|||
@GetMapping(value = "/domain/info/{id}") |
|||
public DomainInfo getDomainInfoById(@PathVariable UUID id) throws ThingsboardException { |
|||
DomainId domainId = new DomainId(id); |
|||
return checkEntityId(domainId, domainService::findDomainInfoById, Operation.READ); |
|||
} |
|||
|
|||
@ApiOperation(value = "Delete Domain by ID (deleteDomain)", |
|||
notes = "Deletes Domain by ID. Referencing non-existing domain Id will cause an error." + SYSTEM_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAuthority('SYS_ADMIN')") |
|||
@DeleteMapping(value = "/domain/{id}") |
|||
public void deleteDomain(@PathVariable UUID id) throws Exception { |
|||
DomainId domainId = new DomainId(id); |
|||
Domain domain = checkDomainId(domainId, Operation.DELETE); |
|||
tbDomainService.delete(domain, getCurrentUser()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,133 @@ |
|||
/** |
|||
* Copyright © 2016-2024 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.controller; |
|||
|
|||
import io.swagger.v3.oas.annotations.Parameter; |
|||
import io.swagger.v3.oas.annotations.media.ArraySchema; |
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import jakarta.validation.Valid; |
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.security.access.prepost.PreAuthorize; |
|||
import org.springframework.web.bind.annotation.DeleteMapping; |
|||
import org.springframework.web.bind.annotation.GetMapping; |
|||
import org.springframework.web.bind.annotation.PathVariable; |
|||
import org.springframework.web.bind.annotation.PostMapping; |
|||
import org.springframework.web.bind.annotation.PutMapping; |
|||
import org.springframework.web.bind.annotation.RequestBody; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RequestParam; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.MobileAppId; |
|||
import org.thingsboard.server.common.data.id.OAuth2ClientId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.mobile.MobileApp; |
|||
import org.thingsboard.server.common.data.mobile.MobileAppInfo; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
import org.thingsboard.server.config.annotations.ApiOperation; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.entitiy.mobile.TbMobileAppService; |
|||
import org.thingsboard.server.service.security.permission.Operation; |
|||
import org.thingsboard.server.service.security.permission.Resource; |
|||
|
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; |
|||
import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK; |
|||
|
|||
@RestController |
|||
@TbCoreComponent |
|||
@RequestMapping("/api") |
|||
@RequiredArgsConstructor |
|||
@Slf4j |
|||
public class MobileAppController extends BaseController { |
|||
|
|||
private final TbMobileAppService tbMobileAppService; |
|||
|
|||
@ApiOperation(value = "Save Or update Mobile app (saveMobileApp)", |
|||
notes = "Create or update the Mobile app. When creating mobile app, platform generates Mobile App Id as " + UUID_WIKI_LINK + |
|||
"The newly created Mobile App Id will be present in the response. " + |
|||
"Specify existing Mobile App Id to update the mobile app. " + |
|||
"Referencing non-existing Mobile App Id will cause 'Not Found' error." + |
|||
"\n\nMobile app package name is unique for entire platform setup.\n\n" + SYSTEM_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')") |
|||
@PostMapping(value = "/mobileApp") |
|||
public MobileApp saveMobileApp( |
|||
@Parameter(description = "A JSON value representing the Mobile Application.", required = true) |
|||
@RequestBody @Valid MobileApp mobileApp, |
|||
@Parameter(description = "A list of entity oauth2 client ids, separated by comma ','", array = @ArraySchema(schema = @Schema(type = "string"))) |
|||
@RequestParam(name = "oauth2ClientIds", required = false) UUID[] ids) throws Exception { |
|||
mobileApp.setTenantId(getTenantId()); |
|||
checkEntity(mobileApp.getId(), mobileApp, Resource.MOBILE_APP); |
|||
return tbMobileAppService.save(mobileApp, getOAuth2ClientIds(ids), getCurrentUser()); |
|||
} |
|||
|
|||
@ApiOperation(value = "Update oauth2 clients (updateOauth2Clients)", |
|||
notes = "Update oauth2 clients of the specified mobile app. ") |
|||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')") |
|||
@PutMapping(value = "/mobileApp/{id}/oauth2Clients") |
|||
public void updateOauth2Clients(@PathVariable UUID id, |
|||
@RequestBody UUID[] clientIds) throws ThingsboardException { |
|||
MobileAppId mobileAppId = new MobileAppId(id); |
|||
MobileApp mobileApp = checkMobileAppId(mobileAppId, Operation.WRITE); |
|||
List<OAuth2ClientId> oAuth2ClientIds = getOAuth2ClientIds(clientIds); |
|||
tbMobileAppService.updateOauth2Clients(mobileApp, oAuth2ClientIds, getCurrentUser()); |
|||
} |
|||
|
|||
@ApiOperation(value = "Get mobile app infos (getTenantMobileAppInfos)", notes = SYSTEM_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')") |
|||
@GetMapping(value = "/mobileApp/infos") |
|||
public PageData<MobileAppInfo> getTenantMobileAppInfos(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) |
|||
@RequestParam int pageSize, |
|||
@Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) |
|||
@RequestParam int page, |
|||
@Parameter(description = "Case-insensitive 'substring' filter based on app's name") |
|||
@RequestParam(required = false) String textSearch, |
|||
@Parameter(description = SORT_PROPERTY_DESCRIPTION) |
|||
@RequestParam(required = false) String sortProperty, |
|||
@Parameter(description = SORT_ORDER_DESCRIPTION) |
|||
@RequestParam(required = false) String sortOrder) throws ThingsboardException { |
|||
accessControlService.checkPermission(getCurrentUser(), Resource.MOBILE_APP, Operation.READ); |
|||
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
|||
return mobileAppService.findMobileAppInfosByTenantId(getTenantId(), pageLink); |
|||
} |
|||
|
|||
@ApiOperation(value = "Get mobile info by id (getMobileAppInfoById)", notes = SYSTEM_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')") |
|||
@GetMapping(value = "/mobileApp/info/{id}") |
|||
public MobileAppInfo getMobileAppInfoById(@PathVariable UUID id) throws ThingsboardException { |
|||
MobileAppId mobileAppId = new MobileAppId(id); |
|||
return checkEntityId(mobileAppId, mobileAppService::findMobileAppInfoById, Operation.READ); |
|||
} |
|||
|
|||
@ApiOperation(value = "Delete Mobile App by ID (deleteMobileApp)", |
|||
notes = "Deletes Mobile App by ID. Referencing non-existing mobile app Id will cause an error." + SYSTEM_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAuthority('SYS_ADMIN')") |
|||
@DeleteMapping(value = "/mobileApp/{id}") |
|||
public void deleteMobileApp(@PathVariable UUID id) throws Exception { |
|||
MobileAppId mobileAppId = new MobileAppId(id); |
|||
MobileApp mobileApp = checkMobileAppId(mobileAppId, Operation.DELETE); |
|||
tbMobileAppService.delete(mobileApp, getCurrentUser()); |
|||
} |
|||
|
|||
} |
|||
@ -1,222 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2024 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; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import jakarta.annotation.PostConstruct; |
|||
import jakarta.annotation.PreDestroy; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.context.ApplicationEventPublisher; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.common.util.ThingsBoardExecutors; |
|||
import org.thingsboard.server.common.data.audit.ActionType; |
|||
import org.thingsboard.server.common.data.edge.Edge; |
|||
import org.thingsboard.server.common.data.edge.EdgeEventType; |
|||
import org.thingsboard.server.common.data.id.RuleChainId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.queue.TbCallback; |
|||
import org.thingsboard.server.dao.edge.EdgeService; |
|||
import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.edge.rpc.processor.alarm.AlarmEdgeProcessor; |
|||
import org.thingsboard.server.service.edge.rpc.processor.asset.AssetEdgeProcessor; |
|||
import org.thingsboard.server.service.edge.rpc.processor.asset.profile.AssetProfileEdgeProcessor; |
|||
import org.thingsboard.server.service.edge.rpc.processor.customer.CustomerEdgeProcessor; |
|||
import org.thingsboard.server.service.edge.rpc.processor.dashboard.DashboardEdgeProcessor; |
|||
import org.thingsboard.server.service.edge.rpc.processor.device.DeviceEdgeProcessor; |
|||
import org.thingsboard.server.service.edge.rpc.processor.device.profile.DeviceProfileEdgeProcessor; |
|||
import org.thingsboard.server.service.edge.rpc.processor.edge.EdgeProcessor; |
|||
import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewEdgeProcessor; |
|||
import org.thingsboard.server.service.edge.rpc.processor.notification.NotificationEdgeProcessor; |
|||
import org.thingsboard.server.service.edge.rpc.processor.oauth2.OAuth2EdgeProcessor; |
|||
import org.thingsboard.server.service.edge.rpc.processor.ota.OtaPackageEdgeProcessor; |
|||
import org.thingsboard.server.service.edge.rpc.processor.queue.QueueEdgeProcessor; |
|||
import org.thingsboard.server.service.edge.rpc.processor.relation.RelationEdgeProcessor; |
|||
import org.thingsboard.server.service.edge.rpc.processor.resource.ResourceEdgeProcessor; |
|||
import org.thingsboard.server.service.edge.rpc.processor.rule.RuleChainEdgeProcessor; |
|||
import org.thingsboard.server.service.edge.rpc.processor.tenant.TenantEdgeProcessor; |
|||
import org.thingsboard.server.service.edge.rpc.processor.tenant.TenantProfileEdgeProcessor; |
|||
import org.thingsboard.server.service.edge.rpc.processor.user.UserEdgeProcessor; |
|||
import org.thingsboard.server.service.edge.rpc.processor.widget.WidgetBundleEdgeProcessor; |
|||
import org.thingsboard.server.service.edge.rpc.processor.widget.WidgetTypeEdgeProcessor; |
|||
|
|||
import java.util.UUID; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
@Service |
|||
@TbCoreComponent |
|||
@Slf4j |
|||
public class DefaultEdgeNotificationService implements EdgeNotificationService { |
|||
|
|||
public static final String EDGE_IS_ROOT_BODY_KEY = "isRoot"; |
|||
|
|||
@Autowired |
|||
private EdgeService edgeService; |
|||
|
|||
@Autowired |
|||
private EdgeProcessor edgeProcessor; |
|||
|
|||
@Autowired |
|||
private AssetEdgeProcessor assetProcessor; |
|||
|
|||
@Autowired |
|||
private AssetProfileEdgeProcessor assetProfileEdgeProcessor; |
|||
|
|||
@Autowired |
|||
private DeviceEdgeProcessor deviceProcessor; |
|||
|
|||
@Autowired |
|||
private DeviceProfileEdgeProcessor deviceProfileEdgeProcessor; |
|||
|
|||
@Autowired |
|||
private EntityViewEdgeProcessor entityViewProcessor; |
|||
|
|||
@Autowired |
|||
private DashboardEdgeProcessor dashboardProcessor; |
|||
|
|||
@Autowired |
|||
private RuleChainEdgeProcessor ruleChainProcessor; |
|||
|
|||
@Autowired |
|||
private UserEdgeProcessor userProcessor; |
|||
|
|||
@Autowired |
|||
private CustomerEdgeProcessor customerProcessor; |
|||
|
|||
@Autowired |
|||
private OtaPackageEdgeProcessor otaPackageProcessor; |
|||
|
|||
@Autowired |
|||
private WidgetBundleEdgeProcessor widgetBundleProcessor; |
|||
|
|||
@Autowired |
|||
private WidgetTypeEdgeProcessor widgetTypeProcessor; |
|||
|
|||
@Autowired |
|||
private QueueEdgeProcessor queueProcessor; |
|||
|
|||
@Autowired |
|||
private TenantEdgeProcessor tenantEdgeProcessor; |
|||
|
|||
@Autowired |
|||
private TenantProfileEdgeProcessor tenantProfileEdgeProcessor; |
|||
|
|||
@Autowired |
|||
private AlarmEdgeProcessor alarmProcessor; |
|||
|
|||
@Autowired |
|||
private RelationEdgeProcessor relationProcessor; |
|||
|
|||
@Autowired |
|||
private ResourceEdgeProcessor resourceEdgeProcessor; |
|||
|
|||
@Autowired |
|||
private NotificationEdgeProcessor notificationEdgeProcessor; |
|||
|
|||
@Autowired |
|||
private OAuth2EdgeProcessor oAuth2EdgeProcessor; |
|||
|
|||
@Autowired |
|||
protected ApplicationEventPublisher eventPublisher; |
|||
|
|||
@Value("${actors.system.edge_dispatcher_pool_size:4}") |
|||
private int edgeDispatcherSize; |
|||
|
|||
private ExecutorService executor; |
|||
|
|||
@PostConstruct |
|||
public void initExecutor() { |
|||
executor = ThingsBoardExecutors.newWorkStealingPool(edgeDispatcherSize, "edge-notifications"); |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void shutdownExecutor() { |
|||
if (executor != null) { |
|||
executor.shutdownNow(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) { |
|||
edge.setRootRuleChainId(ruleChainId); |
|||
Edge savedEdge = edgeService.saveEdge(edge); |
|||
ObjectNode isRootBody = JacksonUtil.newObjectNode(); |
|||
isRootBody.put(EDGE_IS_ROOT_BODY_KEY, Boolean.TRUE); |
|||
eventPublisher.publishEvent(ActionEntityEvent.builder().tenantId(tenantId).edgeId(edge.getId()).entityId(ruleChainId) |
|||
.body(JacksonUtil.toString(isRootBody)).actionType(ActionType.UPDATED).build()); |
|||
return savedEdge; |
|||
} |
|||
|
|||
@Override |
|||
public void pushNotificationToEdge(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback) { |
|||
TenantId tenantId = TenantId.fromUUID(new UUID(edgeNotificationMsg.getTenantIdMSB(), edgeNotificationMsg.getTenantIdLSB())); |
|||
log.debug("[{}] Pushing notification to edge {}", tenantId, edgeNotificationMsg); |
|||
final long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(60); |
|||
try { |
|||
executor.submit(() -> { |
|||
try { |
|||
if (deadline < System.nanoTime()) { |
|||
log.warn("[{}] Skipping notification message because deadline reached {}", tenantId, edgeNotificationMsg); |
|||
return; |
|||
} |
|||
EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); |
|||
switch (type) { |
|||
case EDGE -> edgeProcessor.processEdgeNotification(tenantId, edgeNotificationMsg); |
|||
case ASSET -> assetProcessor.processEntityNotification(tenantId, edgeNotificationMsg); |
|||
case ASSET_PROFILE -> assetProfileEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg); |
|||
case DEVICE -> deviceProcessor.processEntityNotification(tenantId, edgeNotificationMsg); |
|||
case DEVICE_PROFILE -> deviceProfileEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg); |
|||
case ENTITY_VIEW -> entityViewProcessor.processEntityNotification(tenantId, edgeNotificationMsg); |
|||
case DASHBOARD -> dashboardProcessor.processEntityNotification(tenantId, edgeNotificationMsg); |
|||
case RULE_CHAIN -> ruleChainProcessor.processEntityNotification(tenantId, edgeNotificationMsg); |
|||
case USER -> userProcessor.processEntityNotification(tenantId, edgeNotificationMsg); |
|||
case CUSTOMER -> customerProcessor.processCustomerNotification(tenantId, edgeNotificationMsg); |
|||
case OTA_PACKAGE -> otaPackageProcessor.processEntityNotification(tenantId, edgeNotificationMsg); |
|||
case WIDGETS_BUNDLE -> widgetBundleProcessor.processEntityNotification(tenantId, edgeNotificationMsg); |
|||
case WIDGET_TYPE -> widgetTypeProcessor.processEntityNotification(tenantId, edgeNotificationMsg); |
|||
case QUEUE -> queueProcessor.processEntityNotification(tenantId, edgeNotificationMsg); |
|||
case ALARM -> alarmProcessor.processAlarmNotification(tenantId, edgeNotificationMsg); |
|||
case ALARM_COMMENT -> alarmProcessor.processAlarmCommentNotification(tenantId, edgeNotificationMsg); |
|||
case RELATION -> relationProcessor.processRelationNotification(tenantId, edgeNotificationMsg); |
|||
case TENANT -> tenantEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg); |
|||
case TENANT_PROFILE -> tenantProfileEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg); |
|||
case NOTIFICATION_RULE, NOTIFICATION_TARGET, NOTIFICATION_TEMPLATE -> |
|||
notificationEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg); |
|||
case TB_RESOURCE -> resourceEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg); |
|||
case OAUTH2 -> oAuth2EdgeProcessor.processOAuth2Notification(tenantId, edgeNotificationMsg); |
|||
default -> log.warn("[{}] Edge event type [{}] is not designed to be pushed to edge", tenantId, type); |
|||
} |
|||
} catch (Exception e) { |
|||
callBackFailure(tenantId, edgeNotificationMsg, callback, e); |
|||
} |
|||
}); |
|||
callback.onSuccess(); |
|||
} catch (Exception e) { |
|||
callBackFailure(tenantId, edgeNotificationMsg, callback, e); |
|||
} |
|||
} |
|||
|
|||
private void callBackFailure(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback, Throwable throwable) { |
|||
log.error("[{}] Can't push to edge updates, edgeNotificationMsg [{}]", tenantId, edgeNotificationMsg, throwable); |
|||
callback.onFailure(throwable); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,80 @@ |
|||
/** |
|||
* Copyright © 2016-2024 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; |
|||
|
|||
import jakarta.annotation.PostConstruct; |
|||
import jakarta.annotation.PreDestroy; |
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.stereotype.Component; |
|||
import org.springframework.transaction.event.TransactionalEventListener; |
|||
import org.thingsboard.common.util.ThingsBoardThreadFactory; |
|||
import org.thingsboard.server.dao.edge.RelatedEdgesService; |
|||
import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; |
|||
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; |
|||
|
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.Executors; |
|||
|
|||
@Component |
|||
@RequiredArgsConstructor |
|||
@Slf4j |
|||
public class RelatedEdgesSourcingListener { |
|||
|
|||
private final RelatedEdgesService relatedEdgesService; |
|||
|
|||
private ExecutorService executorService; |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
log.debug("RelatedEdgesSourcingListener initiated"); |
|||
executorService = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("related-edges-listener")); |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void destroy() { |
|||
log.debug("RelatedEdgesSourcingListener destroy"); |
|||
if (executorService != null && !executorService.isShutdown()) { |
|||
executorService.shutdown(); |
|||
} |
|||
} |
|||
|
|||
@TransactionalEventListener(fallbackExecution = true) |
|||
public void handleEvent(ActionEntityEvent<?> event) { |
|||
executorService.submit(() -> { |
|||
try { |
|||
switch (event.getActionType()) { |
|||
case ASSIGNED_TO_EDGE, UNASSIGNED_FROM_EDGE -> |
|||
relatedEdgesService.publishRelatedEdgeIdsEvictEvent(event.getTenantId(), event.getEntityId()); |
|||
} |
|||
} catch (Exception e) { |
|||
log.error("[{}] failed to process ActionEntityEvent: {}", event.getTenantId(), event, e); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
@TransactionalEventListener(fallbackExecution = true) |
|||
public void handleEvent(DeleteEntityEvent<?> event) { |
|||
executorService.submit(() -> { |
|||
try { |
|||
relatedEdgesService.publishRelatedEdgeIdsEvictEvent(event.getTenantId(), event.getEntityId()); |
|||
} catch (Exception e) { |
|||
log.error("[{}] failed to process DeleteEntityEvent: {}", event.getTenantId(), event, e); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
} |
|||