feat(console): apply private labeling settings (#2059)

* fetch org design an calc palette

* distinct theme deriv

* calculate self theme

* background palette, fix styles

* component fixes, warn color

* warn color

* stylelint config, set warn color

* lint

* fallback theme

* lint

* granted, owned proejct grid

* dynamic privacy policy, fix home layout, dismissables, info-section

* lint

* apply theme from settings

* enable cache

* fix: add primary tint
This commit is contained in:
Max Peintner
2021-07-28 14:59:52 +02:00
committed by GitHub
parent fb06aed238
commit 7d3c768d8b
47 changed files with 963 additions and 588 deletions
+19 -17
View File
@@ -5,18 +5,20 @@
(click)="drawer.toggle()">
<i class="icon las la-bars"></i>
</button>
<a class="title" [routerLink]="['/']">
<img class="logo" alt="zitadel logo" *ngIf="componentCssClass == 'dark-theme'; else lighttheme"
src="../assets/images/zitadel-logo-solo-light.svg" />
<ng-template #lighttheme>
<img alt="zitadel logo" class="logo" src="../assets/images/zitadel-logo-solo-dark.svg" />
</ng-template>
</a>
<ng-container *ngIf="labelpolicy && !labelpolicy?.disableWatermark">
<a class="title" [routerLink]="['/']">
<img class="logo" alt="zitadel logo" *ngIf="componentCssClass == 'dark-theme'; else lighttheme"
src="../assets/images/zitadel-logo-solo-light.svg" />
<ng-template #lighttheme>
<img alt="zitadel logo" class="logo" src="../assets/images/zitadel-logo-solo-dark.svg" />
</ng-template>
</a>
<svg class="slash" viewBox="0 0 24 24" width="32" height="32" stroke="currentColor" stroke-width="1"
stroke-linecap="round" stroke-linejoin="round" fill="none" shape-rendering="geometricPrecision">
<path d="M16.88 3.549L7.12 20.451"></path>
</svg>
<svg class="slash" viewBox="0 0 24 24" width="32" height="32" stroke="currentColor" stroke-width="1"
stroke-linecap="round" stroke-linejoin="round" fill="none" shape-rendering="geometricPrecision">
<path d="M16.88 3.549L7.12 20.451"></path>
</svg>
</ng-container>
<button class="org-button" (click)="loadOrgs()" *ngIf="user && org" mat-button [matMenuTriggerFor]="menu"
(menuOpened)="focusFilter()">{{org?.name ? org.name : 'NO NAME'}}
@@ -102,7 +104,7 @@
<ng-template appHasRole [appHasRole]="['org.read']">
<a @navitem matTooltip="{{'MENU.TOOLTIP.ORG' | translate}}" class="nav-item"
[routerLinkActive]="['active']" [routerLink]="[ '/org']">
<i class="icon las la-archway"></i>
<i class="icon las la-cog"></i>
<span class="label">{{'MENU.ORGANIZATION' | translate}}</span>
</a>
</ng-template>
@@ -165,25 +167,25 @@
</div>
<a @navitem matTooltip="{{'MENU.TOOLTIP.IAMPOLICIES' | translate}}" class="nav-item"
[routerLinkActive]="['active']" [routerLink]="[ '/iam','policies']">
<i class="icon las la-gem"></i>
<i class="icon las la-cog"></i>
<span class="label">{{'MENU.IAMPOLICIES' | translate}}</span>
</a>
<a @navitem matTooltip="{{'MENU.TOOLTIP.IAMEVENTSTORE' | translate}}" class="nav-item"
[routerLinkActive]="['active']" [routerLink]="[ '/iam', 'eventstore']">
<i class="icon las la-gem"></i>
<i class="icon las la-database"></i>
<span class="label">{{'MENU.IAMEVENTSTORE' | translate}}</span>
</a>
</ng-container>
<span class="fill-space"></span>
<div class="toc-line">
<a class="toc" [href]="'https://docs.zitadel.ch/docs/legal/terms-of-service'" alt="Terms and Conditions"
<div class="toc-line" *ngIf="privacyPolicy">
<a class="toc" [href]="privacyPolicy.tosLink" alt="Terms and Conditions"
target="_blank">{{'MENU.TOS'
| translate}}</a>
<span class="slash">|</span>
<a class="toc" [href]="'https://docs.zitadel.ch/docs/legal/privacy-policy'" alt="Privacy Policy "
<a class="toc" [href]="privacyPolicy.privacyLink" alt="Privacy Policy "
target="_blank">{{'MENU.PRIVACY'
| translate}}</a>
<span>&nbsp;&nbsp;&nbsp;</span>
+303 -238
View File
@@ -14,6 +14,7 @@ import { catchError, debounceTime, finalize, map, take } from 'rxjs/operators';
import { accountCard, adminLineAnimation, navAnimations, routeAnimations, toolbarAnimation } from './animations';
import { TextQueryMethod } from './proto/generated/zitadel/object_pb';
import { Org, OrgNameQuery, OrgQuery } from './proto/generated/zitadel/org_pb';
import { LabelPolicy, PrivacyPolicy } from './proto/generated/zitadel/policy_pb';
import { User } from './proto/generated/zitadel/user_pb';
import { AuthenticationService } from './services/authentication.service';
import { GrpcAuthService } from './services/grpc-auth.service';
@@ -22,284 +23,348 @@ import { ThemeService } from './services/theme.service';
import { UpdateService } from './services/update.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
animations: [
toolbarAnimation,
...navAnimations,
accountCard,
routeAnimations,
adminLineAnimation,
],
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
animations: [
toolbarAnimation,
...navAnimations,
accountCard,
routeAnimations,
adminLineAnimation,
],
})
export class AppComponent implements OnDestroy {
@ViewChild('drawer') public drawer!: MatDrawer;
@ViewChild('input', { static: false }) input!: ElementRef;
public isHandset$: Observable<boolean> = this.breakpointObserver
.observe('(max-width: 599px)')
.pipe(map(result => {
return result.matches;
}));
@HostBinding('class') public componentCssClass: string = 'dark-theme';
@ViewChild('drawer') public drawer!: MatDrawer;
@ViewChild('input', { static: false }) input!: ElementRef;
public isHandset$: Observable<boolean> = this.breakpointObserver
.observe('(max-width: 599px)')
.pipe(map(result => {
return result.matches;
}));
@HostBinding('class') public componentCssClass: string = 'dark-theme';
public showAccount: boolean = false;
public org!: Org.AsObject;
public orgs$: Observable<Org.AsObject[]> = of([]);
public user!: User.AsObject;
public isDarkTheme: Observable<boolean> = of(true);
public showAccount: boolean = false;
public org!: Org.AsObject;
public orgs$: Observable<Org.AsObject[]> = of([]);
public user!: User.AsObject;
public isDarkTheme: Observable<boolean> = of(true);
public orgLoading$: BehaviorSubject<any> = new BehaviorSubject(false);
public orgLoading$: BehaviorSubject<any> = new BehaviorSubject(false);
public showProjectSection: boolean = false;
public showProjectSection: boolean = false;
public filterControl: FormControl = new FormControl('');
private authSub: Subscription = new Subscription();
private orgSub: Subscription = new Subscription();
public filterControl: FormControl = new FormControl('');
private authSub: Subscription = new Subscription();
private orgSub: Subscription = new Subscription();
public labelpolicy!: LabelPolicy.AsObject;
public hideAdminWarn: boolean = true;
public language: string = 'en';
constructor(
public viewPortScroller: ViewportScroller,
@Inject('windowObject') public window: Window,
public translate: TranslateService,
public authenticationService: AuthenticationService,
public authService: GrpcAuthService,
private breakpointObserver: BreakpointObserver,
public overlayContainer: OverlayContainer,
private themeService: ThemeService,
public mgmtService: ManagementService,
public matIconRegistry: MatIconRegistry,
public domSanitizer: DomSanitizer,
private router: Router,
update: UpdateService,
@Inject(DOCUMENT) private document: Document,
) {
console.log('%cWait!', 'text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; color: #5469D4; font-size: 50px');
console.log('%cInserting something here could give attackers access to your zitadel account.', 'color: red; font-size: 18px');
console.log('%cIf you don\'t know exactly what you\'re doing, close the window and stay on the safe side', 'font-size: 16px');
console.log('%cIf you know exactly what you are doing, you should work for us', 'font-size: 16px');
this.setLanguage();
public hideAdminWarn: boolean = true;
public language: string = 'en';
public privacyPolicy!: PrivacyPolicy.AsObject;
constructor(
public viewPortScroller: ViewportScroller,
@Inject('windowObject') public window: Window,
public translate: TranslateService,
public authenticationService: AuthenticationService,
public authService: GrpcAuthService,
private breakpointObserver: BreakpointObserver,
public overlayContainer: OverlayContainer,
private themeService: ThemeService,
public mgmtService: ManagementService,
public matIconRegistry: MatIconRegistry,
public domSanitizer: DomSanitizer,
private router: Router,
update: UpdateService,
@Inject(DOCUMENT) private document: Document,
) {
console.log('%cWait!', 'text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; color: #5469D4; font-size: 50px');
console.log('%cInserting something here could give attackers access to your zitadel account.', 'color: red; font-size: 18px');
console.log('%cIf you don\'t know exactly what you\'re doing, close the window and stay on the safe side', 'font-size: 16px');
console.log('%cIf you know exactly what you are doing, you should work for us', 'font-size: 16px');
this.setLanguage();
this.matIconRegistry.addSvgIcon(
'mdi_account_check_outline',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/account-check-outline.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_account_check_outline',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/account-check-outline.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_account_cancel',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/account-cancel-outline.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_account_cancel',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/account-cancel-outline.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_light_on',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/lightbulb-on-outline.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_light_on',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/lightbulb-on-outline.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_content_copy',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/content-copy.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_content_copy',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/content-copy.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_light_off',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/lightbulb-off-outline.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_light_off',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/lightbulb-off-outline.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_radar',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/radar.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_radar',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/radar.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_lock_question',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/lock-question.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_lock_question',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/lock-question.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_textbox_password',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/textbox-password.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_textbox_password',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/textbox-password.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_lock_reset',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/lock-reset.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_lock_reset',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/lock-reset.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_broom',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/broom.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_broom',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/broom.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_pin_outline',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/pin-outline.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_pin_outline',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/pin-outline.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_pin',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/pin.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_pin',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/pin.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_format-letter-case-lower',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/format-letter-case-lower.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_format-letter-case-lower',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/format-letter-case-lower.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_format-letter-case-upper',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/format-letter-case-upper.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_format-letter-case-upper',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/format-letter-case-upper.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_counter',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/counter.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_counter',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/counter.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_symbol',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/symbol.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_symbol',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/symbol.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_numeric',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/numeric.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_numeric',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/numeric.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_api',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/api.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_api',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/api.svg'),
);
this.getProjectCount();
this.loadPrivateLabelling();
this.orgSub = this.authService.activeOrgChanged.subscribe(org => {
this.org = org;
this.getProjectCount();
this.getProjectCount();
this.orgSub = this.authService.activeOrgChanged.subscribe(org => {
this.org = org;
this.getProjectCount();
});
this.authSub = this.authenticationService.authenticationChanged.subscribe((authenticated) => {
if (authenticated) {
this.authService.getActiveOrg().then(org => {
this.org = org;
});
}
});
this.authSub = this.authenticationService.authenticationChanged.subscribe((authenticated) => {
if (authenticated) {
this.authService.getActiveOrg().then(org => {
this.org = org;
});
}
});
const theme = localStorage.getItem('theme');
if (theme) {
this.overlayContainer.getContainerElement().classList.add(theme);
this.componentCssClass = theme;
}
this.isDarkTheme = this.themeService.isDarkTheme;
this.isDarkTheme.subscribe(thema => this.onSetTheme(thema ? 'dark-theme' : 'light-theme'));
this.translate.onLangChange.subscribe((language: LangChangeEvent) => {
this.document.documentElement.lang = language.lang;
this.language = language.lang;
});
this.filterControl.valueChanges.pipe(debounceTime(300)).subscribe(value => {
this.loadOrgs(
value.trim().toLowerCase(),
);
});
this.hideAdminWarn = localStorage.getItem('hideAdministratorWarning') === 'true' ? true : false;
const theme = localStorage.getItem('theme');
if (theme) {
this.overlayContainer.getContainerElement().classList.add(theme);
this.componentCssClass = theme;
}
public ngOnDestroy(): void {
this.authSub.unsubscribe();
this.orgSub.unsubscribe();
this.isDarkTheme = this.themeService.isDarkTheme;
this.isDarkTheme.subscribe(dark => this.onSetTheme(dark ? 'dark-theme' : 'light-theme'));
this.translate.onLangChange.subscribe((language: LangChangeEvent) => {
this.document.documentElement.lang = language.lang;
this.language = language.lang;
});
this.filterControl.valueChanges.pipe(debounceTime(300)).subscribe(value => {
this.loadOrgs(
value.trim().toLowerCase(),
);
});
this.hideAdminWarn = localStorage.getItem('hideAdministratorWarning') === 'true' ? true : false;
this.loadPolicies();
}
public ngOnDestroy(): void {
this.authSub.unsubscribe();
this.orgSub.unsubscribe();
}
public toggleAdminHide(): void {
this.hideAdminWarn = !this.hideAdminWarn;
localStorage.setItem('hideAdministratorWarning', this.hideAdminWarn.toString());
}
public loadPrivateLabelling(): void {
const setDefaultColors = () => {
const darkPrimary = '#5282c1';
const lightPrimary = '#5282c1';
const darkWarn = '#F44336';
const lightWarn = '#F44336';
const darkBackground = '#212224';
const lightBackground = '#fafafa';
this.themeService.savePrimaryColor(darkPrimary, true);
this.themeService.savePrimaryColor(lightPrimary, false);
this.themeService.saveWarnColor(darkWarn, true);
this.themeService.saveWarnColor(lightWarn, false);
this.themeService.saveBackgroundColor(darkBackground, true);
this.themeService.saveBackgroundColor(lightBackground, false);
};
setDefaultColors();
this.mgmtService.getLabelPolicy().then(labelpolicy => {
if (labelpolicy.policy) {
this.labelpolicy = labelpolicy.policy;
const darkPrimary = this.labelpolicy?.primaryColorDark || '#5282c1';
const lightPrimary = this.labelpolicy?.primaryColor || '#5282c1';
const darkWarn = this.labelpolicy?.warnColorDark || '#F44336';
const lightWarn = this.labelpolicy?.warnColor || '#F44336';
const darkBackground = this.labelpolicy?.backgroundColorDark || '#212224';
const lightBackground = this.labelpolicy?.backgroundColor || '#fafafa';
this.themeService.savePrimaryColor(darkPrimary, true);
this.themeService.savePrimaryColor(lightPrimary, false);
this.themeService.saveWarnColor(darkWarn, true);
this.themeService.saveWarnColor(lightWarn, false);
this.themeService.saveBackgroundColor(darkBackground, true);
this.themeService.saveBackgroundColor(lightBackground, false);
}
});
}
public loadPolicies(): void {
this.mgmtService.getPrivacyPolicy().then(privacypolicy => {
if (privacypolicy.policy) {
this.privacyPolicy = privacypolicy.policy;
}
});
}
public loadOrgs(filter?: string): void {
let query;
if (filter) {
query = new OrgQuery();
const orgNameQuery = new OrgNameQuery();
orgNameQuery.setName(filter);
orgNameQuery.setMethod(TextQueryMethod.TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE);
query.setNameQuery(orgNameQuery);
}
public toggleAdminHide(): void {
this.hideAdminWarn = !this.hideAdminWarn;
localStorage.setItem('hideAdministratorWarning', this.hideAdminWarn.toString());
this.orgLoading$.next(true);
this.orgs$ = from(this.authService.listMyProjectOrgs(10, 0, query ? [query] : undefined)).pipe(
map(resp => {
return resp.resultList;
}),
catchError(() => of([])),
finalize(() => {
this.orgLoading$.next(false);
this.focusFilter();
}),
);
}
public prepareRoute(outlet: RouterOutlet): boolean {
return outlet && outlet.activatedRouteData && outlet.activatedRouteData.animation;
}
public closeAccountCard(): void {
if (this.showAccount) {
this.showAccount = false;
}
}
public loadOrgs(filter?: string): void {
let query;
if (filter) {
query = new OrgQuery();
const orgNameQuery = new OrgNameQuery();
orgNameQuery.setName(filter);
orgNameQuery.setMethod(TextQueryMethod.TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE);
query.setNameQuery(orgNameQuery);
}
public onSetTheme(theme: string): void {
localStorage.setItem('theme', theme);
this.overlayContainer.getContainerElement().classList.add(theme);
this.componentCssClass = theme;
}
this.orgLoading$.next(true);
this.orgs$ = from(this.authService.listMyProjectOrgs(10, 0, query ? [query] : undefined)).pipe(
map(resp => {
return resp.resultList;
}),
catchError(() => of([])),
finalize(() => {
this.orgLoading$.next(false);
this.focusFilter();
}),
);
}
private setLanguage(): void {
this.translate.addLangs(['en', 'de']);
this.translate.setDefaultLang('en');
public prepareRoute(outlet: RouterOutlet): boolean {
return outlet && outlet.activatedRouteData && outlet.activatedRouteData.animation;
}
this.authService.user.subscribe(userprofile => {
if (userprofile) {
this.user = userprofile;
const cropped = navigator.language.split('-')[0] ?? 'en';
const fallbackLang = cropped.match(/en|de/) ? cropped : 'en';
public closeAccountCard(): void {
if (this.showAccount) {
this.showAccount = false;
}
}
const lang =
userprofile?.human?.profile?.preferredLanguage.match(/en|de/) ?
userprofile.human.profile?.preferredLanguage :
fallbackLang;
this.translate.use(lang);
this.language = lang;
this.document.documentElement.lang = lang;
}
});
}
public onSetTheme(theme: string): void {
localStorage.setItem('theme', theme);
this.overlayContainer.getContainerElement().classList.add(theme);
this.componentCssClass = theme;
}
public setActiveOrg(org: Org.AsObject): void {
this.org = org;
this.authService.setActiveOrg(org);
this.loadPrivateLabelling();
this.authService.zitadelPermissionsChanged.pipe(take(1)).subscribe(() => {
this.router.navigate(['/']);
});
}
private setLanguage(): void {
this.translate.addLangs(['en', 'de']);
this.translate.setDefaultLang('en');
private getProjectCount(): void {
this.authService.isAllowed(['project.read']).subscribe((allowed) => {
if (allowed) {
this.mgmtService.listProjects(0, 0);
this.mgmtService.listGrantedProjects(0, 0);
}
});
}
this.authService.user.subscribe(userprofile => {
if (userprofile) {
this.user = userprofile;
const cropped = navigator.language.split('-')[0] ?? 'en';
const fallbackLang = cropped.match(/en|de/) ? cropped : 'en';
const lang =
userprofile?.human?.profile?.preferredLanguage.match(/en|de/) ?
userprofile.human.profile?.preferredLanguage :
fallbackLang;
this.translate.use(lang);
this.language = lang;
this.document.documentElement.lang = lang;
}
});
}
public setActiveOrg(org: Org.AsObject): void {
this.org = org;
this.authService.setActiveOrg(org);
this.authService.zitadelPermissionsChanged.pipe(take(1)).subscribe(() => {
this.router.navigate(['/']);
});
}
private getProjectCount(): void {
this.authService.isAllowed(['project.read']).subscribe((allowed) => {
if (allowed) {
this.mgmtService.listProjects(0, 0);
this.mgmtService.listGrantedProjects(0, 0);
}
});
}
focusFilter(): void {
setTimeout(() => {
this.input.nativeElement.focus();
}, 0);
}
focusFilter(): void {
setTimeout(() => {
this.input.nativeElement.focus();
}, 0);
}
}
@@ -3,10 +3,12 @@
@mixin app-card-theme($theme) {
/* stylelint-disable */
$primary: map-get($theme, primary);
$primary-dark: mat.get-color-from-palette($primary, A900);
$background: map-get($theme, background);
$accent: map-get($theme, accent);
$is-dark-theme: map-get($theme, is-dark);
$accent-color: mat.get-color-from-palette($primary, 500);
$back: map-get($background, background);
/* stylelint-enable */
.cnsl-app-card {
@@ -25,7 +27,7 @@
text-transform: uppercase;
border-radius: .5rem;
font-weight: 800;
background-color: $primary-dark;
background-color: $back;
transition: background-color box-shadow .3s ease-in;
&.web {
@@ -93,7 +93,7 @@
span {
margin: 2rem;
font-size: 30px;
color: if($is-dark-theme, #21222450, #ffffff50);
color: if($is-dark-theme, #00000050, #ffffff50);
}
}
@@ -50,7 +50,7 @@
span {
margin: 2rem;
font-size: 30px;
color: if($is-dark-theme, #21222450, #ffffff50);
color: if($is-dark-theme, #00000050, #ffffff50);
}
}
@@ -1,4 +1,4 @@
<div class="card">
<div class="card" [ngClass]="{'nomargin': nomargin}">
<div *ngIf="title || description" class="header" [ngClass]="{'bottom-margin': expanded}">
<div *ngIf="title" class="row">
<h2 class="title">{{title}}</h2>
@@ -1,4 +1,3 @@
.card {
margin: 1rem 0;
padding: 1.5rem;
@@ -6,6 +5,10 @@
padding-top: 1rem;
min-width: 300px;
&.nomargin {
margin: 0;
}
.header {
margin-top: 0;
+19 -18
View File
@@ -2,24 +2,25 @@ import { animate, style, transition, trigger } from '@angular/animations';
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.scss'],
animations: [
trigger('openClose', [
transition(':enter', [
style({ height: '0', opacity: 0 }),
animate('150ms ease-in-out', style({ height: '*', opacity: 1 })),
]),
transition(':leave', [
animate('150ms ease-in-out', style({ height: '0', opacity: 0 })),
]),
]),
],
selector: 'app-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.scss'],
animations: [
trigger('openClose', [
transition(':enter', [
style({ height: '0', opacity: 0 }),
animate('150ms ease-in-out', style({ height: '*', opacity: 1 })),
]),
transition(':leave', [
animate('150ms ease-in-out', style({ height: '0', opacity: 0 })),
]),
]),
],
})
export class CardComponent {
@Input() public expanded: boolean = true;
@Input() public title: string = '';
@Input() public description: string = '';
@Input() public animate: boolean = false;
@Input() public expanded: boolean = true;
@Input() public title: string = '';
@Input() public description: string = '';
@Input() public animate: boolean = false;
@Input() public nomargin?: boolean = false;
}
+9 -7
View File
@@ -1,18 +1,20 @@
@use '~@angular/material' as mat;
@mixin card-theme($theme) {
/* stylelint-disable */
/* stylelint-disable */
$primary: map-get($theme, primary);
$primary-color: mat.get-color-from-palette($primary, 500);
$primary-dark: mat.get-color-from-palette($primary, A800);
$border-color: mat.get-color-from-palette($primary, A700);
$border-selected-color: mat.get-color-from-palette($primary, A600);
/* stylelint-enable */
$background: map-get($theme, background);
$card-background-color: mat.get-color-from-palette($background, card);
$is-dark-theme: map-get($theme, is-dark);
$border-color: if($is-dark-theme, rgba(#8795a1, .2), rgba(#8795a1, .2));
$border-selected-color: if($is-dark-theme, #ffffff, #000000);
/* stylelint-enable */
.card {
background-color: $primary-dark;
background-color: $card-background-color;
transition: background-color .3s cubic-bezier(.645, .045, .355, 1);
border: 1px solid rgba($border-color, .2);
border: 1px solid $border-color;
box-sizing: border-box;
border-radius: .5rem;
outline: none;
@@ -22,11 +22,12 @@
@mixin changes-theme($theme) {
$is-dark-theme: map-get($theme, is-dark);
$foreground: map-get($theme, foreground);
.scroll-container {
max-height: 50vh;
overflow-y: scroll;
border-bottom: 1px solid if($is-dark-theme, #303131, #e3e8ee);
border-bottom: 1px solid map-get($foreground, divider);
margin-bottom: .5rem;
.date {
@@ -114,11 +115,13 @@
/* stylelint-disable */
$primary: map-get($theme, primary);
$primary-dark: mat.get-color-from-palette($primary, A800);
$is-dark-theme: map-get($theme, is-dark);
$transparent-color: if($is-dark-theme, rgba(#8795a1, .2), rgba(#8795a1, .2));
/* stylelint-enable */
&.change-item-back {
background-color: rgba($primary-dark, .93);
background-color: rgba($transparent-color, .93);
transition: background-color .3s cubic-bezier(.645, .045, .355, 1);
}
}
@@ -4,7 +4,7 @@
/* stylelint-disable */
$primary: map-get($theme, primary);
$primary-color: mat.get-color-from-palette($primary, 500);
$primary-dark: mat.get-color-from-palette($primary, A800);
$is-dark-theme: map-get($theme, is-dark);
/* stylelint-enable */
$lighter-color: rgba(mat.get-color-from-palette($primary, 300), .5);
@@ -35,7 +35,7 @@
<p class="error" *ngIf="(stripeCustomer || stripeCustomer == null) && !customerValid">{{'FEATURES.TIER.CUSTOMERINVALID' | translate}}</p>
<div class="current-tier">
<a [disabled]="!org.id || !customerValid || !stripeURL" mat-raised-button [href]="stripeURL" target="_blank"
<a color="primary" [disabled]="!org.id || !customerValid || !stripeURL" mat-raised-button [href]="stripeURL" target="_blank"
alt="change tier">{{'FEATURES.TIER.BTN' | translate}}</a>
</div>
</ng-container>
@@ -2,6 +2,8 @@
@mixin info-section-theme($theme) {
$primary: map-get($theme, primary);
$background: map-get($theme, background);
$foreground: map-get($theme, foreground);
$primary-color: mat.get-color-from-palette($primary, 500);
$is-dark-theme: map-get($theme, is-dark);
@@ -25,29 +27,29 @@
}
&.info {
background-color: if($is-dark-theme, #6d6f7382, #e4e4e4);
color: if($is-dark-theme, #f0f0f0, #4a4a4a);
background-color: map-get($background, infosection);
color: map-get($foreground, infosection);
.icon {
color: $primary-color;
color: map-get($foreground, infosection);
}
}
&.success {
background-color: if($is-dark-theme, #4f566b, #cbf4c9);
color: if($is-dark-theme, #cbf4c9, #0e6245);
background-color: map-get($background, successinfosection);
color: map-get($foreground, successinfosection);
.icon {
color: $primary-color;
color: map-get($foreground, successinfosection);
}
}
&.warn {
background-color: if($is-dark-theme, #4f566b, #ffc1c1);
color: if($is-dark-theme, #ffc1c1, #620e0e);
background-color: map-get($background, warninfosection);
color: map-get($foreground, warninfosection);
.icon {
color: if($is-dark-theme, #ffc1c1, #620e0e);
color: map-get($foreground, warninfosection);
}
}
}
@@ -5,10 +5,12 @@
$primary: map-get($theme, primary);
$primary-color: mat.get-color-from-palette($primary, 500);
$is-dark-theme: map-get($theme, is-dark);
$foreground: map-get($theme, foreground);
/* stylelint-enable */
.onboarding-row {
box-shadow: inset 0 -1px if($is-dark-theme, #303131, #e3e8ee);
box-shadow: inset 0 -1px map-get($foreground, divider);
.prev {
background: $primary-color;
@@ -17,7 +19,7 @@
.goto {
text-decoration: none;
background: white;
border: 1px solid if($is-dark-theme, #303131, #e3e8ee);
border: 1px solid map-get($foreground, divider);
&.docs {
background-color: $primary-color;
@@ -8,14 +8,14 @@
</div>
<ng-container *ngIf="serviceType === PolicyComponentServiceType.MGMT">
<ng-template appHasRole [appHasRole]="['policy.delete']">
<button *ngIf="!isDefault" matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()"
<button *ngIf="!isDefault" color="primary" matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()"
mat-stroked-button>
{{'POLICY.RESET' | translate}}
</button>
</ng-template>
<ng-template appHasRole [appHasRole]="['policy.write']">
<button *ngIf="isDefault" matTooltip="{{'POLICY.CREATECUSTOM' | translate}}" (click)="savePolicy()"
<button *ngIf="isDefault" color="primary" matTooltip="{{'POLICY.CREATECUSTOM' | translate}}" (click)="savePolicy()"
mat-raised-button>
{{'POLICY.CREATECUSTOM' | translate}}
</button>
@@ -95,11 +95,6 @@
padding: 2px;
}
.meta-info {
font-size: 12px;
color: #fafafa;
}
.rm {
color: #f44336;
position: absolute;
@@ -19,7 +19,7 @@
.dashed {
border: 2px dashed rgba(#697386, .5);
border-radius: 1rem;
border-radius: .5rem;
padding: 100px 20px;
.login-wrapper {
@@ -74,16 +74,12 @@
}
&.light {
background: #fff;
.login-wrapper *:not(.cnsl-label):not(.mat-button-wrapper) {
color: #000;
}
}
&.dark {
background: #212224;
.login-wrapper *:not(.cnsl-label):not(.mat-button-wrapper) {
color: #fff;
}
@@ -66,7 +66,7 @@
<mat-spinner class="spinner" color="primary" diameter="25" *ngIf="loadingImages"></mat-spinner>
<container [ngSwitch]="theme">
<div class="logo-view" *ngSwitchCase="Theme.DARK">
<div class="img-wrapper" *ngIf="images['previewDarkLogo']" >
<div class="img-wrapper mat-elevation-z3" *ngIf="images['previewDarkLogo']" >
<mat-icon matTooltip="{{'ACTIONS.DELETE' | translate}}" color="warn" class="dl-btn" (click)="deleteAsset(AssetType.LOGO, theme)">remove_circle</mat-icon>
<img matTooltip="Preview" class="prev" [src]="images['previewDarkLogo']" alt="dark logo preview"/>
</div>
@@ -90,11 +90,10 @@
(dropped)="onDropLogo(theme, $event)"
[class.hovering]="isHoveringOverDarkLogo">
<label class="file-label">
<input #selectedFile style="display: none;" class="file-input" type="file" (change)="onDropLogo(theme, $event.target.files)">
<button mat-stroked-button class="btn" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) == false" mat-raised-button type="button" (click)="selectedFile.click();">{{'POLICY.PRIVATELABELING.BTN' | translate}}</button>
<i class="icon las la-cloud-upload-alt"></i>
<i class="icon las la-image"></i>
<span>{{isHoveringOverDarkLogo ? ('POLICY.PRIVATELABELING.RELEASE' | translate): ('POLICY.PRIVATELABELING.DROP' | translate)}}</span>
<input #selectedFile style="display: none;" class="file-input" type="file" (change)="onDropLogo(theme, $event.target.files)">
<a class="btn" *ngIf="serviceType == PolicyComponentServiceType.ADMIN || (serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async))" (click)="selectedFile.click();">{{'POLICY.PRIVATELABELING.BTN' | translate}}</a>
</label>
</div>
</div>
@@ -130,11 +129,10 @@
(dropped)="onDropIcon(theme, $event)"
[class.hovering]="isHoveringOverDarkIcon">
<label class="file-label">
<input #selectedFileIcon style="display: none;" class="file-input" type="file" (change)="onDropIcon(theme, $event.target.files)">
<button mat-stroked-button class="btn" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) == false" mat-raised-button type="button" (click)="selectedFileIcon.click();">{{'POLICY.PRIVATELABELING.BTN' | translate}}</button>
<i class="icon las la-cloud-upload-alt"></i>
<i class="icon las la-image"></i>
<span>{{isHoveringOverDarkIcon ? ('POLICY.PRIVATELABELING.RELEASE' | translate): ('POLICY.PRIVATELABELING.DROP' | translate)}}</span>
<input #selectedFileIcon style="display: none;" class="file-input" type="file" (change)="onDropIcon(theme, $event.target.files)">
<a class="btn" *ngIf="serviceType == PolicyComponentServiceType.ADMIN || (serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async))" (click)="selectedFileIcon.click();">{{'POLICY.PRIVATELABELING.BTN' | translate}}</a>
</label>
</div>
</div>
@@ -219,11 +217,10 @@
(dropped)="onDropFont($event)"
[class.hovering]="isHoveringOverFont">
<label class="file-label">
<input #selectedFontFile style="display: none;" class="file-input" type="file" (change)="onDropFont($event.target.files)">
<button mat-stroked-button class="btn" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) == false" mat-raised-button type="button" (click)="selectedFontFile.click();">{{'POLICY.PRIVATELABELING.BTN' | translate}}</button>
<i class="icon las la-cloud-upload-alt"></i>
<i class="icon las la-file"></i>
<span >{{isHoveringOverFont ? ('POLICY.PRIVATELABELING.RELEASEFONT' | translate): ('POLICY.PRIVATELABELING.DROPFONT' | translate)}}</span>
<input #selectedFontFile style="display: none;" class="file-input" type="file" (change)="onDropFont($event.target.files)">
<a class="btn" *ngIf="serviceType == PolicyComponentServiceType.ADMIN || (serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async))" (click)="selectedFontFile.click()">{{'POLICY.PRIVATELABELING.BTN' | translate}}</a>
</label>
</div>
</div>
@@ -6,6 +6,8 @@
$primary: map-get($theme, primary);
$primary-color: mat.get-color-from-palette($primary, 500);
$is-dark-theme: map-get($theme, is-dark);
$background: map-get($theme, background);
$foreground: map-get($theme, foreground);
.policy {
.header {
@@ -157,8 +159,6 @@
margin-bottom: 2rem;
.expansion {
background: if($is-dark-theme, #2d2e30, #fff) !important;
.header {
justify-content: flex-start;
}
@@ -189,8 +189,7 @@
height: 150px;
width: 100%;
border-radius: .5rem;
background: if($is-dark-theme, #2d2e30, #fff);
border: 1px solid if($is-dark-theme, #4a4b4b, #ddd);
border: 1px solid map-get($foreground, divider);
display: flex;
justify-content: center;
align-items: center;
@@ -202,11 +201,8 @@
flex-direction: column;
align-items: center;
.btn {
a {
margin-bottom: .5rem;
}
.btn:not[disabled] {
cursor: pointer;
}
@@ -272,32 +268,29 @@
margin-bottom: 1rem;
.img-wrapper {
flex: 1;
position: relative;
min-height: 80px;
border: 1px solid if($is-dark-theme, #ffffff20, #00000020);
border-radius: .5rem;
max-width: 120px;
height: 80px;
width: 80px;
border-radius: 50vw;
background-color: #00000020;
.dl-btn {
z-index: 2;
position: absolute;
top: -12px;
left: -12px;
top: 0;
left: 0;
cursor: pointer;
visibility: hidden;
}
.prev,
.curr {
position: absolute;
top: 50%;
transform: translateY(-50%);
top: 0;
left: 0;
max-height: 80px;
max-width: 120px;
width: 80px;
height: 80px;
object-fit: contain;
border-radius: .5rem;
border-radius: 50%;
}
&:hover {
@@ -412,7 +405,7 @@
position: absolute;
top: .5rem;
left: .5rem;
border-radius: 10px !important;
border-radius: 8px !important;
z-index: 1;
span {
@@ -22,6 +22,7 @@ import { AssetEndpoint, AssetService, AssetType } from 'src/app/services/asset.s
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { StorageService } from 'src/app/services/storage.service';
import { ThemeService } from 'src/app/services/theme.service';
import { ToastService } from 'src/app/services/toast.service';
import { GridPolicy, PRIVATELABEL_POLICY } from '../../policy-grid/policies';
@@ -95,6 +96,7 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
private assetService: AssetService,
private sanitizer: DomSanitizer,
private storageService: StorageService,
private themeService: ThemeService,
) {
const org: Org.AsObject | null = (this.storageService.getItem(ORG_STORAGE_KEY));
@@ -587,6 +589,7 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
if (data.policy) {
this.data = data.policy;
this.applyToConsole(data.policy);
this.loadImages();
}
});
@@ -603,6 +606,7 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
if (data.policy) {
this.data = data.policy;
this.applyToConsole(data.policy);
this.loadImages();
}
});
@@ -613,6 +617,26 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
}
}
private applyToConsole(labelpolicy: LabelPolicy.AsObject): void {
const darkPrimary = labelpolicy?.primaryColorDark || '#5282c1';
const lightPrimary = labelpolicy?.primaryColor || '#5282c1';
const darkWarn = labelpolicy?.warnColorDark || '#F44336';
const lightWarn = labelpolicy?.warnColor || '#F44336';
const darkBackground = labelpolicy?.backgroundColorDark || '#212224';
const lightBackground = labelpolicy?.backgroundColor || '#fafafa';
this.themeService.savePrimaryColor(darkPrimary, true);
this.themeService.savePrimaryColor(lightPrimary, false);
this.themeService.saveWarnColor(darkWarn, true);
this.themeService.saveWarnColor(lightWarn, false);
this.themeService.saveBackgroundColor(darkBackground, true);
this.themeService.saveBackgroundColor(lightBackground, false);
}
public resetPolicy(): Promise<any> {
return (this.service as ManagementService).resetLabelPolicyToDefault().then(() => {
this.toast.showInfo('POLICY.PRIVATELABELING.RESET', true);
@@ -4,7 +4,7 @@
<p class="wlc_stnce">{{'HOME.WELCOMESENTENCE' | translate}}</p>
</div>
<div class="container">
<div class="gridcontainer">
<ng-template appHasRole [appHasRole]="['iam.write']">
<a matRipple *ngIf="!firstStepsDismissed" class="onboard" [routerLink]="['/firststeps']">
<p class="first-steps">{{'ONBOARDING.HEADER' | translate}}</p>
@@ -17,7 +17,7 @@
</a>
</ng-template>
<a matRipple *ngIf="!firstStepsDismissed" class="quickstart" target="_blank"
<a matRipple *ngIf="!quickstartsDismissed" class="quickstart" target="_blank"
href="https://docs.zitadel.ch/docs/quickstarts/introduction">
<p class="first-steps">{{'HOME.QUICKSTARTS.LABEL' | translate}}</p>
<h2>{{'HOME.QUICKSTARTS.TITLE' | translate}}</h2>
@@ -35,7 +35,7 @@
</a>
<ng-template appHasRole [appHasRole]="['iam.write']">
<app-card class="item">
<app-card class="griditem" [nomargin]="true">
<div class="top">
<h2>
<i class="icon las la-gem"></i>
@@ -66,7 +66,7 @@
</app-card>
</ng-template>
<app-card class="item">
<app-card class="griditem" [nomargin]="true">
<div class="top">
<h2> <i class="icon las la-user-circle"></i>
{{'HOME.SECURITYANDPRIVACY'| translate}}</h2>
@@ -83,7 +83,7 @@
</app-card>
<ng-template appHasRole [appHasRole]="['project.read']">
<app-card class="item">
<app-card class="griditem" [nomargin]="true">
<div class="top">
<h2>
<i class="icon las la-layer-group"></i>
@@ -104,7 +104,7 @@
</ng-template>
<ng-template appHasRole [appHasRole]="['org.read']">
<app-card class="item">
<app-card class="griditem" [nomargin]="true">
<div class="top">
<h2> <i class="icon las la-archway"></i>
{{'HOME.PROTECTION'| translate}}</h2>
@@ -132,7 +132,7 @@
</ng-template>
<ng-template appHasRole [appHasRole]="['user.read']">
<app-card class="item">
<app-card class="griditem" [nomargin]="true">
<div class="top">
<h2>
<i class="las la-users"></i>
+13 -9
View File
@@ -18,21 +18,23 @@
}
}
.container {
display: flex;
flex-wrap: wrap;
margin: -1rem;
.gridcontainer {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
justify-content: space-evenly;
@media only screen and (max-width: 899px) {
grid-template-columns: 1fr;
}
.onboard,
.quickstart {
text-decoration: none;
cursor: pointer;
box-sizing: border-box;
flex: 1 0 45%;
position: relative;
border-radius: .5rem;
margin: 1rem;
padding: 1.5rem;
box-shadow: 0 3px 8px 0 rgb(0 0 0 / 6%);
@@ -96,12 +98,14 @@
}
}
.item {
flex: 1 0 45%;
margin: 0 1rem;
.griditem {
display: flex;
flex-direction: column;
.card {
margin: 0 !important;
}
.top {
h2 {
margin-top: .5rem;
+22 -22
View File
@@ -2,32 +2,32 @@ import { Component } from '@angular/core';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'],
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'],
})
export class HomeComponent {
public dark: boolean = true;
public firstStepsDismissed: boolean = false;
public quickstartsDismissed: boolean = false;
public dark: boolean = true;
public firstStepsDismissed: boolean = false;
public quickstartsDismissed: boolean = false;
constructor(public authService: GrpcAuthService) {
const theme = localStorage.getItem('theme');
this.dark = theme === 'dark-theme' ? true : theme === 'light-theme' ? false : true;
constructor(public authService: GrpcAuthService) {
const theme = localStorage.getItem('theme');
this.dark = theme === 'dark-theme' ? true : theme === 'light-theme' ? false : true;
this.firstStepsDismissed = localStorage.getItem('firstStartDismissed') === 'true' ? true : false;
this.quickstartsDismissed = localStorage.getItem('quickstartsDismissed') === 'true' ? true : false;
}
this.firstStepsDismissed = localStorage.getItem('firstStartDismissed') === 'true' ? true : false;
this.quickstartsDismissed = localStorage.getItem('quickstartsDismissed') === 'true' ? true : false;
}
dismissFirstSteps(event: Event): void {
event.preventDefault();
localStorage.setItem('firstStartDismissed', 'true');
this.firstStepsDismissed = true;
}
dismissFirstSteps(event: Event): void {
event.preventDefault();
localStorage.setItem('firstStartDismissed', 'true');
this.firstStepsDismissed = true;
}
dismissQuickstarts(event: Event): void {
event.preventDefault();
localStorage.setItem('quickstartsDismissed', 'true');
this.firstStepsDismissed = true;
}
dismissQuickstarts(event: Event): void {
event.preventDefault();
localStorage.setItem('quickstartsDismissed', 'true');
this.quickstartsDismissed = true;
}
}
@@ -9,7 +9,7 @@
<h1>{{'APP.PAGES.CREATE_OIDC_DESC_TITLE' | translate}}</h1>
<p class="desc">{{'APP.PAGES.CREATE_OIDC_DESC_SUB' | translate}}</p>
<mat-progress-bar class="progress-bar" color="accent" *ngIf="loading" mode="indeterminate"></mat-progress-bar>
<mat-progress-bar class="progress-bar" color="primary" *ngIf="loading" mode="indeterminate"></mat-progress-bar>
<mat-checkbox class="proswitch" color="primary" [(ngModel)]="devmode">
{{'APP.OIDC.PROSWITCH' | translate}}
@@ -3,8 +3,8 @@
<i class="show list view las la-th-list"></i>
</button>
</div>
<div class="container">
<mat-progress-bar *ngIf="loading" class="spinner" color="accent" mode="indeterminate"></mat-progress-bar>
<div class="granted-project-grid-container">
<mat-progress-bar *ngIf="loading" class="spinner" color="primary" mode="indeterminate"></mat-progress-bar>
<p class="n-items" *ngIf="!loading && selection.selected.length > 0">{{'PROJECT.PAGES.PINNED' | translate}}</p>
@@ -28,7 +28,7 @@
</div>
</div>
<div class="container">
<div class="granted-project-grid-container">
<p class="n-items" *ngIf="!loading && notPinned.length > 0">{{'PROJECT.PAGES.ALL' | translate}}</p>
<div class="item card" *ngFor="let item of notPinned; index as i"
@@ -1,9 +1,16 @@
@mixin granted-project-grid-theme($theme) {
$foreground: map-get($theme, foreground);
.view-toggle {
border-bottom: 1px solid map-get($foreground, divider);
}
}
.view-toggle {
width: 100%;
display: flex;
justify-content: flex-end;
padding-bottom: .5rem;
border-bottom: 1px solid #2d2e30;
.anim-list {
display: flex;
@@ -22,7 +29,7 @@
margin: 1rem;
}
.container {
.granted-project-grid-container {
display: flex;
flex-wrap: wrap;
margin: 0 -1rem;
@@ -4,8 +4,8 @@
</button>
</div>
<div class="container">
<mat-progress-bar *ngIf="loading" class="spinner" color="accent" mode="indeterminate"></mat-progress-bar>
<div class="owned-project-grid-container">
<mat-progress-bar *ngIf="loading" class="spinner" color="primary" mode="indeterminate"></mat-progress-bar>
<p class="n-items" *ngIf="!loading && selection.selected.length > 0">{{'PROJECT.PAGES.PINNED' | translate}}</p>
@@ -31,7 +31,7 @@
</div>
</div>
<div class="container">
<div class="owned-project-grid-container">
<p class="n-items" *ngIf="!loading && notPinned.length > 0">{{'PROJECT.PAGES.ALL' | translate}}</p>
<div class="item card" *ngFor="let item of notPinned; index as i" (click)="navigateToProject(item.id, $event)"
@@ -1,9 +1,16 @@
@mixin owned-project-grid-theme($theme) {
$foreground: map-get($theme, foreground);
.view-toggle {
border-bottom: 1px solid map-get($foreground, divider);
}
}
.view-toggle {
width: 100%;
display: flex;
justify-content: flex-end;
padding-bottom: .5rem;
border-bottom: 1px solid #2d2e30;
.anim-list {
display: flex;
@@ -22,7 +29,7 @@
margin: 1rem;
}
.container {
.owned-project-grid-container {
display: flex;
flex-wrap: wrap;
margin: 0 -1rem;
@@ -1,6 +1,6 @@
<app-detail-layout [backRouterLink]="[ '/users/list/machines']" title="{{ 'USER.CREATE.TITLE' | translate }}"
description="{{ 'USER.CREATE.DESCRIPTION' | translate }}">
<mat-progress-bar *ngIf="loading" color="accent" mode="indeterminate"></mat-progress-bar>
<mat-progress-bar *ngIf="loading" color="primary" mode="indeterminate"></mat-progress-bar>
<form *ngIf="userForm" [formGroup]="userForm" (ngSubmit)="createUser()" class="form">
<div class="content">
@@ -1,6 +1,6 @@
<app-detail-layout [backRouterLink]="[ '/users/list/humans']" title="{{ 'USER.CREATE.TITLE' | translate }}"
description="{{ 'USER.CREATE.DESCRIPTION' | translate }}">
<mat-progress-bar *ngIf="loading" color="accent" mode="indeterminate"></mat-progress-bar>
<mat-progress-bar *ngIf="loading" color="primary" mode="indeterminate"></mat-progress-bar>
<form *ngIf="userForm" [formGroup]="userForm" (ngSubmit)="createUser()" class="form">
<div class="content">
@@ -10,7 +10,7 @@
<app-theme-setting></app-theme-setting>
</div>
</div>
<mat-progress-bar *ngIf="loading" color="accent" mode="indeterminate"></mat-progress-bar>
<mat-progress-bar *ngIf="loading" color="primary" mode="indeterminate"></mat-progress-bar>
<span *ngIf="!loading && !user">{{ 'USER.PAGES.NOUSER' | translate }}</span>
@@ -3,12 +3,14 @@
@mixin theme-card($theme) {
/* stylelint-disable */
$primary: map-get($theme, primary);
$primary-dark: mat.get-color-from-palette($primary, A900);
$background: map-get($theme, background);
$is-dark-theme: map-get($theme, is-dark);
$back: map-get($background, background);
/* stylelint-enable */
.theme-conent,
.crescent {
background-color: $primary-dark;
background-color: $back;
transition: background-color .3s cubic-bezier(.645, .045, .355, 1); // cubic-bezier(.645, .045, .355, 1);
}
}
@@ -1,6 +1,3 @@
$dark-background: #212224;
$light-background: rgb(220, 220, 220);
:root {
transition: none;
}
@@ -31,7 +28,6 @@ $light-background: rgb(220, 220, 220);
right: 0;
width: 40px;
height: 40px;
background: $light-background;
transform: scale(0);
transform-origin: top right;
transition: transform .2s cubic-bezier(.645, .045, .355, 1);
@@ -57,7 +53,6 @@ label {
.toggle {
position: absolute;
background-color: $light-background;
box-shadow: 0 2px 15px rgba(0, 0, 0, .15);
}
@@ -82,7 +77,6 @@ label {
[type="checkbox"]:checked {
.toggle {
transform: translateX(100%);
background-color: $dark-background;
}
.light {
@@ -101,7 +95,6 @@ label {
[type="checkbox"]:checked + .theme-app .crescent {
transform: scale(1);
background: $dark-background;
}
[type="checkbox"]:checked + .theme-app .circle {
@@ -111,8 +104,3 @@ label {
[type="checkbox"]:checked + .theme-app .main-circle {
background: linear-gradient(40deg, #8983f7, #a3dafb 70%);
}
[type="checkbox"]:checked + .app .toggle {
transform: translateX(100%);
background-color: #34323d;
}
@@ -3,9 +3,8 @@
@mixin membership-theme($theme) {
/* stylelint-disable */
$primary: map-get($theme, primary);
$primary-dark: mat.get-color-from-palette($primary, A900);
$accent: map-get($theme, accent);
$accent-color: mat.get-color-from-palette($accent, 500);
$is-dark-theme: map-get($theme, is-dark);
$primary-color: mat.get-color-from-palette($primary, 500);
/* stylelint-enable */
.membership-groups {
@@ -91,7 +90,7 @@
font-size: 8px;
border-radius: .5rem;
transition: background-color .3s cubic-bezier(.645, .045, .355, 1);
background-color: $accent-color;
background-color: $primary-color;
cursor: pointer;
flex-direction: column;
overflow: hidden;
@@ -24,7 +24,7 @@
</ng-template>
</div>
<mat-progress-bar *ngIf="loading" color="accent" mode="indeterminate"></mat-progress-bar>
<mat-progress-bar *ngIf="loading" color="primary" mode="indeterminate"></mat-progress-bar>
<span *ngIf="!loading && !user">{{ 'USER.PAGES.NOUSER' | translate }}</span>
+73 -5
View File
@@ -1,12 +1,80 @@
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
declare const tinycolor: any;
export interface Color {
name: string;
hex: string;
darkContrast: boolean;
}
@Injectable()
export class ThemeService {
private _darkTheme: Subject<boolean> = new Subject<boolean>();
public isDarkTheme: Observable<boolean> = this._darkTheme.asObservable();
private _darkTheme: Subject<boolean> = new Subject<boolean>();
public isDarkTheme: Observable<boolean> = this._darkTheme.asObservable();
setDarkTheme(isDarkTheme: boolean): void {
this._darkTheme.next(isDarkTheme);
}
private primaryColorPalette: Color[] = [];
private warnColorPalette: Color[] = [];
private backgroundColorPalette: Color[] = [];
setDarkTheme(isDarkTheme: boolean): void {
this._darkTheme.next(isDarkTheme);
}
public updateTheme(colors: Color[], type: string, theme: string): void {
colors.forEach(color => {
document.documentElement.style.setProperty(
`--theme-${theme}-${type}-${color.name}`,
color.hex,
);
document.documentElement.style.setProperty(
`--theme-${theme}-${type}-contrast-${color.name}`,
color.darkContrast ? 'hsla(0, 0%, 0%, 0.87)' : '#ffffff',
);
});
}
public savePrimaryColor(color: string, isDark: boolean): void {
this.primaryColorPalette = this.computeColors(color);
this.updateTheme(this.primaryColorPalette, 'primary', isDark ? 'dark' : 'light');
}
public saveWarnColor(color: string, isDark: boolean): void {
this.warnColorPalette = this.computeColors(color);
this.updateTheme(this.warnColorPalette, 'warn', isDark ? 'dark' : 'light');
}
public saveBackgroundColor(color: string, isDark: boolean): void {
this.backgroundColorPalette = this.computeColors(color);
this.updateTheme(this.backgroundColorPalette, 'background', isDark ? 'dark' : 'light');
}
private computeColors(hex: string): Color[] {
return [
this.getColorObject(tinycolor(hex).lighten(52), '50'),
this.getColorObject(tinycolor(hex).lighten(37), '100'),
this.getColorObject(tinycolor(hex).lighten(26), '200'),
this.getColorObject(tinycolor(hex).lighten(12), '300'),
this.getColorObject(tinycolor(hex).lighten(6), '400'),
this.getColorObject(tinycolor(hex), '500'),
this.getColorObject(tinycolor(hex).darken(6), '600'),
this.getColorObject(tinycolor(hex).darken(12), '700'),
this.getColorObject(tinycolor(hex).darken(18), '800'),
this.getColorObject(tinycolor(hex).darken(24), '900'),
this.getColorObject(tinycolor(hex).lighten(50).saturate(30), 'A100'),
this.getColorObject(tinycolor(hex).lighten(30).saturate(30), 'A200'),
this.getColorObject(tinycolor(hex).lighten(10).saturate(15), 'A400'),
this.getColorObject(tinycolor(hex).lighten(5).saturate(5), 'A700'),
];
}
private getColorObject(value: any, name: string): Color {
const c = tinycolor(value);
return {
name: name,
hex: c.toHexString(),
darkContrast: c.isLight(),
};
}
}