Compare commits
6 Commits
8138a63324
...
eda6ca4b1a
Author | SHA1 | Date | |
---|---|---|---|
eda6ca4b1a | |||
10bf53adec | |||
e10075dfed | |||
2b482d2b2d | |||
9017e87175 | |||
16e25905dc |
@ -158,7 +158,7 @@ export default abstract class ApiService {
|
|||||||
|
|
||||||
private handleError(error: HttpErrorResponse): void {
|
private handleError(error: HttpErrorResponse): void {
|
||||||
// todo: change to Retry-After condition
|
// todo: change to Retry-After condition
|
||||||
if (error.error && error.error.toString().includes("setup")) {
|
if (error.error && error.error.detail.includes("setup")) {
|
||||||
this.router.navigate(['/setup/']).then();
|
this.router.navigate(['/setup/']).then();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -168,38 +168,40 @@ export default abstract class ApiService {
|
|||||||
if (error.error instanceof ErrorEvent) {
|
if (error.error instanceof ErrorEvent) {
|
||||||
title = `Произошла ошибка: ${error.error.message}`;
|
title = `Произошла ошибка: ${error.error.message}`;
|
||||||
} else {
|
} else {
|
||||||
switch (error.status) {
|
if (error.error && error.error.type && error.error.title) {
|
||||||
case 0:
|
title = error.error.title || `Ошибка с кодом ${error.status}`;
|
||||||
title = 'Неизвестная ошибка. Пожалуйста, попробуйте позже.';
|
message = error.error.detail || 'Неизвестная ошибка';
|
||||||
break;
|
} else {
|
||||||
case 400:
|
switch (error.status) {
|
||||||
title = 'Ошибка запроса. Пожалуйста, проверьте отправленные данные.';
|
case 0:
|
||||||
break;
|
title = 'Неизвестная ошибка. Пожалуйста, попробуйте позже.';
|
||||||
case 401:
|
break;
|
||||||
this.router.navigate(['/login/']).then();
|
case 400:
|
||||||
title = 'Ошибка авторизации. Пожалуйста, выполните вход с правильными учетными данными.';
|
title = 'Ошибка запроса. Пожалуйста, проверьте отправленные данные.';
|
||||||
break;
|
break;
|
||||||
case 403:
|
case 401:
|
||||||
title = 'Отказано в доступе. У вас нет разрешения на выполнение этого действия.';
|
this.router.navigate(['/login/']).then();
|
||||||
break;
|
title = 'Ошибка авторизации. Пожалуйста, выполните вход с правильными учетными данными.';
|
||||||
case 404:
|
break;
|
||||||
title = 'Запрашиваемый ресурс не найден.';
|
case 403:
|
||||||
break;
|
title = 'Отказано в доступе. У вас нет разрешения на выполнение этого действия.';
|
||||||
case 500:
|
break;
|
||||||
title = 'Внутренняя ошибка сервера. Пожалуйста, попробуйте позже.';
|
case 404:
|
||||||
break;
|
title = 'Запрашиваемый ресурс не найден.';
|
||||||
case 503:
|
break;
|
||||||
title = 'Сервер на обслуживании. Пожалуйста, попробуйте позже.';
|
case 500:
|
||||||
break;
|
title = 'Внутренняя ошибка сервера. Пожалуйста, попробуйте позже.';
|
||||||
default:
|
break;
|
||||||
title = `Сервер вернул код ошибки: ${error.status}`;
|
case 503:
|
||||||
break;
|
title = 'Сервер на обслуживании. Пожалуйста, попробуйте позже.';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
title = `Сервер вернул код ошибки: ${error.status}`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (error.error?.Error)
|
|
||||||
message = error.error.Error;
|
if (!message)
|
||||||
else if (error.error instanceof String)
|
|
||||||
message = error.error.toString();
|
|
||||||
else
|
|
||||||
message = error.error.statusMessage;
|
message = error.error.statusMessage;
|
||||||
}
|
}
|
||||||
this.notify.error(message == '' ? undefined : message, title);
|
this.notify.error(message == '' ? undefined : message, title);
|
||||||
|
@ -75,3 +75,11 @@
|
|||||||
.provider-item.provider-unlink:hover::after {
|
.provider-item.provider-unlink:hover::after {
|
||||||
transform: translate(-50%, -50%) scale(1.0);
|
transform: translate(-50%, -50%) scale(1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.provider-item .provider-spinner {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%) scale(1.0);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
@ -6,9 +6,13 @@
|
|||||||
<div class="provider-container">
|
<div class="provider-container">
|
||||||
@for (provider of providers; track $index) {
|
@for (provider of providers; track $index) {
|
||||||
<a class="provider-item" (click)="provider.disabled ? confirmDelete(provider) : openOAuth(provider)"
|
<a class="provider-item" (click)="provider.disabled ? confirmDelete(provider) : openOAuth(provider)"
|
||||||
[class.disabled]="!canUnlink && provider.disabled" [class.provider-unlink]="canUnlink && provider.disabled">
|
[class.disabled]="!canUnlink && provider.disabled || provider.active"
|
||||||
|
[class.provider-unlink]="canUnlink && provider.disabled">
|
||||||
<img [alt]="provider.providerName" [src]="provider.icon"
|
<img [alt]="provider.providerName" [src]="provider.icon"
|
||||||
class="provider-icon" draggable="false"/>
|
class="provider-icon" draggable="false"/>
|
||||||
|
@if (provider.active) {
|
||||||
|
<app-data-spinner class="provider-spinner"/>
|
||||||
|
}
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {Component, Inject, Input, OnInit} from '@angular/core';
|
import {Component, EventEmitter, Inject, Input, Output} from '@angular/core';
|
||||||
import AuthApiService, {OAuthProviderData} from "@api/v1/authApiService";
|
import AuthApiService, {OAuthProviderData} from "@api/v1/authApiService";
|
||||||
import {OAuthProvider} from "@model/oAuthProvider";
|
import {OAuthProvider} from "@model/oAuthProvider";
|
||||||
import {ToastrService} from "ngx-toastr";
|
import {ToastrService} from "ngx-toastr";
|
||||||
@ -10,9 +10,11 @@ import {
|
|||||||
MatDialogTitle
|
MatDialogTitle
|
||||||
} from "@angular/material/dialog";
|
} from "@angular/material/dialog";
|
||||||
import {MatButton} from "@angular/material/button";
|
import {MatButton} from "@angular/material/button";
|
||||||
|
import {DataSpinnerComponent} from "@component/common/data-spinner/data-spinner.component";
|
||||||
|
|
||||||
interface AvailableOAuthProviders extends OAuthProviderData {
|
interface AvailableOAuthProviders extends OAuthProviderData {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
|
active: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -53,17 +55,24 @@ export class DeleteConfirmDialog {
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'OAuthProviders',
|
selector: 'OAuthProviders',
|
||||||
imports: [],
|
imports: [
|
||||||
|
DataSpinnerComponent
|
||||||
|
],
|
||||||
templateUrl: './OAuthProviders.html',
|
templateUrl: './OAuthProviders.html',
|
||||||
styleUrl: './OAuthProviders.css',
|
styleUrl: './OAuthProviders.css',
|
||||||
providers: [AuthApiService]
|
providers: [AuthApiService]
|
||||||
})
|
})
|
||||||
export class OAuthProviders implements OnInit {
|
export class OAuthProviders {
|
||||||
protected providers: AvailableOAuthProviders[] = [];
|
protected providers: AvailableOAuthProviders[] = [];
|
||||||
protected _activeProvidersId: OAuthProvider[] = [];
|
protected _activeProvidersId: OAuthProvider[] = [];
|
||||||
|
protected _activeProviders: string[] = [];
|
||||||
|
|
||||||
@Input() message: string = 'Вы можете войти в аккаунт через';
|
@Input() message: string = 'Вы можете войти в аккаунт через';
|
||||||
@Input() activeProviders: string[] = [];
|
|
||||||
|
@Input() set activeProviders(data: string[]) {
|
||||||
|
this._activeProviders = data;
|
||||||
|
this.updateDisabledProviders();
|
||||||
|
}
|
||||||
|
|
||||||
@Input() set activeProvidersId(data: OAuthProvider[]) {
|
@Input() set activeProvidersId(data: OAuthProvider[]) {
|
||||||
this._activeProvidersId = data;
|
this._activeProvidersId = data;
|
||||||
@ -72,6 +81,8 @@ export class OAuthProviders implements OnInit {
|
|||||||
|
|
||||||
@Input() canUnlink: boolean = false;
|
@Input() canUnlink: boolean = false;
|
||||||
|
|
||||||
|
@Output() public oAuthUpdateProviders = new EventEmitter();
|
||||||
|
|
||||||
constructor(authApi: AuthApiService, private notify: ToastrService, private dialog: MatDialog) {
|
constructor(authApi: AuthApiService, private notify: ToastrService, private dialog: MatDialog) {
|
||||||
authApi.availableProviders().subscribe(providers => this.updateDisabledProviders(providers));
|
authApi.availableProviders().subscribe(providers => this.updateDisabledProviders(providers));
|
||||||
}
|
}
|
||||||
@ -80,23 +91,12 @@ export class OAuthProviders implements OnInit {
|
|||||||
this.providers = (data ?? this.providers).map(provider => {
|
this.providers = (data ?? this.providers).map(provider => {
|
||||||
return {
|
return {
|
||||||
...provider,
|
...provider,
|
||||||
disabled: this._activeProvidersId.includes(provider.provider) || this.activeProviders.includes(provider.providerName)
|
disabled: this._activeProvidersId.includes(provider.provider) || this._activeProviders.includes(provider.providerName),
|
||||||
|
active: false
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
window.addEventListener('message', (event) => {
|
|
||||||
if (event.data && event.data.success === false) {
|
|
||||||
console.error(event.data.message);
|
|
||||||
this.notify.error(event.data.message, 'OAuth ошибка');
|
|
||||||
} else {
|
|
||||||
this.activeProvidersId.push(event.data.provider);
|
|
||||||
this.updateDisabledProviders();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected openOAuth(provider: AvailableOAuthProviders) {
|
protected openOAuth(provider: AvailableOAuthProviders) {
|
||||||
console.log(provider.redirect);
|
console.log(provider.redirect);
|
||||||
const oauthWindow = window.open(
|
const oauthWindow = window.open(
|
||||||
@ -108,6 +108,16 @@ export class OAuthProviders implements OnInit {
|
|||||||
this.notify.error('Не удалось открыть OAuth окно');
|
this.notify.error('Не удалось открыть OAuth окно');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
provider.active = true;
|
||||||
|
|
||||||
|
const checkInterval = setInterval(() => {
|
||||||
|
if (oauthWindow.closed) {
|
||||||
|
clearInterval(checkInterval);
|
||||||
|
this.oAuthUpdateProviders.emit();
|
||||||
|
provider.active = false;
|
||||||
|
}
|
||||||
|
}, 1500);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected confirmDelete(provider: AvailableOAuthProviders) {
|
protected confirmDelete(provider: AvailableOAuthProviders) {
|
||||||
|
@ -82,7 +82,7 @@ export class LoginComponent {
|
|||||||
})
|
})
|
||||||
.pipe(catchError(error => {
|
.pipe(catchError(error => {
|
||||||
this.loaderActive = false;
|
this.loaderActive = false;
|
||||||
this.errorText = error.error instanceof String ? error.error : error.statusText;
|
this.errorText = error.error instanceof String ? error.statusText : error.error;
|
||||||
this.loginButtonIsDisable = true;
|
this.loginButtonIsDisable = true;
|
||||||
throw error;
|
throw error;
|
||||||
}))
|
}))
|
||||||
|
@ -116,7 +116,7 @@
|
|||||||
}
|
}
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<OAuthProviders [canUnlink]="true" [activeProvidersId]="activatedProviders"
|
<OAuthProviders [canUnlink]="true" [activeProvidersId]="activatedProviders" (oAuthUpdateProviders)="updateProviders()"
|
||||||
[message]="'Или можете получить часть данных от сторонних сервисов'"/>
|
[message]="'Или можете получить часть данных от сторонних сервисов'"/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -70,10 +70,17 @@ export class CreateAdminComponent {
|
|||||||
this.createAdminForm.get('password')?.updateValueAndValidity();
|
this.createAdminForm.get('password')?.updateValueAndValidity();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.updateAdminData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateAdminData() {
|
||||||
this.api.adminConfiguration().subscribe(configuration => {
|
this.api.adminConfiguration().subscribe(configuration => {
|
||||||
if (configuration) {
|
if (configuration) {
|
||||||
this.createAdminForm.get('email')?.setValue(configuration.email);
|
if (this.createAdminForm.get('email')?.value == 0)
|
||||||
this.createAdminForm.get('user')?.setValue(configuration.username);
|
this.createAdminForm.get('email')?.setValue(configuration.email);
|
||||||
|
|
||||||
|
if (this.createAdminForm.get('user')?.value == 0)
|
||||||
|
this.createAdminForm.get('user')?.setValue(configuration.username);
|
||||||
|
|
||||||
this.activatedProviders = configuration.usedOAuthProviders;
|
this.activatedProviders = configuration.usedOAuthProviders;
|
||||||
}
|
}
|
||||||
@ -111,4 +118,8 @@ export class CreateAdminComponent {
|
|||||||
this.hideRetypePass = !this.hideRetypePass;
|
this.hideRetypePass = !this.hideRetypePass;
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected updateProviders() {
|
||||||
|
this.updateAdminData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,11 @@
|
|||||||
Настройте систему логирования как будет удобно для отображения.
|
Настройте систему логирования как будет удобно для отображения.
|
||||||
Можно настроить путь к файлу, имена файлов или вовсе отключить логирование в файл.
|
Можно настроить путь к файлу, имена файлов или вовсе отключить логирование в файл.
|
||||||
</p>
|
</p>
|
||||||
|
<p class="mat-body-2 secondary">
|
||||||
|
Также вы можете настроить интеграцию с Seq.
|
||||||
|
Введите необходимые данные и мы отправим тестовый лог на сервер Seq. Его уровень будет Warning.
|
||||||
|
Если тестовый лог не появился вернитесь на данный шаг и перепроверьте данные.
|
||||||
|
</p>
|
||||||
|
|
||||||
<form [formGroup]="loggingSettings">
|
<form [formGroup]="loggingSettings">
|
||||||
<p>
|
<p>
|
||||||
@ -31,5 +36,18 @@
|
|||||||
matTooltip="Укажите название файла, в который будут записаны логи"
|
matTooltip="Укажите название файла, в который будут записаны логи"
|
||||||
formControlName="logName">
|
formControlName="logName">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field color="accent">
|
||||||
|
<mat-label>Сервер Seq</mat-label>
|
||||||
|
<input matInput
|
||||||
|
matTooltip="Укажите сервер Seq вначале указав схему (http/https)"
|
||||||
|
formControlName="seqServer">
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field color="accent">
|
||||||
|
<mat-label>Api ключ Seq</mat-label>
|
||||||
|
<input matInput
|
||||||
|
matTooltip="Укажите ключ API, который вы создали в Seq"
|
||||||
|
formControlName="seqKey">
|
||||||
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -45,7 +45,9 @@ export class LoggingComponent {
|
|||||||
this.loggingSettings = this.formBuilder.group({
|
this.loggingSettings = this.formBuilder.group({
|
||||||
enabled: [true, Validators.required],
|
enabled: [true, Validators.required],
|
||||||
logPath: [''],
|
logPath: [''],
|
||||||
logName: ['']
|
logName: [''],
|
||||||
|
seqServer: [''],
|
||||||
|
seqKey: ['']
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -56,9 +58,11 @@ export class LoggingComponent {
|
|||||||
|
|
||||||
this.navigationService.nextButtonAction = () => {
|
this.navigationService.nextButtonAction = () => {
|
||||||
return this.api.setLogging({
|
return this.api.setLogging({
|
||||||
"enableLogToFile": this.loggingSettings.get('enabled')?.value,
|
enableLogToFile: this.loggingSettings.get('enabled')?.value,
|
||||||
"logFileName": this.loggingSettings.get('logName')?.value,
|
logFileName: this.loggingSettings.get('logName')?.value,
|
||||||
"logFilePath": this.loggingSettings.get('logPath')?.value
|
logFilePath: this.loggingSettings.get('logPath')?.value,
|
||||||
|
apiServerSeq: this.loggingSettings.get('seqServer')?.value,
|
||||||
|
apiKeySeq: this.loggingSettings.get('seqKey')?.value
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -73,6 +77,8 @@ export class LoggingComponent {
|
|||||||
this.loggingSettings.get('enabled')?.setValue(x.enableLogToFile);
|
this.loggingSettings.get('enabled')?.setValue(x.enableLogToFile);
|
||||||
this.loggingSettings.get('logName')?.setValue(x.logFileName);
|
this.loggingSettings.get('logName')?.setValue(x.logFileName);
|
||||||
this.loggingSettings.get('logPath')?.setValue(x.logFilePath);
|
this.loggingSettings.get('logPath')?.setValue(x.logFilePath);
|
||||||
|
this.loggingSettings.get('seqServer')?.setValue(x.apiServerSeq);
|
||||||
|
this.loggingSettings.get('seqKey')?.setValue(x.apiKeySeq);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,6 +163,16 @@
|
|||||||
Путь к файлу журнала: {{ loggingConfig.logFilePath }}
|
Путь к файлу журнала: {{ loggingConfig.logFilePath }}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@if (loggingConfig.apiServerSeq) {
|
||||||
|
<div>
|
||||||
|
Сервер Seq: {{ loggingConfig.apiServerSeq }}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (loggingConfig.apiKeySeq) {
|
||||||
|
<div>
|
||||||
|
Ключ Seq: {{ loggingConfig.apiKeySeq }}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<h3>Ваш код: <i><strong>{{ secret }}</strong></i></h3>
|
<h3>Ваш код: <i><strong>{{ secret }}</strong></i></h3>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<img [src]="totpImage" alt="totp-qr-code"/>
|
<img [src]="totpImage" alt="totp-qr-code" style="max-height: 60vh;"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form [formGroup]="twoFactorForm">
|
<form [formGroup]="twoFactorForm">
|
||||||
|
@ -2,4 +2,6 @@ export interface LoggingRequest {
|
|||||||
enableLogToFile: boolean;
|
enableLogToFile: boolean;
|
||||||
logFileName?: string;
|
logFileName?: string;
|
||||||
logFilePath?: string;
|
logFilePath?: string;
|
||||||
|
apiServerSeq?: string;
|
||||||
|
apiKeySeq?: string;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user