Loading src/app/common/layout/topbar/navigation.component.scss +6 −0 Original line number Diff line number Diff line Loading @@ -11,6 +11,12 @@ flex-wrap: wrap; } &.m-topbar--navigation__centered { max-width: 100%; justify-content: center; flex-wrap: wrap; } &:not(.m-topbar--navigation--text-only) .m-topbar--navigation--item span { @media screen and (max-width: 840px) { display: none; Loading src/app/controllers/admin/admin.html +11 −1 Original line number Diff line number Diff line <div class="m-toolbar"> <div class="m-topbar--row"> <div class="m-topbar--navigation m-topbar--navigation--text-only"> <div class="m-topbar--navigation m-topbar--navigation__centered m-topbar--navigation--text-only" > <a class="m-topbar--navigation--item" routerLink="/analytics/admin" Loading Loading @@ -99,6 +101,13 @@ > <span i18n="@@M__ADMIN_NAV__REPORTS">Reports</span> </a> <a class="m-topbar--navigation--item" routerLink="/admin/features" routerLinkActive="m-topbar--navigation--item-active" > <span i18n="@@M__ADMIN_NAV__FEATURES">Features</span> </a> </div> </div> </div> Loading @@ -122,3 +131,4 @@ <m-admin--reports-download *ngIf="filter == 'reports-download'" ></m-admin--reports-download> <m-admin--features *ngIf="filter == 'features'"></m-admin--features> src/app/controllers/admin/features/admin-features.component.html 0 → 100644 +47 −0 Original line number Diff line number Diff line <div class="m-adminFeatures"> <ng-container *ngIf="!isLoading && !error"> <div class="m-adminFeatures--label" i18n> <b>Environment</b>: {{ environment }} </div> <div class="m-adminFeatures--label" i18n> <b>Features for</b>: {{ readableFor }} </div> <table class="m-adminFeatures--table" cellspacing="0" cellpadding="0"> <thead> <tr> <th class="m-adminFeaturesTable--cell__first">Feature</th> <th *ngFor="let service of services">{{ service }}</th> </tr> </thead> <tbody> <tr *ngFor="let feature of features"> <td class="m-adminFeaturesTable--cell__first">{{ feature.name }}</td> <td *ngFor="let service of services" class="m-adminFeaturesTable--cell__value" [class.m-adminFeaturesTable--cell__bestValue]=" isBestService(service, feature.services) " > {{ labelForValue(feature.services[service]) }} </td> </tr> </tbody> </table> </ng-container> <ng-container *ngIf="isLoading"> <div class="m-adminFeatures--loader"> <div class="mdl-spinner mdl-js-spinner is-active" [mdl]></div> </div> </ng-container> <ng-container *ngIf="error"> <div class="m-adminFeatures--error"> {{ error }} </div> </ng-container> </div> src/app/controllers/admin/features/admin-features.component.scss 0 → 100644 +82 −0 Original line number Diff line number Diff line .m-adminFeatures { max-width: 960px; margin: 0 auto; padding: 16px; .m-adminFeatures--label { margin-bottom: 8px; padding: 0 4px; font-size: 14px; text-transform: uppercase; letter-spacing: 0.5px; @include m-theme() { color: themed($m-grey-400); } } .m-adminFeatures--table { width: 100%; margin-top: 24px; th, td { text-align: center; &.m-adminFeaturesTable--cell__first { text-align: left; } } th { padding: 4px; font-size: 14px; text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid; @include m-theme() { color: themed($m-grey-400); border-color: themed($m-black); } } td { padding: 8px 4px; &.m-adminFeaturesTable--cell__value { font-size: 14px; text-transform: uppercase; letter-spacing: 0.5px; @include m-theme() { color: themed($m-grey-400); } } &.m-adminFeaturesTable--cell__bestValue { font-weight: bold; @include m-theme() { text-shadow: 0 0 3px rgba(themed($m-blue), 0.6); color: themed($m-black); } } } } .m-adminFeatures--loader { text-align: center; margin: 64px 0; } .m-adminFeatures--error { text-align: center; margin: 100px 0; font-size: 28px; @include m-theme() { color: themed($m-red); } } } src/app/controllers/admin/features/admin-features.component.ts 0 → 100644 +123 −0 Original line number Diff line number Diff line import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, } from '@angular/core'; import { Client } from '../../../services/api/client'; import { ActivatedRoute } from '@angular/router'; import { Subscription } from 'rxjs'; type ServicesEntityStruc = { [service: string]: boolean | null; }; type ResponseFeaturesStruc = Array<{ name: string; services: ServicesEntityStruc; }>; @Component({ selector: 'm-admin--features', changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: 'admin-features.component.html', }) export class AdminFeaturesComponent implements OnInit, OnDestroy { isLoading: boolean; for: string; environment: string; services: Array<string>; features: ResponseFeaturesStruc; error: string; protected params$: Subscription; constructor( protected client: Client, protected cd: ChangeDetectorRef, protected route: ActivatedRoute ) {} ngOnInit(): void { this.params$ = this.route.params.subscribe(params => { if (typeof params.for !== 'undefined') { this.for = params.for; this.load(); } }); this.load(); } ngOnDestroy(): void { this.params$.unsubscribe(); } async load(): Promise<void> { this.isLoading = true; this.error = ''; this.detectChanges(); try { const response: any = await this.client.get('api/v2/admin/features', { for: this.for || '', }); this.environment = response.environment; this.for = response.for; this.services = response.services; this.features = response.features; } catch (e) { this.error = (e && e.message) || 'Internal server error'; } this.isLoading = false; this.detectChanges(); } get readableFor(): string { if (!this.for) { return 'Anonymous user'; } return `@${this.for}`; } isBestService( currentService: string, services: ServicesEntityStruc ): boolean { let bestService = this.services[0]; for (const service of this.services) { if (services[service] !== null) { bestService = service; } } return currentService == bestService; } labelForValue(value: any): string { if (value === false) { return 'OFF'; } else if (value === null) { return '\xa0'; } else if (!value) { return '???'; } return 'ON'; } detectChanges(): void { this.cd.markForCheck(); this.cd.detectChanges(); } } Loading
src/app/common/layout/topbar/navigation.component.scss +6 −0 Original line number Diff line number Diff line Loading @@ -11,6 +11,12 @@ flex-wrap: wrap; } &.m-topbar--navigation__centered { max-width: 100%; justify-content: center; flex-wrap: wrap; } &:not(.m-topbar--navigation--text-only) .m-topbar--navigation--item span { @media screen and (max-width: 840px) { display: none; Loading
src/app/controllers/admin/admin.html +11 −1 Original line number Diff line number Diff line <div class="m-toolbar"> <div class="m-topbar--row"> <div class="m-topbar--navigation m-topbar--navigation--text-only"> <div class="m-topbar--navigation m-topbar--navigation__centered m-topbar--navigation--text-only" > <a class="m-topbar--navigation--item" routerLink="/analytics/admin" Loading Loading @@ -99,6 +101,13 @@ > <span i18n="@@M__ADMIN_NAV__REPORTS">Reports</span> </a> <a class="m-topbar--navigation--item" routerLink="/admin/features" routerLinkActive="m-topbar--navigation--item-active" > <span i18n="@@M__ADMIN_NAV__FEATURES">Features</span> </a> </div> </div> </div> Loading @@ -122,3 +131,4 @@ <m-admin--reports-download *ngIf="filter == 'reports-download'" ></m-admin--reports-download> <m-admin--features *ngIf="filter == 'features'"></m-admin--features>
src/app/controllers/admin/features/admin-features.component.html 0 → 100644 +47 −0 Original line number Diff line number Diff line <div class="m-adminFeatures"> <ng-container *ngIf="!isLoading && !error"> <div class="m-adminFeatures--label" i18n> <b>Environment</b>: {{ environment }} </div> <div class="m-adminFeatures--label" i18n> <b>Features for</b>: {{ readableFor }} </div> <table class="m-adminFeatures--table" cellspacing="0" cellpadding="0"> <thead> <tr> <th class="m-adminFeaturesTable--cell__first">Feature</th> <th *ngFor="let service of services">{{ service }}</th> </tr> </thead> <tbody> <tr *ngFor="let feature of features"> <td class="m-adminFeaturesTable--cell__first">{{ feature.name }}</td> <td *ngFor="let service of services" class="m-adminFeaturesTable--cell__value" [class.m-adminFeaturesTable--cell__bestValue]=" isBestService(service, feature.services) " > {{ labelForValue(feature.services[service]) }} </td> </tr> </tbody> </table> </ng-container> <ng-container *ngIf="isLoading"> <div class="m-adminFeatures--loader"> <div class="mdl-spinner mdl-js-spinner is-active" [mdl]></div> </div> </ng-container> <ng-container *ngIf="error"> <div class="m-adminFeatures--error"> {{ error }} </div> </ng-container> </div>
src/app/controllers/admin/features/admin-features.component.scss 0 → 100644 +82 −0 Original line number Diff line number Diff line .m-adminFeatures { max-width: 960px; margin: 0 auto; padding: 16px; .m-adminFeatures--label { margin-bottom: 8px; padding: 0 4px; font-size: 14px; text-transform: uppercase; letter-spacing: 0.5px; @include m-theme() { color: themed($m-grey-400); } } .m-adminFeatures--table { width: 100%; margin-top: 24px; th, td { text-align: center; &.m-adminFeaturesTable--cell__first { text-align: left; } } th { padding: 4px; font-size: 14px; text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid; @include m-theme() { color: themed($m-grey-400); border-color: themed($m-black); } } td { padding: 8px 4px; &.m-adminFeaturesTable--cell__value { font-size: 14px; text-transform: uppercase; letter-spacing: 0.5px; @include m-theme() { color: themed($m-grey-400); } } &.m-adminFeaturesTable--cell__bestValue { font-weight: bold; @include m-theme() { text-shadow: 0 0 3px rgba(themed($m-blue), 0.6); color: themed($m-black); } } } } .m-adminFeatures--loader { text-align: center; margin: 64px 0; } .m-adminFeatures--error { text-align: center; margin: 100px 0; font-size: 28px; @include m-theme() { color: themed($m-red); } } }
src/app/controllers/admin/features/admin-features.component.ts 0 → 100644 +123 −0 Original line number Diff line number Diff line import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, } from '@angular/core'; import { Client } from '../../../services/api/client'; import { ActivatedRoute } from '@angular/router'; import { Subscription } from 'rxjs'; type ServicesEntityStruc = { [service: string]: boolean | null; }; type ResponseFeaturesStruc = Array<{ name: string; services: ServicesEntityStruc; }>; @Component({ selector: 'm-admin--features', changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: 'admin-features.component.html', }) export class AdminFeaturesComponent implements OnInit, OnDestroy { isLoading: boolean; for: string; environment: string; services: Array<string>; features: ResponseFeaturesStruc; error: string; protected params$: Subscription; constructor( protected client: Client, protected cd: ChangeDetectorRef, protected route: ActivatedRoute ) {} ngOnInit(): void { this.params$ = this.route.params.subscribe(params => { if (typeof params.for !== 'undefined') { this.for = params.for; this.load(); } }); this.load(); } ngOnDestroy(): void { this.params$.unsubscribe(); } async load(): Promise<void> { this.isLoading = true; this.error = ''; this.detectChanges(); try { const response: any = await this.client.get('api/v2/admin/features', { for: this.for || '', }); this.environment = response.environment; this.for = response.for; this.services = response.services; this.features = response.features; } catch (e) { this.error = (e && e.message) || 'Internal server error'; } this.isLoading = false; this.detectChanges(); } get readableFor(): string { if (!this.for) { return 'Anonymous user'; } return `@${this.for}`; } isBestService( currentService: string, services: ServicesEntityStruc ): boolean { let bestService = this.services[0]; for (const service of this.services) { if (services[service] !== null) { bestService = service; } } return currentService == bestService; } labelForValue(value: any): string { if (value === false) { return 'OFF'; } else if (value === null) { return '\xa0'; } else if (!value) { return '???'; } return 'ON'; } detectChanges(): void { this.cd.markForCheck(); this.cd.detectChanges(); } }