Commit c36af7311ef46d64a0396525045ac88d17ff048e
Committed by
Igor Kulikov
1 parent
9f266cd2
Add bulk provision support label (#2096)
* Add to asset support label * Add support import label * Add support update entity type and label * Add translate asset label
Showing
27 changed files
with
127 additions
and
29 deletions
... | ... | @@ -119,6 +119,11 @@ public class ThingsboardInstallService { |
119 | 119 | case "2.4.0": |
120 | 120 | log.info("Upgrading ThingsBoard from version 2.4.0 to 2.4.1 ..."); |
121 | 121 | |
122 | + case "2.4.1": | |
123 | + log.info("Upgrading ThingsBoard from version 2.4.1 to 2.4.2 ..."); | |
124 | + | |
125 | + databaseUpgradeService.upgradeDatabase("2.4.1"); | |
126 | + | |
122 | 127 | log.info("Updating system data..."); |
123 | 128 | |
124 | 129 | systemDataLoaderService.deleteSystemWidgetBundle("charts"); | ... | ... |
... | ... | @@ -267,6 +267,15 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { |
267 | 267 | } catch (InvalidQueryException e) {} |
268 | 268 | log.info("Schema updated."); |
269 | 269 | break; |
270 | + case "2.4.1": | |
271 | + log.info("Updating schema ..."); | |
272 | + String updateAssetTableStmt = "alter table asset add label text"; | |
273 | + try { | |
274 | + cluster.getSession().execute(updateAssetTableStmt); | |
275 | + Thread.sleep(2500); | |
276 | + } catch (InvalidQueryException e) {} | |
277 | + log.info("Schema updated."); | |
278 | + break; | |
270 | 279 | default: |
271 | 280 | throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion); |
272 | 281 | } | ... | ... |
... | ... | @@ -176,6 +176,15 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { |
176 | 176 | log.info("Schema updated."); |
177 | 177 | } |
178 | 178 | break; |
179 | + case "2.4.1": | |
180 | + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { | |
181 | + log.info("Updating schema ..."); | |
182 | + try { | |
183 | + conn.createStatement().execute("ALTER TABLE asset ADD COLUMN label varchar(255)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script | |
184 | + } catch (Exception e) {} | |
185 | + log.info("Schema updated."); | |
186 | + } | |
187 | + break; | |
179 | 188 | default: |
180 | 189 | throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); |
181 | 190 | } | ... | ... |
... | ... | @@ -31,6 +31,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements |
31 | 31 | private CustomerId customerId; |
32 | 32 | private String name; |
33 | 33 | private String type; |
34 | + private String label; | |
34 | 35 | |
35 | 36 | public Asset() { |
36 | 37 | super(); |
... | ... | @@ -46,6 +47,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements |
46 | 47 | this.customerId = asset.getCustomerId(); |
47 | 48 | this.name = asset.getName(); |
48 | 49 | this.type = asset.getType(); |
50 | + this.label = asset.getLabel(); | |
49 | 51 | } |
50 | 52 | |
51 | 53 | public TenantId getTenantId() { |
... | ... | @@ -81,6 +83,14 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements |
81 | 83 | this.type = type; |
82 | 84 | } |
83 | 85 | |
86 | + public String getLabel() { | |
87 | + return label; | |
88 | + } | |
89 | + | |
90 | + public void setLabel(String label) { | |
91 | + this.label = label; | |
92 | + } | |
93 | + | |
84 | 94 | @Override |
85 | 95 | public String getSearchText() { |
86 | 96 | return getName(); |
... | ... | @@ -97,6 +107,8 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements |
97 | 107 | builder.append(name); |
98 | 108 | builder.append(", type="); |
99 | 109 | builder.append(type); |
110 | + builder.append(", label="); | |
111 | + builder.append(label); | |
100 | 112 | builder.append(", additionalInfo="); |
101 | 113 | builder.append(getAdditionalInfo()); |
102 | 114 | builder.append(", createdTime="); | ... | ... |
... | ... | @@ -197,6 +197,7 @@ public class ModelConstants { |
197 | 197 | public static final String ASSET_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY; |
198 | 198 | public static final String ASSET_NAME_PROPERTY = "name"; |
199 | 199 | public static final String ASSET_TYPE_PROPERTY = "type"; |
200 | + public static final String ASSET_LABEL_PROPERTY = "label"; | |
200 | 201 | public static final String ASSET_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY; |
201 | 202 | |
202 | 203 | public static final String ASSET_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "asset_by_tenant_and_search_text"; | ... | ... |
... | ... | @@ -37,6 +37,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.ASSET_CUSTOMER_ID_ |
37 | 37 | import static org.thingsboard.server.dao.model.ModelConstants.ASSET_NAME_PROPERTY; |
38 | 38 | import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TENANT_ID_PROPERTY; |
39 | 39 | import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TYPE_PROPERTY; |
40 | +import static org.thingsboard.server.dao.model.ModelConstants.ASSET_LABEL_PROPERTY; | |
40 | 41 | import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; |
41 | 42 | import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; |
42 | 43 | |
... | ... | @@ -64,6 +65,9 @@ public final class AssetEntity implements SearchTextEntity<Asset> { |
64 | 65 | @Column(name = ASSET_NAME_PROPERTY) |
65 | 66 | private String name; |
66 | 67 | |
68 | + @Column(name = ASSET_LABEL_PROPERTY) | |
69 | + private String label; | |
70 | + | |
67 | 71 | @Column(name = SEARCH_TEXT_PROPERTY) |
68 | 72 | private String searchText; |
69 | 73 | |
... | ... | @@ -86,6 +90,7 @@ public final class AssetEntity implements SearchTextEntity<Asset> { |
86 | 90 | } |
87 | 91 | this.name = asset.getName(); |
88 | 92 | this.type = asset.getType(); |
93 | + this.label = asset.getLabel(); | |
89 | 94 | this.additionalInfo = asset.getAdditionalInfo(); |
90 | 95 | } |
91 | 96 | |
... | ... | @@ -163,8 +168,9 @@ public final class AssetEntity implements SearchTextEntity<Asset> { |
163 | 168 | } |
164 | 169 | asset.setName(name); |
165 | 170 | asset.setType(type); |
171 | + asset.setLabel(label); | |
166 | 172 | asset.setAdditionalInfo(additionalInfo); |
167 | 173 | return asset; |
168 | 174 | } |
169 | 175 | |
170 | -} | |
\ No newline at end of file | ||
176 | +} | ... | ... |
... | ... | @@ -40,6 +40,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.ASSET_CUSTOMER_ID_ |
40 | 40 | import static org.thingsboard.server.dao.model.ModelConstants.ASSET_NAME_PROPERTY; |
41 | 41 | import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TENANT_ID_PROPERTY; |
42 | 42 | import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TYPE_PROPERTY; |
43 | +import static org.thingsboard.server.dao.model.ModelConstants.ASSET_LABEL_PROPERTY; | |
43 | 44 | import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; |
44 | 45 | |
45 | 46 | @Data |
... | ... | @@ -61,6 +62,9 @@ public final class AssetEntity extends BaseSqlEntity<Asset> implements SearchTex |
61 | 62 | @Column(name = ASSET_TYPE_PROPERTY) |
62 | 63 | private String type; |
63 | 64 | |
65 | + @Column(name = ASSET_LABEL_PROPERTY) | |
66 | + private String label; | |
67 | + | |
64 | 68 | @Column(name = SEARCH_TEXT_PROPERTY) |
65 | 69 | private String searchText; |
66 | 70 | |
... | ... | @@ -84,6 +88,7 @@ public final class AssetEntity extends BaseSqlEntity<Asset> implements SearchTex |
84 | 88 | } |
85 | 89 | this.name = asset.getName(); |
86 | 90 | this.type = asset.getType(); |
91 | + this.label = asset.getLabel(); | |
87 | 92 | this.additionalInfo = asset.getAdditionalInfo(); |
88 | 93 | } |
89 | 94 | |
... | ... | @@ -113,8 +118,9 @@ public final class AssetEntity extends BaseSqlEntity<Asset> implements SearchTex |
113 | 118 | } |
114 | 119 | asset.setName(name); |
115 | 120 | asset.setType(type); |
121 | + asset.setLabel(label); | |
116 | 122 | asset.setAdditionalInfo(additionalInfo); |
117 | 123 | return asset; |
118 | 124 | } |
119 | 125 | |
120 | -} | |
\ No newline at end of file | ||
126 | +} | ... | ... |
... | ... | @@ -244,6 +244,7 @@ CREATE TABLE IF NOT EXISTS thingsboard.asset ( |
244 | 244 | customer_id timeuuid, |
245 | 245 | name text, |
246 | 246 | type text, |
247 | + label text, | |
247 | 248 | search_text text, |
248 | 249 | additional_info text, |
249 | 250 | PRIMARY KEY (id, tenant_id, customer_id, type) |
... | ... | @@ -711,4 +712,4 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_ent |
711 | 712 | AND search_text IS NOT NULL |
712 | 713 | AND id IS NOT NULL |
713 | 714 | PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id, type) |
714 | - WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC); | |
\ No newline at end of file | ||
715 | + WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC); | ... | ... |
... | ... | @@ -1130,19 +1130,12 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
1130 | 1130 | let statisticalInfo = {}; |
1131 | 1131 | let newEntity = { |
1132 | 1132 | name: entityParameters.name, |
1133 | - type: entityParameters.type | |
1133 | + type: entityParameters.type, | |
1134 | + label: entityParameters.label | |
1134 | 1135 | }; |
1135 | - let promise; | |
1136 | - switch (entityType) { | |
1137 | - case types.entityType.device: | |
1138 | - promise = deviceService.saveDevice(newEntity, config); | |
1139 | - break; | |
1140 | - case types.entityType.asset: | |
1141 | - promise = assetService.saveAsset(newEntity, true, config); | |
1142 | - break; | |
1143 | - } | |
1136 | + let saveEntityPromise = getEntitySavePromise(entityType, newEntity, config); | |
1144 | 1137 | |
1145 | - promise.then(function success(response) { | |
1138 | + saveEntityPromise.then(function success(response) { | |
1146 | 1139 | saveEntityRelation(entityType, response.id, entityParameters, config).then(function success() { |
1147 | 1140 | statisticalInfo.create = { |
1148 | 1141 | entity: 1 |
... | ... | @@ -1166,7 +1159,15 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
1166 | 1159 | break; |
1167 | 1160 | } |
1168 | 1161 | findIdEntity.then(function success(response) { |
1169 | - saveEntityRelation(entityType, response.id, entityParameters, config).then(function success() { | |
1162 | + let promises = []; | |
1163 | + if(response.label !== entityParameters.label || response.type !== entityParameters.type){ | |
1164 | + response.label = entityParameters.label; | |
1165 | + response.type = entityParameters.type; | |
1166 | + promises.push(getEntitySavePromise(entityType, response, config)); | |
1167 | + } | |
1168 | + promises.push(saveEntityRelation(entityType, response.id, entityParameters, config)); | |
1169 | + | |
1170 | + $q.all(promises).then(function success() { | |
1170 | 1171 | statisticalInfo.update = { |
1171 | 1172 | entity: 1 |
1172 | 1173 | }; |
... | ... | @@ -1193,6 +1194,19 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
1193 | 1194 | return deferred.promise; |
1194 | 1195 | } |
1195 | 1196 | |
1197 | + function getEntitySavePromise(entityType, newEntity, config) { | |
1198 | + let promise; | |
1199 | + switch (entityType) { | |
1200 | + case types.entityType.device: | |
1201 | + promise = deviceService.saveDevice(newEntity, config); | |
1202 | + break; | |
1203 | + case types.entityType.asset: | |
1204 | + promise = assetService.saveAsset(newEntity, true, config); | |
1205 | + break; | |
1206 | + } | |
1207 | + return promise; | |
1208 | + } | |
1209 | + | |
1196 | 1210 | function getRelatedEntity(entityId, keys, typeTranslatePrefix) { |
1197 | 1211 | var deferred = $q.defer(); |
1198 | 1212 | getEntityPromise(entityId.entityType, entityId.id, {ignoreLoading: true}).then( | ... | ... |
... | ... | @@ -64,6 +64,10 @@ |
64 | 64 | entity-type="types.entityType.asset"> |
65 | 65 | </tb-entity-subtype-autocomplete> |
66 | 66 | <md-input-container class="md-block"> |
67 | + <label translate>asset.label</label> | |
68 | + <input name="label" ng-model="asset.label"> | |
69 | + </md-input-container> | |
70 | + <md-input-container class="md-block"> | |
67 | 71 | <label translate>asset.description</label> |
68 | 72 | <textarea ng-model="asset.additionalInfo.description" rows="2"></textarea> |
69 | 73 | </md-input-container> | ... | ... |
... | ... | @@ -369,6 +369,10 @@ export default angular.module('thingsboard.types', []) |
369 | 369 | name: 'import.column-type.type', |
370 | 370 | value: 'type' |
371 | 371 | }, |
372 | + label: { | |
373 | + name: 'import.column-type.label', | |
374 | + value: 'label' | |
375 | + }, | |
372 | 376 | clientAttribute: { |
373 | 377 | name: 'import.column-type.client-attribute', |
374 | 378 | value: 'CLIENT_ATTRIBUTE' | ... | ... |
... | ... | @@ -98,7 +98,7 @@ export default function ImportDialogCsvController($scope, $mdDialog, toast, impo |
98 | 98 | vm.columnsParam = []; |
99 | 99 | var columnParam = {}; |
100 | 100 | for (var i = 0; i < parseData.headers.length; i++) { |
101 | - if (vm.importParameters.isHeader && parseData.headers[i].search(/^(name|type)$/im) === 0) { | |
101 | + if (vm.importParameters.isHeader && parseData.headers[i].search(/^(name|type|label)$/im) === 0) { | |
102 | 102 | columnParam = { |
103 | 103 | type: types.importEntityColumnType[parseData.headers[i].toLowerCase()].value, |
104 | 104 | key: parseData.headers[i].toLowerCase(), |
... | ... | @@ -126,6 +126,7 @@ export default function ImportDialogCsvController($scope, $mdDialog, toast, impo |
126 | 126 | var entityData = { |
127 | 127 | name: "", |
128 | 128 | type: "", |
129 | + label: "", | |
129 | 130 | accessToken: "", |
130 | 131 | attributes: { |
131 | 132 | server: [], |
... | ... | @@ -162,6 +163,9 @@ export default function ImportDialogCsvController($scope, $mdDialog, toast, impo |
162 | 163 | case types.importEntityColumnType.type.value: |
163 | 164 | entityData.type = importData.rows[i][j]; |
164 | 165 | break; |
166 | + case types.importEntityColumnType.label.value: | |
167 | + entityData.label = importData.rows[i][j]; | |
168 | + break; | |
165 | 169 | } |
166 | 170 | } |
167 | 171 | entitiesData.push(entityData); | ... | ... |
... | ... | @@ -44,6 +44,7 @@ function TableColumnsAssignmentController($scope, types, $timeout) { |
44 | 44 | |
45 | 45 | vm.columnTypes.name = types.importEntityColumnType.name; |
46 | 46 | vm.columnTypes.type = types.importEntityColumnType.type; |
47 | + vm.columnTypes.label = types.importEntityColumnType.label; | |
47 | 48 | |
48 | 49 | switch (vm.entityType) { |
49 | 50 | case types.entityType.device: |
... | ... | @@ -62,6 +63,7 @@ function TableColumnsAssignmentController($scope, types, $timeout) { |
62 | 63 | if (newVal) { |
63 | 64 | var isSelectName = false; |
64 | 65 | var isSelectType = false; |
66 | + var isSelectLabel = false; | |
65 | 67 | var isSelectCredentials = false; |
66 | 68 | for (var i = 0; i < newVal.length; i++) { |
67 | 69 | switch (newVal[i].type) { |
... | ... | @@ -71,6 +73,9 @@ function TableColumnsAssignmentController($scope, types, $timeout) { |
71 | 73 | case types.importEntityColumnType.type.value: |
72 | 74 | isSelectType = true; |
73 | 75 | break; |
76 | + case types.importEntityColumnType.label.value: | |
77 | + isSelectLabel = true; | |
78 | + break; | |
74 | 79 | case types.importEntityColumnType.accessToken.value: |
75 | 80 | isSelectCredentials = true; |
76 | 81 | break; |
... | ... | @@ -84,6 +89,7 @@ function TableColumnsAssignmentController($scope, types, $timeout) { |
84 | 89 | $timeout(function () { |
85 | 90 | vm.columnTypes.name.disable = isSelectName; |
86 | 91 | vm.columnTypes.type.disable = isSelectType; |
92 | + vm.columnTypes.label.disable = isSelectLabel; | |
87 | 93 | if (angular.isDefined(vm.columnTypes.accessToken)) { |
88 | 94 | vm.columnTypes.accessToken.disable = isSelectCredentials; |
89 | 95 | } | ... | ... |
... | ... | @@ -41,6 +41,7 @@ |
41 | 41 | <md-input-container md-no-float |
42 | 42 | ng-if="column.type != vm.columnTypes.name.value && |
43 | 43 | column.type != vm.columnTypes.type.value && |
44 | + column.type != vm.columnTypes.label.value && | |
44 | 45 | column.type != vm.columnTypes.accessToken.value"> |
45 | 46 | <input required name="columnKeyName" |
46 | 47 | placeholder="{{ 'import.column-value' | translate }}" | ... | ... |
... | ... | @@ -246,7 +246,8 @@ |
246 | 246 | "select-asset": "Vybrat aktivum", |
247 | 247 | "no-assets-matching": "Žádná aktiva odpovídající '{{entity}}' nebyla nalezena.", |
248 | 248 | "asset-required": "Aktivum je povinné", |
249 | - "name-starts-with": "Název aktiva začíná" | |
249 | + "name-starts-with": "Název aktiva začíná", | |
250 | + "label": "Název" | |
250 | 251 | }, |
251 | 252 | "attribute": { |
252 | 253 | "attributes": "Atributy", | ... | ... |
... | ... | @@ -267,7 +267,8 @@ |
267 | 267 | "select-asset": "Objekt auswählen", |
268 | 268 | "no-assets-matching": "Es wurden keine zu '{{entity}}' passenden Objekte gefunden.", |
269 | 269 | "asset-required": "Objekt ist erforderlich", |
270 | - "name-starts-with": "Name des Objekts beginnt mit" | |
270 | + "name-starts-with": "Name des Objekts beginnt mit", | |
271 | + "label": "Bezeichnung" | |
271 | 272 | }, |
272 | 273 | "attribute": { |
273 | 274 | "attributes": "Eigenschaften", | ... | ... |
... | ... | @@ -271,7 +271,8 @@ |
271 | 271 | "asset-required": "Asset is required", |
272 | 272 | "name-starts-with": "Asset name starts with", |
273 | 273 | "import": "Import assets", |
274 | - "asset-file": "Asset file" | |
274 | + "asset-file": "Asset file", | |
275 | + "label": "Label" | |
275 | 276 | }, |
276 | 277 | "attribute": { |
277 | 278 | "attributes": "Attributes", |
... | ... | @@ -1139,6 +1140,7 @@ |
1139 | 1140 | "column-type": { |
1140 | 1141 | "name": "Name", |
1141 | 1142 | "type": "Type", |
1143 | + "label": "Label", | |
1142 | 1144 | "column-type": "Column type", |
1143 | 1145 | "client-attribute": "Client attribute", |
1144 | 1146 | "shared-attribute": "Shared attribute", | ... | ... |
... | ... | @@ -271,7 +271,8 @@ |
271 | 271 | "asset-required": "El activo es requerido", |
272 | 272 | "name-starts-with": "El nombre del activo comienza con", |
273 | 273 | "import": "Importar activos", |
274 | - "asset-file": "Archivo del activo" | |
274 | + "asset-file": "Archivo del activo", | |
275 | + "label": "Etiqueta" | |
275 | 276 | }, |
276 | 277 | "attribute": { |
277 | 278 | "attributes": "Atributos", | ... | ... |
... | ... | @@ -246,7 +246,8 @@ |
246 | 246 | "select-asset": "انتخاب دارايي", |
247 | 247 | "no-assets-matching": ".يافت نشد '{{entity}}' هيچ دارايي منطبق بر", |
248 | 248 | "asset-required": "دارايي مود نياز است", |
249 | - "name-starts-with": "نام دارايي شروع مي شود با" | |
249 | + "name-starts-with": "نام دارايي شروع مي شود با", | |
250 | + "label": "برچسب" | |
250 | 251 | }, |
251 | 252 | "attribute": { |
252 | 253 | "attributes": "ويژگي ها", | ... | ... |
... | ... | @@ -271,7 +271,8 @@ |
271 | 271 | "unassign-assets-text": "Après la confirmation, tous les actifs sélectionnés ne seront pas attribués et ne seront pas accessibles au client.", |
272 | 272 | "unassign-assets-title": "Êtes-vous sûr de vouloir retirer l'attribution de {count, plural, 1 {1 asset} other {# assets}}?", |
273 | 273 | "unassign-from-customer": "Retirer du client", |
274 | - "view-assets": "Afficher les actifs" | |
274 | + "view-assets": "Afficher les actifs", | |
275 | + "label": "Label" | |
275 | 276 | }, |
276 | 277 | "attribute": { |
277 | 278 | "add": "Ajouter un attribut", | ... | ... |
... | ... | @@ -268,7 +268,8 @@ |
268 | 268 | "select-asset": "Seleziona asset", |
269 | 269 | "no-assets-matching": "Nessun asset corrispondente a '{{entity}}' è stato trovato.", |
270 | 270 | "asset-required": "Asset obbligatorio", |
271 | - "name-starts-with": "Asset con nome che inizia per" | |
271 | + "name-starts-with": "Asset con nome che inizia per", | |
272 | + "label": "Etichetta" | |
272 | 273 | }, |
273 | 274 | "attribute": { |
274 | 275 | "attributes": "Attributi", | ... | ... |
... | ... | @@ -236,7 +236,8 @@ |
236 | 236 | "select-asset": "アセットを選択", |
237 | 237 | "no-assets-matching": "'{{entity}}'発見されました。", |
238 | 238 | "asset-required": "資産が必要です", |
239 | - "name-starts-with": "アセット名はで始まります" | |
239 | + "name-starts-with": "アセット名はで始まります", | |
240 | + "label": "ラベル" | |
240 | 241 | }, |
241 | 242 | "attribute": { |
242 | 243 | "attributes": "属性", | ... | ... |
... | ... | @@ -250,7 +250,8 @@ |
250 | 250 | "asset-required": "Актив обязателен", |
251 | 251 | "name-starts-with": "Название актива, начинающееся с", |
252 | 252 | "import": "Импортировать активы", |
253 | - "asset-file": "Файл с активами" | |
253 | + "asset-file": "Файл с активами", | |
254 | + "label": "Метка" | |
254 | 255 | }, |
255 | 256 | "attribute": { |
256 | 257 | "attributes": "Атрибуты", |
... | ... | @@ -1113,6 +1114,7 @@ |
1113 | 1114 | "column-type": { |
1114 | 1115 | "name": "Название", |
1115 | 1116 | "type": "Тип", |
1117 | + "label": "Метка", | |
1116 | 1118 | "column-type": "Тип колонки", |
1117 | 1119 | "client-attribute": "Клиентский атрибут", |
1118 | 1120 | "shared-attribute": "Общий атрибут", | ... | ... |
... | ... | @@ -236,7 +236,8 @@ |
236 | 236 | "select-asset": "Varlık seç", |
237 | 237 | "no-assets-matching": "'{{entity}}' isimli varlık bulunamadı.", |
238 | 238 | "asset-required": "Varlık gerekli", |
239 | - "name-starts-with": "... ile başlayan varlık adı" | |
239 | + "name-starts-with": "... ile başlayan varlık adı", | |
240 | + "label": "Etiket" | |
240 | 241 | }, |
241 | 242 | "attribute": { |
242 | 243 | "attributes": "Öznitelikler", | ... | ... |
... | ... | @@ -280,7 +280,8 @@ |
280 | 280 | "list-of-groups": "{ count, plural, 1 {Одна група активів} other {Список # груп активів} }", |
281 | 281 | "group-name-starts-with": "Групи активів, чиї імена починаються з '{{prefix}}'", |
282 | 282 | "import": "Імпортувати активи", |
283 | - "asset-file": "Файл з активами" | |
283 | + "asset-file": "Файл з активами", | |
284 | + "label": "Мітка" | |
284 | 285 | }, |
285 | 286 | "attribute": { |
286 | 287 | "attributes": "Атрибути", |
... | ... | @@ -1365,6 +1366,7 @@ |
1365 | 1366 | "column-type": { |
1366 | 1367 | "name": "Назва", |
1367 | 1368 | "type": "Тип", |
1369 | + "label": "Мітка", | |
1368 | 1370 | "column-type": "Тип колонки", |
1369 | 1371 | "client-attribute": "Атрибут клієнта", |
1370 | 1372 | "shared-attribute": "Спільний атрибут", |
... | ... | @@ -2343,4 +2345,4 @@ |
2343 | 2345 | "cs_CZ": "Чеська" |
2344 | 2346 | } |
2345 | 2347 | } |
2346 | -} | |
2348 | +} | |
\ No newline at end of file | ... | ... |
... | ... | @@ -241,7 +241,8 @@ |
241 | 241 | "select-asset": "选择资产", |
242 | 242 | "no-assets-matching": "没有找到匹配 '{{entity}}' 的资产。", |
243 | 243 | "asset-required": "资产必填", |
244 | - "name-starts-with": "资产名称以此开头" | |
244 | + "name-starts-with": "资产名称以此开头", | |
245 | + "label": "标签" | |
245 | 246 | }, |
246 | 247 | "attribute": { |
247 | 248 | "attributes": "属性", | ... | ... |