Showing
18 changed files
with
697 additions
and
32 deletions
@@ -112,11 +112,10 @@ public class FirmwareController extends BaseController { | @@ -112,11 +112,10 @@ public class FirmwareController extends BaseController { | ||
112 | @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.POST) | 112 | @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.POST) |
113 | @ResponseBody | 113 | @ResponseBody |
114 | public Firmware saveFirmwareData(@PathVariable(FIRMWARE_ID) String strFirmwareId, | 114 | public Firmware saveFirmwareData(@PathVariable(FIRMWARE_ID) String strFirmwareId, |
115 | - @RequestParam String checksum, | 115 | + @RequestParam(required = false) String checksum, |
116 | @RequestParam(required = false) String checksumAlgorithm, | 116 | @RequestParam(required = false) String checksumAlgorithm, |
117 | - @RequestBody MultipartFile firmwareFile) throws ThingsboardException { | 117 | + @RequestBody MultipartFile file) throws ThingsboardException { |
118 | checkParameter(FIRMWARE_ID, strFirmwareId); | 118 | checkParameter(FIRMWARE_ID, strFirmwareId); |
119 | - checkParameter("checksum", checksum); | ||
120 | try { | 119 | try { |
121 | FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId)); | 120 | FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId)); |
122 | FirmwareInfo info = checkFirmwareInfoId(firmwareId, Operation.READ); | 121 | FirmwareInfo info = checkFirmwareInfoId(firmwareId, Operation.READ); |
@@ -130,9 +129,9 @@ public class FirmwareController extends BaseController { | @@ -130,9 +129,9 @@ public class FirmwareController extends BaseController { | ||
130 | 129 | ||
131 | firmware.setChecksumAlgorithm(checksumAlgorithm); | 130 | firmware.setChecksumAlgorithm(checksumAlgorithm); |
132 | firmware.setChecksum(checksum); | 131 | firmware.setChecksum(checksum); |
133 | - firmware.setFileName(firmwareFile.getOriginalFilename()); | ||
134 | - firmware.setContentType(firmwareFile.getContentType()); | ||
135 | - firmware.setData(ByteBuffer.wrap(firmwareFile.getBytes())); | 132 | + firmware.setFileName(file.getOriginalFilename()); |
133 | + firmware.setContentType(file.getContentType()); | ||
134 | + firmware.setData(ByteBuffer.wrap(file.getBytes())); | ||
136 | return firmwareService.saveFirmware(firmware); | 135 | return firmwareService.saveFirmware(firmware); |
137 | } catch (Exception e) { | 136 | } catch (Exception e) { |
138 | throw handleException(e); | 137 | throw handleException(e); |
@@ -186,4 +185,4 @@ public class FirmwareController extends BaseController { | @@ -186,4 +185,4 @@ public class FirmwareController extends BaseController { | ||
186 | } | 185 | } |
187 | } | 186 | } |
188 | 187 | ||
189 | -} | ||
188 | +} |
@@ -206,16 +206,16 @@ public class BaseFirmwareService implements FirmwareService { | @@ -206,16 +206,16 @@ public class BaseFirmwareService implements FirmwareService { | ||
206 | throw new DataValidationException("Firmware content type should be specified!"); | 206 | throw new DataValidationException("Firmware content type should be specified!"); |
207 | } | 207 | } |
208 | 208 | ||
209 | - if (StringUtils.isEmpty(firmware.getChecksum())) { | ||
210 | - throw new DataValidationException("Firmware checksum should be specified!"); | ||
211 | - } | ||
212 | - | ||
213 | ByteBuffer data = firmware.getData(); | 209 | ByteBuffer data = firmware.getData(); |
214 | if (data == null || !data.hasArray() || data.array().length == 0) { | 210 | if (data == null || !data.hasArray() || data.array().length == 0) { |
215 | throw new DataValidationException("Firmware data should be specified!"); | 211 | throw new DataValidationException("Firmware data should be specified!"); |
216 | } | 212 | } |
217 | 213 | ||
218 | if (firmware.getChecksumAlgorithm() != null) { | 214 | if (firmware.getChecksumAlgorithm() != null) { |
215 | + if (StringUtils.isEmpty(firmware.getChecksum())) { | ||
216 | + throw new DataValidationException("Firmware checksum should be specified!"); | ||
217 | + } | ||
218 | + | ||
219 | HashFunction hashFunction; | 219 | HashFunction hashFunction; |
220 | switch (firmware.getChecksumAlgorithm()) { | 220 | switch (firmware.getChecksumAlgorithm()) { |
221 | case "sha256": | 221 | case "sha256": |
@@ -232,7 +232,6 @@ public class BaseFirmwareService implements FirmwareService { | @@ -232,7 +232,6 @@ public class BaseFirmwareService implements FirmwareService { | ||
232 | } | 232 | } |
233 | 233 | ||
234 | String currentChecksum = hashFunction.hashBytes(data.array()).toString(); | 234 | String currentChecksum = hashFunction.hashBytes(data.array()).toString(); |
235 | - ; | ||
236 | 235 | ||
237 | if (!currentChecksum.equals(firmware.getChecksum())) { | 236 | if (!currentChecksum.equals(firmware.getChecksum())) { |
238 | throw new DataValidationException("Wrong firmware file!"); | 237 | throw new DataValidationException("Wrong firmware file!"); |
ui-ngx/src/app/core/http/firmware.service.ts
0 → 100644
1 | +/// | ||
2 | +/// Copyright © 2016-2021 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { Injectable } from '@angular/core'; | ||
18 | +import { HttpClient } from '@angular/common/http'; | ||
19 | +import { PageLink } from '@shared/models/page/page-link'; | ||
20 | +import { defaultHttpOptionsFromConfig, defaultHttpUploadOptions, RequestConfig } from '@core/http/http-utils'; | ||
21 | +import { Observable } from 'rxjs'; | ||
22 | +import { PageData } from '@shared/models/page/page-data'; | ||
23 | +import { Firmware, FirmwareInfo } from '@shared/models/firmware.models'; | ||
24 | +import { catchError, map, mergeMap } from 'rxjs/operators'; | ||
25 | +import { deepClone, isDefinedAndNotNull } from '@core/utils'; | ||
26 | +import { InterceptorHttpParams } from '@core/interceptors/interceptor-http-params'; | ||
27 | +import { InterceptorConfig } from '@core/interceptors/interceptor-config'; | ||
28 | + | ||
29 | +@Injectable({ | ||
30 | + providedIn: 'root' | ||
31 | +}) | ||
32 | +export class FirmwareService { | ||
33 | + constructor( | ||
34 | + private http: HttpClient | ||
35 | + ) { | ||
36 | + | ||
37 | + } | ||
38 | + | ||
39 | + public getFirmwares(pageLink: PageLink, hasData?: boolean, config?: RequestConfig): Observable<PageData<FirmwareInfo>> { | ||
40 | + let url = `/api/firmwares${pageLink.toQuery()}`; | ||
41 | + if (isDefinedAndNotNull(hasData)) { | ||
42 | + url += `&hasData=${hasData}`; | ||
43 | + } | ||
44 | + return this.http.get<PageData<FirmwareInfo>>(url, defaultHttpOptionsFromConfig(config)); | ||
45 | + } | ||
46 | + | ||
47 | + public getFirmware(firmwareId: string, config?: RequestConfig): Observable<Firmware> { | ||
48 | + return this.http.get<Firmware>(`/api/firmware/${firmwareId}`, defaultHttpOptionsFromConfig(config)); | ||
49 | + } | ||
50 | + | ||
51 | + public getFirmwareInfo(firmwareId: string, config?: RequestConfig): Observable<FirmwareInfo> { | ||
52 | + return this.http.get<FirmwareInfo>(`/api/firmware/info/${firmwareId}`, defaultHttpOptionsFromConfig(config)); | ||
53 | + } | ||
54 | + | ||
55 | + public downloadFirmware(firmwareId: string): Observable<any> { | ||
56 | + return this.http.get(`/api/firmware/${firmwareId}/download`, { responseType: 'arraybuffer', observe: 'response' }).pipe( | ||
57 | + map((response) => { | ||
58 | + const headers = response.headers; | ||
59 | + const filename = headers.get('x-filename'); | ||
60 | + const contentType = headers.get('content-type'); | ||
61 | + const linkElement = document.createElement('a'); | ||
62 | + try { | ||
63 | + const blob = new Blob([response.body], { type: contentType }); | ||
64 | + const url = URL.createObjectURL(blob); | ||
65 | + linkElement.setAttribute('href', url); | ||
66 | + linkElement.setAttribute('download', filename); | ||
67 | + const clickEvent = new MouseEvent('click', | ||
68 | + { | ||
69 | + view: window, | ||
70 | + bubbles: true, | ||
71 | + cancelable: false | ||
72 | + } | ||
73 | + ); | ||
74 | + linkElement.dispatchEvent(clickEvent); | ||
75 | + return null; | ||
76 | + } catch (e) { | ||
77 | + throw e; | ||
78 | + } | ||
79 | + }) | ||
80 | + ); | ||
81 | + } | ||
82 | + | ||
83 | + public saveFirmware(firmware: Firmware, config?: RequestConfig): Observable<Firmware> { | ||
84 | + if (!firmware.file) { | ||
85 | + return this.saveFirmwareInfo(firmware, config); | ||
86 | + } | ||
87 | + const firmwareInfo = deepClone(firmware); | ||
88 | + delete firmwareInfo.file; | ||
89 | + delete firmwareInfo.checksum; | ||
90 | + delete firmwareInfo.checksumAlgorithm; | ||
91 | + return this.saveFirmwareInfo(firmwareInfo, config).pipe( | ||
92 | + mergeMap(res => { | ||
93 | + return this.uploadFirmwareFile(res.id.id, firmware.file, firmware.checksumAlgorithm, firmware.checksum).pipe( | ||
94 | + catchError(() => this.deleteFirmware(res.id.id)) | ||
95 | + ); | ||
96 | + }) | ||
97 | + ); | ||
98 | + } | ||
99 | + | ||
100 | + public saveFirmwareInfo(firmware: FirmwareInfo, config?: RequestConfig): Observable<Firmware> { | ||
101 | + return this.http.post<Firmware>('/api/firmware', firmware, defaultHttpOptionsFromConfig(config)); | ||
102 | + } | ||
103 | + | ||
104 | + public uploadFirmwareFile(firmwareId: string, file: File, checksumAlgorithm?: string, | ||
105 | + checksum?: string, config?: RequestConfig): Observable<any> { | ||
106 | + if (!config) { | ||
107 | + config = {}; | ||
108 | + } | ||
109 | + const formData = new FormData(); | ||
110 | + formData.append('file', file); | ||
111 | + let url = `/api/firmware/${firmwareId}`; | ||
112 | + if (checksumAlgorithm && checksum) { | ||
113 | + url += `?checksumAlgorithm=${checksumAlgorithm}&checksum=${checksum}`; | ||
114 | + } | ||
115 | + return this.http.post(url, formData, | ||
116 | + defaultHttpUploadOptions(config.ignoreLoading, config.ignoreErrors, config.resendRequest)); | ||
117 | + } | ||
118 | + | ||
119 | + public deleteFirmware(firmwareId: string, config?: RequestConfig) { | ||
120 | + return this.http.delete(`/api/firmware/${firmwareId}`, defaultHttpOptionsFromConfig(config)); | ||
121 | + } | ||
122 | + | ||
123 | +} |
@@ -39,3 +39,11 @@ export function defaultHttpOptions(ignoreLoading: boolean = false, | @@ -39,3 +39,11 @@ export function defaultHttpOptions(ignoreLoading: boolean = false, | ||
39 | params: new InterceptorHttpParams(new InterceptorConfig(ignoreLoading, ignoreErrors, resendRequest)) | 39 | params: new InterceptorHttpParams(new InterceptorConfig(ignoreLoading, ignoreErrors, resendRequest)) |
40 | }; | 40 | }; |
41 | } | 41 | } |
42 | + | ||
43 | +export function defaultHttpUploadOptions(ignoreLoading: boolean = false, | ||
44 | + ignoreErrors: boolean = false, | ||
45 | + resendRequest: boolean = false) { | ||
46 | + return { | ||
47 | + params: new InterceptorHttpParams(new InterceptorConfig(ignoreLoading, ignoreErrors, resendRequest)) | ||
48 | + }; | ||
49 | +} |
@@ -281,6 +281,13 @@ export class MenuService { | @@ -281,6 +281,13 @@ export class MenuService { | ||
281 | }, | 281 | }, |
282 | { | 282 | { |
283 | id: guid(), | 283 | id: guid(), |
284 | + name: 'firmware.firmware', | ||
285 | + type: 'link', | ||
286 | + path: '/firmwares', | ||
287 | + icon: 'memory' | ||
288 | + }, | ||
289 | + { | ||
290 | + id: guid(), | ||
284 | name: 'entity-view.entity-views', | 291 | name: 'entity-view.entity-views', |
285 | type: 'link', | 292 | type: 'link', |
286 | path: '/entityViews', | 293 | path: '/entityViews', |
@@ -379,6 +386,11 @@ export class MenuService { | @@ -379,6 +386,11 @@ export class MenuService { | ||
379 | icon: 'mdi:alpha-d-box', | 386 | icon: 'mdi:alpha-d-box', |
380 | isMdiIcon: true, | 387 | isMdiIcon: true, |
381 | path: '/deviceProfiles' | 388 | path: '/deviceProfiles' |
389 | + }, | ||
390 | + { | ||
391 | + name: 'firmware.firmware', | ||
392 | + icon: 'memory', | ||
393 | + path: '/firmwares' | ||
382 | } | 394 | } |
383 | ] | 395 | ] |
384 | }, | 396 | }, |
@@ -407,7 +407,7 @@ export function sortObjectKeys<T>(obj: T): T { | @@ -407,7 +407,7 @@ export function sortObjectKeys<T>(obj: T): T { | ||
407 | } | 407 | } |
408 | 408 | ||
409 | export function deepTrim<T>(obj: T): T { | 409 | export function deepTrim<T>(obj: T): T { |
410 | - if (isNumber(obj) || isUndefined(obj) || isString(obj) || obj === null) { | 410 | + if (isNumber(obj) || isUndefined(obj) || isString(obj) || obj === null || obj instanceof File) { |
411 | return obj; | 411 | return obj; |
412 | } | 412 | } |
413 | return Object.keys(obj).reduce((acc, curr) => { | 413 | return Object.keys(obj).reduce((acc, curr) => { |
@@ -172,7 +172,7 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P | @@ -172,7 +172,7 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P | ||
172 | deleteEntityContent: EntityStringFunction<L> = () => ''; | 172 | deleteEntityContent: EntityStringFunction<L> = () => ''; |
173 | deleteEntitiesTitle: EntityCountStringFunction = () => ''; | 173 | deleteEntitiesTitle: EntityCountStringFunction = () => ''; |
174 | deleteEntitiesContent: EntityCountStringFunction = () => ''; | 174 | deleteEntitiesContent: EntityCountStringFunction = () => ''; |
175 | - loadEntity: EntityByIdOperation<T> = () => of(); | 175 | + loadEntity: EntityByIdOperation<T | L> = () => of(); |
176 | saveEntity: EntityTwoWayOperation<T> = (entity) => of(entity); | 176 | saveEntity: EntityTwoWayOperation<T> = (entity) => of(entity); |
177 | deleteEntity: EntityIdOneWayOperation = () => of(); | 177 | deleteEntity: EntityIdOneWayOperation = () => of(); |
178 | entitiesFetchFunction: EntitiesFetchFunction<L, P> = () => of(emptyPageData<L>()); | 178 | entitiesFetchFunction: EntitiesFetchFunction<L, P> = () => of(emptyPageData<L>()); |
1 | +/// | ||
2 | +/// Copyright © 2016-2021 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { RouterModule, Routes } from '@angular/router'; | ||
18 | +import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; | ||
19 | +import { Authority } from '@shared/models/authority.enum'; | ||
20 | +import { NgModule } from '@angular/core'; | ||
21 | +import { FirmwareTableConfigResolve } from '@home/pages/firmware/firmware-table-config.resolve'; | ||
22 | + | ||
23 | +const routes: Routes = [ | ||
24 | + { | ||
25 | + path: 'firmwares', | ||
26 | + component: EntitiesTableComponent, | ||
27 | + data: { | ||
28 | + auth: [Authority.TENANT_ADMIN], | ||
29 | + title: 'firmware.firmware', | ||
30 | + breadcrumb: { | ||
31 | + label: 'firmware.firmware', | ||
32 | + icon: 'memory' | ||
33 | + } | ||
34 | + }, | ||
35 | + resolve: { | ||
36 | + entitiesTableConfig: FirmwareTableConfigResolve | ||
37 | + } | ||
38 | + } | ||
39 | +]; | ||
40 | + | ||
41 | +@NgModule({ | ||
42 | + imports: [RouterModule.forChild(routes)], | ||
43 | + exports: [RouterModule], | ||
44 | + providers: [ | ||
45 | + FirmwareTableConfigResolve | ||
46 | + ] | ||
47 | +}) | ||
48 | +export class FirmwareRoutingModule{ } |
1 | +/// | ||
2 | +/// Copyright © 2016-2021 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { Injectable } from '@angular/core'; | ||
18 | +import { Resolve } from '@angular/router'; | ||
19 | +import { | ||
20 | + DateEntityTableColumn, | ||
21 | + EntityTableColumn, | ||
22 | + EntityTableConfig | ||
23 | +} from '@home/models/entity/entities-table-config.models'; | ||
24 | +import { Firmware, FirmwareInfo } from '@shared/models/firmware.models'; | ||
25 | +import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models'; | ||
26 | +import { TranslateService } from '@ngx-translate/core'; | ||
27 | +import { DatePipe } from '@angular/common'; | ||
28 | +import { FirmwareService } from '@core/http/firmware.service'; | ||
29 | +import { PageLink } from '@shared/models/page/page-link'; | ||
30 | +import { FirmwaresComponent } from '@home/pages/firmware/firmwares.component'; | ||
31 | +import { EntityAction } from '@home/models/entity/entity-component.models'; | ||
32 | +import { DeviceInfo } from '@shared/models/device.models'; | ||
33 | + | ||
34 | +@Injectable() | ||
35 | +export class FirmwareTableConfigResolve implements Resolve<EntityTableConfig<Firmware, PageLink, FirmwareInfo>> { | ||
36 | + | ||
37 | + private readonly config: EntityTableConfig<Firmware, PageLink, FirmwareInfo> = new EntityTableConfig<Firmware, PageLink, FirmwareInfo>(); | ||
38 | + | ||
39 | + constructor(private translate: TranslateService, | ||
40 | + private datePipe: DatePipe, | ||
41 | + private firmwareService: FirmwareService) { | ||
42 | + this.config.entityType = EntityType.FIRMWARE; | ||
43 | + this.config.entityComponent = FirmwaresComponent; | ||
44 | + this.config.entityTranslations = entityTypeTranslations.get(EntityType.FIRMWARE); | ||
45 | + this.config.entityResources = entityTypeResources.get(EntityType.FIRMWARE); | ||
46 | + | ||
47 | + this.config.entityTitle = (firmware) => firmware ? firmware.title : ''; | ||
48 | + | ||
49 | + this.config.columns.push( | ||
50 | + new DateEntityTableColumn<FirmwareInfo>('createdTime', 'common.created-time', this.datePipe, '150px'), | ||
51 | + new EntityTableColumn<FirmwareInfo>('title', 'firmware.title', '50%'), | ||
52 | + new EntityTableColumn<FirmwareInfo>('version', 'firmware.version', '50%') | ||
53 | + ); | ||
54 | + | ||
55 | + this.config.cellActionDescriptors.push( | ||
56 | + { | ||
57 | + name: this.translate.instant('firmware.export'), | ||
58 | + icon: 'file_download', | ||
59 | + isEnabled: (firmware) => firmware.hasData, | ||
60 | + onAction: ($event, entity) => this.exportFirmware($event, entity) | ||
61 | + } | ||
62 | + ); | ||
63 | + | ||
64 | + this.config.deleteEntityTitle = firmware => this.translate.instant('firmware.delete-firmware-title', | ||
65 | + { firmwareTitle: firmware.title }); | ||
66 | + this.config.deleteEntityContent = () => this.translate.instant('firmware.delete-firmware-text'); | ||
67 | + this.config.deleteEntitiesTitle = count => this.translate.instant('firmware.delete-firmwares-title', {count}); | ||
68 | + this.config.deleteEntitiesContent = () => this.translate.instant('firmware.delete-firmwares-text'); | ||
69 | + | ||
70 | + this.config.entitiesFetchFunction = pageLink => this.firmwareService.getFirmwares(pageLink); | ||
71 | + this.config.loadEntity = id => this.firmwareService.getFirmwareInfo(id.id); | ||
72 | + this.config.saveEntity = firmware => this.firmwareService.saveFirmware(firmware); | ||
73 | + this.config.deleteEntity = id => this.firmwareService.deleteFirmware(id.id); | ||
74 | + | ||
75 | + this.config.onEntityAction = action => this.onFirmwareAction(action); | ||
76 | + } | ||
77 | + | ||
78 | + resolve(): EntityTableConfig<Firmware, PageLink, FirmwareInfo> { | ||
79 | + this.config.tableTitle = this.translate.instant('firmware.firmware'); | ||
80 | + return this.config; | ||
81 | + } | ||
82 | + | ||
83 | + exportFirmware($event: Event, firmware: FirmwareInfo) { | ||
84 | + if ($event) { | ||
85 | + $event.stopPropagation(); | ||
86 | + } | ||
87 | + this.firmwareService.downloadFirmware(firmware.id.id).subscribe(); | ||
88 | + } | ||
89 | + | ||
90 | + onFirmwareAction(action: EntityAction<FirmwareInfo>): boolean { | ||
91 | + switch (action.action) { | ||
92 | + case 'uploadFirmware': | ||
93 | + this.exportFirmware(action.event, action.entity); | ||
94 | + return true; | ||
95 | + } | ||
96 | + return false; | ||
97 | + } | ||
98 | + | ||
99 | +} |
1 | +/// | ||
2 | +/// Copyright © 2016-2021 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { NgModule } from '@angular/core'; | ||
18 | +import { CommonModule } from '@angular/common'; | ||
19 | +import { SharedModule } from '@shared/shared.module'; | ||
20 | +import { HomeComponentsModule } from '@home/components/home-components.module'; | ||
21 | +import { FirmwareRoutingModule } from '@home/pages/firmware/firmware-routing.module'; | ||
22 | +import { FirmwaresComponent } from '@home/pages/firmware/firmwares.component'; | ||
23 | + | ||
24 | +@NgModule({ | ||
25 | + declarations: [ | ||
26 | + FirmwaresComponent | ||
27 | + ], | ||
28 | + imports: [ | ||
29 | + CommonModule, | ||
30 | + SharedModule, | ||
31 | + HomeComponentsModule, | ||
32 | + FirmwareRoutingModule | ||
33 | + ] | ||
34 | +}) | ||
35 | +export class FirmwareModule { } |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2021 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<div class="tb-details-buttons" fxLayout.xs="column"> | ||
19 | + <button mat-raised-button color="primary" fxFlex.xs | ||
20 | + [disabled]="(isLoading$ | async) || !entity?.hasData" | ||
21 | + (click)="onEntityAction($event, 'uploadFirmware')" | ||
22 | + [fxShow]="!isEdit"> | ||
23 | + {{'firmware.export' | translate }} | ||
24 | + </button> | ||
25 | + <button mat-raised-button color="primary" fxFlex.xs | ||
26 | + [disabled]="(isLoading$ | async)" | ||
27 | + (click)="onEntityAction($event, 'delete')" | ||
28 | + [fxShow]="!hideDelete() && !isEdit"> | ||
29 | + {{'resource.delete' | translate }} | ||
30 | + </button> | ||
31 | +</div> | ||
32 | +<div class="mat-padding" fxLayout="column"> | ||
33 | + <form [formGroup]="entityForm"> | ||
34 | + <fieldset [disabled]="(isLoading$ | async) || !isEdit"> | ||
35 | + <mat-hint class="tb-hint" translate *ngIf="isAdd">firmware.warning-after-save-no-edit</mat-hint> | ||
36 | + <div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column"> | ||
37 | + <mat-form-field class="mat-block" fxFlex="45"> | ||
38 | + <mat-label translate>firmware.title</mat-label> | ||
39 | + <input matInput formControlName="title" type="text" required> | ||
40 | + <mat-error *ngIf="entityForm.get('title').hasError('required')"> | ||
41 | + {{ 'firmware.title-required' | translate }} | ||
42 | + </mat-error> | ||
43 | + </mat-form-field> | ||
44 | + <mat-form-field class="mat-block" fxFlex> | ||
45 | + <mat-label translate>firmware.version</mat-label> | ||
46 | + <input matInput formControlName="version" type="text" required> | ||
47 | + <mat-error *ngIf="entityForm.get('version').hasError('required')"> | ||
48 | + {{ 'firmware.version-required' | translate }} | ||
49 | + </mat-error> | ||
50 | + </mat-form-field> | ||
51 | + </div> | ||
52 | + <section *ngIf="isAdd" style="padding-bottom: 8px"> | ||
53 | + <div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column"> | ||
54 | + <mat-form-field class="mat-block" fxFlex="45"> | ||
55 | + <mat-label translate>firmware.checksum-algorithm</mat-label> | ||
56 | + <mat-select formControlName="checksumAlgorithm"> | ||
57 | + <mat-option [value]=null></mat-option> | ||
58 | + <mat-option *ngFor="let checksumAlgorithm of checksumAlgorithms" [value]="checksumAlgorithm"> | ||
59 | + {{ checksumAlgorithmTranslationMap.get(checksumAlgorithm) }} | ||
60 | + </mat-option> | ||
61 | + </mat-select> | ||
62 | + </mat-form-field> | ||
63 | + <mat-form-field class="mat-block" fxFlex> | ||
64 | + <mat-label translate>firmware.checksum</mat-label> | ||
65 | + <input matInput formControlName="checksum" type="text" | ||
66 | + [required]="entityForm.get('checksumAlgorithm').value != null"> | ||
67 | + <mat-error *ngIf="entityForm.get('checksumAlgorithm').hasError('required')"> | ||
68 | + {{ 'firmware.checksum-required' | translate }} | ||
69 | + </mat-error> | ||
70 | + </mat-form-field> | ||
71 | + </div> | ||
72 | + <tb-file-input | ||
73 | + formControlName="file" | ||
74 | + workFromFileObj="true" | ||
75 | + required | ||
76 | + dropLabel="{{'resource.drop-file' | translate}}"> | ||
77 | + </tb-file-input> | ||
78 | + </section> | ||
79 | + <div formGroupName="additionalInfo"> | ||
80 | + <mat-form-field class="mat-block"> | ||
81 | + <mat-label translate>firmware.description</mat-label> | ||
82 | + <textarea matInput formControlName="description" rows="2"></textarea> | ||
83 | + </mat-form-field> | ||
84 | + </div> | ||
85 | + </fieldset> | ||
86 | + </form> | ||
87 | +</div> |
1 | +/// | ||
2 | +/// Copyright © 2016-2021 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; | ||
18 | +import { Subject } from 'rxjs'; | ||
19 | +import { Store } from '@ngrx/store'; | ||
20 | +import { AppState } from '@core/core.state'; | ||
21 | +import { TranslateService } from '@ngx-translate/core'; | ||
22 | +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; | ||
23 | +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; | ||
24 | +import { EntityComponent } from '@home/components/entity/entity.component'; | ||
25 | +import { ChecksumAlgorithm, ChecksumAlgorithmTranslationMap, Firmware } from '@shared/models/firmware.models'; | ||
26 | +import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators'; | ||
27 | + | ||
28 | +@Component({ | ||
29 | + selector: 'tb-firmware', | ||
30 | + templateUrl: './firmwares.component.html' | ||
31 | +}) | ||
32 | +export class FirmwaresComponent extends EntityComponent<Firmware> implements OnInit, OnDestroy { | ||
33 | + | ||
34 | + private destroy$ = new Subject(); | ||
35 | + | ||
36 | + checksumAlgorithms = Object.values(ChecksumAlgorithm); | ||
37 | + checksumAlgorithmTranslationMap = ChecksumAlgorithmTranslationMap; | ||
38 | + | ||
39 | + constructor(protected store: Store<AppState>, | ||
40 | + protected translate: TranslateService, | ||
41 | + @Inject('entity') protected entityValue: Firmware, | ||
42 | + @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<Firmware>, | ||
43 | + public fb: FormBuilder) { | ||
44 | + super(store, fb, entityValue, entitiesTableConfigValue); | ||
45 | + } | ||
46 | + | ||
47 | + ngOnInit() { | ||
48 | + super.ngOnInit(); | ||
49 | + if (this.isAdd) { | ||
50 | + this.entityForm.get('checksumAlgorithm').valueChanges.pipe( | ||
51 | + map(algorithm => !!algorithm), | ||
52 | + distinctUntilChanged(), | ||
53 | + takeUntil(this.destroy$) | ||
54 | + ).subscribe( | ||
55 | + setAlgorithm => { | ||
56 | + if (setAlgorithm) { | ||
57 | + this.entityForm.get('checksum').setValidators([Validators.maxLength(1020), Validators.required]); | ||
58 | + } else { | ||
59 | + this.entityForm.get('checksum').clearValidators(); | ||
60 | + } | ||
61 | + this.entityForm.get('checksum').updateValueAndValidity({emitEvent: false}); | ||
62 | + } | ||
63 | + ); | ||
64 | + } | ||
65 | + } | ||
66 | + | ||
67 | + ngOnDestroy() { | ||
68 | + super.ngOnDestroy(); | ||
69 | + this.destroy$.next(); | ||
70 | + this.destroy$.complete(); | ||
71 | + } | ||
72 | + | ||
73 | + hideDelete() { | ||
74 | + if (this.entitiesTableConfig) { | ||
75 | + return !this.entitiesTableConfig.deleteEnabled(this.entity); | ||
76 | + } else { | ||
77 | + return false; | ||
78 | + } | ||
79 | + } | ||
80 | + | ||
81 | + buildForm(entity: Firmware): FormGroup { | ||
82 | + const form = this.fb.group({ | ||
83 | + title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]], | ||
84 | + version: [entity ? entity.version : '', [Validators.required, Validators.maxLength(255)]], | ||
85 | + additionalInfo: this.fb.group( | ||
86 | + { | ||
87 | + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], | ||
88 | + } | ||
89 | + ) | ||
90 | + }); | ||
91 | + if (this.isAdd) { | ||
92 | + form.addControl('checksumAlgorithm', this.fb.control(null)); | ||
93 | + form.addControl('checksum', this.fb.control('', Validators.maxLength(1020))); | ||
94 | + form.addControl('file', this.fb.control(null, Validators.required)); | ||
95 | + } | ||
96 | + return form; | ||
97 | + } | ||
98 | + | ||
99 | + updateForm(entity: Firmware) { | ||
100 | + if (this.isEdit) { | ||
101 | + this.entityForm.get('title').disable({emitEvent: false}); | ||
102 | + this.entityForm.get('version').disable({emitEvent: false}); | ||
103 | + } | ||
104 | + this.entityForm.patchValue({ | ||
105 | + title: entity.title, | ||
106 | + version: entity.version, | ||
107 | + additionalInfo: { | ||
108 | + description: entity.additionalInfo ? entity.additionalInfo.description : '' | ||
109 | + } | ||
110 | + }); | ||
111 | + } | ||
112 | +} |
@@ -35,6 +35,7 @@ import { modulesMap } from '../../common/modules-map'; | @@ -35,6 +35,7 @@ import { modulesMap } from '../../common/modules-map'; | ||
35 | import { DeviceProfileModule } from './device-profile/device-profile.module'; | 35 | import { DeviceProfileModule } from './device-profile/device-profile.module'; |
36 | import { ApiUsageModule } from '@home/pages/api-usage/api-usage.module'; | 36 | import { ApiUsageModule } from '@home/pages/api-usage/api-usage.module'; |
37 | import { ResourceModule } from '@home/pages/resource/resource.module'; | 37 | import { ResourceModule } from '@home/pages/resource/resource.module'; |
38 | +import { FirmwareModule } from '@home/pages/firmware/firmware.module'; | ||
38 | 39 | ||
39 | @NgModule({ | 40 | @NgModule({ |
40 | exports: [ | 41 | exports: [ |
@@ -54,6 +55,7 @@ import { ResourceModule } from '@home/pages/resource/resource.module'; | @@ -54,6 +55,7 @@ import { ResourceModule } from '@home/pages/resource/resource.module'; | ||
54 | AuditLogModule, | 55 | AuditLogModule, |
55 | ApiUsageModule, | 56 | ApiUsageModule, |
56 | ResourceModule, | 57 | ResourceModule, |
58 | + FirmwareModule, | ||
57 | UserModule | 59 | UserModule |
58 | ], | 60 | ], |
59 | providers: [ | 61 | providers: [ |
@@ -105,6 +105,9 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, | @@ -105,6 +105,9 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, | ||
105 | @Input() | 105 | @Input() |
106 | readAsBinary = false; | 106 | readAsBinary = false; |
107 | 107 | ||
108 | + @Input() | ||
109 | + workFromFileObj = false; | ||
110 | + | ||
108 | private multipleFileValue = false; | 111 | private multipleFileValue = false; |
109 | 112 | ||
110 | @Input() | 113 | @Input() |
@@ -124,6 +127,7 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, | @@ -124,6 +127,7 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, | ||
124 | 127 | ||
125 | fileName: string | string[]; | 128 | fileName: string | string[]; |
126 | fileContent: any; | 129 | fileContent: any; |
130 | + files: File[]; | ||
127 | 131 | ||
128 | @ViewChild('flow', {static: true}) | 132 | @ViewChild('flow', {static: true}) |
129 | flow: FlowDirective; | 133 | flow: FlowDirective; |
@@ -151,15 +155,17 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, | @@ -151,15 +155,17 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, | ||
151 | } | 155 | } |
152 | }); | 156 | }); |
153 | if (readers.length) { | 157 | if (readers.length) { |
154 | - Promise.all(readers).then((filesContent) => { | ||
155 | - filesContent = filesContent.filter(content => content.fileContent != null); | ||
156 | - if (filesContent.length === 1) { | ||
157 | - this.fileContent = filesContent[0].fileContent; | ||
158 | - this.fileName = filesContent[0].fileName; | 158 | + Promise.all(readers).then((files) => { |
159 | + files = files.filter(file => file.fileContent != null || file.files != null); | ||
160 | + if (files.length === 1) { | ||
161 | + this.fileContent = files[0].fileContent; | ||
162 | + this.fileName = files[0].fileName; | ||
163 | + this.files = files[0].files; | ||
159 | this.updateModel(); | 164 | this.updateModel(); |
160 | - } else if (filesContent.length > 1) { | ||
161 | - this.fileContent = filesContent.map(content => content.fileContent); | ||
162 | - this.fileName = filesContent.map(content => content.fileName); | 165 | + } else if (files.length > 1) { |
166 | + this.fileContent = files.map(content => content.fileContent); | ||
167 | + this.fileName = files.map(content => content.fileName); | ||
168 | + this.files = files.map(content => content.files); | ||
163 | this.updateModel(); | 169 | this.updateModel(); |
164 | } | 170 | } |
165 | }); | 171 | }); |
@@ -177,21 +183,27 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, | @@ -177,21 +183,27 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, | ||
177 | reader.onload = () => { | 183 | reader.onload = () => { |
178 | let fileName = null; | 184 | let fileName = null; |
179 | let fileContent = null; | 185 | let fileContent = null; |
186 | + let files = null; | ||
180 | if (typeof reader.result === 'string') { | 187 | if (typeof reader.result === 'string') { |
181 | fileContent = reader.result; | 188 | fileContent = reader.result; |
182 | if (fileContent && fileContent.length > 0) { | 189 | if (fileContent && fileContent.length > 0) { |
183 | - if (this.contentConvertFunction) { | ||
184 | - fileContent = this.contentConvertFunction(fileContent); | ||
185 | - } | ||
186 | - if (fileContent) { | 190 | + if (!this.workFromFileObj) { |
191 | + if (this.contentConvertFunction) { | ||
192 | + fileContent = this.contentConvertFunction(fileContent); | ||
193 | + } | ||
194 | + if (fileContent) { | ||
195 | + fileName = file.name; | ||
196 | + } | ||
197 | + } else { | ||
198 | + files = file.file; | ||
187 | fileName = file.name; | 199 | fileName = file.name; |
188 | } | 200 | } |
189 | } | 201 | } |
190 | } | 202 | } |
191 | - resolve({fileContent, fileName}); | 203 | + resolve({fileContent, fileName, files}); |
192 | }; | 204 | }; |
193 | reader.onerror = () => { | 205 | reader.onerror = () => { |
194 | - resolve({fileContent: null, fileName: null}); | 206 | + resolve({fileContent: null, fileName: null, files: null}); |
195 | }; | 207 | }; |
196 | if (this.readAsBinary) { | 208 | if (this.readAsBinary) { |
197 | reader.readAsBinaryString(file.file); | 209 | reader.readAsBinaryString(file.file); |
@@ -227,7 +239,11 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, | @@ -227,7 +239,11 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, | ||
227 | } | 239 | } |
228 | 240 | ||
229 | writeValue(value: any): void { | 241 | writeValue(value: any): void { |
230 | - this.fileName = this.existingFileName || null; | 242 | + let fileName = null; |
243 | + if (this.workFromFileObj && value instanceof File) { | ||
244 | + fileName = Array.isArray(value) ? value.map(file => file.name) : value.name; | ||
245 | + } | ||
246 | + this.fileName = this.existingFileName || fileName; | ||
231 | } | 247 | } |
232 | 248 | ||
233 | ngOnChanges(changes: SimpleChanges): void { | 249 | ngOnChanges(changes: SimpleChanges): void { |
@@ -242,13 +258,18 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, | @@ -242,13 +258,18 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, | ||
242 | } | 258 | } |
243 | 259 | ||
244 | private updateModel() { | 260 | private updateModel() { |
245 | - this.propagateChange(this.fileContent); | ||
246 | - this.fileNameChanged.emit(this.fileName); | 261 | + if (this.workFromFileObj) { |
262 | + this.propagateChange(this.files); | ||
263 | + } else { | ||
264 | + this.propagateChange(this.fileContent); | ||
265 | + this.fileNameChanged.emit(this.fileName); | ||
266 | + } | ||
247 | } | 267 | } |
248 | 268 | ||
249 | clearFile() { | 269 | clearFile() { |
250 | this.fileName = null; | 270 | this.fileName = null; |
251 | this.fileContent = null; | 271 | this.fileContent = null; |
272 | + this.files = null; | ||
252 | this.updateModel(); | 273 | this.updateModel(); |
253 | } | 274 | } |
254 | 275 |
@@ -33,7 +33,8 @@ export enum EntityType { | @@ -33,7 +33,8 @@ export enum EntityType { | ||
33 | WIDGETS_BUNDLE = 'WIDGETS_BUNDLE', | 33 | WIDGETS_BUNDLE = 'WIDGETS_BUNDLE', |
34 | WIDGET_TYPE = 'WIDGET_TYPE', | 34 | WIDGET_TYPE = 'WIDGET_TYPE', |
35 | API_USAGE_STATE = 'API_USAGE_STATE', | 35 | API_USAGE_STATE = 'API_USAGE_STATE', |
36 | - TB_RESOURCE = 'TB_RESOURCE' | 36 | + TB_RESOURCE = 'TB_RESOURCE', |
37 | + FIRMWARE = 'FIRMWARE' | ||
37 | } | 38 | } |
38 | 39 | ||
39 | export enum AliasEntityType { | 40 | export enum AliasEntityType { |
@@ -278,6 +279,16 @@ export const entityTypeTranslations = new Map<EntityType | AliasEntityType, Enti | @@ -278,6 +279,16 @@ export const entityTypeTranslations = new Map<EntityType | AliasEntityType, Enti | ||
278 | selectedEntities: 'resource.selected-resources' | 279 | selectedEntities: 'resource.selected-resources' |
279 | } | 280 | } |
280 | ], | 281 | ], |
282 | + [ | ||
283 | + EntityType.FIRMWARE, | ||
284 | + { | ||
285 | + details: 'firmware.firmware-details', | ||
286 | + add: 'firmware.add', | ||
287 | + noEntities: 'firmware.no-firmware-text', | ||
288 | + search: 'firmware.search', | ||
289 | + selectedEntities: 'firmware.selected-firmware' | ||
290 | + } | ||
291 | + ] | ||
281 | ] | 292 | ] |
282 | ); | 293 | ); |
283 | 294 | ||
@@ -354,6 +365,12 @@ export const entityTypeResources = new Map<EntityType, EntityTypeResource<BaseDa | @@ -354,6 +365,12 @@ export const entityTypeResources = new Map<EntityType, EntityTypeResource<BaseDa | ||
354 | { | 365 | { |
355 | helpLinkId: 'resources' | 366 | helpLinkId: 'resources' |
356 | } | 367 | } |
368 | + ], | ||
369 | + [ | ||
370 | + EntityType.FIRMWARE, | ||
371 | + { | ||
372 | + helpLinkId: 'firmware' | ||
373 | + } | ||
357 | ] | 374 | ] |
358 | ] | 375 | ] |
359 | ); | 376 | ); |
1 | +/// | ||
2 | +/// Copyright © 2016-2021 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { BaseData } from '@shared/models/base-data'; | ||
18 | +import { TenantId } from '@shared/models/id/tenant-id'; | ||
19 | +import { FirmwareId } from '@shared/models/id/firmware-id'; | ||
20 | + | ||
21 | +export enum ChecksumAlgorithm { | ||
22 | + MD5 = 'md5', | ||
23 | + SHA256 = 'sha256', | ||
24 | + CRC32 = 'crc32' | ||
25 | +} | ||
26 | + | ||
27 | +export const ChecksumAlgorithmTranslationMap = new Map<ChecksumAlgorithm, string>( | ||
28 | + [ | ||
29 | + [ChecksumAlgorithm.MD5, 'MD5'], | ||
30 | + [ChecksumAlgorithm.SHA256, 'SHA-256'], | ||
31 | + [ChecksumAlgorithm.CRC32, 'CRC-32'] | ||
32 | + ] | ||
33 | +); | ||
34 | + | ||
35 | +export interface FirmwareInfo extends BaseData<FirmwareId> { | ||
36 | + tenantId?: TenantId; | ||
37 | + title?: string; | ||
38 | + version?: string; | ||
39 | + hasData?: boolean; | ||
40 | + additionalInfo?: any; | ||
41 | +} | ||
42 | + | ||
43 | +export interface Firmware extends FirmwareInfo { | ||
44 | + file?: File; | ||
45 | + data: string; | ||
46 | + fileName: string; | ||
47 | + checksum?: ChecksumAlgorithm; | ||
48 | + checksumAlgorithm?: string; | ||
49 | + contentType: string; | ||
50 | +} |
1 | +/// | ||
2 | +/// Copyright © 2016-2021 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { EntityId } from '@shared/models/id/entity-id'; | ||
18 | +import { EntityType } from '@shared/models/entity-type.models'; | ||
19 | + | ||
20 | +export class FirmwareId implements EntityId { | ||
21 | + entityType = EntityType.FIRMWARE; | ||
22 | + id: string; | ||
23 | + constructor(id: string) { | ||
24 | + this.id = id; | ||
25 | + } | ||
26 | +} |
@@ -1693,6 +1693,33 @@ | @@ -1693,6 +1693,33 @@ | ||
1693 | "inherit-owner": "Inherit from owner", | 1693 | "inherit-owner": "Inherit from owner", |
1694 | "source-attribute-not-set": "If source attribute isn't set" | 1694 | "source-attribute-not-set": "If source attribute isn't set" |
1695 | }, | 1695 | }, |
1696 | + "firmware": { | ||
1697 | + "add": "Add firmware", | ||
1698 | + "checksum": "Checksum", | ||
1699 | + "checksum-required": "Checksum is required.", | ||
1700 | + "checksum-algorithm": "Checksum algorithm", | ||
1701 | + "description": "Description", | ||
1702 | + "delete": "Delete firmware", | ||
1703 | + "delete-firmware-text": "Be careful, after the confirmation the firmware will become unrecoverable.", | ||
1704 | + "delete-firmware-title": "Are you sure you want to delete the firmware '{{firmwareTitle}}'?", | ||
1705 | + "delete-firmwares-action-title": "Delete { count, plural, 1 {1 firmware} other {# firmwares} }", | ||
1706 | + "delete-firmwares-text": "Be careful, after the confirmation all selected resources will be removed.", | ||
1707 | + "delete-firmwares-title": "Are you sure you want to delete { count, plural, 1 {1 firmware} other {# firmwares} }?", | ||
1708 | + "drop-file": "Drop a firmware file or click to select a file to upload.", | ||
1709 | + "empty": "Firmware is empty", | ||
1710 | + "export": "Export firmware", | ||
1711 | + "no-firmware-matching": "No firmware matching '{{firmware}}' were found.", | ||
1712 | + "no-firmware-text": "No firmwares found", | ||
1713 | + "firmware": "Firmware", | ||
1714 | + "firmware-details": "Firmware details", | ||
1715 | + "search": "Search firmwares", | ||
1716 | + "selected-firmware": "{ count, plural, 1 {1 firmware} other {# firmwares} } selected", | ||
1717 | + "title": "Title", | ||
1718 | + "title-required": "Title is required.", | ||
1719 | + "version": "Version", | ||
1720 | + "version-required": "Version is required.", | ||
1721 | + "warning-after-save-no-edit": "Once the firmware is saved, it will not be possible to change the title and version fields." | ||
1722 | + }, | ||
1696 | "fullscreen": { | 1723 | "fullscreen": { |
1697 | "expand": "Expand to fullscreen", | 1724 | "expand": "Expand to fullscreen", |
1698 | "exit": "Exit fullscreen", | 1725 | "exit": "Exit fullscreen", |