diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..881ac7f --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,37 @@ +name: Build Docker Image + +on: + push: + branches: [ main, master ] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + + - name: Copy Repo Files + uses: actions/checkout@master + + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Login to Portus + uses: docker/login-action@v1 + with: + registry: https://flotte-docker-registry.spdns.org/ + username: ${{ secrets.PORTUS_USERNAME }} + password: ${{ secrets.PORTUS_PASSWORD }} + + - name: Build and push + uses: docker/build-push-action@v2 + with: + context: . + file: ./Dockerfile + platforms: linux/amd64 + push: true + tags: | + flotte-docker-registry.spdns.org/frontend-server:latest diff --git a/Dockerfile b/Dockerfile index a33ada4..a4bc160 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,7 @@ FROM node:14.14.0-alpine3.10 AS builder WORKDIR / COPY package.json package-lock.json ./ +RUN apk add --no-cache python3 build-base RUN npm install && npm install -g @angular/cli && mkdir frontend RUN mv node_modules ./frontend WORKDIR /frontend @@ -12,7 +13,7 @@ FROM golang:1.13.4-alpine as builder2 ARG ARCH=amd64 RUN apk add git WORKDIR / -COPY --from=builder /frontend/dist /dist +COPY --from=builder /frontend/dist /dist RUN go get github.com/rakyll/statik RUN statik --src=/dist/flotte-frontend COPY *.go *.sum *.mod / diff --git a/README.md b/README.md index eadd3e0..e733c3e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # FlotteFrontend -[![Build Status](https://travis-ci.com/fLotte-meets-HWR-DB/frontend.svg?token=YfRmpHAXqyUafCgSEexw&branch=master)](https://travis-ci.com/fLotte-meets-HWR-DB/frontend) +![Build Docker Image](https://github.com/fLotte-meets-HWR-DB/frontend/workflows/Build%20Docker%20Image/badge.svg) This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.0.8. @@ -59,4 +59,4 @@ We can't pass it in an other form, else it would not be possible to pass a value If the backend url changes, it has to be changed in the following files: - ./src/environments/ - ./apollo.config.js for autocompletion when writing queries etc. -- ./codegen.yml for graphQL codegen (to generate schema, types etc.) \ No newline at end of file +- ./codegen.yml for graphQL codegen (to generate schema, types etc.) diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 93302a0..b4c7ed2 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -12,10 +12,12 @@ import { LendingStationsComponent } from './pages/tables/lending-stations/lendin import { ParticipantsComponent } from './pages/tables/participants/participants.component'; import { TimeFramesComponent } from './pages/tables/time-frames/time-frames.component'; import {AuthGuard} from './helper/auth.guard'; +import { ProfileComponent } from './pages/profile/profile.component'; const routes: Routes = [ { path: 'login', component: LoginComponent }, { path: 'tableOverview', component: TableOverviewComponent, canActivate: [AuthGuard]}, + { path: 'profile', component: ProfileComponent, canActivate: [AuthGuard]}, { path: 'table/bikes', component: BikesComponent, canActivate: [AuthGuard] }, { path: 'bike/:id', component: BikeComponent, canActivate: [AuthGuard] }, { path: 'table/participants', component: ParticipantsComponent, canActivate: [AuthGuard] }, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 6ab213a..9505330 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -65,6 +65,7 @@ import { DateRangeCellComponent } from './components/tableComponents/date-range- import { SelectObjectDialogComponent } from './components/select-object-dialog/select-object-dialog.component'; import { AutocompleteSelectComponent } from './components/autocomplete-select/autocomplete-select.component'; import { LendingStationComponent } from './pages/dataPages/lending-station/lending-station.component'; +import { ProfileComponent } from './pages/profile/profile.component'; @@ -94,6 +95,7 @@ import { LendingStationComponent } from './pages/dataPages/lending-station/lendi SelectObjectDialogComponent, AutocompleteSelectComponent, LendingStationComponent, + ProfileComponent, ], imports: [ BrowserModule, diff --git a/src/app/components/sidenav-profile/sidenav-profile.component.html b/src/app/components/sidenav-profile/sidenav-profile.component.html index 832786d..275b3cd 100644 --- a/src/app/components/sidenav-profile/sidenav-profile.component.html +++ b/src/app/components/sidenav-profile/sidenav-profile.component.html @@ -1,4 +1,4 @@ -
+

{{name}}

{{email}}

diff --git a/src/app/pages/profile/profile.component.html b/src/app/pages/profile/profile.component.html index ecca39d..1199841 100644 --- a/src/app/pages/profile/profile.component.html +++ b/src/app/pages/profile/profile.component.html @@ -1,4 +1,96 @@ - +

Profil aktualisieren

+ + + Aktuelles Passwort + + + Bitte geben Sie Ihr Passwort ein. + + + + + + + + Neues Passwort + + + Bitte geben Sie Ihr neues Passwort ein. + + + Das Passwort muss mindestens aus {{minPw}} Zeichen bestehen + + + + + Passwort wiederholen + + + Bitte wiederholen sie ihr Passwort + + + Das Passwort stimmt nicht überein + + + + + + +
+ + + + diff --git a/src/app/pages/profile/profile.component.scss b/src/app/pages/profile/profile.component.scss index e69de29..6d6c43e 100644 --- a/src/app/pages/profile/profile.component.scss +++ b/src/app/pages/profile/profile.component.scss @@ -0,0 +1,27 @@ +#login-form { + display: flex; + flex-direction: column; + max-width: 32em; + min-width: 5em; + margin: auto; + margin-top: 3em; + padding: 1em; + .mat-form-field { + margin: 0.5em 0; + } + #loading-bar { + margin-bottom: 1em; + } + .login-error-message { + margin-top: 0.5em; + } +} + +hr { + color: #7fc600; + width: 100%; +} + +.currentPW { + margin-bottom: 3rem !important; +} \ No newline at end of file diff --git a/src/app/pages/profile/profile.component.ts b/src/app/pages/profile/profile.component.ts index b970ac6..26f0dc0 100644 --- a/src/app/pages/profile/profile.component.ts +++ b/src/app/pages/profile/profile.component.ts @@ -1,4 +1,7 @@ import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { AuthService } from 'src/app/services/auth.service'; import { BikesService } from '../../services/bikes.service'; @Component({ @@ -7,149 +10,63 @@ import { BikesService } from '../../services/bikes.service'; styleUrls: ['./profile.component.scss'], }) export class ProfileComponent implements OnInit { - columnInfo = [ - { - dataPath: 'name', - translation: 'Name', - sticky: true, - link: (row: any) => { - return '/bike/' + row.id; - }, - }, - { dataPath: 'id', translation: 'ID', readonly: true }, - { dataPath: 'group', translation: 'Gruppe' }, - { dataPath: 'modelName', translation: 'Modell' }, - { - dataPath: 'insuranceData.billing', - translation: 'Versicherung Abrechnung', - }, - { dataPath: 'insuranceData.hasFixedRate', translation: 'Pauschale j/n' }, - { dataPath: 'insuranceData.fixedRate', translation: 'Pauschale Betrag' }, - { dataPath: 'insuranceData.name', translation: 'Versicherer' }, - { dataPath: 'insuranceData.benefactor', translation: 'Kostenträger' }, - { dataPath: 'insuranceData.noPnP', translation: 'Nr. P&P' }, - { - dataPath: 'insuranceData.maintenanceResponsible', - translation: 'Wartung zuständig', - }, - { - dataPath: 'insuranceData.maintenanceBenefactor', - translation: 'Wartung Kostenträger', - }, - { - dataPath: 'insuranceData.maintenanceAgreement', - translation: 'Wartungsvereinbarung', - }, - { - dataPath: 'insuranceData.projectAllowance', - translation: 'Projektzuschuss', - }, - { dataPath: 'insuranceData.notes', translation: 'Sonstiges' }, - { dataPath: 'dimensionsAndLoad.bikeLength', translation: 'Länge' }, - { dataPath: 'dimensionsAndLoad.bikeWeight', translation: 'Gewicht' }, - { dataPath: 'dimensionsAndLoad.bikeHeight', translation: 'Höhe' }, - { dataPath: 'dimensionsAndLoad.bikeWidth', translation: 'Breite' }, - { dataPath: 'dimensionsAndLoad.boxHeightRange', translation: 'Boxhöhe' }, - { dataPath: 'dimensionsAndLoad.boxLengthRange', translation: 'Boxlänge' }, - { dataPath: 'dimensionsAndLoad.boxWidthRange', translation: 'Boxbreite' }, - { - dataPath: 'dimensionsAndLoad.hasCoverBox', - translation: 'Boxabdeckung j/n', - }, - { dataPath: 'dimensionsAndLoad.lockable', translation: 'Box abschließbar' }, - { - dataPath: 'dimensionsAndLoad.maxWeightBox', - translation: 'max Zuladung Box', - }, - { - dataPath: 'dimensionsAndLoad.maxWeightLuggageRack', - translation: 'max Zuladung Gepäckträger', - }, - { - dataPath: 'dimensionsAndLoad.maxWeightTotal', - translation: 'max Gesamtgewicht', - }, - { dataPath: 'numberOfChildren', translation: 'Anzahl Kinder' }, - { dataPath: 'numberOfWheels', translation: 'Anzahl Räder' }, - { dataPath: 'forCargo', translation: 'für Lasten j/n' }, - { dataPath: 'forChildren', translation: 'für Kinder j/n' }, - { dataPath: 'security.frameNumber', translation: 'Rahmennummer' }, - { dataPath: 'security.adfcCoding', translation: 'ADFC Codierung' }, - { - dataPath: 'security.keyNumberAXAChain', - translation: 'Schlüsselnrummer Rahmenschloss', - }, - { - dataPath: 'security.keyNumberFrameLock', - translation: 'Schlüsselnrummer AXA-Kette', - }, - { dataPath: 'security.policeCoding', translation: 'Polizei Codierung' }, - { dataPath: 'technicalEquipment.bicycleShift', translation: 'Schaltung' }, - { dataPath: 'technicalEquipment.isEBike', translation: 'E-Bike j/n' }, - { - dataPath: 'technicalEquipment.hasLightSystem', - translation: 'Lichtanlage j/n', - }, - { - dataPath: 'technicalEquipment.specialFeatures', - translation: 'Besonderheiten', - }, - { dataPath: 'stickerBikeNameState', translation: 'Aufkleber Status' }, - { dataPath: 'note', translation: 'Aufkleber Kommentar' }, - { dataPath: 'taxes.costCenter', translation: 'Steuern Kostenstelle' }, - { - dataPath: 'taxes.organisationArea', - translation: 'Steuern Vereinsbereich', - }, - { dataPath: 'provider.id', translation: '' }, - { dataPath: 'provider.formName', translation: '' }, - { dataPath: 'provider.privatePerson.id', translation: '' }, - { dataPath: 'provider.privatePerson.person.id', translation: '' }, - { dataPath: 'provider.privatePerson.person.name', translation: '' }, - { dataPath: 'provider.privatePerson.person.firstName', translation: '' }, - { - dataPath: 'provider.privatePerson.person.contactInformation.email', - translation: '', - }, - { dataPath: 'lendingStation.id', translation: '' }, - { dataPath: 'lendingStation.name', translation: '' }, - { dataPath: 'lendingStation.address.number', translation: '' }, - { dataPath: 'lendingStation.address.street', translation: '' }, - { dataPath: 'lendingStation.address.zip', translation: '' }, - ]; - dataService: any; + minPw: number = 8; + email = new FormControl('', [Validators.required, Validators.email]); + password = new FormControl('', [Validators.required]); + passwordNew = new FormControl('', [Validators.required,Validators.minLength(this.minPw)]); + passwordNew2 = new FormControl('', [Validators.required]); + pwGroup: FormGroup; + hide = true; + loading = false; + errorOccurred = false; + errorMessage = ''; - tableDataGQLType: string = 'CargoBike'; - tableDataGQLCreateInputType: string = 'CargoBikeCreateInput'; - tableDataGQLUpdateInputType: string = 'CargoBikeUpdateInput'; + returnUrl : string; - headline = 'Lastenräder'; + constructor(private authService: AuthService, private router: Router, private route: ActivatedRoute, private formBuilder : FormBuilder) {} - loadingRowIds: string[] = []; - constructor(private bikesService: BikesService) {} - - ngOnInit() { - this.dataService = this.bikesService; - } - - create(object: { currentId: string; row: any }) { - this.bikesService.createBike(object.currentId, { bike: object.row }); - } - - lock(row: any) { - this.bikesService.lockBike({ id: row.id }); - } - - save(row: any) { - this.bikesService.updateBike({ bike: row }); + ngOnInit(): void { + this.pwGroup = this.formBuilder.group({ + passwordNew: this.passwordNew, + passwordNew2: this.passwordNew2 + }, {validator: passwordMatchValidator}); } - cancel(row: any) { - this.bikesService.unlockBike({ id: row.id }); + onPasswordInput() { + if (this.pwGroup.hasError('passwordMismatch')) + this.passwordNew2.setErrors([{'passwordMismatch': true}]); + else + this.passwordNew2.setErrors(null); } - delete(row: any) { - this.bikesService.deleteBike({ id: row.id }); + login() { + this.errorMessage = ''; + this.errorOccurred = false; + if (this.email.invalid || this.password.invalid) { + return; + } + this.loading = true; + this.authService + .login(this.email.value, this.password.value) + .subscribe( + () => this.router.navigateByUrl(this.returnUrl), + (error) => { + this.errorOccurred = true; + this.errorMessage = + error.error.message || + 'Ein Fehler bei Einloggen ist aufgetreten. Überprüfen Sie Ihre Internetverbindung oder versuchen Sie es später erneut.'; + } + ) + .add(() => { + this.loading = false; + }); } } + +export const passwordMatchValidator: ValidatorFn = (formGroup: FormGroup): ValidationErrors | null => { + if (formGroup.get('passwordNew').value === formGroup.get('passwordNew2').value) + return null; + else + return {passwordMismatch: true}; +};