import { SilenceFormComponent } from './prometheus/silence-form/silence-form.component';
import { SilenceListComponent } from './prometheus/silence-list/silence-list.component';
import { SilenceMatcherModalComponent } from './prometheus/silence-matcher-modal/silence-matcher-modal.component';
+import { PlacementPipe } from './services/placement.pipe';
import { ServiceDaemonListComponent } from './services/service-daemon-list/service-daemon-list.component';
import { ServiceDetailsComponent } from './services/service-details/service-details.component';
import { ServiceFormComponent } from './services/service-form/service-form.component';
ServiceDaemonListComponent,
TelemetryComponent,
OsdFlagsIndivModalComponent,
- ServiceFormComponent
+ ServiceFormComponent,
+ PlacementPipe
]
})
export class ClusterModule {}
--- /dev/null
+import { TestBed } from '@angular/core/testing';
+import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
+
+import { I18n } from '@ngx-translate/i18n-polyfill';
+
+import { PlacementPipe } from './placement.pipe';
+
+describe('PlacementPipe', () => {
+ let pipe: PlacementPipe;
+
+ configureTestBed({
+ providers: [i18nProviders]
+ });
+
+ beforeEach(() => {
+ const i18n = TestBed.get(I18n);
+ pipe = new PlacementPipe(i18n);
+ });
+
+ it('create an instance', () => {
+ expect(pipe).toBeTruthy();
+ });
+
+ it('transforms to no spec', () => {
+ expect(pipe.transform(undefined)).toBe('no spec');
+ });
+
+ it('transforms to unmanaged', () => {
+ expect(pipe.transform({ unmanaged: true })).toBe('unmanaged');
+ });
+
+ it('transforms placement (1)', () => {
+ expect(
+ pipe.transform({
+ placement: {
+ hosts: ['mon0']
+ }
+ })
+ ).toBe('mon0');
+ });
+
+ it('transforms placement (2)', () => {
+ expect(
+ pipe.transform({
+ placement: {
+ hosts: ['mon0', 'mgr0']
+ }
+ })
+ ).toBe('mon0;mgr0');
+ });
+
+ it('transforms placement (3)', () => {
+ expect(
+ pipe.transform({
+ placement: {
+ count: 1
+ }
+ })
+ ).toBe('count:1');
+ });
+
+ it('transforms placement (4)', () => {
+ expect(
+ pipe.transform({
+ placement: {
+ label: 'foo'
+ }
+ })
+ ).toBe('label:foo');
+ });
+
+ it('transforms placement (5)', () => {
+ expect(
+ pipe.transform({
+ placement: {
+ host_pattern: '*'
+ }
+ })
+ ).toBe('*');
+ });
+
+ it('transforms placement (6)', () => {
+ expect(
+ pipe.transform({
+ placement: {
+ count: 2,
+ hosts: ['mon0', 'mgr0']
+ }
+ })
+ ).toBe('mon0;mgr0;count:2');
+ });
+});
--- /dev/null
+import { Pipe, PipeTransform } from '@angular/core';
+
+import { I18n } from '@ngx-translate/i18n-polyfill';
+import * as _ from 'lodash';
+
+@Pipe({
+ name: 'placement'
+})
+export class PlacementPipe implements PipeTransform {
+ constructor(private i18n: I18n) {}
+
+ /**
+ * Convert the placement configuration into human readable form.
+ * The output is equal to the column 'PLACEMENT' in 'ceph orch ls'.
+ * @param serviceSpec The service specification to process.
+ * @return The placement configuration as human readable string.
+ */
+ transform(serviceSpec: object | undefined): string {
+ if (_.isUndefined(serviceSpec)) {
+ return this.i18n('no spec');
+ }
+ if (_.get(serviceSpec, 'unmanaged', false)) {
+ return this.i18n('unmanaged');
+ }
+ const kv: Array<any> = [];
+ const hosts: Array<string> = _.get(serviceSpec, 'placement.hosts');
+ const count: number = _.get(serviceSpec, 'placement.count');
+ const label: string = _.get(serviceSpec, 'placement.label');
+ const hostPattern: string = _.get(serviceSpec, 'placement.host_pattern');
+ if (_.isArray(hosts)) {
+ kv.push(...hosts);
+ }
+ if (_.isNumber(count)) {
+ kv.push(this.i18n('count:{{count}}', { count }));
+ }
+ if (_.isString(label)) {
+ kv.push(this.i18n('label:{{label}}', { label }));
+ }
+ if (_.isString(hostPattern)) {
+ kv.push(...hostPattern);
+ }
+ return kv.join(';');
+ }
+}
it('should have columns that are sortable', () => {
expect(
component.columns
+ // Filter the 'Expand/Collapse Row' column.
.filter((column) => !(column.cellClass === 'cd-datatable-expand-collapse'))
+ // Filter the 'Placement' column.
+ .filter((column) => !(column.prop === ''))
.every((column) => Boolean(column.prop))
).toBeTruthy();
});
import { AuthStorageService } from '../../../shared/services/auth-storage.service';
import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
import { URLBuilderService } from '../../../shared/services/url-builder.service';
+import { PlacementPipe } from './placement.pipe';
const BASE_URL = 'services';
length: 12
}
},
+ {
+ name: this.i18n('Placement'),
+ prop: '',
+ pipe: new PlacementPipe(this.i18n),
+ flexGrow: 1
+ },
{
name: this.i18n('Running'),
prop: 'status.running',