some work on sessions

This commit is contained in:
simon987
2019-02-07 22:29:05 -05:00
parent 6ad42366ea
commit f577e76afa
31 changed files with 323 additions and 186 deletions

View File

@@ -1262,7 +1262,6 @@
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
"integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
"dev": true,
"optional": true,
"requires": {
"delegates": "^1.0.0",
"readable-stream": "^2.0.6"
@@ -2516,8 +2515,7 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
"dev": true,
"optional": true
"dev": true
},
"constants-browserify": {
"version": "1.0.0",
@@ -2919,8 +2917,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
"dev": true,
"optional": true
"dev": true
},
"depd": {
"version": "1.1.2",
@@ -3995,8 +3992,7 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"aproba": {
"version": "1.2.0",
@@ -4017,14 +4013,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -4039,20 +4033,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"core-util-is": {
"version": "1.0.2",
@@ -4169,8 +4160,7 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"ini": {
"version": "1.3.5",
@@ -4182,7 +4172,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -4197,7 +4186,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -4205,14 +4193,12 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -4231,7 +4217,6 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -4312,8 +4297,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"object-assign": {
"version": "4.1.1",
@@ -4325,7 +4309,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@@ -4411,8 +4394,7 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -4448,7 +4430,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -4468,7 +4449,6 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -4512,14 +4492,12 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
}
}
},
@@ -4528,7 +4506,6 @@
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
"integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
"dev": true,
"optional": true,
"requires": {
"graceful-fs": "^4.1.2",
"inherits": "~2.0.0",
@@ -4541,7 +4518,6 @@
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
"dev": true,
"optional": true,
"requires": {
"aproba": "^1.0.3",
"console-control-strings": "^1.0.0",
@@ -4579,8 +4555,7 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
"integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
"dev": true,
"optional": true
"dev": true
},
"get-stream": {
"version": "3.0.0",
@@ -4805,8 +4780,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
"dev": true,
"optional": true
"dev": true
},
"has-value": {
"version": "1.0.0",
@@ -5562,8 +5536,7 @@
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
"integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
"dev": true,
"optional": true
"dev": true
},
"is-windows": {
"version": "1.0.2",
@@ -6246,7 +6219,6 @@
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"dev": true,
"optional": true,
"requires": {
"graceful-fs": "^4.1.2",
"parse-json": "^2.2.0",
@@ -6259,8 +6231,7 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true,
"optional": true
"dev": true
}
}
},
@@ -6534,8 +6505,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
"integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
"dev": true,
"optional": true
"dev": true
},
"map-visit": {
"version": "1.0.0",
@@ -7167,7 +7137,6 @@
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"dev": true,
"optional": true,
"requires": {
"are-we-there-yet": "~1.1.2",
"console-control-strings": "~1.1.0",
@@ -8259,7 +8228,6 @@
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
"integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
"dev": true,
"optional": true,
"requires": {
"load-json-file": "^1.0.0",
"normalize-package-data": "^2.3.2",
@@ -8271,7 +8239,6 @@
"resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
"integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
"dev": true,
"optional": true,
"requires": {
"graceful-fs": "^4.1.2",
"pify": "^2.0.0",
@@ -8282,8 +8249,7 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true,
"optional": true
"dev": true
}
}
},
@@ -8292,7 +8258,6 @@
"resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
"integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
"dev": true,
"optional": true,
"requires": {
"find-up": "^1.0.0",
"read-pkg": "^1.0.0"
@@ -8303,7 +8268,6 @@
"resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
"integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
"dev": true,
"optional": true,
"requires": {
"path-exists": "^2.0.0",
"pinkie-promise": "^2.0.0"
@@ -8314,7 +8278,6 @@
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
"integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
"dev": true,
"optional": true,
"requires": {
"pinkie-promise": "^2.0.0"
}
@@ -9605,7 +9568,6 @@
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
"integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
"dev": true,
"optional": true,
"requires": {
"is-utf8": "^0.2.0"
}
@@ -10936,7 +10898,6 @@
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
"dev": true,
"optional": true,
"requires": {
"string-width": "^1.0.2 || 2"
}

View File

@@ -0,0 +1,3 @@
<pre>
{{authService.account | json}}
</pre>

View File

@@ -0,0 +1,17 @@
import {Component, OnInit} from '@angular/core';
import {AuthService} from "../auth.service";
@Component({
selector: 'app-account-details',
templateUrl: './account-details.component.html',
styleUrls: ['./account-details.component.css']
})
export class AccountDetailsComponent implements OnInit {
constructor(private authService: AuthService) {
}
ngOnInit() {
}
}

View File

@@ -1,11 +1,16 @@
import {Injectable} from '@angular/core';
import {HttpClient} from "@angular/common/http";
import {Project} from "./models/project";
import {Credentials} from "./models/credentials";
@Injectable()
export class ApiService {
private url: string = "http://localhost:42901";
private url: string = "http://localhost/api";
private options: {
withCredentials: true,
responseType: "json"
};
constructor(
private http: HttpClient,
@@ -13,22 +18,36 @@ export class ApiService {
}
getLogs() {
return this.http.post(this.url + "/logs", "{\"level\":6, \"since\":1}");
return this.http.post(this.url + "/logs", "{\"level\":6, \"since\":1}", this.options);
}
getProjects() {
return this.http.get(this.url + "/project/list")
return this.http.get(this.url + "/project/list", this.options)
}
getProject(id: number) {
return this.http.get(this.url + "/project/get/" + id)
return this.http.get(this.url + "/project/get/" + id, this.options)
}
createProject(project: Project) {
return this.http.post(this.url + "/project/create", project)
return this.http.post(this.url + "/project/create", project, this.options)
}
updateProject(project: Project) {
return this.http.post(this.url + "/project/update/" + project.id, project)
return this.http.post(this.url + "/project/update/" + project.id, project, this.options)
}
register(credentials: Credentials) {
return this.http.post(this.url + "/register", credentials, this.options)
}
login(credentials: Credentials) {
return this.http.post(this.url + "/login", credentials, this.options)
}
getAccountDetails() {
return this.http.get(this.url + "/account", this.options)
}
}

View File

@@ -9,12 +9,12 @@ import {Title} from "@angular/platform-browser";
import {filter} from "rxjs/operators";
import {TranslateService} from "@ngx-translate/core";
import {LoginComponent} from "./login/login.component";
import {CreateAccountComponent} from "./create-account/create-account.component";
import {AccountDetailsComponent} from "./account-details/account-details.component";
const routes: Routes = [
{path: "log", component: LogsComponent},
{path: "login", component: LoginComponent},
{path: "new_account", component: CreateAccountComponent},
{path: "account", component: AccountDetailsComponent},
{path: "projects", component: ProjectListComponent},
{path: "project/:id", component: ProjectDashboardComponent},
{path: "project/:id/update", component: UpdateProjectComponent},

View File

@@ -4,6 +4,8 @@
<button mat-button [class.mat-accent]="router.url == '/projects'" class="nav-link" [routerLink]="'projects'">{{"nav.project_list" | translate}}</button>
<button mat-button [class.mat-accent]="router.url == '/new_project'" class="nav-link" [routerLink]="'new_project'">{{"nav.new_project" | translate}}</button>
<span class="nav-spacer"></span>
<button mat-button [class.mat-accent]="router.url == '/login'" class="nav-link"
[routerLink]="'login'">{{"nav.login" | translate}}</button>
<mat-form-field [floatLabel]="'never'">
<mat-select [placeholder]="'nav.langSelect' | translate" (selectionChange)="langChange($event)">
<mat-option *ngFor="let lang of langList" [value]="lang.lang">

View File

@@ -19,12 +19,14 @@ import {
MatMenuModule,
MatPaginatorIntl,
MatPaginatorModule,
MatProgressBarModule,
MatSelectModule,
MatSliderModule,
MatSlideToggleModule,
MatSnackBarModule,
MatSortModule,
MatTableModule,
MatTabsModule,
MatToolbarModule,
MatTreeModule
} from "@angular/material";
@@ -41,7 +43,7 @@ import {TranslateLoader, TranslateModule, TranslateService} from "@ngx-translate
import {TranslateHttpLoader} from "@ngx-translate/http-loader";
import {TranslatedPaginator} from "./TranslatedPaginatorConfiguration";
import {LoginComponent} from './login/login.component';
import {CreateAccountComponent} from './create-account/create-account.component';
import {AccountDetailsComponent} from './account-details/account-details.component';
export function createTranslateLoader(http: HttpClient) {
@@ -59,7 +61,7 @@ export function createTranslateLoader(http: HttpClient) {
UpdateProjectComponent,
SnackBarComponent,
LoginComponent,
CreateAccountComponent,
AccountDetailsComponent,
],
imports: [
BrowserModule,
@@ -94,7 +96,9 @@ export function createTranslateLoader(http: HttpClient) {
}
}
),
MatSelectModule
MatSelectModule,
MatProgressBarModule,
MatTabsModule
],
exports: [],

View File

@@ -1,3 +0,0 @@
mat-form-field {
width: 100%;
}

View File

@@ -1,30 +0,0 @@
<div class="small-container">
<mat-card class="mat-elevation-z8">
<mat-card-title>{{"create_account.title" | translate}}</mat-card-title>
<mat-card-content style="padding: 2em 0 1em">
<form>
<mat-form-field appearance="outline" [hideRequiredMarker]="true">
<mat-label>{{"login.username" | translate}}</mat-label>
<mat-hint align="end">{{credentials.username?.length || 0}}/16</mat-hint>
<input maxlength="16" type="text" matInput [(ngModel)]="credentials.username" name="username"
required>
</mat-form-field>
<mat-form-field appearance="outline" [hideRequiredMarker]="true">
<mat-label>{{ "login.password" | translate}}</mat-label>
<input type="password" matInput [(ngModel)]="credentials.password" name="password" required>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{ "login.repeat_password" | translate}}</mat-label>
<input type="password" matInput [(ngModel)]="credentials.repeatPassword" name="password2"
>
</mat-form-field>
</form>
</mat-card-content>
<mat-card-actions>
<button mat-raised-button color="primary" [disabled]="!canCreate()"
(click)="onClick()">{{"create_account.create" | translate}}</button>
</mat-card-actions>
</mat-card>
</div>

View File

@@ -1,28 +0,0 @@
import {Component, OnInit} from '@angular/core';
import {Credentials} from "../models/credentials";
@Component({
selector: 'app-create-account',
templateUrl: './create-account.component.html',
styleUrls: ['./create-account.component.css']
})
export class CreateAccountComponent implements OnInit {
credentials: Credentials = <Credentials>{};
constructor() {
}
ngOnInit() {
}
canCreate(): boolean {
return this.credentials.username && this.credentials.username != "" &&
this.credentials.password == this.credentials.repeatPassword
}
onClick() {
alert("e")
}
}

View File

@@ -12,7 +12,7 @@ import {Router} from "@angular/router";
})
export class CreateProjectComponent implements OnInit {
project = new Project();
project = <Project>{};
constructor(private apiService: ApiService,
private messengerService: MessengerService,

View File

@@ -1,3 +1,8 @@
.mat-form-field {
width: 100%;
}
.pad {
padding-top: 2em;
}

View File

@@ -1,8 +1,8 @@
<div class="small-container">
<mat-card class="mat-elevation-z8">
<mat-card-title>{{"login.title" | translate}}</mat-card-title>
<mat-card-content style="padding: 2em 0 1em">
<form>
<mat-tab-group>
<mat-tab [label]="'login.title' | translate" class="pad">
<mat-form-field appearance="outline">
<mat-label>{{"login.username" | translate}}</mat-label>
<input type="text" matInput [(ngModel)]="credentials.username">
@@ -12,11 +12,32 @@
<mat-label>{{ "login.password" | translate}}</mat-label>
<input type="password" matInput [(ngModel)]="credentials.password">
</mat-form-field>
</form>
</mat-card-content>
<mat-card-actions>
<button mat-raised-button color="primary"
(click)="onClick()">{{"login.login" | translate}}</button>
</mat-card-actions>
<button mat-raised-button color="primary"
(click)="login()">{{"login.login" | translate}}</button>
</mat-tab>
<mat-tab [label]="'create_account.title' | translate" class="pad">
<mat-form-field appearance="outline" [hideRequiredMarker]="true">
<mat-label>{{"login.username" | translate}}</mat-label>
<mat-hint align="end">{{credentials.username?.length || 0}}/16</mat-hint>
<input maxlength="16" type="text" matInput [(ngModel)]="credentials.username" name="username"
required>
</mat-form-field>
<mat-form-field appearance="outline" [hideRequiredMarker]="true">
<mat-label>{{ "login.password" | translate}}</mat-label>
<input type="password" matInput [(ngModel)]="credentials.password" name="password" required>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{ "login.repeat_password" | translate}}</mat-label>
<input type="password" matInput [(ngModel)]="credentials.repeatPassword" name="password2"
>
</mat-form-field>
<button mat-raised-button color="primary" [disabled]="!canCreate()"
(click)="register()">{{"create_account.create" | translate}}</button>
</mat-tab>
</mat-tab-group>
</mat-card>
</div>

View File

@@ -1,5 +1,9 @@
import {Component, OnInit} from '@angular/core';
import {Credentials} from "../models/credentials";
import {ApiService} from "../api.service";
import {MessengerService} from "../messenger.service";
import {Router} from "@angular/router";
import {AuthService} from "../auth.service";
@Component({
selector: 'app-login',
@@ -10,13 +14,34 @@ export class LoginComponent implements OnInit {
credentials: Credentials = <Credentials>{};
constructor() {
constructor(private apiService: ApiService,
private messengerService: MessengerService,
private router: Router,
private authService: AuthService) {
}
ngOnInit() {
}
onClick() {
login() {
this.authService.login(this.credentials)
}
register() {
this.apiService.register(this.credentials)
.subscribe(
() => {
this.router.navigateByUrl("/account")
},
error => {
console.log(error);
this.messengerService.show(error.error.message);
}
)
}
canCreate(): boolean {
return this.credentials.username && this.credentials.username != "" &&
this.credentials.password == this.credentials.repeatPassword
}
}

View File

@@ -1,5 +1,6 @@
import {Component, OnInit, ViewChild} from '@angular/core';
import {ApiService} from "../api.service";
import {LogEntry} from "../models/logentry";
import _ from "lodash"
import * as moment from "moment";

View File

@@ -1,4 +1,4 @@
interface LogEntry {
export interface LogEntry {
level: string,
message: string,
data: any,

View File

@@ -0,0 +1,3 @@
interface Manager {
username: string
}

View File

@@ -1,11 +1,10 @@
export class Project {
public id: number;
public priority: number;
public motd: string;
public name: string;
public clone_url: string;
public git_repo: string;
public version: string;
public public: boolean;
export interface Project {
id: number;
priority: number;
motd: string;
name: string;
clone_url: string;
git_repo: string;
version: string;
public: boolean;
}

View File

@@ -19,6 +19,11 @@
<mat-icon>build</mat-icon>{{"project.update" | translate}}</button>
</div>
</mat-expansion-panel>
<span *ngIf="projects && projects.length == 0">
{{"projects.empty" | translate}}
</span>
<mat-progress-bar mode="indeterminate"
*ngIf="projects == null"></mat-progress-bar>
</mat-accordion>
</mat-card-content>
</mat-card>

View File

@@ -4,7 +4,8 @@
"langSelect": "Language",
"logs": "Logs",
"project_list": "Projects",
"new_project": "New Project"
"new_project": "New Project",
"login": "Login"
},
"logs": {
"filter": "Filter",
@@ -21,7 +22,8 @@
},
"projects": {
"projects": "Projects",
"dashboard": "Dashboard"
"dashboard": "Dashboard",
"empty": "No projects"
},
"title": {
"": "Index",
@@ -30,7 +32,8 @@
"log": "Logs",
"new_project": "New project",
"login": "Login",
"new_account": "Create account"
"new_account": "Create account",
"account": "Account details"
},
"project": {
"name": "Project name",
@@ -51,14 +54,15 @@
"metadata": "Project metadata"
},
"login": {
"title": "Manager login",
"title": "Login",
"login": "Login",
"username": "Username",
"password": "Password",
"repeat_password": "Repeat password"
"repeat_password": "Repeat password",
"create_account": ""
},
"create_account": {
"title": "Create manager account",
"title": "Register",
"create": "Create account"
}
}

View File

@@ -4,7 +4,8 @@
"langSelect": "Langue",
"logs": "Journal",
"project_list": "Projets",
"new_project": "Nouveau projet"
"new_project": "Nouveau projet",
"login": "Ouvrir un session"
},
"logs": {
"filter": "Filtrer",
@@ -21,7 +22,8 @@
},
"projects": {
"projects": "Projets",
"dashboard": "Tableau de bord"
"dashboard": "Tableau de bord",
"empty": "Pas de projets"
},
"title": {
"": "Accueil",
@@ -31,7 +33,8 @@
"new_project": "Nouveau projet",
"update": "Modifier",
"login": "Ouverture de session",
"new_account": "Création de compte"
"new_account": "Création de compte",
"account": "Compte"
},
"project": {
"name": "Nom du projet",
@@ -52,14 +55,15 @@
"metadata": "Métadonnés du projet"
},
"login": {
"title": "Identification - chef de projet",
"title": "Ouvrir un session",
"login": "Ouvrir un session",
"username": "Nom d'utilisateur",
"password": "Mot de passe",
"repeat_password": "Répéter le mot de passe"
"repeat_password": "Répéter le mot de passe",
"create_account": "Créer un compte"
},
"create_account": {
"title": "Création d'un compte de chef de projet",
"title": "Créer un compte",
"create": "Créer un compte"
}
}

View File

@@ -70,3 +70,11 @@ body {
max-width: 720px;
}
}
.mat-tab-label {
width: 100%;
}
.mat-tab-body {
padding-top: 1em;
}