1
0
mirror of https://github.com/ceph/ceph synced 2025-04-01 23:02:17 +00:00

mgr/dashboard: Update bootstrap ()

mgr/dashboard: Update bootstrap

Reviewed-by: Ricardo Marques <rimarques@suse.com>
Reviewed-by: Tatjana Dehler <tdehler@suse.com>
This commit is contained in:
Lenz Grimmer 2019-07-11 12:15:20 +02:00 committed by GitHub
commit c58ca8ea5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
198 changed files with 3659 additions and 4153 deletions
src/pybind/mgr/dashboard
HACKING.rst
frontend
angular.jsonpackage-lock.jsonpackage.json
src/app
app.component.scssapp.component.spec.tsapp.component.tsapp.module.ts
ceph
block
block.module.ts
iscsi-tabs
iscsi-target-details
iscsi-target-discovery-modal
iscsi-target-form
iscsi-target-image-settings-modal
iscsi-target-iqn-settings-modal
iscsi-target-list
iscsi
mirroring
rbd-configuration-form
rbd-configuration-list
rbd-details
rbd-form
rbd-images
rbd-list
rbd-snapshot-form
rbd-snapshot-list
rbd-trash-list
rbd-trash-move-modal
rbd-trash-purge-modal
rbd-trash-restore-modal
cephfs/cephfs-detail
cluster
dashboard
nfs
pool/erasure-code-profile-form

View File

@ -252,7 +252,7 @@ Example:
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { ToastsManager } from 'ng2-toastr';
import { ToastrManager } from 'ngx-toastr';
import { Credentials } from '../../../shared/models/credentials.model';
import { HostService } from './services/host.service';
@ -295,7 +295,7 @@ or removed to/from a set of items (e.g.: 'Add permission' to a user vs. 'Create
In order to enforce the use of this wording, a service ``ActionLabelsI18n`` has
been created, which provides translated labels for use in UI elements.
Frontend branding
~~~~~~~~~~~~~~~~~
@ -1500,7 +1500,7 @@ decorators that can be used to add more information:
* ``@EndpointDoc()`` for documentation of endpoints. It has four optional arguments
(explained below): ``description``, ``group``, ``parameters`` and``responses``.
* ``@ControllerDoc()`` for documentation of controller or group associated with
* ``@ControllerDoc()`` for documentation of controller or group associated with
the endpoints. It only takes the two first arguments: ``description`` and``group``.
@ -1509,7 +1509,7 @@ decorators that can be used to add more information:
``group``: By default, an endpoint is grouped together with other endpoints
within the same controller class. ``group`` is a string that can be used to
assign an endpoint or all endpoints in a class to another controller or a
assign an endpoint or all endpoints in a class to another controller or a
conceived group name.
@ -1543,7 +1543,7 @@ for nested parameters).
'item2': (str, 'Description of item2', True), # item2 is optional
'item3': (str, 'Description of item3', True, 'foo'), # item3 is optional with 'foo' as default value
}, 'Description of my_dictionary')})
If the parameter is a ``list`` of primitive types, the type should be
surrounded with square brackets.

View File

@ -21,18 +21,21 @@
"src/favicon.ico"
],
"styles": [
"node_modules/bootstrap/dist/css/bootstrap.css",
"node_modules/ng2-toastr/bundles/ng2-toastr.min.css",
"node_modules/fork-awesome/css/fork-awesome.css",
"node_modules/awesome-bootstrap-checkbox/awesome-bootstrap-checkbox.css",
"node_modules/ngx-toastr/toastr.css",
"node_modules/ngx-bootstrap/datepicker/bs-datepicker.css",
"src/styles.scss",
"src/vendor.overrides.scss",
"src/styles/vendor.overrides.scss",
"node_modules/ng2-tree/styles.css"
],
"scripts": [
"node_modules/chart.js/dist/Chart.bundle.js"
]
],
"stylePreprocessorOptions": {
"includePaths": [
"src/styles",
"src"
]
}
},
"configurations": {
"production": {

File diff suppressed because it is too large Load Diff

View File

@ -67,8 +67,7 @@
"@auth0/angular-jwt": "2.1.0",
"@ngx-translate/i18n-polyfill": "1.0.0",
"@swimlane/ngx-datatable": "14.0.0",
"awesome-bootstrap-checkbox": "0.3.7",
"bootstrap": "3.4.1",
"bootstrap": "4.3.1",
"chart.js": "2.7.3",
"core-js": "2.6.5",
"detect-browser": "4.1.0",
@ -76,10 +75,11 @@
"lodash": "4.17.11",
"moment": "2.24.0",
"ng-block-ui": "2.1.1",
"ng-bootstrap-form-validation": "4.0.0",
"ng2-charts": "1.6.0",
"ng2-toastr": "zzakir/ng2-toastr#0eafd72",
"ng2-tree": "2.0.0-rc.11",
"ngx-bootstrap": "3.2.0",
"ngx-bootstrap": "4.3.0",
"ngx-toastr": "10.0.2",
"rxjs": "6.4.0",
"rxjs-compat": "6.4.0",
"tslib": "1.9.3",

View File

@ -1,4 +1,4 @@
@import '../defaults';
@import 'defaults';
.dashboard {
background-color: $color-whitesmoke-gray;
@ -13,7 +13,7 @@
margin-top: 2.5vw;
}
@media (max-width: $screen-sm-max) {
@media (max-width: $screen-md-max) {
margin-top: 9vw;
}

View File

@ -2,8 +2,6 @@ import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastModule } from 'ng2-toastr';
import { configureTestBed } from '../testing/unit-test-helper';
import { AppComponent } from './app.component';
import { AuthStorageService } from './shared/services/auth-storage.service';
@ -13,7 +11,7 @@ describe('AppComponent', () => {
let fixture: ComponentFixture<AppComponent>;
configureTestBed({
imports: [RouterTestingModule, ToastModule.forRoot()],
imports: [RouterTestingModule],
declarations: [AppComponent],
schemas: [NO_ERRORS_SCHEMA],
providers: [AuthStorageService]

View File

@ -1,7 +1,6 @@
import { Component, ViewContainerRef } from '@angular/core';
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { ToastsManager } from 'ng2-toastr';
import { TooltipConfig } from 'ngx-bootstrap/tooltip';
import { AuthStorageService } from './shared/services/auth-storage.service';
@ -23,14 +22,7 @@ import { AuthStorageService } from './shared/services/auth-storage.service';
export class AppComponent {
title = 'cd';
constructor(
private authStorageService: AuthStorageService,
private router: Router,
public toastr: ToastsManager,
private vcr: ViewContainerRef
) {
this.toastr.setRootViewContainerRef(this.vcr);
}
constructor(private authStorageService: AuthStorageService, private router: Router) {}
isLoginActive() {
return this.router.url === '/login' || !this.authStorageService.isLoggedIn();

View File

@ -7,10 +7,11 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { JwtModule } from '@auth0/angular-jwt';
import { I18n } from '@ngx-translate/i18n-polyfill';
import { BlockUIModule } from 'ng-block-ui';
import { ToastModule, ToastOptions } from 'ng2-toastr/ng2-toastr';
import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
import { AccordionModule } from 'ngx-bootstrap/accordion';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { ToastrModule } from 'ngx-toastr';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@ -21,13 +22,6 @@ import { ApiInterceptorService } from './shared/services/api-interceptor.service
import { JsErrorHandler } from './shared/services/js-error-handler.service';
import { SharedModule } from './shared/shared.module';
export class CustomOption extends ToastOptions {
animate = 'flyRight';
newestOnTop = true;
showCloseButton = true;
enableHTML = true;
}
export function jwtTokenGetter() {
return localStorage.getItem('access_token');
}
@ -41,7 +35,11 @@ registerLocaleData(LocaleHelper.getLocaleData(), LocaleHelper.getLocale());
BlockUIModule.forRoot(),
BrowserModule,
BrowserAnimationsModule,
ToastModule.forRoot(),
ToastrModule.forRoot({
positionClass: 'toast-top-right',
preventDuplicates: true,
enableHtml: true
}),
AppRoutingModule,
CoreModule,
SharedModule,
@ -53,7 +51,8 @@ registerLocaleData(LocaleHelper.getLocaleData(), LocaleHelper.getLocale());
config: {
tokenGetter: jwtTokenGetter
}
})
}),
NgBootstrapFormValidationModule.forRoot()
],
exports: [SharedModule],
providers: [
@ -66,10 +65,6 @@ registerLocaleData(LocaleHelper.getLocaleData(), LocaleHelper.getLocale());
useClass: ApiInterceptorService,
multi: true
},
{
provide: ToastOptions,
useClass: CustomOption
},
i18nProviders,
I18n
],

View File

@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule, Routes } from '@angular/router';
import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
import { TreeModule } from 'ng2-tree';
import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
@ -62,7 +63,8 @@ import { RbdTrashRestoreModalComponent } from './rbd-trash-restore-modal/rbd-tra
ModalModule.forRoot(),
SharedModule,
RouterModule,
TreeModule
TreeModule,
NgBootstrapFormValidationModule
],
declarations: [
RbdListComponent,

View File

@ -2,11 +2,11 @@
<tab heading="Overview"
i18n-heading
[active]="url === '/block/iscsi/overview'"
(select)="navigateTo('/block/iscsi/overview')">
(selectTab)="navigateTo('/block/iscsi/overview')">
</tab>
<tab heading="Targets"
i18n-heading
[active]="url === '/block/iscsi/targets'"
(select)="navigateTo('/block/iscsi/targets')">
(selectTab)="navigateTo('/block/iscsi/targets')">
</tab>
</tabset>

View File

@ -1,30 +1,32 @@
<div class="col-sm-6 col-lg-6">
<legend i18n>iSCSI Topology</legend>
<tree [tree]="tree"
(nodeSelected)="onNodeSelected($event)">
<ng-template let-node>
<span class="node-name"
[innerHTML]="node.value"></span>
<span>&nbsp;</span>
<div class="row">
<div class="col-6">
<legend i18n>iSCSI Topology</legend>
<tree [tree]="tree"
(nodeSelected)="onNodeSelected($event)">
<ng-template let-node>
<span class="node-name"
[innerHTML]="node.value"></span>
<span>&nbsp;</span>
<span class="label"
[ngClass]="{'label-success': ['logged_in'].includes(node.status), 'label-danger': ['logged_out'].includes(node.status)}">
{{ node.status }}
</span>
</ng-template>
</tree>
</div>
<span class="badge"
[ngClass]="{'badge-success': ['logged_in'].includes(node.status), 'badge-danger': ['logged_out'].includes(node.status)}">
{{ node.status }}
</span>
</ng-template>
</tree>
</div>
<div class="col-sm-6 col-lg-6 metadata"
*ngIf="data">
<legend>{{ title }}</legend>
<div class="col-6 metadata"
*ngIf="data">
<legend>{{ title }}</legend>
<cd-table #detailTable
[data]="data"
columnMode="flex"
[columns]="columns"
[limit]="0">
</cd-table>
<cd-table #detailTable
[data]="data"
columnMode="flex"
[columns]="columns"
[limit]="0">
</cd-table>
</div>
</div>
<ng-template #highlightTpl

View File

@ -111,8 +111,8 @@ describe('IscsiTargetDetailsComponent', () => {
children: [{ id: 'disk_rbd_disk_1', value: 'rbd/disk_1' }],
settings: {
cssClasses: {
expanded: _.join([Icons.width, Icons.large, Icons.disk], ' '),
leaf: _.join([Icons.width, Icons.disk], ' ')
expanded: _.join([Icons.large, Icons.disk], ' '),
leaf: _.join([Icons.disk], ' ')
},
selectionAllowed: false
},
@ -122,8 +122,8 @@ describe('IscsiTargetDetailsComponent', () => {
children: [{ value: 'node1:192.168.100.201' }],
settings: {
cssClasses: {
expanded: _.join([Icons.width, Icons.large, Icons.server], ' '),
leaf: _.join([Icons.width, Icons.large, Icons.server], ' ')
expanded: _.join([Icons.large, Icons.server], ' '),
leaf: _.join([Icons.large, Icons.server], ' ')
},
selectionAllowed: false
},
@ -137,8 +137,8 @@ describe('IscsiTargetDetailsComponent', () => {
id: 'disk_rbd_disk_1',
settings: {
cssClasses: {
expanded: _.join([Icons.width, Icons.large, Icons.disk], ' '),
leaf: _.join([Icons.width, Icons.disk], ' ')
expanded: _.join([Icons.large, Icons.disk], ' '),
leaf: _.join([Icons.disk], ' ')
}
},
value: 'rbd/disk_1'
@ -151,8 +151,8 @@ describe('IscsiTargetDetailsComponent', () => {
],
settings: {
cssClasses: {
expanded: _.join([Icons.width, Icons.large, Icons.user], ' '),
leaf: _.join([Icons.width, Icons.user], ' ')
expanded: _.join([Icons.large, Icons.user], ' '),
leaf: _.join([Icons.user], ' ')
},
selectionAllowed: false
},
@ -162,8 +162,8 @@ describe('IscsiTargetDetailsComponent', () => {
children: [],
settings: {
cssClasses: {
expanded: _.join([Icons.width, Icons.large, Icons.user], ' '),
leaf: _.join([Icons.width, Icons.user], ' ')
expanded: _.join([Icons.large, Icons.user], ' '),
leaf: _.join([Icons.user], ' ')
},
selectionAllowed: false
},
@ -172,7 +172,7 @@ describe('IscsiTargetDetailsComponent', () => {
],
id: 'root',
settings: {
cssClasses: { expanded: _.join([Icons.width, Icons.large, Icons.bullseye], ' ') },
cssClasses: { expanded: _.join([Icons.large, Icons.bullseye], ' ') },
static: true
},
value: 'iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw'

View File

@ -78,23 +78,23 @@ export class IscsiTargetDetailsComponent implements OnChanges, OnInit {
this.metadata = { root: this.selectedItem.target_controls };
const cssClasses = {
target: {
expanded: _.join([Icons.width, Icons.large, Icons.bullseye], ' ')
expanded: _.join([Icons.large, Icons.bullseye], ' ')
},
initiators: {
expanded: _.join([Icons.width, Icons.large, Icons.user], ' '),
leaf: _.join([Icons.width, Icons.user], ' ')
expanded: _.join([Icons.large, Icons.user], ' '),
leaf: _.join([Icons.user], ' ')
},
groups: {
expanded: _.join([Icons.width, Icons.large, Icons.user], ' '),
leaf: _.join([Icons.width, Icons.user], ' ')
expanded: _.join([Icons.large, Icons.user], ' '),
leaf: _.join([Icons.user], ' ')
},
disks: {
expanded: _.join([Icons.width, Icons.large, Icons.disk], ' '),
leaf: _.join([Icons.width, Icons.disk], ' ')
expanded: _.join([Icons.large, Icons.disk], ' '),
leaf: _.join([Icons.disk], ' ')
},
portals: {
expanded: _.join([Icons.width, Icons.large, Icons.server], ' '),
leaf: _.join([Icons.width, Icons.large, Icons.server], ' ')
expanded: _.join([Icons.large, Icons.server], ' '),
leaf: _.join([Icons.large, Icons.server], ' ')
}
};

View File

@ -4,15 +4,13 @@
<ng-container class="modal-content">
<form name="discoveryForm"
class="form-horizontal"
#formDir="ngForm"
[formGroup]="discoveryForm"
novalidate>
<div class="modal-body">
<!-- User -->
<div class="form-group"
[ngClass]="{'has-error': discoveryForm.showError('user', formDir)}">
<label class="control-label col-sm-4"
<div class="form-group row">
<label class="col-form-label col-sm-4"
for="user"
i18n>User</label>
<div class="col-sm-8">
@ -20,11 +18,11 @@
class="form-control"
formControlName="user"
type="text">
<span class="help-block"
<span class="invalid-feedback"
*ngIf="discoveryForm.showError('user', formDir, 'required')"
i18n>This field is required.</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="discoveryForm.showError('user', formDir, 'pattern')"
i18n>Usernames must have a length of 8 to 64 characters and
can only contain letters, '.', '@', '-', '_' or ':'.</span>
@ -32,9 +30,8 @@
</div>
<!-- Password -->
<div class="form-group"
[ngClass]="{'has-error': discoveryForm.showError('password', formDir)}">
<label class="control-label col-sm-4"
<div class="form-group row">
<label class="col-form-label col-sm-4"
for="password"
i18n>Password</label>
<div class="col-sm-8">
@ -44,22 +41,22 @@
formControlName="password"
type="password">
<span class="input-group-btn">
<span class="input-group-append">
<button type="button"
class="btn btn-default"
class="btn btn-light"
cdPasswordButton="password">
</button>
<button type="button"
class="btn btn-default"
class="btn btn-light"
cdCopy2ClipboardButton="password">
</button>
</span>
</div>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="discoveryForm.showError('password', formDir, 'required')"
i18n>This field is required.</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="discoveryForm.showError('password', formDir, 'pattern')"
i18n>Passwords must have a length of 12 to 16 characters
and can only contain letters, '@', '-', '_' or '/'.</span>
@ -67,9 +64,8 @@
</div>
<!-- mutual_user -->
<div class="form-group"
[ngClass]="{'has-error': discoveryForm.showError('mutual_user', formDir)}">
<label class="control-label col-sm-4"
<div class="form-group row">
<label class="col-form-label col-sm-4"
for="mutual_user">
<ng-container i18n>Mutual User</ng-container>
</label>
@ -79,11 +75,11 @@
formControlName="mutual_user"
type="text">
<span class="help-block"
<span class="invalid-feedback"
*ngIf="discoveryForm.showError('mutual_user', formDir, 'required')"
i18n>This field is required.</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="discoveryForm.showError('mutual_user', formDir, 'pattern')"
i18n>Usernames must have a length of 8 to 64 characters and
can only contain letters, '.', '@', '-', '_' or ':'.</span>
@ -91,9 +87,8 @@
</div>
<!-- mutual_password -->
<div class="form-group"
[ngClass]="{'has-error': discoveryForm.showError('mutual_password', formDir)}">
<label class="control-label col-sm-4"
<div class="form-group row">
<label class="col-form-label col-sm-4"
for="mutual_password"
i18n>Mutual Password</label>
<div class="col-sm-8">
@ -103,22 +98,22 @@
formControlName="mutual_password"
type="password">
<span class="input-group-btn">
<span class="input-group-append">
<button type="button"
class="btn btn-default"
class="btn btn-light"
cdPasswordButton="mutual_password">
</button>
<button type="button"
class="btn btn-default"
class="btn btn-light"
cdCopy2ClipboardButton="mutual_password">
</button>
</span>
</div>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="discoveryForm.showError('mutual_password', formDir, 'required')"
i18n>This field is required.</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="discoveryForm.showError('mutual_password', formDir, 'pattern')"
i18n>Passwords must have a length of 12 to 16 characters and
can only contain letters, '@', '-', '_' or '/'.</span>

View File

@ -8,8 +8,8 @@ import { ReactiveFormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastModule } from 'ng2-toastr';
import { BsModalRef } from 'ngx-bootstrap/modal';
import { ToastrModule } from 'ngx-toastr';
import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
import { Permission } from '../../../shared/models/permissions';
@ -31,7 +31,7 @@ describe('IscsiTargetDiscoveryModalComponent', () => {
HttpClientTestingModule,
ReactiveFormsModule,
SharedModule,
ToastModule.forRoot(),
ToastrModule.forRoot(),
RouterTestingModule
],
providers: [i18nProviders, BsModalRef]

View File

@ -1,21 +1,17 @@
<div class="col-sm-12 col-lg-6">
<form name="targetForm"
class="form-horizontal"
#formDir="ngForm"
[formGroup]="targetForm"
novalidate
*ngIf="targetForm">
<div class="panel panel-default">
<div class="panel-heading">
<h3 i18n="form title|Example: Create Pool@@formTitle"
class="panel-title">{{ action | titlecase }} {{ resource | upperFirst }}</h3>
</div>
<div class="card">
<div i18n="form title|Example: Create Pool@@formTitle"
class="card-header">{{ action | titlecase }} {{ resource | upperFirst }}</div>
<div class="panel-body">
<div class="card-body">
<!-- Target IQN -->
<div class="form-group"
[ngClass]="{'has-error': targetForm.showError('target_iqn', formDir)}">
<label class="control-label col-sm-3"
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="target_iqn">
<ng-container i18n>Target IQN</ng-container>
<span class="required"></span>
@ -27,28 +23,29 @@
id="target_iqn"
name="target_iqn"
formControlName="target_iqn" />
<span class="input-group-btn">
<button class="btn btn-default"
<span class="input-group-append">
<button class="btn btn-light"
id="ecp-info-button"
type="button"
(click)="targetSettingsModal()">
<i [ngClass]="[icons.deepCheck, icons.width]"
<i [ngClass]="[icons.deepCheck]"
aria-hidden="true"></i>
</button>
</span>
</div>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="targetForm.showError('target_iqn', formDir, 'required')"
i18n>This field is required.</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="targetForm.showError('target_iqn', formDir, 'pattern')"
i18n>IQN has wrong pattern.</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="targetForm.showError('target_iqn', formDir, 'iqn')">
<ng-container i18n>An IQN has the following notation 'iqn.$year-$month.$reversedAddress:$definedName'</ng-container>
<ng-container i18n>An IQN has the following notation
'iqn.$year-$month.$reversedAddress:$definedName'</ng-container>
<br>
<ng-container i18n>For example: iqn.2016-06.org.dashboard:storage:disk.sn-a8675309</ng-container>
<br>
@ -57,7 +54,7 @@
i18n>More information</a>
</span>
<span class="help-block"
<span class="form-text text-muted"
*ngIf="hasAdvancedSettings(targetForm.getValue('target_controls'))"
i18n>This target has modified advanced settings.</span>
<hr />
@ -65,9 +62,8 @@
</div>
<!-- Portals -->
<div class="form-group"
[ngClass]="{'has-error': targetForm.showError('portals', formDir)}">
<label class="control-label col-sm-3"
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="portals">
<ng-container i18n>Portals</ng-container>
<span class="required"></span>
@ -76,22 +72,22 @@
<ng-container *ngFor="let portal of portals.value; let i = index">
<div class="input-group cd-mb">
<input class="form-control"
<input class="cd-form-control"
type="text"
[value]="portal"
disabled />
<span class="input-group-btn">
<button class="btn btn-default"
<span class="input-group-append">
<button class="btn btn-light"
type="button"
(click)="removePortal(i, portal)">
<i [ngClass]="[icons.destroy, icons.width]"
<i [ngClass]="[icons.destroy]"
aria-hidden="true"></i>
</button>
</span>
</div>
</ng-container>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="targetForm.showError('portals', formDir, 'minGateways')"
i18n>At least {{ minimum_gateways }} gateways are required.</span>
@ -101,8 +97,8 @@
[options]="portalsSelections"
[messages]="messages.portals"
(selection)="onPortalSelection($event)"
elemClass="btn btn-default pull-right">
<i [ngClass]="[icons.add, icons.width]"></i>
elemClass="btn btn-light float-right">
<i [ngClass]="[icons.add]"></i>
<ng-container i18n>Add portal</ng-container>
</cd-select>
</div>
@ -113,36 +109,35 @@
</div>
<!-- Images -->
<div class="form-group"
[ngClass]="{'has-error': targetForm.showError('disks', formDir)}">
<label class="control-label col-sm-3"
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="disks"
i18n>Images</label>
<div class="col-sm-9">
<ng-container *ngFor="let image of targetForm.getValue('disks'); let i = index">
<div class="input-group cd-mb">
<input class="form-control"
<input class="cd-form-control"
type="text"
[value]="image"
disabled />
<span class="input-group-btn">
<button class="btn btn-default"
<span class="input-group-append">
<button class="btn btn-light"
type="button"
(click)="imageSettingsModal(image)">
<i [ngClass]="[icons.deepCheck, icons.width]"
<i [ngClass]="[icons.deepCheck]"
aria-hidden="true"></i>
</button>
<button class="btn btn-default"
<button class="btn btn-light"
type="button"
(click)="removeImage(i, image)">
<i [ngClass]="[icons.destroy, icons.width]"
<i [ngClass]="[icons.destroy]"
aria-hidden="true"></i>
</button>
</span>
</div>
<span class="help-block">
<span class="form-text text-muted">
<ng-container *ngIf="backstores.length > 1"
i18n>Backstore: {{ imagesSettings[image].backstore | iscsiBackstore }}.&nbsp;</ng-container>
@ -151,7 +146,7 @@
</span>
</ng-container>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="targetForm.showError('disks', formDir, 'required')"
i18n>At least 1 image is required.</span>
@ -161,8 +156,8 @@
[options]="imagesSelections"
[messages]="messages.images"
(selection)="onImageSelection($event)"
elemClass="btn btn-default pull-right">
<i [ngClass]="[icons.add, icons.width]"></i>
elemClass="btn btn-light float-right">
<i [ngClass]="[icons.add]"></i>
<ng-container i18n>Add image</ng-container>
</cd-select>
</div>
@ -173,14 +168,16 @@
</div>
<!-- acl_enabled -->
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<div class="checkbox checkbox-primary">
<div class="form-group row">
<div class="offset-sm-3 col-sm-9">
<div class="custom-control custom-checkbox">
<input type="checkbox"
class="custom-control-input"
formControlName="acl_enabled"
name="acl_enabled"
id="acl_enabled">
<label for="acl_enabled"
class="custom-control-label"
i18n>ACL authentication</label>
</div>
@ -189,29 +186,28 @@
</div>
<!-- Initiators -->
<div class="form-group"
<div class="form-group row"
*ngIf="targetForm.getValue('acl_enabled')">
<label class="control-label col-sm-3"
<label class="col-form-label col-sm-3"
for="initiators"
i18n>Initiators</label>
<div class="col-sm-9"
formArrayName="initiators">
<div class="panel panel-default"
<div class="card mb-2"
*ngFor="let initiator of initiators.controls; let ii = index"
[formGroupName]="ii">
<div class="panel-heading">
<div class="card-header">
<ng-container i18n>Initiator</ng-container>: {{ initiator.getValue('client_iqn') }}
<button type="button"
class="close"
(click)="removeInitiator(ii)">
<i [ngClass]="[icons.deepCheck, icons.width]"></i>
<i [ngClass]="[icons.deepCheck]"></i>
</button>
</div>
<div class="panel-body">
<div class="card-body">
<!-- Initiator: Name -->
<div class="form-group"
[ngClass]="{'has-error': initiator.showError('client_iqn', formDir)}">
<label class="control-label col-sm-3"
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="client_iqn">
<ng-container i18n>Client IQN</ng-container>
<span class="required"></span>
@ -222,15 +218,15 @@
formControlName="client_iqn"
(blur)="updatedInitiatorSelector()">
<span class="help-block"
<span class="invalid-feedback"
*ngIf="initiator.showError('client_iqn', formDir, 'notUnique')"
i18n>Initiator IQN needs to be unique.</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="initiator.showError('client_iqn', formDir, 'required')"
i18n>This field is required.</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="initiator.showError('client_iqn', formDir, 'pattern')"
i18n>IQN has wrong pattern.</span>
</div>
@ -238,9 +234,8 @@
<ng-container formGroupName="auth">
<!-- Initiator: User -->
<div class="form-group"
[ngClass]="{'has-error': initiator.showError('user', formDir)}">
<label class="control-label col-sm-3"
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="user"
i18n>User</label>
<div class="col-sm-9">
@ -248,11 +243,11 @@
class="form-control"
formControlName="user"
type="text">
<span class="help-block"
<span class="invalid-feedback"
*ngIf="initiator.showError('user', formDir, 'required')"
i18n>This field is required.</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="initiator.showError('user', formDir, 'pattern')"
i18n>Usernames must have a length of 8 to 64 characters and
can only contain letters, '.', '@', '-', '_' or ':'.</span>
@ -260,9 +255,8 @@
</div>
<!-- Initiator: Password -->
<div class="form-group"
[ngClass]="{'has-error': initiator.showError('password', formDir)}">
<label class="control-label col-sm-3"
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="password"
i18n>Password</label>
<div class="col-sm-9">
@ -272,22 +266,22 @@
formControlName="password"
type="password">
<span class="input-group-btn">
<span class="input-group-append">
<button type="button"
class="btn btn-default"
class="btn btn-light"
[cdPasswordButton]="'password' + ii">
</button>
<button type="button"
class="btn btn-default"
class="btn btn-light"
[cdCopy2ClipboardButton]="'password' + ii">
</button>
</span>
</div>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="initiator.showError('password', formDir, 'required')"
i18n>This field is required.</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="initiator.showError('password', formDir, 'pattern')"
i18n>Passwords must have a length of 12 to 16 characters
and can only contain letters, '@', '-', '_' or '/'.</span>
@ -296,9 +290,8 @@
<!-- Initiator: mutual_user -->
<div class="form-group"
[ngClass]="{'has-error': initiator.showError('mutual_user', formDir)}">
<label class="control-label col-sm-3"
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="mutual_user">
<ng-container i18n>Mutual User</ng-container>
</label>
@ -308,11 +301,11 @@
formControlName="mutual_user"
type="text">
<span class="help-block"
<span class="invalid-feedback"
*ngIf="initiator.showError('mutual_user', formDir, 'required')"
i18n>This field is required.</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="initiator.showError('mutual_user', formDir, 'pattern')"
i18n>Usernames must have a length of 8 to 64 characters and
can only contain letters, '.', '@', '-', '_' or ':'.</span>
@ -320,9 +313,8 @@
</div>
<!-- Initiator: mutual_password -->
<div class="form-group"
[ngClass]="{'has-error': initiator.showError('mutual_password', formDir)}">
<label class="control-label col-sm-3"
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="mutual_password"
i18n>Mutual Password</label>
<div class="col-sm-9">
@ -332,22 +324,22 @@
formControlName="mutual_password"
type="password">
<span class="input-group-btn">
<span class="input-group-append">
<button type="button"
class="btn btn-default"
class="btn btn-light"
[cdPasswordButton]="'mutual_password' + ii">
</button>
<button type="button"
class="btn btn-default"
class="btn btn-light"
[cdCopy2ClipboardButton]="'mutual_password' + ii">
</button>
</span>
</div>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="initiator.showError('mutual_password', formDir, 'required')"
i18n>This field is required.</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="initiator.showError('mutual_password', formDir, 'pattern')"
i18n>Passwords must have a length of 12 to 16 characters and
can only contain letters, '@', '-', '_' or '/'.</span>
@ -356,23 +348,22 @@
</ng-container>
<!-- Initiator: Images -->
<div class="form-group"
[ngClass]="{'has-error': initiator.showError('luns', formDir)}">
<label class="control-label col-sm-3"
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="luns"
i18n>Images</label>
<div class="col-sm-9">
<ng-container *ngFor="let image of initiator.getValue('luns'); let li = index">
<div class="input-group cd-mb">
<input class="form-control"
<input class="cd-form-control"
type="text"
[value]="image"
disabled />
<span class="input-group-btn">
<button class="btn btn-default"
<span class="input-group-append">
<button class="btn btn-light"
type="button"
(click)="removeInitiatorImage(initiator, li, ii, image)">
<i [ngClass]="[icons.destroy, icons.width]"
<i [ngClass]="[icons.destroy]"
aria-hidden="true"></i>
</button>
</span>
@ -388,8 +379,8 @@
<cd-select [data]="initiator.getValue('luns')"
[options]="imagesInitiatorSelections[ii]"
[messages]="messages.initiatorImage"
elemClass="btn btn-default pull-right">
<i [ngClass]="[icons.add, icons.width]"></i>
elemClass="btn btn-light float-right">
<i [ngClass]="[icons.add]"></i>
<ng-container i18n>Add image</ng-container>
</cd-select>
</div>
@ -401,13 +392,13 @@
<div class="row">
<div class="col-md-12">
<span class="text-muted"
<span class="form-text text-muted"
*ngIf="initiators.controls.length === 0"
i18n>No items added.</span>
<button (click)="addInitiator(); false"
class="btn btn-default pull-right">
<i [ngClass]="[icons.add, icons.width]"></i>
class="btn btn-light float-right">
<i [ngClass]="[icons.add]"></i>
<ng-container i18n>Add initiator</ng-container>
</button>
</div>
@ -418,29 +409,28 @@
</div>
<!-- Groups -->
<div class="form-group"
*ngIf="targetForm.getValue('acl_enabled')"
[ngClass]="{'has-error': targetForm.showError('groups', formDir)}">
<label class="control-label col-sm-3"
<div class="form-group row"
*ngIf="targetForm.getValue('acl_enabled')">
<label class="col-form-label col-sm-3"
for="initiators"
i18n>Groups</label>
<div class="col-sm-9"
formArrayName="groups">
<div class="panel panel-default"
<div class="card mb-2"
*ngFor="let group of groups.controls; let gi = index"
[formGroupName]="gi">
<div class="panel-heading">
<div class="card-header">
<ng-container i18n>Group</ng-container>: {{ group.getValue('group_id') }}
<button type="button"
class="close"
(click)="groups.removeAt(gi)">
<i [ngClass]="[icons.destroy, icons.width]"></i>
<i [ngClass]="[icons.destroy]"></i>
</button>
</div>
<div class="panel-body">
<div class="card-body">
<!-- Group: group_id -->
<div class="form-group">
<label class="control-label col-sm-3"
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="group_id">
<ng-container i18n>Name</ng-container>
<span class="required"></span>
@ -453,24 +443,23 @@
</div>
<!-- Group: members -->
<div class="form-group"
[ngClass]="{'has-error': group.showError('members', formDir)}">
<label class="control-label col-sm-3"
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="members">
<ng-container i18n>Initiators</ng-container>
</label>
<div class="col-sm-9">
<ng-container *ngFor="let member of group.getValue('members'); let i = index">
<div class="input-group cd-mb">
<input class="form-control"
<input class="cd-form-control"
type="text"
[value]="member"
disabled />
<span class="input-group-btn">
<button class="btn btn-default"
<span class="input-group-append">
<button class="btn btn-light"
type="button"
(click)="removeGroupInitiator(group, i, gi)">
<i [ngClass]="[icons.destroy, icons.width]"
<i [ngClass]="[icons.destroy]"
aria-hidden="true"></i>
</button>
</span>
@ -483,8 +472,8 @@
[options]="groupMembersSelections[gi]"
[messages]="messages.groupInitiator"
(selection)="onGroupMemberSelection($event)"
elemClass="btn btn-default pull-right">
<i [ngClass]="[icons.add, icons.width]"></i>
elemClass="btn btn-light float-right">
<i [ngClass]="[icons.add]"></i>
<ng-container i18n>Add initiator</ng-container>
</cd-select>
</div>
@ -495,24 +484,23 @@
</div>
<!-- Group: disks -->
<div class="form-group"
[ngClass]="{'has-error': group.showError('disks', formDir)}">
<label class="control-label col-sm-3"
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="disks">
<ng-container i18n>Images</ng-container>
</label>
<div class="col-sm-9">
<ng-container *ngFor="let disk of group.getValue('disks'); let i = index">
<div class="input-group cd-mb">
<input class="form-control"
<input class="cd-form-control"
type="text"
[value]="disk"
disabled />
<span class="input-group-btn">
<button class="btn btn-default"
<span class="input-group-append">
<button class="btn btn-light"
type="button"
(click)="removeGroupDisk(group, i, gi)">
<i [ngClass]="[icons.destroy, icons.width]"
<i [ngClass]="[icons.destroy]"
aria-hidden="true"></i>
</button>
</span>
@ -524,8 +512,8 @@
<cd-select [data]="group.getValue('disks')"
[options]="groupDiskSelections[gi]"
[messages]="messages.initiatorImage"
elemClass="btn btn-default pull-right">
<i [ngClass]="[icons.add, icons.width]"></i>
elemClass="btn btn-light float-right">
<i [ngClass]="[icons.add]"></i>
<ng-container i18n>Add image</ng-container>
</cd-select>
</div>
@ -539,13 +527,13 @@
<div class="row">
<div class="col-md-12">
<span class="text-muted"
<span class="form-text text-muted"
*ngIf="groups.controls.length === 0"
i18n>No items added.</span>
<button (click)="addGroup(); false"
class="btn btn-default pull-right">
<i [ngClass]="[icons.add, icons.width]"></i>
class="btn btn-light float-right">
<i [ngClass]="[icons.add]"></i>
<ng-container i18n>Add group</ng-container>
</button>
</div>
@ -554,13 +542,11 @@
</div>
</div>
<div class="panel-footer">
<div class="card-footer">
<div class="button-group text-right">
<cd-submit-button
[form]="formDir"
(submitAction)="submit()"
i18n="form action button|Example: Create Pool@@formActionButton"
type="button">{{ action | titlecase }} {{ resource | upperFirst }}</cd-submit-button>
<cd-submit-button (submitAction)="submit()"
i18n="form action button|Example: Create Pool@@formActionButton"
[form]="formDir">{{ action | titlecase }} {{ resource | upperFirst }}</cd-submit-button>
<cd-back-button></cd-back-button>
</div>
</div>

View File

@ -4,7 +4,7 @@ import { ReactiveFormsModule } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastModule } from 'ng2-toastr';
import { ToastrModule } from 'ngx-toastr';
import { ActivatedRouteStub } from '../../../../testing/activated-route-stub';
import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
@ -130,7 +130,7 @@ describe('IscsiTargetFormComponent', () => {
ReactiveFormsModule,
HttpClientTestingModule,
RouterTestingModule,
ToastModule.forRoot()
ToastrModule.forRoot()
],
providers: [
i18nProviders,

View File

@ -12,11 +12,11 @@
<!-- BACKSTORE -->
<div class="form-group row">
<div class="col-sm-12">
<label class="control-label"
<label class="col-form-label"
i18n>Backstore</label>
<select id="backstore"
name="backstore"
class="form-control"
class="form-control custom-select"
[(ngModel)]="model.backstore"
[disabled]="backstores.length == 1">
<option *ngFor="let bs of backstores"
@ -31,12 +31,12 @@
<div class="form-group row"
*ngFor="let setting of disk_default_controls[bs] | keyvalue">
<div class="col-sm-12">
<label class="control-label"
<label class="col-form-label"
for="{{ setting.key }}">{{ setting.key }}</label>
<input type="number"
class="form-control"
[(ngModel)]="model[bs][setting.key]">
<span class="help-block">{{ helpText[setting.key]?.help }}</span>
<span class="form-text text-muted">{{ helpText[setting.key]?.help }}</span>
</div>
</div>
</ng-container>
@ -45,7 +45,7 @@
<div class="modal-footer">
<div class="button-group text-right">
<button class="btn btn-sm btn-primary"
<button class="btn btn-secondary"
(click)="save()"
i18n>Confirm</button>
<cd-back-button [back]="modalRef.hide"

View File

@ -4,7 +4,6 @@
<ng-container class="modal-content">
<form name="settingsForm"
class="form"
#formDir="ngForm"
[formGroup]="settingsForm"
novalidate>
@ -13,10 +12,9 @@
i18n>Changing these parameters from their default values is usually not necessary.</p>
<div class="form-group row"
*ngFor="let setting of settingsForm.controls | keyvalue"
[ngClass]="{'has-error': settingsForm.showError(setting.key, formDir)}">
*ngFor="let setting of settingsForm.controls | keyvalue">
<div class="col-sm-12">
<label class="control-label"
<label class="col-form-label"
for="{{ setting.key }}">{{ setting.key }}</label>
<input class="form-control"
*ngIf="!isRadio(setting.key)"
@ -25,23 +23,27 @@
<ng-container *ngIf="isRadio(setting.key)">
<br>
<div class="radio radio-inline">
<div class="custom-control custom-radio custom-control-inline">
<input type="radio"
[id]="setting.key + 'Yes'"
value="Yes"
[formControlName]="setting.key">
<label [for]="setting.key + 'Yes'">Yes</label>
[formControlName]="setting.key"
class="custom-control-input">
<label class="custom-col-form-label"
[for]="setting.key + 'Yes'">Yes</label>
</div>
<div class="radio radio-inline">
<div class="custom-control custom-radio custom-control-inline">
<input type="radio"
[id]="setting.key + 'No'"
value="No"
class="custom-control-input"
[formControlName]="setting.key">
<label [for]="setting.key + 'No'">No</label>
<label class="custom-col-form-label"
[for]="setting.key + 'No'">No</label>
</div>
</ng-container>
<span class="help-block">{{ helpText[setting.key]?.help }}</span>
<span class="form-text text-muted">{{ helpText[setting.key]?.help }}</span>
</div>
</div>
</div>

View File

@ -30,10 +30,10 @@
[tableActions]="tableActions">
</cd-table-actions>
<button class="btn btn-sm btn-default btn-label"
<button class="btn btn-light"
type="button"
(click)="configureDiscoveryAuth()">
<i [ngClass]="[icons.key, icons.width]"
<i [ngClass]="[icons.key]"
aria-hidden="true">
</i>
<ng-container i18n>Discovery authentication</ng-container>

View File

@ -3,9 +3,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastModule } from 'ng2-toastr';
import { TreeModule } from 'ng2-tree';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { ToastrModule } from 'ngx-toastr';
import { BehaviorSubject, of } from 'rxjs';
import {
@ -40,7 +40,7 @@ describe('IscsiTargetListComponent', () => {
SharedModule,
TabsModule.forRoot(),
TreeModule,
ToastModule.forRoot()
ToastrModule.forRoot()
],
declarations: [IscsiTargetListComponent, IscsiTabsComponent, IscsiTargetDetailsComponent],
providers: [TaskListService, i18nProviders]

View File

@ -13,8 +13,8 @@
<ng-template #statusColorTpl
let-value="value">
<span class="label"
[ngClass]="{'label-success': 'up' == value, 'label-danger': 'down' == value}">{{ value }}</span>
<span class="badge"
[ngClass]="{'badge-success': 'up' == value, 'badge-danger': 'down' == value}">{{ value }}</span>
</ng-template>
<ng-template #iscsiSparklineTpl

View File

@ -35,7 +35,7 @@
</ng-template>
<ng-template #syncTmpl>
<span class="label label-info" i18n>Syncing</span>
<span class="badge badge-info" i18n>Syncing</span>
</ng-template>
<ng-template #progressTmpl

View File

@ -8,18 +8,18 @@ describe('MirrorHealthColorPipe', () => {
});
it('transforms "warning"', () => {
expect(pipe.transform('warning')).toBe('label label-warning');
expect(pipe.transform('warning')).toBe('badge badge-warning');
});
it('transforms "error"', () => {
expect(pipe.transform('error')).toBe('label label-danger');
expect(pipe.transform('error')).toBe('badge badge-danger');
});
it('transforms "success"', () => {
expect(pipe.transform('success')).toBe('label label-success');
expect(pipe.transform('success')).toBe('badge badge-success');
});
it('transforms others', () => {
expect(pipe.transform('abc')).toBe('label label-info');
expect(pipe.transform('abc')).toBe('badge badge-info');
});
});

View File

@ -6,12 +6,12 @@ import { Pipe, PipeTransform } from '@angular/core';
export class MirrorHealthColorPipe implements PipeTransform {
transform(value: any): any {
if (value === 'warning') {
return 'label label-warning';
return 'badge badge-warning';
} else if (value === 'error') {
return 'label label-danger';
return 'badge badge-danger';
} else if (value === 'success') {
return 'label label-success';
return 'badge badge-success';
}
return 'label label-info';
return 'badge badge-info';
}
}

View File

@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
import { TreeModule } from 'ng2-tree';
import { AlertModule } from 'ngx-bootstrap/alert';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
@ -35,7 +36,8 @@ import { PoolListComponent } from './pool-list/pool-list.component';
ModalModule.forRoot(),
AlertModule.forRoot(),
TooltipModule.forRoot(),
TreeModule
TreeModule,
NgBootstrapFormValidationModule
],
declarations: [
DaemonListComponent,

View File

@ -2,10 +2,10 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastModule } from 'ng2-toastr';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { ProgressbarModule } from 'ngx-bootstrap/progressbar';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { ToastrModule } from 'ngx-toastr';
import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
import { SharedModule } from '../../../../shared/shared.module';
@ -34,7 +34,7 @@ describe('OverviewComponent', () => {
ProgressbarModule.forRoot(),
HttpClientTestingModule,
RouterTestingModule,
ToastModule.forRoot()
ToastrModule.forRoot()
],
providers: i18nProviders
});

View File

@ -15,20 +15,19 @@
<kbd>Update</kbd>.</ng-container>
</p>
<div class="form-group"
[ngClass]="{'has-error': editModeForm.showError('mirrorMode', formDir)}">
<label class="control-label"
<div class="form-group">
<label class="col-form-label"
for="mirrorMode">
<span i18n>Mode</span>
</label>
<select id="mirrorMode"
name="mirrorMode"
class="form-control"
class="form-control custom-select"
formControlName="mirrorMode">
<option *ngFor="let mirrorMode of mirrorModes"
[value]="mirrorMode.id">{{ mirrorMode.name }}</option>
</select>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="editModeForm.showError('mirrorMode', formDir, 'cannotDisable')"
i18n>Peer clusters must be removed prior to disabling mirror.</span>
</div>

View File

@ -3,8 +3,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastModule } from 'ng2-toastr';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { ToastrModule } from 'ngx-toastr';
import { of } from 'rxjs';
import {
@ -31,7 +31,7 @@ describe('PoolEditModeModalComponent', () => {
ReactiveFormsModule,
RouterTestingModule,
SharedModule,
ToastModule.forRoot()
ToastrModule.forRoot()
],
providers: [BsModalRef, BsModalService, i18nProviders]
});

View File

@ -12,13 +12,12 @@
<div class="modal-body">
<p>
<ng-container i18n>{mode, select, edit {Edit} other {Add}} the pool
mirror peer attributes for pool <kbd>{{ poolName }}</kbd> and click
mirror peer attributes for pool <kbd>{{ poolName }}</kbd> and click
<kbd>Submit</kbd>.</ng-container>
</p>
<div class="form-group"
[ngClass]="{'has-error': editPeerForm.showError('clusterName', formDir)}">
<label class="control-label"
<div class="form-group">
<label class="col-form-label"
for="clusterName">
<span i18n>Cluster Name</span>
<span class="required"></span>
@ -31,17 +30,16 @@
name="clusterName"
formControlName="clusterName"
autofocus>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="editPeerForm.showError('clusterName', formDir, 'required')"
i18n>This field is required.</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="editPeerForm.showError('clusterName', formDir, 'invalidClusterName')"
i18n>The cluster name is not valid.</span>
</div>
<div class="form-group"
[ngClass]="{'has-error': editPeerForm.showError('clientID', formDir)}">
<label class="control-label"
<div class="form-group">
<label class="col-form-label"
for="clientID">
<span i18n>CephX ID</span>
<span class="required"></span>
@ -53,17 +51,16 @@
id="clientID"
name="clientID"
formControlName="clientID">
<span class="help-block"
<span class="invalid-feedback"
*ngIf="editPeerForm.showError('clientID', formDir, 'required')"
i18n>This field is required.</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="editPeerForm.showError('clientID', formDir, 'invalidClientID')"
i18n>The CephX ID is not valid.</span>
</div>
<div class="form-group"
[ngClass]="{'has-error': editPeerForm.showError('monAddr', formDir)}">
<label class="control-label"
<div class="form-group">
<label class="col-form-label"
for="monAddr">
<span i18n>Monitor Addresses</span>
</label>
@ -74,14 +71,13 @@
id="monAddr"
name="monAddr"
formControlName="monAddr">
<span class="help-block"
<span class="invalid-feedback"
*ngIf="editPeerForm.showError('monAddr', formDir, 'invalidMonAddr')"
i18n>The monitory address is not valid.</span>
</div>
<div class="form-group"
[ngClass]="{'has-error': editPeerForm.showError('key', formDir)}">
<label class="control-label"
<div class="form-group">
<label class="col-form-label"
for="key">
<span i18n>CephX Key</span>
</label>
@ -92,7 +88,7 @@
id="key"
name="key"
formControlName="key">
<span class="help-block"
<span class="invalid-feedback"
*ngIf="editPeerForm.showError('key', formDir, 'invalidKey')"
i18n>CephX key must be base64 encoded.</span>
</div>

View File

@ -3,8 +3,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastModule } from 'ng2-toastr';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { ToastrModule } from 'ngx-toastr';
import { of } from 'rxjs';
import {
@ -32,7 +32,7 @@ describe('PoolEditPeerModalComponent', () => {
ReactiveFormsModule,
RouterTestingModule,
SharedModule,
ToastModule.forRoot()
ToastrModule.forRoot()
],
providers: [BsModalRef, BsModalService, i18nProviders]
});

View File

@ -2,10 +2,10 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastModule } from 'ng2-toastr';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { ProgressbarModule } from 'ngx-bootstrap/progressbar';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { ToastrModule } from 'ngx-toastr';
import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
import { SharedModule } from '../../../../shared/shared.module';
@ -25,7 +25,7 @@ describe('PoolListComponent', () => {
ProgressbarModule.forRoot(),
HttpClientTestingModule,
RouterTestingModule,
ToastModule.forRoot()
ToastrModule.forRoot()
],
providers: i18nProviders
});

View File

@ -1,74 +1,68 @@
<fieldset #cfgFormGroup [formGroup]="form.get('configuration')">
<fieldset #cfgFormGroup
[formGroup]="form.get('configuration')">
<legend i18n>RBD Configuration</legend>
<div *ngFor="let section of rbdConfigurationService.sections">
<h3 class="page-header">
<span
(click)="toggleSectionVisibility(section.class)"
<div *ngFor="let section of rbdConfigurationService.sections"
class="col-12">
<h3 class="cd-header">
<span (click)="toggleSectionVisibility(section.class)"
class="collapsible">{{ section.heading }} <i [ngClass]="!sectionVisibility[section.class] ? icons.addCircle : icons.minusCircle" aria-hidden="true"></i></span>
</h3>
<div class="{{ section.class }}" [hidden]="!sectionVisibility[section.class]">
<div
class="form-group"
*ngFor="let option of section.options"
[ngClass]="{'has-error': form.showError('configuration.' + option.name, cfgFormGroup)}">
<label
class="control-label col-sm-3"
[for]="option.name">{{ option.displayName }}<cd-helper>{{ option.description }}</cd-helper></label>
<div class="{{ section.class }}"
[hidden]="!sectionVisibility[section.class]">
<div class="form-group row"
*ngFor="let option of section.options">
<label class="col-form-label col-sm-3"
[for]="option.name">{{ option.displayName }}<cd-helper>{{ option.description }}</cd-helper></label>
<div class="col-sm-9 {{ section.heading }}">
<div class="input-group">
<ng-container [ngSwitch]="option.type">
<ng-container *ngSwitchCase="configurationType.milliseconds">
<input
[id]="option.name"
[name]="option.name"
[formControlName]="option.name"
type="text"
class="form-control"
[ngDataReady]="ngDataReady"
cdMilliseconds>
<input [id]="option.name"
[name]="option.name"
[formControlName]="option.name"
type="text"
class="form-control"
[ngDataReady]="ngDataReady"
cdMilliseconds>
</ng-container>
<ng-container *ngSwitchCase="configurationType.bps">
<input
[id]="option.name"
[name]="option.name"
[formControlName]="option.name"
type="text"
class="form-control"
defaultUnit="b"
[ngDataReady]="ngDataReady"
cdDimlessBinaryPerSecond>
<input [id]="option.name"
[name]="option.name"
[formControlName]="option.name"
type="text"
class="form-control"
defaultUnit="b"
[ngDataReady]="ngDataReady"
cdDimlessBinaryPerSecond>
</ng-container>
<ng-container *ngSwitchCase="configurationType.iops">
<input
[id]="option.name"
[name]="option.name"
[formControlName]="option.name"
type="text"
class="form-control"
[ngDataReady]="ngDataReady"
cdIops>
<input [id]="option.name"
[name]="option.name"
[formControlName]="option.name"
type="text"
class="form-control"
[ngDataReady]="ngDataReady"
cdIops>
</ng-container>
</ng-container>
<span class="input-group-btn">
<button
class="btn btn-default"
type="button"
data-toggle="button"
[ngClass]="{'active': isDisabled(option.name)}"
title="Remove the local configuration value. The parent configuration value will be inherited and used instead."
i18n-title
(click)="reset(option.name)">
<span class="input-group-append">
<button class="btn btn-light"
type="button"
data-toggle="button"
[ngClass]="{'active': isDisabled(option.name)}"
title="Remove the local configuration value. The parent configuration value will be inherited and used instead."
i18n-title
(click)="reset(option.name)">
<i [ngClass]="[icons.erase]"
aria-hidden="true"></i>
</button>
</span>
</div>
<span
i18n
class="help-block"
*ngIf="form.showError('configuration.' + option.name, cfgFormGroup, 'min')">The mininum value is 0</span>
<span i18n
class="invalid-feedback"
*ngIf="form.showError('configuration.' + option.name, cfgFormGroup, 'min')">The mininum value is 0</span>
</div>
</div>
</div>

View File

@ -5,6 +5,7 @@ import { RouterTestingModule } from '@angular/router/testing';
import { NgxDatatableModule } from '@swimlane/ngx-datatable';
import { ChartsModule } from 'ng2-charts';
import { AlertModule } from 'ngx-bootstrap/alert';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
import { ErrorPanelComponent } from '../../../shared/components/error-panel/error-panel.component';
@ -26,6 +27,7 @@ describe('RbdConfigurationListComponent', () => {
NgxDatatableModule,
RouterTestingModule,
AlertModule,
BsDropdownModule.forRoot(),
ChartsModule,
PipesModule
],

View File

@ -10,54 +10,54 @@
<tbody>
<tr>
<td i18n
class="bold col-sm-1">Name</td>
<td class="col-sm-3">{{ selectedItem.name }}</td>
class="bold w-25">Name</td>
<td class="w-75">{{ selectedItem.name }}</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Pool</td>
<td class="col-sm-3">{{ selectedItem.pool_name }}</td>
class="bold">Pool</td>
<td>{{ selectedItem.pool_name }}</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Data Pool</td>
<td class="col-sm-3">{{ selectedItem.data_pool | empty }}</td>
class="bold">Data Pool</td>
<td>{{ selectedItem.data_pool | empty }}</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Created</td>
<td class="col-sm-3">{{ selectedItem.timestamp | cdDate }}</td>
class="bold">Created</td>
<td>{{ selectedItem.timestamp | cdDate }}</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Size</td>
<td class="col-sm-3">{{ selectedItem.size | dimlessBinary }}</td>
class="bold">Size</td>
<td>{{ selectedItem.size | dimlessBinary }}</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Objects</td>
<td class="col-sm-3">{{ selectedItem.num_objs | dimless }}</td>
class="bold">Objects</td>
<td>{{ selectedItem.num_objs | dimless }}</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Object size</td>
<td class="col-sm-3">{{ selectedItem.obj_size | dimlessBinary }}</td>
class="bold">Object size</td>
<td>{{ selectedItem.obj_size | dimlessBinary }}</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Features</td>
<td class="col-sm-3">
class="bold">Features</td>
<td>
<span *ngFor="let feature of selectedItem.features_name">
<span class="badge badge-pill badge-primary margin-right-sm">{{ feature }}</span>
<span class="badge badge-pill badge-dark mr-2">{{ feature }}</span>
</span>
</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Provisioned</td>
<td class="col-sm-3">
class="bold">Provisioned</td>
<td>
<span *ngIf="selectedItem.features_name?.indexOf('fast-diff') === -1">
<span class="text-muted"
<span class="form-text text-muted"
[tooltip]="usageNotAvailableTooltipTpl"
placement="right"
i18n>N/A</span>
@ -69,10 +69,10 @@
</tr>
<tr>
<td i18n
class="bold col-sm-1">Total provisioned</td>
<td class="col-sm-3">
class="bold">Total provisioned</td>
<td>
<span *ngIf="selectedItem.features_name?.indexOf('fast-diff') === -1">
<span class="text-muted"
<span class="form-text text-muted"
[tooltip]="usageNotAvailableTooltipTpl"
placement="right"
i18n>N/A</span>
@ -84,18 +84,18 @@
</tr>
<tr>
<td i18n
class="bold col-sm-1">Striping unit</td>
<td class="col-sm-3">{{ selectedItem.stripe_unit | dimlessBinary }}</td>
class="bold">Striping unit</td>
<td>{{ selectedItem.stripe_unit | dimlessBinary }}</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Striping count</td>
<td class="col-sm-3">{{ selectedItem.stripe_count }}</td>
class="bold">Striping count</td>
<td>{{ selectedItem.stripe_count }}</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Parent</td>
<td class="col-sm-3">
class="bold">Parent</td>
<td>
<span *ngIf="selectedItem.parent">{{ selectedItem.parent.pool_name }}
/{{ selectedItem.parent.image_name }}
@{{ selectedItem.parent.snap_name }}</span>
@ -104,13 +104,13 @@
</tr>
<tr>
<td i18n
class="bold col-sm-1">Block name prefix</td>
<td class="col-sm-3">{{ selectedItem.block_name_prefix }}</td>
class="bold">Block name prefix</td>
<td>{{ selectedItem.block_name_prefix }}</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Order</td>
<td class="col-sm-3">{{ selectedItem.order }}</td>
class="bold">Order</td>
<td>{{ selectedItem.order }}</td>
</tr>
</tbody>
</table>

View File

@ -1,21 +1,18 @@
<div class="col-sm-12 col-lg-6">
<form name="rbdForm"
class="form-horizontal"
#formDir="ngForm"
[formGroup]="rbdForm"
novalidate>
<div class="panel panel-default">
<div class="panel-heading">
<h3 i18n="form title|Example: Create Pool@@formTitle"
class="panel-title">{{ action | titlecase }} {{ resource | upperFirst }}</h3>
</div>
<div class="panel-body">
<div class="card">
<div i18n="form title|Example: Create Pool@@formTitle"
class="card-header">{{ action | titlecase }} {{ resource | upperFirst }}</div>
<div class="card-body">
<!-- Parent -->
<div class="form-group"
<div class="form-group row"
*ngIf="rbdForm.getValue('parent')">
<label i18n
class="control-label col-sm-3"
class="col-form-label col-sm-3"
for="name">{{ action | titlecase }} from</label>
<div class="col-sm-9">
<input class="form-control"
@ -28,9 +25,8 @@
</div>
<!-- Name -->
<div class="form-group"
[ngClass]="{'has-error': rbdForm.showError('name', formDir)}">
<label class="control-label col-sm-3"
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="name">
<ng-container i18n>Name</ng-container>
<span class="required"></span>
@ -43,11 +39,11 @@
name="name"
formControlName="name"
autofocus>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="rbdForm.showError('name', formDir, 'required')">
<ng-container i18n>This field is required.</ng-container>
</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="rbdForm.showError('name', formDir, 'pattern')">
<ng-container i18n>'/' and '@' are not allowed.</ng-container>
</span>
@ -55,10 +51,9 @@
</div>
<!-- Pool -->
<div class="form-group"
[ngClass]="{'has-error': rbdForm.showError('pool', formDir)}"
<div class="form-group row"
(change)="onPoolChange($event.target.value)">
<label class="control-label col-sm-3"
<label class="col-form-label col-sm-3"
for="pool">
Pool
<span class="required"
@ -74,7 +69,7 @@
*ngIf="mode === 'editing' || !poolPermission.read">
<select id="pool"
name="pool"
class="form-control"
class="form-control custom-select"
formControlName="pool"
*ngIf="mode !== 'editing' && poolPermission.read">
<option *ngIf="pools === null"
@ -90,31 +85,32 @@
[value]="pool.pool_name">{{ pool.pool_name }}</option>
</select>
<span *ngIf="rbdForm.showError('pool', formDir, 'required')"
class="help-block"
class="invalid-feedback"
i18n>This field is required.</span>
</div>
</div>
<!-- Use a dedicated pool -->
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<div class="checkbox checkbox-primary">
<div class="form-group row">
<div class="offset-sm-3 col-sm-9">
<div class="custom-control custom-checkbox">
<input type="checkbox"
class="custom-control-input"
id="useDataPool"
name="useDataPool"
formControlName="useDataPool"
(change)="onUseDataPoolChange()">
<label i18n
for="useDataPool">Use a dedicated data pool</label>
<label class="custom-control-label"
for="useDataPool"
i18n>Use a dedicated data pool</label>
</div>
</div>
</div>
<!-- Data Pool -->
<div class="form-group"
[ngClass]="{'has-error': rbdForm.showError('dataPool', formDir)}"
<div class="form-group row"
*ngIf="rbdForm.getValue('useDataPool')">
<label class="control-label col-sm-3"
<label class="col-form-label col-sm-3"
for="dataPool">
<ng-container i18n>Data pool</ng-container>
<span class="required"
@ -133,7 +129,7 @@
*ngIf="mode === 'editing' || !poolPermission.read">
<select id="dataPool"
name="dataPool"
class="form-control"
class="form-control custom-select"
formControlName="dataPool"
(change)="onDataPoolChange($event.target.value)"
*ngIf="mode !== 'editing' && poolPermission.read">
@ -149,16 +145,15 @@
<option *ngFor="let dataPool of dataPools"
[value]="dataPool.pool_name">{{ dataPool.pool_name }}</option>
</select>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="rbdForm.showError('dataPool', formDir, 'required')"
i18n>This field is required.</span>
</div>
</div>
<!-- Size -->
<div class="form-group"
[ngClass]="{'has-error': rbdForm.showError('size', formDir)}">
<label class="control-label col-sm-3"
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="size">
<ng-container i18n>Size</ng-container>
<span class="required"></span>
@ -173,30 +168,31 @@
placeholder="e.g., 10GiB"
defaultUnit="GiB"
cdDimlessBinary>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="rbdForm.showError('size', formDir, 'required')"
i18n>This field is required.</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="rbdForm.showError('size', formDir, 'invalidSizeObject')"
i18n>You have to increase the size.</span>
</div>
</div>
<!-- Features -->
<div class="form-group"
[ngClass]="{'has-error': (formDir.submitted || rbdForm.get('features').dirty) && rbdForm.get('features').invalid}"
<div class="form-group row"
formGroupName="features">
<label i18n
class="col-sm-3 control-label"
class="col-sm-3 col-form-label"
for="features">Features</label>
<div class="col-sm-9">
<div class="checkbox checkbox-primary"
<div class="custom-control custom-checkbox"
*ngFor="let feature of featuresList">
<input type="checkbox"
class="custom-control-input"
id="{{ feature.key }}"
name="{{ feature.key }}"
formControlName="{{ feature.key }}">
<label for="{{ feature.key }}">{{ feature.desc }}</label>
<label class="custom-control-label"
for="{{ feature.key }}">{{ feature.desc }}</label>
<cd-helper *ngIf="feature.helperHtml"
html="{{ feature.helperHtml }}">
</cd-helper>
@ -207,30 +203,32 @@
<!-- Advanced -->
<div class="row">
<div class="col-sm-12">
<a class="pull-right margin-right-md"
(click)="advancedEnabled = true"
<a class="float-right margin-right-md"
(click)="advancedEnabled = true; false"
*ngIf="!advancedEnabled"
href=""
i18n>Advanced...</a>
</div>
</div>
<div [hidden]="!advancedEnabled">
<h2 i18n
class="page-header">Advanced</h2>
<legend class="cd-header"
i18n>Advanced</legend>
<div class="section">
<h3 class="page-header" i18n>Striping</h3>
<div class="col-md-12">
<h3 class="cd-header"
i18n>Striping</h3>
<!-- Object Size -->
<div class="form-group"
[ngClass]="{'has-error': rbdForm.showError('obj_size', formDir)}">
<div class="form-group row">
<label i18n
class="control-label col-sm-3"
class="col-form-label col-sm-3"
for="size">Object size</label>
<div class="col-sm-9">
<select id="obj_size"
name="obj_size"
class="form-control"
class="form-control custom-select"
formControlName="obj_size">
<option *ngFor="let objectSize of objectSizes"
[value]="objectSize">{{ objectSize }}</option>
@ -238,44 +236,42 @@
</div>
</div>
<!-- Stripe Unit -->
<div class="form-group"
[ngClass]="{'has-error': rbdForm.showError('stripingUnit', formDir)}">
<label class="control-label col-sm-3"
<!-- stripingUnit -->
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="stripingUnit">
<span i18n>Stripe unit</span>
<span class="required"
*ngIf="rbdForm.getValue('stripingCount')">
</span>
</span>
</label>
<div class="col-sm-9">
<select id="stripingUnit"
name="stripingUnit"
class="form-control"
class="form-control custom-select"
formControlName="stripingUnit">
<option i18n
[ngValue]="null">-- Select stripe unit --</option>
<option *ngFor="let objectSize of objectSizes"
[value]="objectSize">{{ objectSize }}</option>
</select>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="rbdForm.showError('stripingUnit', formDir, 'required')"
i18n>This field is required because stripe count is defined!</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="rbdForm.showError('stripingUnit', formDir, 'invalidStripingUnit')"
i18n>Stripe unit is greater than object size.</span>
</div>
</div>
<!-- Stripe Count -->
<div class="form-group"
[ngClass]="{'has-error': rbdForm.showError('stripingCount', formDir)}">
<label class="control-label col-sm-3"
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="stripingCount">
<span i18n>Stripe count</span>
<span class="required"
*ngIf="rbdForm.getValue('stripingUnit')">
</span>
</span>
</label>
<div class="col-sm-9">
<input id="stripingCount"
@ -283,32 +279,27 @@
formControlName="stripingCount"
class="form-control"
type="number">
<span class="help-block"
<span class="invalid-feedback"
*ngIf="rbdForm.showError('stripingCount', formDir, 'required')"
i18n>This field is required because stripe unit is defined!</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="rbdForm.showError('stripingCount', formDir, 'min')"
i18n>Stripe count must be greater than 0.</span>
</div>
</div>
</div>
<div class="section">
<cd-rbd-configuration-form [form]="rbdForm"
[initializeData]="initializeConfigData"
(changes)="getDirtyConfigurationValues = $event"></cd-rbd-configuration-form>
</div>
<cd-rbd-configuration-form [form]="rbdForm"
[initializeData]="initializeConfigData"
(changes)="getDirtyConfigurationValues = $event"></cd-rbd-configuration-form>
</div>
</div>
<div class="panel-footer">
<div class="card-footer">
<div class="button-group text-right">
<cd-submit-button
[form]="formDir"
(submitAction)="submit()"
i18n="form action button|Example: Create Pool@@formActionButton"
type="button">{{ action | titlecase }} {{ resource | upperFirst }}</cd-submit-button>
<cd-submit-button (submitAction)="submit()"
i18n="form action button|Example: Create Pool@@formActionButton"
[form]="formDir">{{ action | titlecase }} {{ resource | upperFirst }}</cd-submit-button>
<cd-back-button></cd-back-button>
</div>
</div>

View File

@ -5,7 +5,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { TooltipModule } from 'ngx-bootstrap/tooltip';
import { ToastModule } from 'ng2-toastr';
import { ToastrModule } from 'ngx-toastr';
import { By } from '@angular/platform-browser';
import { of } from 'rxjs';
@ -30,7 +30,7 @@ describe('RbdFormComponent', () => {
HttpClientTestingModule,
ReactiveFormsModule,
RouterTestingModule,
ToastModule.forRoot(),
ToastrModule.forRoot(),
SharedModule,
TooltipModule
],
@ -83,7 +83,10 @@ describe('RbdFormComponent', () => {
describe('test image configuration component', () => {
it('is visible', () => {
fixture.detectChanges();
expect(queryNativeElement('cd-rbd-configuration-form').parentElement.hidden).toBe(false);
expect(
fixture.debugElement.query(By.css('cd-rbd-configuration-form')).nativeElement.parentElement
.hidden
).toBe(true);
});
});

View File

@ -2,9 +2,9 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastModule } from 'ng2-toastr';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { TooltipModule } from 'ngx-bootstrap/tooltip';
import { ToastrModule } from 'ngx-toastr';
import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
import { TaskListService } from '../../../shared/services/task-list.service';
@ -34,7 +34,7 @@ describe('RbdImagesComponent', () => {
RouterTestingModule,
SharedModule,
TabsModule.forRoot(),
ToastModule.forRoot(),
ToastrModule.forRoot(),
TooltipModule.forRoot()
],
providers: [TaskListService, i18nProviders]

View File

@ -3,12 +3,12 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastModule } from 'ng2-toastr';
import { AlertModule } from 'ngx-bootstrap/alert';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { ModalModule } from 'ngx-bootstrap/modal';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { TooltipModule } from 'ngx-bootstrap/tooltip';
import { ToastrModule } from 'ngx-toastr';
import { BehaviorSubject, of } from 'rxjs';
import {
@ -47,7 +47,7 @@ describe('RbdListComponent', () => {
TabsModule.forRoot(),
ModalModule.forRoot(),
TooltipModule.forRoot(),
ToastModule.forRoot(),
ToastrModule.forRoot(),
AlertModule.forRoot(),
RouterTestingModule,
HttpClientTestingModule

View File

@ -1,24 +1,22 @@
<div class="modal-header">
<h4 class="modal-title pull-left"
<h4 class="modal-title float-left"
i18n>{ editing, select, true {Rename} other {Create}} RBD Snapshot</h4>
<button type="button"
class="close pull-right"
class="close float-right"
aria-label="Close"
(click)="modalRef.hide()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form name="snapshotForm"
class="form-horizontal"
#formDir="ngForm"
[formGroup]="snapshotForm"
novalidate>
<div class="modal-body">
<!-- Name -->
<div class="form-group"
[ngClass]="{'has-error': snapshotForm.showError('snapshotName', formDir)}">
<label class="control-label col-sm-3"
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="snapshotName">
<ng-container i18n>Name</ng-container>
<span class="required"></span>
@ -31,7 +29,7 @@
name="snapshotName"
formControlName="snapshotName"
autofocus>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="snapshotForm.showError('snapshotName', formDir, 'required')"
i18n>This field is required.</span>
</div>

View File

@ -3,8 +3,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastModule } from 'ng2-toastr';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { ToastrModule } from 'ngx-toastr';
import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
import { ApiModule } from '../../../shared/api/api.module';
@ -22,7 +22,7 @@ describe('RbdSnapshotFormComponent', () => {
ComponentsModule,
HttpClientTestingModule,
ApiModule,
ToastModule.forRoot(),
ToastrModule.forRoot(),
RouterTestingModule
],
declarations: [RbdSnapshotFormComponent],

View File

@ -13,10 +13,10 @@
<ng-template #protectTpl
let-value="value">
<span *ngIf="value"
class="label label-success"
class="badge badge-success">
i18n>PROTECTED</span>
<span *ngIf="!value"
class="label label-info"
class="badge badge-info"
i18n>UNPROTECTED</span>
</ng-template>

View File

@ -4,8 +4,8 @@ import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { I18n } from '@ngx-translate/i18n-polyfill';
import { ToastModule } from 'ng2-toastr';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { ToastrModule } from 'ngx-toastr';
import { Subject, throwError as observableThrowError } from 'rxjs';
import {
@ -48,7 +48,7 @@ describe('RbdSnapshotListComponent', () => {
imports: [
DataTableModule,
ComponentsModule,
ToastModule.forRoot(),
ToastrModule.forRoot(),
ApiModule,
HttpClientTestingModule,
RouterTestingModule,

View File

@ -16,11 +16,11 @@
[tableActions]="tableActions">
</cd-table-actions>
<button class="btn btn-sm btn-default btn-label"
<button class="btn btn-light"
type="button"
(click)="purgeModal()"
*ngIf="permission.delete">
<i [ngClass]="[icons.destroy, icons.width]"
<i [ngClass]="[icons.destroy]"
aria-hidden="true"></i>
<ng-container i18n>Purge Trash</ng-container>
</button>

View File

@ -2,7 +2,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastModule } from 'ng2-toastr';
import { ToastrModule } from 'ngx-toastr';
import { of } from 'rxjs';
import { By } from '@angular/platform-browser';
@ -23,7 +23,7 @@ describe('RbdTrashListComponent', () => {
configureTestBed({
declarations: [RbdTrashListComponent],
imports: [SharedModule, HttpClientTestingModule, RouterTestingModule, ToastModule.forRoot()],
imports: [SharedModule, HttpClientTestingModule, RouterTestingModule, ToastrModule.forRoot()],
providers: [TaskListService, i18nProviders]
});

View File

@ -12,8 +12,7 @@
<p i18n>To move <kbd>{{ poolName }}/{{ imageName }}</kbd> to trash,
click <kbd>Move Image</kbd>. Optionally, you can pick an expiration date.</p>
<div class="form-group"
[ngClass]="{'has-error': moveForm.showError('expiresAt', formDir)}">
<div class="form-group">
<label for="expires"
i18n>Protection expires at</label>
<input type="text"
@ -24,10 +23,10 @@
[bsConfig]="bsConfig"
formControlName="expiresAt"
bsDatepicker>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="moveForm.showError('expiresAt', formDir, 'format')"
i18n>Wrong date format. Please use "YYYY-MM-DD HH:mm:ss".</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="moveForm.showError('expiresAt', formDir, 'expired')"
i18n>Protection has already expired. Please pick a future date or leave it empty.</span>
</div>

View File

@ -4,9 +4,9 @@ import { ReactiveFormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import * as moment from 'moment';
import { ToastModule } from 'ng2-toastr';
import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { ToastrModule } from 'ngx-toastr';
import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
import { NotificationService } from '../../../shared/services/notification.service';
@ -24,7 +24,7 @@ describe('RbdTrashMoveModalComponent', () => {
HttpClientTestingModule,
RouterTestingModule,
SharedModule,
ToastModule.forRoot(),
ToastrModule.forRoot(),
BsDatepickerModule.forRoot()
],
declarations: [RbdTrashMoveModalComponent],

View File

@ -15,7 +15,7 @@
</p>
<div class="form-group">
<label class="center-block"
<label class="mx-auto"
i18n>Pool:</label>
<input class="form-control"
type="text"
@ -23,7 +23,7 @@
i18n-placeholder
formControlName="poolName"
*ngIf="!poolPermission.read">
<select class="form-control"
<select class="form-control custom-select"
formControlName="poolName"
*ngIf="poolPermission.read">
<option value=""

View File

@ -3,8 +3,8 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testin
import { ReactiveFormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastModule } from 'ng2-toastr';
import { BsModalRef } from 'ngx-bootstrap/modal';
import { ToastrModule } from 'ngx-toastr';
import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
import { Permission } from '../../../shared/models/permissions';
@ -22,7 +22,7 @@ describe('RbdTrashPurgeModalComponent', () => {
HttpClientTestingModule,
ReactiveFormsModule,
SharedModule,
ToastModule.forRoot(),
ToastrModule.forRoot(),
RouterTestingModule
],
declarations: [RbdTrashPurgeModalComponent],

View File

@ -16,8 +16,7 @@
<kbd i18n>Restore Image</kbd>.
</p>
<div class="form-group"
[ngClass]="{'has-error': restoreForm.showError('name', formDir)}">
<div class="form-group">
<label for="name"
i18n>New Name</label>
<input type="text"
@ -27,7 +26,7 @@
autocomplete="off"
formControlName="name"
autofocus>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="restoreForm.showError('name', formDir, 'required')"
i18n>This field is required.</span>
</div>

View File

@ -3,8 +3,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastModule } from 'ng2-toastr';
import { BsModalRef } from 'ngx-bootstrap/modal';
import { ToastrModule } from 'ngx-toastr';
import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
import { NotificationService } from '../../../shared/services/notification.service';
@ -20,7 +20,7 @@ describe('RbdTrashRestoreModalComponent', () => {
imports: [
ReactiveFormsModule,
HttpClientTestingModule,
ToastModule.forRoot(),
ToastrModule.forRoot(),
SharedModule,
RouterTestingModule
],

View File

@ -35,7 +35,7 @@
</tab>
<tab i18n-heading
heading="Clients: {{ clientCount }}"
(select)="clientsSelect=true"
(selectTab)="clientsSelect=true"
(deselect)="clientsSelect=false">
<cd-cephfs-clients [id]="id"
*ngIf="clientsSelect">

View File

@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
import { TreeModule } from 'ng2-tree';
import { AlertModule } from 'ngx-bootstrap/alert';
import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
@ -66,7 +67,8 @@ import { SilenceMatcherModalComponent } from './prometheus/silence-matcher-modal
MgrModulesModule,
TypeaheadModule.forRoot(),
TimepickerModule.forRoot(),
BsDatepickerModule.forRoot()
BsDatepickerModule.forRoot(),
NgBootstrapFormValidationModule
],
declarations: [
HostsComponent,

View File

@ -5,102 +5,102 @@
<tbody>
<tr>
<td i18n
class="bold col-sm-1">Name</td>
<td class="col-sm-3">{{ selectedItem.name }}</td>
class="bold w-25">Name</td>
<td class="w-75">{{ selectedItem.name }}</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Description</td>
<td class="col-sm-3">{{ selectedItem.desc }}</td>
class="bold">Description</td>
<td>{{ selectedItem.desc }}</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Long description</td>
<td class="col-sm-3">{{ selectedItem.long_desc }}</td>
class="bold">Long description</td>
<td>{{ selectedItem.long_desc }}</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Current values</td>
<td class="col-sm-3">
class="bold">Current values</td>
<td>
<span *ngFor="let conf of selectedItem.value; last as isLast">
{{ conf.section }}: {{ conf.value }}{{ !isLast ? "," : "" }}<br/>
{{ conf.section }}: {{ conf.value }}{{ !isLast ? "," : "" }}<br />
</span>
</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Default</td>
<td class="col-sm-3">{{ selectedItem.default }}</td>
class="bold">Default</td>
<td>{{ selectedItem.default }}</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Daemon default</td>
<td class="col-sm-3">{{ selectedItem.daemon_default }}</td>
class="bold">Daemon default</td>
<td>{{ selectedItem.daemon_default }}</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Type</td>
<td class="col-sm-3">{{ selectedItem.type }}</td>
class="bold">Type</td>
<td>{{ selectedItem.type }}</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Min</td>
<td class="col-sm-3">{{ selectedItem.min }}</td>
class="bold">Min</td>
<td>{{ selectedItem.min }}</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Max</td>
<td class="col-sm-3">{{ selectedItem.max }}</td>
class="bold">Max</td>
<td>{{ selectedItem.max }}</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Flags</td>
<td class="col-sm-3">
class="bold">Flags</td>
<td>
<span *ngFor="let flag of selectedItem.flags">
<span title="{{ flags[flag] }}">
<span class="badge badge-pill badge-primary margin-right-sm">{{ flag | uppercase }}</span>
<span class="badge badge-pill badge-dark mr-2">{{ flag | uppercase }}</span>
</span>
</span>
</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Services</td>
<td class="col-sm-3">
class="bold">Services</td>
<td>
<span *ngFor="let service of selectedItem.services">
<span class="badge badge-pill badge-primary margin-right-sm">{{ service }}</span>
<span class="badge badge-pill badge-dark mr-2">{{ service }}</span>
</span>
</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Source</td>
<td class="col-sm-3">{{ selectedItem.source }}</td>
class="bold">Source</td>
<td>{{ selectedItem.source }}</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Level</td>
<td class="col-sm-3">{{ selectedItem.level }}</td>
class="bold">Level</td>
<td>{{ selectedItem.level }}</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Can be updated at runtime (editable)</td>
<td class="col-sm-3">{{ selectedItem.can_update_at_runtime | booleanText }}</td>
class="bold">Can be updated at runtime (editable)</td>
<td>{{ selectedItem.can_update_at_runtime | booleanText }}</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Tags</td>
<td class="col-sm-3">{{ selectedItem.tags }}</td>
class="bold">Tags</td>
<td>{{ selectedItem.tags }}</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">Enum values</td>
<td class="col-sm-3">{{ selectedItem.enum_values }}</td>
class="bold">Enum values</td>
<td>{{ selectedItem.enum_values }}</td>
</tr>
<tr>
<td i18n
class="bold col-sm-1">See also</td>
<td class="col-sm-3">{{ selectedItem.see_also }}</td>
class="bold">See also</td>
<td>{{ selectedItem.see_also }}</td>
</tr>
</tbody>
</table>

View File

@ -1,21 +1,18 @@
<div class="col-sm-12 col-lg-6">
<div class="col-sm-12 col-md-12 col-lg-6">
<form name="configForm"
class="form-horizontal"
#formDir="ngForm"
[formGroup]="configForm"
novalidate>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<ng-container i18n>Edit</ng-container> {{ configForm.getValue('name') }}
</h3>
<div class="card">
<div class="card-header">
<ng-container i18>Edit</ng-container> {{ configForm.getValue('name') }}
</div>
<div class="panel-body">
<div class="card-body">
<!-- Name -->
<div class="form-group">
<div class="form-group row">
<label i18n
class="control-label col-sm-3">Name</label>
class="col-form-label col-sm-3">Name</label>
<div class="col-sm-9">
<input class="form-control"
type="text"
@ -26,10 +23,10 @@
</div>
<!-- Description -->
<div class="form-group"
<div class="form-group row"
*ngIf="configForm.getValue('desc')">
<label i18n
class="control-label col-sm-3">Description</label>
class="col-form-label col-sm-3">Description</label>
<div class="col-sm-9">
<textarea class="form-control resize-vertical"
id="desc"
@ -40,10 +37,10 @@
</div>
<!-- Long description -->
<div class="form-group"
<div class="form-group row"
*ngIf="configForm.getValue('long_desc')">
<label i18n
class="control-label col-sm-3">Long description</label>
class="col-form-label col-sm-3">Long description</label>
<div class="col-sm-9">
<textarea class="form-control resize-vertical"
id="long_desc"
@ -54,10 +51,10 @@
</div>
<!-- Default -->
<div class="form-group"
<div class="form-group row"
*ngIf="configForm.getValue('default') !== ''">
<label i18n
class="control-label col-sm-3">Default</label>
class="col-form-label col-sm-3">Default</label>
<div class="col-sm-9">
<input class="form-control"
type="text"
@ -68,10 +65,10 @@
</div>
<!-- Daemon default -->
<div class="form-group"
<div class="form-group row"
*ngIf="configForm.getValue('daemon_default') !== ''">
<label i18n
class="control-label col-sm-3">Daemon default</label>
class="col-form-label col-sm-3">Daemon default</label>
<div class="col-sm-9">
<input class="form-control"
type="text"
@ -82,41 +79,41 @@
</div>
<!-- Services -->
<div class="form-group"
<div class="form-group row"
*ngIf="configForm.getValue('services').length > 0">
<label i18n
class="control-label col-sm-3">Services</label>
class="col-form-label col-sm-3">Services</label>
<div class="col-sm-9">
<span *ngFor="let service of configForm.getValue('services')"
class="form-component-badge">
<span class="badge badge-pill badge-primary">{{ service }}</span>
<span class="badge badge-pill badge-dark">{{ service }}</span>
</span>
</div>
</div>
<!-- Values -->
<div class="col-sm-12"
formGroupName="values">
<div formGroupName="values">
<h2 i18n
class="page-header">Values</h2>
<div class="row"
*ngFor="let section of availSections">
<div class="form-group"
class="cd-header">Values</h2>
<ng-container *ngFor="let section of availSections">
<div class="form-group row"
*ngIf="type === 'bool'">
<div class="col-sm-offset-3 col-sm-9">
<div class="checkbox checkbox-primary">
<input [id]="section"
type="checkbox"
<div class="offset-sm-3 col-sm-9">
<div class="custom-control custom-checkbox">
<input type="checkbox"
class="custom-control-input"
[id]="section"
[formControlName]="section">
<label [for]="section">{{ section }}
<label class="custom-control-label"
[for]="section">{{ section }}
</label>
</div>
</div>
</div>
<div class="form-group"
[ngClass]="{'has-error': configForm.showError(section, formDir)}"
<div class="form-group row"
*ngIf="type !== 'bool'">
<label class="control-label col-sm-3"
<label class="col-form-label col-sm-3"
[for]="section">{{ section }}
</label>
<div class="col-sm-9">
@ -126,30 +123,29 @@
[placeholder]="humanReadableType"
[formControlName]="section"
[step]="getStep(type, this.configForm.getValue(section))">
<span class="help-block"
<span class="invalid-feedback"
*ngIf="configForm.showError(section, formDir, 'pattern')">
{{ patternHelpText }}
</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="configForm.showError(section, formDir, 'invalidUuid')">
{{ patternHelpText }}
</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="configForm.showError(section, formDir, 'max')"
i18n>The entered value is too high! It must not be greater than {{ maxValue }}.</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="configForm.showError(section, formDir, 'min')"
i18n>The entered value is too low! It must not be lower than {{ minValue }}.</span>
</div>
</div>
</div>
</ng-container>
</div>
</div>
<!-- Footer -->
<div class="panel-footer">
<div class="card-footer">
<div class="button-group text-right">
<cd-submit-button [form]="formDir"
type="button"
(submitAction)="submit()">
<span i18n>Save</span>
</cd-submit-button>

View File

@ -4,7 +4,7 @@ import { ReactiveFormsModule } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastModule } from 'ng2-toastr';
import { ToastrModule } from 'ngx-toastr';
import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
import { ConfigFormModel } from '../../../../shared/components/config-option/config-option.model';
@ -20,7 +20,7 @@ describe('ConfigurationFormComponent', () => {
HttpClientTestingModule,
ReactiveFormsModule,
RouterTestingModule,
ToastModule.forRoot(),
ToastrModule.forRoot(),
SharedModule
],
declarations: [ConfigurationFormComponent],

View File

@ -8,32 +8,40 @@
[selection]="selection"
[tableActions]="tableActions">
</cd-table-actions>
<div class="table-filters">
<div class="table-filters form-inline">
<div class="form-group filter"
*ngFor="let filter of filters">
<label>{{ filter.label }}: </label>
<select class="form-control input-sm"
<select class="form-control custom-select"
[(ngModel)]="filter.value"
(ngModelChange)="updateFilter()">
<option *ngFor="let opt of filter.options">{{ opt }}</option>
</select>
</div>
<a [ngClass]="[icons.stack]"
title="Reset filters"
(click)="resetFilter()">
<i [ngClass]="[icons.filter, icons.stack2x]"></i>
<i [ngClass]="[icons.destroy, icons.stack1x]" style="margin-left: 8px; margin-top: 5px;"></i>
</a>
<div class="widget-toolbar tc_refreshBtn">
<button type="button"
title="Reset filters"
class="btn btn-light"
(click)="resetFilter()">
<span [ngClass]="[icons.stack]">
<i [ngClass]="[icons.filter, icons.stack2x]"></i>
<i [ngClass]="[icons.destroy, icons.stack1x]"
style="margin-left: 8px; margin-top: 5px;"></i>
</span>
</button>
</div>
</div>
<cd-configuration-details cdTableDetail
[selection]="selection">
</cd-configuration-details>
</cd-table>
<ng-template #confValTpl let-value="value">
<ng-template #confValTpl
let-value="value">
<span *ngIf="value">
<span *ngFor="let conf of value; last as isLast">
{{ conf.section }}: {{ conf.value }}{{ !isLast ? "," : "" }}<br/>
{{ conf.section }}: {{ conf.value }}{{ !isLast ? "," : "" }}<br />
</span>
</span>
</ng-template>

View File

@ -2,6 +2,10 @@
padding-right: 8px;
}
.fa-stack {
font-size: 0.79rem;
}
::ng-deep datatable-body-cell.wrap {
word-break: break-all;
}

View File

@ -1,29 +1,31 @@
<div class="row">
<div class="col-sm-12 col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<span i18n>CRUSH map viewer</span>
</h3>
</div>
<div class="panel-body">
<div class="col-sm-6 col-lg-6">
<tree [tree]="tree"
[settings]="{rootIsVisible: false}"
(nodeSelected)="onNodeSelected($event)">
<ng-template let-node>
<span class="label"
[ngClass]="{'label-success': ['in', 'up'].includes(node.status), 'label-danger': ['down', 'out'].includes(node.status)}">{{ node.status }}</span>
<span>&nbsp;</span>
<span class="node-name" [innerHTML]="node.value"></span>
</ng-template>
</tree>
</div>
<div class="col-sm-6 col-lg-6 metadata" *ngIf="metadata">
<legend>{{ metadataTitle }}</legend>
<cd-table-key-value [data]="metadata"></cd-table-key-value>
<div class="card">
<div class="card-header"
i18n>CRUSH map viewer</div>
<div class="card-body">
<div class="row">
<div class="col-sm-6 col-lg-6">
<tree [tree]="tree"
[settings]="{rootIsVisible: false}"
(nodeSelected)="onNodeSelected($event)">
<ng-template let-node>
<span class="badge"
[ngClass]="{'badge-success': ['in', 'up'].includes(node.status), 'badge-danger': ['down', 'out'].includes(node.status)}">
{{ node.status }}
</span>
<span>&nbsp;</span>
<span class="node-name"
[innerHTML]="node.value"></span>
</ng-template>
</tree>
</div>
<div class="col-sm-6 col-lg-6 metadata"
*ngIf="metadata">
<legend>{{ metadataTitle }}</legend>
<cd-table-key-value [data]="metadata"></cd-table-key-value>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -33,7 +33,7 @@ describe('CrushmapComponent', () => {
it('should display right title', () => {
fixture.detectChanges();
const span = debugElement.nativeElement.querySelector('span');
const span = debugElement.nativeElement.querySelector('.card-header');
expect(span.textContent).toBe('CRUSH map viewer');
});

View File

@ -1,72 +1,80 @@
<div *ngIf="contentData">
<ng-container *ngTemplateOutlet="logFiltersTpl"></ng-container>
<tabset>
<tab i18n-heading
heading="Cluster Logs">
<div class="well">
<div *ngIf="clog">
<p *ngFor="let line of clog">
<span class="timestamp">{{ line.stamp | cdDate }}</span>
<span class="priority {{ line.priority | logPriority }}">{{ line.priority }}</span>
<span class="message">{{ line.message }}</span>
</p>
</div>
<div *ngIf="contentData.clog.length === 0">
<p i18n>No entries found</p>
</div>
</div>
</tab>
<ng-container *ngTemplateOutlet="logFiltersTpl"></ng-container>
<tab i18n-heading
heading="Audit Logs">
<div class="well">
<div *ngIf="audit_log">
<p *ngFor="let line of audit_log">
<span class="timestamp">{{ line.stamp | cdDate }}</span>
<span class="priority {{ line.priority | logPriority }}">{{ line.priority }}</span>
<span class="message">{{ line.message }}</span>
</p>
</div>
<div *ngIf="contentData.audit_log.length === 0">
<p i18n>No entries found</p>
</div>
</div>
</tab>
</tabset>
<tabset>
<tab i18n-heading
heading="Cluster Logs">
<div class="card bg-light mb-3"
*ngIf="clog">
<div class="card-body">
<p *ngFor="let line of clog">
<span class="timestamp">{{ line.stamp | cdDate }}</span>
<span class="priority {{ line.priority | logPriority }}">{{ line.priority }}</span>
<span class="message">{{ line.message }}</span>
</p>
<span *ngIf="contentData.clog.length === 0"
i18n>No entries found</span>
</div>
</div>
</tab>
<tab i18n-heading
heading="Audit Logs">
<div class="card bg-light mb-3"
*ngIf="audit_log">
<div class="card-body">
<p *ngFor="let line of audit_log">
<span class="timestamp">{{ line.stamp | cdDate }}</span>
<span class="priority {{ line.priority | logPriority }}">{{ line.priority }}</span>
<span class="message">{{ line.message }}</span>
</p>
<span *ngIf="contentData.audit_log.length === 0"
i18n>No entries found</span>
</div>
</div>
</tab>
</tabset>
</div>
<ng-template #logFiltersTpl>
<div class="row log-filters">
<div class="col-xs-4 col-md-2 cd-col-1 filter-box">
<div class="form-inline">
<div class="form-group">
<label i18n>Priority:</label>
<select class="form-control"
<select class="form-control custom-select"
[(ngModel)]="priority"
(ngModelChange)="filterLogs()">
<option class="form-control"
*ngFor="let prio of prioritys"
<option *ngFor="let prio of prioritys"
[value]="prio.value">{{ prio.name }}</option>
</select>
</div>
<div class="col-xs-4 col-md-3 cd-col-3 filter-box">
<div class="form-group">
<label i18n>Keyword:</label>
<div class="input-group">
<span class="input-group-addon">
<i [ngClass]="[icons.search]"></i>
</span>
<div class="input-group-prepend">
<span class="input-group-text">
<i [ngClass]="[icons.search]"></i>
</span>
</div>
<input class="form-control"
type="text"
[(ngModel)]="search"
(keyup)="filterLogs()">
<span class="input-group-btn">
<div class="input-group-append">
<button type="button"
class="btn btn-default clear-input tc_clearInputBtn"
class="btn btn-light"
(click)="clearSearchKey()">
<i class="icon-prepend {{ icons.destroy }}"></i>
</button>
</span>
</div>
</div>
</div>
<div class="col-xs-4 col-md-3 cd-col-2 filter-box">
<div class="form-group">
<label i18n>Date:</label>
<div class="input-group">
<input type="text"
@ -77,31 +85,33 @@
bsDatepicker
[(ngModel)]="selectedDate"
(ngModelChange)="filterLogs()">
<span class="input-group-btn">
<span class="input-group-append">
<button type="button"
class="btn btn-default clear-input tc_clearInputBtn"
class="btn btn-light"
(click)="clearDate()">
<i class="icon-prepend {{ icons.destroy }}"></i>
</button>
</span>
</div>
</div>
<div class="clearfix visible-xs-block"></div>
<div class="col-xs-8 col-md-4 cd-col-4 filter-box time-box">
<div class="form-group">
<label i18n>Time range:</label>
<timepicker [showMeridian]="false"
[showSpinners]="showSpinners"
[minuteStep]="1"
[(ngModel)]="startTime"
(ngModelChange)="filterLogs()">
</timepicker>
<span>&nbsp;&mdash;&nbsp;</span>
<timepicker [showMeridian]="false"
[showSpinners]="showSpinners"
[minuteStep]="1"
[(ngModel)]="endTime"
(ngModelChange)="filterLogs()">
</timepicker>
<div class="d-inline-flex">
<timepicker [showMeridian]="false"
[showSpinners]="false"
[minuteStep]="1"
[(ngModel)]="startTime"
(ngModelChange)="filterLogs()">
</timepicker>
<span class="middle">&nbsp;&mdash;&nbsp;</span>
<timepicker [showMeridian]="false"
[showSpinners]="false"
[minuteStep]="1"
[(ngModel)]="endTime"
(ngModelChange)="filterLogs()">
</timepicker>
</div>
</div>
</div>
</ng-template>

View File

@ -1,11 +1,10 @@
@import '../../../../defaults';
@import 'styles';
p {
font-family: monospace;
color: black;
}
.well {
.card {
div p {
display: flex;
@ -41,103 +40,22 @@ p {
}
::ng-deep timepicker table tbody tr td {
.bs-timepicker-field {
width: 3.5rem;
input.bs-timepicker-field {
width: 3.5rem !important;
font-size: 1rem;
padding: 4px 6px;
}
.btn {
font-size: 1rem;
}
}
.log-filters {
margin-bottom: 5px;
padding: 0 30px;
* {
box-sizing: border-box;
}
.filter-box {
margin: 0;
padding: 0 15px 5px 0;
display: -webkit-flex;
display: flex;
justify-content: flex-start;
align-items: center;
label {
padding-top: 5px;
padding-right: 5px;
}
}
@media (max-width: 991px) {
.time-box {
margin-top: 20px;
}
}
@media (min-width: 1200px) {
.cd-col-4 {
width: 28vw;
}
.cd-col-3 {
width: 20vw;
}
.cd-col-2 {
width: 16vw;
}
.cd-col-1 {
width: 14vw;
}
}
@media (min-width: 1400px) {
.cd-col-4 {
width: 24vw;
}
.cd-col-3 {
width: 18vw;
}
.cd-col-2 {
width: 14vw;
}
.cd-col-1 {
width: 12vw;
}
}
@media (min-width: 1600px) {
.cd-col-4 {
width: 22vw;
}
.cd-col-3 {
width: 16vw;
}
.cd-col-2 {
width: 12vw;
}
.cd-col-1 {
width: 10vw;
}
}
@media (min-width: 1800px) {
.cd-col-3 {
width: 14vw;
}
.cd-col-2 {
width: 11vw;
}
.cd-col-1 {
width: 9vw;
}
}
label {
@extend .mr-2;
}
.form-group {
@extend .mr-3;
@extend .mb-3;
}
.middle {
padding-top: 7px;
}

View File

@ -6,21 +6,18 @@
<div class="col-sm-12 col-lg-6"
*ngIf="!loading && !error">
<form name="mgrModuleForm"
class="form-horizontal"
#frm="ngForm"
[formGroup]="mgrModuleForm"
novalidate>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title" i18n>Edit Manager module</h3>
</div>
<div class="panel-body">
<div class="form-group"
[ngClass]="{'has-error': mgrModuleForm.showError(moduleOption.value.name, frm)}"
<div class="card">
<div class="card-header"
i18n>Edit Manager module</div>
<div class="card-body">
<div class="form-group row"
*ngFor="let moduleOption of moduleOptions | keyvalue">
<!-- Field label -->
<label class="control-label col-sm-3"
<label class="col-form-label col-sm-3"
for="{{ moduleOption.value.name }}">
{{ moduleOption.value.name }}
<cd-helper *ngIf="moduleOption.value.long_desc || moduleOption.value.desc">
@ -32,11 +29,13 @@
<!-- bool -->
<div class="col-sm-7"
*ngIf="moduleOption.value.type === 'bool'">
<div class="checkbox checkbox-primary">
<div class="custom-control custom-checkbox">
<input id="{{ moduleOption.value.name }}"
type="checkbox"
class="custom-control-input"
formControlName="{{ moduleOption.value.name }}">
<label for="{{ moduleOption.value.name }}"></label>
<label class="custom-control-label"
for="{{ moduleOption.value.name }}"></label>
</div>
</div>
@ -49,7 +48,7 @@
formControlName="{{ moduleOption.value.name }}"
*ngIf="moduleOption.value.enum_allowed.length === 0">
<select id="{{ moduleOption.value.name }}"
class="form-control"
class="form-control custom-select"
formControlName="{{ moduleOption.value.name }}"
*ngIf="moduleOption.value.enum_allowed.length > 0">
<option *ngFor="let value of moduleOption.value.enum_allowed"
@ -57,10 +56,10 @@
{{ value }}
</option>
</select>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="mgrModuleForm.showError(moduleOption.value.name, frm, 'invalidUuid')"
i18n>The entered value is not a valid UUID, e.g.: 67dcac9f-2c03-4d6c-b7bd-1210b3a259a8</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="mgrModuleForm.showError(moduleOption.value.name, frm, 'pattern')"
i18n>The entered value needs to be a valid IP address.</span>
</div>
@ -74,16 +73,16 @@
formControlName="{{ moduleOption.value.name }}"
min="{{ moduleOption.value.min }}"
max="{{ moduleOption.value.max }}">
<span class="help-block"
<span class="invalid-feedback"
*ngIf="mgrModuleForm.showError(moduleOption.value.name, frm, 'required')"
i18n>This field is required.</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="mgrModuleForm.showError(moduleOption.value.name, frm, 'max')"
i18n>The entered value is too high! It must be lower or equal to {{ moduleOption.value.max }}.</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="mgrModuleForm.showError(moduleOption.value.name, frm, 'min')"
i18n>The entered value is too low! It must be greater or equal to {{ moduleOption.value.min }}.</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="mgrModuleForm.showError(moduleOption.value.name, frm, 'pattern')"
i18n>The entered value needs to be a number.</span>
</div>
@ -95,25 +94,24 @@
class="form-control"
type="number"
formControlName="{{ moduleOption.value.name }}">
<span class="help-block"
<span class="invalid-feedback"
*ngIf="mgrModuleForm.showError(moduleOption.value.name, frm, 'required')"
i18n>This field is required.</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="mgrModuleForm.showError(moduleOption.value.name, frm, 'pattern')"
i18n>The entered value needs to be a number or decimal.</span>
</div>
</div>
</div>
<div class="panel-footer">
<div class="card-footer">
<div class="button-group text-right">
<cd-submit-button type="button"
(submitAction)="onSubmit()"
<cd-submit-button (submitAction)="onSubmit()"
[form]="mgrModuleForm">
<ng-container i18n>Update</ng-container>
</cd-submit-button>
<button type="button"
class="btn btn-sm btn-default"
class="btn btn-light"
routerLink="/mgr-modules"
i18n>Back</button>
</div>

View File

@ -3,7 +3,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastModule } from 'ng2-toastr';
import { ToastrModule } from 'ngx-toastr';
import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
import { SharedModule } from '../../../../shared/shared.module';
@ -20,7 +20,7 @@ describe('MgrModuleFormComponent', () => {
ReactiveFormsModule,
RouterTestingModule,
SharedModule,
ToastModule.forRoot()
ToastrModule.forRoot()
],
providers: i18nProviders
});

View File

@ -3,8 +3,8 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testin
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastModule } from 'ng2-toastr';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { ToastrModule } from 'ngx-toastr';
import { of as observableOf, throwError as observableThrowError } from 'rxjs';
import {
@ -33,7 +33,7 @@ describe('MgrModuleListComponent', () => {
SharedModule,
HttpClientTestingModule,
TabsModule.forRoot(),
ToastModule.forRoot()
ToastrModule.forRoot()
],
providers: [MgrModuleService, NotificationService, i18nProviders]
});

View File

@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { AppRoutingModule } from '../../../app-routing.module';
@ -16,7 +17,8 @@ import { MgrModuleListComponent } from './mgr-module-list/mgr-module-list.compon
CommonModule,
ReactiveFormsModule,
SharedModule,
TabsModule.forRoot()
TabsModule.forRoot(),
NgBootstrapFormValidationModule
],
declarations: [MgrModuleListComponent, MgrModuleFormComponent, MgrModuleDetailsComponent]
})

View File

@ -1,7 +1,7 @@
<div class="row">
<div class="col-md-4">
<div class="col-lg-4">
<fieldset>
<legend i18n>Status</legend>
<legend class="cd-header" i18n>Status</legend>
<table class="table table-striped"
*ngIf="mon_status">
<tr>
@ -43,15 +43,15 @@
</fieldset>
</div>
<div class="col-md-8">
<div class="col-lg-8">
<legend i18n
class="in-quorum">In Quorum</legend>
class="in-quorum cd-header">In Quorum</legend>
<cd-table [data]="inQuorum.data"
[columns]="inQuorum.columns">
</cd-table>
<legend i18n
class="in-quorum">Not In Quorum</legend>
class="in-quorum cd-header">Not In Quorum</legend>
<cd-table [data]="notInQuorum.data"
(fetchData)="refresh()"
[columns]="notInQuorum.columns">

View File

@ -8,19 +8,21 @@
[formGroup]="osdFlagsForm"
novalidate>
<div class="modal-body osd-modal">
<div class="checkbox checkbox-primary"
<div class="custom-control custom-checkbox"
*ngFor="let flag of flags; let last = last">
<input type="checkbox"
<input class="custom-control-input"
type="checkbox"
[checked]="flag.value"
(change)="flag.value = !flag.value"
[name]="flag.code"
[id]="flag.code"
[disabled]="flag.disabled">
<label [for]="flag.code"
<label class="custom-control-label"
[for]="flag.code"
ng-class="['tc_' + key]">
<strong>{{ flag.name }}</strong>
<br>
<span class="text-muted">{{ flag.description }}</span>
<span class="form-text text-muted">{{ flag.description }}</span>
</label>
<hr class="oa-hr-small"
*ngIf="!last">

View File

@ -4,8 +4,8 @@ import { ReactiveFormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import * as _ from 'lodash';
import { ToastModule } from 'ng2-toastr';
import { BsModalRef, ModalModule } from 'ngx-bootstrap/modal';
import { ToastrModule } from 'ngx-toastr';
import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
import { NotificationType } from '../../../../shared/enum/notification-type.enum';
@ -32,7 +32,7 @@ describe('OsdFlagsModalComponent', () => {
SharedModule,
HttpClientTestingModule,
RouterTestingModule,
ToastModule.forRoot()
ToastrModule.forRoot()
],
declarations: [OsdFlagsModalComponent],
providers: [BsModalRef, i18nProviders]

View File

@ -17,7 +17,7 @@
<cd-table-actions [permission]="{read: true}"
[selection]="selection"
dropDownOnly="Cluster-wide configuration"
btnColor="default"
btnColor="light"
class="btn-group"
id="cluster-wide-actions"
[tableActions]="clusterWideActions">
@ -32,8 +32,8 @@
<ng-template #statusColor
let-value="value">
<span *ngFor="let state of value; last as last">
<span class="label"
[ngClass]="{'label-success': ['in', 'up'].includes(state), 'label-danger': ['down', 'out'].includes(state)}">{{ state }}</span>
<span class="badge"
[ngClass]="{'badge-success': ['in', 'up'].includes(state), 'badge-danger': ['down', 'out'].includes(state)}">{{ state }}</span>
<span *ngIf="!last">&nbsp;</span>
</span>
</ng-template>
@ -58,7 +58,7 @@
<ng-template #markOsdConfirmationTpl
let-markActionDescription="markActionDescription">
<ng-container i18n><strong>OSD {{ selection.first().id }}</strong> will be marked
<strong>{{ markActionDescription }}</strong> if you proceed.</ng-container>
<strong>{{ markActionDescription }}</strong> if you proceed.</ng-container>
</ng-template>
<ng-template #criticalConfirmationTpl
@ -69,5 +69,5 @@
<cd-warning-panel i18n>The OSD is not safe to destroy!</cd-warning-panel>
</div>
<ng-container i18n><strong>OSD {{ selection.first().id }}</strong> will be
<strong>{{ actionDescription }}</strong> if you proceed.</ng-container>
<strong>{{ actionDescription }}</strong> if you proceed.</ng-container>
</ng-template>

View File

@ -263,7 +263,7 @@ describe('OsdListComponent', () => {
const tableActionElement = fixture.debugElement.query(By.directive(TableActionsComponent));
const toClassName = TestBed.get(TableActionsComponent).toClassName;
const getActionClasses = (action: CdTableAction) =>
tableActionElement.query(By.css('.' + toClassName(action.name))).classes;
tableActionElement.query(By.css(`.${toClassName(action.name)} .dropdown-item`)).classes;
component.tableActions.forEach((action) => {
expect(getActionClasses(action).disabled).toBe(true);

View File

@ -3,8 +3,7 @@
class="modal-title">{{ action | titlecase }} {{ resource | upperFirst }}</ng-container>
<ng-container class="modal-content">
<form class="form-horizontal"
#formDir="ngForm"
<form #formDir="ngForm"
[formGroup]="osdPgScrubForm"
novalidate>
<div class="modal-body osd-modal">
@ -37,9 +36,8 @@
<div class="button-group text-right">
<cd-submit-button *ngIf="permissions.configOpt.update"
(submitAction)="submitAction()"
[form]="osdPgScrubForm"
i18n="form action button|Example: Create Pool@@formActionButton"
type="button">{{ action | titlecase }} {{ resource | upperFirst }}</cd-submit-button>
[form]="osdPgScrubForm">{{ action | titlecase }} {{ resource | upperFirst }}</cd-submit-button>
<cd-back-button [back]="bsModalRef.hide">
</cd-back-button>
</div>

View File

@ -4,8 +4,8 @@ import { ReactiveFormsModule } from '@angular/forms';
import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastModule } from 'ng2-toastr';
import { BsModalRef, ModalModule } from 'ngx-bootstrap/modal';
import { ToastrModule } from 'ngx-toastr';
import { of as observableOf } from 'rxjs';
import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
@ -27,7 +27,7 @@ describe('OsdPgScrubModalComponent', () => {
ReactiveFormsModule,
RouterTestingModule,
SharedModule,
ToastModule.forRoot()
ToastrModule.forRoot()
],
declarations: [OsdPgScrubModalComponent],
providers: [BsModalRef, i18nProviders]

View File

@ -3,21 +3,19 @@
i18n>OSD Recovery Priority</ng-container>
<ng-container class="modal-content">
<form class="form-horizontal"
#formDir="ngForm"
<form #formDir="ngForm"
[formGroup]="osdRecvSpeedForm"
novalidate>
<div class="modal-body">
<!-- Priority -->
<div class="form-group"
[ngClass]="{'has-error': osdRecvSpeedForm.showError('priority', formDir)}">
<label class="control-label col-sm-6"
<div class="form-group row">
<label class="col-form-label col-sm-6"
for="priority">
<ng-container i18n>Priority</ng-container>
<span class="required"></span>
</label>
<div class="col-sm-6">
<select class="form-control"
<select class="form-control custom-select"
formControlName="priority"
id="priority"
(change)="onPriorityChange($event.target.value)">
@ -26,34 +24,37 @@
{{ priority.text }}
</option>
</select>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="osdRecvSpeedForm.showError('priority', formDir, 'required')"
i18n>This field is required.</span>
</div>
</div>
<!-- Customize priority -->
<div class="form-group">
<div class="col-sm-offset-6 col-sm-6">
<div class="checkbox checkbox-primary">
<div class="form-group row">
<div class="offset-sm-6 col-sm-6">
<div class="custom-control custom-checkbox">
<input formControlName="customizePriority"
class="custom-control-input"
id="customizePriority"
name="customizePriority"
type="checkbox"
(change)="onCustomizePriorityChange()">
<label i18n
for="customizePriority">Customize priority values</label>
<label class="custom-control-label"
for="customizePriority"
i18n>Customize priority values</label>
</div>
</div>
</div>
<!-- Priority values -->
<div class="form-group" *ngFor="let attr of priorityAttrs | keyvalue"
[ngClass]="{'has-error': osdRecvSpeedForm.getValue('customizePriority') &&
osdRecvSpeedForm.showError(attr.key, formDir)}">
<label class="control-label col-sm-6"
<div class="form-group row"
*ngFor="let attr of priorityAttrs | keyvalue">
<label class="col-form-label col-sm-6"
[for]="attr.key">{{ attr.value.text }}
<cd-helper *ngIf="attr.value.desc">{{ attr.value.desc }}</cd-helper>
<span class="required" *ngIf="osdRecvSpeedForm.getValue('customizePriority')"></span>
<span class="required"
*ngIf="osdRecvSpeedForm.getValue('customizePriority')"></span>
</label>
<div class="col-sm-6">
<input class="form-control"
@ -61,19 +62,19 @@
[id]="attr.key"
[formControlName]="attr.key"
[readonly]="!osdRecvSpeedForm.getValue('customizePriority')">
<span class="help-block"
<span class="invalid-feedback"
*ngIf="osdRecvSpeedForm.getValue('customizePriority') &&
osdRecvSpeedForm.showError(attr.key, formDir, 'required')"
i18n>This field is required!</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="osdRecvSpeedForm.getValue('customizePriority') &&
osdRecvSpeedForm.showError(attr.key, formDir, 'pattern')"
i18n>{{ attr.value.patternHelpText }}</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="osdRecvSpeedForm.getValue('customizePriority') &&
osdRecvSpeedForm.showError(attr.key, formDir, 'max')"
i18n>The entered value is too high! It must not be greater than {{ attr.value.maxValue }}.</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="osdRecvSpeedForm.getValue('customizePriority') &&
osdRecvSpeedForm.showError(attr.key, formDir, 'min')"
i18n>The entered value is too low! It must not be lower than {{ attr.value.minValue }}.</span>

View File

@ -4,8 +4,8 @@ import { ReactiveFormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import * as _ from 'lodash';
import { ToastModule } from 'ng2-toastr';
import { BsModalRef, ModalModule } from 'ngx-bootstrap/modal';
import { ToastrModule } from 'ngx-toastr';
import { of as observableOf } from 'rxjs';
import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
@ -25,7 +25,7 @@ describe('OsdRecvSpeedModalComponent', () => {
ReactiveFormsModule,
RouterTestingModule,
SharedModule,
ToastModule.forRoot()
ToastrModule.forRoot()
],
declarations: [OsdRecvSpeedModalComponent],
providers: [BsModalRef, i18nProviders]

View File

@ -3,16 +3,15 @@
i18n>Reweight OSD</ng-container>
<ng-container class="modal-content">
<form class="form-horizontal"
[formGroup]="reweightForm">
<div class="modal-body" [ngClass]="{'has-error': weight.errors}">
<form [formGroup]="reweightForm">
<div class="modal-body">
<div class="row">
<label for="weight" class="col-sm-2 control-label">Weight</label>
<label for="weight" class="col-sm-2 col-form-label">Weight</label>
<div class="col-sm-10">
<input id="weight" class="form-control" type="number"
step="0.1" formControlName="weight" min="0" max="1"
[value]="currentWeight">
<span class="help-block"
<span class="invalid-feedback"
*ngIf="weight.errors">
<span *ngIf="weight.errors?.required"
i18n>This field is required.</span>

View File

@ -4,7 +4,6 @@
<ng-container class="modal-content">
<form name="scrubForm"
class="form-horizontal"
#formDir="ngForm"
[formGroup]="scrubForm"
novalidate>

View File

@ -3,8 +3,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastModule } from 'ng2-toastr';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { ToastrModule } from 'ngx-toastr';
import {
configureTestBed,
@ -25,7 +25,7 @@ describe('PrometheusListComponent', () => {
HttpClientTestingModule,
TabsModule.forRoot(),
RouterTestingModule,
ToastModule.forRoot(),
ToastrModule.forRoot(),
SharedModule
],
declarations: [AlertListComponent, PrometheusTabsComponent],

View File

@ -28,9 +28,9 @@ export class AlertListComponent implements OnInit {
selection = new CdTableSelection();
icons = Icons;
customCss = {
'label label-danger': 'active',
'label label-warning': 'unprocessed',
'label label-info': 'suppressed'
'badge badge-danger': 'active',
'badge badge-warning': 'unprocessed',
'badge badge-info': 'suppressed'
};
constructor(

View File

@ -2,12 +2,12 @@
<tab heading="Alerts"
i18n-heading
[active]="url === '/alerts'"
(select)="navigateTo('/alerts')">
(selectTab)="navigateTo('/alerts')">
</tab>
<tab heading="Silences"
i18n-heading
[active]="url === '/silence'"
(select)="navigateTo('/silence')">
(selectTab)="navigateTo('/silence')">
</tab>
</tabset>

View File

@ -15,7 +15,7 @@ describe('PrometheusTabsComponent', () => {
let router: Router;
const selectTab = (index) => {
fixture.debugElement.queryAll(By.css('tab'))[index].triggerEventHandler('select', null);
fixture.debugElement.queryAll(By.css('tab'))[index].triggerEventHandler('selectTab', null);
};
configureTestBed({

View File

@ -1,10 +1,15 @@
<ng-template #matcherTpl let-matcher="matcher" let-index="index">
<div class="input-group">
<ng-template #matcherTpl
let-matcher="matcher"
let-index="index">
<div class="input-group my-2">
<ng-container *ngFor="let config of matcherConfig">
<span class="input-group-addon"
[tooltip]=config.tooltip>
<i class="icon-prepend" [ngClass]="[config.icon]"></i>
</span>
<div class="input-group-prepend">
<span class="input-group-text"
[tooltip]=config.tooltip>
<i [ngClass]="[config.icon]"></i>
</span>
</div>
<ng-container *ngIf="config.attribute !== 'isRegex'">
<input type="text"
id="matcher-{{config.attribute}}-{{index}}"
@ -13,20 +18,24 @@
disabled
readonly>
</ng-container>
<ng-container *ngIf="config.attribute === 'isRegex'">
<span class="input-group-addon">
<input type="checkbox"
id="matcher-{{config.attribute}}-{{index}}"
[checked]="matcher[config.attribute]"
disabled
readonly>
</span>
<div class="input-group-append">
<div class="input-group-text">
<input type="checkbox"
id="matcher-{{config.attribute}}-{{index}}"
[checked]="matcher[config.attribute]"
disabled
readonly>
</div>
</div>
</ng-container>
</ng-container>
<!-- Matcher actions -->
<span class="input-group-btn">
<span class="input-group-append">
<button type="button"
class="btn btn-default"
class="btn btn-light"
id="matcher-edit-{{index}}"
i18n-tooltip
tooltip="Edit"
@ -34,7 +43,7 @@
<i [ngClass]="[icons.edit]"></i>
</button>
<button type="button"
class="btn btn-default"
class="btn btn-light"
id="matcher-delete-{{index}}"
i18n-tooltip
tooltip="Delete"
@ -49,25 +58,22 @@
<div class="col-sm-12 col-lg-6">
<form #formDir="ngForm"
[formGroup]="form"
class="form-horizontal"
class="form"
name="form"
novalidate>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<span i18n="@@formTitle">
{{ action | titlecase }} {{ resource | upperFirst }}
</span>
<cd-helper *ngIf="edit"
i18n>Editing a silence will expire the old silence and recreate it as a new silence</cd-helper>
</h3>
<div class="card">
<div class="card-header">
<span i18n="@@formTitle">
{{ action | titlecase }} {{ resource | upperFirst }}
</span>
<cd-helper *ngIf="edit"
i18n>Editing a silence will expire the old silence and recreate it as a new silence</cd-helper>
</div>
<!-- Creator -->
<div class="panel-body">
<div [ngClass]="{'has-error': form.showError('createdBy', formDir)}"
class="form-group">
<label class="control-label col-sm-3"
<div class="card-body">
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="created-by">
<ng-container i18n>Creator</ng-container>
<span class="required"></span>
@ -79,15 +85,14 @@
name="created-by"
type="text">
<span *ngIf="form.showError('createdBy', formDir, 'required')"
class="help-block"
class="invalid-feedback"
i18n>This field is required!</span>
</div>
</div>
<!-- Comment -->
<div [ngClass]="{'has-error': form.showError('comment', formDir)}"
class="form-group">
<label class="control-label col-sm-3"
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="comment">
<ng-container i18n>Comment</ng-container>
<span class="required"></span>
@ -100,15 +105,14 @@
type="text">
</textarea>
<span *ngIf="form.showError('comment', formDir, 'required')"
class="help-block"
class="invalid-feedback"
i18n>This field is required!</span>
</div>
</div>
<!-- Start time -->
<div [ngClass]="{'has-error': form.showError('startsAt', formDir)}"
class="form-group">
<label class="control-label col-sm-3"
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="starts-at">
<ng-container i18n>Start time</ng-container>
<cd-helper i18n>If the start time lies in the past the creation time will be used</cd-helper>
@ -122,15 +126,14 @@
id="starts-at"
name="starts-at">
<span *ngIf="form.showError('startsAt', formDir, 'required')"
class="help-block"
class="invalid-feedback"
i18n>This field is required!</span>
</div>
</div>
<!-- Duration -->
<div [ngClass]="{'has-error': form.showError('duration', formDir)}"
class="form-group">
<label class="control-label col-sm-3"
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="duration">
<ng-container i18n>Duration</ng-container>
<span class="required"></span>
@ -142,15 +145,14 @@
name="duration"
type="text">
<span *ngIf="form.showError('duration', formDir, 'required')"
class="help-block"
class="invalid-feedback"
i18n>This field is required!</span>
</div>
</div>
<!-- End time -->
<div [ngClass]="{'has-error': form.showError('endsAt', formDir)}"
class="form-group">
<label class="control-label col-sm-3"
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="ends-at">
<ng-container i18n>End time</ng-container>
<span class="required"></span>
@ -163,7 +165,7 @@
id="ends-at"
name="ends-at">
<span *ngIf="form.showError('endsAt', formDir, 'required')"
class="help-block"
class="invalid-feedback"
i18n>This field is required!</span>
</div>
</div>
@ -171,7 +173,8 @@
<!-- Matchers -->
<fieldset>
<legend i18n>Matchers<span class="required">*</span></legend>
<div class="col-sm-offset-3 col-sm-9">
<div class="offset-sm-3 col-sm-9">
<h5 *ngIf="matchers.length === 0"
[ngClass]="{'text-warning': !formDir.submitted, 'text-danger': formDir.submitted}">
<strong i18n>A silence requires at least one matcher</strong>
@ -181,34 +184,35 @@
<ng-container *ngTemplateOutlet="matcherTpl; context:{index: i, matcher: matcher}"></ng-container>
</span>
<span class="form-control no-border">
<button type="button"
id="add-matcher"
class="btn btn-sm btn-default btn-label pull-right"
[ngClass]="{'btn-warning': formDir.submitted && matchers.length === 0 }"
(click)="showMatcherModal()">
<i [ngClass]="[icons.width, icons.add]"></i>
<ng-container i18n>Add matcher</ng-container>
</button>
</span>
<div class="row">
<div class="col-12">
<button type="button"
id="add-matcher"
class="btn btn-light float-right my-3"
[ngClass]="{'btn-warning': formDir.submitted && matchers.length === 0 }"
(click)="showMatcherModal()">
<i [ngClass]="[icons.add]"></i>
<ng-container i18n>Add matcher</ng-container>
</button>
</div>
</div>
</div>
<div *ngIf="matchers.length && matcherMatch"
class="col-sm-offset-3 col-sm-9 {{matcherMatch.cssClass}}"
class="offset-sm-3 col-sm-9 {{matcherMatch.cssClass}}"
id="match-state">
<span class="help-block {{matcherMatch.cssClass}}">
<span class="text-muted {{matcherMatch.cssClass}}">
{{ matcherMatch.status }}
</span>
</div>
</fieldset>
</div>
<div class="panel-footer">
<div class="card-footer">
<div class="button-group text-right">
<cd-submit-button (submitAction)="submit()"
[form]="formDir"
id="submit"
i18n="@@formTitle"
type="button">
i18n="@@formTitle">
{{ action | titlecase }} {{ resource | upperFirst }}
</cd-submit-button>
<cd-back-button></cd-back-button>

View File

@ -6,10 +6,10 @@ import { ActivatedRoute, Router, Routes } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import * as _ from 'lodash';
import { ToastModule } from 'ng2-toastr';
import { BsDatepickerDirective, BsDatepickerModule } from 'ngx-bootstrap/datepicker';
import { BsModalService } from 'ngx-bootstrap/modal';
import { TooltipModule } from 'ngx-bootstrap/tooltip';
import { ToastrModule } from 'ngx-toastr';
import { of, throwError } from 'rxjs';
import {
@ -61,7 +61,7 @@ describe('SilenceFormComponent', () => {
RouterTestingModule.withRoutes(routes),
BsDatepickerModule.forRoot(),
SharedModule,
ToastModule.forRoot(),
ToastrModule.forRoot(),
TooltipModule.forRoot(),
ReactiveFormsModule
],

View File

@ -3,10 +3,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastModule } from 'ng2-toastr';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { BsModalRef, BsModalService, ModalModule } from 'ngx-bootstrap/modal';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { ToastrModule } from 'ngx-toastr';
import { of } from 'rxjs';
import {
@ -34,7 +34,7 @@ describe('SilenceListComponent', () => {
BsDropdownModule.forRoot(),
TabsModule.forRoot(),
ModalModule.forRoot(),
ToastModule.forRoot(),
ToastrModule.forRoot(),
RouterTestingModule,
HttpClientTestingModule
],

View File

@ -40,9 +40,9 @@ export class SilenceListComponent implements OnInit {
selection = new CdTableSelection();
modalRef: BsModalRef;
customCss = {
'label label-danger': 'active',
'label label-warning': 'pending',
'label label-default': 'expired'
'badge badge-danger': 'active',
'badge badge-warning': 'pending',
'badge badge-default': 'expired'
};
sorts: SortPropDir[] = [{ prop: 'endsAt', dir: SortDirection.desc }];

View File

@ -9,15 +9,14 @@
</button>
</div>
<form class="form-horizontal"
<form class="form"
#formDir="ngForm"
[formGroup]="form"
novalidate>
<div class="modal-body">
<!-- Name -->
<div class="form-group"
[ngClass]="{'has-error': form.showError('name', formDir)}">
<label class="control-label col-sm-3"
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="name">
<ng-container i18n>Name</ng-container>
<span class="required"></span>
@ -41,9 +40,8 @@
</div>
<!-- Value -->
<div class="form-group"
[ngClass]="{'has-error': form.showError('value', formDir)}">
<label class="control-label col-sm-3"
<div class="form-group row">
<label class="col-form-label col-sm-3"
for="value">
<ng-container i18n>Value</ng-container>
<span class="required"></span>
@ -60,25 +58,26 @@
i18n>This field is required!</span>
</div>
<div *ngIf="form.getValue('value') && !form.getValue('isRegex') && matcherMatch"
class="col-sm-offset-3 col-sm-9 {{matcherMatch.cssClass}}"
class="offset-sm-3 col-sm-9 {{matcherMatch.cssClass}}"
id="match-state">
<span class="help-block {{matcherMatch.cssClass}}">
<span class="text-muted {{matcherMatch.cssClass}}">
{{matcherMatch.status}}
</span>
</div>
</div>
<!-- isRegex -->
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<div class="input-group">
<div class="checkbox checkbox-primary">
<input id="is-regex"
type="checkbox"
formControlName="isRegex">
<label for="is-regex"
i18n>Use regular expression</label>
</div>
<div class="form-group row">
<div class="offset-sm-3 col-sm-9">
<div class="custom-control custom-checkbox">
<input type="checkbox"
class="custom-control-input"
formControlName="isRegex"
name="is-regex"
id="is-regex">
<label for="is-regex"
class="custom-control-label"
i18n>Use regular expression</label>
</div>
</div>
</div>
@ -95,4 +94,3 @@
</cd-back-button>
</div>
</form>

View File

@ -2,7 +2,6 @@
class="container-fluid">
<cd-info-group groupTitle="Status"
i18n-groupTitle
class="row info-group"
*ngIf="healthData.health?.status
|| healthData.mon_status
|| healthData.osd_map
@ -14,8 +13,8 @@
<cd-info-card cardTitle="Cluster Status"
i18n-cardTitle
class="col-sm-6 col-md-4 col-lg-3"
[contentClass]="healthData.health?.checks?.length > 0 ? 'content-highlight text-area-size-2' : 'content-highlight'"
class="cd-status-card"
contentClass="content-highlight"
*ngIf="healthData.health?.status">
<ng-container *ngIf="healthData.health?.checks?.length > 0">
<ng-template #healthChecks>
@ -48,7 +47,7 @@
<cd-info-card cardTitle="Monitors"
i18n-cardTitle
link="/monitor"
class="col-sm-6 col-md-4 col-lg-3"
class="cd-status-card"
contentClass="content-highlight"
*ngIf="healthData.mon_status">
{{ healthData.mon_status | monSummary }}
@ -57,9 +56,9 @@
<cd-info-card cardTitle="OSDs"
i18n-cardTitle
link="/osd"
class="col-sm-6 col-md-4 col-lg-3"
class="cd-status-card"
*ngIf="(healthData.osd_map | osdSummary) as transformedResult"
[contentClass]="(transformedResult.length == 5 ? 'text-area-size-3' : 'text-area-size-2') + ' content-highlight'">
contentClass="content-highlight">
<span *ngFor="let result of transformedResult"
[ngClass]="result.class">
{{ result.content }}
@ -68,8 +67,8 @@
<cd-info-card cardTitle="Manager Daemons"
i18n-cardTitle
class="col-sm-6 col-md-4 col-lg-3"
contentClass="content-highlight text-area-size-2"
class="cd-status-card"
contentClass="content-highlight"
*ngIf="healthData.mgr_map">
<span *ngFor="let result of (healthData.mgr_map | mgrSummary)"
[ngClass]="result.class"
@ -81,8 +80,8 @@
<cd-info-card cardTitle="Hosts"
i18n-cardTitle
link="/hosts"
class="col-sm-6 col-md-4 col-lg-3"
contentClass="content-medium content-highlight"
class="cd-status-card"
contentClass="content-highlight"
*ngIf="healthData.hosts != null">
{{ healthData.hosts }} total
</cd-info-card>
@ -90,16 +89,18 @@
<cd-info-card cardTitle="Object Gateways"
i18n-cardTitle
link="/rgw/daemon"
class="col-sm-6 col-md-4 col-lg-3"
contentClass="content-medium content-highlight"
class="cd-status-card"
contentClass="content-highlight"
*ngIf="enabledFeature.rgw && healthData.rgw != null">
{{ healthData.rgw }} total
</cd-info-card>
<cd-info-card cardTitle="Metadata Servers"
i18n-cardTitle
class="col-sm-6 col-md-4 col-lg-3"
class="cd-status-card"
*ngIf="(enabledFeature.cephfs && healthData.fs_map | mdsSummary) as transformedResult"
[contentClass]="(transformedResult.length > 1 ? 'text-area-size-2' : '') + ' content-highlight'">
<!-- TODO: check text-area-size-2 -->
<span *ngFor="let result of transformedResult"
[ngClass]="result.class">
{{ result.content }}
@ -109,8 +110,8 @@
<cd-info-card cardTitle="iSCSI Gateways"
i18n-cardTitle
link="/block/iscsi"
class="col-sm-6 col-md-4 col-lg-3"
contentClass="content-medium content-highlight"
class="cd-status-card"
contentClass="content-highlight"
*ngIf="enabledFeature.iscsi && healthData.iscsi_daemons != null">
{{ healthData.iscsi_daemons }} total
</cd-info-card>
@ -118,143 +119,126 @@
<cd-info-group groupTitle="Performance"
i18n-groupTitle
class="row info-group"
*ngIf="healthData.client_perf || healthData.scrub_status">
<div class="cd-container-flex">
<cd-info-card cardTitle="Client IOPS"
i18n-cardTitle
class="cd-col-5"
cardClass="card-medium"
contentClass="content-medium content-highlight"
*ngIf="healthData.client_perf">
{{ (healthData.client_perf.read_op_per_sec + healthData.client_perf.write_op_per_sec) | round:1 }}
</cd-info-card>
<cd-info-card cardTitle="Client IOPS"
i18n-cardTitle
class="cd-performance-card"
contentClass="content-highlight"
*ngIf="healthData.client_perf">
{{ (healthData.client_perf.read_op_per_sec + healthData.client_perf.write_op_per_sec) | round:1 }}
</cd-info-card>
<cd-info-card cardTitle="Client Throughput"
i18n-cardTitle
class="cd-col-5"
cardClass="card-medium"
contentClass="content-medium content-highlight"
*ngIf="healthData.client_perf">
{{ ((healthData.client_perf.read_bytes_sec + healthData.client_perf.write_bytes_sec) | dimlessBinary) + '/s' }}
</cd-info-card>
<cd-info-card cardTitle="Client Throughput"
i18n-cardTitle
class="cd-performance-card"
contentClass="content-highlight"
*ngIf="healthData.client_perf">
{{ ((healthData.client_perf.read_bytes_sec + healthData.client_perf.write_bytes_sec) | dimlessBinary) + '/s' }}
</cd-info-card>
<cd-info-card cardTitle="Client Read/Write"
i18n-cardTitle
class="cd-col-5"
cardClass="card-medium"
[contentClass]="isClientReadWriteChartShowable() ? 'content-chart': 'content-medium content-highlight'"
*ngIf="healthData.client_perf">
<cd-health-pie *ngIf="isClientReadWriteChartShowable()"
[data]="healthData"
(prepareFn)="prepareReadWriteRatio($event[0], $event[1])">
</cd-health-pie>
<span *ngIf="!isClientReadWriteChartShowable()">
N/A
</span>
</cd-info-card>
<cd-info-card cardTitle="Client Read/Write"
i18n-cardTitle
class="cd-performance-card"
[contentClass]="isClientReadWriteChartShowable() ? 'content-chart': 'content-highlight'"
*ngIf="healthData.client_perf">
<cd-health-pie *ngIf="isClientReadWriteChartShowable()"
[data]="healthData"
(prepareFn)="prepareReadWriteRatio($event[0], $event[1])">
</cd-health-pie>
<span *ngIf="!isClientReadWriteChartShowable()">
N/A
</span>
</cd-info-card>
<cd-info-card cardTitle="Recovery Throughput"
i18n-cardTitle
class="cd-col-5"
cardClass="card-medium"
contentClass="content-medium content-highlight"
*ngIf="healthData.client_perf">
{{ (healthData.client_perf.recovering_bytes_per_sec | dimlessBinary) + '/s' }}
</cd-info-card>
<cd-info-card cardTitle="Recovery Throughput"
i18n-cardTitle
class="cd-performance-card"
contentClass="content-highlight"
*ngIf="healthData.client_perf">
{{ (healthData.client_perf.recovering_bytes_per_sec | dimlessBinary) + '/s' }}
</cd-info-card>
<cd-info-card cardTitle="Scrub"
i18n-cardTitle
class="cd-col-5"
cardClass="card-medium"
contentClass="content-medium content-highlight"
*ngIf="healthData.scrub_status">
{{ healthData.scrub_status }}
</cd-info-card>
</div>
<cd-info-card cardTitle="Scrub"
i18n-cardTitle
class="cd-performance-card"
contentClass="content-highlight"
*ngIf="healthData.scrub_status">
{{ healthData.scrub_status }}
</cd-info-card>
</cd-info-group>
<cd-info-group groupTitle="Capacity"
i18n-groupTitle
class="row info-group"
*ngIf="healthData.pools
|| healthData.df
|| healthData.pg_info">
<cd-info-card cardTitle="Pools"
i18n-cardTitle
link="/pool"
class="cd-capacity-card order-md-1 order-lg-4 order-xl-1"
contentClass="content-highlight"
*ngIf="healthData.pools">
{{ healthData.pools.length }}
</cd-info-card>
<div class="cd-container-flex">
<cd-info-card cardTitle="Pools"
i18n-cardTitle
link="/pool"
class="cd-col-5"
cardClass="card-medium"
contentClass="content-medium content-highlight"
*ngIf="healthData.pools">
{{ healthData.pools.length }}
</cd-info-card>
<cd-info-card cardTitle="Raw Capacity"
i18n-cardTitle
class="cd-capacity-card order-md-3 order-lg-1 order-xl-2"
contentClass="content-chart"
*ngIf="healthData.df">
<cd-health-pie [data]="healthData"
[config]="rawCapacityChartConfig"
[isBytesData]="true"
(prepareFn)="prepareRawUsage($event[0], $event[1])">
</cd-health-pie>
</cd-info-card>
<cd-info-card cardTitle="Raw Capacity"
i18n-cardTitle
class="cd-col-5"
cardClass="card-medium"
contentClass="content-chart"
*ngIf="healthData.df">
<cd-health-pie [data]="healthData"
[config]="rawCapacityChartConfig"
[showLabelAsTooltip]="true"
(prepareFn)="prepareRawUsage($event[0], $event[1])">
</cd-health-pie>
</cd-info-card>
<cd-info-card cardTitle="Objects"
i18n-cardTitle
class="cd-capacity-card order-md-4 order-lg-2 order-xl-3"
contentClass="content-chart"
*ngIf="healthData.pg_info?.object_stats?.num_objects != null">
<cd-health-pie [data]="healthData"
[config]="objectsChartConfig"
(prepareFn)="prepareObjects($event[0], $event[1])">
</cd-health-pie>
</cd-info-card>
<cd-info-card cardTitle="Objects"
i18n-cardTitle
class="cd-col-5"
cardClass="card-medium"
contentClass="content-chart"
*ngIf="healthData.pg_info?.object_stats?.num_objects != null">
<cd-health-pie [data]="healthData"
[config]="objectsChartConfig"
(prepareFn)="prepareObjects($event[0], $event[1])">
</cd-health-pie>
</cd-info-card>
<cd-info-card cardTitle="PGs per OSD"
i18n-cardTitle
class="cd-capacity-card order-md-2 order-lg-5 order-xl-4"
contentClass="content-highlight"
*ngIf="healthData.pg_info">
{{ healthData.pg_info.pgs_per_osd | dimless }}
</cd-info-card>
<cd-info-card cardTitle="PGs per OSD"
i18n-cardTitle
class="cd-col-5"
cardClass="card-medium"
contentClass="content-medium content-highlight"
*ngIf="healthData.pg_info">
{{ healthData.pg_info.pgs_per_osd | dimless }}
</cd-info-card>
<cd-info-card cardTitle="PG Status"
i18n-cardTitle
class="cd-col-5"
cardClass="card-medium"
contentClass="content-chart"
(click)="pgStatusTarget.toggle()"
*ngIf="healthData.pg_info">
<ng-template #pgStatus>
<ng-container *ngTemplateOutlet="logsLink"></ng-container>
<ul>
<li *ngFor="let pgStatesText of healthData.pg_info.statuses | keyvalue">
{{ pgStatesText.key }}: {{ pgStatesText.value }}
</li>
</ul>
</ng-template>
<div class="pg-status-popover-wrapper">
<div [popover]="pgStatus"
triggers=""
#pgStatusTarget="bs-popover"
placement="bottom">
<cd-health-pie [data]="healthData"
[config]="pgStatusChartConfig"
(prepareFn)="preparePgStatus($event[0], $event[1])">
</cd-health-pie>
</div>
<cd-info-card cardTitle="PG Status"
i18n-cardTitle
class="cd-capacity-card order-md-5 order-lg-3 order-xl-5"
contentClass="content-chart"
(click)="pgStatusTarget.toggle()"
*ngIf="healthData.pg_info">
<ng-template #pgStatus>
<ng-container *ngTemplateOutlet="logsLink"></ng-container>
<ul>
<li *ngFor="let pgStatesText of healthData.pg_info.statuses | keyvalue">
{{ pgStatesText.key }}: {{ pgStatesText.value }}
</li>
</ul>
</ng-template>
<div class="pg-status-popover-wrapper">
<div [popover]="pgStatus"
triggers=""
#pgStatusTarget="bs-popover"
placement="bottom">
<cd-health-pie [data]="healthData"
[config]="pgStatusChartConfig"
(prepareFn)="preparePgStatus($event[0], $event[1])">
</cd-health-pie>
</div>
</cd-info-card>
</div>
</div>
</cd-info-card>
</cd-info-group>
<ng-template #logsLink>

View File

@ -1,55 +1,9 @@
@import '../../../../defaults';
@import 'styles';
cd-info-card {
padding: 0 0.5vw 0 0.5vw;
}
.cd-container-flex {
margin: 0;
padding: 0;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
flex-flow: row wrap;
justify-content: space-between;
}
.cd-col-5 {
width: 20%;
}
@media (max-width: 1599px) {
.cd-col-5 {
width: 25%;
}
}
@media (max-width: $screen-md-max) {
.cd-col-5 {
width: 33%;
}
}
@media (max-width: $screen-sm-max) {
.cd-col-5 {
width: 50%;
}
}
@media (max-width: $screen-xs-max) {
cd-info-card {
padding: 0;
}
.cd-col-5 {
width: 100%;
}
}
.info-group {
margin: 0;
padding: 0;
@extend .d-flex;
@extend .flex-column;
}
::ng-deep .pg-status-popover-wrapper {
@ -87,3 +41,19 @@ cd-info-card {
.mgr-active-name:hover {
cursor: pointer;
}
::ng-deep cd-info-card {
@extend .col-12;
@extend .col-sm-12;
@extend .col-md-6;
@extend .col-lg-4;
&.cd-status-card {
@extend .col-xl-3;
}
&.cd-performance-card,
&.cd-capacity-card {
@extend .col-xl;
}
}

View File

@ -1,4 +1,4 @@
@import '../../../../defaults';
@import 'defaults';
.info-card-popover-cluster-status {
max-width: 23vw;
@ -11,18 +11,18 @@
}
}
@media (max-width: $screen-md-max) {
@media (max-width: $screen-lg-max) {
.info-card-popover-cluster-status {
max-width: 31vw;
}
}
@media (max-width: $screen-sm-max) {
@media (max-width: $screen-md-max) {
.info-card-popover-cluster-status {
max-width: 46vw;
}
}
@media (max-width: $screen-xs-max) {
@media (max-width: $screen-sm-max) {
.info-card-popover-cluster-status {
max-width: 83vw;
}

View File

@ -1,14 +1,18 @@
<div class="card"
<div class="card mb-4"
[ngClass]="cardClass">
<div class="card-title">
<a *ngIf="link; else noLinkTitle"
[routerLink]="link">{{ cardTitle }}</a>
<ng-template #noLinkTitle>
{{ cardTitle }}
</ng-template>
</div>
<div class="card-body"
[ngClass]="contentClass">
<ng-content></ng-content>
<div class="card-body d-flex align-items-center justify-content-center">
<h5 class="card-title m-4">
<a *ngIf="link; else noLinkTitle"
[routerLink]="link">{{ cardTitle }}</a>
<ng-template #noLinkTitle>
{{ cardTitle }}
</ng-template>
</h5>
<div class="card-text text-center"
[ngClass]="contentClass">
<ng-content></ng-content>
</div>
</div>
</div>

View File

@ -1,53 +1,36 @@
@import '../../../../defaults';
@import 'styles';
$card-height: 6vw;
$card-medium-height: 12vw;
$card-font-min-width: 320px;
$card-font-max-width: 2048px;
$card-font-min-size: 12px;
$card-font-max-size: 21px;
$logs-text-font-size: $card-font-min-size;
.card {
height: 100%;
@extend .pb-2;
border: 0.5px solid $color-info-card-border;
border-radius: 3px;
background-color: $color-solid-white;
box-shadow: 0 1px 1px $color-shadow-gray;
margin: 0 -10px 20px;
padding: 0 20px;
width: auto;
height: auto;
margin-left: auto;
margin-right: auto;
min-height: $card-height;
@include fluid-font-size(
$card-font-min-width,
$card-font-max-width,
$card-font-min-size,
$card-font-max-size
);
position: relative;
}
.card-title {
margin: 1.1vw 0;
padding: 0;
}
.card-body {
padding-top: 40px !important;
.card-body {
text-align: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%);
}
.card-title {
position: absolute;
left: 0;
top: 0;
}
.content-chart {
margin-top: -0.7vw;
position: unset;
top: unset;
left: unset;
transform: unset;
.card-text {
@extend .pt-2;
}
}
}
.no-center {
@ -57,79 +40,6 @@ $logs-text-font-size: $card-font-min-size;
transform: unset;
}
.text-area-size-2 {
margin-right: -50%;
transform: translate(-50%, -20%);
}
.text-area-size-3 {
margin-right: -50%;
transform: translate(-50%, -40%);
}
.content-highlight {
font-weight: bold;
}
.card-medium {
min-height: $card-medium-height;
}
.scroll {
max-height: ($card-medium-height * 1.5);
overflow-y: auto;
}
.text-monospace {
font-size: $logs-text-font-size;
font-family: monospace;
text-align: left;
}
@media (max-width: 1599px) {
.card {
min-height: $card-height * 1.3;
}
.card-medium {
min-height: $card-medium-height * 1.2;
}
}
@media (max-width: $screen-md-max) {
.card-medium {
min-height: $card-medium-height * 1.5;
}
.content-chart {
margin-top: -0.6vw;
}
}
@media (max-width: $screen-sm-max) {
.card {
min-height: $card-height * 2;
}
.content-chart {
margin-top: -0.3vw;
}
}
@media (max-width: $screen-sm-max) and (min-width: $screen-sm-min) {
.card-medium {
min-height: $card-medium-height * 2.2;
}
}
@media (max-width: 599px) {
.card {
min-height: $card-height * 3;
}
}
@media (max-width: 319px) {
.card {
min-height: $card-height * 4;
}
}

View File

@ -58,7 +58,7 @@ describe('InfoCardComponent', () => {
const contentClass = 'my-css-content-class';
component.contentClass = contentClass;
fixture.detectChanges();
const card = fixture.debugElement.nativeElement.querySelector(`.card-body.${contentClass}`);
const card = fixture.debugElement.nativeElement.querySelector(`.card-body .${contentClass}`);
expect(card).toBeTruthy();
});

View File

@ -1,4 +1,6 @@
<div class="info-group-title">
{{ groupTitle }}
<div class="row">
<span class="info-group-title">{{ groupTitle }}</span>
</div>
<div class="row">
<ng-content></ng-content>
</div>
<ng-content></ng-content>

View File

@ -1,7 +1,4 @@
@import '../../../../defaults';
.info-group-title {
margin: 0 0 0.5vw 0.5vw;
padding: 0;
font-size: 21px;
}

View File

@ -1,34 +1,32 @@
<div class="form-group">
<label class="col-sm-3 control-label"
<div class="form-group row">
<label class="col-sm-3 col-form-label"
i18n>Clients</label>
<div class="col-sm-9"
[formGroup]="form"
#formDir="ngForm">
<span *ngIf="form.get('clients').value.length === 0"
class="form-control no-border text-muted">
<span class="text-muted"
class="no-border text-muted">
<span class="form-text text-muted"
i18n>Any client can access</span>
</span>
<ng-container formArrayName="clients">
<div *ngFor="let item of form.get('clients').value; let index = index; trackBy: trackByFn">
<div class="panel panel-default"
<div class="card"
[formGroupName]="index">
<div class="panel-heading">
<h3 class="panel-title">{{ (index + 1) | ordinal }}
<span class="pull-right clickable"
(click)="removeClient(index)"
tooltip="Remove">&times;</span>
</h3>
<div class="card-header">
{{ (index + 1) | ordinal }}
<span class="float-right clickable"
(click)="removeClient(index)"
tooltip="Remove">&times;</span>
</div>
<div class="panel-body">
<div class="card-body">
<!-- Addresses -->
<div class="form-group"
[ngClass]="{ 'has-error': showError(index, 'addresses', formDir) }">
<div class="form-group row">
<label i18n
class="col-sm-3 control-label"
class="col-sm-3 col-form-label"
for="addresses">Addresses</label>
<div class="col-sm-9">
<input type="text"
@ -37,7 +35,7 @@
id="addresses"
formControlName="addresses"
placeholder="192.168.0.10, 192.168.1.0/8">
<span class="help-block">
<span class="invalid-feedback">
<span *ngIf="showError(index, 'addresses', formDir, 'required')"
i18n>Required field</span>
@ -51,12 +49,12 @@
</div>
<!-- Access Type-->
<div class="form-group">
<div class="form-group row">
<label i18n
class="col-sm-3 control-label"
class="col-sm-3 col-form-label"
for="access_type">Access Type</label>
<div class="col-sm-9">
<select class="form-control"
<select class="form-control custom-select"
name="access_type"
id="access_type"
formControlName="access_type">
@ -64,7 +62,7 @@
<option *ngFor="let item of nfsAccessType"
[value]="item.value">{{ item.value }}</option>
</select>
<span class="help-block"
<span class="form-text text-muted"
*ngIf="getValue(index, 'access_type')">
{{ getAccessTypeHelp(index) }}
</span>
@ -72,12 +70,12 @@
</div>
<!-- Squash -->
<div class="form-group">
<div class="form-group row">
<label i18n
class="col-sm-3 control-label"
class="col-sm-3 col-form-label"
for="squash">Squash</label>
<div class="col-sm-9">
<select class="form-control"
<select class="form-control custom-select"
name="squash"
id="squash"
formControlName="squash">
@ -92,13 +90,16 @@
</div>
</ng-container>
<span class="form-control no-border">
<button class="btn btn-default btn-label pull-right"
(click)="addClient()">
<i [ngClass]="[icons.add, icons.width]"></i>
<ng-container i18n>Add clients</ng-container>
</button>
</span>
<hr>
<div class="row">
<div class="col-12">
<div class="float-right">
<button class="btn btn-light "
(click)="addClient()">
<i [ngClass]="[icons.add]"></i>
<ng-container i18n>Add clients</ng-container>
</button>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,28 +1,23 @@
<div class="col-sm-12 col-lg-6">
<form name="nfsForm"
class="form-horizontal"
#formDir="ngForm"
[formGroup]="nfsForm"
novalidate>
<div class="panel panel-default">
<div class="panel-heading">
<h3 i18n="form title|Example: Create Pool@@formTitle"
class="panel-title">{{ action | titlecase }} {{ resource | upperFirst }}</h3>
</div>
<div class="panel-body">
<div class="card">
<div i18n="form title|Example: Create Pool@@formTitle"
class="card-header">{{ action | titlecase }} {{ resource | upperFirst }}</div>
<div class="card-body">
<!-- cluster_id -->
<div class="form-group"
[ngClass]="{'has-error': nfsForm.showError('cluster_id', formDir)}"
<div class="form-group row"
*ngIf="!isDefaultCluster">
<label class="col-sm-3 control-label"
<label class="col-sm-3 col-form-label"
for="cluster_id">
<ng-container i18n>Cluster</ng-container>
<span class="required"></span>
</label>
<div class="col-sm-9">
<select class="form-control"
<select class="form-control custom-select"
formControlName="cluster_id"
name="cluster_id"
id="cluster_id"
@ -39,31 +34,30 @@
<option *ngFor="let cluster of allClusters"
[value]="cluster">{{ cluster }}</option>
</select>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="nfsForm.showError('cluster_id', formDir, 'required')"
i18n>Required field</span>
</div>
</div>
<!-- daemons -->
<div class="form-group"
[ngClass]="{'has-error': nfsForm.showError('daemons', formDir)}">
<label class="col-sm-3 control-label"
<div class="form-group row">
<label class="col-sm-3 col-form-label"
for="daemons">
<ng-container i18n>Daemons</ng-container>
</label>
<div class="col-sm-9">
<ng-container *ngFor="let daemon of nfsForm.getValue('daemons'); let i = index">
<div class="input-group cd-mb">
<input class="form-control"
<input class="cd-form-control"
type="text"
[value]="daemon"
disabled />
<span class="input-group-btn">
<button class="btn btn-default"
<span class="input-group-append">
<button class="btn btn-light"
type="button"
(click)="removeDaemon(i, daemon)">
<i [ngClass]="[icons.destroy, icons.width]"
<i [ngClass]="[icons.destroy]"
aria-hidden="true"></i>
</button>
</span>
@ -76,8 +70,8 @@
[options]="daemonsSelections"
[messages]="daemonsMessages"
(selection)="onDaemonSelection()"
elemClass="btn btn-default pull-right">
<i [ngClass]="[icons.add, icons.width]"></i>
elemClass="btn btn-light float-right">
<i [ngClass]="[icons.add]"></i>
<ng-container i18n>Add daemon</ng-container>
</cd-select>
</div>
@ -88,15 +82,14 @@
<!-- FSAL -->
<div formGroupName="fsal">
<!-- Name -->
<div class="form-group"
[ngClass]="{'has-error': nfsForm.showError('name', formDir)}">
<label class="col-sm-3 control-label"
<div class="form-group row">
<label class="col-sm-3 col-form-label"
for="name">
<ng-container i18n>Storage Backend</ng-container>
<span class="required"></span>
</label>
<div class="col-sm-9">
<select class="form-control"
<select class="form-control custom-select"
formControlName="name"
name="name"
id="name"
@ -113,23 +106,22 @@
<option *ngFor="let fsal of allFsals"
[value]="fsal.value">{{ fsal.descr }}</option>
</select>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="nfsForm.showError('name', formDir, 'required')"
i18n>Required field</span>
</div>
</div>
<!-- RGW user -->
<div class="form-group"
[ngClass]="{'has-error': nfsForm.showError('rgw_user_id', formDir)}"
<div class="form-group row"
*ngIf="nfsForm.getValue('name') === 'RGW'">
<label class="col-sm-3 control-label"
<label class="col-sm-3 col-form-label"
for="rgw_user_id">
<ng-container i18n>Object Gateway User</ng-container>
<span class="required"></span>
</label>
<div class="col-sm-9">
<select class="form-control"
<select class="form-control custom-select"
formControlName="rgw_user_id"
name="rgw_user_id"
id="rgw_user_id"
@ -146,23 +138,22 @@
<option *ngFor="let rgwUserId of allRgwUsers"
[value]="rgwUserId">{{ rgwUserId }}</option>
</select>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="nfsForm.showError('rgw_user_id', formDir, 'required')"
i18n>Required field</span>
</div>
</div>
<!-- CephFS user_id -->
<div class="form-group"
[ngClass]="{'has-error': nfsForm.showError('user_id', formDir)}"
<div class="form-group row"
*ngIf="nfsForm.getValue('name') === 'CEPH'">
<label class="col-sm-3 control-label"
<label class="col-sm-3 col-form-label"
for="user_id">
<ng-container i18n>CephFS User ID</ng-container>
<span class="required"></span>
</label>
<div class="col-sm-9">
<select class="form-control"
<select class="form-control custom-select"
formControlName="user_id"
name="user_id"
id="user_id">
@ -178,23 +169,22 @@
<option *ngFor="let client of allCephxClients"
[value]="client">{{ client }}</option>
</select>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="nfsForm.showError('user_id', formDir, 'required')"
i18n>Required field</span>
</div>
</div>
<!-- CephFS fs_name -->
<div class="form-group"
[ngClass]="{'has-error': nfsForm.showError('fs_name', formDir)}"
<div class="form-group row"
*ngIf="nfsForm.getValue('name') === 'CEPH'">
<label class="col-sm-3 control-label"
<label class="col-sm-3 col-form-label"
for="fs_name">
<ng-container i18n>CephFS Name</ng-container>
<span class="required"></span>
</label>
<div class="col-sm-9">
<select class="form-control"
<select class="form-control custom-select"
formControlName="fs_name"
name="fs_name"
id="fs_name"
@ -211,7 +201,7 @@
<option *ngFor="let filesystem of allFsNames"
[value]="filesystem.name">{{ filesystem.name }}</option>
</select>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="nfsForm.showError('fs_name', formDir, 'required')"
i18n>Required field</span>
</div>
@ -219,10 +209,9 @@
</div>
<!-- Secutiry Label -->
<div class="form-group"
[ngClass]="{'has-error': nfsForm.showError('security_label', formDir)}"
<div class="form-group row"
*ngIf="nfsForm.getValue('name') === 'CEPH'">
<label class="col-sm-3 control-label"
<label class="col-sm-3 col-form-label"
for="security_label">
<ng-container i18n>Security Label</ng-container>
<span class="required"
@ -230,12 +219,14 @@
</label>
<div class="col-sm-9">
<div class="checkbox checkbox-primary">
<div class="custom-control custom-checkbox">
<input type="checkbox"
class="custom-control-input"
formControlName="security_label"
name="security_label"
id="security_label">
<label for="security_label"
class="custom-control-label"
i18n>Enable security label</label>
</div>
@ -248,17 +239,16 @@
id="sec_label_xattr"
formControlName="sec_label_xattr">
<span class="help-block"
<span class="invalid-feedback"
*ngIf="nfsForm.showError('sec_label_xattr', formDir, 'required')"
i18n>Required field</span>
</div>
</div>
<!-- Path -->
<div class="form-group"
[ngClass]="{'has-error': nfsForm.showError('path', formDir)}"
<div class="form-group row"
*ngIf="nfsForm.getValue('name') === 'CEPH'">
<label class="col-sm-3 control-label"
<label class="col-sm-3 col-form-label"
for="path">
<ng-container i18n>CephFS Path</ng-container>
<span class="required"></span>
@ -272,24 +262,23 @@
[typeahead]="pathDataSource"
(typeaheadOnSelect)="pathChangeHandler()"
(blur)="pathChangeHandler()">
<span class="help-block"
<span class="invalid-feedback"
*ngIf="nfsForm.showError('path', formDir, 'required')"
i18n>Required field</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="nfsForm.showError('path', formDir, 'pattern')"
i18n>Path need to start with a '/' and can be followed by a word</span>
<span class="help-block"
<span class="form-text text-muted"
*ngIf="isNewDirectory && !nfsForm.showError('path', formDir)"
i18n>New directory will be created</span>
</div>
</div>
<!-- Bucket -->
<div class="form-group"
[ngClass]="{'has-error': nfsForm.showError('path', formDir)}"
<div class="form-group row"
*ngIf="nfsForm.getValue('name') === 'RGW'">
<label class="col-sm-3 control-label"
<label class="col-sm-3 col-form-label"
for="path">
<ng-container i18n>Path</ng-container>
<span class="required"></span>
@ -304,46 +293,49 @@
(typeaheadOnSelect)="bucketChangeHandler()"
(blur)="bucketChangeHandler()">
<span class="help-block"
<span class="invalid-feedback"
*ngIf="nfsForm.showError('path', formDir, 'required')"
i18n>Required field</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="nfsForm.showError('path', formDir, 'pattern')"
i18n>Path can only be a single '/' or a word</span>
<span class="help-block"
<span class="form-text text-muted"
*ngIf="isNewBucket && !nfsForm.showError('path', formDir)"
i18n>New bucket will be created</span>
</div>
</div>
<!-- NFS Protocol -->
<div class="form-group"
[ngClass]="{'has-error': nfsForm.showError('protocolNfsv3', formDir) || nfsForm.showError('protocolNfsv4', formDir)}">
<label class="col-sm-3 control-label"
<div class="form-group row">
<label class="col-sm-3 col-form-label"
for="protocols">
<ng-container i18n>NFS Protocol</ng-container>
<span class="required"></span>
</label>
<div class="col-sm-9">
<div class="checkbox checkbox-primary">
<div class="custom-control custom-checkbox">
<input type="checkbox"
class="custom-control-input"
id="protocolNfsv3"
name="protocolNfsv3"
formControlName="protocolNfsv3">
<label i18n
class="custom-control-label"
for="protocolNfsv3">NFSv3</label>
</div>
<div class="checkbox checkbox-primary">
<div class="custom-control custom-checkbox">
<input type="checkbox"
class="custom-control-input"
formControlName="protocolNfsv4"
name="protocolNfsv4"
id="protocolNfsv4">
<label i18n
class="custom-control-label"
for="protocolNfsv4">NFSv4</label>
</div>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="nfsForm.showError('protocolNfsv3', formDir, 'required') ||
nfsForm.showError('protocolNfsv4', formDir, 'required')"
i18n>Required field</span>
@ -351,9 +343,9 @@
</div>
<!-- Tag -->
<div class="form-group"
<div class="form-group row"
*ngIf="nfsForm.getValue('protocolNfsv3')">
<label class="col-sm-3 control-label"
<label class="col-sm-3 col-form-label"
for="tag">
<ng-container i18n>NFS Tag</ng-container>
<cd-helper>
@ -372,10 +364,9 @@
</div>
<!-- Pseudo -->
<div class="form-group"
[ngClass]="{'has-error': nfsForm.showError('pseudo', formDir)}"
<div class="form-group row"
*ngIf="nfsForm.getValue('protocolNfsv4')">
<label class="col-sm-3 control-label"
<label class="col-sm-3 col-form-label"
for="pseudo">
<ng-container i18n>Pseudo</ng-container>
<span class="required"></span>
@ -391,25 +382,24 @@
name="pseudo"
id="pseudo"
formControlName="pseudo">
<span class="help-block"
<span class="invalid-feedback"
*ngIf="nfsForm.showError('pseudo', formDir, 'required')"
i18n>Required field</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="nfsForm.showError('pseudo', formDir, 'pattern')"
i18n>Pseudo needs to start with a '/' and can't contain any of the following: >, <, |, &, ( or ).</span>
</div>
</div>
<!-- Access Type -->
<div class="form-group"
[ngClass]="{'has-error': nfsForm.showError('access_type', formDir)}">
<label class="col-sm-3 control-label"
<div class="form-group row">
<label class="col-sm-3 col-form-label"
for="access_type">
<ng-container i18n>Access Type</ng-container>
<span class="required"></span>
</label>
<div class="col-sm-9">
<select class="form-control"
<select class="form-control custom-select"
formControlName="access_type"
name="access_type"
id="access_type">
@ -425,26 +415,25 @@
<option *ngFor="let accessType of nfsAccessType"
[value]="accessType.value">{{ accessType.value }}</option>
</select>
<span class="help-block"
<span class="form-text text-muted"
*ngIf="nfsForm.getValue('access_type')">
{{ getAccessTypeHelp(nfsForm.getValue('access_type')) }}
</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="nfsForm.showError('access_type', formDir, 'required')"
i18n>Required field</span>
</div>
</div>
<!-- Squash -->
<div class="form-group"
[ngClass]="{'has-error': nfsForm.showError('squash', formDir)}">
<label class="col-sm-3 control-label"
<div class="form-group row">
<label class="col-sm-3 col-form-label"
for="squash">
<ng-container i18n>Squash</ng-container>
<span class="required"></span>
</label>
<div class="col-sm-9">
<select class="form-control"
<select class="form-control custom-select"
name="squash"
formControlName="squash"
id="squash">
@ -461,38 +450,41 @@
[value]="squash">{{ squash }}</option>
</select>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="nfsForm.showError('squash', formDir,'required')"
i18n>Required field</span>
</div>
</div>
<!-- Transport Protocol -->
<div class="form-group"
[ngClass]="{'has-error': nfsForm.showError('transportUDP', formDir) || nfsForm.showError('transportTCP', formDir)}">
<label class="col-sm-3 control-label"
<div class="form-group row">
<label class="col-sm-3 col-form-label"
for="transports">
<ng-container i18n>Transport Protocol</ng-container>
<span class="required"></span>
</label>
<div class="col-sm-9">
<div class="checkbox checkbox-primary">
<div class="custom-control custom-checkbox">
<input type="checkbox"
class="custom-control-input"
formControlName="transportUDP"
name="transportUDP"
id="transportUDP">
<label for="transportUDP"
class="custom-control-label"
i18n>UDP</label>
</div>
<div class="checkbox checkbox-primary">
<div class="custom-control custom-checkbox">
<input type="checkbox"
class="custom-control-input"
formControlName="transportTCP"
name="transportTCP"
id="transportTCP">
<label for="transportTCP"
class="custom-control-label"
i18n>TCP</label>
</div>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="nfsForm.showError('transportUDP', formDir, 'required') ||
nfsForm.showError('transportTCP', formDir, 'required')"
i18n>Required field</span>
@ -507,13 +499,12 @@
</div>
<div class="panel-footer">
<div class="card-footer">
<div class="button-group text-right">
<cd-submit-button
[form]="formDir"
(submitAction)="submitAction()"
i18n="form action button|Example: Create Pool@@formActionButton"
type="button">{{ action | titlecase }} {{ resource | upperFirst }}</cd-submit-button>
[form]="formDir">{{ action | titlecase }} {{ resource | upperFirst }}</cd-submit-button>
<cd-back-button></cd-back-button>
</div>
</div>

View File

@ -4,8 +4,8 @@ import { ReactiveFormsModule } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastModule } from 'ng2-toastr';
import { TypeaheadModule } from 'ngx-bootstrap/typeahead';
import { ToastrModule } from 'ngx-toastr';
import { ActivatedRouteStub } from '../../../../testing/activated-route-stub';
import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
@ -28,7 +28,7 @@ describe('NfsFormComponent', () => {
ReactiveFormsModule,
RouterTestingModule,
SharedModule,
ToastModule.forRoot(),
ToastrModule.forRoot(),
TypeaheadModule.forRoot()
],
providers: [

View File

@ -3,8 +3,8 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testin
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastModule } from 'ng2-toastr';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { ToastrModule } from 'ngx-toastr';
import { BehaviorSubject, of } from 'rxjs';
import {
@ -39,7 +39,7 @@ describe('NfsListComponent', () => {
HttpClientTestingModule,
RouterTestingModule,
SharedModule,
ToastModule.forRoot(),
ToastrModule.forRoot(),
TabsModule.forRoot()
],
providers: [TaskListService, i18nProviders]

View File

@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { TypeaheadModule } from 'ngx-bootstrap/typeahead';
@ -20,7 +21,8 @@ import { NfsListComponent } from './nfs-list/nfs-list.component';
SharedModule,
TabsModule.forRoot(),
CommonModule,
TypeaheadModule.forRoot()
TypeaheadModule.forRoot(),
NgBootstrapFormValidationModule
],
declarations: [
NfsListComponent,

View File

@ -1,23 +1,22 @@
<div class="modal-header">
<h4 i18n="form title|Example: Create Pool@@formTitle"
class="modal-title pull-left">{{ action | titlecase }} {{ resource | upperFirst }}</h4>
class="modal-title float-left">{{ action | titlecase }} {{ resource | upperFirst }}</h4>
<button type="button"
class="close pull-right"
class="close float-right"
aria-label="Close"
(click)="bsModalRef.hide()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form class="form-horizontal"
#frm="ngForm"
<form #frm="ngForm"
[formGroup]="form"
novalidate>
<div class="modal-body">
<div class="form-group"
[ngClass]="{'has-error': form.showError('name', frm)}">
<div class="form-group row">
<label for="name"
class="control-label col-sm-3">
class="col-form-label col-sm-3">
<ng-container i18n>Name</ng-container>
<span class="required"></span>
</label>
@ -29,28 +28,28 @@
placeholder="Name..."
formControlName="name"
autofocus>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="form.showError('name', frm, 'required')"
i18n>This field is required!</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="form.showError('name', frm, 'pattern')"
i18n>The name can only consist of alphanumeric characters, dashes and underscores.</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="form.showError('name', frm, 'uniqueName')"
i18n>The chosen erasure code profile name is already in use.</span>
</div>
</div>
<div class="form-group">
<div class="form-group row">
<label for="plugin"
class="control-label col-sm-3">
class="col-form-label col-sm-3">
<ng-container i18n>Plugin</ng-container>
<span class="required"></span>
<cd-helper [html]="tooltips.plugins[plugin].description">
</cd-helper>
</label>
<div class="col-sm-9">
<select class="form-control"
<select class="form-control custom-select"
id="plugin"
name="plugin"
formControlName="plugin">
@ -62,16 +61,15 @@
{{ plugin }}
</option>
</select>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="form.showError('name', frm, 'required')"
i18n>This field is required!</span>
</div>
</div>
<div class="form-group"
[ngClass]="{'has-error': form.showError('k', frm)}">
<div class="form-group row">
<label for="k"
class="control-label col-sm-3">
class="col-form-label col-sm-3">
<ng-container i18n>Data chunks (k)</ng-container>
<span class="required"
*ngIf="requiredControls.includes('k')"></span>
@ -86,19 +84,18 @@
ng-model="$ctrl.erasureCodeProfile.k"
placeholder="Data chunks..."
formControlName="k">
<span class="help-block"
<span class="invalid-feedback"
*ngIf="form.showError('k', frm, 'required')"
i18n>This field is required!</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="form.showError('k', frm, 'min')"
i18n>Must be equal to or greater than 2.</span>
</div>
</div>
<div class="form-group"
[ngClass]="{'has-error': form.showError('m', frm)}">
<div class="form-group row">
<label for="m"
class="control-label col-sm-3">
class="col-form-label col-sm-3">
<ng-container i18n>Coding chunks (m)</ng-container>
<span class="required"
*ngIf="requiredControls.includes('m')"></span>
@ -112,20 +109,19 @@
class="form-control"
placeholder="Coding chunks..."
formControlName="m">
<span class="help-block"
<span class="invalid-feedback"
*ngIf="form.showError('m', frm, 'required')"
i18n>This field is required!</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="form.showError('m', frm, 'min')"
i18n>Must be equal to or greater than 1.</span>
</div>
</div>
<div class="form-group"
*ngIf="plugin === 'shec'"
[ngClass]="{'has-error': form.showError('c', frm)}">
<div class="form-group row"
*ngIf="plugin === 'shec'">
<label for="c"
class="control-label col-sm-3">
class="col-form-label col-sm-3">
<ng-container i18n>Durability estimator (c)</ng-container>
<cd-helper [html]="tooltips.plugins.shec.c">
</cd-helper>
@ -137,17 +133,16 @@
class="form-control"
placeholder="Coding chunks..."
formControlName="c">
<span class="help-block"
<span class="invalid-feedback"
*ngIf="form.showError('c', frm, 'min')"
i18n>Must be equal to or greater than 1.</span>
</div>
</div>
<div class="form-group"
*ngIf="plugin === PLUGIN.LRC"
[ngClass]="{'has-error': form.showError('l', frm)}">
<div class="form-group row"
*ngIf="plugin === PLUGIN.LRC">
<label for="l"
class="control-label col-sm-3">
class="col-form-label col-sm-3">
<ng-container i18n>Locality (l)</ng-container>
<span class="required"></span>
<cd-helper [html]="tooltips.plugins.lrc.l">
@ -160,24 +155,24 @@
class="form-control"
placeholder="Coding chunks..."
formControlName="l">
<span class="help-block"
<span class="invalid-feedback"
*ngIf="form.showError('l', frm, 'required')"
i18n>This field is required!</span>
<span class="help-block"
<span class="invalid-feedback"
*ngIf="form.showError('l', frm, 'min')"
i18n>Must be equal to or greater than 1.</span>
</div>
</div>
<div class="form-group">
<div class="form-group row">
<label for="crushFailureDomain"
class="control-label col-sm-3">
class="col-form-label col-sm-3">
<ng-container i18n>Crush failure domain</ng-container>
<cd-helper [html]="tooltips.crushFailureDomain">
</cd-helper>
</label>
<div class="col-sm-9">
<select class="form-control"
<select class="form-control custom-select"
id="crushFailureDomain"
name="crushFailureDomain"
formControlName="crushFailureDomain">
@ -192,16 +187,16 @@
</div>
</div>
<div class="form-group"
<div class="form-group row"
*ngIf="plugin === PLUGIN.LRC">
<label for="crushLocality"
class="control-label col-sm-3">
class="col-form-label col-sm-3">
<ng-container i18n>Crush Locality</ng-container>
<cd-helper [html]="tooltips.plugins.lrc.crushLocality">
</cd-helper>
</label>
<div class="col-sm-9">
<select class="form-control"
<select class="form-control custom-select"
id="crushLocality"
name="crushLocality"
formControlName="crushLocality">
@ -219,16 +214,16 @@
</div>
</div>
<div class="form-group"
<div class="form-group row"
*ngIf="[PLUGIN.JERASURE, PLUGIN.ISA].includes(plugin)">
<label for="technique"
class="control-label col-sm-3">
class="col-form-label col-sm-3">
<ng-container i18n>Technique</ng-container>
<cd-helper [html]="tooltips.plugins[plugin].technique">
</cd-helper>
</label>
<div class="col-sm-9">
<select class="form-control"
<select class="form-control custom-select"
id="technique"
name="technique"
formControlName="technique">
@ -240,11 +235,10 @@
</div>
</div>
<div class="form-group"
*ngIf="plugin === PLUGIN.JERASURE"
[ngClass]="{'has-error': form.showError('packetSize', frm)}">
<div class="form-group row"
*ngIf="plugin === PLUGIN.JERASURE">
<label for="packetSize"
class="control-label col-sm-3">
class="col-form-label col-sm-3">
<ng-container i18n>Packetsize</ng-container>
<cd-helper [html]="tooltips.plugins.jerasure.packetSize">
</cd-helper>
@ -256,16 +250,15 @@
class="form-control"
placeholder="Packetsize..."
formControlName="packetSize">
<span class="help-block"
<span class="invalid-feedback"
*ngIf="form.showError('packetSize', frm, 'min')"
i18n>Must be equal to or greater than 1.</span>
</div>
</div>
<div class="form-group"
[ngClass]="{'has-error': form.showError('crushRoot', frm)}">
<div class="form-group row">
<label for="crushRoot"
class="control-label col-sm-3">
class="col-form-label col-sm-3">
<ng-container i18n>Crush root</ng-container>
<cd-helper [html]="tooltips.crushRoot">
</cd-helper>
@ -280,15 +273,15 @@
</div>
</div>
<div class="form-group">
<div class="form-group row">
<label for="crushDeviceClass"
class="control-label col-sm-3">
class="col-form-label col-sm-3">
<ng-container i18n>Crush device class</ng-container>
<cd-helper [html]="tooltips.crushDeviceClass">
</cd-helper>
</label>
<div class="col-sm-9">
<select class="form-control"
<select class="form-control custom-select"
id="crushDeviceClass"
name="crushDeviceClass"
formControlName="crushDeviceClass">
@ -302,9 +295,9 @@
</div>
</div>
<div class="form-group">
<div class="form-group row">
<label for="directory"
class="control-label col-sm-3">
class="col-form-label col-sm-3">
<ng-container i18n>Directory</ng-container>
<cd-helper [html]="tooltips.directory">
</cd-helper>
@ -321,10 +314,9 @@
</div>
<div class="modal-footer">
<cd-submit-button
(submitAction)="onSubmit()"
i18n="form action button|Example: Create Pool@@formActionButton"
[form]="frm">{{ action | titlecase }} {{ resource | upperFirst }}</cd-submit-button>
<cd-submit-button (submitAction)="onSubmit()"
i18n="form action button|Example: Create Pool@@formActionButton"
[form]="frm">{{ action | titlecase }} {{ resource | upperFirst }}</cd-submit-button>
<cd-back-button [back]="bsModalRef.hide"></cd-back-button>
</div>
</form>

Some files were not shown because too many files have changed in this diff Show More