Fixed minor issues + added language support
This commit is contained in:
@@ -82,6 +82,8 @@ public class ProjectApi : IProjectApi {
|
||||
await _docker.DeleteContainer(project.ContainerName);
|
||||
|
||||
await _proxy.RemoveLocation(project.ProxyId, project.CertificateId);
|
||||
|
||||
Directory.Delete(_options.Root + projectId, true);
|
||||
|
||||
_context.Projects.Remove(project);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Text;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
||||
using Microsoft.Extensions.Options;
|
||||
using ProjectManager.Backend.Entities;
|
||||
|
||||
@@ -88,6 +88,16 @@ public class ProjectController : ControllerBase {
|
||||
return Redirect($"http://{_options.Host}:{project.Port}/_/");
|
||||
}
|
||||
|
||||
[Authorized]
|
||||
[HttpGet("{projectId}/url/string")]
|
||||
public IActionResult GetProjectUrlHead(string projectId) {
|
||||
var project = _projects.GetProject(projectId);
|
||||
if (project == null) return NotFound();
|
||||
if (project.OwnerId != _context.UserId) return Unauthorized();
|
||||
if (_options.Enable) return Ok(new {url = $"https://{projectId}.{_options.Domain}/_/"});
|
||||
return Ok(new {url = $"http://{_options.Host}:{project.Port}/_/"});
|
||||
}
|
||||
|
||||
[Authorized]
|
||||
[HttpGet("{projectId}/start")]
|
||||
public async Task<IActionResult> StartProject(string projectId) {
|
||||
|
||||
@@ -36,7 +36,7 @@ if (app.Environment.IsDevelopment()) {
|
||||
|
||||
app.UseCors(
|
||||
options => options
|
||||
.WithOrigins(new []{app.Configuration.GetSection("Frontend").Get<string>() ?? ""})
|
||||
.WithOrigins(app.Configuration.GetSection("Frontend").Get<string>() ?? "")
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials()
|
||||
@@ -45,5 +45,10 @@ app.UseCors(
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
app.MapGet("", async context => {
|
||||
context.Response.StatusCode = StatusCodes.Status200OK;
|
||||
await context.Response.BodyWriter.WriteAsync("Ok"u8.ToArray());
|
||||
await context.Response.BodyWriter.CompleteAsync();
|
||||
});
|
||||
|
||||
app.Run();
|
||||
@@ -3,7 +3,7 @@ import 'zone.js/node';
|
||||
import { APP_BASE_HREF } from '@angular/common';
|
||||
import { ngExpressEngine } from '@nguniversal/express-engine';
|
||||
import * as express from 'express';
|
||||
import { existsSync } from 'fs';
|
||||
import { existsSync, readdirSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { AppServerModule } from './src/main.server';
|
||||
@@ -25,11 +25,16 @@ export function app(): express.Express {
|
||||
// Example Express Rest API endpoints
|
||||
// server.get('/api/**', (req, res) => { });
|
||||
server.get('/backend', (req, res) => {
|
||||
let backend = process.env['BACKEND']
|
||||
let backend = process.env['BACKEND'] || "http://localhost:5110/";
|
||||
if (!backend?.endsWith("/")) backend += "/";
|
||||
res.json({url: backend});
|
||||
});
|
||||
|
||||
server.get('/lang', (req, res) => {
|
||||
const files: string[] = readdirSync(distFolder + "/assets/languages");
|
||||
res.json({files});
|
||||
})
|
||||
|
||||
// Serve static files from /browser
|
||||
server.get('*.*', express.static(distFolder, {
|
||||
maxAge: '1y'
|
||||
|
||||
@@ -26,6 +26,7 @@ import {MatSnackBarModule} from "@angular/material/snack-bar";
|
||||
import {MatTooltipModule} from "@angular/material/tooltip";
|
||||
import { TextDialogComponent } from './components/text-dialog/text-dialog.component';
|
||||
import { ProjectComponent } from './sites/project/project.component';
|
||||
import {MatMenuModule} from "@angular/material/menu";
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -40,7 +41,7 @@ import { ProjectComponent } from './sites/project/project.component';
|
||||
ProjectComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule.withServerTransition({ appId: 'serverApp' }),
|
||||
BrowserModule.withServerTransition({appId: 'serverApp'}),
|
||||
AppRoutingModule,
|
||||
BrowserAnimationsModule,
|
||||
HttpClientModule,
|
||||
@@ -57,7 +58,8 @@ import { ProjectComponent } from './sites/project/project.component';
|
||||
FormsModule,
|
||||
MatDialogModule,
|
||||
MatSnackBarModule,
|
||||
MatTooltipModule
|
||||
MatTooltipModule,
|
||||
MatMenuModule
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
|
||||
@@ -8,24 +8,24 @@
|
||||
<mat-selection-list id="actions">
|
||||
<mat-list-item routerLink="/dashboard">
|
||||
<mat-icon matListItemIcon>dashboard</mat-icon>
|
||||
<div matListItemTitle>Übersicht</div>
|
||||
<div matListItemTitle>{{langs.currentLang?.dashboard}}</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item (click)="logout()">
|
||||
<mat-icon matListItemIcon>logout</mat-icon>
|
||||
<div matListItemTitle>Ausloggen</div>
|
||||
<div matListItemTitle>{{langs.currentLang?.logout}}</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item (click)="createProject()">
|
||||
<mat-icon matListItemIcon>add</mat-icon>
|
||||
<div matListItemTitle>Neues Projekt</div>
|
||||
<div matListItemTitle>{{langs.currentLang?.createProject}}</div>
|
||||
</mat-list-item>
|
||||
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<div mat-subheader>Projekte</div>
|
||||
<mat-list-item *ngFor="let project of projects.projects" (click)="openProject(project.projectId)">
|
||||
<div mat-subheader>{{langs.currentLang?.projects}}</div>
|
||||
<mat-list-item *ngFor="let project of projects.projects" (click)="openProject(project.projectId)" class="project">
|
||||
<mat-icon matListItemIcon>open_in_new</mat-icon>
|
||||
<div matListItemTitle>{{project.name}}</div>
|
||||
<div matListItemLine [ngClass]="{startColor: project.running, stopColor: !project.running}">{{project.running ? 'Läuft' : 'Gestoppt'}}</div>
|
||||
<div matListItemLine [ngClass]="{startColor: project.running, stopColor: !project.running}">{{project.running ? langs.currentLang?.running : langs.currentLang?.stopped}}</div>
|
||||
</mat-list-item>
|
||||
</mat-selection-list>
|
||||
|
||||
@@ -37,8 +37,16 @@
|
||||
<span>Project Manager</span>
|
||||
|
||||
<section id="top-actions">
|
||||
<button mat-icon-button (click)="onModeChange()" matTooltip="Farbmodus ändern"><mat-icon>{{darkMode ? 'light_mode' : 'dark_mode'}}</mat-icon></button>
|
||||
<button mat-icon-button routerLink="/profile" *ngIf="showActions()" matTooltip="Profil Einstellungen"><mat-icon>account_circle</mat-icon></button>
|
||||
<button mat-icon-button [matMenuTriggerFor]="menu" [matTooltip]="langs.currentLang?.selectLang"><mat-icon>language</mat-icon></button>
|
||||
<mat-menu #menu>
|
||||
<button mat-menu-item (click)="langs.setLanguage(language)" *ngFor="let language of langs.allLanguages">
|
||||
<mat-icon>language</mat-icon>
|
||||
<span>{{language}}</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
|
||||
<button mat-icon-button (click)="onModeChange()" [matTooltip]="langs.currentLang?.design"><mat-icon>{{darkMode ? 'light_mode' : 'dark_mode'}}</mat-icon></button>
|
||||
<button mat-icon-button routerLink="/profile" *ngIf="showActions()" [matTooltip]="langs.currentLang?.profileSettings"><mat-icon>account_circle</mat-icon></button>
|
||||
</section>
|
||||
</mat-toolbar>
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@ mat-drawer-container {
|
||||
height: calc(100vh - 80px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.project mat-icon {
|
||||
margin-block: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {Router} from "@angular/router";
|
||||
import {CrudService} from "../../services/crud.service";
|
||||
import {ProjectService} from "../../services/project.service";
|
||||
@@ -7,6 +7,8 @@ import {TextDialogComponent} from "../text-dialog/text-dialog.component";
|
||||
import {firstValueFrom} from "rxjs";
|
||||
import {MatSnackBar} from "@angular/material/snack-bar";
|
||||
import {StorageService} from "../../services/storage.service";
|
||||
import {LangService} from "../../services/lang.service";
|
||||
import {Language} from "../../entities/language";
|
||||
|
||||
@Component({
|
||||
selector: 'app-navigation',
|
||||
@@ -17,7 +19,7 @@ export class NavigationComponent {
|
||||
public static spinnerVisible: boolean = false;
|
||||
public darkMode: boolean;
|
||||
|
||||
public constructor(public router: Router, public crud: CrudService, public projects: ProjectService, public dialog: MatDialog, private snackBar: MatSnackBar, private storage: StorageService) {
|
||||
public constructor(public router: Router, public langs: LangService, public crud: CrudService, public projects: ProjectService, public dialog: MatDialog, private snackBar: MatSnackBar, private storage: StorageService) {
|
||||
this.darkMode = storage.getItem("darkMode") == "true";
|
||||
}
|
||||
|
||||
|
||||
54
ProjectManager.Frontend/src/app/entities/language.ts
Normal file
54
ProjectManager.Frontend/src/app/entities/language.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
export interface Language {
|
||||
// Navigation
|
||||
selectLang: string,
|
||||
design: string,
|
||||
profileSettings: string,
|
||||
dashboard: string,
|
||||
logout: string,
|
||||
addProject: string,
|
||||
projects: string,
|
||||
running: string,
|
||||
stopped: string,
|
||||
|
||||
// Dashboard
|
||||
welcome: string,
|
||||
noProjects: string,
|
||||
open: string,
|
||||
edit: string,
|
||||
delete: string,
|
||||
|
||||
// Popups
|
||||
name: string,
|
||||
cancel: string,
|
||||
createProject: string,
|
||||
editProject: string
|
||||
|
||||
// Profile
|
||||
profile: string,
|
||||
profileSub: string,
|
||||
email: string,
|
||||
username: string,
|
||||
password: string,
|
||||
passwordRepeat: string,
|
||||
updateAccount: string,
|
||||
deleteAccount: string,
|
||||
saveChanges: string,
|
||||
updateFailed: string,
|
||||
accountUpdated: string,
|
||||
deleteQuestion: string,
|
||||
deleteWarning: string,
|
||||
accountDeleted: string,
|
||||
submit: string,
|
||||
|
||||
// Login / Register
|
||||
login: string,
|
||||
register: string,
|
||||
noAccount: string,
|
||||
alreadyAccount: string,
|
||||
valueToLong: string,
|
||||
validEmail: string,
|
||||
isRequired: string,
|
||||
emailOrPasswordWrong: string,
|
||||
passwordsDontMatch: string,
|
||||
registerFailed: string,
|
||||
}
|
||||
@@ -25,7 +25,7 @@ export class CrudService {
|
||||
'Authorization': ''
|
||||
});
|
||||
|
||||
constructor(private client: HttpClient, private storage: StorageService) {
|
||||
constructor(public client: HttpClient, private storage: StorageService) {
|
||||
this.getBackendUrl().then(() => {
|
||||
this.authKey = storage.getItem("api_key");
|
||||
this.setAuthKey(this.authKey);
|
||||
|
||||
41
ProjectManager.Frontend/src/app/services/lang.service.ts
Normal file
41
ProjectManager.Frontend/src/app/services/lang.service.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {Language} from "../entities/language";
|
||||
import {CrudService} from "./crud.service";
|
||||
import {firstValueFrom} from "rxjs";
|
||||
import {StorageService} from "./storage.service";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class LangService {
|
||||
private languages: Map<string, Language> = new Map<string, Language>();
|
||||
public allLanguages: string[] = [];
|
||||
public currentLang: Language;
|
||||
|
||||
constructor(private crud: CrudService, private storage: StorageService) {
|
||||
this.loadLanguages();
|
||||
}
|
||||
|
||||
private async loadLanguages() {
|
||||
this.languages = new Map<string, Language>();
|
||||
const res = await firstValueFrom(this.crud.client.get<{files: string[]}>(location?.origin + "/lang"));
|
||||
const languages = res.files;
|
||||
this.allLanguages = languages.map(lang => lang.replace(".json", ""));
|
||||
const tasks = [];
|
||||
|
||||
for (let lang of languages) {
|
||||
const task = firstValueFrom(this.crud.client.get<Language>(location?.origin + "/assets/languages/" + lang))
|
||||
.then(result => this.languages.set(lang.replace(".json", ""), result));
|
||||
tasks.push(task);
|
||||
}
|
||||
|
||||
await Promise.all(tasks);
|
||||
this.currentLang = this.languages.get(this.storage.getItem("language") || "en-US");
|
||||
}
|
||||
|
||||
public setLanguage(lang: string) {
|
||||
this.currentLang = this.languages.get(lang);
|
||||
this.storage.setItem("language", lang);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
<section id="main">
|
||||
<h1 id="welcome">Willkommen {{crud.user.username}}</h1>
|
||||
<h1 id="welcome">{{langs.currentLang?.welcome}} {{crud.user.username}}</h1>
|
||||
|
||||
<h2 id="title">Projekte</h2>
|
||||
<h2 id="title">{{langs.currentLang?.projects}}</h2>
|
||||
<div id="projects">
|
||||
<span *ngIf="projects.projects.length == 0 && crud.user != undefined" class="disabled">Du hast noch keine Projekte erstellt</span>
|
||||
<span *ngIf="projects.projects.length == 0 && crud.user != undefined" class="disabled">{{langs.currentLang?.noProjects}}</span>
|
||||
<mat-card *ngFor="let project of projects.projects" class="project">
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{project.name}}</mat-card-title>
|
||||
@@ -11,9 +11,9 @@
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-actions>
|
||||
<button mat-button color="primary" (click)="router.navigate(['/project', project.projectId])">Öffnen</button>
|
||||
<button mat-button color="accent" (click)="editProject(project.projectId)">Bearbeiten</button>
|
||||
<button mat-button color="warn" (click)="deleteProject(project.projectId)">Löschen</button>
|
||||
<button mat-button color="primary" (click)="openProject(project.projectId)">{{langs.currentLang?.open}}</button>
|
||||
<button mat-button color="accent" (click)="editProject(project.projectId)">{{langs.currentLang?.edit}}</button>
|
||||
<button mat-button color="warn" (click)="deleteProject(project.projectId)">{{langs.currentLang?.delete}}</button>
|
||||
|
||||
<button mat-icon-button color="warn" *ngIf="project.running" (click)="updateProjectStatus(project.projectId, false)"><mat-icon>pause</mat-icon></button>
|
||||
<button mat-icon-button color="accent" *ngIf="!project.running" (click)="updateProjectStatus(project.projectId, true)"><mat-icon>play_arrow</mat-icon></button>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ProjectService} from "../../services/project.service";
|
||||
import {CrudService} from "../../services/crud.service";
|
||||
import {Router} from "@angular/router";
|
||||
@@ -8,6 +8,9 @@ import {firstValueFrom} from "rxjs";
|
||||
import {MatSnackBar} from "@angular/material/snack-bar";
|
||||
import {TextDialogComponent} from "../../components/text-dialog/text-dialog.component";
|
||||
import {NavigationComponent} from "../../components/navigation/navigation.component";
|
||||
import {Project} from "../../entities/project";
|
||||
import {LangService} from "../../services/lang.service";
|
||||
import {Language} from "../../entities/language";
|
||||
|
||||
@Component({
|
||||
selector: 'app-dashboard',
|
||||
@@ -16,7 +19,17 @@ import {NavigationComponent} from "../../components/navigation/navigation.compon
|
||||
})
|
||||
export class DashboardComponent {
|
||||
|
||||
public constructor(public crud: CrudService, public projects: ProjectService, public router: Router, private dialog: MatDialog, private snackBar: MatSnackBar) {}
|
||||
public constructor(public langs: LangService, public crud: CrudService, public projects: ProjectService, public router: Router, private dialog: MatDialog, private snackBar: MatSnackBar) {}
|
||||
|
||||
public async openProject(projectId: string) {
|
||||
const response = await this.crud.sendGetRequest<{url: string}>('projects/' + projectId + '/url/string');
|
||||
const url = response.content.url;
|
||||
if (!url.startsWith("https")) {
|
||||
window.open(`${this.crud.backendUrl}projects/${projectId}/url?token=${this.crud.authKey}`, '_blank').focus();
|
||||
} else {
|
||||
await this.router.navigate(['/project', projectId]);
|
||||
}
|
||||
}
|
||||
|
||||
public async editProject(projectId: string) {
|
||||
const dialogRef = this.dialog.open(TextDialogComponent, {
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
<mat-card>
|
||||
<mat-card-title>Einloggen</mat-card-title>
|
||||
<mat-card-title>{{langs.currentLang?.login}}</mat-card-title>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-card-content>
|
||||
<form [formGroup]="form" (ngSubmit)="submit()">
|
||||
<mat-form-field>
|
||||
<mat-label>E-Mail</mat-label>
|
||||
<mat-label>{{langs.currentLang?.email}}</mat-label>
|
||||
<input type="text" matInput formControlName="email" required>
|
||||
<mat-error *ngIf="form.hasError('required', 'email')">E-Mail ist erforderlich</mat-error>
|
||||
<mat-error *ngIf="form.hasError('required', 'email')">{{langs.currentLang?.email}} {{langs.currentLang?.isRequired}}</mat-error>
|
||||
<mat-error *ngIf="form.hasError('email', 'email') && !form.hasError('required', 'email')">
|
||||
Bitte geben Sie eine gültige E-Mail-Adresse ein
|
||||
{{langs.currentLang?.validEmail}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-label>Passwort</mat-label>
|
||||
<mat-label>{{langs.currentLang?.password}}</mat-label>
|
||||
<input type="password" matInput formControlName="password" required>
|
||||
<mat-error *ngIf="form.hasError('required', 'password')">Passwort ist erforderlich</mat-error>
|
||||
<mat-error *ngIf="form.hasError('required', 'password')">{{langs.currentLang?.password}} {{langs.currentLang?.isRequired}}</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-divider></mat-divider>
|
||||
<span>Du besitzt keinen Account? <a routerLink="/register">Registrieren</a></span>
|
||||
<span>{{langs.currentLang?.noAccount}} <a routerLink="/register">{{langs.currentLang?.register}}</a></span>
|
||||
|
||||
<mat-error *ngIf="error">{{error}}</mat-error>
|
||||
|
||||
<button type="submit" mat-button>Einloggen</button>
|
||||
<button type="submit" mat-button>{{langs.currentLang?.login}}</button>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {FormControl, FormGroup, Validators} from "@angular/forms";
|
||||
import {CrudService} from "../../services/crud.service";
|
||||
import {User} from "../../entities/user";
|
||||
import {Router} from "@angular/router";
|
||||
import {Language} from "../../entities/language";
|
||||
import {LangService} from "../../services/lang.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
@@ -16,7 +18,7 @@ export class LoginComponent {
|
||||
});
|
||||
public error: string;
|
||||
|
||||
public constructor(private crud: CrudService, private router: Router) {
|
||||
public constructor(public langs: LangService, private crud: CrudService, private router: Router) {
|
||||
this.form.reset();
|
||||
this.error = "";
|
||||
}
|
||||
@@ -33,7 +35,7 @@ export class LoginComponent {
|
||||
await this.crud.loadUser(true);
|
||||
await this.router.navigate(["/dashboard"]);
|
||||
}else {
|
||||
this.error = "E-Mail oder Passwort ist falsch";
|
||||
this.error = this.langs.currentLang.emailOrPasswordWrong;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
<mat-card id="content">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Profil</mat-card-title>
|
||||
<mat-card-subtitle>Einstellungen</mat-card-subtitle>
|
||||
<mat-card-title>{{langs.currentLang?.profile}}</mat-card-title>
|
||||
<mat-card-subtitle>{{langs.currentLang?.profileSub}}</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content id="main">
|
||||
<form [formGroup]="form" (ngSubmit)="update()" id="form">
|
||||
<mat-form-field>
|
||||
<mat-label>E-Mail</mat-label>
|
||||
<mat-label>{{langs.currentLang?.email}}</mat-label>
|
||||
<input type="text" matInput formControlName="email" required>
|
||||
<mat-error *ngIf="form.hasError('required', 'email')">E-Mail ist erforderlich</mat-error>
|
||||
<mat-error *ngIf="form.hasError('required', 'email')">{{langs.currentLang?.email}} {{langs.currentLang?.isRequired}}</mat-error>
|
||||
<mat-error *ngIf="form.hasError('email', 'email') && !form.hasError('required', 'email')">
|
||||
Bitte geben Sie eine gültige E-Mail-Adresse ein
|
||||
{{langs.currentLang?.validEmail}}
|
||||
</mat-error>
|
||||
<mat-error *ngIf="form.hasError('maxlength', 'email') && !form.hasError('required', 'email')">
|
||||
Der eingegebene Wert ist zu lang
|
||||
{{langs.currentLang?.valueToLong}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-label>Benutzername</mat-label>
|
||||
<mat-label>{{langs.currentLang?.username}}</mat-label>
|
||||
<input type="text" matInput formControlName="username" required>
|
||||
<mat-error *ngIf="form.hasError('required', 'username')">Benutzername ist erforderlich</mat-error>
|
||||
<mat-error *ngIf="form.hasError('required', 'username')">{{langs.currentLang?.username}} {{langs.currentLang?.isRequired}}</mat-error>
|
||||
<mat-error *ngIf="form.hasError('maxlength', 'username') && !form.hasError('required', 'username')">
|
||||
Der eingegebene Wert ist zu lang
|
||||
{{langs.currentLang?.valueToLong}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<mat-label>Passwort</mat-label>
|
||||
<mat-label>{{langs.currentLang?.password}}</mat-label>
|
||||
<input type="password" matInput formControlName="password">
|
||||
<mat-error *ngIf="form.hasError('maxlength', 'password')">
|
||||
Der eingegebene Wert ist zu lang
|
||||
{{langs.currentLang?.valueToLong}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-label>Passwort wiederholen</mat-label>
|
||||
<mat-label>{{langs.currentLang?.passwordRepeat}}</mat-label>
|
||||
<input type="password" matInput formControlName="passwordRepeat">
|
||||
<mat-error *ngIf="form.hasError('maxlength', 'passwordRepeat')">
|
||||
Der eingegebene Wert ist zu lang
|
||||
{{langs.currentLang?.valueToLong}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@@ -51,7 +51,7 @@
|
||||
</mat-card-content>
|
||||
|
||||
<mat-card-actions id="actions">
|
||||
<button mat-button color="primary" (click)="update()">Account aktualisieren</button>
|
||||
<button mat-button color="warn" (click)="delete()">Account löschen</button>
|
||||
<button mat-button color="primary" (click)="update()">{{langs.currentLang?.updateAccount}}</button>
|
||||
<button mat-button color="warn" (click)="delete()">{{langs.currentLang?.deleteAccount}}</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {CrudService} from "../../services/crud.service";
|
||||
import {FormControl, FormGroup, Validators} from "@angular/forms";
|
||||
import {MatDialog} from "@angular/material/dialog";
|
||||
@@ -7,6 +7,8 @@ import {firstValueFrom} from "rxjs";
|
||||
import {User} from "../../entities/user";
|
||||
import {MatSnackBar} from "@angular/material/snack-bar";
|
||||
import {Router} from "@angular/router";
|
||||
import {Language} from "../../entities/language";
|
||||
import {LangService} from "../../services/lang.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-profile',
|
||||
@@ -22,14 +24,14 @@ export class ProfileComponent {
|
||||
});
|
||||
public error: string;
|
||||
|
||||
public constructor(public crud: CrudService, private router: Router, public dialog: MatDialog, private snackBar: MatSnackBar) {
|
||||
public constructor(public langs: LangService, public crud: CrudService, private router: Router, public dialog: MatDialog, private snackBar: MatSnackBar) {
|
||||
this.form.get("email").setValue(this.crud.user?.email);
|
||||
this.form.get("username").setValue(this.crud.user?.username);
|
||||
}
|
||||
|
||||
public async update() {
|
||||
if (!this.form.valid) return;
|
||||
const result = await this.openDialog("Änderungen speichern?");
|
||||
const result = await this.openDialog(this.langs.currentLang?.saveChanges);
|
||||
if (!result) return;
|
||||
|
||||
this.error = "";
|
||||
@@ -39,7 +41,7 @@ export class ProfileComponent {
|
||||
const passwordRepeat = this.form.get("passwordRepeat").value;
|
||||
|
||||
if (password != passwordRepeat) {
|
||||
this.error = "Passwörter stimmen nicht überein";
|
||||
this.error = this.langs.currentLang?.passwordsDontMatch;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -47,25 +49,25 @@ export class ProfileComponent {
|
||||
|
||||
const response = await this.crud.sendPutRequest("users", user);
|
||||
if (!response.success) {
|
||||
this.error = "Aktualiserung fehlgeschlagen!";
|
||||
this.error = this.langs.currentLang?.updateFailed;
|
||||
return;
|
||||
}
|
||||
|
||||
await this.crud.loadUser(true);
|
||||
this.form.reset();
|
||||
this.snackBar.open("Account aktualisiert!", undefined, {duration: 2000});
|
||||
this.snackBar.open(this.langs.currentLang?.updateAccount, undefined, {duration: 2000});
|
||||
await this.router.navigate(["dashboard"]);
|
||||
}
|
||||
|
||||
public async delete() {
|
||||
const result = await this.openDialog("Möchtest du deinen Account wirklich löschen?", "All deine Projekte werden für immer gelöscht!", ['', 'warn']);
|
||||
const result = await this.openDialog(this.langs.currentLang?.deleteQuestion, this.langs.currentLang?.deleteWarning, ['', 'warn']);
|
||||
if (!result) return;
|
||||
|
||||
await this.crud.sendDeleteRequest("users");
|
||||
|
||||
this.crud.setAuthKey(undefined);
|
||||
this.crud.user = undefined;
|
||||
this.snackBar.open("Account gelöscht!", undefined, {duration: 2000});
|
||||
this.snackBar.open(this.langs.currentLang?.accountDeleted, undefined, {duration: 2000});
|
||||
await this.router.navigate(["login"]);
|
||||
}
|
||||
|
||||
@@ -75,8 +77,8 @@ export class ProfileComponent {
|
||||
return new Promise<boolean>(async (resolve) => {
|
||||
const dialogRef = this.dialog.open(DialogComponent, {
|
||||
data: {title, subtitle, buttons: [
|
||||
{text: "Abbrechen", value: false, color: colors[0]},
|
||||
{text: "Bestätigen", value: true, color: colors[1]},
|
||||
{text: this.langs.currentLang?.cancel, value: false, color: colors[0]},
|
||||
{text: this.langs.currentLang?.submit, value: true, color: colors[1]},
|
||||
]}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,53 +1,53 @@
|
||||
<mat-card>
|
||||
<mat-card-title>Registrieren</mat-card-title>
|
||||
<mat-card-title>{{langs.currentLang?.register}}</mat-card-title>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-card-content>
|
||||
<form [formGroup]="form" (ngSubmit)="submit()">
|
||||
<mat-form-field>
|
||||
<mat-label>E-Mail</mat-label>
|
||||
<mat-label>{{langs.currentLang?.email}}</mat-label>
|
||||
<input type="text" matInput formControlName="email" required>
|
||||
<mat-error *ngIf="form.hasError('required', 'email')">E-Mail ist erforderlich</mat-error>
|
||||
<mat-error *ngIf="form.hasError('required', 'email')">{{langs.currentLang?.email}} {{langs.currentLang?.isRequired}}</mat-error>
|
||||
<mat-error *ngIf="form.hasError('email', 'email') && !form.hasError('required', 'email')">
|
||||
Bitte geben Sie eine gültige E-Mail-Adresse ein
|
||||
{{langs.currentLang?.validEmail}}
|
||||
</mat-error>
|
||||
<mat-error *ngIf="form.hasError('maxlength', 'email') && !form.hasError('required', 'email')">
|
||||
Der eingegebene Wert ist zu lang
|
||||
{{langs.currentLang?.valueToLong}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-label>Benutzername</mat-label>
|
||||
<mat-label>{{langs.currentLang?.username}}</mat-label>
|
||||
<input type="text" matInput formControlName="username" required>
|
||||
<mat-error *ngIf="form.hasError('required', 'username')">Benutzername ist erforderlich</mat-error>
|
||||
<mat-error *ngIf="form.hasError('required', 'username')">{{langs.currentLang?.username}} {{langs.currentLang?.isRequired}}</mat-error>
|
||||
<mat-error *ngIf="form.hasError('maxlength', 'username') && !form.hasError('required', 'username')">
|
||||
Der eingegebene Wert ist zu lang
|
||||
{{langs.currentLang?.valueToLong}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-label>Passwort</mat-label>
|
||||
<mat-label>{{langs.currentLang?.password}}</mat-label>
|
||||
<input type="password" matInput formControlName="password" required>
|
||||
<mat-error *ngIf="form.hasError('required', 'password')">Passwort ist erforderlich</mat-error>
|
||||
<mat-error *ngIf="form.hasError('required', 'password')">{{langs.currentLang?.password}} {{langs.currentLang?.isRequired}}</mat-error>
|
||||
<mat-error *ngIf="form.hasError('maxlength', 'password') && !form.hasError('required', 'password')">
|
||||
Der eingegebene Wert ist zu lang
|
||||
{{langs.currentLang?.valueToLong}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-label>Passwort wiederholen</mat-label>
|
||||
<mat-label>{{langs.currentLang?.passwordRepeat}}</mat-label>
|
||||
<input type="password" matInput formControlName="passwordRepeat" required>
|
||||
<mat-error *ngIf="form.hasError('required', 'passwordRepeat')">Passwort ist erforderlich</mat-error>
|
||||
<mat-error *ngIf="form.hasError('required', 'passwordRepeat')">{{langs.currentLang?.password}} {{langs.currentLang?.isRequired}}</mat-error>
|
||||
<mat-error *ngIf="form.hasError('maxlength', 'passwordRepeat') && !form.hasError('required', 'passwordRepeat')">
|
||||
Der eingegebene Wert ist zu lang
|
||||
{{langs.currentLang?.valueToLong}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-divider></mat-divider>
|
||||
<span>Du hast bereits einen Account? <a routerLink="/login">Einloggen</a></span>
|
||||
<span>{{langs.currentLang?.alreadyAccount}} <a routerLink="/login">{{langs.currentLang?.login}}</a></span>
|
||||
|
||||
<mat-error *ngIf="error">{{error}}</mat-error>
|
||||
|
||||
<button type="submit" mat-button>Registrieren</button>
|
||||
<button type="submit" mat-button>{{langs.currentLang?.register}}</button>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {FormControl, FormGroup, Validators} from "@angular/forms";
|
||||
import {CrudService} from "../../services/crud.service";
|
||||
import {Router} from "@angular/router";
|
||||
import {User} from "../../entities/user";
|
||||
import {Language} from "../../entities/language";
|
||||
import {LangService} from "../../services/lang.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-register',
|
||||
@@ -18,7 +20,7 @@ export class RegisterComponent {
|
||||
});
|
||||
public error: string;
|
||||
|
||||
public constructor(private crud: CrudService, private router: Router) {}
|
||||
public constructor(public langs: LangService, private crud: CrudService, private router: Router) {}
|
||||
|
||||
public async submit() {
|
||||
this.error = "";
|
||||
@@ -28,14 +30,14 @@ export class RegisterComponent {
|
||||
const passwordRepeat = this.form.get("passwordRepeat").value;
|
||||
|
||||
if (password != passwordRepeat) {
|
||||
this.error = "Passwörter stimmen nicht überein";
|
||||
this.error = this.langs.currentLang?.passwordsDontMatch;
|
||||
return;
|
||||
}
|
||||
|
||||
const user: User = {email, username, password};
|
||||
const result = await this.crud.sendPostRequest<{token: string}>("users/register", user);
|
||||
if (!result.success) {
|
||||
this.error = "Registrierung fehlgeschlagen";
|
||||
this.error = this.langs.currentLang?.registerFailed;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
49
ProjectManager.Frontend/src/assets/languages/de-DE.json
Normal file
49
ProjectManager.Frontend/src/assets/languages/de-DE.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"selectLang": "Sprache auswählen",
|
||||
"design": "Farbmodus wählen",
|
||||
"profileSettings": "Profil Einstellungen",
|
||||
"dashboard": "Übersicht",
|
||||
"logout": "Ausloggen",
|
||||
"addProject": "Neues Projekt",
|
||||
"projects": "Projekte",
|
||||
"running": "Läuft",
|
||||
"stopped": "Gestoppt",
|
||||
|
||||
"welcome": "Willkommen",
|
||||
"noProjects": "Du hast noch keine Projekte erstellt",
|
||||
"open": "Öffnen",
|
||||
"edit": "Bearbeiten",
|
||||
"delete": "Löschen",
|
||||
|
||||
"name": "Name",
|
||||
"cancel": "Abbrechen",
|
||||
"createProject": "Projekt erstellen",
|
||||
"editProject": "Projekt bearbeiten",
|
||||
|
||||
"profile": "Profil",
|
||||
"profileSub": "Einstellungen",
|
||||
"email": "E-Mail",
|
||||
"username": "Benutzername",
|
||||
"password": "Passwort",
|
||||
"passwordRepeat": "Passwort wiederholen",
|
||||
"updateAccount": "Account aktualisieren",
|
||||
"deleteAccount": "Account löschen",
|
||||
"saveChanges": "Änderungen speichern?",
|
||||
"updateFailed": "Aktualisierung fehlgeschlagen",
|
||||
"accountUpdated": "Account aktualisiert!",
|
||||
"deleteQuestion": "Möchtest du deinen Account wirklich löschen?",
|
||||
"deleteWarning": "All deine Projekte werden für immer gelöscht!",
|
||||
"accountDeleted": "Account gelöscht!",
|
||||
"submit": "Bestätigen",
|
||||
|
||||
"login": "Einloggen",
|
||||
"register": "Registrieren",
|
||||
"noAccount": "Du besitzt keinen Account?",
|
||||
"alreadyAccount": "Du hast bereits einen Account?",
|
||||
"valueToLong": "Der eingegebene Wert ist zu lang",
|
||||
"validEmail": "Gebe eine gültige E-Mail Addresse an",
|
||||
"isRequired": "ist erforderlich",
|
||||
"emailOrPasswordWrong": "E-Mail oder Passwort ist falsch",
|
||||
"passwordsDontMatch": "Passwörter stimmen nicht überein",
|
||||
"registerFailed": "Registrierung fehlgeschlagen"
|
||||
}
|
||||
49
ProjectManager.Frontend/src/assets/languages/en-US.json
Normal file
49
ProjectManager.Frontend/src/assets/languages/en-US.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"selectLang": "Select language",
|
||||
"design": "Design",
|
||||
"profileSettings": "Profile settings",
|
||||
"dashboard": "Dashboard",
|
||||
"logout": "Logout",
|
||||
"addProject": "Add Project",
|
||||
"projects": "Projects",
|
||||
"running": "Running",
|
||||
"stopped": "Stopped",
|
||||
|
||||
"welcome": "Welcome",
|
||||
"noProjects": "You have not created a Project yet",
|
||||
"open": "Open",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
|
||||
"name": "Name",
|
||||
"cancel": "Cancel",
|
||||
"createProject": "Create project",
|
||||
"editProject": "Edit project",
|
||||
|
||||
"profile": "Profile",
|
||||
"profileSub": "Settings",
|
||||
"email": "Email",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"passwordRepeat": "Repeat password",
|
||||
"updateAccount": "Update account",
|
||||
"deleteAccount": "Delete account",
|
||||
"saveChanges": "Save changes?",
|
||||
"updateFailed": "Update failed",
|
||||
"accountUpdated": "Account updated",
|
||||
"deleteQuestion": "Do you really want to delete your account?",
|
||||
"deleteWarning": "All your data will be lost!",
|
||||
"accountDeleted": "Account deleted!",
|
||||
"submit": "Submit",
|
||||
|
||||
"login": "Login",
|
||||
"register": "Register",
|
||||
"noAccount": "You don't have a account?",
|
||||
"alreadyAccount": "You already have an account?",
|
||||
"valueToLong": "The entered value is to long",
|
||||
"validEmail": "Please enter a valid Email address",
|
||||
"isRequired": "is required",
|
||||
"emailOrPasswordWrong": "Email or Password is wrong",
|
||||
"passwordsDontMatch": "Passwords doesn't match",
|
||||
"registerFailed": "Register failed"
|
||||
}
|
||||
@@ -6,7 +6,7 @@ Es handelt sich hierbei um ein einfach zu benutzendes WebInterface zum Erstellen
|
||||
- [x] Automatische Docker Konfiguration
|
||||
- [x] Automatisches DNS Mapping
|
||||
- [x] Automatische SSL-Konfiguration mithilfe von NginxProxyManager
|
||||
- [ ] Projekt Transferierung auf verschiedene Nodes
|
||||
- [ ] Projekte exportieren / importieren
|
||||
- [ ] Eigene Domains
|
||||
- [ ] Mehrere Sprachen
|
||||
|
||||
@@ -60,7 +60,7 @@ services:
|
||||
|
||||
### SourceCode bearbeiten und Docker Images selber erstellen
|
||||
Falls Sie den SourceCode selbst bearbeiten wollen, steht Ihnen dieser natürlich zur Verfügung. Um das Backend im Debug Modus zu verwenden sollten sie eine
|
||||
``appsettings.Development.json`` Datei im Hauptverzeichnis anlegen um dort die entsprechende Konfiguration für die Entwicklungsumgebung erstellen.
|
||||
``appsettings.Development.json`` Datei im Hauptverzeichnis anlegen, um dort die entsprechende Konfiguration für die Entwicklungsumgebung erstellen.
|
||||
Zum Debuggen empfehle ich ``npm run dev:ssr`` um das Frontend zu starten und ``dotnet run`` um das Backend zu starten. Sofern Sie mit den Bearbeitungen fertig
|
||||
sind, gibt es eine ``docker-compose.example.yml`` Datei im dem Repository die automatisch den SoruceCode neu baut und die Container startet.
|
||||
Wahlweise können Sie diese mit ``docker build`` auch selbst bauen.
|
||||
|
||||
Reference in New Issue
Block a user