mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-12 17:15:15 +00:00
fix(console): user create loading state and i18n, improved app create stepper, invalid token dialog, cleanup, new home (#509)
* 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 * Update console/src/assets/i18n/en.json Co-authored-by: Florian Forster <[email protected]> * Update console/src/assets/i18n/de.json Co-authored-by: Florian Forster <[email protected]> * Update console/src/assets/i18n/de.json Co-authored-by: Florian Forster <[email protected]> * Update console/src/assets/i18n/de.json Co-authored-by: Florian Forster <[email protected]> Co-authored-by: Florian Forster <[email protected]>
This commit is contained in:
co-authored by
Florian Forster
parent
c105bf483b
commit
af60b88997
@@ -41,29 +41,13 @@
|
||||
</form>
|
||||
</mat-step>
|
||||
|
||||
<mat-step [editable]="true">
|
||||
<ng-template matStepLabel>{{'APP.OIDC.GRANTSECTION' | translate}}</ng-template>
|
||||
|
||||
<p class="step-title">{{'APP.OIDC.GRANTTITLE' | translate}}</p>
|
||||
|
||||
<div class="checkbox-container">
|
||||
<mat-checkbox (change)="changeGrant()" *ngFor="let granttype of oidcGrantTypes" color="primary"
|
||||
[(ngModel)]="granttype.checked" [disabled]="granttype.disabled">
|
||||
{{'APP.OIDC.GRANT'+granttype.type | translate}}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button mat-stroked-button color="primary" matStepperPrevious>{{'ACTIONS.BACK' | translate}}</button>
|
||||
<button mat-raised-button color="primary" matStepperNext>{{'ACTIONS.CONTINUE' | translate}}</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
|
||||
<mat-step [editable]="true" *ngIf="!grantTypeChecked(OIDCGrantType.OIDCGRANTTYPE_AUTHORIZATION_CODE)">
|
||||
<mat-step [editable]="true"
|
||||
*ngIf="oidcApp.applicationType === OIDCApplicationType.OIDCAPPLICATIONTYPE_USER_AGENT">
|
||||
<ng-template matStepLabel>{{'APP.OIDC.RESPONSESECTION' | translate}}</ng-template>
|
||||
<div class="checkbox-container">
|
||||
<mat-checkbox *ngFor="let responsetype of oidcResponseTypes | keyvalue" (change)="changeResponseType()"
|
||||
color="primary" [(ngModel)]="responsetype.checked">
|
||||
{{'APP.OIDC.RESPONSE'+responsetype.key | translate}}
|
||||
<mat-checkbox *ngFor="let responsetype of oidcResponseTypes" (change)="changeResponseType()"
|
||||
color="primary" [(ngModel)]="responsetype.checked" [disabled]="responsetype.disabled">
|
||||
{{'APP.OIDC.RESPONSE'+responsetype.type | translate}}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div class="actions">
|
||||
@@ -72,8 +56,8 @@
|
||||
</div>
|
||||
</mat-step>
|
||||
|
||||
<mat-step [stepControl]="secondFormGroup" [editable]="true"
|
||||
*ngIf="applicationType?.value !== OIDCApplicationType.OIDCAPPLICATIONTYPE_NATIVE">
|
||||
<mat-step *ngIf="oidcApp.applicationType === OIDCApplicationType.OIDCAPPLICATIONTYPE_WEB"
|
||||
[stepControl]="secondFormGroup" [editable]="true">
|
||||
<form [formGroup]="secondFormGroup">
|
||||
<ng-template matStepLabel>{{'APP.OIDC.AUTHMETHODSECTION' | translate}}</ng-template>
|
||||
|
||||
@@ -157,19 +141,32 @@
|
||||
<span class="left">
|
||||
{{ 'APP.GRANT' | translate }}
|
||||
</span>
|
||||
<span class="right">
|
||||
<span *ngFor="let element of oidcApp.grantTypesList">
|
||||
<span class="right" *ngIf="oidcApp.grantTypesList?.length > 0">
|
||||
[<span *ngFor="let element of oidcApp.grantTypesList; index as i">
|
||||
{{'APP.OIDC.GRANT'+element | translate}}
|
||||
</span>
|
||||
{{i < oidcApp.grantTypesList.length - 1 ? ', ': ''}}
|
||||
</span>]
|
||||
</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="left">
|
||||
{{ 'APP.OIDC.RESPONSE' | translate }}
|
||||
</span>
|
||||
<span class="right" *ngIf="oidcApp.responseTypesList?.length > 0">
|
||||
[<span *ngFor="let element of oidcApp.responseTypesList; index as i">
|
||||
{{('APP.OIDC.RESPONSE'+element | translate)}}
|
||||
{{i < oidcApp.responseTypesList.length - 1 ? ', ': ''}}
|
||||
</span>]
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<span class="left">
|
||||
{{ 'APP.OIDC.AUTHMETHOD' | translate }}
|
||||
</span>
|
||||
<span class="right">
|
||||
<span *ngFor="let element of oidcApp.responseTypesList">
|
||||
{{'APP.OIDC.RESPONSE'+element | translate}}
|
||||
<span>
|
||||
{{'APP.OIDC.AUTHMETHOD'+oidcApp?.authMethodType | translate}}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
@@ -178,10 +175,11 @@
|
||||
<span class="left">
|
||||
{{ 'APP.OIDC.REDIRECT' | translate }}
|
||||
</span>
|
||||
<span class="right">
|
||||
<span *ngFor="let redirect of oidcApp.redirectUrisList">
|
||||
<span class="right" *ngIf="oidcApp.redirectUrisList?.length > 0">
|
||||
[<span *ngFor="let redirect of oidcApp.redirectUrisList; index as i">
|
||||
{{redirect}}
|
||||
</span>
|
||||
{{i < oidcApp.redirectUrisList.length - 1 ? ', ': ''}}
|
||||
</span>]
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -189,10 +187,11 @@
|
||||
<span class="left">
|
||||
{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}
|
||||
</span>
|
||||
<span class="right">
|
||||
<span *ngFor="let redirect of oidcApp.postLogoutRedirectUrisList">
|
||||
<span class="right" *ngIf="oidcApp.postLogoutRedirectUrisList?.length > 0">
|
||||
[<span *ngFor="let redirect of oidcApp.postLogoutRedirectUrisList; index as i">
|
||||
{{redirect}}
|
||||
</span>
|
||||
{{i < oidcApp.postLogoutRedirectUrisList.length - 1 ? ', ': ''}}
|
||||
</span>]
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -14,7 +14,11 @@ p.desc {
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 4rem 4rem 2rem 4rem;
|
||||
padding: 4rem 4rem 2rem 4rem;
|
||||
|
||||
mat-progress-bar {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.abort-container {
|
||||
display: flex;
|
||||
|
||||
@@ -32,11 +32,36 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
public loading: boolean = false;
|
||||
public oidcApp: OIDCApplicationCreate.AsObject = new OIDCApplicationCreate().toObject();
|
||||
|
||||
public oidcResponseTypes: { type: OIDCResponseType, checked: boolean; }[] = [
|
||||
{ type: OIDCResponseType.OIDCRESPONSETYPE_CODE, checked: false },
|
||||
{ type: OIDCResponseType.OIDCRESPONSETYPE_ID_TOKEN, checked: false },
|
||||
{ type: OIDCResponseType.OIDCRESPONSETYPE_ID_TOKEN_TOKEN, checked: false },
|
||||
public oidcResponseTypes: { type: OIDCResponseType, checked: boolean; disabled: boolean; }[] = [
|
||||
{ type: OIDCResponseType.OIDCRESPONSETYPE_CODE, checked: false, disabled: false },
|
||||
{ type: OIDCResponseType.OIDCRESPONSETYPE_ID_TOKEN, checked: false, disabled: false },
|
||||
{ type: OIDCResponseType.OIDCRESPONSETYPE_ID_TOKEN_TOKEN, checked: false, disabled: false },
|
||||
];
|
||||
|
||||
public oidcAppTypes: OIDCApplicationType[] = [
|
||||
OIDCApplicationType.OIDCAPPLICATIONTYPE_WEB,
|
||||
OIDCApplicationType.OIDCAPPLICATIONTYPE_USER_AGENT,
|
||||
OIDCApplicationType.OIDCAPPLICATIONTYPE_NATIVE,
|
||||
];
|
||||
|
||||
public oidcAuthMethodType: { type: OIDCAuthMethodType, checked: boolean, disabled: boolean; }[] = [
|
||||
{ type: OIDCAuthMethodType.OIDCAUTHMETHODTYPE_BASIC, checked: false, disabled: false },
|
||||
{ type: OIDCAuthMethodType.OIDCAUTHMETHODTYPE_NONE, checked: false, disabled: false },
|
||||
{ type: OIDCAuthMethodType.OIDCAUTHMETHODTYPE_POST, checked: false, disabled: false },
|
||||
];
|
||||
|
||||
// stepper
|
||||
firstFormGroup!: FormGroup;
|
||||
secondFormGroup!: FormGroup;
|
||||
// thirdFormGroup!: FormGroup;
|
||||
|
||||
// devmode
|
||||
public form!: FormGroup;
|
||||
|
||||
public OIDCApplicationType: any = OIDCApplicationType;
|
||||
public OIDCGrantType: any = OIDCGrantType;
|
||||
public OIDCAuthMethodType: any = OIDCAuthMethodType;
|
||||
|
||||
public oidcGrantTypes: {
|
||||
type: OIDCGrantType,
|
||||
checked: boolean,
|
||||
@@ -47,28 +72,6 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
// { type: OIDCGrantType.OIDCGRANTTYPE_REFRESH_TOKEN, checked: false, disabled: true },
|
||||
// TODO show when implemented
|
||||
];
|
||||
public oidcAppTypes: OIDCApplicationType[] = [
|
||||
OIDCApplicationType.OIDCAPPLICATIONTYPE_WEB,
|
||||
OIDCApplicationType.OIDCAPPLICATIONTYPE_USER_AGENT,
|
||||
OIDCApplicationType.OIDCAPPLICATIONTYPE_NATIVE,
|
||||
];
|
||||
public oidcAuthMethodType: { type: OIDCAuthMethodType, checked: boolean, disabled: boolean; }[] = [
|
||||
{ type: OIDCAuthMethodType.OIDCAUTHMETHODTYPE_BASIC, checked: false, disabled: false },
|
||||
{ type: OIDCAuthMethodType.OIDCAUTHMETHODTYPE_NONE, checked: false, disabled: false },
|
||||
{ type: OIDCAuthMethodType.OIDCAUTHMETHODTYPE_POST, checked: false, disabled: false },
|
||||
];
|
||||
|
||||
// stepper
|
||||
firstFormGroup!: FormGroup;
|
||||
secondFormGroup!: FormGroup;
|
||||
thirdFormGroup!: FormGroup;
|
||||
|
||||
// devmode
|
||||
public form!: FormGroup;
|
||||
|
||||
public OIDCApplicationType: any = OIDCApplicationType;
|
||||
public OIDCGrantType: any = OIDCGrantType;
|
||||
public OIDCAuthMethodType: any = OIDCAuthMethodType;
|
||||
|
||||
public readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE];
|
||||
|
||||
@@ -92,8 +95,8 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
this.form.valueChanges.pipe(debounceTime(300)).subscribe((value) => {
|
||||
this.oidcApp.name = this.formname?.value;
|
||||
this.oidcApp.applicationType = this.formapplicationType?.value;
|
||||
this.oidcApp.grantTypesList = this.formgrantTypesList?.value;
|
||||
this.oidcApp.responseTypesList = this.formresponseTypesList?.value;
|
||||
this.oidcApp.grantTypesList = this.formgrantTypesList?.value;
|
||||
this.oidcApp.authMethodType = this.formauthMethodType?.value;
|
||||
|
||||
console.log(this.oidcApp);
|
||||
@@ -103,48 +106,60 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
name: ['', [Validators.required]],
|
||||
applicationType: ['', [Validators.required]],
|
||||
});
|
||||
|
||||
this.firstFormGroup.valueChanges.subscribe(value => {
|
||||
if (this.firstFormGroup.valid) {
|
||||
console.log('change firstform', value);
|
||||
switch (value.applicationType) {
|
||||
case OIDCApplicationType.OIDCAPPLICATIONTYPE_NATIVE:
|
||||
console.log('NATIVE');
|
||||
this.oidcResponseTypes[0].checked = true;
|
||||
this.oidcApp.responseTypesList = [OIDCResponseType.OIDCRESPONSETYPE_CODE];
|
||||
|
||||
this.oidcApp.grantTypesList =
|
||||
[OIDCGrantType.OIDCGRANTTYPE_AUTHORIZATION_CODE];
|
||||
this.oidcApp.authMethodType = OIDCAuthMethodType.OIDCAUTHMETHODTYPE_NONE;
|
||||
break;
|
||||
case OIDCApplicationType.OIDCAPPLICATIONTYPE_WEB:
|
||||
console.log('WEB');
|
||||
|
||||
this.oidcAuthMethodType[0].disabled = false;
|
||||
this.oidcAuthMethodType[1].disabled = true; // NONE DISABLED
|
||||
this.oidcAuthMethodType[2].disabled = false; // POST POSSIBLE
|
||||
this.authMethodType?.setValue(OIDCAuthMethodType.OIDCAUTHMETHODTYPE_BASIC); // BASIC DEFAULT
|
||||
this.oidcApp.authMethodType = OIDCAuthMethodType.OIDCAUTHMETHODTYPE_BASIC;
|
||||
|
||||
this.oidcResponseTypes[0].checked = true;
|
||||
this.oidcApp.responseTypesList = [OIDCResponseType.OIDCRESPONSETYPE_CODE];
|
||||
this.changeResponseType();
|
||||
|
||||
this.oidcApp.grantTypesList =
|
||||
[OIDCGrantType.OIDCGRANTTYPE_AUTHORIZATION_CODE];
|
||||
|
||||
break;
|
||||
case OIDCApplicationType.OIDCAPPLICATIONTYPE_USER_AGENT:
|
||||
console.log('USERAGENT');
|
||||
|
||||
this.oidcResponseTypes[0].checked = true;
|
||||
this.oidcApp.responseTypesList = [OIDCResponseType.OIDCRESPONSETYPE_CODE];
|
||||
|
||||
this.oidcApp.grantTypesList =
|
||||
[OIDCGrantType.OIDCGRANTTYPE_AUTHORIZATION_CODE, OIDCGrantType.OIDCGRANTTYPE_IMPLICIT];
|
||||
|
||||
this.oidcApp.authMethodType = OIDCAuthMethodType.OIDCAUTHMETHODTYPE_NONE;
|
||||
break;
|
||||
}
|
||||
|
||||
this.oidcApp.name = this.name?.value;
|
||||
this.oidcApp.applicationType = this.applicationType?.value;
|
||||
}
|
||||
});
|
||||
|
||||
this.secondFormGroup = this.fb.group({
|
||||
authMethodType: [OIDCAuthMethodType.OIDCAUTHMETHODTYPE_BASIC, [Validators.required]],
|
||||
});
|
||||
|
||||
this.oidcApp.authMethodType = OIDCAuthMethodType.OIDCAUTHMETHODTYPE_BASIC;
|
||||
|
||||
this.firstFormGroup.valueChanges.subscribe(value => {
|
||||
if (this.applicationType?.value === OIDCApplicationType.OIDCAPPLICATIONTYPE_NATIVE) {
|
||||
this.oidcResponseTypes[0].checked = true;
|
||||
// this.oidcApp.responseTypesList = [this.oidcResponseTypes[0].type];
|
||||
|
||||
this.authMethodType?.setValue(OIDCAuthMethodType.OIDCAUTHMETHODTYPE_NONE);
|
||||
this.oidcAuthMethodType[1].disabled = true;
|
||||
}
|
||||
if (this.applicationType?.value === OIDCApplicationType.OIDCAPPLICATIONTYPE_WEB) {
|
||||
this.oidcResponseTypes[0].checked = true;
|
||||
this.oidcApp.responseTypesList = [this.oidcResponseTypes[0].type];
|
||||
|
||||
this.authMethodType?.setValue(OIDCAuthMethodType.OIDCAUTHMETHODTYPE_NONE);
|
||||
this.oidcAuthMethodType[0].disabled = true;
|
||||
this.oidcAuthMethodType[2].disabled = true;
|
||||
}
|
||||
if (this.applicationType?.value === OIDCApplicationType.OIDCAPPLICATIONTYPE_USER_AGENT) {
|
||||
this.oidcResponseTypes[0].checked = true;
|
||||
this.oidcApp.responseTypesList = [this.oidcResponseTypes[0].type];
|
||||
|
||||
this.authMethodType?.setValue(OIDCAuthMethodType.OIDCAUTHMETHODTYPE_NONE);
|
||||
this.oidcAuthMethodType[0].disabled = true;
|
||||
this.oidcAuthMethodType[2].disabled = true;
|
||||
|
||||
this.oidcGrantTypes[1].disabled = false;
|
||||
}
|
||||
|
||||
this.changeResponseType();
|
||||
this.changeGrant();
|
||||
|
||||
this.oidcApp.name = this.name?.value;
|
||||
this.oidcApp.applicationType = this.applicationType?.value;
|
||||
});
|
||||
|
||||
this.secondFormGroup.valueChanges.subscribe(value => {
|
||||
this.oidcApp.authMethodType = this.authMethodType?.value;
|
||||
this.oidcApp.authMethodType = value.authMethodType;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -226,14 +241,14 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
changeGrant(): void {
|
||||
this.oidcApp.grantTypesList = this.oidcGrantTypes.filter(gt => gt.checked).map(gt => gt.type);
|
||||
}
|
||||
|
||||
changeResponseType(): void {
|
||||
this.oidcApp.responseTypesList = this.oidcResponseTypes.filter(gt => gt.checked).map(gt => gt.type);
|
||||
}
|
||||
|
||||
moreThanOneOption(options: Array<{ type: OIDCGrantType, checked: boolean, disabled: boolean; }>): boolean {
|
||||
return options.filter(option => option.disabled === false).length > 1;
|
||||
}
|
||||
|
||||
get name(): AbstractControl | null {
|
||||
return this.firstFormGroup.get('name');
|
||||
}
|
||||
|
||||
@@ -86,7 +86,9 @@
|
||||
<mat-form-field class="full-width" appearance="outline">
|
||||
<mat-label>{{ 'APP.OIDC.REDIRECT' | translate }}</mat-label>
|
||||
<mat-chip-list #chipRedirectList>
|
||||
<mat-chip *ngFor="let redirect of redirectUrisList" [selectable]="selectable"
|
||||
<mat-chip *ngFor="let redirect of redirectUrisList" selected
|
||||
[matTooltip]="!redirect.startsWith('https://') ? ('APP.OIDC.UNSECUREREDIRECT' | translate): ''"
|
||||
[color]="!redirect.startsWith('https://') ? 'warn': 'green'"
|
||||
(removed)="remove(redirect, RedirectType.REDIRECT)">
|
||||
{{redirect}}
|
||||
<mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
|
||||
@@ -100,8 +102,10 @@
|
||||
<mat-form-field class="full-width" appearance="outline">
|
||||
<mat-label>{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}</mat-label>
|
||||
<mat-chip-list #chipPostRedirectList>
|
||||
<mat-chip *ngFor="let redirect of postLogoutRedirectUrisList" [selectable]="selectable"
|
||||
(removed)="remove(redirect, RedirectType.POSTREDIRECT)">
|
||||
<mat-chip *ngFor="let redirect of postLogoutRedirectUrisList" selected
|
||||
(removed)="remove(redirect, RedirectType.POSTREDIRECT)"
|
||||
[matTooltip]="!redirect.startsWith('https://') ? ('APP.OIDC.UNSECUREREDIRECT' | translate): ''"
|
||||
[color]="!redirect.startsWith('https://') ? 'warn': 'green'">
|
||||
{{redirect}}
|
||||
<mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
|
||||
</mat-chip>
|
||||
|
||||
@@ -84,4 +84,8 @@
|
||||
border-radius: .5rem;
|
||||
margin: 0 .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
mat-chip[color='green'] {
|
||||
background-color: #56a392 !important;
|
||||
}
|
||||
@@ -35,7 +35,6 @@ enum RedirectType {
|
||||
})
|
||||
export class AppDetailComponent implements OnInit, OnDestroy {
|
||||
public errorMessage: string = '';
|
||||
public selectable: boolean = false;
|
||||
public removable: boolean = true;
|
||||
public addOnBlur: boolean = true;
|
||||
public readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE];
|
||||
@@ -219,7 +218,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.projectService
|
||||
.UpdateOIDCAppConfig(this.projectId, this.app.id, this.app.oidcConfig)
|
||||
.then((data: OIDCConfig) => {
|
||||
.then(() => {
|
||||
this.toast.showInfo('APP.TOAST.OIDCUPDATED', true);
|
||||
})
|
||||
.catch(error => {
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
|
||||
<div *ngIf="data.clientSecret" class="flex">
|
||||
<button color="primary" [disabled]="copied == data.clientSecret" matTooltip="copy to clipboard"
|
||||
(click)="copytoclipboard(data.clientSecret)" mat-icon-button>
|
||||
appCopyToClipboard [valueToCopy]="data.clientSecret" (copiedValue)="this.copied = $event" mat-icon-button>
|
||||
<i *ngIf="copied != data.clientSecret" class="las la-clipboard"></i>
|
||||
<i *ngIf="copied == data.clientSecret" class="las la-clipboard-check"></i>
|
||||
</button>
|
||||
|
||||
@@ -14,22 +14,4 @@ export class AppSecretDialogComponent {
|
||||
public closeDialog(): void {
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
|
||||
public copytoclipboard(value: string): void {
|
||||
const selBox = document.createElement('textarea');
|
||||
selBox.style.position = 'fixed';
|
||||
selBox.style.left = '0';
|
||||
selBox.style.top = '0';
|
||||
selBox.style.opacity = '0';
|
||||
selBox.value = value;
|
||||
document.body.appendChild(selBox);
|
||||
selBox.focus();
|
||||
selBox.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(selBox);
|
||||
this.copied = value;
|
||||
setTimeout(() => {
|
||||
this.copied = '';
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatStepperModule } from '@angular/material/stepper';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CopyToClipboardModule } from 'src/app/directives/copy-to-clipboard/copy-to-clipboard.module';
|
||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||
import { CardModule } from 'src/app/modules/card/card.module';
|
||||
|
||||
@@ -55,6 +56,7 @@ import { AppsRoutingModule } from './apps-routing.module';
|
||||
TranslateModule,
|
||||
MatStepperModule,
|
||||
MatRadioModule,
|
||||
CopyToClipboardModule,
|
||||
],
|
||||
exports: [TranslateModule],
|
||||
})
|
||||
|
||||
+2
-2
@@ -34,8 +34,8 @@
|
||||
|
||||
<mat-tab-group mat-stretch-tabs class="tab-group" disablePagination="true">
|
||||
<mat-tab label="Details">
|
||||
<app-contributors [totalResult]="totalMemberResult" [membersSubject]="membersSubject"
|
||||
title="{{ 'PROJECT.MEMBER.TITLE' | translate }}"
|
||||
<app-contributors [totalResult]="totalMemberResult" [loading]="loading$ | async"
|
||||
[membersSubject]="membersSubject" title="{{ 'PROJECT.MEMBER.TITLE' | translate }}"
|
||||
description="{{ 'PROJECT.MEMBER.TITLEDESC' | translate }}" (addClicked)="openAddMember()"
|
||||
(showDetailClicked)="showDetail()" [disabled]="false">
|
||||
</app-contributors>
|
||||
|
||||
+3
-2
@@ -5,7 +5,7 @@ import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { BehaviorSubject, from, of, Subscription } from 'rxjs';
|
||||
import { BehaviorSubject, from, Observable, of, Subscription } from 'rxjs';
|
||||
import { catchError, finalize, map } from 'rxjs/operators';
|
||||
import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
|
||||
import { ChangeType } from 'src/app/modules/changes/changes.component';
|
||||
@@ -72,7 +72,8 @@ export class GrantedProjectDetailComponent implements OnInit, OnDestroy {
|
||||
public totalMemberResult: number = 0;
|
||||
public membersSubject: BehaviorSubject<ProjectMemberView.AsObject[]>
|
||||
= new BehaviorSubject<ProjectMemberView.AsObject[]>([]);
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
||||
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||
|
||||
constructor(
|
||||
public translate: TranslateService,
|
||||
|
||||
+4
-2
@@ -26,7 +26,8 @@
|
||||
</div>
|
||||
<button [ngClass]="{ selected: selection.isSelected(item)}" (click)="selection.toggle(item)" class="edit-button"
|
||||
mat-icon-button>
|
||||
<mat-icon>push_pin_outline</mat-icon>
|
||||
<mat-icon *ngIf="selection.isSelected(item)" svgIcon="mdi_pin"></mat-icon>
|
||||
<mat-icon svgIcon="mdi_pin_outline" *ngIf="!selection.isSelected(item)"></mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -52,7 +53,8 @@
|
||||
</div>
|
||||
<button [ngClass]="{ selected: selection.isSelected(item)}" (click)="selection.toggle(item)" class="edit-button"
|
||||
mat-icon-button>
|
||||
<mat-icon>push_pin_outline</mat-icon>
|
||||
<mat-icon *ngIf="selection.isSelected(item)" svgIcon="mdi_pin"></mat-icon>
|
||||
<mat-icon svgIcon="mdi_pin_outline" *ngIf="!selection.isSelected(item)"></mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<p class="n-items" *ngIf="!loading && items.length === 0 && selection.selected.length === 0">
|
||||
|
||||
+7
-2
@@ -6,16 +6,21 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="app-container">
|
||||
<div class="sp-container" *ngIf="(loading$ | async)">
|
||||
<mat-spinner color="accent" diameter="50"></mat-spinner>
|
||||
</div>
|
||||
|
||||
<div [routerLink]="['/projects', projectId, 'apps', app.id ]" class="app-wrap"
|
||||
*ngFor="let app of appsSubject | async">
|
||||
<div class="morph-card">
|
||||
<div class="morph-card" matRipple>
|
||||
{{ app.name.charAt(0)}}
|
||||
</div>
|
||||
<span class="name">{{app.name}}</span>
|
||||
</div>
|
||||
|
||||
<ng-template appHasRole [appHasRole]="['project.app.write']">
|
||||
<div class="app-wrap" *ngIf="!disabled" [routerLink]="[ '/projects', projectId, 'apps', 'create']">
|
||||
<div class="morph-card add">
|
||||
<div class="morph-card add" matRipple>
|
||||
<mat-icon>add</mat-icon>
|
||||
</div>
|
||||
<span class="name">{{ 'ACTIONS.NEW' | translate }}</span>
|
||||
|
||||
+22
-1
@@ -21,6 +21,14 @@
|
||||
margin: 0 -1rem;
|
||||
padding-bottom: 2rem;
|
||||
|
||||
.sp-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: calc(82px + 2rem);
|
||||
height: calc(82px + 2rem);
|
||||
}
|
||||
|
||||
.app-wrap {
|
||||
outline: none;
|
||||
display: flex;
|
||||
@@ -40,11 +48,24 @@
|
||||
margin: 1rem;
|
||||
border-radius: .5rem;
|
||||
border: 1px solid $accent-color;
|
||||
font-weight: 800;
|
||||
background-color: $primary-dark;
|
||||
transition: background-color .4s ease-in-out;
|
||||
transition: background-color .2s ease-in-out;
|
||||
background-image:
|
||||
linear-gradient(transparent 11px, rgba($accent-color,.5) 12px, transparent 12px),
|
||||
linear-gradient(90deg, transparent 11px, rgba($accent-color,.5) 12px, transparent 12px);
|
||||
background-size: 100% 12px, 12px 100%;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($accent-color,.2);
|
||||
}
|
||||
|
||||
&.add {
|
||||
background: $accent-color;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($accent-color,.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ export class ApplicationGridComponent implements OnInit {
|
||||
@Input() public disabled: boolean = false;
|
||||
@Output() public changeView: EventEmitter<void> = new EventEmitter();
|
||||
public appsSubject: BehaviorSubject<Application.AsObject[]> = new BehaviorSubject<Application.AsObject[]>([]);
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
||||
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||
|
||||
constructor(private projectService: ProjectService) { }
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ export class ProjectApplicationsDataSource extends DataSource<Application.AsObje
|
||||
super();
|
||||
}
|
||||
|
||||
public loadApps(projectId: string, pageIndex: number, pageSize: number, sortDirection?: string): void {
|
||||
public loadApps(projectId: string, pageIndex: number, pageSize: number): void {
|
||||
const offset = pageIndex * pageSize;
|
||||
|
||||
this.loadingSubject.next(true);
|
||||
|
||||
+34
-47
@@ -1,54 +1,41 @@
|
||||
<div class="table-header-row">
|
||||
<div class="col">
|
||||
<ng-container *ngIf="!selection.hasValue()">
|
||||
<span class="desc">{{'ORG_DETAIL.TABLE.TOTAL' | translate}}</span>
|
||||
<span class="count">{{dataSource?.appsSubject.value.length}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="selection.hasValue()">
|
||||
<span class="desc">{{'ORG_DETAIL.TABLE.SELECTION' | translate}}</span>
|
||||
<span class="count">{{selection?.selected?.length}}</span>
|
||||
</ng-container>
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
<ng-template appHasRole [appHasRole]="['project.app.write']">
|
||||
<app-refresh-table [loading]="dataSource.loading$ | async" [selection]="selection" (refreshed)="refreshPage()"
|
||||
[dataSize]="dataSource.totalResult">
|
||||
<ng-template appHasRole [appHasRole]="['project.app.write']" actions>
|
||||
<a [disabled]="disabled" class="add-button" [routerLink]="[ '/projects', projectId, 'apps', 'create']"
|
||||
color="primary" mat-raised-button>
|
||||
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
|
||||
</a>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<div class="table-wrapper">
|
||||
<div class="spinner-container" *ngIf="dataSource.loading$ | async">
|
||||
<mat-spinner diameter="50"></mat-spinner>
|
||||
<div class="table-wrapper">
|
||||
<table [dataSource]="dataSource" mat-table class="full-width-table" matSort aria-label="Elements">
|
||||
<ng-container matColumnDef="select">
|
||||
<th class="selection" mat-header-cell *matHeaderCellDef>
|
||||
<mat-checkbox color="primary" (change)="$event ? masterToggle() : null"
|
||||
[checked]="selection.hasValue() && isAllSelected()"
|
||||
[indeterminate]="selection.hasValue() && !isAllSelected()">
|
||||
</mat-checkbox>
|
||||
</th>
|
||||
<td class="selection" mat-cell *matCellDef="let row">
|
||||
<mat-checkbox color="primary" (click)="$event.stopPropagation()"
|
||||
(change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)">
|
||||
</mat-checkbox>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.ROLE.NAME' | translate }} </th>
|
||||
<td class="pointer" [routerLink]="['/projects', projectId, 'apps', role.id ]" mat-cell
|
||||
*matCellDef="let role">
|
||||
{{role.name}} </td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr class="data-row" mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
|
||||
<mat-paginator #paginator [length]="dataSource.totalResult" [pageSize]="25"
|
||||
[pageSizeOptions]="[25, 50, 100, 250]">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
|
||||
<table [dataSource]="dataSource" mat-table class="full-width-table" matSort aria-label="Elements">
|
||||
<ng-container matColumnDef="select">
|
||||
<th class="selection" mat-header-cell *matHeaderCellDef>
|
||||
<mat-checkbox color="primary" (change)="$event ? masterToggle() : null"
|
||||
[checked]="selection.hasValue() && isAllSelected()"
|
||||
[indeterminate]="selection.hasValue() && !isAllSelected()">
|
||||
</mat-checkbox>
|
||||
</th>
|
||||
<td class="selection" mat-cell *matCellDef="let row">
|
||||
<mat-checkbox color="primary" (click)="$event.stopPropagation()"
|
||||
(change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)">
|
||||
</mat-checkbox>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.ROLE.NAME' | translate }} </th>
|
||||
<td class="pointer" [routerLink]="['/projects', projectId, 'apps', role.id ]" mat-cell
|
||||
*matCellDef="let role">
|
||||
{{role.name}} </td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr class="data-row" mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
|
||||
<mat-paginator #paginator [length]="dataSource.totalResult" [pageSize]="50" [pageSizeOptions]="[25, 50, 100, 250]">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
</app-refresh-table>
|
||||
+30
-64
@@ -1,73 +1,39 @@
|
||||
|
||||
.table-header-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.desc {
|
||||
font-size: .8rem;
|
||||
color: #8795a1;
|
||||
}
|
||||
.count {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.fill-space {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
border-radius: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
overflow: auto;
|
||||
|
||||
.spinner-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
table, mat-paginator {
|
||||
width: 100%;
|
||||
background-color: #2d2e30;
|
||||
|
||||
td, th {
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.data-row {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #ffffff05;
|
||||
}
|
||||
}
|
||||
|
||||
.selection {
|
||||
width: 50px;
|
||||
max-width: 50px;
|
||||
}
|
||||
|
||||
.margin-neg {
|
||||
margin-left: -1rem;
|
||||
}
|
||||
}
|
||||
|
||||
table, mat-paginator {
|
||||
width: 100%;
|
||||
background-color: #2d2e30;
|
||||
|
||||
td, th {
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.data-row {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #ffffff05;
|
||||
}
|
||||
}
|
||||
|
||||
.selection {
|
||||
width: 50px;
|
||||
max-width: 50px;
|
||||
}
|
||||
|
||||
.margin-neg {
|
||||
margin-left: -1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pointer {
|
||||
|
||||
+5
-2
@@ -33,7 +33,7 @@ export class ApplicationsComponent implements AfterViewInit, OnInit {
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.dataSource = new ProjectApplicationsDataSource(this.projectService);
|
||||
this.dataSource.loadApps(this.projectId, 0, 25, 'asc');
|
||||
this.dataSource.loadApps(this.projectId, 0, 25);
|
||||
}
|
||||
|
||||
public ngAfterViewInit(): void {
|
||||
@@ -51,7 +51,6 @@ export class ApplicationsComponent implements AfterViewInit, OnInit {
|
||||
this.projectId,
|
||||
this.paginator.pageIndex,
|
||||
this.paginator.pageSize,
|
||||
this.sort.direction,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,4 +65,8 @@ export class ApplicationsComponent implements AfterViewInit, OnInit {
|
||||
this.selection.clear() :
|
||||
this.dataSource.appsSubject.value.forEach((row: Application.AsObject) => this.selection.select(row));
|
||||
}
|
||||
|
||||
public refreshPage(): void {
|
||||
this.dataSource.loadApps(this.projectId, this.paginator.pageIndex, this.paginator.pageSize);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -101,8 +101,8 @@
|
||||
|
||||
<mat-tab-group mat-stretch-tabs class="tab-group" disablePagination="true">
|
||||
<mat-tab label="Details">
|
||||
<app-contributors [totalResult]="totalMemberResult" [membersSubject]="membersSubject"
|
||||
title="{{ 'PROJECT.MEMBER.TITLE' | translate }}"
|
||||
<app-contributors [loading]="loading$ | async" [totalResult]="totalMemberResult"
|
||||
[membersSubject]="membersSubject" title="{{ 'PROJECT.MEMBER.TITLE' | translate }}"
|
||||
description="{{ 'PROJECT.MEMBER.TITLEDESC' | translate }}" (addClicked)="openAddMember()"
|
||||
(showDetailClicked)="showDetail()" [disabled]="false">
|
||||
</app-contributors>
|
||||
|
||||
+3
-2
@@ -5,7 +5,7 @@ import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { BehaviorSubject, from, of, Subscription } from 'rxjs';
|
||||
import { BehaviorSubject, from, Observable, of, Subscription } from 'rxjs';
|
||||
import { catchError, finalize, map } from 'rxjs/operators';
|
||||
import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
|
||||
import { ChangeType } from 'src/app/modules/changes/changes.component';
|
||||
@@ -72,7 +72,8 @@ export class OwnedProjectDetailComponent implements OnInit, OnDestroy {
|
||||
public totalMemberResult: number = 0;
|
||||
public membersSubject: BehaviorSubject<ProjectMemberView.AsObject[]>
|
||||
= new BehaviorSubject<ProjectMemberView.AsObject[]>([]);
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
||||
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||
|
||||
constructor(
|
||||
public translate: TranslateService,
|
||||
|
||||
+2
@@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatRippleModule } from '@angular/material/core';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
@@ -56,6 +57,7 @@ import { ProjectGrantsComponent } from './project-grants/project-grants.componen
|
||||
MatFormFieldModule,
|
||||
CardModule,
|
||||
MatPaginatorModule,
|
||||
MatRippleModule,
|
||||
MatCheckboxModule,
|
||||
MatSelectModule,
|
||||
MatProgressSpinnerModule,
|
||||
|
||||
+4
-2
@@ -29,7 +29,8 @@
|
||||
</div>
|
||||
<button [ngClass]="{ selected: selection.isSelected(item)}" (click)="selection.toggle(item)" class="edit-button"
|
||||
mat-icon-button>
|
||||
<mat-icon>push_pin</mat-icon>
|
||||
<mat-icon *ngIf="selection.isSelected(item)" svgIcon="mdi_pin"></mat-icon>
|
||||
<mat-icon svgIcon="mdi_pin_outline" *ngIf="!selection.isSelected(item)"></mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -57,7 +58,8 @@
|
||||
</div>
|
||||
<button [ngClass]="{ selected: selection.isSelected(item)}" (click)="selection.toggle(item)" class="edit-button"
|
||||
mat-icon-button>
|
||||
<mat-icon>push_pin</mat-icon>
|
||||
<mat-icon *ngIf="selection.isSelected(item)" svgIcon="mdi_pin"></mat-icon>
|
||||
<mat-icon svgIcon="mdi_pin_outline" *ngIf="!selection.isSelected(item)"></mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
-4
@@ -133,10 +133,6 @@
|
||||
margin-bottom: 0.25rem;
|
||||
color: #8795a1;
|
||||
|
||||
&:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
+2
@@ -9,6 +9,7 @@ import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
@@ -39,6 +40,7 @@ import { ProjectGrantMembersModule } from './project-grant-members/project-grant
|
||||
MatProgressSpinnerModule,
|
||||
FormsModule,
|
||||
TranslateModule,
|
||||
MatSelectModule,
|
||||
],
|
||||
})
|
||||
export class ProjectGrantDetailModule { }
|
||||
|
||||
Reference in New Issue
Block a user