Unverified Commit 82c67942 authored by Michael Adams's avatar Michael Adams Committed by GitHub

Merge pull request #139 from unosquare/unquietwiki-master

.NET Core 2.0 and Angular 6; DefaultDomain option
parents 0c66c86b cfe6bfec
......@@ -22,7 +22,7 @@ bld/
[Bb]in/
[Oo]bj/
# Visual Studo 2015 cache/options directory
# Visual Studo cache/options directory
.vs/
# MSTest test Results
......@@ -200,3 +200,6 @@ FakesAssemblies/
/src/Unosquare.PassCore.Web/.vscode/tasks.json
/src/Unosquare.PassCore.Web/wwwroot/**
/src/Unosquare.PassCore.Web/package-lock.json
# AOT results
aot/*
{
"recommendations": [
"Angular.ng-template",
"chenxsan.vscode-standardjs",
"christian-kohler.npm-intellisense",
"fernandoescolar.vscode-solution-explorer",
"Leopotam.csharpfixformat",
"ms-vscode.csharp",
"oderwat.indent-rainbow",
"pflannery.vscode-versionlens",
"pmneo.tsimporter",
"stringham.angular-template-formatter",
"Tyriar.sort-lines",
"xykong.format-all-files",
]
}
\ No newline at end of file
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/src/Unosquare.PassCore.Web/bin/Debug/netcoreapp2.0/Unosquare.PassCore.Web.dll",
"args": [],
"cwd": "${workspaceFolder}/src/Unosquare.PassCore.Web",
"stopAtEntry": false,
"internalConsoleOptions": "openOnSessionStart",
"launchBrowser": {
"enabled": true,
"args": "${auto-detect-url}",
"windows": {
"command": "cmd.exe",
"args": "/C start ${auto-detect-url}"
},
"osx": {
"command": "open"
},
"linux": {
"command": "xdg-open"
}
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}"
}
,]
}
\ No newline at end of file
{
"csharpfixformat.style.braces.onSameLine": false,
"csharpfixformat.style.spaces.afterBracket": false,
"csharpfixformat.style.spaces.afterParenthesis": false,
"csharpfixformat.style.spaces.beforeBracket": false,
"csharpfixformat.style.spaces.beforeParenthesis": false,
"html.format.enable": false,
"editor.formatOnSave": false,
"editor.formatOnPaste": false,
"htmlhint.options": {
"attr-lowercase": false,
"doctype-first": false
}
}
\ No newline at end of file
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/src/Unosquare.PassCore.Web/Unosquare.PassCore.Web.csproj"
],
"problemMatcher": "$msCompile"
}
]
}
\ No newline at end of file
This diff is collapsed.
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="Rules for StyleCop.Analyzers" Description="Code analysis rules for StyleCop.Analyzers.csproj." ToolsVersion="14.0">
<RuleSet Name="Rules for StyleCop.Analyzers" Description="Code analysis rules for StyleCop.Analyzers.csproj." ToolsVersion="15.0">
<Rules AnalyzerId="AsyncUsageAnalyzers" RuleNamespace="AsyncUsageAnalyzers">
<Rule Id="UseConfigureAwait" Action="Warning" />
</Rules>
......@@ -71,24 +71,23 @@
<Rule Id="IDE0003" Action="None" />
</Rules>
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
<Rule Id="SA1305" Action="Warning" />
<Rule Id="SA1412" Action="Warning" />
<Rule Id="SA1600" Action="None" />
<Rule Id="SA1001" Action="None" />
<Rule Id="SA1003" Action="None" />
<Rule Id="SA1009" Action="None" />
<Rule Id="SA1028" Action="None" />
<Rule Id="SA1011" Action="None" />
<Rule Id="SA1012" Action="None" />
<Rule Id="SA1013" Action="None" />
<Rule Id="SA1021" Action="None" />
<Rule Id="SA1028" Action="None" />
<Rule Id="SA1101" Action="None" />
<Rule Id="SA1208" Action="None" />
<Rule Id="SA1116" Action="None" />
<Rule Id="SA1121" Action="None" />
<Rule Id="SA1124" Action="None" />
<Rule Id="SA1123" Action="None" />
<Rule Id="SA1124" Action="None" />
<Rule Id="SA1200" Action="Error" />
<Rule Id="SA1208" Action="None" />
<Rule Id="SA1210" Action="None" />
<Rule Id="SA1305" Action="Warning" />
<Rule Id="SA1308" Action="None" />
<Rule Id="SA1309" Action="None" />
<Rule Id="SA1402" Action="None" />
......@@ -96,8 +95,9 @@
<Rule Id="SA1503" Action="None" />
<Rule Id="SA1507" Action="Error" />
<Rule Id="SA1516" Action="None" />
<Rule Id="SA1633" Action="None" />
<Rule Id="SA1609" Action="None" />
<Rule Id="SA1600" Action="None" />
<Rule Id="SA1623" Action="None" />
<Rule Id="SA1633" Action="None" />
<Rule Id="SA1652" Action="None" />
</Rules>
</RuleSet>
\ No newline at end of file
{
"lockfileVersion": 1
}
import { NgModule } from '@angular/core';
import {
MatButtonModule,
MatIconModule,
MatCardModule,
MatDialogModule,
MatIconModule,
MatInputModule,
MatProgressBarModule,
MatTooltipModule,
MatToolbarModule,
MatSnackBarModule,
MatDialogModule
MatToolbarModule,
MatTooltipModule
} from '@angular/material';
import { NgModule } from '@angular/core';
@NgModule({
imports: [
......@@ -36,4 +35,4 @@ import {
MatDialogModule
]
})
export class MaterialModule {}
\ No newline at end of file
export class MaterialModule { }
\ No newline at end of file
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app.routing-module';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { BrowserModule } from '@angular/platform-browser';
import { ChangePasswordComponent } from './change-password/app.change-password';
import { DialogOverview } from './dialog/app.dialog';
import { FlexLayoutModule } from '@angular/flex-layout';
import { FooterComponent } from './footer/app.footer';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { FlexLayoutModule } from "@angular/flex-layout";
import { HttpClientModule } from '@angular/common/http';
import { MatDialogModule } from '@angular/material/dialog';
import { MaterialModule } from './app.material-module';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { NgModule } from '@angular/core';
import { RecaptchaModule } from 'ng-recaptcha';
import { RecaptchaFormsModule } from 'ng-recaptcha/forms';
import { AppRoutingModule } from './app.routing-module';
import { MaterialModule } from './app.material-module';
import ChangePasswordComponent from './change-password/app.change-password';
import FooterComponent from './footer/app.footer';
import DialogOverview from './dialog/app.dialog';
import { RouterModule } from '@angular/router';
@NgModule({
bootstrap: [ChangePasswordComponent],
declarations: [
ChangePasswordComponent,
FooterComponent,
DialogOverview
DialogOverview,
FooterComponent
],
entryComponents: [DialogOverview],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
BrowserModule,
FormsModule,
MaterialModule,
HttpModule,
FlexLayoutModule,
HttpClientModule,
MaterialModule,
MatDialogModule,
MatSnackBarModule,
RecaptchaModule.forRoot(),
RecaptchaFormsModule,
ReactiveFormsModule,
AppRoutingModule
RouterModule
],
providers: [],
bootstrap: [ChangePasswordComponent],
entryComponents: [DialogOverview]
exports: [
]
})
export class AppModule { }
export class AppModule { }
\ No newline at end of file
import { NgModule } from '@angular/core';
import { ChangePasswordComponent } from './change-password/app.change-password';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import ChangePasswordComponent from './change-password/app.change-password';
const appRoutes: Routes = [{ path: '', component: ChangePasswordComponent }];
const appRoutes: Routes = [
{ path: '', component: ChangePasswordComponent },
];
// AOT
// export const appRoutes: Routes = [{ path: '', loadChildren: './change-password/app.change-password#ChangePasswordComponent' }];
@NgModule({
imports: [
RouterModule.forRoot(appRoutes),
],
exports: [
RouterModule,
]
imports: [RouterModule.forRoot(appRoutes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
\ No newline at end of file
......@@ -26,7 +26,7 @@
.margin-top {
margin-top: 20px;
}
}
form {
width: 95%;
......@@ -46,7 +46,7 @@ mat-hint {
width: 100%;
}
mat-placeholder {
mat-label {
top: 15px;
position: relative;
}
......@@ -58,14 +58,11 @@ mat-progress-bar {
@media screen and (min-width: 600px) {
.margin-top {
margin-top: 10px;
}
form{
}
form {
width: 85%;
}
button {
width: 40%;
}
}
\ No newline at end of file
import { Title } from '@angular/platform-browser';
import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http';
import { MatSnackBar, MatDialog } from '@angular/material';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Params } from '@angular/router';
import ViewOptions from '../models/view-options.model';
import Alerts from '../models/alerts.model';
import Recaptcha from '../models/recaptcha.model';
import ChangePasswordForm from '../models/change-password-form.model';
import Result from '../models/result-data.model';
import PasswordModel from '../models/password.model';
import DialogOverview from '../dialog/app.dialog'
import PasswordValidator from '../helpers/passwordValidator';
import PasswordStrength from '../helpers/passwordStrength';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/operator/map';
import { Alerts } from '../models/alerts.model';
import { ChangePasswordForm } from '../models/change-password-form.model';
import { Component, OnInit } from '@angular/core';
import { DialogOverview } from '../dialog/app.dialog';
import { HttpClient } from '@angular/common/http';
import { MatDialog, MatSnackBar } from '@angular/material';
import { PasswordMatch } from '../helpers/passwordMatch';
import { PasswordModel } from '../models/password.model';
import { PasswordStrength } from '../helpers/passwordStrength';
import { Recaptcha } from '../models/recaptcha.model';
import { Title } from '@angular/platform-browser';
import { ViewOptions } from '../models/view-options.model';
import { ErrorsPasswordForm } from '../models/errors-password-form.model';
const emailRegex = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
const usernameRegex = /^[a-z0-9._-]{3,15}$/; // Maybe find a better regex
@Component({
selector: 'app-root',
templateUrl: './change-password.html',
styleUrls: ['./app.change-password.css']
styleUrls: ['./app.change-password.css'],
providers: [ChangePasswordComponent],
viewProviders: [ViewOptions]
})
export default class ChangePasswordComponent implements OnInit {
subscription: Subscription;
// Form Controls
FormGroup = new FormGroup({
username: new FormControl('', [Validators.required, Validators.pattern(emailRegex)]),
currentPassword: new FormControl('', [Validators.required]),
newPassword: new FormControl('', [Validators.required]),
newPasswordVerify: new FormControl('', [Validators.required])
}, PasswordValidator.MatchPassword);
// Variables
ViewOptions: ViewOptions;
ResultData: Result;
Loading: boolean = false;
export class ChangePasswordComponent implements OnInit {
// Constructor: parent "this" doesn't work here
constructor(
private http: HttpClient,
private snackBar: MatSnackBar,
private titleService: Title,
private dialog: MatDialog,
private r: ActivatedRoute
) { }
// Properties
color: string = 'warn';
ErrorAlertMessage: string = '';
FormData: PasswordModel;
color: string = 'warn';
Loading: boolean = false;
value: number = 0;
ViewOptions: ViewOptions;
constructor(private http: Http, private snackBar: MatSnackBar,
private titleService: Title, public dialog: MatDialog, private r: ActivatedRoute) {
// Form Controls
FormGroup = new FormGroup({
username: new FormControl('', [Validators.required]),
currentPassword: new FormControl('', [Validators.required]),
newPassword: new FormControl('', [Validators.required]),
newPasswordVerify: new FormControl('', [Validators.required])
}, PasswordMatch);
// Angular "OnInit": happens only on first page load
ngOnInit() {
this.FormData = new PasswordModel;
this.ViewOptions = new ViewOptions;
this.ViewOptions.alerts = new Alerts;
this.ViewOptions.recaptcha = new Recaptcha;
this.ViewOptions.changePasswordForm = new ChangePasswordForm;
this.FormGroup.valueChanges.subscribe(data => {
if (data.newPassword != null)
this.changeProgressBar(PasswordStrength.measureStrength(data.newPassword));
this.ViewOptions.errorsPasswordForm = new ErrorsPasswordForm;
this.r.queryParams.subscribe((params: Params) => {
let userId = params['userName'] || '';
this.GetData(userId);
});
}
ngOnInit(): void {
this.subscription = this.r.queryParams.subscribe((params: Params) => {
let userId = params['userName'] || "";
this.GetData(userId);
this.FormGroup.valueChanges.subscribe(data => {
if (data.newPassword != null)
this.changeProgressBar(PasswordStrength.measureStrength(data.newPassword));
});
}
private changeProgressBar(strength: number) {
// Progress bar for password strength
changeProgressBar(strength: number) {
this.value = strength;
if (strength < 33) {
this.color = 'warn';
......@@ -76,20 +81,24 @@ export default class ChangePasswordComponent implements OnInit {
}
}
private openSnackBar(message: string, action: string) {
// Uses MatSnackBar
openSnackBar(message: string, action: string) {
this.snackBar.open(message, action, {
duration: 5000
});
}
private openDialog(title: string, message: string) {
let refDialog = this.dialog.open(DialogOverview, {
width: '300px',
data: { Title: title, Message: message }
// Uses MatDialogRef
openDialog(title: string, message: string) {
this.dialog.open(DialogOverview, {
width: '300px',
height: '300px',
data: { Title: title, Message: message }
});
}
private clean(submited: string) {
// Reset form
clean(submited: string) {
this.Loading = false;
this.ErrorAlertMessage = '';
this.color = 'warn';
......@@ -109,41 +118,49 @@ export default class ChangePasswordComponent implements OnInit {
}
}
private GetData(queryParam: string): void {
this.FormData.Username = queryParam;
this.http.get('api/password').subscribe(values => {
this.ViewOptions = values.json();
this.titleService.setTitle(this.ViewOptions.changePasswordTitle + " - " + this.ViewOptions.applicationTitle);
if (this.ViewOptions.recaptcha.isEnabled) {
this.FormGroup.addControl('reCaptcha', new FormControl('', [Validators.required]));
const sp = document.createElement('script');
sp.type = 'text/javascript';
sp.async = true;
sp.defer = true;
sp.src = 'https://www.google.com/recaptcha/api.js?onload=vcRecaptchaApiLoaded&render=explicit&hl=' + this.ViewOptions.recaptcha.languageCode;
}
});
// Get data from the form
GetData(queryParam: string) {
this.FormData.Username = queryParam;
this.http.get<ViewOptions>('api/password')
.subscribe(data => {
this.ViewOptions = data;
this.titleService.setTitle(this.ViewOptions.changePasswordTitle + ' - ' + this.ViewOptions.applicationTitle);
if (this.ViewOptions.recaptcha.isEnabled) {
this.FormGroup.addControl('reCaptcha', new FormControl('', [Validators.required]));
const sp = document.createElement('script');
sp.type = 'text/javascript';
sp.async = true;
sp.defer = true;
sp.src = 'https://www.google.com/recaptcha/api.js?onload=vcRecaptchaApiLoaded&render=explicit&hl=' + this.ViewOptions.recaptcha.languageCode;
}
if (this.ViewOptions.defaultDomain) {
this.FormGroup.get('username').setValidators(Validators.pattern(usernameRegex));
} else {
this.FormGroup.get('username').setValidators(Validators.pattern(emailRegex));
}
});
}
private SetRecaptchaResponse(captchaResponse: string) {
// Uses RecaptchaModule / RecaptchaFormsModule
SetRecaptchaResponse(captchaResponse: string) {
this.FormData.Recaptcha = captchaResponse;
}
// Form submission
Submit() {
this.Loading = true;
this.http.post('api/password', this.FormData)
.subscribe((response) => {
this.http.post('api/password', this.FormData).subscribe(
response => {
this.openDialog(this.ViewOptions.alerts.successAlertTitle, this.ViewOptions.alerts.successAlertBody);
this.clean('success');
}, (error) => {
this.ResultData = error.json() as Result;
this.ResultData.errors.map(errData => {
this.ErrorAlertMessage += errData.message;
});
},
errorResponse => {
errorResponse.error.errors.forEach((error: any) => {
this.ErrorAlertMessage += error.message;
});
this.openSnackBar(this.ErrorAlertMessage, 'OK');
this.clean('error');
});
}
);
}
}
\ No newline at end of file
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
import { Component, Inject } from '@angular/core';
@Component({
providers: [DialogOverview],
selector: 'dialog-overview',
templateUrl: './dialog.html'
})
export default class DialogOverview {
export class DialogOverview {
constructor(
public dialogRef: MatDialogRef<DialogOverview>,
......
......@@ -4,6 +4,8 @@
<h3>{{data.Message}}</h3>
</div>
<div mat-dialog-actions fxLayoutAlign="end">
<button mat-button mat-raised-button color="primary" (click)="close()" tabindex="2">Ok</button>
<button mat-button mat-raised-button color="primary" (click)="close()" tabindex="2">
Ok
</button>
</div>
</div>
\ No newline at end of file
import { Component } from '@angular/core';
@Component({
selector: 'app-footer',
templateUrl: './footer.html',