@c8y/ngx-components 1024.0.0 → 1024.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/c8y-ngx-components-datapoint-explorer-devicemanagement.mjs +0 -1
- package/fesm2022/c8y-ngx-components-datapoint-explorer-devicemanagement.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-operations-bulk-single-operations-list.mjs +4 -6
- package/fesm2022/c8y-ngx-components-operations-bulk-single-operations-list.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-repository-configuration.mjs +493 -417
- package/fesm2022/c8y-ngx-components-repository-configuration.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-repository-shared.mjs +69 -10
- package/fesm2022/c8y-ngx-components-repository-shared.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-widgets-implementations-quick-links.mjs +53 -24
- package/fesm2022/c8y-ngx-components-widgets-implementations-quick-links.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components.mjs +51 -24
- package/fesm2022/c8y-ngx-components.mjs.map +1 -1
- package/locales/de.po +42 -39
- package/locales/es.po +32 -29
- package/locales/fr.po +24 -21
- package/locales/ja_JP.po +47 -44
- package/locales/ko.po +30 -27
- package/locales/locales.pot +3 -0
- package/locales/nl.po +27 -24
- package/locales/pl.po +42 -39
- package/locales/pt_BR.po +29 -26
- package/locales/zh_CN.po +28 -25
- package/locales/zh_TW.po +38 -35
- package/package.json +1 -1
- package/types/c8y-ngx-components-datapoint-explorer-devicemanagement.d.ts.map +1 -1
- package/types/c8y-ngx-components-operations-bulk-single-operations-list.d.ts.map +1 -1
- package/types/c8y-ngx-components-repository-configuration.d.ts +30 -11
- package/types/c8y-ngx-components-repository-configuration.d.ts.map +1 -1
- package/types/c8y-ngx-components-repository-shared.d.ts +26 -2
- package/types/c8y-ngx-components-repository-shared.d.ts.map +1 -1
- package/types/c8y-ngx-components-widgets-implementations-quick-links.d.ts +18 -5
- package/types/c8y-ngx-components-widgets-implementations-quick-links.d.ts.map +1 -1
- package/types/c8y-ngx-components.d.ts +16 -5
- package/types/c8y-ngx-components.d.ts.map +1 -1
|
@@ -1,35 +1,55 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { EventEmitter, Injectable, Component, Pipe,
|
|
2
|
+
import { EventEmitter, Injectable, Component, Pipe, inject, ChangeDetectorRef, signal, computed, Input, ChangeDetectionStrategy, Output, ViewChild, NgModule } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/router';
|
|
4
|
-
import * as
|
|
4
|
+
import * as i3$1 from '@c8y/client';
|
|
5
5
|
import { OperationStatus, InventoryBinaryService } from '@c8y/client';
|
|
6
6
|
import { gettext } from '@c8y/ngx-components/gettext';
|
|
7
7
|
import * as i2 from '@c8y/ngx-components';
|
|
8
|
-
import { IconDirective, C8yTranslatePipe,
|
|
8
|
+
import { IconDirective, C8yTranslatePipe, BottomDrawerRef, AlertService, ValidationPattern, isBinaryFile, C8yTranslateDirective, FormGroupComponent, TypeaheadComponent, ForOfDirective, ListItemComponent, HighlightComponent, FilePickerComponent, MessagesComponent, RequiredInputPlaceholderDirective, SelectComponent, Permissions, DatePipe, FilterInputComponent, ActionBarItemComponent, TabsetAriaDirective, EmptyStateComponent, hookRoute, ViewContext, DataGridService, BottomDrawerService, ModalService, BuiltInActionType, Status, TitleComponent, BreadcrumbComponent, BreadcrumbItemComponent, HelpComponent, DataGridComponent, EmptyStateContextDirective, GuideDocsComponent, GuideHrefDirective, NavigatorNode, hookNavigator, CoreModule, FormsModule as FormsModule$1 } from '@c8y/ngx-components';
|
|
9
9
|
import * as i3 from '@c8y/ngx-components/repository/shared';
|
|
10
|
-
import { DeviceConfigurationOperation, RepositoryType,
|
|
10
|
+
import { DeviceConfigurationOperation, RepositoryService, RepositoryType, mimeTypeToEditorLanguage, SharedRepositoryModule, RepositoryItemNameGridColumn, DescriptionGridColumn, FileGridColumn, DeviceTypeGridColumn, TypeGridColumn } from '@c8y/ngx-components/repository/shared';
|
|
11
11
|
import { NgClass, NgIf, NgFor, AsyncPipe } from '@angular/common';
|
|
12
|
-
import * as
|
|
12
|
+
import * as i1$1 from '@angular/forms';
|
|
13
13
|
import { FormsModule, FormGroup, FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
|
|
14
14
|
import { OperationDetailsComponent, OperationDetailsModule } from '@c8y/ngx-components/operations/operation-details';
|
|
15
|
-
import { has,
|
|
15
|
+
import { has, uniqBy } from 'lodash-es';
|
|
16
16
|
import { saveAs } from 'file-saver';
|
|
17
|
-
import * as i1$1 from 'ngx-bootstrap/modal';
|
|
18
17
|
import { map } from 'rxjs/operators';
|
|
19
|
-
import * as i1$2 from 'ngx-bootstrap/tabs';
|
|
20
|
-
import { TabsetComponent, TabDirective, TabsModule } from 'ngx-bootstrap/tabs';
|
|
21
|
-
import { TranslateService } from '@ngx-translate/core';
|
|
22
18
|
import { toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
23
19
|
import { EditorComponent, MonacoEditorMarkerValidatorDirective } from '@c8y/ngx-components/editor';
|
|
24
20
|
import { pipe } from 'rxjs';
|
|
21
|
+
import * as i1$3 from 'ngx-bootstrap/tabs';
|
|
22
|
+
import { TabsetComponent, TabDirective, TabsModule } from 'ngx-bootstrap/tabs';
|
|
23
|
+
import * as i1$2 from 'ngx-bootstrap/modal';
|
|
24
|
+
import { TranslateService } from '@ngx-translate/core';
|
|
25
25
|
|
|
26
26
|
class DeviceConfigurationService {
|
|
27
|
-
constructor() {
|
|
27
|
+
constructor(bottomDrawerService) {
|
|
28
|
+
this.bottomDrawerService = bottomDrawerService;
|
|
28
29
|
this.configurationsUpdated = new EventEmitter();
|
|
29
30
|
}
|
|
30
31
|
updateConfigurations(repositorySnapsOnly) {
|
|
31
32
|
this.configurationsUpdated.emit(repositorySnapsOnly);
|
|
32
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Opens a component in a bottom drawer prefilled with `initialState` and resolves once the
|
|
36
|
+
* user saves (the drawer's `result` promise settles), or `false` if they cancel. Shared by
|
|
37
|
+
* the "Save to repository" and "Edit in repository" flows, which differ only in the payload.
|
|
38
|
+
*/
|
|
39
|
+
async openConfigurationDrawer(component, initialState) {
|
|
40
|
+
try {
|
|
41
|
+
const drawer = this.bottomDrawerService.openDrawer(component, {
|
|
42
|
+
disableClickOutside: true,
|
|
43
|
+
closeOnNavigation: true,
|
|
44
|
+
initialState
|
|
45
|
+
});
|
|
46
|
+
await drawer.instance.result;
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
33
53
|
hasAnySupportedOperation(mo, operation) {
|
|
34
54
|
const supported = mo.c8y_SupportedOperations;
|
|
35
55
|
if (!supported) {
|
|
@@ -40,12 +60,12 @@ class DeviceConfigurationService {
|
|
|
40
60
|
}
|
|
41
61
|
return supported.some(supportedOperation => operation.includes(supportedOperation));
|
|
42
62
|
}
|
|
43
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DeviceConfigurationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
63
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DeviceConfigurationService, deps: [{ token: i2.BottomDrawerService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
44
64
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DeviceConfigurationService }); }
|
|
45
65
|
}
|
|
46
66
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DeviceConfigurationService, decorators: [{
|
|
47
67
|
type: Injectable
|
|
48
|
-
}] });
|
|
68
|
+
}], ctorParameters: () => [{ type: i2.BottomDrawerService }] });
|
|
49
69
|
|
|
50
70
|
class TextBasedConfigurationComponent {
|
|
51
71
|
constructor(route, alertService, repositoryService, deviceConfigurationService, inventoryService) {
|
|
@@ -144,13 +164,13 @@ class TextBasedConfigurationComponent {
|
|
|
144
164
|
withChildren: false
|
|
145
165
|
})).data;
|
|
146
166
|
}
|
|
147
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TextBasedConfigurationComponent, deps: [{ token: i1.ActivatedRoute }, { token: i2.AlertService }, { token: i3.RepositoryService }, { token: DeviceConfigurationService }, { token:
|
|
148
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: TextBasedConfigurationComponent, isStandalone: true, selector: "c8y-text-based-configuration", ngImport: i0, template: "<div class=\"d-flex d-col fit-h\">\n <fieldset class=\"card-block bg-level-1 fit-w\">\n <div class=\"content-flex-50\">\n <div class=\"m-l-auto d-flex\">\n @if (showTextBasedConfigReload) {\n <button\n class=\"btn btn-default btn-sm a-s-center m-t-8 m-b-8\"\n title=\"{{ 'Get configuration from device' | translate }}\"\n type=\"button\"\n (click)=\"reloadConfiguration()\"\n [disabled]=\"reloadingConfig || savingConfig\"\n >\n @if (reloadingConfig) {\n <i\n class=\"m-r-4\"\n c8yIcon=\"refresh\"\n [ngClass]=\"{ 'icon-spin': reloadingConfig }\"\n ></i>\n }\n @if (!reloadingConfig) {\n <i\n class=\"m-r-4\"\n c8yIcon=\"download\"\n ></i>\n }\n\n {{ 'Get configuration from device' | translate }}\n </button>\n }\n </div>\n </div>\n </fieldset>\n <div class=\"flex-grow\">\n <textarea\n class=\"form-control fit-h p-r-16 p-l-16\"\n [attr.aria-label]=\"'Operations' | translate\"\n [(ngModel)]=\"config\"\n [disabled]=\"reloadingConfig || savingConfig\"\n c8y-spellcheck=\"false\"\n ></textarea>\n </div>\n @if (latestOperation !== undefined) {\n <c8y-operation-details\n class=\"bg-level-2 p-0\"\n [operation]=\"latestOperation\"\n ></c8y-operation-details>\n }\n @if (showTextBasedConfigSave) {\n <div class=\"card-footer fit-w separator\">\n <button\n class=\"btn btn-primary\"\n id=\"send-config-btn\"\n type=\"button\"\n (click)=\"updateConfiguration(config)\"\n [disabled]=\"reloadingConfig || savingConfig || !config\"\n [ngClass]=\"{ 'btn-pending': savingConfig }\"\n >\n @if (!savingConfig) {\n <span title=\"{{ 'Send' | translate }}\">\n {{ 'Send configuration to device' | translate }}\n </span>\n }\n @if (savingConfig) {\n <span title=\"{{ 'Sending\u2026' | translate }}\">\n {{ 'Sending\u2026' | translate }}\n </span>\n }\n </button>\n </div>\n }\n</div>\n", dependencies: [{ kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type:
|
|
167
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TextBasedConfigurationComponent, deps: [{ token: i1.ActivatedRoute }, { token: i2.AlertService }, { token: i3.RepositoryService }, { token: DeviceConfigurationService }, { token: i3$1.InventoryService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
168
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: TextBasedConfigurationComponent, isStandalone: true, selector: "c8y-text-based-configuration", ngImport: i0, template: "<div class=\"d-flex d-col fit-h\">\n <fieldset class=\"card-block bg-level-1 fit-w\">\n <div class=\"content-flex-50\">\n <div class=\"m-l-auto d-flex\">\n @if (showTextBasedConfigReload) {\n <button\n class=\"btn btn-default btn-sm a-s-center m-t-8 m-b-8\"\n title=\"{{ 'Get configuration from device' | translate }}\"\n type=\"button\"\n (click)=\"reloadConfiguration()\"\n [disabled]=\"reloadingConfig || savingConfig\"\n >\n @if (reloadingConfig) {\n <i\n class=\"m-r-4\"\n c8yIcon=\"refresh\"\n [ngClass]=\"{ 'icon-spin': reloadingConfig }\"\n ></i>\n }\n @if (!reloadingConfig) {\n <i\n class=\"m-r-4\"\n c8yIcon=\"download\"\n ></i>\n }\n\n {{ 'Get configuration from device' | translate }}\n </button>\n }\n </div>\n </div>\n </fieldset>\n <div class=\"flex-grow\">\n <textarea\n class=\"form-control fit-h p-r-16 p-l-16\"\n [attr.aria-label]=\"'Operations' | translate\"\n [(ngModel)]=\"config\"\n [disabled]=\"reloadingConfig || savingConfig\"\n c8y-spellcheck=\"false\"\n ></textarea>\n </div>\n @if (latestOperation !== undefined) {\n <c8y-operation-details\n class=\"bg-level-2 p-0\"\n [operation]=\"latestOperation\"\n ></c8y-operation-details>\n }\n @if (showTextBasedConfigSave) {\n <div class=\"card-footer fit-w separator\">\n <button\n class=\"btn btn-primary\"\n id=\"send-config-btn\"\n type=\"button\"\n (click)=\"updateConfiguration(config)\"\n [disabled]=\"reloadingConfig || savingConfig || !config\"\n [ngClass]=\"{ 'btn-pending': savingConfig }\"\n >\n @if (!savingConfig) {\n <span title=\"{{ 'Send' | translate }}\">\n {{ 'Send configuration to device' | translate }}\n </span>\n }\n @if (savingConfig) {\n <span title=\"{{ 'Sending\u2026' | translate }}\">\n {{ 'Sending\u2026' | translate }}\n </span>\n }\n </button>\n </div>\n }\n</div>\n", dependencies: [{ kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: OperationDetailsComponent, selector: "c8y-operation-details", inputs: ["operation"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
|
|
149
169
|
}
|
|
150
170
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TextBasedConfigurationComponent, decorators: [{
|
|
151
171
|
type: Component,
|
|
152
172
|
args: [{ selector: 'c8y-text-based-configuration', imports: [IconDirective, NgClass, FormsModule, OperationDetailsComponent, C8yTranslatePipe], template: "<div class=\"d-flex d-col fit-h\">\n <fieldset class=\"card-block bg-level-1 fit-w\">\n <div class=\"content-flex-50\">\n <div class=\"m-l-auto d-flex\">\n @if (showTextBasedConfigReload) {\n <button\n class=\"btn btn-default btn-sm a-s-center m-t-8 m-b-8\"\n title=\"{{ 'Get configuration from device' | translate }}\"\n type=\"button\"\n (click)=\"reloadConfiguration()\"\n [disabled]=\"reloadingConfig || savingConfig\"\n >\n @if (reloadingConfig) {\n <i\n class=\"m-r-4\"\n c8yIcon=\"refresh\"\n [ngClass]=\"{ 'icon-spin': reloadingConfig }\"\n ></i>\n }\n @if (!reloadingConfig) {\n <i\n class=\"m-r-4\"\n c8yIcon=\"download\"\n ></i>\n }\n\n {{ 'Get configuration from device' | translate }}\n </button>\n }\n </div>\n </div>\n </fieldset>\n <div class=\"flex-grow\">\n <textarea\n class=\"form-control fit-h p-r-16 p-l-16\"\n [attr.aria-label]=\"'Operations' | translate\"\n [(ngModel)]=\"config\"\n [disabled]=\"reloadingConfig || savingConfig\"\n c8y-spellcheck=\"false\"\n ></textarea>\n </div>\n @if (latestOperation !== undefined) {\n <c8y-operation-details\n class=\"bg-level-2 p-0\"\n [operation]=\"latestOperation\"\n ></c8y-operation-details>\n }\n @if (showTextBasedConfigSave) {\n <div class=\"card-footer fit-w separator\">\n <button\n class=\"btn btn-primary\"\n id=\"send-config-btn\"\n type=\"button\"\n (click)=\"updateConfiguration(config)\"\n [disabled]=\"reloadingConfig || savingConfig || !config\"\n [ngClass]=\"{ 'btn-pending': savingConfig }\"\n >\n @if (!savingConfig) {\n <span title=\"{{ 'Send' | translate }}\">\n {{ 'Send configuration to device' | translate }}\n </span>\n }\n @if (savingConfig) {\n <span title=\"{{ 'Sending\u2026' | translate }}\">\n {{ 'Sending\u2026' | translate }}\n </span>\n }\n </button>\n </div>\n }\n</div>\n" }]
|
|
153
|
-
}], ctorParameters: () => [{ type: i1.ActivatedRoute }, { type: i2.AlertService }, { type: i3.RepositoryService }, { type: DeviceConfigurationService }, { type:
|
|
173
|
+
}], ctorParameters: () => [{ type: i1.ActivatedRoute }, { type: i2.AlertService }, { type: i3.RepositoryService }, { type: DeviceConfigurationService }, { type: i3$1.InventoryService }] });
|
|
154
174
|
|
|
155
175
|
class DeviceConfigurationGuard {
|
|
156
176
|
constructor(deviceConfigurationService) {
|
|
@@ -197,91 +217,368 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
197
217
|
args: [{ name: 'configurationFilterPipe' }]
|
|
198
218
|
}] });
|
|
199
219
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
220
|
+
const EXT_LANGUAGE_MAP = {
|
|
221
|
+
json: 'json',
|
|
222
|
+
yaml: 'yaml',
|
|
223
|
+
yml: 'yaml',
|
|
224
|
+
xml: 'xml',
|
|
225
|
+
html: 'html',
|
|
226
|
+
css: 'css',
|
|
227
|
+
ts: 'typescript',
|
|
228
|
+
js: 'javascript',
|
|
229
|
+
md: 'markdown',
|
|
230
|
+
toml: 'ini',
|
|
231
|
+
properties: 'ini',
|
|
232
|
+
ini: 'ini',
|
|
233
|
+
conf: 'ini',
|
|
234
|
+
cfg: 'ini',
|
|
235
|
+
sh: 'shell'
|
|
236
|
+
};
|
|
237
|
+
function detectLanguage(filename) {
|
|
238
|
+
const ext = (filename.split('.').pop() ?? '').toLowerCase();
|
|
239
|
+
return EXT_LANGUAGE_MAP[ext] ?? 'plaintext';
|
|
240
|
+
}
|
|
241
|
+
const EXT_MIME_MAP = {
|
|
242
|
+
json: 'application/json',
|
|
243
|
+
xml: 'application/xml',
|
|
244
|
+
yaml: 'application/yaml',
|
|
245
|
+
yml: 'application/yaml',
|
|
246
|
+
js: 'application/javascript',
|
|
247
|
+
ts: 'application/typescript',
|
|
248
|
+
html: 'text/html',
|
|
249
|
+
css: 'text/css',
|
|
250
|
+
md: 'text/markdown',
|
|
251
|
+
sh: 'application/x-sh',
|
|
252
|
+
toml: 'application/toml'
|
|
253
|
+
};
|
|
254
|
+
function mimeForFilename(filename) {
|
|
255
|
+
const ext = (filename.split('.').pop() ?? '').toLowerCase();
|
|
256
|
+
return EXT_MIME_MAP[ext] ?? 'text/plain';
|
|
257
|
+
}
|
|
258
|
+
/** Sentinel value meaning "derive language from filename". */
|
|
259
|
+
const AUTO_LANGUAGE = 'auto';
|
|
260
|
+
const EDITOR_LANGUAGES = [
|
|
261
|
+
{ value: AUTO_LANGUAGE, label: gettext('Auto (from filename)') },
|
|
262
|
+
{ value: 'json', label: gettext('JSON') },
|
|
263
|
+
{ value: 'javascript', label: gettext('JavaScript') },
|
|
264
|
+
{ value: 'typescript', label: gettext('TypeScript') },
|
|
265
|
+
{ value: 'css', label: gettext('CSS') },
|
|
266
|
+
{ value: 'html', label: gettext('HTML') },
|
|
267
|
+
{ value: 'yaml', label: gettext('YAML') },
|
|
268
|
+
{ value: 'markdown', label: gettext('Markdown') },
|
|
269
|
+
{ value: 'xml', label: gettext('XML') },
|
|
270
|
+
{ value: 'ini', label: gettext('INI / TOML / Properties') },
|
|
271
|
+
{ value: 'shell', label: gettext('Shell') },
|
|
272
|
+
{ value: 'plaintext', label: gettext('Plain text') }
|
|
273
|
+
];
|
|
274
|
+
class ConfigurationDetailComponent {
|
|
275
|
+
constructor() {
|
|
276
|
+
this.repositoryService = inject(RepositoryService);
|
|
277
|
+
this.bottomDrawerRef = inject(BottomDrawerRef);
|
|
278
|
+
this.alert = inject(AlertService);
|
|
279
|
+
this.inventoryBinaryService = inject(InventoryBinaryService);
|
|
280
|
+
this.cdr = inject(ChangeDetectorRef);
|
|
281
|
+
this.binary = { file: undefined, url: undefined };
|
|
282
|
+
this.mo = {};
|
|
283
|
+
this.form = new FormGroup({
|
|
284
|
+
version: new FormControl('', [
|
|
285
|
+
Validators.required,
|
|
286
|
+
Validators.maxLength(254),
|
|
287
|
+
Validators.pattern(ValidationPattern.rules.noWhiteSpaceOnly.pattern)
|
|
288
|
+
]),
|
|
289
|
+
deviceType: new FormControl('', [
|
|
290
|
+
Validators.maxLength(254),
|
|
291
|
+
Validators.pattern(ValidationPattern.rules.noWhiteSpaceOnly.pattern)
|
|
292
|
+
]),
|
|
293
|
+
description: new FormControl('', [
|
|
294
|
+
Validators.maxLength(254),
|
|
295
|
+
Validators.pattern(ValidationPattern.rules.noWhiteSpaceOnly.pattern)
|
|
296
|
+
]),
|
|
297
|
+
uploadChoice: new FormControl('uploadBinary'),
|
|
298
|
+
editorFilename: new FormControl('', [
|
|
299
|
+
Validators.pattern(ValidationPattern.rules.noWhiteSpaceOnly.pattern)
|
|
300
|
+
]),
|
|
301
|
+
editorLanguage: new FormControl(EDITOR_LANGUAGES[0]),
|
|
302
|
+
editorContent: new FormControl('')
|
|
303
|
+
});
|
|
304
|
+
this.configurationTypeMO = {};
|
|
305
|
+
this.saving = signal(false, ...(ngDevMode ? [{ debugName: "saving" }] : /* istanbul ignore next */ []));
|
|
306
|
+
this.pattern = signal('', ...(ngDevMode ? [{ debugName: "pattern" }] : /* istanbul ignore next */ []));
|
|
307
|
+
this.isExternalUrl = signal(false, ...(ngDevMode ? [{ debugName: "isExternalUrl" }] : /* istanbul ignore next */ []));
|
|
308
|
+
this.ValidationPattern = ValidationPattern;
|
|
309
|
+
this.textForConfigurationUrlPopover = gettext(`Path for binaries can vary depending on device agent implementation, for example:
|
|
310
|
+
/configuration/binaries/configuration1.bin
|
|
311
|
+
https://configuration/binary/123
|
|
312
|
+
ftp://configuration/binary/123.tar.gz
|
|
313
|
+
Configurations with external URLs only work with the configuration typed devices (file-based configuration), not with devices with a legacy configuration.
|
|
314
|
+
`);
|
|
205
315
|
this.result = new Promise((resolve, reject) => {
|
|
206
316
|
this._save = resolve;
|
|
207
317
|
this._cancel = reject;
|
|
208
318
|
});
|
|
319
|
+
this.settled = false;
|
|
320
|
+
this.editorFilenameValue = toSignal(this.form.controls.editorFilename.valueChanges, {
|
|
321
|
+
initialValue: ''
|
|
322
|
+
});
|
|
323
|
+
this.editorLanguageValue = toSignal(this.form.controls.editorLanguage.valueChanges, {
|
|
324
|
+
initialValue: EDITOR_LANGUAGES[0]
|
|
325
|
+
});
|
|
326
|
+
/** User's manual pick (when not "auto") wins over auto-detection from filename. */
|
|
327
|
+
this.editorLanguage = computed(() => {
|
|
328
|
+
const picked = this.editorLanguageValue()?.value;
|
|
329
|
+
return !picked || picked === AUTO_LANGUAGE
|
|
330
|
+
? detectLanguage(this.editorFilenameValue() ?? '')
|
|
331
|
+
: picked;
|
|
332
|
+
}, ...(ngDevMode ? [{ debugName: "editorLanguage" }] : /* istanbul ignore next */ []));
|
|
333
|
+
this.editorLanguages = EDITOR_LANGUAGES;
|
|
334
|
+
this.submitButtonTitle = computed(() => this.mo?.id ? gettext('Update configuration') : gettext('Add configuration'), ...(ngDevMode ? [{ debugName: "submitButtonTitle" }] : /* istanbul ignore next */ []));
|
|
335
|
+
this.form.controls.uploadChoice.valueChanges.pipe(takeUntilDestroyed()).subscribe(choice => {
|
|
336
|
+
const ctrl = this.form.controls.editorFilename;
|
|
337
|
+
if (choice === 'editInline') {
|
|
338
|
+
ctrl.addValidators(Validators.required);
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
ctrl.removeValidators(Validators.required);
|
|
342
|
+
}
|
|
343
|
+
ctrl.updateValueAndValidity();
|
|
344
|
+
});
|
|
345
|
+
// Drawer can be closed externally (Escape, navigation, programmatic close).
|
|
346
|
+
// Make sure the result promise is always settled, otherwise callers awaiting it hang.
|
|
347
|
+
this.bottomDrawerRef.onClosed$.pipe(takeUntilDestroyed()).subscribe(() => {
|
|
348
|
+
if (!this.settled) {
|
|
349
|
+
this.settled = true;
|
|
350
|
+
this._cancel('Drawer closed');
|
|
351
|
+
}
|
|
352
|
+
});
|
|
209
353
|
}
|
|
210
|
-
async
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
354
|
+
async ngOnInit() {
|
|
355
|
+
this.configs = await this.repositoryService.listRepositoryEntries(RepositoryType.CONFIGURATION);
|
|
356
|
+
this.setPipe('');
|
|
357
|
+
// Prefill when editing an existing entry (mo.id) or when the caller supplies
|
|
358
|
+
// content to seed a new entry — e.g. saving a device snapshot to the repository.
|
|
359
|
+
const shouldPrefill = !!(this.mo?.id || this.binary?.file || this.version);
|
|
360
|
+
if (shouldPrefill) {
|
|
361
|
+
this.configurationTypeMO = this.mo;
|
|
362
|
+
const hasExternalUrl = !!this.binary?.url && !this.inventoryBinaryService.getIdFromUrl(this.binary.url);
|
|
363
|
+
if (hasExternalUrl) {
|
|
364
|
+
this.isExternalUrl.set(true);
|
|
365
|
+
this.form.patchValue({
|
|
366
|
+
version: this.version ?? '',
|
|
367
|
+
deviceType: this.deviceType ?? '',
|
|
368
|
+
description: this.description ?? '',
|
|
369
|
+
uploadChoice: 'uploadUrl'
|
|
370
|
+
});
|
|
227
371
|
}
|
|
228
|
-
|
|
229
|
-
|
|
372
|
+
else {
|
|
373
|
+
const patchValues = {
|
|
374
|
+
version: this.version ?? '',
|
|
375
|
+
deviceType: this.deviceType ?? '',
|
|
376
|
+
description: this.description ?? '',
|
|
377
|
+
uploadChoice: 'editInline'
|
|
378
|
+
};
|
|
379
|
+
if (this.binary?.file) {
|
|
380
|
+
this.existingBinary = this.binary.file;
|
|
381
|
+
// Decide from the content, not the declared MIME type: platform-served binaries
|
|
382
|
+
// are often labelled text/plain, and their bytes must never open in the editor.
|
|
383
|
+
if (await isBinaryFile(this.binary.file, { ignoreMimeType: true })) {
|
|
384
|
+
patchValues.uploadChoice = 'uploadBinary';
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
patchValues.editorContent = await this.binary.file.text();
|
|
388
|
+
patchValues.editorFilename = this.binary.file.name;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
this.form.patchValue(patchValues);
|
|
230
392
|
}
|
|
393
|
+
this.form.markAsDirty();
|
|
231
394
|
}
|
|
395
|
+
this.cdr.markForCheck();
|
|
232
396
|
}
|
|
233
|
-
|
|
234
|
-
this.
|
|
235
|
-
this.
|
|
236
|
-
|
|
237
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SaveToRepositoryComponent, deps: [{ token: i1$1.BsModalRef }, { token: i2.AlertService }, { token: i3.RepositoryService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
238
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: SaveToRepositoryComponent, isStandalone: true, selector: "c8y-save-config-to-configuration-repository", ngImport: i0, template: "<div class=\"modal-header dialog-header\">\n <i c8yIcon=\"gears\"></i>\n <h4 id=\"modal-title\" translate>\n Save configuration\n </h4>\n</div>\n<div class=\"modal-body\" id=\"modal-body\">\n <form #saveConfigurationSnapshot=\"ngForm\" class=\"p-t-24\">\n <c8y-form-group>\n <label translate for=\"name\">Name</label>\n <input\n id=\"name\"\n type=\"text\"\n class=\"form-control\"\n autocomplete=\"off\"\n name=\"name\"\n [(ngModel)]=\"configSnapshot.name\"\n required\n />\n </c8y-form-group>\n <c8y-form-group>\n <label translate for=\"deviceType\">Device type</label>\n <input\n id=\"deviceType\"\n class=\"form-control\"\n rows=\"6\"\n name=\"deviceType\"\n [(ngModel)]=\"configSnapshot.deviceType\"\n />\n </c8y-form-group>\n <c8y-form-group>\n <label translate for=\"description\">Description</label>\n <input\n type=\"text\"\n id=\"description\"\n class=\"form-control\"\n maxlength=\"254\"\n autocomplete=\"off\"\n name=\"description\"\n [(ngModel)]=\"configSnapshot.description\"\n />\n </c8y-form-group>\n <c8y-form-group>\n <label translate for=\"configurationType\">Configuration type</label>\n <input\n id=\"configurationType\"\n class=\"form-control\"\n rows=\"6\"\n name=\"configurationType\"\n [(ngModel)]=\"configSnapshot.configurationType\"\n />\n </c8y-form-group>\n </form>\n</div>\n<div class=\"modal-footer\">\n <button title=\"{{ 'Cancel' | translate }}\" class=\"btn btn-default\" (click)=\"close()\" translate>\n Cancel\n </button>\n\n <button\n title=\"{{ 'Save configuration to repository' | translate }}\"\n class=\"btn btn-primary\"\n (click)=\"save()\"\n [disabled]=\"saveConfigurationSnapshot.form.invalid\"\n translate\n >\n Save\n </button>\n</div>\n", dependencies: [{ kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i4$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i4$1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i4$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i4$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
|
|
239
|
-
}
|
|
240
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SaveToRepositoryComponent, decorators: [{
|
|
241
|
-
type: Component,
|
|
242
|
-
args: [{ selector: 'c8y-save-config-to-configuration-repository', imports: [
|
|
243
|
-
IconDirective,
|
|
244
|
-
C8yTranslateDirective,
|
|
245
|
-
FormsModule,
|
|
246
|
-
FormGroupComponent,
|
|
247
|
-
RequiredInputPlaceholderDirective,
|
|
248
|
-
C8yTranslatePipe
|
|
249
|
-
], template: "<div class=\"modal-header dialog-header\">\n <i c8yIcon=\"gears\"></i>\n <h4 id=\"modal-title\" translate>\n Save configuration\n </h4>\n</div>\n<div class=\"modal-body\" id=\"modal-body\">\n <form #saveConfigurationSnapshot=\"ngForm\" class=\"p-t-24\">\n <c8y-form-group>\n <label translate for=\"name\">Name</label>\n <input\n id=\"name\"\n type=\"text\"\n class=\"form-control\"\n autocomplete=\"off\"\n name=\"name\"\n [(ngModel)]=\"configSnapshot.name\"\n required\n />\n </c8y-form-group>\n <c8y-form-group>\n <label translate for=\"deviceType\">Device type</label>\n <input\n id=\"deviceType\"\n class=\"form-control\"\n rows=\"6\"\n name=\"deviceType\"\n [(ngModel)]=\"configSnapshot.deviceType\"\n />\n </c8y-form-group>\n <c8y-form-group>\n <label translate for=\"description\">Description</label>\n <input\n type=\"text\"\n id=\"description\"\n class=\"form-control\"\n maxlength=\"254\"\n autocomplete=\"off\"\n name=\"description\"\n [(ngModel)]=\"configSnapshot.description\"\n />\n </c8y-form-group>\n <c8y-form-group>\n <label translate for=\"configurationType\">Configuration type</label>\n <input\n id=\"configurationType\"\n class=\"form-control\"\n rows=\"6\"\n name=\"configurationType\"\n [(ngModel)]=\"configSnapshot.configurationType\"\n />\n </c8y-form-group>\n </form>\n</div>\n<div class=\"modal-footer\">\n <button title=\"{{ 'Cancel' | translate }}\" class=\"btn btn-default\" (click)=\"close()\" translate>\n Cancel\n </button>\n\n <button\n title=\"{{ 'Save configuration to repository' | translate }}\"\n class=\"btn btn-primary\"\n (click)=\"save()\"\n [disabled]=\"saveConfigurationSnapshot.form.invalid\"\n translate\n >\n Save\n </button>\n</div>\n" }]
|
|
250
|
-
}], ctorParameters: () => [{ type: i1$1.BsModalRef }, { type: i2.AlertService }, { type: i3.RepositoryService }] });
|
|
251
|
-
|
|
252
|
-
class SourceCodePreviewComponent {
|
|
253
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SourceCodePreviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
254
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: SourceCodePreviewComponent, isStandalone: true, selector: "c8y-source-code-preview", inputs: { isDisabled: "isDisabled", text: "text" }, ngImport: i0, template: "<textarea\n [disabled]=\"isDisabled\"\n class=\"text-monospace form-control no-resize flex-grow\"\n rows=\"4\"\n >{{ text }}</textarea\n>\n" }); }
|
|
255
|
-
}
|
|
256
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SourceCodePreviewComponent, decorators: [{
|
|
257
|
-
type: Component,
|
|
258
|
-
args: [{ selector: 'c8y-source-code-preview', template: "<textarea\n [disabled]=\"isDisabled\"\n class=\"text-monospace form-control no-resize flex-grow\"\n rows=\"4\"\n >{{ text }}</textarea\n>\n" }]
|
|
259
|
-
}], propDecorators: { isDisabled: [{
|
|
260
|
-
type: Input
|
|
261
|
-
}], text: [{
|
|
262
|
-
type: Input
|
|
263
|
-
}] } });
|
|
264
|
-
|
|
265
|
-
class ConfigurationPreviewComponent {
|
|
266
|
-
set configurationType(type) {
|
|
267
|
-
this._configurationType = type;
|
|
268
|
-
this.setOperation(type);
|
|
397
|
+
cancel() {
|
|
398
|
+
this.settled = true;
|
|
399
|
+
this._cancel('User canceled');
|
|
400
|
+
this.bottomDrawerRef.close();
|
|
269
401
|
}
|
|
270
|
-
|
|
271
|
-
|
|
402
|
+
setPipe(filterStr) {
|
|
403
|
+
this.pattern.set(filterStr);
|
|
404
|
+
this.filterPipe = pipe(map(data => uniqBy(data, 'configurationType')), map(data => data.filter(mo => mo.configurationType &&
|
|
405
|
+
mo.configurationType.toLowerCase().indexOf(filterStr.toLowerCase()) > -1)));
|
|
272
406
|
}
|
|
273
|
-
|
|
274
|
-
this.
|
|
275
|
-
this.
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
this.
|
|
279
|
-
this.
|
|
280
|
-
this.
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
this.
|
|
284
|
-
this.
|
|
407
|
+
onUploadChoiceChange(choice) {
|
|
408
|
+
this.form.controls.uploadChoice.setValue(choice);
|
|
409
|
+
this.form.markAsDirty();
|
|
410
|
+
}
|
|
411
|
+
onFileBinaryChange(file) {
|
|
412
|
+
this.binary = { ...this.binary, file };
|
|
413
|
+
this.form.markAsDirty();
|
|
414
|
+
this.cdr.markForCheck();
|
|
415
|
+
}
|
|
416
|
+
onFileUrlChange(url) {
|
|
417
|
+
this.binary = { ...this.binary, url };
|
|
418
|
+
this.form.markAsDirty();
|
|
419
|
+
this.cdr.markForCheck();
|
|
420
|
+
}
|
|
421
|
+
onTextFilePicked({ name, content }) {
|
|
422
|
+
this.form.patchValue({
|
|
423
|
+
uploadChoice: 'editInline',
|
|
424
|
+
editorFilename: name,
|
|
425
|
+
editorContent: content
|
|
426
|
+
});
|
|
427
|
+
this.form.markAsDirty();
|
|
428
|
+
this.cdr.markForCheck();
|
|
429
|
+
}
|
|
430
|
+
get isSaveDisabled() {
|
|
431
|
+
if (this.saving() || !this.form.valid || this.form.pristine) {
|
|
432
|
+
return true;
|
|
433
|
+
}
|
|
434
|
+
const { uploadChoice, editorFilename, editorContent } = this.form.value;
|
|
435
|
+
if (uploadChoice === 'uploadBinary') {
|
|
436
|
+
return !this.binary?.file;
|
|
437
|
+
}
|
|
438
|
+
if (uploadChoice === 'uploadUrl') {
|
|
439
|
+
return !this.binary?.url?.trim();
|
|
440
|
+
}
|
|
441
|
+
if (uploadChoice === 'editInline') {
|
|
442
|
+
return !editorFilename?.trim() || !editorContent;
|
|
443
|
+
}
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
async save() {
|
|
447
|
+
if (this.isSaveDisabled) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
try {
|
|
451
|
+
this.saving.set(true);
|
|
452
|
+
const { version, deviceType, description, uploadChoice, editorFilename, editorContent } = this.form.value;
|
|
453
|
+
let binary = { ...this.binary };
|
|
454
|
+
if (uploadChoice === 'uploadUrl') {
|
|
455
|
+
binary = { url: this.binary?.url };
|
|
456
|
+
}
|
|
457
|
+
else if (uploadChoice === 'editInline') {
|
|
458
|
+
binary = {
|
|
459
|
+
file: new File([editorContent], editorFilename, { type: mimeForFilename(editorFilename) })
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
else if (this.existingBinary === this.binary?.file && this.binary?.url) {
|
|
463
|
+
// Editing an entry whose binary is unchanged: reuse the persisted url instead of
|
|
464
|
+
// re-uploading. Only valid when a url exists — a new entry (e.g. from a device
|
|
465
|
+
// snapshot) has no persisted binary, so its file must still be uploaded.
|
|
466
|
+
binary = { ...this.binary, file: undefined };
|
|
467
|
+
}
|
|
468
|
+
await this.repositoryService.create({
|
|
469
|
+
version,
|
|
470
|
+
description,
|
|
471
|
+
binary,
|
|
472
|
+
deviceType,
|
|
473
|
+
configurationType: this.configurationTypeMO?.configurationType
|
|
474
|
+
}, RepositoryType.CONFIGURATION, this.mo);
|
|
475
|
+
this.alert.success(this.mo?.id ? gettext('Configuration updated.') : gettext('Configuration created.'));
|
|
476
|
+
this.settled = true;
|
|
477
|
+
this._save();
|
|
478
|
+
this.bottomDrawerRef.close();
|
|
479
|
+
}
|
|
480
|
+
catch (ex) {
|
|
481
|
+
// Keep the drawer open so the user can fix and retry.
|
|
482
|
+
// Do NOT settle the result promise here.
|
|
483
|
+
this.alert.addServerFailure(ex);
|
|
484
|
+
}
|
|
485
|
+
finally {
|
|
486
|
+
this.saving.set(false);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ConfigurationDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
490
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: ConfigurationDetailComponent, isStandalone: true, selector: "c8y-configuration-detail", inputs: { version: "version", description: "description", binary: "binary", deviceType: "deviceType", mo: "mo" }, host: { classAttribute: "d-contents" }, ngImport: i0, template: "<div class=\"card-header j-c-center separator flex-no-shrink p-24\">\n <i\n class=\"m-r-8\"\n [c8yIcon]=\"'cogs'\"\n aria-hidden=\"true\"\n ></i>\n <h4 id=\"drawerTitle\">\n @if (mo?.id) {\n {{ 'Update configuration' | translate }}\n } @else {\n {{ 'Add configuration' | translate }}\n }\n </h4>\n</div>\n\n<form\n class=\"d-contents\"\n [formGroup]=\"form\"\n (ngSubmit)=\"save()\"\n>\n <div\n class=\"card-body inner-scroll p-24\"\n [class.d-col]=\"form.value.uploadChoice === 'editInline'\"\n >\n <div class=\"row\">\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confVersion\"\n translate\n >\n Name\n </label>\n <input\n class=\"form-control\"\n id=\"confVersion\"\n placeholder=\"{{ 'e.g.' | translate }} hosts\"\n aria-required=\"true\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"version\"\n />\n </c8y-form-group>\n\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confDeviceType\"\n translate\n >\n Device type\n </label>\n <input\n class=\"form-control\"\n id=\"confDeviceType\"\n placeholder=\"{{ 'e.g.' | translate }} c8y_Linux\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"deviceType\"\n />\n </c8y-form-group>\n\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confDescription\"\n translate\n >\n Description\n </label>\n <input\n class=\"form-control\"\n id=\"confDescription\"\n placeholder=\"{{ 'e.g.' | translate }} Host configuration\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"description\"\n />\n </c8y-form-group>\n\n <c8y-form-group class=\"col-sm-3\">\n <label translate>Configuration type</label>\n <c8y-typeahead\n placeholder=\"{{ 'e.g.' | translate }} ssh\"\n name=\"confType\"\n data-cy=\"conf-detail--configuration-type\"\n [ngModel]=\"configurationTypeMO\"\n [ngModelOptions]=\"{ standalone: true }\"\n (ngModelChange)=\"configurationTypeMO = $event\"\n maxlength=\"254\"\n (onSearch)=\"setPipe($event)\"\n displayProperty=\"configurationType\"\n >\n @let patternStr = pattern();\n <c8y-li\n class=\"p-l-8 p-r-8 c8y-list__item--link\"\n *c8yFor=\"let config of configs; pipe: filterPipe; notFound: notFoundTemplate\"\n (click)=\"configurationTypeMO = config; setPipe('')\"\n [active]=\"configurationTypeMO === config\"\n >\n <c8y-highlight\n [text]=\"config.configurationType || '--'\"\n [pattern]=\"patternStr\"\n ></c8y-highlight>\n </c8y-li>\n <ng-template #notFoundTemplate>\n @if (patternStr.length > 0) {\n <c8y-li class=\"bg-level-2 p-8\">\n <span translate>No match found.</span>\n <button\n class=\"btn btn-primary btn-xs m-l-8\"\n title=\"{{ 'Add new`configuration type`' | translate }}\"\n type=\"button\"\n translate\n >\n Add new`configuration type`\n </button>\n </c8y-li>\n }\n </ng-template>\n </c8y-typeahead>\n </c8y-form-group>\n </div>\n\n <fieldset\n class=\"d-col\"\n [class.flex-grow]=\"form.value.uploadChoice === 'editInline'\"\n [class.min-height-0]=\"form.value.uploadChoice === 'editInline'\"\n >\n <legend\n class=\"legend form-block m-t-40\"\n translate\n >\n Configuration file\n </legend>\n\n @if (isExternalUrl()) {\n <p\n class=\"text-muted m-b-8\"\n translate\n >\n Content is sourced from a remote URL and cannot be edited inline.\n </p>\n <c8y-form-group>\n <label\n for=\"confFileUrl\"\n translate\n >\n File URL\n </label>\n <input\n class=\"form-control\"\n id=\"confFileUrl\"\n placeholder=\"https://...\"\n type=\"url\"\n autocomplete=\"off\"\n [ngModel]=\"binary.url\"\n [ngModelOptions]=\"{ standalone: true }\"\n (ngModelChange)=\"binary = { url: $event }; form.markAsDirty()\"\n />\n </c8y-form-group>\n } @else {\n <c8y-file-picker\n [maxAllowedFiles]=\"1\"\n [allowedUploadChoices]=\"['uploadBinary', 'uploadUrl', 'editInline']\"\n [uploadChoice]=\"form.controls.uploadChoice.value\"\n (uploadChoiceChange)=\"onUploadChoiceChange($event)\"\n [fileUrl]=\"binary.url\"\n (fileUrlChange)=\"onFileUrlChange($event)\"\n [fileBinary]=\"binary.file\"\n (fileBinaryChange)=\"onFileBinaryChange($event)\"\n (textFilePicked)=\"onTextFilePicked($event)\"\n [fileUrlPopover]=\"textForConfigurationUrlPopover\"\n >\n <ng-container c8yInlineEditor>\n <div class=\"row\">\n <c8y-form-group class=\"col-sm-9\">\n <label\n for=\"confEditorFilename\"\n translate\n >\n Filename\n </label>\n <input\n class=\"form-control\"\n id=\"confEditorFilename\"\n placeholder=\"config.json\"\n aria-required=\"true\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"editorFilename\"\n />\n </c8y-form-group>\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confEditorLanguage\"\n translate\n >\n Language\n </label>\n <c8y-select\n aria-label=\"{{ 'Editor language' | translate }}\"\n id=\"confEditorLanguage\"\n [items]=\"editorLanguages\"\n formControlName=\"editorLanguage\"\n ></c8y-select>\n </c8y-form-group>\n </div>\n <c8y-form-group class=\"d-col flex-grow min-height-0 m-b-0\">\n <label\n for=\"confEditorContent\"\n translate\n >\n Content\n </label>\n <c8y-editor\n class=\"flex-grow\"\n style=\"min-height: 95px\"\n id=\"confEditorContent\"\n formControlName=\"editorContent\"\n monacoEditorMarkerValidator\n [editorOptions]=\"{ language: editorLanguage() }\"\n ></c8y-editor>\n <c8y-messages></c8y-messages>\n </c8y-form-group>\n </ng-container>\n </c8y-file-picker>\n }\n </fieldset>\n </div>\n\n <div class=\"card-footer j-c-between separator flex-no-shrink p-24\">\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Cancel' | translate }}\"\n type=\"button\"\n (click)=\"cancel()\"\n [disabled]=\"saving()\"\n >\n <span translate>Cancel</span>\n </button>\n <button\n class=\"btn btn-primary\"\n [class.btn-pending]=\"saving()\"\n [title]=\"submitButtonTitle() | translate\"\n type=\"submit\"\n [disabled]=\"isSaveDisabled\"\n >\n {{ submitButtonTitle() | translate }}\n </button>\n </div>\n</form>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "component", type: TypeaheadComponent, selector: "c8y-typeahead", inputs: ["required", "maxlength", "disabled", "allowFreeEntries", "placeholder", "displayProperty", "icon", "name", "autoClose", "hideNew", "container", "selected", "title", "highlightFirstItem"], outputs: ["onSearch", "onIconClick"] }, { kind: "directive", type: ForOfDirective, selector: "[c8yFor]", inputs: ["c8yForOf", "c8yForLoadMore", "c8yForPipe", "c8yForNotFound", "c8yForMaxIterations", "c8yForLoadingTemplate", "c8yForLoadNextLabel", "c8yForLoadingLabel", "c8yForRealtime", "c8yForRealtimeOptions", "c8yForComparator", "c8yForEnableVirtualScroll", "c8yForVirtualScrollElementSize", "c8yForVirtualScrollStrategy", "c8yForVirtualScrollContainerHeight"], outputs: ["c8yForCount", "c8yForChange", "c8yForLoadMoreComponent"] }, { kind: "component", type: ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "component", type: HighlightComponent, selector: "c8y-highlight", inputs: ["pattern", "text", "elementClass", "shouldTrimPattern"] }, { kind: "component", type: FilePickerComponent, selector: "c8y-file-picker", inputs: ["maxAllowedFiles", "uploadChoice", "allowedUploadChoices", "fileUrl", "fileBinary", "config", "filePickerIndex", "fileUrlPopover", "editInlineConfirmTitle", "editInlineConfirmMessage"], outputs: ["onFilesPicked", "uploadChoiceChange", "fileBinaryChange", "fileUrlChange", "textFilePicked"] }, { kind: "component", type: EditorComponent, selector: "c8y-editor", inputs: ["editorOptions", "theme"], outputs: ["editorInit"] }, { kind: "directive", type: MonacoEditorMarkerValidatorDirective, selector: "c8y-editor [monacoEditorMarkerValidator]" }, { kind: "component", type: MessagesComponent, selector: "c8y-messages", inputs: ["show", "defaults", "helpMessage", "additionalMessages"] }, { kind: "directive", type: RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "component", type: SelectComponent, selector: "c8y-select", inputs: ["placeholder", "items", "selected", "container", "multi", "canSelectWithSpace", "disabled", "autoClose", "insideClick", "required", "canDeselect", "name", "icon", "filterItems"], outputs: ["onSelect", "onDeselect", "onIconClick"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
491
|
+
}
|
|
492
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ConfigurationDetailComponent, decorators: [{
|
|
493
|
+
type: Component,
|
|
494
|
+
args: [{ selector: 'c8y-configuration-detail', changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'd-contents' }, imports: [
|
|
495
|
+
ReactiveFormsModule,
|
|
496
|
+
FormsModule,
|
|
497
|
+
IconDirective,
|
|
498
|
+
C8yTranslateDirective,
|
|
499
|
+
FormGroupComponent,
|
|
500
|
+
TypeaheadComponent,
|
|
501
|
+
ForOfDirective,
|
|
502
|
+
ListItemComponent,
|
|
503
|
+
HighlightComponent,
|
|
504
|
+
FilePickerComponent,
|
|
505
|
+
NgClass,
|
|
506
|
+
C8yTranslatePipe,
|
|
507
|
+
EditorComponent,
|
|
508
|
+
MonacoEditorMarkerValidatorDirective,
|
|
509
|
+
MessagesComponent,
|
|
510
|
+
RequiredInputPlaceholderDirective,
|
|
511
|
+
SelectComponent
|
|
512
|
+
], template: "<div class=\"card-header j-c-center separator flex-no-shrink p-24\">\n <i\n class=\"m-r-8\"\n [c8yIcon]=\"'cogs'\"\n aria-hidden=\"true\"\n ></i>\n <h4 id=\"drawerTitle\">\n @if (mo?.id) {\n {{ 'Update configuration' | translate }}\n } @else {\n {{ 'Add configuration' | translate }}\n }\n </h4>\n</div>\n\n<form\n class=\"d-contents\"\n [formGroup]=\"form\"\n (ngSubmit)=\"save()\"\n>\n <div\n class=\"card-body inner-scroll p-24\"\n [class.d-col]=\"form.value.uploadChoice === 'editInline'\"\n >\n <div class=\"row\">\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confVersion\"\n translate\n >\n Name\n </label>\n <input\n class=\"form-control\"\n id=\"confVersion\"\n placeholder=\"{{ 'e.g.' | translate }} hosts\"\n aria-required=\"true\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"version\"\n />\n </c8y-form-group>\n\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confDeviceType\"\n translate\n >\n Device type\n </label>\n <input\n class=\"form-control\"\n id=\"confDeviceType\"\n placeholder=\"{{ 'e.g.' | translate }} c8y_Linux\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"deviceType\"\n />\n </c8y-form-group>\n\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confDescription\"\n translate\n >\n Description\n </label>\n <input\n class=\"form-control\"\n id=\"confDescription\"\n placeholder=\"{{ 'e.g.' | translate }} Host configuration\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"description\"\n />\n </c8y-form-group>\n\n <c8y-form-group class=\"col-sm-3\">\n <label translate>Configuration type</label>\n <c8y-typeahead\n placeholder=\"{{ 'e.g.' | translate }} ssh\"\n name=\"confType\"\n data-cy=\"conf-detail--configuration-type\"\n [ngModel]=\"configurationTypeMO\"\n [ngModelOptions]=\"{ standalone: true }\"\n (ngModelChange)=\"configurationTypeMO = $event\"\n maxlength=\"254\"\n (onSearch)=\"setPipe($event)\"\n displayProperty=\"configurationType\"\n >\n @let patternStr = pattern();\n <c8y-li\n class=\"p-l-8 p-r-8 c8y-list__item--link\"\n *c8yFor=\"let config of configs; pipe: filterPipe; notFound: notFoundTemplate\"\n (click)=\"configurationTypeMO = config; setPipe('')\"\n [active]=\"configurationTypeMO === config\"\n >\n <c8y-highlight\n [text]=\"config.configurationType || '--'\"\n [pattern]=\"patternStr\"\n ></c8y-highlight>\n </c8y-li>\n <ng-template #notFoundTemplate>\n @if (patternStr.length > 0) {\n <c8y-li class=\"bg-level-2 p-8\">\n <span translate>No match found.</span>\n <button\n class=\"btn btn-primary btn-xs m-l-8\"\n title=\"{{ 'Add new`configuration type`' | translate }}\"\n type=\"button\"\n translate\n >\n Add new`configuration type`\n </button>\n </c8y-li>\n }\n </ng-template>\n </c8y-typeahead>\n </c8y-form-group>\n </div>\n\n <fieldset\n class=\"d-col\"\n [class.flex-grow]=\"form.value.uploadChoice === 'editInline'\"\n [class.min-height-0]=\"form.value.uploadChoice === 'editInline'\"\n >\n <legend\n class=\"legend form-block m-t-40\"\n translate\n >\n Configuration file\n </legend>\n\n @if (isExternalUrl()) {\n <p\n class=\"text-muted m-b-8\"\n translate\n >\n Content is sourced from a remote URL and cannot be edited inline.\n </p>\n <c8y-form-group>\n <label\n for=\"confFileUrl\"\n translate\n >\n File URL\n </label>\n <input\n class=\"form-control\"\n id=\"confFileUrl\"\n placeholder=\"https://...\"\n type=\"url\"\n autocomplete=\"off\"\n [ngModel]=\"binary.url\"\n [ngModelOptions]=\"{ standalone: true }\"\n (ngModelChange)=\"binary = { url: $event }; form.markAsDirty()\"\n />\n </c8y-form-group>\n } @else {\n <c8y-file-picker\n [maxAllowedFiles]=\"1\"\n [allowedUploadChoices]=\"['uploadBinary', 'uploadUrl', 'editInline']\"\n [uploadChoice]=\"form.controls.uploadChoice.value\"\n (uploadChoiceChange)=\"onUploadChoiceChange($event)\"\n [fileUrl]=\"binary.url\"\n (fileUrlChange)=\"onFileUrlChange($event)\"\n [fileBinary]=\"binary.file\"\n (fileBinaryChange)=\"onFileBinaryChange($event)\"\n (textFilePicked)=\"onTextFilePicked($event)\"\n [fileUrlPopover]=\"textForConfigurationUrlPopover\"\n >\n <ng-container c8yInlineEditor>\n <div class=\"row\">\n <c8y-form-group class=\"col-sm-9\">\n <label\n for=\"confEditorFilename\"\n translate\n >\n Filename\n </label>\n <input\n class=\"form-control\"\n id=\"confEditorFilename\"\n placeholder=\"config.json\"\n aria-required=\"true\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"editorFilename\"\n />\n </c8y-form-group>\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confEditorLanguage\"\n translate\n >\n Language\n </label>\n <c8y-select\n aria-label=\"{{ 'Editor language' | translate }}\"\n id=\"confEditorLanguage\"\n [items]=\"editorLanguages\"\n formControlName=\"editorLanguage\"\n ></c8y-select>\n </c8y-form-group>\n </div>\n <c8y-form-group class=\"d-col flex-grow min-height-0 m-b-0\">\n <label\n for=\"confEditorContent\"\n translate\n >\n Content\n </label>\n <c8y-editor\n class=\"flex-grow\"\n style=\"min-height: 95px\"\n id=\"confEditorContent\"\n formControlName=\"editorContent\"\n monacoEditorMarkerValidator\n [editorOptions]=\"{ language: editorLanguage() }\"\n ></c8y-editor>\n <c8y-messages></c8y-messages>\n </c8y-form-group>\n </ng-container>\n </c8y-file-picker>\n }\n </fieldset>\n </div>\n\n <div class=\"card-footer j-c-between separator flex-no-shrink p-24\">\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Cancel' | translate }}\"\n type=\"button\"\n (click)=\"cancel()\"\n [disabled]=\"saving()\"\n >\n <span translate>Cancel</span>\n </button>\n <button\n class=\"btn btn-primary\"\n [class.btn-pending]=\"saving()\"\n [title]=\"submitButtonTitle() | translate\"\n type=\"submit\"\n [disabled]=\"isSaveDisabled\"\n >\n {{ submitButtonTitle() | translate }}\n </button>\n </div>\n</form>\n" }]
|
|
513
|
+
}], ctorParameters: () => [], propDecorators: { version: [{
|
|
514
|
+
type: Input
|
|
515
|
+
}], description: [{
|
|
516
|
+
type: Input
|
|
517
|
+
}], binary: [{
|
|
518
|
+
type: Input
|
|
519
|
+
}], deviceType: [{
|
|
520
|
+
type: Input
|
|
521
|
+
}], mo: [{
|
|
522
|
+
type: Input
|
|
523
|
+
}] } });
|
|
524
|
+
|
|
525
|
+
class SourceCodePreviewComponent {
|
|
526
|
+
constructor() {
|
|
527
|
+
// Kept as a stable reference — a fresh object every change detection cycle would make the
|
|
528
|
+
// editor re-run updateOptions() needlessly. Only rebuilt when the relevant inputs change.
|
|
529
|
+
this.editorOptions = this.buildEditorOptions();
|
|
530
|
+
this.cdr = inject(ChangeDetectorRef);
|
|
531
|
+
}
|
|
532
|
+
ngOnChanges(changes) {
|
|
533
|
+
if (changes.isDisabled || changes.binaryType) {
|
|
534
|
+
this.editorOptions = this.buildEditorOptions();
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
onEditorInit() {
|
|
538
|
+
this.cdr.detectChanges();
|
|
539
|
+
}
|
|
540
|
+
buildEditorOptions() {
|
|
541
|
+
return {
|
|
542
|
+
readOnly: this.isDisabled,
|
|
543
|
+
language: mimeTypeToEditorLanguage(this.binaryType),
|
|
544
|
+
minimap: { enabled: false },
|
|
545
|
+
scrollBeyondLastLine: false
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SourceCodePreviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
549
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: SourceCodePreviewComponent, isStandalone: true, selector: "c8y-source-code-preview", inputs: { isDisabled: "isDisabled", text: "text", binaryType: "binaryType" }, usesOnChanges: true, ngImport: i0, template: "<c8y-editor\n class=\"flex-grow\"\n [ngModel]=\"text\"\n [editorOptions]=\"editorOptions\"\n (editorInit)=\"onEditorInit()\"\n></c8y-editor>\n", dependencies: [{ kind: "component", type: EditorComponent, selector: "c8y-editor", inputs: ["editorOptions", "theme"], outputs: ["editorInit"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
550
|
+
}
|
|
551
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SourceCodePreviewComponent, decorators: [{
|
|
552
|
+
type: Component,
|
|
553
|
+
args: [{ selector: 'c8y-source-code-preview', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [EditorComponent, FormsModule], template: "<c8y-editor\n class=\"flex-grow\"\n [ngModel]=\"text\"\n [editorOptions]=\"editorOptions\"\n (editorInit)=\"onEditorInit()\"\n></c8y-editor>\n" }]
|
|
554
|
+
}], propDecorators: { isDisabled: [{
|
|
555
|
+
type: Input
|
|
556
|
+
}], text: [{
|
|
557
|
+
type: Input
|
|
558
|
+
}], binaryType: [{
|
|
559
|
+
type: Input
|
|
560
|
+
}] } });
|
|
561
|
+
|
|
562
|
+
class ConfigurationPreviewComponent {
|
|
563
|
+
set configurationType(type) {
|
|
564
|
+
this._configurationType = type;
|
|
565
|
+
this.setOperation(type);
|
|
566
|
+
}
|
|
567
|
+
get configurationType() {
|
|
568
|
+
return this._configurationType;
|
|
569
|
+
}
|
|
570
|
+
constructor(deviceConfigurationService, operationRealtime, user, appState, repositoryService, operationService, alertService) {
|
|
571
|
+
this.deviceConfigurationService = deviceConfigurationService;
|
|
572
|
+
this.operationRealtime = operationRealtime;
|
|
573
|
+
this.user = user;
|
|
574
|
+
this.appState = appState;
|
|
575
|
+
this.repositoryService = repositoryService;
|
|
576
|
+
this.operationService = operationService;
|
|
577
|
+
this.alertService = alertService;
|
|
578
|
+
this.isLegacy = false;
|
|
579
|
+
this.editInRepository = new EventEmitter();
|
|
580
|
+
this.canCallAction = true;
|
|
581
|
+
this.deviceConfigurationOperation = DeviceConfigurationOperation;
|
|
285
582
|
}
|
|
286
583
|
async ngOnInit() {
|
|
287
584
|
this.setCanCallAction();
|
|
@@ -365,23 +662,22 @@ class ConfigurationPreviewComponent {
|
|
|
365
662
|
saveAs(blob, fileName);
|
|
366
663
|
}
|
|
367
664
|
async saveToRepository() {
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
665
|
+
const snapshot = this.configSnapshot;
|
|
666
|
+
// Prefer the original binary (real bytes + filename) so the drawer can tell text
|
|
667
|
+
// from binary: text opens in the inline editor, binary in the upload-binary option.
|
|
668
|
+
const file = snapshot.binaryFile ??
|
|
669
|
+
new File([snapshot.binary ?? ''], snapshot.name, { type: snapshot.binaryType });
|
|
670
|
+
const saved = await this.deviceConfigurationService.openConfigurationDrawer(ConfigurationDetailComponent, {
|
|
671
|
+
version: snapshot.name,
|
|
672
|
+
deviceType: snapshot.deviceType,
|
|
673
|
+
description: snapshot.description,
|
|
674
|
+
binary: { file },
|
|
675
|
+
// No id — the drawer treats this as a new entry, but carries the
|
|
676
|
+
// configuration type so the user can review and edit before saving.
|
|
677
|
+
mo: { configurationType: snapshot.configurationType }
|
|
678
|
+
});
|
|
679
|
+
if (saved) {
|
|
380
680
|
this.deviceConfigurationService.updateConfigurations(true);
|
|
381
|
-
modal.close();
|
|
382
|
-
}
|
|
383
|
-
catch (ex) {
|
|
384
|
-
// do nothing
|
|
385
681
|
}
|
|
386
682
|
}
|
|
387
683
|
hasPermission() {
|
|
@@ -409,21 +705,20 @@ class ConfigurationPreviewComponent {
|
|
|
409
705
|
this.deviceConfigurationService.updateConfigurations();
|
|
410
706
|
}
|
|
411
707
|
}
|
|
412
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ConfigurationPreviewComponent, deps: [{ token: DeviceConfigurationService }, { token: i2.OperationRealtimeService }, { token:
|
|
413
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
708
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ConfigurationPreviewComponent, deps: [{ token: DeviceConfigurationService }, { token: i2.OperationRealtimeService }, { token: i3$1.UserService }, { token: i2.AppStateService }, { token: i3.RepositoryService }, { token: i3$1.OperationService }, { token: i2.AlertService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
709
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: ConfigurationPreviewComponent, isStandalone: true, selector: "c8y-device-configuration-preview", inputs: { device: "device", configurationType: "configurationType", configSnapshot: "configSnapshot", canSaveSnapshot: "canSaveSnapshot", actionButtonText: "actionButtonText", actionButtonIcon: "actionButtonIcon", isLegacy: "isLegacy", operationToTrigger: "operationToTrigger", editableRepositoryMo: "editableRepositoryMo" }, outputs: { editInRepository: "editInRepository" }, ngImport: i0, template: "<div class=\"content-flex-55 p-b-16\">\n <div class=\"col-7 p-t-4\">\n <p>\n <span\n class=\"text-label-small text-uppercase m-r-4\"\n translate\n >Configuration</span\n >\n @if (configSnapshot?.name) {\n <span>\n <strong>{{ configSnapshot.name }}</strong>\n </span>\n } @else {\n ---\n }\n </p>\n <p>\n <span\n class=\"text-label-small text-uppercase m-r-4\"\n translate\n >Last updated</span\n >\n @if (configSnapshot?.time) {\n <small>\n {{ configSnapshot.time | c8yDate }}\n </small>\n } @else {\n ---\n }\n </p>\n </div>\n <div class=\"col-5 d-flex j-c-end gap-4\">\n @if (editableRepositoryMo) {\n <button\n class=\"btn btn-default btn-sm\"\n title=\"{{ 'Edit in repository' | translate }}\"\n type=\"button\"\n (click)=\"editInRepository.emit(editableRepositoryMo)\"\n >\n <i\n [c8yIcon]=\"'pencil'\"\n aria-hidden=\"true\"\n ></i>\n {{ 'Edit in repository' | translate }}\n </button>\n }\n @if (canCallAction) {\n <button\n class=\"btn btn-default btn-sm\"\n title=\"{{ actionButtonText | translate }}\"\n id=\"action-btn\"\n type=\"button\"\n (click)=\"createDeviceOperation()\"\n [disabled]=\"isCreateOperationDisabled()\"\n >\n <i [c8yIcon]=\"actionButtonIcon\"></i>\n {{ actionButtonText | translate }}\n </button>\n }\n </div>\n</div>\n@if (!configSnapshot?.binary && showBinary()) {\n <div class=\"c8y-empty-state text-left\">\n <h1 [c8yIcon]=\"'file-image-o'\"></h1>\n <p>\n <strong translate>No preview available.</strong>\n <br />\n @if (configSnapshot?.binary !== '') {\n <small translate> The file is not available. </small>\n } @else {\n <small translate>The file is empty.</small>\n }\n </p>\n </div>\n}\n@if (configSnapshot?.binary && showBinary()) {\n <div class=\"flex-grow d-flex d-col\">\n <c8y-source-code-preview\n class=\"d-contents\"\n [text]=\"configSnapshot.binary\"\n [binaryType]=\"configSnapshot.binaryType\"\n [isDisabled]=\"true\"\n ></c8y-source-code-preview>\n @if (canSaveSnapshot) {\n <div class=\"p-t-16\">\n <button\n class=\"btn btn-primary btn-sm pull-right m-l-8\"\n title=\"{{ 'Download' | translate }}\"\n type=\"button\"\n (click)=\"download()\"\n >\n {{ 'Download' | translate }}\n </button>\n @if (hasPermission()) {\n <button\n class=\"btn btn-default btn-sm pull-right\"\n title=\"{{ 'Save to repository' | translate }}\"\n type=\"button\"\n (click)=\"saveToRepository()\"\n >\n {{ 'Save to repository' | translate }}\n </button>\n }\n </div>\n }\n </div>\n}\n@if (showOperation()) {\n <div>\n <c8y-operation-details [operation]=\"operation\"></c8y-operation-details>\n </div>\n}\n", dependencies: [{ kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: SourceCodePreviewComponent, selector: "c8y-source-code-preview", inputs: ["isDisabled", "text", "binaryType"] }, { kind: "component", type: OperationDetailsComponent, selector: "c8y-operation-details", inputs: ["operation"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: DatePipe, name: "c8yDate" }] }); }
|
|
414
710
|
}
|
|
415
711
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ConfigurationPreviewComponent, decorators: [{
|
|
416
712
|
type: Component,
|
|
417
713
|
args: [{ selector: 'c8y-device-configuration-preview', imports: [
|
|
418
714
|
C8yTranslateDirective,
|
|
419
|
-
NgIf,
|
|
420
715
|
IconDirective,
|
|
421
716
|
SourceCodePreviewComponent,
|
|
422
717
|
OperationDetailsComponent,
|
|
423
718
|
C8yTranslatePipe,
|
|
424
719
|
DatePipe
|
|
425
|
-
], template: "<div class=\"content-flex-55 p-b-16\">\n <div class=\"col-7 p-t-4\">\n <p>\n <span
|
|
426
|
-
}], ctorParameters: () => [{ type: DeviceConfigurationService }, { type: i2.OperationRealtimeService }, { type:
|
|
720
|
+
], template: "<div class=\"content-flex-55 p-b-16\">\n <div class=\"col-7 p-t-4\">\n <p>\n <span\n class=\"text-label-small text-uppercase m-r-4\"\n translate\n >Configuration</span\n >\n @if (configSnapshot?.name) {\n <span>\n <strong>{{ configSnapshot.name }}</strong>\n </span>\n } @else {\n ---\n }\n </p>\n <p>\n <span\n class=\"text-label-small text-uppercase m-r-4\"\n translate\n >Last updated</span\n >\n @if (configSnapshot?.time) {\n <small>\n {{ configSnapshot.time | c8yDate }}\n </small>\n } @else {\n ---\n }\n </p>\n </div>\n <div class=\"col-5 d-flex j-c-end gap-4\">\n @if (editableRepositoryMo) {\n <button\n class=\"btn btn-default btn-sm\"\n title=\"{{ 'Edit in repository' | translate }}\"\n type=\"button\"\n (click)=\"editInRepository.emit(editableRepositoryMo)\"\n >\n <i\n [c8yIcon]=\"'pencil'\"\n aria-hidden=\"true\"\n ></i>\n {{ 'Edit in repository' | translate }}\n </button>\n }\n @if (canCallAction) {\n <button\n class=\"btn btn-default btn-sm\"\n title=\"{{ actionButtonText | translate }}\"\n id=\"action-btn\"\n type=\"button\"\n (click)=\"createDeviceOperation()\"\n [disabled]=\"isCreateOperationDisabled()\"\n >\n <i [c8yIcon]=\"actionButtonIcon\"></i>\n {{ actionButtonText | translate }}\n </button>\n }\n </div>\n</div>\n@if (!configSnapshot?.binary && showBinary()) {\n <div class=\"c8y-empty-state text-left\">\n <h1 [c8yIcon]=\"'file-image-o'\"></h1>\n <p>\n <strong translate>No preview available.</strong>\n <br />\n @if (configSnapshot?.binary !== '') {\n <small translate> The file is not available. </small>\n } @else {\n <small translate>The file is empty.</small>\n }\n </p>\n </div>\n}\n@if (configSnapshot?.binary && showBinary()) {\n <div class=\"flex-grow d-flex d-col\">\n <c8y-source-code-preview\n class=\"d-contents\"\n [text]=\"configSnapshot.binary\"\n [binaryType]=\"configSnapshot.binaryType\"\n [isDisabled]=\"true\"\n ></c8y-source-code-preview>\n @if (canSaveSnapshot) {\n <div class=\"p-t-16\">\n <button\n class=\"btn btn-primary btn-sm pull-right m-l-8\"\n title=\"{{ 'Download' | translate }}\"\n type=\"button\"\n (click)=\"download()\"\n >\n {{ 'Download' | translate }}\n </button>\n @if (hasPermission()) {\n <button\n class=\"btn btn-default btn-sm pull-right\"\n title=\"{{ 'Save to repository' | translate }}\"\n type=\"button\"\n (click)=\"saveToRepository()\"\n >\n {{ 'Save to repository' | translate }}\n </button>\n }\n </div>\n }\n </div>\n}\n@if (showOperation()) {\n <div>\n <c8y-operation-details [operation]=\"operation\"></c8y-operation-details>\n </div>\n}\n" }]
|
|
721
|
+
}], ctorParameters: () => [{ type: DeviceConfigurationService }, { type: i2.OperationRealtimeService }, { type: i3$1.UserService }, { type: i2.AppStateService }, { type: i3.RepositoryService }, { type: i3$1.OperationService }, { type: i2.AlertService }], propDecorators: { device: [{
|
|
427
722
|
type: Input
|
|
428
723
|
}], configurationType: [{
|
|
429
724
|
type: Input
|
|
@@ -439,6 +734,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
439
734
|
type: Input
|
|
440
735
|
}], operationToTrigger: [{
|
|
441
736
|
type: Input
|
|
737
|
+
}], editableRepositoryMo: [{
|
|
738
|
+
type: Input
|
|
739
|
+
}], editInRepository: [{
|
|
740
|
+
type: Output
|
|
442
741
|
}] } });
|
|
443
742
|
|
|
444
743
|
class DeviceConfigurationListComponent {
|
|
@@ -479,10 +778,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
479
778
|
}] } });
|
|
480
779
|
|
|
481
780
|
class DeviceConfigurationComponent {
|
|
482
|
-
constructor(route, deviceConfigurationService,
|
|
781
|
+
constructor(route, deviceConfigurationService, repositoryService) {
|
|
483
782
|
this.route = route;
|
|
484
783
|
this.deviceConfigurationService = deviceConfigurationService;
|
|
485
|
-
this.realtime = realtime;
|
|
486
784
|
this.repositoryService = repositoryService;
|
|
487
785
|
this.supportedConfigurations = [];
|
|
488
786
|
this.showBinaryBasedConfig = false;
|
|
@@ -525,9 +823,11 @@ class DeviceConfigurationComponent {
|
|
|
525
823
|
async onConfigTypeSelected(config) {
|
|
526
824
|
this.configurationType = config.name;
|
|
527
825
|
this.isLegacy = config.isLegacy;
|
|
826
|
+
this.selectedRepositoryMo = undefined;
|
|
528
827
|
this.updateSnapshots();
|
|
529
828
|
}
|
|
530
829
|
async onRepositoryConfigSelected(config) {
|
|
830
|
+
this.selectedRepositoryMo = config;
|
|
531
831
|
this.repositorySnapshot = {
|
|
532
832
|
id: config.id,
|
|
533
833
|
time: config.creationTime,
|
|
@@ -543,9 +843,10 @@ class DeviceConfigurationComponent {
|
|
|
543
843
|
});
|
|
544
844
|
if (binary) {
|
|
545
845
|
this.repositorySnapshot.binary = await binary.text();
|
|
846
|
+
this.repositorySnapshot.binaryType = binary.type || undefined;
|
|
546
847
|
}
|
|
547
848
|
}
|
|
548
|
-
catch
|
|
849
|
+
catch {
|
|
549
850
|
// do nothing
|
|
550
851
|
}
|
|
551
852
|
}
|
|
@@ -553,6 +854,7 @@ class DeviceConfigurationComponent {
|
|
|
553
854
|
async updateSnapshots(repositorySnapsOnly) {
|
|
554
855
|
this.reloading = true;
|
|
555
856
|
this.repositorySnapshot = undefined;
|
|
857
|
+
this.selectedRepositoryMo = undefined;
|
|
556
858
|
this.repositorySnapshots = await this.repositoryService.getSnapshotsFromRepository(this.device, this.configurationType);
|
|
557
859
|
if (!repositorySnapsOnly) {
|
|
558
860
|
this.configSnapshot = this.isLegacy
|
|
@@ -564,8 +866,27 @@ class DeviceConfigurationComponent {
|
|
|
564
866
|
}
|
|
565
867
|
this.reloading = false;
|
|
566
868
|
}
|
|
567
|
-
|
|
568
|
-
|
|
869
|
+
async openEditInRepository(mo) {
|
|
870
|
+
let fileBinary = null;
|
|
871
|
+
try {
|
|
872
|
+
fileBinary = await this.repositoryService.getBinaryFile(mo.url, { allowExternal: false });
|
|
873
|
+
}
|
|
874
|
+
catch {
|
|
875
|
+
// Binary unavailable — drawer still opens; editor will be empty
|
|
876
|
+
}
|
|
877
|
+
const saved = await this.deviceConfigurationService.openConfigurationDrawer(ConfigurationDetailComponent, {
|
|
878
|
+
version: mo.name,
|
|
879
|
+
deviceType: mo.deviceType,
|
|
880
|
+
description: mo.description,
|
|
881
|
+
binary: { file: fileBinary, url: mo.url },
|
|
882
|
+
mo
|
|
883
|
+
});
|
|
884
|
+
if (saved) {
|
|
885
|
+
this.updateSnapshots(true);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DeviceConfigurationComponent, deps: [{ token: i1.ActivatedRoute }, { token: DeviceConfigurationService }, { token: i3.RepositoryService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
889
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: DeviceConfigurationComponent, isStandalone: true, selector: "c8y-device-configuration", viewQueries: [{ propertyName: "textBasedConfigurationComponent", first: true, predicate: TextBasedConfigurationComponent, descendants: true }], ngImport: i0, template: "<c8y-action-bar-item [placement]=\"'right'\">\n <button\n class=\"btn btn-link\"\n title=\"{{ 'Reload' | translate }}\"\n (click)=\"updateSnapshots()\"\n >\n <i\n c8yIcon=\"refresh\"\n [ngClass]=\"{ 'icon-spin': reloading }\"\n ></i>\n {{ 'Reload' | translate }}\n </button>\n</c8y-action-bar-item>\n\n<div class=\"card content-fullpage card-has-tabs\">\n <tabset>\n @if (showBinaryBasedConfig && !showTextBasedConfig) {\n <div class=\"card-header separator\">\n <div class=\"card-title\">{{ 'Configurations' | translate }}</div>\n </div>\n }\n @if (!showBinaryBasedConfig && showTextBasedConfig) {\n <div class=\"card-header separator\">\n <div class=\"card-title\">{{ 'Text-based configuration' | translate }}</div>\n </div>\n }\n @if (showBinaryBasedConfig) {\n <tab heading=\"{{ 'Configurations' | translate }}\">\n <div class=\"card--grid card grid__col--4-8--md grid__row--6-6--md m-b-0\">\n <!-- DEVICE SUPPORTED CONFIGURATIONS -->\n <div class=\"card--grid__inner-scroll bg-level-1\">\n <div class=\"p-l-16 p-r-16\">\n <h5 class=\"legend form-block\">\n <span translate>Device-supported configurations</span>\n </h5>\n </div>\n <c8y-device-configuration-list\n [itemIcon]=\"'gears'\"\n [items]=\"supportedConfigurations\"\n (configSelected)=\"onConfigTypeSelected($event)\"\n ></c8y-device-configuration-list>\n </div>\n <!-- CONFIGURATION PREVIEW -->\n <div class=\"card--grid__inner-scroll d-flex d-col flex-grow\">\n <div class=\"card-block d-flex d-col flex-grow\">\n <h5 class=\"legend form-block\"><span translate>Preview</span></h5>\n <!-- EMPTY STATE -->\n @if (!configurationType) {\n <c8y-ui-empty-state\n [icon]=\"'file-text'\"\n [title]=\"'No configuration selected.' | translate\"\n [subtitle]=\"'Select a configuration to preview.' | translate\"\n [horizontal]=\"true\"\n ></c8y-ui-empty-state>\n }\n <!-- PREVIEW AVAILABLE STATE -->\n @if (configurationType) {\n <c8y-device-configuration-preview\n class=\"d-flex d-col flex-grow\"\n [actionButtonIcon]=\"'download'\"\n [device]=\"device\"\n [configurationType]=\"configurationType\"\n [configSnapshot]=\"configSnapshot\"\n [canSaveSnapshot]=\"true\"\n [operationToTrigger]=\"'c8y_UploadConfigFile'\"\n [actionButtonText]=\"'Get snapshot from device' | translate\"\n [isLegacy]=\"isLegacy\"\n ></c8y-device-configuration-preview>\n }\n </div>\n </div>\n <!-- AVAILABLE SUPPORTED CONFIGURATIONS -->\n <div class=\"card--grid__inner-scroll bg-level-1\">\n <div class=\"p-l-16 p-r-16\">\n <h5\n class=\"legend form-block\"\n translate\n >\n Available supported configurations\n </h5>\n </div>\n <!-- EMPTY STATE -->\n @if (!configurationType) {\n <c8y-ui-empty-state\n [icon]=\"'gears'\"\n [title]=\"'No selection.' | translate\"\n [subtitle]=\"\n 'Select a configuration from the device-supported configuration list.' | translate\n \"\n [horizontal]=\"true\"\n ></c8y-ui-empty-state>\n }\n @if (configurationType) {\n <div class=\"p-r-16\">\n <c8y-device-configuration-list\n [itemIcon]=\"'file-text'\"\n [items]=\"repositorySnapshots\"\n [emptyState]=\"repositorySnapshotsEmptyState\"\n [isFilterEnabled]=\"true\"\n (configSelected)=\"onRepositoryConfigSelected($event)\"\n ></c8y-device-configuration-list>\n </div>\n }\n </div>\n <!-- CONFIGURATION PREVIEW -->\n <div class=\"card--grid__inner-scroll d-flex d-col flex-grow\">\n <div class=\"card-block flex-grow d-flex d-col\">\n <h5\n class=\"legend form-block\"\n translate\n >\n Preview\n </h5>\n <!-- EMPTY STATE -->\n @if (!repositorySnapshot) {\n <c8y-ui-empty-state\n [icon]=\"'file-text'\"\n [title]=\"'No configuration selected.' | translate\"\n [subtitle]=\"'Select a configuration to preview.' | translate\"\n [horizontal]=\"true\"\n ></c8y-ui-empty-state>\n }\n <!-- CONFIGURATION SELECTED STATE -->\n @if (repositorySnapshot) {\n <c8y-device-configuration-preview\n class=\"d-flex d-col flex-grow\"\n [actionButtonIcon]=\"'upload'\"\n [device]=\"device\"\n [configurationType]=\"configurationType\"\n [configSnapshot]=\"repositorySnapshot\"\n [operationToTrigger]=\"'c8y_DownloadConfigFile'\"\n [actionButtonText]=\"'Send configuration to device' | translate\"\n [isLegacy]=\"isLegacy\"\n [editableRepositoryMo]=\"selectedRepositoryMo\"\n (editInRepository)=\"openEditInRepository($event)\"\n ></c8y-device-configuration-preview>\n }\n </div>\n </div>\n </div>\n </tab>\n }\n @if (showTextBasedConfig) {\n <tab heading=\"{{ 'Text-based configuration' | translate }}\">\n <c8y-text-based-configuration></c8y-text-based-configuration>\n </tab>\n }\n </tabset>\n</div>\n", dependencies: [{ kind: "component", type: ActionBarItemComponent, selector: "c8y-action-bar-item", inputs: ["placement", "priority", "itemClass", "injector", "groupId", "inGroupPriority"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: TabsetAriaDirective, selector: "tabset" }, { kind: "component", type: TabsetComponent, selector: "tabset", inputs: ["vertical", "justified", "type"] }, { kind: "directive", type: TabDirective, selector: "tab, [tab]", inputs: ["heading", "id", "disabled", "removable", "tabOrder", "customClass", "active"], outputs: ["selectTab", "deselect", "removed"], exportAs: ["tab"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: DeviceConfigurationListComponent, selector: "c8y-device-configuration-list", inputs: ["items", "itemIcon", "emptyState", "isFilterEnabled"], outputs: ["configSelected"] }, { kind: "component", type: EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "component", type: ConfigurationPreviewComponent, selector: "c8y-device-configuration-preview", inputs: ["device", "configurationType", "configSnapshot", "canSaveSnapshot", "actionButtonText", "actionButtonIcon", "isLegacy", "operationToTrigger", "editableRepositoryMo"], outputs: ["editInRepository"] }, { kind: "component", type: TextBasedConfigurationComponent, selector: "c8y-text-based-configuration" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
|
|
569
890
|
}
|
|
570
891
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DeviceConfigurationComponent, decorators: [{
|
|
571
892
|
type: Component,
|
|
@@ -575,7 +896,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
575
896
|
NgClass,
|
|
576
897
|
TabsetAriaDirective,
|
|
577
898
|
TabsetComponent,
|
|
578
|
-
NgIf,
|
|
579
899
|
TabDirective,
|
|
580
900
|
C8yTranslateDirective,
|
|
581
901
|
DeviceConfigurationListComponent,
|
|
@@ -583,12 +903,64 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
583
903
|
ConfigurationPreviewComponent,
|
|
584
904
|
TextBasedConfigurationComponent,
|
|
585
905
|
C8yTranslatePipe
|
|
586
|
-
], template: "<c8y-action-bar-item [placement]=\"'right'\">\n <button
|
|
587
|
-
}], ctorParameters: () => [{ type: i1.ActivatedRoute }, { type: DeviceConfigurationService }, { type:
|
|
906
|
+
], template: "<c8y-action-bar-item [placement]=\"'right'\">\n <button\n class=\"btn btn-link\"\n title=\"{{ 'Reload' | translate }}\"\n (click)=\"updateSnapshots()\"\n >\n <i\n c8yIcon=\"refresh\"\n [ngClass]=\"{ 'icon-spin': reloading }\"\n ></i>\n {{ 'Reload' | translate }}\n </button>\n</c8y-action-bar-item>\n\n<div class=\"card content-fullpage card-has-tabs\">\n <tabset>\n @if (showBinaryBasedConfig && !showTextBasedConfig) {\n <div class=\"card-header separator\">\n <div class=\"card-title\">{{ 'Configurations' | translate }}</div>\n </div>\n }\n @if (!showBinaryBasedConfig && showTextBasedConfig) {\n <div class=\"card-header separator\">\n <div class=\"card-title\">{{ 'Text-based configuration' | translate }}</div>\n </div>\n }\n @if (showBinaryBasedConfig) {\n <tab heading=\"{{ 'Configurations' | translate }}\">\n <div class=\"card--grid card grid__col--4-8--md grid__row--6-6--md m-b-0\">\n <!-- DEVICE SUPPORTED CONFIGURATIONS -->\n <div class=\"card--grid__inner-scroll bg-level-1\">\n <div class=\"p-l-16 p-r-16\">\n <h5 class=\"legend form-block\">\n <span translate>Device-supported configurations</span>\n </h5>\n </div>\n <c8y-device-configuration-list\n [itemIcon]=\"'gears'\"\n [items]=\"supportedConfigurations\"\n (configSelected)=\"onConfigTypeSelected($event)\"\n ></c8y-device-configuration-list>\n </div>\n <!-- CONFIGURATION PREVIEW -->\n <div class=\"card--grid__inner-scroll d-flex d-col flex-grow\">\n <div class=\"card-block d-flex d-col flex-grow\">\n <h5 class=\"legend form-block\"><span translate>Preview</span></h5>\n <!-- EMPTY STATE -->\n @if (!configurationType) {\n <c8y-ui-empty-state\n [icon]=\"'file-text'\"\n [title]=\"'No configuration selected.' | translate\"\n [subtitle]=\"'Select a configuration to preview.' | translate\"\n [horizontal]=\"true\"\n ></c8y-ui-empty-state>\n }\n <!-- PREVIEW AVAILABLE STATE -->\n @if (configurationType) {\n <c8y-device-configuration-preview\n class=\"d-flex d-col flex-grow\"\n [actionButtonIcon]=\"'download'\"\n [device]=\"device\"\n [configurationType]=\"configurationType\"\n [configSnapshot]=\"configSnapshot\"\n [canSaveSnapshot]=\"true\"\n [operationToTrigger]=\"'c8y_UploadConfigFile'\"\n [actionButtonText]=\"'Get snapshot from device' | translate\"\n [isLegacy]=\"isLegacy\"\n ></c8y-device-configuration-preview>\n }\n </div>\n </div>\n <!-- AVAILABLE SUPPORTED CONFIGURATIONS -->\n <div class=\"card--grid__inner-scroll bg-level-1\">\n <div class=\"p-l-16 p-r-16\">\n <h5\n class=\"legend form-block\"\n translate\n >\n Available supported configurations\n </h5>\n </div>\n <!-- EMPTY STATE -->\n @if (!configurationType) {\n <c8y-ui-empty-state\n [icon]=\"'gears'\"\n [title]=\"'No selection.' | translate\"\n [subtitle]=\"\n 'Select a configuration from the device-supported configuration list.' | translate\n \"\n [horizontal]=\"true\"\n ></c8y-ui-empty-state>\n }\n @if (configurationType) {\n <div class=\"p-r-16\">\n <c8y-device-configuration-list\n [itemIcon]=\"'file-text'\"\n [items]=\"repositorySnapshots\"\n [emptyState]=\"repositorySnapshotsEmptyState\"\n [isFilterEnabled]=\"true\"\n (configSelected)=\"onRepositoryConfigSelected($event)\"\n ></c8y-device-configuration-list>\n </div>\n }\n </div>\n <!-- CONFIGURATION PREVIEW -->\n <div class=\"card--grid__inner-scroll d-flex d-col flex-grow\">\n <div class=\"card-block flex-grow d-flex d-col\">\n <h5\n class=\"legend form-block\"\n translate\n >\n Preview\n </h5>\n <!-- EMPTY STATE -->\n @if (!repositorySnapshot) {\n <c8y-ui-empty-state\n [icon]=\"'file-text'\"\n [title]=\"'No configuration selected.' | translate\"\n [subtitle]=\"'Select a configuration to preview.' | translate\"\n [horizontal]=\"true\"\n ></c8y-ui-empty-state>\n }\n <!-- CONFIGURATION SELECTED STATE -->\n @if (repositorySnapshot) {\n <c8y-device-configuration-preview\n class=\"d-flex d-col flex-grow\"\n [actionButtonIcon]=\"'upload'\"\n [device]=\"device\"\n [configurationType]=\"configurationType\"\n [configSnapshot]=\"repositorySnapshot\"\n [operationToTrigger]=\"'c8y_DownloadConfigFile'\"\n [actionButtonText]=\"'Send configuration to device' | translate\"\n [isLegacy]=\"isLegacy\"\n [editableRepositoryMo]=\"selectedRepositoryMo\"\n (editInRepository)=\"openEditInRepository($event)\"\n ></c8y-device-configuration-preview>\n }\n </div>\n </div>\n </div>\n </tab>\n }\n @if (showTextBasedConfig) {\n <tab heading=\"{{ 'Text-based configuration' | translate }}\">\n <c8y-text-based-configuration></c8y-text-based-configuration>\n </tab>\n }\n </tabset>\n</div>\n" }]
|
|
907
|
+
}], ctorParameters: () => [{ type: i1.ActivatedRoute }, { type: DeviceConfigurationService }, { type: i3.RepositoryService }], propDecorators: { textBasedConfigurationComponent: [{
|
|
588
908
|
type: ViewChild,
|
|
589
909
|
args: [TextBasedConfigurationComponent]
|
|
590
910
|
}] } });
|
|
591
911
|
|
|
912
|
+
class SaveToRepositoryComponent {
|
|
913
|
+
constructor(modal, alertService, repositoryService) {
|
|
914
|
+
this.modal = modal;
|
|
915
|
+
this.alertService = alertService;
|
|
916
|
+
this.repositoryService = repositoryService;
|
|
917
|
+
this.result = new Promise((resolve, reject) => {
|
|
918
|
+
this._save = resolve;
|
|
919
|
+
this._cancel = reject;
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
async save() {
|
|
923
|
+
{
|
|
924
|
+
try {
|
|
925
|
+
const configSnapshotData = {
|
|
926
|
+
selected: {
|
|
927
|
+
configurationType: this.configSnapshot.configurationType
|
|
928
|
+
},
|
|
929
|
+
version: this.configSnapshot.name,
|
|
930
|
+
deviceType: this.configSnapshot.deviceType,
|
|
931
|
+
description: this.configSnapshot.description,
|
|
932
|
+
binary: {
|
|
933
|
+
file: new File([this.configSnapshot.binary], this.configSnapshot.name)
|
|
934
|
+
}
|
|
935
|
+
};
|
|
936
|
+
await this.repositoryService.create(configSnapshotData, RepositoryType.CONFIGURATION);
|
|
937
|
+
this.alertService.success(gettext('Configuration saved.'));
|
|
938
|
+
this._save();
|
|
939
|
+
}
|
|
940
|
+
catch (ex) {
|
|
941
|
+
this.alertService.addServerFailure(ex);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
close() {
|
|
946
|
+
this._cancel();
|
|
947
|
+
this.modal.hide();
|
|
948
|
+
}
|
|
949
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SaveToRepositoryComponent, deps: [{ token: i1$2.BsModalRef }, { token: i2.AlertService }, { token: i3.RepositoryService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
950
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: SaveToRepositoryComponent, isStandalone: true, selector: "c8y-save-config-to-configuration-repository", ngImport: i0, template: "<div class=\"modal-header dialog-header\">\n <i c8yIcon=\"gears\"></i>\n <h4 id=\"modal-title\" translate>\n Save configuration\n </h4>\n</div>\n<div class=\"modal-body\" id=\"modal-body\">\n <form #saveConfigurationSnapshot=\"ngForm\" class=\"p-t-24\">\n <c8y-form-group>\n <label translate for=\"name\">Name</label>\n <input\n id=\"name\"\n type=\"text\"\n class=\"form-control\"\n autocomplete=\"off\"\n name=\"name\"\n [(ngModel)]=\"configSnapshot.name\"\n required\n />\n </c8y-form-group>\n <c8y-form-group>\n <label translate for=\"deviceType\">Device type</label>\n <input\n id=\"deviceType\"\n class=\"form-control\"\n rows=\"6\"\n name=\"deviceType\"\n [(ngModel)]=\"configSnapshot.deviceType\"\n />\n </c8y-form-group>\n <c8y-form-group>\n <label translate for=\"description\">Description</label>\n <input\n type=\"text\"\n id=\"description\"\n class=\"form-control\"\n maxlength=\"254\"\n autocomplete=\"off\"\n name=\"description\"\n [(ngModel)]=\"configSnapshot.description\"\n />\n </c8y-form-group>\n <c8y-form-group>\n <label translate for=\"configurationType\">Configuration type</label>\n <input\n id=\"configurationType\"\n class=\"form-control\"\n rows=\"6\"\n name=\"configurationType\"\n [(ngModel)]=\"configSnapshot.configurationType\"\n />\n </c8y-form-group>\n </form>\n</div>\n<div class=\"modal-footer\">\n <button title=\"{{ 'Cancel' | translate }}\" class=\"btn btn-default\" (click)=\"close()\" translate>\n Cancel\n </button>\n\n <button\n title=\"{{ 'Save configuration to repository' | translate }}\"\n class=\"btn btn-primary\"\n (click)=\"save()\"\n [disabled]=\"saveConfigurationSnapshot.form.invalid\"\n translate\n >\n Save\n </button>\n</div>\n", dependencies: [{ kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
|
|
951
|
+
}
|
|
952
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SaveToRepositoryComponent, decorators: [{
|
|
953
|
+
type: Component,
|
|
954
|
+
args: [{ selector: 'c8y-save-config-to-configuration-repository', imports: [
|
|
955
|
+
IconDirective,
|
|
956
|
+
C8yTranslateDirective,
|
|
957
|
+
FormsModule,
|
|
958
|
+
FormGroupComponent,
|
|
959
|
+
RequiredInputPlaceholderDirective,
|
|
960
|
+
C8yTranslatePipe
|
|
961
|
+
], template: "<div class=\"modal-header dialog-header\">\n <i c8yIcon=\"gears\"></i>\n <h4 id=\"modal-title\" translate>\n Save configuration\n </h4>\n</div>\n<div class=\"modal-body\" id=\"modal-body\">\n <form #saveConfigurationSnapshot=\"ngForm\" class=\"p-t-24\">\n <c8y-form-group>\n <label translate for=\"name\">Name</label>\n <input\n id=\"name\"\n type=\"text\"\n class=\"form-control\"\n autocomplete=\"off\"\n name=\"name\"\n [(ngModel)]=\"configSnapshot.name\"\n required\n />\n </c8y-form-group>\n <c8y-form-group>\n <label translate for=\"deviceType\">Device type</label>\n <input\n id=\"deviceType\"\n class=\"form-control\"\n rows=\"6\"\n name=\"deviceType\"\n [(ngModel)]=\"configSnapshot.deviceType\"\n />\n </c8y-form-group>\n <c8y-form-group>\n <label translate for=\"description\">Description</label>\n <input\n type=\"text\"\n id=\"description\"\n class=\"form-control\"\n maxlength=\"254\"\n autocomplete=\"off\"\n name=\"description\"\n [(ngModel)]=\"configSnapshot.description\"\n />\n </c8y-form-group>\n <c8y-form-group>\n <label translate for=\"configurationType\">Configuration type</label>\n <input\n id=\"configurationType\"\n class=\"form-control\"\n rows=\"6\"\n name=\"configurationType\"\n [(ngModel)]=\"configSnapshot.configurationType\"\n />\n </c8y-form-group>\n </form>\n</div>\n<div class=\"modal-footer\">\n <button title=\"{{ 'Cancel' | translate }}\" class=\"btn btn-default\" (click)=\"close()\" translate>\n Cancel\n </button>\n\n <button\n title=\"{{ 'Save configuration to repository' | translate }}\"\n class=\"btn btn-primary\"\n (click)=\"save()\"\n [disabled]=\"saveConfigurationSnapshot.form.invalid\"\n translate\n >\n Save\n </button>\n</div>\n" }]
|
|
962
|
+
}], ctorParameters: () => [{ type: i1$2.BsModalRef }, { type: i2.AlertService }, { type: i3.RepositoryService }] });
|
|
963
|
+
|
|
592
964
|
class ConfigurationRepositoryDeviceTabModule {
|
|
593
965
|
static forRoot() {
|
|
594
966
|
return {
|
|
@@ -609,7 +981,7 @@ class ConfigurationRepositoryDeviceTabModule {
|
|
|
609
981
|
}
|
|
610
982
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ConfigurationRepositoryDeviceTabModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
611
983
|
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.17", ngImport: i0, type: ConfigurationRepositoryDeviceTabModule, imports: [SharedRepositoryModule,
|
|
612
|
-
OperationDetailsModule, i1$
|
|
984
|
+
OperationDetailsModule, i1$3.TabsModule, DeviceConfigurationComponent,
|
|
613
985
|
DeviceConfigurationListComponent,
|
|
614
986
|
ConfigurationPreviewComponent,
|
|
615
987
|
ConfigurationFilterPipe,
|
|
@@ -623,6 +995,7 @@ class ConfigurationRepositoryDeviceTabModule {
|
|
|
623
995
|
DeviceConfigurationListComponent,
|
|
624
996
|
ConfigurationPreviewComponent,
|
|
625
997
|
SaveToRepositoryComponent,
|
|
998
|
+
SourceCodePreviewComponent,
|
|
626
999
|
TextBasedConfigurationComponent] }); }
|
|
627
1000
|
}
|
|
628
1001
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ConfigurationRepositoryDeviceTabModule, decorators: [{
|
|
@@ -644,303 +1017,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
644
1017
|
}]
|
|
645
1018
|
}] });
|
|
646
1019
|
|
|
647
|
-
const EXT_LANGUAGE_MAP = {
|
|
648
|
-
json: 'json',
|
|
649
|
-
yaml: 'yaml',
|
|
650
|
-
yml: 'yaml',
|
|
651
|
-
xml: 'xml',
|
|
652
|
-
html: 'html',
|
|
653
|
-
css: 'css',
|
|
654
|
-
ts: 'typescript',
|
|
655
|
-
js: 'javascript',
|
|
656
|
-
md: 'markdown',
|
|
657
|
-
toml: 'ini',
|
|
658
|
-
properties: 'ini',
|
|
659
|
-
ini: 'ini',
|
|
660
|
-
conf: 'ini',
|
|
661
|
-
cfg: 'ini',
|
|
662
|
-
sh: 'shell'
|
|
663
|
-
};
|
|
664
|
-
function detectLanguage(filename) {
|
|
665
|
-
const ext = (filename.split('.').pop() ?? '').toLowerCase();
|
|
666
|
-
return EXT_LANGUAGE_MAP[ext] ?? 'plaintext';
|
|
667
|
-
}
|
|
668
|
-
const EXT_MIME_MAP = {
|
|
669
|
-
json: 'application/json',
|
|
670
|
-
xml: 'application/xml',
|
|
671
|
-
yaml: 'application/yaml',
|
|
672
|
-
yml: 'application/yaml',
|
|
673
|
-
js: 'application/javascript',
|
|
674
|
-
ts: 'application/typescript',
|
|
675
|
-
html: 'text/html',
|
|
676
|
-
css: 'text/css',
|
|
677
|
-
md: 'text/markdown',
|
|
678
|
-
sh: 'application/x-sh',
|
|
679
|
-
toml: 'application/toml'
|
|
680
|
-
};
|
|
681
|
-
function mimeForFilename(filename) {
|
|
682
|
-
const ext = (filename.split('.').pop() ?? '').toLowerCase();
|
|
683
|
-
return EXT_MIME_MAP[ext] ?? 'text/plain';
|
|
684
|
-
}
|
|
685
|
-
/** Sentinel value meaning "derive language from filename". */
|
|
686
|
-
const AUTO_LANGUAGE = 'auto';
|
|
687
|
-
const EDITOR_LANGUAGES = [
|
|
688
|
-
{ value: AUTO_LANGUAGE, label: gettext('Auto (from filename)') },
|
|
689
|
-
{ value: 'json', label: gettext('JSON') },
|
|
690
|
-
{ value: 'javascript', label: gettext('JavaScript') },
|
|
691
|
-
{ value: 'typescript', label: gettext('TypeScript') },
|
|
692
|
-
{ value: 'css', label: gettext('CSS') },
|
|
693
|
-
{ value: 'html', label: gettext('HTML') },
|
|
694
|
-
{ value: 'yaml', label: gettext('YAML') },
|
|
695
|
-
{ value: 'markdown', label: gettext('Markdown') },
|
|
696
|
-
{ value: 'xml', label: gettext('XML') },
|
|
697
|
-
{ value: 'ini', label: gettext('INI / TOML / Properties') },
|
|
698
|
-
{ value: 'shell', label: gettext('Shell') },
|
|
699
|
-
{ value: 'plaintext', label: gettext('Plain text') }
|
|
700
|
-
];
|
|
701
|
-
class ConfigurationDetailComponent {
|
|
702
|
-
constructor() {
|
|
703
|
-
this.repositoryService = inject(RepositoryService);
|
|
704
|
-
this.bottomDrawerRef = inject(BottomDrawerRef);
|
|
705
|
-
this.alert = inject(AlertService);
|
|
706
|
-
this.inventoryBinaryService = inject(InventoryBinaryService);
|
|
707
|
-
this.cdr = inject(ChangeDetectorRef);
|
|
708
|
-
this.binary = { file: undefined, url: undefined };
|
|
709
|
-
this.mo = {};
|
|
710
|
-
this.form = new FormGroup({
|
|
711
|
-
version: new FormControl('', [
|
|
712
|
-
Validators.required,
|
|
713
|
-
Validators.maxLength(254),
|
|
714
|
-
Validators.pattern(ValidationPattern.rules.noWhiteSpaceOnly.pattern)
|
|
715
|
-
]),
|
|
716
|
-
deviceType: new FormControl('', [
|
|
717
|
-
Validators.maxLength(254),
|
|
718
|
-
Validators.pattern(ValidationPattern.rules.noWhiteSpaceOnly.pattern)
|
|
719
|
-
]),
|
|
720
|
-
description: new FormControl('', [
|
|
721
|
-
Validators.maxLength(254),
|
|
722
|
-
Validators.pattern(ValidationPattern.rules.noWhiteSpaceOnly.pattern)
|
|
723
|
-
]),
|
|
724
|
-
uploadChoice: new FormControl('uploadBinary'),
|
|
725
|
-
editorFilename: new FormControl('', [
|
|
726
|
-
Validators.pattern(ValidationPattern.rules.noWhiteSpaceOnly.pattern)
|
|
727
|
-
]),
|
|
728
|
-
editorLanguage: new FormControl(EDITOR_LANGUAGES[0]),
|
|
729
|
-
editorContent: new FormControl('')
|
|
730
|
-
});
|
|
731
|
-
this.configurationTypeMO = {};
|
|
732
|
-
this.saving = signal(false, ...(ngDevMode ? [{ debugName: "saving" }] : /* istanbul ignore next */ []));
|
|
733
|
-
this.pattern = signal('', ...(ngDevMode ? [{ debugName: "pattern" }] : /* istanbul ignore next */ []));
|
|
734
|
-
this.isExternalUrl = signal(false, ...(ngDevMode ? [{ debugName: "isExternalUrl" }] : /* istanbul ignore next */ []));
|
|
735
|
-
this.ValidationPattern = ValidationPattern;
|
|
736
|
-
this.textForConfigurationUrlPopover = gettext(`Path for binaries can vary depending on device agent implementation, for example:
|
|
737
|
-
/configuration/binaries/configuration1.bin
|
|
738
|
-
https://configuration/binary/123
|
|
739
|
-
ftp://configuration/binary/123.tar.gz
|
|
740
|
-
Configurations with external URLs only work with the configuration typed devices (file-based configuration), not with devices with a legacy configuration.
|
|
741
|
-
`);
|
|
742
|
-
this.result = new Promise((resolve, reject) => {
|
|
743
|
-
this._save = resolve;
|
|
744
|
-
this._cancel = reject;
|
|
745
|
-
});
|
|
746
|
-
this.settled = false;
|
|
747
|
-
this.editorFilenameValue = toSignal(this.form.controls.editorFilename.valueChanges, {
|
|
748
|
-
initialValue: ''
|
|
749
|
-
});
|
|
750
|
-
this.editorLanguageValue = toSignal(this.form.controls.editorLanguage.valueChanges, {
|
|
751
|
-
initialValue: EDITOR_LANGUAGES[0]
|
|
752
|
-
});
|
|
753
|
-
/** User's manual pick (when not "auto") wins over auto-detection from filename. */
|
|
754
|
-
this.editorLanguage = computed(() => {
|
|
755
|
-
const picked = this.editorLanguageValue()?.value;
|
|
756
|
-
return !picked || picked === AUTO_LANGUAGE
|
|
757
|
-
? detectLanguage(this.editorFilenameValue() ?? '')
|
|
758
|
-
: picked;
|
|
759
|
-
}, ...(ngDevMode ? [{ debugName: "editorLanguage" }] : /* istanbul ignore next */ []));
|
|
760
|
-
this.editorLanguages = EDITOR_LANGUAGES;
|
|
761
|
-
this.submitButtonTitle = computed(() => this.mo?.id ? gettext('Update configuration') : gettext('Add configuration'), ...(ngDevMode ? [{ debugName: "submitButtonTitle" }] : /* istanbul ignore next */ []));
|
|
762
|
-
this.form.controls.uploadChoice.valueChanges.pipe(takeUntilDestroyed()).subscribe(choice => {
|
|
763
|
-
const ctrl = this.form.controls.editorFilename;
|
|
764
|
-
if (choice === 'editInline') {
|
|
765
|
-
ctrl.addValidators(Validators.required);
|
|
766
|
-
}
|
|
767
|
-
else {
|
|
768
|
-
ctrl.removeValidators(Validators.required);
|
|
769
|
-
}
|
|
770
|
-
ctrl.updateValueAndValidity();
|
|
771
|
-
});
|
|
772
|
-
// Drawer can be closed externally (Escape, navigation, programmatic close).
|
|
773
|
-
// Make sure the result promise is always settled, otherwise callers awaiting it hang.
|
|
774
|
-
this.bottomDrawerRef.onClosed$.pipe(takeUntilDestroyed()).subscribe(() => {
|
|
775
|
-
if (!this.settled) {
|
|
776
|
-
this.settled = true;
|
|
777
|
-
this._cancel('Drawer closed');
|
|
778
|
-
}
|
|
779
|
-
});
|
|
780
|
-
}
|
|
781
|
-
async ngOnInit() {
|
|
782
|
-
this.configs = await this.repositoryService.listRepositoryEntries(RepositoryType.CONFIGURATION);
|
|
783
|
-
this.setPipe('');
|
|
784
|
-
if (this.mo?.id) {
|
|
785
|
-
this.configurationTypeMO = this.mo;
|
|
786
|
-
const hasExternalUrl = !!this.binary?.url && !this.inventoryBinaryService.getIdFromUrl(this.binary.url);
|
|
787
|
-
if (hasExternalUrl) {
|
|
788
|
-
this.isExternalUrl.set(true);
|
|
789
|
-
this.form.patchValue({
|
|
790
|
-
version: this.version ?? '',
|
|
791
|
-
deviceType: this.deviceType ?? '',
|
|
792
|
-
description: this.description ?? '',
|
|
793
|
-
uploadChoice: 'uploadUrl'
|
|
794
|
-
});
|
|
795
|
-
}
|
|
796
|
-
else {
|
|
797
|
-
const patchValues = {
|
|
798
|
-
version: this.version ?? '',
|
|
799
|
-
deviceType: this.deviceType ?? '',
|
|
800
|
-
description: this.description ?? '',
|
|
801
|
-
uploadChoice: 'editInline'
|
|
802
|
-
};
|
|
803
|
-
if (this.binary?.file) {
|
|
804
|
-
this.existingBinary = this.binary.file;
|
|
805
|
-
if (await isBinaryFile(this.binary.file)) {
|
|
806
|
-
patchValues.uploadChoice = 'uploadBinary';
|
|
807
|
-
}
|
|
808
|
-
else {
|
|
809
|
-
patchValues.editorContent = await this.binary.file.text();
|
|
810
|
-
patchValues.editorFilename = this.binary.file.name;
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
this.form.patchValue(patchValues);
|
|
814
|
-
}
|
|
815
|
-
this.form.markAsDirty();
|
|
816
|
-
}
|
|
817
|
-
this.cdr.markForCheck();
|
|
818
|
-
}
|
|
819
|
-
cancel() {
|
|
820
|
-
this.settled = true;
|
|
821
|
-
this._cancel('User canceled');
|
|
822
|
-
this.bottomDrawerRef.close();
|
|
823
|
-
}
|
|
824
|
-
setPipe(filterStr) {
|
|
825
|
-
this.pattern.set(filterStr);
|
|
826
|
-
this.filterPipe = pipe(map(data => uniqBy(data, 'configurationType')), map(data => data.filter(mo => mo.configurationType &&
|
|
827
|
-
mo.configurationType.toLowerCase().indexOf(filterStr.toLowerCase()) > -1)));
|
|
828
|
-
}
|
|
829
|
-
onUploadChoiceChange(choice) {
|
|
830
|
-
this.form.controls.uploadChoice.setValue(choice);
|
|
831
|
-
this.form.markAsDirty();
|
|
832
|
-
}
|
|
833
|
-
onFileBinaryChange(file) {
|
|
834
|
-
this.binary = { ...this.binary, file };
|
|
835
|
-
this.form.markAsDirty();
|
|
836
|
-
this.cdr.markForCheck();
|
|
837
|
-
}
|
|
838
|
-
onFileUrlChange(url) {
|
|
839
|
-
this.binary = { ...this.binary, url };
|
|
840
|
-
this.form.markAsDirty();
|
|
841
|
-
this.cdr.markForCheck();
|
|
842
|
-
}
|
|
843
|
-
onTextFilePicked({ name, content }) {
|
|
844
|
-
this.form.patchValue({
|
|
845
|
-
uploadChoice: 'editInline',
|
|
846
|
-
editorFilename: name,
|
|
847
|
-
editorContent: content
|
|
848
|
-
});
|
|
849
|
-
this.form.markAsDirty();
|
|
850
|
-
this.cdr.markForCheck();
|
|
851
|
-
}
|
|
852
|
-
get isSaveDisabled() {
|
|
853
|
-
if (this.saving() || !this.form.valid || this.form.pristine) {
|
|
854
|
-
return true;
|
|
855
|
-
}
|
|
856
|
-
const { uploadChoice, editorFilename, editorContent } = this.form.value;
|
|
857
|
-
if (uploadChoice === 'uploadBinary') {
|
|
858
|
-
return !this.binary?.file;
|
|
859
|
-
}
|
|
860
|
-
if (uploadChoice === 'uploadUrl') {
|
|
861
|
-
return !this.binary?.url?.trim();
|
|
862
|
-
}
|
|
863
|
-
if (uploadChoice === 'editInline') {
|
|
864
|
-
return !editorFilename?.trim() || !editorContent;
|
|
865
|
-
}
|
|
866
|
-
return false;
|
|
867
|
-
}
|
|
868
|
-
async save() {
|
|
869
|
-
if (this.isSaveDisabled) {
|
|
870
|
-
return;
|
|
871
|
-
}
|
|
872
|
-
try {
|
|
873
|
-
this.saving.set(true);
|
|
874
|
-
const { version, deviceType, description, uploadChoice, editorFilename, editorContent } = this.form.value;
|
|
875
|
-
let binary = { ...this.binary };
|
|
876
|
-
if (uploadChoice === 'uploadUrl') {
|
|
877
|
-
binary = { url: this.binary?.url };
|
|
878
|
-
}
|
|
879
|
-
else if (uploadChoice === 'editInline') {
|
|
880
|
-
binary = {
|
|
881
|
-
file: new File([editorContent], editorFilename, { type: mimeForFilename(editorFilename) })
|
|
882
|
-
};
|
|
883
|
-
}
|
|
884
|
-
else if (this.existingBinary === this.binary?.file) {
|
|
885
|
-
binary = { ...this.binary, file: undefined };
|
|
886
|
-
}
|
|
887
|
-
await this.repositoryService.create({
|
|
888
|
-
version,
|
|
889
|
-
description,
|
|
890
|
-
binary,
|
|
891
|
-
deviceType,
|
|
892
|
-
configurationType: this.configurationTypeMO?.configurationType
|
|
893
|
-
}, RepositoryType.CONFIGURATION, this.mo);
|
|
894
|
-
this.alert.success(this.mo?.id ? gettext('Configuration updated.') : gettext('Configuration created.'));
|
|
895
|
-
this.settled = true;
|
|
896
|
-
this._save();
|
|
897
|
-
this.bottomDrawerRef.close();
|
|
898
|
-
}
|
|
899
|
-
catch (ex) {
|
|
900
|
-
// Keep the drawer open so the user can fix and retry.
|
|
901
|
-
// Do NOT settle the result promise here.
|
|
902
|
-
this.alert.addServerFailure(ex);
|
|
903
|
-
}
|
|
904
|
-
finally {
|
|
905
|
-
this.saving.set(false);
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ConfigurationDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
909
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: ConfigurationDetailComponent, isStandalone: true, selector: "c8y-configuration-detail", inputs: { version: "version", description: "description", binary: "binary", deviceType: "deviceType", mo: "mo" }, host: { classAttribute: "d-contents" }, ngImport: i0, template: "<div class=\"card-header j-c-center separator flex-no-shrink p-24\">\n <i\n class=\"m-r-8\"\n [c8yIcon]=\"'cogs'\"\n aria-hidden=\"true\"\n ></i>\n <h4 id=\"drawerTitle\">\n @if (mo?.id) {\n {{ 'Update configuration' | translate }}\n } @else {\n {{ 'Add configuration' | translate }}\n }\n </h4>\n</div>\n\n<form\n class=\"d-contents\"\n [formGroup]=\"form\"\n (ngSubmit)=\"save()\"\n>\n <div\n class=\"card-body inner-scroll p-24\"\n [class.d-col]=\"form.value.uploadChoice === 'editInline'\"\n >\n <div class=\"row\">\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confVersion\"\n translate\n >\n Name\n </label>\n <input\n class=\"form-control\"\n id=\"confVersion\"\n placeholder=\"{{ 'e.g.' | translate }} hosts\"\n aria-required=\"true\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"version\"\n />\n </c8y-form-group>\n\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confDeviceType\"\n translate\n >\n Device type\n </label>\n <input\n class=\"form-control\"\n id=\"confDeviceType\"\n placeholder=\"{{ 'e.g.' | translate }} c8y_Linux\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"deviceType\"\n />\n </c8y-form-group>\n\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confDescription\"\n translate\n >\n Description\n </label>\n <input\n class=\"form-control\"\n id=\"confDescription\"\n placeholder=\"{{ 'e.g.' | translate }} Host configuration\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"description\"\n />\n </c8y-form-group>\n\n <c8y-form-group class=\"col-sm-3\">\n <label translate>Configuration type</label>\n <c8y-typeahead\n placeholder=\"{{ 'e.g.' | translate }} ssh\"\n name=\"confType\"\n data-cy=\"conf-detail--configuration-type\"\n [ngModel]=\"configurationTypeMO\"\n [ngModelOptions]=\"{ standalone: true }\"\n (ngModelChange)=\"configurationTypeMO = $event\"\n maxlength=\"254\"\n (onSearch)=\"setPipe($event)\"\n displayProperty=\"configurationType\"\n >\n @let patternStr = pattern();\n <c8y-li\n class=\"p-l-8 p-r-8 c8y-list__item--link\"\n *c8yFor=\"let config of configs; pipe: filterPipe; notFound: notFoundTemplate\"\n (click)=\"configurationTypeMO = config; setPipe('')\"\n [active]=\"configurationTypeMO === config\"\n >\n <c8y-highlight\n [text]=\"config.configurationType || '--'\"\n [pattern]=\"patternStr\"\n ></c8y-highlight>\n </c8y-li>\n <ng-template #notFoundTemplate>\n @if (patternStr.length > 0) {\n <c8y-li class=\"bg-level-2 p-8\">\n <span translate>No match found.</span>\n <button\n class=\"btn btn-primary btn-xs m-l-8\"\n title=\"{{ 'Add new`configuration type`' | translate }}\"\n type=\"button\"\n translate\n >\n Add new`configuration type`\n </button>\n </c8y-li>\n }\n </ng-template>\n </c8y-typeahead>\n </c8y-form-group>\n </div>\n\n <fieldset\n class=\"d-col\"\n [class.flex-grow]=\"form.value.uploadChoice === 'editInline'\"\n [class.min-height-0]=\"form.value.uploadChoice === 'editInline'\"\n >\n <legend\n class=\"legend form-block m-t-40\"\n translate\n >\n Configuration file\n </legend>\n\n @if (isExternalUrl()) {\n <p\n class=\"text-muted m-b-8\"\n translate\n >\n Content is sourced from a remote URL and cannot be edited inline.\n </p>\n <c8y-form-group>\n <label\n for=\"confFileUrl\"\n translate\n >\n File URL\n </label>\n <input\n class=\"form-control\"\n id=\"confFileUrl\"\n placeholder=\"https://...\"\n type=\"url\"\n autocomplete=\"off\"\n [ngModel]=\"binary.url\"\n [ngModelOptions]=\"{ standalone: true }\"\n (ngModelChange)=\"binary = { url: $event }; form.markAsDirty()\"\n />\n </c8y-form-group>\n } @else {\n <c8y-file-picker\n [maxAllowedFiles]=\"1\"\n [allowedUploadChoices]=\"['uploadBinary', 'uploadUrl', 'editInline']\"\n [uploadChoice]=\"form.controls.uploadChoice.value\"\n (uploadChoiceChange)=\"onUploadChoiceChange($event)\"\n [fileUrl]=\"binary.url\"\n (fileUrlChange)=\"onFileUrlChange($event)\"\n [fileBinary]=\"binary.file\"\n (fileBinaryChange)=\"onFileBinaryChange($event)\"\n (textFilePicked)=\"onTextFilePicked($event)\"\n [fileUrlPopover]=\"textForConfigurationUrlPopover\"\n >\n <ng-container c8yInlineEditor>\n <div class=\"row\">\n <c8y-form-group class=\"col-sm-9\">\n <label\n for=\"confEditorFilename\"\n translate\n >\n Filename\n </label>\n <input\n class=\"form-control\"\n id=\"confEditorFilename\"\n placeholder=\"config.json\"\n aria-required=\"true\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"editorFilename\"\n />\n </c8y-form-group>\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confEditorLanguage\"\n translate\n >\n Language\n </label>\n <c8y-select\n aria-label=\"{{ 'Editor language' | translate }}\"\n id=\"confEditorLanguage\"\n [items]=\"editorLanguages\"\n formControlName=\"editorLanguage\"\n ></c8y-select>\n </c8y-form-group>\n </div>\n <c8y-form-group class=\"d-col flex-grow min-height-0 m-b-0\">\n <label\n for=\"confEditorContent\"\n translate\n >\n Content\n </label>\n <c8y-editor\n class=\"flex-grow\"\n style=\"min-height: 95px\"\n id=\"confEditorContent\"\n formControlName=\"editorContent\"\n monacoEditorMarkerValidator\n [editorOptions]=\"{ language: editorLanguage() }\"\n ></c8y-editor>\n <c8y-messages></c8y-messages>\n </c8y-form-group>\n </ng-container>\n </c8y-file-picker>\n }\n </fieldset>\n </div>\n\n <div class=\"card-footer j-c-between separator flex-no-shrink p-24\">\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Cancel' | translate }}\"\n type=\"button\"\n (click)=\"cancel()\"\n [disabled]=\"saving()\"\n >\n <span translate>Cancel</span>\n </button>\n <button\n class=\"btn btn-primary\"\n [class.btn-pending]=\"saving()\"\n [title]=\"submitButtonTitle() | translate\"\n type=\"submit\"\n [disabled]=\"isSaveDisabled\"\n >\n {{ submitButtonTitle() | translate }}\n </button>\n </div>\n</form>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i4$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i4$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4$1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i4$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i4$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "component", type: TypeaheadComponent, selector: "c8y-typeahead", inputs: ["required", "maxlength", "disabled", "allowFreeEntries", "placeholder", "displayProperty", "icon", "name", "autoClose", "hideNew", "container", "selected", "title", "highlightFirstItem"], outputs: ["onSearch", "onIconClick"] }, { kind: "directive", type: ForOfDirective, selector: "[c8yFor]", inputs: ["c8yForOf", "c8yForLoadMore", "c8yForPipe", "c8yForNotFound", "c8yForMaxIterations", "c8yForLoadingTemplate", "c8yForLoadNextLabel", "c8yForLoadingLabel", "c8yForRealtime", "c8yForRealtimeOptions", "c8yForComparator", "c8yForEnableVirtualScroll", "c8yForVirtualScrollElementSize", "c8yForVirtualScrollStrategy", "c8yForVirtualScrollContainerHeight"], outputs: ["c8yForCount", "c8yForChange", "c8yForLoadMoreComponent"] }, { kind: "component", type: ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "component", type: HighlightComponent, selector: "c8y-highlight", inputs: ["pattern", "text", "elementClass", "shouldTrimPattern"] }, { kind: "component", type: FilePickerComponent, selector: "c8y-file-picker", inputs: ["maxAllowedFiles", "uploadChoice", "allowedUploadChoices", "fileUrl", "fileBinary", "config", "filePickerIndex", "fileUrlPopover", "editInlineConfirmTitle", "editInlineConfirmMessage"], outputs: ["onFilesPicked", "uploadChoiceChange", "fileBinaryChange", "fileUrlChange", "textFilePicked"] }, { kind: "component", type: EditorComponent, selector: "c8y-editor", inputs: ["editorOptions", "theme"], outputs: ["editorInit"] }, { kind: "directive", type: MonacoEditorMarkerValidatorDirective, selector: "c8y-editor [monacoEditorMarkerValidator]" }, { kind: "component", type: MessagesComponent, selector: "c8y-messages", inputs: ["show", "defaults", "helpMessage", "additionalMessages"] }, { kind: "directive", type: RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "component", type: SelectComponent, selector: "c8y-select", inputs: ["placeholder", "items", "selected", "container", "multi", "canSelectWithSpace", "disabled", "autoClose", "insideClick", "required", "canDeselect", "name", "icon", "filterItems"], outputs: ["onSelect", "onDeselect", "onIconClick"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
910
|
-
}
|
|
911
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ConfigurationDetailComponent, decorators: [{
|
|
912
|
-
type: Component,
|
|
913
|
-
args: [{ selector: 'c8y-configuration-detail', changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'd-contents' }, imports: [
|
|
914
|
-
ReactiveFormsModule,
|
|
915
|
-
FormsModule,
|
|
916
|
-
IconDirective,
|
|
917
|
-
C8yTranslateDirective,
|
|
918
|
-
FormGroupComponent,
|
|
919
|
-
TypeaheadComponent,
|
|
920
|
-
ForOfDirective,
|
|
921
|
-
ListItemComponent,
|
|
922
|
-
HighlightComponent,
|
|
923
|
-
FilePickerComponent,
|
|
924
|
-
NgClass,
|
|
925
|
-
C8yTranslatePipe,
|
|
926
|
-
EditorComponent,
|
|
927
|
-
MonacoEditorMarkerValidatorDirective,
|
|
928
|
-
MessagesComponent,
|
|
929
|
-
RequiredInputPlaceholderDirective,
|
|
930
|
-
SelectComponent
|
|
931
|
-
], template: "<div class=\"card-header j-c-center separator flex-no-shrink p-24\">\n <i\n class=\"m-r-8\"\n [c8yIcon]=\"'cogs'\"\n aria-hidden=\"true\"\n ></i>\n <h4 id=\"drawerTitle\">\n @if (mo?.id) {\n {{ 'Update configuration' | translate }}\n } @else {\n {{ 'Add configuration' | translate }}\n }\n </h4>\n</div>\n\n<form\n class=\"d-contents\"\n [formGroup]=\"form\"\n (ngSubmit)=\"save()\"\n>\n <div\n class=\"card-body inner-scroll p-24\"\n [class.d-col]=\"form.value.uploadChoice === 'editInline'\"\n >\n <div class=\"row\">\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confVersion\"\n translate\n >\n Name\n </label>\n <input\n class=\"form-control\"\n id=\"confVersion\"\n placeholder=\"{{ 'e.g.' | translate }} hosts\"\n aria-required=\"true\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"version\"\n />\n </c8y-form-group>\n\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confDeviceType\"\n translate\n >\n Device type\n </label>\n <input\n class=\"form-control\"\n id=\"confDeviceType\"\n placeholder=\"{{ 'e.g.' | translate }} c8y_Linux\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"deviceType\"\n />\n </c8y-form-group>\n\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confDescription\"\n translate\n >\n Description\n </label>\n <input\n class=\"form-control\"\n id=\"confDescription\"\n placeholder=\"{{ 'e.g.' | translate }} Host configuration\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"description\"\n />\n </c8y-form-group>\n\n <c8y-form-group class=\"col-sm-3\">\n <label translate>Configuration type</label>\n <c8y-typeahead\n placeholder=\"{{ 'e.g.' | translate }} ssh\"\n name=\"confType\"\n data-cy=\"conf-detail--configuration-type\"\n [ngModel]=\"configurationTypeMO\"\n [ngModelOptions]=\"{ standalone: true }\"\n (ngModelChange)=\"configurationTypeMO = $event\"\n maxlength=\"254\"\n (onSearch)=\"setPipe($event)\"\n displayProperty=\"configurationType\"\n >\n @let patternStr = pattern();\n <c8y-li\n class=\"p-l-8 p-r-8 c8y-list__item--link\"\n *c8yFor=\"let config of configs; pipe: filterPipe; notFound: notFoundTemplate\"\n (click)=\"configurationTypeMO = config; setPipe('')\"\n [active]=\"configurationTypeMO === config\"\n >\n <c8y-highlight\n [text]=\"config.configurationType || '--'\"\n [pattern]=\"patternStr\"\n ></c8y-highlight>\n </c8y-li>\n <ng-template #notFoundTemplate>\n @if (patternStr.length > 0) {\n <c8y-li class=\"bg-level-2 p-8\">\n <span translate>No match found.</span>\n <button\n class=\"btn btn-primary btn-xs m-l-8\"\n title=\"{{ 'Add new`configuration type`' | translate }}\"\n type=\"button\"\n translate\n >\n Add new`configuration type`\n </button>\n </c8y-li>\n }\n </ng-template>\n </c8y-typeahead>\n </c8y-form-group>\n </div>\n\n <fieldset\n class=\"d-col\"\n [class.flex-grow]=\"form.value.uploadChoice === 'editInline'\"\n [class.min-height-0]=\"form.value.uploadChoice === 'editInline'\"\n >\n <legend\n class=\"legend form-block m-t-40\"\n translate\n >\n Configuration file\n </legend>\n\n @if (isExternalUrl()) {\n <p\n class=\"text-muted m-b-8\"\n translate\n >\n Content is sourced from a remote URL and cannot be edited inline.\n </p>\n <c8y-form-group>\n <label\n for=\"confFileUrl\"\n translate\n >\n File URL\n </label>\n <input\n class=\"form-control\"\n id=\"confFileUrl\"\n placeholder=\"https://...\"\n type=\"url\"\n autocomplete=\"off\"\n [ngModel]=\"binary.url\"\n [ngModelOptions]=\"{ standalone: true }\"\n (ngModelChange)=\"binary = { url: $event }; form.markAsDirty()\"\n />\n </c8y-form-group>\n } @else {\n <c8y-file-picker\n [maxAllowedFiles]=\"1\"\n [allowedUploadChoices]=\"['uploadBinary', 'uploadUrl', 'editInline']\"\n [uploadChoice]=\"form.controls.uploadChoice.value\"\n (uploadChoiceChange)=\"onUploadChoiceChange($event)\"\n [fileUrl]=\"binary.url\"\n (fileUrlChange)=\"onFileUrlChange($event)\"\n [fileBinary]=\"binary.file\"\n (fileBinaryChange)=\"onFileBinaryChange($event)\"\n (textFilePicked)=\"onTextFilePicked($event)\"\n [fileUrlPopover]=\"textForConfigurationUrlPopover\"\n >\n <ng-container c8yInlineEditor>\n <div class=\"row\">\n <c8y-form-group class=\"col-sm-9\">\n <label\n for=\"confEditorFilename\"\n translate\n >\n Filename\n </label>\n <input\n class=\"form-control\"\n id=\"confEditorFilename\"\n placeholder=\"config.json\"\n aria-required=\"true\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"editorFilename\"\n />\n </c8y-form-group>\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confEditorLanguage\"\n translate\n >\n Language\n </label>\n <c8y-select\n aria-label=\"{{ 'Editor language' | translate }}\"\n id=\"confEditorLanguage\"\n [items]=\"editorLanguages\"\n formControlName=\"editorLanguage\"\n ></c8y-select>\n </c8y-form-group>\n </div>\n <c8y-form-group class=\"d-col flex-grow min-height-0 m-b-0\">\n <label\n for=\"confEditorContent\"\n translate\n >\n Content\n </label>\n <c8y-editor\n class=\"flex-grow\"\n style=\"min-height: 95px\"\n id=\"confEditorContent\"\n formControlName=\"editorContent\"\n monacoEditorMarkerValidator\n [editorOptions]=\"{ language: editorLanguage() }\"\n ></c8y-editor>\n <c8y-messages></c8y-messages>\n </c8y-form-group>\n </ng-container>\n </c8y-file-picker>\n }\n </fieldset>\n </div>\n\n <div class=\"card-footer j-c-between separator flex-no-shrink p-24\">\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Cancel' | translate }}\"\n type=\"button\"\n (click)=\"cancel()\"\n [disabled]=\"saving()\"\n >\n <span translate>Cancel</span>\n </button>\n <button\n class=\"btn btn-primary\"\n [class.btn-pending]=\"saving()\"\n [title]=\"submitButtonTitle() | translate\"\n type=\"submit\"\n [disabled]=\"isSaveDisabled\"\n >\n {{ submitButtonTitle() | translate }}\n </button>\n </div>\n</form>\n" }]
|
|
932
|
-
}], ctorParameters: () => [], propDecorators: { version: [{
|
|
933
|
-
type: Input
|
|
934
|
-
}], description: [{
|
|
935
|
-
type: Input
|
|
936
|
-
}], binary: [{
|
|
937
|
-
type: Input
|
|
938
|
-
}], deviceType: [{
|
|
939
|
-
type: Input
|
|
940
|
-
}], mo: [{
|
|
941
|
-
type: Input
|
|
942
|
-
}] } });
|
|
943
|
-
|
|
944
1020
|
class ConfigurationListComponent {
|
|
945
1021
|
constructor() {
|
|
946
1022
|
this.alert = inject(AlertService);
|