mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 20:57:31 +00:00
fix(console): hide granted project navigation if none, cache zitadel permissions, emit refresh on org change, cleanup contributors, styling (#511)
* fix iam member model * fix org member model * fix auth user loading * copytoclipboard directive * directive logs, load bar on init, create user * typo * welcome section, contributor spinner * fix home link * fix stepper flow * show dialog on invalid token * fix app table refresh, pin icons light theme * cleanup contributor * inherit parent color, animations * use localized date pipe everywhere * cmp styles refactor, dont show granted p if none * fix navitem desc, fixed header * change permissions, caching * roles on org emit, use prom instead of hot obs * dont calc 100vh
This commit is contained in:
153
console/src/app/animations.ts
Normal file
153
console/src/app/animations.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
import {
|
||||
animate,
|
||||
animateChild,
|
||||
AnimationTriggerMetadata,
|
||||
group,
|
||||
query,
|
||||
stagger,
|
||||
style,
|
||||
transition,
|
||||
trigger,
|
||||
} from '@angular/animations';
|
||||
|
||||
export const toolbarAnimation: AnimationTriggerMetadata =
|
||||
trigger('toolbar', [
|
||||
transition(':enter', [
|
||||
style({
|
||||
transform: 'translateY(-100%)',
|
||||
opacity: 0,
|
||||
}),
|
||||
animate(
|
||||
'.2s ease-out',
|
||||
style({
|
||||
transform: 'translateY(0%)',
|
||||
opacity: 1,
|
||||
}),
|
||||
),
|
||||
]),
|
||||
]);
|
||||
|
||||
export const accountCard: AnimationTriggerMetadata = trigger('accounts', [
|
||||
transition(':enter', [
|
||||
style({
|
||||
transform: 'scale(.9) translateY(-10%)',
|
||||
height: '200px',
|
||||
opacity: 0,
|
||||
}),
|
||||
animate(
|
||||
'.1s ease-out',
|
||||
style({
|
||||
transform: 'scale(1) translateY(0%)',
|
||||
height: '*',
|
||||
opacity: 1,
|
||||
}),
|
||||
),
|
||||
]),
|
||||
]);
|
||||
|
||||
export const navAnimations: Array<AnimationTriggerMetadata> = [
|
||||
trigger('navAnimation', [
|
||||
transition('* => *', [
|
||||
query('@navitem', stagger('50ms', animateChild()), { optional: true }),
|
||||
]),
|
||||
]),
|
||||
trigger('navitem', [
|
||||
transition(':enter', [
|
||||
style({
|
||||
opacity: 0,
|
||||
}),
|
||||
animate(
|
||||
'.0s',
|
||||
style({
|
||||
opacity: 1,
|
||||
}),
|
||||
),
|
||||
]),
|
||||
transition(':leave', [
|
||||
style({
|
||||
opacity: 1,
|
||||
}),
|
||||
animate(
|
||||
'.0s',
|
||||
style({
|
||||
opacity: 0,
|
||||
}),
|
||||
),
|
||||
]),
|
||||
]),
|
||||
];
|
||||
|
||||
export const routeAnimations: AnimationTriggerMetadata = trigger('routeAnimations', [
|
||||
transition('HomePage => AddPage', [
|
||||
style({ transform: 'translateX(100%)' }),
|
||||
animate('250ms ease-in-out', style({ transform: 'translateX(0%)' })),
|
||||
]),
|
||||
transition('AddPage => HomePage', [animate('250ms', style({ transform: 'translateX(100%)' }))]),
|
||||
transition('HomePage => DetailPage', [
|
||||
query(':enter, :leave', style({ position: 'absolute', left: 0, right: 0 }), {
|
||||
optional: true,
|
||||
}),
|
||||
group([
|
||||
query(
|
||||
':enter',
|
||||
[
|
||||
style({
|
||||
transform: 'translateX(20%)',
|
||||
opacity: 0.5,
|
||||
}),
|
||||
animate(
|
||||
'.35s ease-in',
|
||||
style({
|
||||
transform: 'translateX(0%)',
|
||||
opacity: 1,
|
||||
}),
|
||||
),
|
||||
],
|
||||
{
|
||||
optional: true,
|
||||
},
|
||||
),
|
||||
query(
|
||||
':leave',
|
||||
[style({ opacity: 1, width: '100%' }), animate('.35s ease-out', style({ opacity: 0 }))],
|
||||
{
|
||||
optional: true,
|
||||
},
|
||||
),
|
||||
]),
|
||||
]),
|
||||
transition('DetailPage => HomePage', [
|
||||
query(':enter, :leave', style({ position: 'absolute', left: 0, right: 0 }), {
|
||||
optional: true,
|
||||
}),
|
||||
group([
|
||||
query(
|
||||
':enter',
|
||||
[
|
||||
style({
|
||||
opacity: 0,
|
||||
}),
|
||||
animate(
|
||||
'.35s ease-out',
|
||||
style({
|
||||
opacity: 1,
|
||||
}),
|
||||
),
|
||||
],
|
||||
{
|
||||
optional: true,
|
||||
},
|
||||
),
|
||||
query(
|
||||
':leave',
|
||||
[
|
||||
style({ width: '100%', transform: 'translateX(0%)' }),
|
||||
animate('.35s ease-in', style({ transform: 'translateX(30%)', opacity: 0 })),
|
||||
],
|
||||
{
|
||||
optional: true,
|
||||
},
|
||||
),
|
||||
]),
|
||||
]),
|
||||
]);
|
@@ -1,6 +1,6 @@
|
||||
<ng-container *ngIf="(authService.user | async) || {} as user">
|
||||
<ng-container *ngIf="((['iam.read','iam.write'] | hasRole)) as iamuser$">
|
||||
<mat-toolbar class="root-header">
|
||||
<mat-toolbar @toolbar class="root-header">
|
||||
<button aria-label="Toggle sidenav" mat-icon-button (click)="drawer.toggle()">
|
||||
<mat-icon aria-label="Side nav toggle icon">menu</mat-icon>
|
||||
</button>
|
||||
@@ -53,7 +53,7 @@
|
||||
<div class="side-column">
|
||||
<div class="list">
|
||||
<ng-container *ngIf="authService.authenticationChanged | async">
|
||||
<a class="nav-item" [routerLinkActive]="['active']"
|
||||
<a @navitem class="nav-item" [routerLinkActive]="['active']"
|
||||
[routerLinkActiveOptions]="{ exact: true }" [routerLink]="['/users/me']">
|
||||
<i class="icon las la-user-circle"></i>
|
||||
<span class="label">{{ 'MENU.PERSONAL_INFO' | translate }}</span>
|
||||
@@ -64,52 +64,65 @@
|
||||
<div class="divider">
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
<a class="nav-item" [routerLinkActive]="['active']" [routerLink]="[ '/iam']">
|
||||
<a @navitem class="nav-item" [routerLinkActive]="['active']" [routerLink]="[ '/iam']">
|
||||
<i class="icon las la-gem"></i>
|
||||
<span class="label">{{'MENU.IAM' | translate}}</span>
|
||||
</a>
|
||||
</ng-container>
|
||||
|
||||
<div *ngIf="org" [@navAnimation]="org">
|
||||
<ng-template appHasRole [appHasRole]="['org.read']">
|
||||
<a class="nav-item" [routerLinkActive]="['active']" [routerLink]="[ '/org']">
|
||||
<a @navitem class="nav-item" [routerLinkActive]="['active']" [routerLink]="[ '/org']">
|
||||
<i class="icon las la-archway"></i>
|
||||
<span class="label">{{org?.name ? org.name : 'MENU.ORGANIZATION' | translate}}</span>
|
||||
<span
|
||||
class="label">{{org?.name ? org.name : 'MENU.ORGANIZATION' | translate}}</span>
|
||||
</a>
|
||||
</ng-template>
|
||||
|
||||
<ng-template appHasRole [appHasRole]="['project.read']">
|
||||
<div class="divider">
|
||||
<div @navitem class="divider">
|
||||
<div class="line"></div>
|
||||
<span>{{'MENU.PROJECTSSECTION' | translate}}</span>
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
|
||||
<a class="nav-item" [routerLinkActive]="['active']" [routerLink]="[ '/projects']">
|
||||
<a @navitem class="nav-item" [routerLinkActive]="['active']"
|
||||
[routerLink]="[ '/projects']">
|
||||
<i class="icon las la-layer-group"></i>
|
||||
<span class="label">{{org?.name ? org.name : 'MENU.ORGANIZATION' | translate}}
|
||||
{{ 'MENU.PROJECT' | translate }}</span>
|
||||
|
||||
<div class="c_label">
|
||||
<span>{{org?.name ? org.name : 'MENU.ORGANIZATION' | translate}}
|
||||
{{'MENU.PROJECT' | translate}} </span>
|
||||
<span *ngIf="ownedProjectsCount as ownedPCount"
|
||||
class="count">{{ownedPCount}}</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a class="nav-item" [routerLinkActive]="['active']" [routerLink]="[ '/granted-projects']">
|
||||
<a @navitem *ngIf="grantedProjectsCount as grantPCount" class="nav-item"
|
||||
[routerLinkActive]="['active']" [routerLink]="[ '/granted-projects']">
|
||||
<i class="icon las la-layer-group"></i>
|
||||
<span class="label">{{ 'MENU.GRANTEDPROJECT' | translate }}</span>
|
||||
<div class="c_label">
|
||||
<span>{{ 'MENU.GRANTEDPROJECT' | translate }}</span>
|
||||
<span class="count">{{grantPCount}}</span>
|
||||
</div>
|
||||
</a>
|
||||
</ng-template>
|
||||
|
||||
<ng-template appHasRole [appHasRole]="['user.read']">
|
||||
<div class="divider">
|
||||
<div @navitem class="divider">
|
||||
<div class="line"></div>
|
||||
<span class="label">
|
||||
{{ 'MENU.USERSECTION' | translate }}</span>
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
|
||||
<a class="nav-item" [routerLinkActive]="['active']" [routerLink]="[ '/users/all']"
|
||||
[routerLinkActiveOptions]="{ exact: true }">
|
||||
<a @navitem class="nav-item" [routerLinkActive]="['active']"
|
||||
[routerLink]="[ '/users/all']" [routerLinkActiveOptions]="{ exact: true }">
|
||||
<i class="icon las la-users"></i>
|
||||
<span class="label">{{ 'MENU.USER' | translate }}</span>
|
||||
</a>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<span class="fill-space"></span>
|
||||
</div>
|
||||
@@ -117,7 +130,7 @@
|
||||
</div>
|
||||
</mat-drawer>
|
||||
<mat-drawer-content class="content">
|
||||
<div *ngIf="iamuser$ | async" class="admin-line" matTooltip="IAM Administrator">
|
||||
<div @toolbar *ngIf="iamuser$ | async" class="admin-line" matTooltip="IAM Administrator">
|
||||
<span>{{'MENU.IAMADMIN' | translate}}</span>
|
||||
</div>
|
||||
<div class="router" [@routeAnimations]="prepareRoute(outlet)">
|
||||
|
@@ -1,11 +1,14 @@
|
||||
|
||||
.root-header {
|
||||
position: relative;
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
height: 60px;
|
||||
align-items: center;
|
||||
padding: 0 1rem;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
.logo {
|
||||
height: 40px;
|
||||
@@ -74,15 +77,16 @@
|
||||
.main-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 60px);
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
padding-top: 60px;
|
||||
|
||||
.sidenav {
|
||||
width: 300px;
|
||||
border-right: none;
|
||||
|
||||
.side-column {
|
||||
height: calc(100vh - 70px);
|
||||
padding-top: 60px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
@@ -121,6 +125,17 @@
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
.c_label {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.count {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #00000010;
|
||||
border-top-right-radius: 1.5rem;
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import { animate, group, query, style, transition, trigger } from '@angular/animations';
|
||||
import { BreakpointObserver } from '@angular/cdk/layout';
|
||||
import { OverlayContainer } from '@angular/cdk/overlay';
|
||||
import { ViewportScroller } from '@angular/common';
|
||||
@@ -11,9 +10,11 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { Observable, of, Subscription } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { accountCard, navAnimations, routeAnimations, toolbarAnimation } from './animations';
|
||||
import { Org, UserProfileView } from './proto/generated/auth_pb';
|
||||
import { AuthUserService } from './services/auth-user.service';
|
||||
import { AuthService } from './services/auth.service';
|
||||
import { ProjectService } from './services/project.service';
|
||||
import { ThemeService } from './services/theme.service';
|
||||
import { ToastService } from './services/toast.service';
|
||||
import { UpdateService } from './services/update.service';
|
||||
@@ -23,97 +24,10 @@ import { UpdateService } from './services/update.service';
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss'],
|
||||
animations: [
|
||||
trigger('accounts', [
|
||||
transition(':enter', [
|
||||
style({
|
||||
transform: 'scale(.9) translateY(-10%)',
|
||||
height: '200px',
|
||||
opacity: 0,
|
||||
}),
|
||||
animate(
|
||||
'.1s ease-out',
|
||||
style({
|
||||
transform: 'scale(1) translateY(0%)',
|
||||
height: '*',
|
||||
opacity: 1,
|
||||
}),
|
||||
),
|
||||
]),
|
||||
]),
|
||||
trigger('routeAnimations', [
|
||||
transition('HomePage => AddPage', [
|
||||
style({ transform: 'translateX(100%)' }),
|
||||
animate('250ms ease-in-out', style({ transform: 'translateX(0%)' })),
|
||||
]),
|
||||
transition('AddPage => HomePage', [animate('250ms', style({ transform: 'translateX(100%)' }))]),
|
||||
transition('HomePage => DetailPage', [
|
||||
query(':enter, :leave', style({ position: 'absolute', left: 0, right: 0 }), {
|
||||
optional: true,
|
||||
}),
|
||||
group([
|
||||
query(
|
||||
':enter',
|
||||
[
|
||||
style({
|
||||
transform: 'translateX(20%)',
|
||||
opacity: 0.5,
|
||||
}),
|
||||
animate(
|
||||
'.35s ease-in',
|
||||
style({
|
||||
transform: 'translateX(0%)',
|
||||
opacity: 1,
|
||||
}),
|
||||
),
|
||||
],
|
||||
{
|
||||
optional: true,
|
||||
},
|
||||
),
|
||||
query(
|
||||
':leave',
|
||||
[style({ opacity: 1, width: '100%' }), animate('.35s ease-out', style({ opacity: 0 }))],
|
||||
{
|
||||
optional: true,
|
||||
},
|
||||
),
|
||||
]),
|
||||
]),
|
||||
transition('DetailPage => HomePage', [
|
||||
query(':enter, :leave', style({ position: 'absolute', left: 0, right: 0 }), {
|
||||
optional: true,
|
||||
}),
|
||||
group([
|
||||
query(
|
||||
':enter',
|
||||
[
|
||||
style({
|
||||
opacity: 0,
|
||||
}),
|
||||
animate(
|
||||
'.35s ease-out',
|
||||
style({
|
||||
opacity: 1,
|
||||
}),
|
||||
),
|
||||
],
|
||||
{
|
||||
optional: true,
|
||||
},
|
||||
),
|
||||
query(
|
||||
':leave',
|
||||
[
|
||||
style({ width: '100%', transform: 'translateX(0%)' }),
|
||||
animate('.35s ease-in', style({ transform: 'translateX(30%)', opacity: 0 })),
|
||||
],
|
||||
{
|
||||
optional: true,
|
||||
},
|
||||
),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
toolbarAnimation,
|
||||
...navAnimations,
|
||||
accountCard,
|
||||
routeAnimations,
|
||||
],
|
||||
})
|
||||
export class AppComponent implements OnDestroy {
|
||||
@@ -135,6 +49,10 @@ export class AppComponent implements OnDestroy {
|
||||
public orgLoading: boolean = false;
|
||||
|
||||
public showProjectSection: boolean = false;
|
||||
|
||||
public grantedProjectsCount: number = 0;
|
||||
public ownedProjectsCount: number = 0;
|
||||
|
||||
private authSub: Subscription = new Subscription();
|
||||
private orgSub: Subscription = new Subscription();
|
||||
|
||||
@@ -147,6 +65,7 @@ export class AppComponent implements OnDestroy {
|
||||
public overlayContainer: OverlayContainer,
|
||||
private themeService: ThemeService,
|
||||
public userService: AuthUserService,
|
||||
private projectService: ProjectService,
|
||||
public matIconRegistry: MatIconRegistry,
|
||||
public domSanitizer: DomSanitizer,
|
||||
private toast: ToastService,
|
||||
@@ -218,9 +137,12 @@ export class AppComponent implements OnDestroy {
|
||||
'mdi_pin',
|
||||
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/pin.svg'),
|
||||
);
|
||||
this.getProjectCount();
|
||||
|
||||
this.orgSub = this.authService.activeOrgChanged.subscribe(org => {
|
||||
this.org = org;
|
||||
|
||||
this.getProjectCount();
|
||||
});
|
||||
|
||||
this.authSub = this.authService.authenticationChanged.subscribe((authenticated) => {
|
||||
@@ -289,5 +211,15 @@ export class AppComponent implements OnDestroy {
|
||||
this.authService.setActiveOrg(org);
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
|
||||
private async getProjectCount(): Promise<any> {
|
||||
this.ownedProjectsCount = await this.projectService.SearchProjects(0, 0).then(res => {
|
||||
return res.toObject().totalResult;
|
||||
});
|
||||
|
||||
this.grantedProjectsCount = await this.projectService.SearchGrantedProjects(0, 0).then(res => {
|
||||
return res.toObject().totalResult;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
|
||||
import { AuthUserService } from 'src/app/services/auth-user.service';
|
||||
import { AuthService } from 'src/app/services/auth.service';
|
||||
|
||||
|
||||
@Directive({
|
||||
@@ -10,7 +10,7 @@ export class HasRoleDirective {
|
||||
private hasView: boolean = false;
|
||||
@Input() public set appHasRole(roles: string[]) {
|
||||
if (roles && roles.length > 0) {
|
||||
this.userService.isAllowed(roles).subscribe(isAllowed => {
|
||||
this.authService.isAllowed(roles).subscribe(isAllowed => {
|
||||
if (isAllowed && !this.hasView) {
|
||||
this.viewContainerRef.clear();
|
||||
this.viewContainerRef.createEmbeddedView(this.templateRef);
|
||||
@@ -23,7 +23,7 @@ export class HasRoleDirective {
|
||||
}
|
||||
|
||||
constructor(
|
||||
private userService: AuthUserService,
|
||||
private authService: AuthService,
|
||||
protected templateRef: TemplateRef<any>,
|
||||
protected viewContainerRef: ViewContainerRef,
|
||||
) { }
|
||||
|
@@ -2,19 +2,19 @@ import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { AuthUserService } from '../services/auth-user.service';
|
||||
import { AuthService } from '../services/auth.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class RoleGuard implements CanActivate {
|
||||
|
||||
constructor(private userService: AuthUserService) { }
|
||||
constructor(private authService: AuthService) { }
|
||||
|
||||
public canActivate(
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
): Observable<boolean> {
|
||||
return this.userService.isAllowed(route.data['roles'], true);
|
||||
return this.authService.isAllowed(route.data['roles'], true);
|
||||
}
|
||||
}
|
||||
|
@@ -15,9 +15,5 @@
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
color: white;
|
||||
|
||||
// &.active:hover {
|
||||
// border: 2px solid #8795a1;
|
||||
// }
|
||||
}
|
||||
}
|
@@ -1,9 +1,6 @@
|
||||
@import '~@angular/material/theming';
|
||||
|
||||
@mixin card-theme($theme) {
|
||||
$accent: map-get($theme, accent);
|
||||
$background: map-get($theme, background);
|
||||
$background-color: mat-color($background, card);
|
||||
$primary: map-get($theme, primary);
|
||||
$primary-color: mat-color($primary, 500);
|
||||
$primary-dark: mat-color($primary, A800);
|
||||
@@ -17,7 +14,6 @@
|
||||
box-sizing: border-box;
|
||||
border-radius: .5rem;
|
||||
outline: none;
|
||||
// box-shadow: 0px 2px 1px -1px rgba(0, 0, 0, 0.1), 0px 1px 1px 0px rgba(0, 0, 0, 0.1), 0px 1px 3px 0px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.selection-icon {
|
||||
opacity: 0;
|
@@ -1,7 +1,8 @@
|
||||
<span class="header">{{ 'CHANGES.LISTTITLE' | translate }}</span>
|
||||
|
||||
<div class="scroll-container" appScrollable (scrollPosition)="scrollHandler($event)">
|
||||
<li class="item change-item-back" *ngFor="let event of data | async">
|
||||
<div class="scroll-container" appScrollable (scrollPosition)="scrollHandler($event)"
|
||||
[@cardAnimation]="data && (data | async)?.length">
|
||||
<li class="item change-item-back" *ngFor="let event of data | async" @animate>
|
||||
<span class="seq">
|
||||
{{event.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'}}
|
||||
</span>
|
||||
|
@@ -1,3 +1,6 @@
|
||||
@import '~@angular/material/theming';
|
||||
|
||||
|
||||
.header {
|
||||
display: block;
|
||||
margin-bottom: 1rem;
|
||||
@@ -5,7 +8,9 @@
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.scroll-container {
|
||||
@mixin changes-theme($theme) {
|
||||
|
||||
.scroll-container {
|
||||
max-height: 60vh;
|
||||
overflow-y: scroll;
|
||||
|
||||
@@ -31,6 +36,14 @@
|
||||
overflow-x: auto;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
$primary: map-get($theme, primary);
|
||||
$primary-dark: mat-color($primary, A800);
|
||||
|
||||
&.change-item-back {
|
||||
background-color: rgba($primary-dark, 0.93);
|
||||
transition: background-color .4s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
.sp-wrapper {
|
||||
@@ -43,4 +56,5 @@
|
||||
font-size: 12px;
|
||||
color: #8795a1;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +1,4 @@
|
||||
import { animate, animateChild, keyframes, query, stagger, style, transition, trigger } from '@angular/animations';
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { BehaviorSubject, from, Observable, of } from 'rxjs';
|
||||
import { catchError, scan, take, tap } from 'rxjs/operators';
|
||||
@@ -16,6 +17,22 @@ export enum ChangeType {
|
||||
selector: 'app-changes',
|
||||
templateUrl: './changes.component.html',
|
||||
styleUrls: ['./changes.component.scss'],
|
||||
animations: [
|
||||
trigger('cardAnimation', [
|
||||
transition('* => *', [
|
||||
query('@animate', stagger('50ms', animateChild()), { optional: true }),
|
||||
]),
|
||||
]),
|
||||
trigger('animate', [
|
||||
transition(':enter', [
|
||||
animate('.2s ease-in', keyframes([
|
||||
style({ opacity: 0 }),
|
||||
style({ opacity: .5, transform: 'scale(1.02)' }),
|
||||
style({ opacity: 1, transform: 'scale(1)' }),
|
||||
])),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class ChangesComponent implements OnInit {
|
||||
@Input() public changeType: ChangeType = ChangeType.USER;
|
||||
|
@@ -2,12 +2,12 @@
|
||||
<span class="header">{{ title }}</span>
|
||||
<span class="sub-header">{{ description }}</span>
|
||||
<div class="people">
|
||||
<div class="img-list">
|
||||
<div class="img-list" [@cardAnimation]="totalResult">
|
||||
<mat-spinner diameter="20" *ngIf="loading"></mat-spinner>
|
||||
|
||||
<ng-container *ngIf="totalResult < 10; else compact">
|
||||
<ng-container *ngFor="let member of membersSubject | async; index as i">
|
||||
<div (click)="emitShowDetail()" class="avatar-circle"
|
||||
<div @animate (click)="emitShowDetail()" class="avatar-circle"
|
||||
matTooltip="{{ member.email }} | {{member.rolesList?.join(' ')}}"
|
||||
[ngStyle]="{'z-index': 100 - i}">
|
||||
<app-avatar *ngIf="member && (member.displayName || (member.firstName && member.lastName))"
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { animate, animateChild, query, stagger, style, transition, trigger } from '@angular/animations';
|
||||
import { animate, animateChild, keyframes, query, stagger, style, transition, trigger } from '@angular/animations';
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
@@ -7,17 +7,18 @@ import { BehaviorSubject } from 'rxjs';
|
||||
templateUrl: './contributors.component.html',
|
||||
styleUrls: ['./contributors.component.scss'],
|
||||
animations: [
|
||||
trigger('list', [
|
||||
transition(':enter', [
|
||||
query('@animate',
|
||||
stagger(80, animateChild()),
|
||||
),
|
||||
trigger('cardAnimation', [
|
||||
transition('* => *', [
|
||||
query('@animate', stagger('40ms', animateChild()), { optional: true }),
|
||||
]),
|
||||
]),
|
||||
trigger('animate', [
|
||||
transition(':enter', [
|
||||
style({ opacity: 0, transform: 'translateX(100%)' }),
|
||||
animate('100ms', style({ opacity: 1, transform: 'translateX(0)' })),
|
||||
animate('.2s ease-in', keyframes([
|
||||
style({ opacity: 0, offset: 0 }),
|
||||
style({ opacity: .5, transform: 'scale(1.05)', offset: 0.3 }),
|
||||
style({ opacity: 1, transform: 'scale(1)', offset: 1 }),
|
||||
])),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
|
@@ -51,7 +51,7 @@
|
||||
<ng-container matColumnDef="creationDate">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.ROLE.CREATIONDATE' | translate }} </th>
|
||||
<td (click)="openDetailDialog(role)" mat-cell *matCellDef="let role">
|
||||
<span>{{role.creationDate | timestampToDate | date: 'dd. MMM, HH:mm' }}</span>
|
||||
<span>{{role.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
@@ -16,6 +16,7 @@ import { RouterModule } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe.module';
|
||||
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe.module';
|
||||
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe.module';
|
||||
|
||||
import { RefreshTableModule } from '../refresh-table/refresh-table.module';
|
||||
@@ -46,6 +47,7 @@ import { ProjectRolesComponent } from './project-roles.component';
|
||||
MatMenuModule,
|
||||
TimestampToDatePipeModule,
|
||||
RefreshTableModule,
|
||||
LocalizedDatePipeModule,
|
||||
],
|
||||
exports: [
|
||||
ProjectRolesComponent,
|
||||
|
@@ -51,13 +51,13 @@
|
||||
<ng-container matColumnDef="creationDate">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.CREATIONDATE' | translate }} </th>
|
||||
<td class="pointer" mat-cell *matCellDef="let grant">
|
||||
{{grant.creationDate | timestampToDate | date: 'dd. MMM, HH:mm' }} </td>
|
||||
{{grant.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="changeDate">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.CHANGEDATE' | translate }} </th>
|
||||
<td class="pointer" mat-cell *matCellDef="let grant">
|
||||
{{grant.changeDate | timestampToDate | date: 'dd. MMM, HH:mm' }} </td>
|
||||
{{grant.changeDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="roleNamesList">
|
||||
|
@@ -14,6 +14,7 @@ import { RouterModule } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe.module';
|
||||
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe.module';
|
||||
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe.module';
|
||||
|
||||
import { AvatarModule } from '../avatar/avatar.module';
|
||||
@@ -43,6 +44,7 @@ import { UserGrantsComponent } from './user-grants.component';
|
||||
HasRolePipeModule,
|
||||
TimestampToDatePipeModule,
|
||||
RefreshTableModule,
|
||||
LocalizedDatePipeModule,
|
||||
],
|
||||
exports: [
|
||||
UserGrantsComponent,
|
||||
|
@@ -14,12 +14,12 @@
|
||||
<div class="text-part">
|
||||
<span *ngIf="item.changeDate" class="top">{{'PROJECT.PAGES.LASTMODIFIED' | translate}}
|
||||
{{
|
||||
item.changeDate | timestampToDate | date: 'EEE dd. MMM, HH:mm'
|
||||
item.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
|
||||
}}</span>
|
||||
<span class="name" *ngIf="item.projectName">{{ item.projectName }}</span>
|
||||
<span class="description" *ngIf="item.resourceOwnerName">{{item.resourceOwnerName}}</span>
|
||||
<span *ngIf="item.changeDate" class="created">{{'PROJECT.PAGES.CREATEDON' | translate}}
|
||||
{{ item.creationDate | timestampToDate | date: 'EEE dd. MMM, HH:mm' }}</span>
|
||||
{{ item.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span>
|
||||
<span class="fill-space"></span>
|
||||
<div class="icons">
|
||||
</div>
|
||||
@@ -41,12 +41,12 @@
|
||||
<div class="text-part">
|
||||
<span *ngIf="item.changeDate" class="top">{{'PROJECT.PAGES.LASTMODIFIED' | translate}}
|
||||
{{
|
||||
item.changeDate | timestampToDate | date: 'EEE dd. MMM, HH:mm'
|
||||
item.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
|
||||
}}</span>
|
||||
<span class="name" *ngIf="item.projectName">{{ item.projectName }}</span>
|
||||
<span class="description" *ngIf="item.resourceOwnerName">{{item.resourceOwnerName}}</span>
|
||||
<span *ngIf="item.changeDate" class="created">{{'PROJECT.PAGES.CREATEDON' | translate}}
|
||||
{{ item.creationDate | timestampToDate | date: 'EEE dd. MMM, HH:mm' }}</span>
|
||||
{{ item.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span>
|
||||
<span class="fill-space"></span>
|
||||
<div class="icons">
|
||||
</div>
|
||||
|
@@ -72,7 +72,7 @@
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.CREATIONDATE' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let project">
|
||||
<span
|
||||
*ngIf="project.creationDate">{{project.creationDate | timestampToDate | date: 'EEE dd. MMM, HH:mm'}}</span>
|
||||
*ngIf="project.creationDate">{{project.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'}}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.CHANGEDATE' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let project">
|
||||
<span
|
||||
*ngIf="project.changeDate">{{project.changeDate | timestampToDate | date: 'EEE dd. MMM, HH:mm'}}</span>
|
||||
*ngIf="project.changeDate">{{project.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'}}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
@@ -25,6 +25,7 @@ import { ProjectRolesModule } from 'src/app/modules/project-roles/project-roles.
|
||||
import { SharedModule } from 'src/app/modules/shared/shared.module';
|
||||
import { UserGrantsModule } from 'src/app/modules/user-grants/user-grants.module';
|
||||
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe.module';
|
||||
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe.module';
|
||||
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe.module';
|
||||
|
||||
import { GrantedProjectDetailComponent } from './granted-project-detail/granted-project-detail.component';
|
||||
@@ -70,6 +71,7 @@ import { GrantedProjectsComponent } from './granted-projects.component';
|
||||
TranslateModule,
|
||||
TimestampToDatePipeModule,
|
||||
SharedModule,
|
||||
LocalizedDatePipeModule,
|
||||
MemberCreateDialogModule,
|
||||
],
|
||||
})
|
||||
|
@@ -23,6 +23,7 @@ import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.
|
||||
import { UserGrantsModule } from 'src/app/modules/user-grants/user-grants.module';
|
||||
import { WarnDialogModule } from 'src/app/modules/warn-dialog/warn-dialog.module';
|
||||
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe.module';
|
||||
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe.module';
|
||||
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe.module';
|
||||
|
||||
import { ApplicationGridComponent } from './application-grid/application-grid.component';
|
||||
@@ -65,6 +66,7 @@ import { ProjectGrantsComponent } from './project-grants/project-grants.componen
|
||||
MetaLayoutModule,
|
||||
RefreshTableModule,
|
||||
MemberCreateDialogModule,
|
||||
LocalizedDatePipeModule,
|
||||
],
|
||||
})
|
||||
export class OwnedProjectDetailModule { }
|
||||
|
@@ -43,14 +43,14 @@
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.CREATIONDATE' | translate }} </th>
|
||||
<td [routerLink]="['/projects',grant.projectId,'grant', grant.id]" class="pointer" mat-cell
|
||||
*matCellDef="let grant">
|
||||
{{grant.creationDate | timestampToDate | date: 'dd. MMM, HH:mm' }} </td>
|
||||
{{grant.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="changeDate">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.CHANGEDATE' | translate }} </th>
|
||||
<td [routerLink]="['/projects',grant.projectId,'grant', grant.id]" class="pointer" mat-cell
|
||||
*matCellDef="let grant">
|
||||
{{grant.changeDate | timestampToDate | date: 'dd. MMM, HH:mm' }} </td>
|
||||
{{grant.changeDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }} </td>
|
||||
|
||||
</ng-container>
|
||||
|
||||
|
@@ -15,13 +15,13 @@
|
||||
<div class="text-part">
|
||||
<span *ngIf="item.changeDate" class="top">{{'PROJECT.PAGES.LASTMODIFIED' | translate}}
|
||||
{{
|
||||
item.changeDate | timestampToDate | date: 'EEE dd. MMM, HH:mm'
|
||||
item.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
|
||||
}}</span>
|
||||
<span class="name" *ngIf="item.name">{{ item.name }}</span>
|
||||
|
||||
<span *ngIf="item.changeDate" class="created">{{'PROJECT.PAGES.CREATEDON' | translate}}
|
||||
{{
|
||||
item.creationDate | timestampToDate | date: 'EEE dd. MMM, HH:mm'
|
||||
item.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
|
||||
}}</span>
|
||||
<span class="fill-space"></span>
|
||||
<div class="icons">
|
||||
@@ -44,13 +44,13 @@
|
||||
<div class="text-part">
|
||||
<span *ngIf="item.changeDate" class="top">{{'PROJECT.PAGES.LASTMODIFIED' | translate}}
|
||||
{{
|
||||
item.changeDate | timestampToDate | date: 'EEE dd. MMM, HH:mm'
|
||||
item.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
|
||||
}}</span>
|
||||
<span class="name" *ngIf="item.name">{{ item.name }}</span>
|
||||
|
||||
<span *ngIf="item.changeDate" class="created">{{'PROJECT.PAGES.CREATEDON' | translate}}
|
||||
{{
|
||||
item.creationDate | timestampToDate | date: 'EEE dd. MMM, HH:mm'
|
||||
item.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
|
||||
}}</span>
|
||||
<span class="fill-space"></span>
|
||||
<div class="icons">
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { animate, animateChild, query, stagger, style, transition, trigger } from '@angular/animations';
|
||||
import { animate, animateChild, keyframes, query, stagger, style, transition, trigger } from '@angular/animations';
|
||||
import { SelectionModel } from '@angular/cdk/collections';
|
||||
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
@@ -10,21 +10,25 @@ import { AuthService } from 'src/app/services/auth.service';
|
||||
templateUrl: './owned-project-grid.component.html',
|
||||
styleUrls: ['./owned-project-grid.component.scss'],
|
||||
animations: [
|
||||
trigger('list', [
|
||||
transition(':enter', [
|
||||
query('@animate',
|
||||
stagger(100, animateChild()),
|
||||
),
|
||||
trigger('cardAnimation', [
|
||||
transition('* => *', [
|
||||
query('@animate', stagger('100ms', animateChild()), { optional: true }),
|
||||
]),
|
||||
]),
|
||||
trigger('animate', [
|
||||
transition(':enter', [
|
||||
style({ opacity: 0, transform: 'translateY(-100%)' }),
|
||||
animate('100ms', style({ opacity: 1, transform: 'translateY(0)' })),
|
||||
animate('.2s ease-in', keyframes([
|
||||
style({ opacity: 0, transform: 'translateY(-50%)', offset: 0 }),
|
||||
style({ opacity: .5, transform: 'translateY(-10px) scale(1.1)', offset: 0.3 }),
|
||||
style({ opacity: 1, transform: 'translateY(0)', offset: 1 }),
|
||||
])),
|
||||
]),
|
||||
transition(':leave', [
|
||||
style({ opacity: 1, transform: 'translateY(0)' }),
|
||||
animate('100ms', style({ opacity: 0, transform: 'translateY(100%)' })),
|
||||
animate('.2s ease-out', keyframes([
|
||||
style({ opacity: 1, transform: 'scale(1.1)', offset: 0 }),
|
||||
style({ opacity: .5, transform: 'scale(.5)', offset: 0.3 }),
|
||||
style({ opacity: 0, transform: 'scale(0)', offset: 1 }),
|
||||
])),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
|
@@ -61,7 +61,7 @@
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.CREATIONDATE' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let project">
|
||||
<span
|
||||
*ngIf="project.creationDate">{{project.creationDate | timestampToDate | date: 'EEE dd. MMM, HH:mm'}}</span>
|
||||
*ngIf="project.creationDate">{{project.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'}}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.CHANGEDATE' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let project">
|
||||
<span
|
||||
*ngIf="project.changeDate">{{project.changeDate | timestampToDate | date: 'EEE dd. MMM, HH:mm'}}</span>
|
||||
*ngIf="project.changeDate">{{project.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'}}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
@@ -88,7 +88,7 @@ export class OwnedProjectListComponent implements OnInit, OnDestroy {
|
||||
this.projectService.SearchProjects(limit, offset).then(res => {
|
||||
this.ownedProjectList = res.toObject().resultList;
|
||||
this.totalResult = res.toObject().totalResult;
|
||||
if (this.totalResult > 5) {
|
||||
if (this.totalResult > 10) {
|
||||
this.grid = false;
|
||||
}
|
||||
this.dataSource.data = this.ownedProjectList;
|
||||
|
@@ -20,6 +20,7 @@ import { CardModule } from 'src/app/modules/card/card.module';
|
||||
import { SharedModule } from 'src/app/modules/shared/shared.module';
|
||||
import { UserGrantsModule } from 'src/app/modules/user-grants/user-grants.module';
|
||||
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe.module';
|
||||
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe.module';
|
||||
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe.module';
|
||||
|
||||
import { OwnedProjectGridComponent } from './owned-project-list/owned-project-grid/owned-project-grid.component';
|
||||
@@ -58,6 +59,7 @@ import { OwnedProjectsComponent } from './owned-projects.component';
|
||||
MatSortModule,
|
||||
HasRolePipeModule,
|
||||
TimestampToDatePipeModule,
|
||||
LocalizedDatePipeModule,
|
||||
SharedModule,
|
||||
],
|
||||
})
|
||||
|
@@ -4,7 +4,7 @@
|
||||
$primary: map-get($theme, primary);
|
||||
$primary-dark: mat-color($primary, A800);
|
||||
|
||||
.theme-conent, .theme-app , .crescent {
|
||||
.theme-conent, .crescent {
|
||||
background-color: $primary-dark;
|
||||
transition: background-color .4s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
}
|
||||
|
@@ -102,8 +102,8 @@ label {
|
||||
}
|
||||
|
||||
[type="checkbox"]:checked + .theme-app{
|
||||
background-color: $dark-background;
|
||||
color: $light-background;
|
||||
background-color: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
[type="checkbox"]:checked + .theme-app .crescent{
|
||||
|
@@ -38,10 +38,8 @@
|
||||
|
||||
<ng-template appHasRole [appHasRole]="['user.read', 'user.read:'+user?.id]">
|
||||
<app-card title="{{ 'USER.PROFILE.TITLE' | translate }}">
|
||||
<app-detail-form
|
||||
*ngIf="((authUserService.isAllowed(['user.write:' + user?.id, 'user.write']) | async) || false) as canwrite"
|
||||
[disabled]="canwrite" [genders]="genders" [languages]="languages" [profile]="user"
|
||||
(submitData)="saveProfile($event)">
|
||||
<app-detail-form [disabled]="(['user.write:' + user?.id, 'user.write'] | hasRole | async) == false"
|
||||
[genders]="genders" [languages]="languages" [profile]="user" (submitData)="saveProfile($event)">
|
||||
</app-detail-form>
|
||||
</app-card>
|
||||
</ng-template>
|
||||
|
@@ -44,7 +44,6 @@ export class UserMfaComponent implements OnInit, OnDestroy {
|
||||
|
||||
public getOTP(): void {
|
||||
this.mgmtUserService.getUserMfas(this.user.id).then(mfas => {
|
||||
console.log(mfas.toObject().mfasList);
|
||||
this.dataSource = new MatTableDataSource(mfas.toObject().mfasList);
|
||||
this.dataSource.sort = this.sort;
|
||||
}).catch(error => {
|
||||
|
@@ -1,16 +1,16 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { AuthUserService } from '../services/auth-user.service';
|
||||
import { AuthService } from '../services/auth.service';
|
||||
|
||||
@Pipe({
|
||||
name: 'hasRole',
|
||||
})
|
||||
export class HasRolePipe implements PipeTransform {
|
||||
|
||||
constructor(private authUserService: AuthUserService) { }
|
||||
constructor(private authService: AuthService) { }
|
||||
|
||||
public transform(values: string[], each: boolean = false): Observable<boolean> {
|
||||
return this.authUserService.isAllowed(values, each);
|
||||
return this.authService.isAllowed(values, each);
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Empty } from 'google-protobuf/google/protobuf/empty_pb';
|
||||
import { Metadata } from 'grpc-web';
|
||||
import { from, Observable, of } from 'rxjs';
|
||||
import { catchError, switchMap } from 'rxjs/operators';
|
||||
|
||||
import { AuthServicePromiseClient } from '../proto/generated/auth_grpc_web_pb';
|
||||
import {
|
||||
@@ -11,6 +9,7 @@ import {
|
||||
Gender,
|
||||
MfaOtpResponse,
|
||||
MultiFactors,
|
||||
MyPermissions,
|
||||
MyProjectOrgSearchQuery,
|
||||
MyProjectOrgSearchRequest,
|
||||
MyProjectOrgSearchResponse,
|
||||
@@ -38,8 +37,6 @@ import { GrpcService, RequestFactory, ResponseMapper } from './grpc.service';
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AuthUserService {
|
||||
private _roleCache: string[] = [];
|
||||
|
||||
constructor(private readonly grpcClient: GrpcService,
|
||||
private grpcBackendService: GrpcBackendService,
|
||||
) { }
|
||||
@@ -75,7 +72,6 @@ export class AuthUserService {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public async GetMyUser(): Promise<UserView> {
|
||||
return await this.request(
|
||||
c => c.getMyUser,
|
||||
@@ -175,7 +171,7 @@ export class AuthUserService {
|
||||
);
|
||||
}
|
||||
|
||||
private async getMyzitadelPermissions(): Promise<any> {
|
||||
public async GetMyzitadelPermissions(): Promise<MyPermissions> {
|
||||
return await this.request(
|
||||
c => c.getMyZitadelPermissions,
|
||||
new Empty(),
|
||||
@@ -183,19 +179,6 @@ export class AuthUserService {
|
||||
);
|
||||
}
|
||||
|
||||
public GetMyzitadelPermissions(): Observable<any> {
|
||||
return from(this.getMyzitadelPermissions());
|
||||
}
|
||||
|
||||
public hasRoles(userRoles: string[], requestedRoles: string[], each: boolean = false): boolean {
|
||||
return each ?
|
||||
requestedRoles.every(role => userRoles.includes(role)) :
|
||||
requestedRoles.findIndex(role => {
|
||||
return userRoles.findIndex(i => i.includes(role)) > -1;
|
||||
// return userRoles.includes(role);
|
||||
}) > -1;
|
||||
}
|
||||
|
||||
public async GetMyUserPhone(): Promise<UserPhone> {
|
||||
// return this.grpcClient.auth.getMyUserPhone(new Empty());
|
||||
return await this.request(
|
||||
@@ -312,31 +295,4 @@ export class AuthUserService {
|
||||
f => f,
|
||||
);
|
||||
}
|
||||
|
||||
public isAllowed(roles: string[], each: boolean = false): Observable<boolean> {
|
||||
if (roles && roles.length > 0) {
|
||||
if (this._roleCache.length > 0) {
|
||||
return of(this.hasRoles(this._roleCache, roles));
|
||||
}
|
||||
|
||||
return this.GetMyzitadelPermissions().pipe(
|
||||
switchMap(response => {
|
||||
let userRoles = [];
|
||||
if (response.toObject().permissionsList) {
|
||||
userRoles = response.toObject().permissionsList;
|
||||
} else {
|
||||
userRoles = ['user.resourceowner'];
|
||||
}
|
||||
this._roleCache = userRoles;
|
||||
return of(this.hasRoles(userRoles, roles, each));
|
||||
}),
|
||||
catchError((err) => {
|
||||
return of(false);
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
return of(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { AuthConfig, OAuthService } from 'angular-oauth2-oidc';
|
||||
import { BehaviorSubject, from, merge, Observable, of, Subject } from 'rxjs';
|
||||
import { catchError, filter, map, mergeMap, take, timeout } from 'rxjs/operators';
|
||||
import { catchError, filter, finalize, first, map, mergeMap, switchMap, take, timeout } from 'rxjs/operators';
|
||||
|
||||
import { Org, UserProfileView } from '../proto/generated/auth_pb';
|
||||
import { AuthUserService } from './auth-user.service';
|
||||
@@ -22,6 +22,8 @@ export class AuthService {
|
||||
boolean
|
||||
> = new BehaviorSubject(this.authenticated);
|
||||
|
||||
private zitadelPermissions: BehaviorSubject<string[]> = new BehaviorSubject(['user.resourceowner']);
|
||||
|
||||
constructor(
|
||||
private grpcService: GrpcService,
|
||||
private config: AuthConfig,
|
||||
@@ -44,9 +46,50 @@ export class AuthService {
|
||||
).pipe(
|
||||
take(1),
|
||||
mergeMap(token => {
|
||||
return from(this.userService.GetMyUserProfile()).pipe(map(userprofile => userprofile.toObject()));
|
||||
console.log(token);
|
||||
return from(this.userService.GetMyUserProfile().then(userprofile => userprofile.toObject()));
|
||||
}),
|
||||
finalize(() => {
|
||||
this.loadPermissions();
|
||||
}),
|
||||
);
|
||||
|
||||
this.activeOrgChanged.subscribe(() => {
|
||||
console.log('org change');
|
||||
this.loadPermissions();
|
||||
});
|
||||
}
|
||||
|
||||
private loadPermissions(): void {
|
||||
console.log('load permissions');
|
||||
merge([
|
||||
// this.authenticationChanged,
|
||||
this.activeOrgChanged.pipe(map(org => !!org)),
|
||||
]).pipe(
|
||||
first(),
|
||||
switchMap(() => from(this.userService.GetMyzitadelPermissions())),
|
||||
map(rolesResp => rolesResp.toObject().permissionsList),
|
||||
).subscribe(roles => this.zitadelPermissions.next(roles));
|
||||
}
|
||||
|
||||
public isAllowed(roles: string[], each: boolean = false): Observable<boolean> {
|
||||
if (roles && roles.length > 0) {
|
||||
return this.zitadelPermissions.pipe(switchMap(zroles => {
|
||||
return of(this.hasRoles(zroles, roles, each));
|
||||
}));
|
||||
} else {
|
||||
return of(false);
|
||||
}
|
||||
}
|
||||
|
||||
public hasRoles(userRoles: string[], requestedRoles: string[], each: boolean = false): boolean {
|
||||
// console.log('has', userRoles);
|
||||
// console.log('needs', requestedRoles);
|
||||
return each ?
|
||||
requestedRoles.every(role => userRoles.includes(role)) :
|
||||
requestedRoles.findIndex(role => {
|
||||
return userRoles.findIndex(i => i.includes(role)) > -1;
|
||||
}) > -1;
|
||||
}
|
||||
|
||||
public get authenticated(): boolean {
|
||||
|
@@ -1,8 +1,8 @@
|
||||
@import './styles/card';
|
||||
@import 'src/app/modules/card/card';
|
||||
@import './styles/table';
|
||||
@import './styles/sidenav-list';
|
||||
@import 'src/app/modules/avatar/avatar.component';
|
||||
@import './styles/changes';
|
||||
@import 'src/app/modules/changes/changes.component';
|
||||
@import 'src/app/pages/projects/owned-projects/owned-project-detail/application-grid/application-grid.component';
|
||||
@import './styles/meta';
|
||||
@import 'src/app/pages/users/user-detail/auth-user-detail/theme-setting/theme-card';
|
||||
|
@@ -1,11 +0,0 @@
|
||||
@import '~@angular/material/theming';
|
||||
|
||||
@mixin changes-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
$primary-dark: mat-color($primary, A800);
|
||||
|
||||
.change-item-back {
|
||||
background-color: rgba($primary-dark, 0.93);
|
||||
transition: background-color .4s ease-in-out;
|
||||
}
|
||||
}
|
@@ -6,9 +6,9 @@
|
||||
$primary-color: mat-color($primary, 500);
|
||||
$accent-color: mat-color($accent, 500);
|
||||
$primary-dark: mat-color($primary, A900);
|
||||
$inverse-color: mat-color($primary, A600);
|
||||
$sec-dark: mat-color($primary, A800);
|
||||
|
||||
|
||||
.mat-menu-item {
|
||||
&.show-all {
|
||||
height: 2rem;
|
||||
@@ -30,6 +30,15 @@
|
||||
color: $primary-color !important;
|
||||
background-color: rgba($color: $primary-color, $alpha: 0.1) !important;
|
||||
}
|
||||
|
||||
.c_label {
|
||||
.count {
|
||||
background-color: $primary-color;
|
||||
padding: 3px 6px;
|
||||
border-radius: 50vw;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mat-menu-content, .mat-menu-panel {
|
||||
|
Reference in New Issue
Block a user