authentification
This commit is contained in:
1016
WEB/package-lock.json
generated
1016
WEB/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,7 @@
|
||||
"@angular/common": "~8.2.0",
|
||||
"@angular/compiler": "~8.2.0",
|
||||
"@angular/core": "~8.2.0",
|
||||
"@angular/fire": "^5.2.1",
|
||||
"@angular/forms": "~8.2.0",
|
||||
"@angular/http": "~7.2.15",
|
||||
"@angular/material": "^8.1.2",
|
||||
@@ -28,6 +29,7 @@
|
||||
"@fortawesome/free-solid-svg-icons": "^5.10.1",
|
||||
"angular2-uuid": "^1.1.1",
|
||||
"core-js": "^3.1.4",
|
||||
"firebase": "^6.3.4",
|
||||
"ng2-file-upload": "^1.3.0",
|
||||
"odata-v4-ng": "^1.2.1",
|
||||
"rxjs": "^6.5.2",
|
||||
|
||||
17
WEB/src/app/account/account-routing.module.ts
Normal file
17
WEB/src/app/account/account-routing.module.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'login',
|
||||
loadChildren: () => import('./modules/login/login.module').then(_ => _.LoginModule)
|
||||
}
|
||||
]
|
||||
;
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AccountRoutingModule {
|
||||
}
|
||||
14
WEB/src/app/account/account.module.ts
Normal file
14
WEB/src/app/account/account.module.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
|
||||
import {AccountRoutingModule} from './account-routing.module';
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
AccountRoutingModule,
|
||||
]
|
||||
})
|
||||
export class AccountModule {
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>Login</mat-card-title>
|
||||
<mat-card-subtitle></mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<form>
|
||||
<mat-form-field>
|
||||
<input [formControl]="user" matInput placeholder="Benutzername">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<input [formControl]="pass" matInput placeholder="Passwort" type="password">
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button (click)="onLogin()" mat-button>Login</button>
|
||||
<button mat-button>Abbrechen</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
@@ -0,0 +1,5 @@
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px 0;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {LoginComponent} from './login.component';
|
||||
|
||||
describe('LoginComponent', () => {
|
||||
let component: LoginComponent;
|
||||
let fixture: ComponentFixture<LoginComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [LoginComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {AuthService} from '../../../../../services/auth.service';
|
||||
import {FormControl} from '@angular/forms';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
templateUrl: './login.component.html',
|
||||
styleUrls: ['./login.component.less']
|
||||
})
|
||||
export class LoginComponent implements OnInit {
|
||||
public user = new FormControl();
|
||||
public pass = new FormControl();
|
||||
public loginError = false;
|
||||
|
||||
private redirect: string = null;
|
||||
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private router: Router,
|
||||
activatedRoute: ActivatedRoute
|
||||
) {
|
||||
activatedRoute.queryParams.subscribe(_ => this.redirect = _.redirect);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.loginError = false;
|
||||
}
|
||||
|
||||
public onLogin(): void {
|
||||
this.authService.login$(this.user.value, this.pass.value).subscribe(_ => {
|
||||
if (_ === null) {
|
||||
this.loginError = true;
|
||||
} else {
|
||||
if (this.redirect) {
|
||||
this.router.navigateByUrl('/' + this.redirect);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
19
WEB/src/app/account/modules/login/login-routing.module.ts
Normal file
19
WEB/src/app/account/modules/login/login-routing.module.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {LoginComponent} from './components/login/login.component';
|
||||
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: LoginComponent,
|
||||
pathMatch: 'full'
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class LoginRoutingModule {
|
||||
}
|
||||
24
WEB/src/app/account/modules/login/login.module.ts
Normal file
24
WEB/src/app/account/modules/login/login.module.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
|
||||
import {LoginRoutingModule} from './login-routing.module';
|
||||
import {LoginComponent} from './components/login/login.component';
|
||||
import {MatButtonModule, MatCardModule, MatInputModule} from '@angular/material';
|
||||
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [LoginComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
LoginRoutingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
|
||||
MatCardModule,
|
||||
MatInputModule,
|
||||
MatButtonModule
|
||||
]
|
||||
})
|
||||
export class LoginModule {
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {AuthGuard} from './guards/auth.guard';
|
||||
|
||||
const routes: Routes = [
|
||||
|
||||
@@ -10,7 +11,12 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'songs',
|
||||
loadChildren: () => import('./songs/songs.module').then(_ => _.SongsModule)
|
||||
loadChildren: () => import('./songs/songs.module').then(_ => _.SongsModule),
|
||||
canActivate: [AuthGuard],
|
||||
},
|
||||
{
|
||||
path: 'account',
|
||||
loadChildren: () => import('./account/account.module').then(_ => _.AccountModule)
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -19,6 +19,11 @@ import {MatSelectModule} from '@angular/material/select';
|
||||
import {MatTooltipModule} from '@angular/material/tooltip';
|
||||
import {MatProgressBarModule} from '@angular/material/progress-bar';
|
||||
import {FileUploadModule} from 'ng2-file-upload';
|
||||
import {AngularFireModule} from '@angular/fire';
|
||||
import {AngularFireAuthModule} from '@angular/fire/auth';
|
||||
import {AngularFirestoreModule} from '@angular/fire/firestore';
|
||||
import {environment} from '../environments/environment';
|
||||
import {AngularFireDatabaseModule} from '@angular/fire/database';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -45,6 +50,11 @@ import {FileUploadModule} from 'ng2-file-upload';
|
||||
FontAwesomeModule,
|
||||
FileUploadModule,
|
||||
AppRoutingModule,
|
||||
|
||||
AngularFireModule.initializeApp(environment.firebase),
|
||||
AngularFirestoreModule, // imports firebase/firestore, only needed for database features
|
||||
AngularFireAuthModule, // imports firebase/auth, only needed for auth features
|
||||
AngularFireDatabaseModule,
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
|
||||
15
WEB/src/app/guards/auth.guard.spec.ts
Normal file
15
WEB/src/app/guards/auth.guard.spec.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import {inject, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {AuthGuard} from './auth.guard';
|
||||
|
||||
describe('AuthGuard', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [AuthGuard]
|
||||
});
|
||||
});
|
||||
|
||||
it('should ...', inject([AuthGuard], (guard: AuthGuard) => {
|
||||
expect(guard).toBeTruthy();
|
||||
}));
|
||||
});
|
||||
27
WEB/src/app/guards/auth.guard.ts
Normal file
27
WEB/src/app/guards/auth.guard.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree} from '@angular/router';
|
||||
import {AuthService} from '../services/auth.service';
|
||||
import {Observable} from 'rxjs';
|
||||
import {map} from 'rxjs/operators';
|
||||
import {Role} from '../services/roles.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthGuard implements CanActivate {
|
||||
|
||||
constructor(private authService: AuthService,
|
||||
private router: Router
|
||||
) {
|
||||
}
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> {
|
||||
return this.authService.userMay$(Role.reader).pipe(
|
||||
map(_ => {
|
||||
return _
|
||||
? true
|
||||
: this.router.createUrlTree(['/account/login'], {queryParams: {redirect: route.url}});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
41
WEB/src/app/services/access-right.service.spec.ts
Normal file
41
WEB/src/app/services/access-right.service.spec.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import {async, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {AccessRightService} from './access-right.service';
|
||||
import {AngularFirestore} from '@angular/fire/firestore';
|
||||
import {of} from 'rxjs';
|
||||
import {Role} from './roles.model';
|
||||
|
||||
describe('AccessRightService', () => {
|
||||
const mockAngularFirestore = {
|
||||
collection: () => ({
|
||||
doc: () => ({
|
||||
valueChanges: () => of({
|
||||
role: 'reader'
|
||||
})
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
beforeEach(() => TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: AngularFirestore, useValue: mockAngularFirestore}
|
||||
]
|
||||
}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: AccessRightService = TestBed.get(AccessRightService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should get user', async(() => {
|
||||
const service: AccessRightService = TestBed.get(AccessRightService);
|
||||
service.getUserInfo('userid').subscribe(_ => {
|
||||
expect(_).toEqual({
|
||||
user: 'userid',
|
||||
role: Role.reader
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
});
|
||||
43
WEB/src/app/services/access-right.service.ts
Normal file
43
WEB/src/app/services/access-right.service.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {AngularFirestore} from '@angular/fire/firestore';
|
||||
import {map} from 'rxjs/operators';
|
||||
import {User, UserDB} from './user.model';
|
||||
import {Observable} from 'rxjs';
|
||||
import {Role} from './roles.model';
|
||||
import {RoleDefinitions} from './role.definition';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AccessRightService {
|
||||
|
||||
constructor(
|
||||
private angularFirestore: AngularFirestore
|
||||
) {
|
||||
}
|
||||
|
||||
public getUserInfo(userId: string): Observable<User> {
|
||||
if (userId === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const user$ = this.angularFirestore
|
||||
.collection('/user')
|
||||
.doc<UserDB>(userId)
|
||||
.valueChanges()
|
||||
.pipe
|
||||
(map(user => ({
|
||||
user: userId,
|
||||
role: user.role
|
||||
})));
|
||||
|
||||
return user$;
|
||||
}
|
||||
|
||||
public userMay(role: Role, requestedRole: Role): boolean {
|
||||
const allowedRoles = RoleDefinitions.filter(_ => _.role === requestedRole)[0].when;
|
||||
const isAllowed = allowedRoles.indexOf(role) !== -1;
|
||||
|
||||
return isAllowed;
|
||||
}
|
||||
}
|
||||
47
WEB/src/app/services/auth.service.spec.ts
Normal file
47
WEB/src/app/services/auth.service.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import {fakeAsync, TestBed, tick} from '@angular/core/testing';
|
||||
|
||||
import {AuthService} from './auth.service';
|
||||
import {of} from 'rxjs';
|
||||
import {Role} from './roles.model';
|
||||
import {AccessRightService} from './access-right.service';
|
||||
import {AngularFireAuth} from '@angular/fire/auth';
|
||||
|
||||
describe('AuthService', () => {
|
||||
const mockAccessRightService = {
|
||||
getUserInfo: () => of({
|
||||
user: 'userid',
|
||||
role: Role.reader
|
||||
})
|
||||
};
|
||||
const mockAngularFireAuth = {
|
||||
auth: {
|
||||
signInWithEmailAndPassword: Promise.resolve({user: {uid: 'userid'}})
|
||||
}
|
||||
};
|
||||
beforeEach(() => TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: AccessRightService, useValue: mockAccessRightService},
|
||||
{provide: AngularFireAuth, useValue: mockAngularFireAuth},
|
||||
]
|
||||
}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: AuthService = TestBed.get(AuthService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should be created', fakeAsync(() => {
|
||||
const service: AuthService = TestBed.get(AuthService);
|
||||
const authSpy = spyOn(TestBed.get(AngularFireAuth).auth, 'signInWithEmailAndPassword');
|
||||
const accessSpy = spyOn(TestBed.get(AccessRightService), 'getUserInfo');
|
||||
service.login$('user', 'pass');
|
||||
|
||||
expect(authSpy).toHaveBeenCalledWith('user', 'pass');
|
||||
tick();
|
||||
expect(accessSpy).toHaveBeenCalledWith('userid');
|
||||
tick();
|
||||
expect(service.user.user).toBe('userid');
|
||||
expect(service.user.user).toBe(Role.reader);
|
||||
|
||||
}));
|
||||
});
|
||||
64
WEB/src/app/services/auth.service.ts
Normal file
64
WEB/src/app/services/auth.service.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {AngularFireAuth} from '@angular/fire/auth';
|
||||
import {from, Observable, of, OperatorFunction} from 'rxjs';
|
||||
import {AccessRightService} from './access-right.service';
|
||||
import {catchError, map, switchMap, tap} from 'rxjs/operators';
|
||||
import {User} from './user.model';
|
||||
import {Role} from './roles.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthService {
|
||||
private _user: User = null;
|
||||
|
||||
constructor(
|
||||
private angularFireAuth: AngularFireAuth,
|
||||
private accessRightService: AccessRightService
|
||||
) {
|
||||
angularFireAuth.authState.subscribe(_ => console.log(_));
|
||||
}
|
||||
|
||||
public login$(user: string, pass: string): Observable<User> {
|
||||
const authPromise = this.angularFireAuth.auth.signInWithEmailAndPassword(user, pass);
|
||||
const auth$ = from(authPromise).pipe(
|
||||
map(_ => _.user.uid),
|
||||
this.processLogin()
|
||||
);
|
||||
|
||||
return auth$;
|
||||
}
|
||||
|
||||
public getUser$(): Observable<User> {
|
||||
if (this._user) {
|
||||
return of(this._user);
|
||||
}
|
||||
|
||||
return this.angularFireAuth.authState.pipe(
|
||||
map(_ => _ ? _.uid : null),
|
||||
this.processLogin()
|
||||
);
|
||||
}
|
||||
|
||||
public userMay$(requestedRole: Role): Observable<boolean> {
|
||||
const allowed$ = this.getUser$().pipe(
|
||||
map(_ => _ ? this.accessRightService.userMay(_.role, requestedRole) : false)
|
||||
);
|
||||
|
||||
return allowed$;
|
||||
}
|
||||
|
||||
private processLogin(): OperatorFunction<string, User> {
|
||||
const self = this;
|
||||
return function (source$: Observable<string>): Observable<User> {
|
||||
return source$.pipe(
|
||||
switchMap(_ => self.accessRightService.getUserInfo(_)),
|
||||
tap((_ => self._user = _)),
|
||||
catchError(_ => {
|
||||
self._user = null;
|
||||
return of(null);
|
||||
})
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
7
WEB/src/app/services/role.definition.ts
Normal file
7
WEB/src/app/services/role.definition.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import {Role} from './roles.model';
|
||||
|
||||
export const RoleDefinitions = [
|
||||
{role: Role.reader, when: [Role.admin, Role.writer, Role.reader]},
|
||||
{role: Role.writer, when: [Role.admin, Role.writer]},
|
||||
{role: Role.admin, when: [Role.admin]},
|
||||
];
|
||||
5
WEB/src/app/services/roles.model.ts
Normal file
5
WEB/src/app/services/roles.model.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum Role {
|
||||
admin = 'admin',
|
||||
reader = 'reader',
|
||||
writer = 'writer'
|
||||
}
|
||||
10
WEB/src/app/services/user.model.ts
Normal file
10
WEB/src/app/services/user.model.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import {Role} from './roles.model';
|
||||
|
||||
export interface User {
|
||||
user: string;
|
||||
role: Role;
|
||||
}
|
||||
|
||||
export interface UserDB {
|
||||
role: Role;
|
||||
}
|
||||
@@ -1,24 +1,4 @@
|
||||
table {
|
||||
border-radius: 8px;
|
||||
background: #fffe;
|
||||
|
||||
tr.selected {
|
||||
background-color: #0002;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
cursor: pointer;
|
||||
background-color: #0001;
|
||||
|
||||
td {
|
||||
color: #ff9900;
|
||||
}
|
||||
}
|
||||
|
||||
td.mat-cell {
|
||||
padding: 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.table-container {
|
||||
overflow: auto;
|
||||
|
||||
@@ -4,7 +4,16 @@
|
||||
|
||||
export const environment = {
|
||||
production: false,
|
||||
api: 'http://test.benjamin-ifland.de'
|
||||
api: 'http://test.benjamin-ifland.de',
|
||||
firebase: {
|
||||
apiKey: 'AIzaSyBcIa6m8F7fT4gRLTx2zcBufUEE81gWVFg',
|
||||
authDomain: 'worshipgenerator.firebaseapp.com',
|
||||
databaseURL: 'https://worshipgenerator.firebaseio.com',
|
||||
projectId: 'worshipgenerator',
|
||||
storageBucket: 'worshipgenerator.appspot.com',
|
||||
messagingSenderId: '317378238562',
|
||||
appId: '1:317378238562:web:552ca27bc5e9086e'
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -2,7 +2,14 @@
|
||||
@import "~@angular/material/prebuilt-themes/indigo-pink.css";
|
||||
|
||||
html {
|
||||
background-image: url(https://images.unsplash.com/photo-1476136236990-838240be4859?ixlib=rb-1.2.1&auto=format&fit=crop&w=2167&q=80);
|
||||
background: #2c3e50; /* fallback for old browsers */
|
||||
background: -webkit-linear-gradient(to top, #2c3e50, #3498db); /* Chrome 10-25, Safari 5.1-6 */
|
||||
background: linear-gradient(to top, #2c3e50, #3498db); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.card-3 {
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -14,53 +21,28 @@ body {
|
||||
}
|
||||
|
||||
.page-container {
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
.card-3;
|
||||
margin: 10px;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
|
||||
.mat-table tbody {
|
||||
table.mat-table thead,
|
||||
table.mat-table tbody {
|
||||
background: none;
|
||||
}
|
||||
|
||||
th.mat-header-cell:first-of-type {
|
||||
border-top-left-radius: 8px;
|
||||
}
|
||||
|
||||
th.mat-header-cell:last-of-type {
|
||||
border-top-right-radius: 8px;
|
||||
}
|
||||
|
||||
.mat-table thead {
|
||||
border-top-right-radius: 8px;
|
||||
border-top-left-radius: 8px;
|
||||
}
|
||||
|
||||
|
||||
&.pinned {
|
||||
padding: 0;
|
||||
max-width: 300px;
|
||||
border-radius: 0;
|
||||
|
||||
th.mat-header-cell:first-of-type {
|
||||
border-top-left-radius: 0;
|
||||
}
|
||||
|
||||
th.mat-header-cell:last-of-type {
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
.mat-table thead {
|
||||
border-top-right-radius: 0;
|
||||
border-top-left-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mat-card {
|
||||
.card-3;
|
||||
width: 600px;
|
||||
border-radius: 8px;
|
||||
background: #fffd;
|
||||
background: #fffa;
|
||||
margin: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -92,7 +74,7 @@ body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table {
|
||||
table.mat-table {
|
||||
width: 100%;
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
@@ -105,4 +87,35 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.mat-paginator {
|
||||
background: #fffa;
|
||||
}
|
||||
|
||||
table.mat-table {
|
||||
background: #fffa;
|
||||
|
||||
tr.selected {
|
||||
background-color: #0002;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
cursor: pointer;
|
||||
background-color: #0001;
|
||||
|
||||
td {
|
||||
color: #ff9900;
|
||||
}
|
||||
}
|
||||
|
||||
tr:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
td.mat-cell {
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
true,
|
||||
"check-space"
|
||||
],
|
||||
"curly": true,
|
||||
"deprecation": {
|
||||
"severity": "warn"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user