Merge pull request #9605 from imbeacon/feature/add-gateways-dashboard

Added gateways dashboard creation on tenant add
This commit is contained in:
Andrew Shvayka 2023-11-20 12:45:53 +02:00 committed by GitHub
commit 1d1e8decd4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 43 additions and 1336 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
{ {
"title": "Gateway", "title": "ThingsBoard IoT Gateways",
"image": null, "image": null,
"mobileHide": false, "mobileHide": false,
"mobileOrder": null, "mobileOrder": null,
@ -40,7 +40,7 @@
"color": "rgba(0, 0, 0, 0.87)", "color": "rgba(0, 0, 0, 0.87)",
"padding": "4px", "padding": "4px",
"settings": { "settings": {
"entitiesTitle": "Gateway list", "entitiesTitle": "Gateways list",
"enableSearch": true, "enableSearch": true,
"enableSelectColumnDisplay": false, "enableSelectColumnDisplay": false,
"enableStickyHeader": true, "enableStickyHeader": true,
@ -55,7 +55,7 @@
"defaultSortOrder": "entityName", "defaultSortOrder": "entityName",
"useRowStyleFunction": false "useRowStyleFunction": false
}, },
"title": "New Entities table", "title": "Gateways list",
"dropShadow": true, "dropShadow": true,
"enableFullscreen": false, "enableFullscreen": false,
"titleStyle": { "titleStyle": {
@ -571,11 +571,11 @@
"padding": "8px", "padding": "8px",
"settings": { "settings": {
"useMarkdownTextFunction": true, "useMarkdownTextFunction": true,
"markdownTextFunction": "var blockData = '';\nvar connectorsIndex = ctx.actionsApi.getActionDescriptors('elementClick').findIndex(action=>action.name==\"Connecotrs\");\nvar logsIndex = ctx.actionsApi.getActionDescriptors('elementClick').findIndex(action=>action.name==\"Logs\");\nfunction generateMatHeader(index) {\n if( index !== undefined && index > -1) {\n return `<mat-card-header class='tb-home-widget-link' (click)=\"ctx.actionsApi.handleWidgetAction($event, ctx.actionsApi.getActionDescriptors('elementClick')[${index}], ctx.datasources[0].entity.id)\">`\n } else {\n return \"<mat-card-header >\" \n }\n}\nfunction createDataBlock(value, label, dividerStyle, mobile, index) {\n blockData += `\n <mat-card style=\"flex-grow: 1; width: ${mobile? '100%': 'auto'}; min-height: ${mobile? 'auto': '57px'}\" class=\" ${dividerStyle}\">\n <div class=\"divider\"></div>\n <mat-divider vertical style=\"height:100%\"></mat-divider>\n ${generateMatHeader(index)}\n <mat-card-subtitle>${label}</mat-card-subtitle>\n </mat-card-header>\n <mat-card-content> ${value}</mat-card-content>\n </mat-card>`;\n}\ncreateDataBlock(data[0].Status, \"Status\", data[0].Status === \"Active\"? 'divider-green' : 'divider-red');\ncreateDataBlock(data[0].Name, \"Gateway Name\", '', ctx.isMobile);\ncreateDataBlock(data[0].Type, \"Gateway Type\", '');\ncreateDataBlock(\n `<span style=\"color:rgb(25,128,56)\">${(data[1]?data[1].count:0)} </span>`\n + \" | \" + \n `<span style=\"color:rgb(203,37,48)\">${(data[2]?data[2][\"count 2\"]:0)} </span>`\n , \"Devices <span class='tb-hint' style='padding-left: 0'>(Active | Inactive)</span>\", '');\ncreateDataBlock(\n `<span style=\"color:rgb(25,128,56)\">${(data[0].active_connectors?JSON.parse(data[0].active_connectors).length:0)} </span>`\n + \" | \" + \n `<span style=\"color:rgb(203,37,48)\">${(data[0].inactive_connectors?JSON.parse(data[0].inactive_connectors).length:0)} </span>`\n , \"Connectors <span class='tb-hint' style='padding-left: 0'>(Active | Inactive)</span>\", '', '', connectorsIndex);\ncreateDataBlock(data[0].ALL_ERRORS_COUNT || 0, \"Errors\", (data[0].ALL_ERRORS_COUNT || 0) === 0 ? 'divider-green' : 'divider-red', '', logsIndex);\nreturn `<div fxLayout=\"row wrap\" fxLayoutGap=\"8px\" class=\"cards-container\">${blockData}</div>`;", "markdownTextFunction": "var blockData = '';\nvar connectorsIndex = ctx.actionsApi.getActionDescriptors('elementClick').findIndex(action=>action.name==\"Connectors\");\nvar logsIndex = ctx.actionsApi.getActionDescriptors('elementClick').findIndex(action=>action.name==\"Logs\");\nfunction generateMatHeader(index) {\n if( index !== undefined && index > -1) {\n return `<mat-card-header class='tb-home-widget-link' (click)=\"ctx.actionsApi.handleWidgetAction($event, ctx.actionsApi.getActionDescriptors('elementClick')[${index}], ctx.datasources[0].entity.id)\">`\n } else {\n return \"<mat-card-header >\" \n }\n}\nfunction createDataBlock(value, label, dividerStyle, mobile, index) {\n blockData += `\n <mat-card style=\"flex-grow: 1; width: ${mobile? '100%': 'auto'}; min-height: ${mobile? 'auto': '57px'}\" class=\" ${dividerStyle}\">\n <div class=\"divider\"></div>\n <mat-divider vertical style=\"height:100%\"></mat-divider>\n ${generateMatHeader(index)}\n <mat-card-subtitle>${label}</mat-card-subtitle>\n </mat-card-header>\n <mat-card-content> ${value}</mat-card-content>\n </mat-card>`;\n}\ncreateDataBlock(data[0].Status, \"Status\", data[0].Status === \"Active\"? 'divider-green' : 'divider-red');\ncreateDataBlock(data[0].Name, \"Gateway Name\", '', ctx.isMobile);\ncreateDataBlock(data[0].Type, \"Gateway Type\", '');\ncreateDataBlock(\n `<span style=\"color:rgb(25,128,56)\">${(data[1]?data[1].count:0)} </span>`\n + \" | \" + \n `<span style=\"color:rgb(203,37,48)\">${(data[2]?data[2][\"count 2\"]:0)} </span>`\n , \"Devices <span class='tb-hint' style='padding-left: 0'>(Active | Inactive)</span>\", '');\ncreateDataBlock(\n `<span style=\"color:rgb(25,128,56)\">${(data[0].active_connectors?JSON.parse(data[0].active_connectors).length:0)} </span>`\n + \" | \" + \n `<span style=\"color:rgb(203,37,48)\">${(data[0].inactive_connectors?JSON.parse(data[0].inactive_connectors).length:0)} </span>`\n , \"Connectors <span class='tb-hint' style='padding-left: 0'>(Active | Inactive)</span>\", '', '', connectorsIndex);\ncreateDataBlock(data[0].ALL_ERRORS_COUNT || 0, \"Errors\", (data[0].ALL_ERRORS_COUNT || 0) === 0 ? 'divider-green' : 'divider-red', '', logsIndex);\nreturn `<div fxLayout=\"row wrap\" fxLayoutGap=\"8px\" class=\"cards-container\">${blockData}</div>`;",
"applyDefaultMarkdownStyle": false, "applyDefaultMarkdownStyle": false,
"markdownCss": ".divider {\n position: absolute;\n width: 3px;\n top: 8px;\n border-radius: 2px;\n bottom: 8px;\n border: 1px solid rgba(31, 70, 144, 1);\n background-color: rgba(31, 70, 144, 1);\n left: 10px;\n}\n.divider-green .divider {\n border: 1px solid rgb(25,128,56);\n background-color: rgb(25,128,56);\n}\n\n.divider-green .mat-mdc-card-content {\n color: rgb(25,128,56);\n}\n\n.divider-red .divider {\n border: 1px solid rgb(203,37,48);\n background-color: rgb(203,37,48);\n}\n\n.divider-red .mat-mdc-card-content {\n color: rgb(203,37,48);\n}\n\n.mdc-card {\n position: relative;\n padding-left: 10px;\n margin-bottom: 1px;\n}\n\n.mat-mdc-card-subtitle {\n font-weight: 400;\n font-size: 12px;\n}\n\n.mat-mdc-card-header {\n padding: 8px 16px 0;\n}\n\n.mat-mdc-card-content:last-child {\n padding-bottom: 8px;\n font-size: 16px;\n}\n\n.cards-container {\n height: calc(100% - 1px);\n justify-content: stretch;\n align-items: center;\n margin-bottom: 1px;\n}\n\n::ng-deep.tb-home-widget-link > div {\n flex-grow: 1;\n cursor: pointer;\n}\n\n .tb-home-widget-link {\n width: 100%;\n }\n\n .tb-home-widget-link:hover::after{\n color: inherit;\n }\n \n .tb-home-widget-link::after{\n content: 'arrow_forward';\n display: inline-block;\n transform: rotate(315deg);\n font-family: 'Material Icons';\n font-weight: normal;\n font-style: normal;\n font-size: 18px;\n color: rgba(0, 0, 0, 0.12);\n vertical-align: bottom;\n margin-left: 6px;\n}" "markdownCss": ".divider {\n position: absolute;\n width: 3px;\n top: 8px;\n border-radius: 2px;\n bottom: 8px;\n border: 1px solid rgba(31, 70, 144, 1);\n background-color: rgba(31, 70, 144, 1);\n left: 10px;\n}\n.divider-green .divider {\n border: 1px solid rgb(25,128,56);\n background-color: rgb(25,128,56);\n}\n\n.divider-green .mat-mdc-card-content {\n color: rgb(25,128,56);\n}\n\n.divider-red .divider {\n border: 1px solid rgb(203,37,48);\n background-color: rgb(203,37,48);\n}\n\n.divider-red .mat-mdc-card-content {\n color: rgb(203,37,48);\n}\n\n.mdc-card {\n position: relative;\n padding-left: 10px;\n margin-bottom: 1px;\n}\n\n.mat-mdc-card-subtitle {\n font-weight: 400;\n font-size: 12px;\n}\n\n.mat-mdc-card-header {\n padding: 8px 16px 0;\n}\n\n.mat-mdc-card-content:last-child {\n padding-bottom: 8px;\n font-size: 16px;\n}\n\n.cards-container {\n height: calc(100% - 1px);\n justify-content: stretch;\n align-items: center;\n margin-bottom: 1px;\n}\n\n::ng-deep.tb-home-widget-link > div {\n flex-grow: 1;\n cursor: pointer;\n}\n\n .tb-home-widget-link {\n width: 100%;\n }\n\n .tb-home-widget-link:hover::after{\n color: inherit;\n }\n \n .tb-home-widget-link::after{\n content: 'arrow_forward';\n display: inline-block;\n transform: rotate(315deg);\n font-family: 'Material Icons';\n font-weight: normal;\n font-style: normal;\n font-size: 18px;\n color: rgba(0, 0, 0, 0.12);\n vertical-align: bottom;\n margin-left: 6px;\n}"
}, },
"title": "New Markdown/HTML Card", "title": "Connectors",
"showTitleIcon": false, "showTitleIcon": false,
"iconColor": "rgba(0, 0, 0, 0.87)", "iconColor": "rgba(0, 0, 0, 0.87)",
"iconSize": "24px", "iconSize": "24px",
@ -599,7 +599,7 @@
"actions": { "actions": {
"elementClick": [ "elementClick": [
{ {
"name": "Connecotrs", "name": "Connectors",
"icon": "more_horiz", "icon": "more_horiz",
"useShowWidgetActionFunction": null, "useShowWidgetActionFunction": null,
"showWidgetActionFunction": "return true;", "showWidgetActionFunction": "return true;",
@ -680,7 +680,7 @@
"defaultSortOrder": "-createdTime", "defaultSortOrder": "-createdTime",
"useRowStyleFunction": false "useRowStyleFunction": false
}, },
"title": "New Alarms table", "title": "Alarms",
"dropShadow": true, "dropShadow": true,
"enableFullscreen": false, "enableFullscreen": false,
"titleStyle": { "titleStyle": {
@ -1074,7 +1074,7 @@
} }
] ]
}, },
"title": "New RPC remote shell", "title": "RPC remote shell",
"dropShadow": true, "dropShadow": true,
"enableFullscreen": true, "enableFullscreen": true,
"widgetStyle": { "widgetStyle": {
@ -1485,7 +1485,7 @@
} }
] ]
}, },
"title": "New RPC debug terminal", "title": "RPC debug terminal",
"dropShadow": true, "dropShadow": true,
"enableFullscreen": true, "enableFullscreen": true,
"widgetStyle": {}, "widgetStyle": {},
@ -1868,7 +1868,7 @@
"applyDefaultMarkdownStyle": false, "applyDefaultMarkdownStyle": false,
"markdownCss": ".action-buttons-container {\r\n display: flex;\r\n flex-wrap: wrap;\r\n flex-direction: row;\r\n height: 100%;\r\n width: 100%;\r\n align-content: center;\r\n}\r\n\r\nbutton {\r\n flex-grow: 1;\r\n margin: 10px;\r\n min-width: 150px;\r\n height: auto;\r\n}" "markdownCss": ".action-buttons-container {\r\n display: flex;\r\n flex-wrap: wrap;\r\n flex-direction: row;\r\n height: 100%;\r\n width: 100%;\r\n align-content: center;\r\n}\r\n\r\nbutton {\r\n flex-grow: 1;\r\n margin: 10px;\r\n min-width: 150px;\r\n height: auto;\r\n}"
}, },
"title": "New Markdown/HTML Card", "title": "Service command",
"showTitleIcon": false, "showTitleIcon": false,
"iconColor": "rgba(0, 0, 0, 0.87)", "iconColor": "rgba(0, 0, 0, 0.87)",
"iconSize": "24px", "iconSize": "24px",
@ -1980,7 +1980,7 @@
"applyDefaultMarkdownStyle": false, "applyDefaultMarkdownStyle": false,
"markdownCss": ".action-buttons-container {\r\n display: flex;\r\n flex-wrap: wrap;\r\n flex-direction: row;\r\n height: 100%;\r\n width: 100%;\r\n align-content: start;\r\n}\r\n\r\nbutton {\r\n flex-grow: 1;\r\n margin: 10px;\r\n min-width: 150px;\r\n height: auto;\r\n}" "markdownCss": ".action-buttons-container {\r\n display: flex;\r\n flex-wrap: wrap;\r\n flex-direction: row;\r\n height: 100%;\r\n width: 100%;\r\n align-content: start;\r\n}\r\n\r\nbutton {\r\n flex-grow: 1;\r\n margin: 10px;\r\n min-width: 150px;\r\n height: auto;\r\n}"
}, },
"title": "New Markdown/HTML Card", "title": "General configuration",
"showTitleIcon": false, "showTitleIcon": false,
"iconColor": "rgba(0, 0, 0, 0.87)", "iconColor": "rgba(0, 0, 0, 0.87)",
"iconSize": "24px", "iconSize": "24px",
@ -2145,7 +2145,7 @@
"applyDefaultMarkdownStyle": true, "applyDefaultMarkdownStyle": true,
"markdownCss": ".mat-mdc-form-field-subscript-wrapper {\n display: none !important;\n}" "markdownCss": ".mat-mdc-form-field-subscript-wrapper {\n display: none !important;\n}"
}, },
"title": "New Markdown/HTML Card", "title": "Gateway devices",
"showTitleIcon": false, "showTitleIcon": false,
"iconColor": "rgba(0, 0, 0, 0.87)", "iconColor": "rgba(0, 0, 0, 0.87)",
"iconSize": "24px", "iconSize": "24px",
@ -4939,7 +4939,7 @@
"applyDefaultMarkdownStyle": false, "applyDefaultMarkdownStyle": false,
"markdownCss": ".action-container {\r\n display: flex;\r\n flex-wrap: wrap;\r\n flex-direction: row;\r\n height: 100%;\r\n width: 100%;\r\n}\r\n\r\nbutton {\r\n flex-grow: 1;\r\n margin: 10px;\r\n min-width: 150px;\r\n height: auto;\r\n}" "markdownCss": ".action-container {\r\n display: flex;\r\n flex-wrap: wrap;\r\n flex-direction: row;\r\n height: 100%;\r\n width: 100%;\r\n}\r\n\r\nbutton {\r\n flex-grow: 1;\r\n margin: 10px;\r\n min-width: 150px;\r\n height: auto;\r\n}"
}, },
"title": "New Markdown/HTML Card", "title": "Gateway commands",
"showTitleIcon": false, "showTitleIcon": false,
"iconColor": "rgba(0, 0, 0, 0.87)", "iconColor": "rgba(0, 0, 0, 0.87)",
"iconSize": "24px", "iconSize": "24px",

View File

@ -54,6 +54,7 @@ public class DefaultTbTenantService extends AbstractTbEntityService implements T
if (created) { if (created) {
installScripts.createDefaultRuleChains(savedTenant.getId()); installScripts.createDefaultRuleChains(savedTenant.getId());
installScripts.createDefaultEdgeRuleChains(savedTenant.getId()); installScripts.createDefaultEdgeRuleChains(savedTenant.getId());
installScripts.createDefaultTenantDashboards(savedTenant.getId(), null);
} }
tenantProfileCache.evict(savedTenant.getId()); tenantProfileCache.evict(savedTenant.getId());
notificationEntityService.notifyCreateOrUpdateTenant(savedTenant, created ? notificationEntityService.notifyCreateOrUpdateTenant(savedTenant, created ?

View File

@ -299,6 +299,15 @@ public class InstallScripts {
public void loadDashboards(TenantId tenantId, CustomerId customerId) throws Exception { public void loadDashboards(TenantId tenantId, CustomerId customerId) throws Exception {
Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR); Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR);
loadDashboardsFromDir(tenantId, customerId, dashboardsDir);
}
public void createDefaultTenantDashboards(TenantId tenantId, CustomerId customerId) throws Exception {
Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, DASHBOARDS_DIR);
loadDashboardsFromDir(tenantId, customerId, dashboardsDir);
}
private void loadDashboardsFromDir(TenantId tenantId, CustomerId customerId, Path dashboardsDir) throws IOException {
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) { try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) {
dirStream.forEach( dirStream.forEach(
path -> { path -> {

View File

@ -327,7 +327,18 @@ public class DashboardControllerTest extends AbstractControllerTest {
@Test @Test
public void testFindTenantDashboards() throws Exception { public void testFindTenantDashboards() throws Exception {
List<DashboardInfo> dashboards = new ArrayList<>(); List<DashboardInfo> expectedDashboards = new ArrayList<>();
PageLink pageLink = new PageLink(24);
PageData<DashboardInfo> pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
new TypeReference<PageData<DashboardInfo>>() {
}, pageLink);
expectedDashboards.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
Mockito.reset(tbClusterService, auditLogService); Mockito.reset(tbClusterService, auditLogService);
@ -335,7 +346,7 @@ public class DashboardControllerTest extends AbstractControllerTest {
for (int i = 0; i < cntEntity; i++) { for (int i = 0; i < cntEntity; i++) {
Dashboard dashboard = new Dashboard(); Dashboard dashboard = new Dashboard();
dashboard.setTitle("Dashboard" + i); dashboard.setTitle("Dashboard" + i);
dashboards.add(new DashboardInfo(doPost("/api/dashboard", dashboard, Dashboard.class))); expectedDashboards.add(new DashboardInfo(doPost("/api/dashboard", dashboard, Dashboard.class)));
} }
testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new Dashboard(), new Dashboard(), testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new Dashboard(), new Dashboard(),
@ -343,8 +354,6 @@ public class DashboardControllerTest extends AbstractControllerTest {
ActionType.ADDED, cntEntity, cntEntity, cntEntity); ActionType.ADDED, cntEntity, cntEntity, cntEntity);
List<DashboardInfo> loadedDashboards = new ArrayList<>(); List<DashboardInfo> loadedDashboards = new ArrayList<>();
PageLink pageLink = new PageLink(24);
PageData<DashboardInfo> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
new TypeReference<PageData<DashboardInfo>>() { new TypeReference<PageData<DashboardInfo>>() {
@ -355,10 +364,10 @@ public class DashboardControllerTest extends AbstractControllerTest {
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
dashboards.sort(idComparator); expectedDashboards.sort(idComparator);
loadedDashboards.sort(idComparator); loadedDashboards.sort(idComparator);
Assert.assertEquals(dashboards, loadedDashboards); Assert.assertEquals(expectedDashboards, loadedDashboards);
} }
@Test @Test

View File

@ -92,6 +92,8 @@ public class HomePageApiTest extends AbstractControllerTest {
@MockBean @MockBean
private SmsService smsService; private SmsService smsService;
private static final int DEFAULT_DASHBOARDS_COUNT = 1;
//For system administrator //For system administrator
@Test @Test
public void testTenantsCountWsCmd() throws Exception { public void testTenantsCountWsCmd() throws Exception {
@ -408,7 +410,7 @@ public class HomePageApiTest extends AbstractControllerTest {
Assert.assertEquals(2, usageInfo.getUsers()); Assert.assertEquals(2, usageInfo.getUsers());
Assert.assertEquals(configuration.getMaxUsers(), usageInfo.getMaxUsers()); Assert.assertEquals(configuration.getMaxUsers(), usageInfo.getMaxUsers());
Assert.assertEquals(0, usageInfo.getDashboards()); Assert.assertEquals(DEFAULT_DASHBOARDS_COUNT, usageInfo.getDashboards());
Assert.assertEquals(configuration.getMaxDashboards(), usageInfo.getMaxDashboards()); Assert.assertEquals(configuration.getMaxDashboards(), usageInfo.getMaxDashboards());
Assert.assertEquals(0, usageInfo.getTransportMessages()); Assert.assertEquals(0, usageInfo.getTransportMessages());
@ -478,7 +480,8 @@ public class HomePageApiTest extends AbstractControllerTest {
} }
usageInfo = doGet("/api/usage", UsageInfo.class); usageInfo = doGet("/api/usage", UsageInfo.class);
Assert.assertEquals(dashboards.size(), usageInfo.getDashboards()); int expectedDashboardsCount = dashboards.size() + DEFAULT_DASHBOARDS_COUNT;
Assert.assertEquals(expectedDashboardsCount, usageInfo.getDashboards());
} }
private Long getInitialEntityCount(EntityType entityType) throws Exception { private Long getInitialEntityCount(EntityType entityType) throws Exception {