mirror of
https://github.com/bpatrik/pigallery2.git
synced 2024-12-21 01:22:08 +02:00
Add users panel to users #569
This commit is contained in:
parent
82bc7ab280
commit
a6e60c7c19
@ -3,7 +3,7 @@ import * as path from 'path';
|
||||
import { ConfigClass, ConfigClassBuilder } from 'typeconfig/node';
|
||||
import { ConfigProperty, SubConfigClass } from 'typeconfig/common';
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class BenchmarksConfig {
|
||||
@ConfigProperty()
|
||||
bmScanDirectory: boolean = true;
|
||||
|
@ -91,7 +91,7 @@ export type videoResolutionType =
|
||||
| 4320;
|
||||
export type videoFormatType = 'mp4' | 'webm';
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class MySQLConfig {
|
||||
@ConfigProperty({
|
||||
envAlias: 'MYSQL_HOST',
|
||||
@ -140,7 +140,7 @@ export class MySQLConfig {
|
||||
password: string = '';
|
||||
}
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class SQLiteConfig {
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
@ -153,7 +153,7 @@ export class SQLiteConfig {
|
||||
DBFileName: string = 'sqlite.db';
|
||||
}
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class UserConfig {
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
@ -216,7 +216,7 @@ export class UserConfig {
|
||||
}
|
||||
}
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class ServerDataBaseConfig {
|
||||
@ConfigProperty<DatabaseType, ServerConfig>({
|
||||
type: DatabaseType,
|
||||
@ -262,7 +262,7 @@ export class ServerDataBaseConfig {
|
||||
}
|
||||
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class ServerUserConfig extends ClientUserConfig {
|
||||
@ConfigProperty({
|
||||
arrayType: UserConfig,
|
||||
@ -273,13 +273,13 @@ export class ServerUserConfig extends ClientUserConfig {
|
||||
uiOptional: true,
|
||||
githubIssue: 575
|
||||
} as TAGS,
|
||||
description: $localize`Creates these users in the DB if they do not exist. If a user with this name exist, it won't be overwritten, even if the role is different.`,
|
||||
description: $localize`Creates these users in the DB during startup if they do not exist. If a user with this name exist, it won't be overwritten, even if the role is different.`,
|
||||
})
|
||||
enforcedUsers: UserConfig[] = [];
|
||||
}
|
||||
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class ServerThumbnailConfig extends ClientThumbnailConfig {
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
@ -312,7 +312,7 @@ export class ServerThumbnailConfig extends ClientThumbnailConfig {
|
||||
personFaceMargin: number = 0.6; // in ration [0-1]
|
||||
}
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class ServerGPXCompressingConfig extends ClientGPXCompressingConfig {
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
@ -350,7 +350,7 @@ export class ServerGPXCompressingConfig extends ClientGPXCompressingConfig {
|
||||
minTimeDistance: number = 5000;
|
||||
}
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class ServerMetaFileConfig extends ClientMetaFileConfig {
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
@ -367,7 +367,7 @@ export class ServerMetaFileConfig extends ClientMetaFileConfig {
|
||||
}
|
||||
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class ServerSharingConfig extends ClientSharingConfig {
|
||||
@ConfigProperty({
|
||||
type: 'unsignedInt',
|
||||
@ -382,7 +382,7 @@ export class ServerSharingConfig extends ClientSharingConfig {
|
||||
updateTimeout: number = 1000 * 60 * 5;
|
||||
}
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class ServerIndexingConfig {
|
||||
@ConfigProperty({
|
||||
type: 'unsignedInt',
|
||||
@ -431,7 +431,7 @@ export class ServerIndexingConfig {
|
||||
excludeFileList: string[] = [];
|
||||
}
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class ServerThreadingConfig {
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
@ -453,7 +453,7 @@ export class ServerThreadingConfig {
|
||||
thumbnailThreads: number = 0; // if zero-> CPU count -1
|
||||
}
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class ServerDuplicatesConfig {
|
||||
@ConfigProperty({
|
||||
type: 'unsignedInt',
|
||||
@ -467,7 +467,7 @@ export class ServerDuplicatesConfig {
|
||||
listingLimit: number = 1000;
|
||||
}
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class ServerLogConfig {
|
||||
@ConfigProperty({
|
||||
type: LogLevel,
|
||||
@ -497,13 +497,13 @@ export class ServerLogConfig {
|
||||
logServerTiming: boolean = false;
|
||||
}
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class NeverJobTriggerConfig implements NeverJobTrigger {
|
||||
@ConfigProperty({type: JobTriggerType})
|
||||
readonly type = JobTriggerType.never;
|
||||
}
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class ScheduledJobTriggerConfig implements ScheduledJobTrigger {
|
||||
@ConfigProperty({type: JobTriggerType})
|
||||
readonly type = JobTriggerType.scheduled;
|
||||
@ -512,7 +512,7 @@ export class ScheduledJobTriggerConfig implements ScheduledJobTrigger {
|
||||
time: number; // data time
|
||||
}
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class PeriodicJobTriggerConfig implements PeriodicJobTrigger {
|
||||
@ConfigProperty({type: JobTriggerType})
|
||||
readonly type = JobTriggerType.periodic;
|
||||
@ -522,7 +522,7 @@ export class PeriodicJobTriggerConfig implements PeriodicJobTrigger {
|
||||
atTime: number | undefined = 0; // day time
|
||||
}
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class AfterJobTriggerConfig implements AfterJobTrigger {
|
||||
@ConfigProperty({type: JobTriggerType})
|
||||
readonly type = JobTriggerType.after;
|
||||
@ -534,7 +534,7 @@ export class AfterJobTriggerConfig implements AfterJobTrigger {
|
||||
}
|
||||
}
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class JobScheduleConfig implements JobScheduleDTO {
|
||||
@ConfigProperty()
|
||||
name: string;
|
||||
@ -586,7 +586,7 @@ export class JobScheduleConfig implements JobScheduleDTO {
|
||||
}
|
||||
}
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class ServerJobConfig {
|
||||
@ConfigProperty({
|
||||
type: 'unsignedInt',
|
||||
@ -661,7 +661,7 @@ export class ServerJobConfig {
|
||||
];
|
||||
}
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class VideoTranscodingConfig {
|
||||
@ConfigProperty({
|
||||
type: 'unsignedInt',
|
||||
@ -761,7 +761,7 @@ export class VideoTranscodingConfig {
|
||||
customOptions: string[] = [];
|
||||
}
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class ServerVideoConfig extends ClientVideoConfig {
|
||||
@ConfigProperty({
|
||||
tags: {
|
||||
@ -774,7 +774,7 @@ export class ServerVideoConfig extends ClientVideoConfig {
|
||||
transcoding: VideoTranscodingConfig = new VideoTranscodingConfig();
|
||||
}
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class PhotoConvertingConfig extends ClientPhotoConvertingConfig {
|
||||
@ConfigProperty({
|
||||
tags: {
|
||||
@ -802,7 +802,7 @@ export class PhotoConvertingConfig extends ClientPhotoConvertingConfig {
|
||||
resolution: videoResolutionType = 1080;
|
||||
}
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class ServerPhotoConfig extends ClientPhotoConfig {
|
||||
@ConfigProperty({
|
||||
tags: {
|
||||
@ -813,7 +813,7 @@ export class ServerPhotoConfig extends ClientPhotoConfig {
|
||||
Converting: PhotoConvertingConfig = new PhotoConvertingConfig();
|
||||
}
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class ServerPreviewConfig {
|
||||
@ConfigProperty({
|
||||
type: 'object',
|
||||
@ -842,7 +842,7 @@ export class ServerPreviewConfig {
|
||||
];
|
||||
}
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class ServerMediaConfig extends ClientMediaConfig {
|
||||
@ConfigProperty({
|
||||
tags: {
|
||||
@ -914,7 +914,7 @@ export class ServerMediaConfig extends ClientMediaConfig {
|
||||
Thumbnail: ServerThumbnailConfig = new ServerThumbnailConfig();
|
||||
}
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class ServerServiceConfig extends ClientServiceConfig {
|
||||
@ConfigProperty({
|
||||
arrayType: 'string',
|
||||
@ -974,7 +974,7 @@ export class ServerServiceConfig extends ClientServiceConfig {
|
||||
Log: ServerLogConfig = new ServerLogConfig();
|
||||
}
|
||||
|
||||
@SubConfigClass()
|
||||
@SubConfigClass({softReadonly: true})
|
||||
export class ServerEnvironmentConfig {
|
||||
@ConfigProperty({volatile: true})
|
||||
upTime: string | undefined;
|
||||
@ -988,7 +988,7 @@ export class ServerEnvironmentConfig {
|
||||
isDocker: boolean | undefined;
|
||||
}
|
||||
|
||||
@SubConfigClass<TAGS>()
|
||||
@SubConfigClass<TAGS>({softReadonly: true})
|
||||
export class ServerConfig extends ClientConfig {
|
||||
|
||||
@ConfigProperty({volatile: true})
|
||||
|
@ -13,7 +13,6 @@ if (typeof $localize === 'undefined') {
|
||||
}
|
||||
|
||||
|
||||
|
||||
export enum MapProviders {
|
||||
OpenStreetMap = 1,
|
||||
Mapbox = 2,
|
||||
@ -49,7 +48,7 @@ export type TAGS = {
|
||||
}[]
|
||||
};
|
||||
|
||||
@SubConfigClass<TAGS>({tags: {client: true}})
|
||||
@SubConfigClass<TAGS>({tags: {client: true}, softReadonly: true})
|
||||
export class AutoCompleteConfig {
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
@ -93,7 +92,7 @@ export class AutoCompleteConfig {
|
||||
cacheTimeout: number = 1000 * 60 * 60;
|
||||
}
|
||||
|
||||
@SubConfigClass({tags: {client: true}})
|
||||
@SubConfigClass({tags: {client: true}, softReadonly: true})
|
||||
export class ClientSearchConfig {
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
@ -162,7 +161,7 @@ export class ClientSearchConfig {
|
||||
listMetafiles: boolean = true;
|
||||
}
|
||||
|
||||
@SubConfigClass({tags: {client: true}})
|
||||
@SubConfigClass({tags: {client: true}, softReadonly: true})
|
||||
export class ClientAlbumConfig {
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
@ -174,7 +173,7 @@ export class ClientAlbumConfig {
|
||||
enabled: boolean = true;
|
||||
}
|
||||
|
||||
@SubConfigClass({tags: {client: true}})
|
||||
@SubConfigClass({tags: {client: true}, softReadonly: true})
|
||||
export class ClientSharingConfig {
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
@ -196,7 +195,7 @@ export class ClientSharingConfig {
|
||||
passwordProtected: boolean = true;
|
||||
}
|
||||
|
||||
@SubConfigClass({tags: {client: true}})
|
||||
@SubConfigClass({tags: {client: true}, softReadonly: true})
|
||||
export class ClientRandomPhotoConfig {
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
@ -209,7 +208,7 @@ export class ClientRandomPhotoConfig {
|
||||
enabled: boolean = true;
|
||||
}
|
||||
|
||||
@SubConfigClass({tags: {client: true}})
|
||||
@SubConfigClass({tags: {client: true}, softReadonly: true})
|
||||
export class MapLayers {
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
@ -230,7 +229,7 @@ export class MapLayers {
|
||||
url: string = '';
|
||||
}
|
||||
|
||||
@SubConfigClass({tags: {client: true}})
|
||||
@SubConfigClass({tags: {client: true}, softReadonly: true})
|
||||
export class ClientMapConfig {
|
||||
@ConfigProperty<boolean, ClientConfig, TAGS>({
|
||||
onNewValue: (value, config) => {
|
||||
@ -293,7 +292,7 @@ export class ClientMapConfig {
|
||||
maxPreviewMarkers: number = 50;
|
||||
}
|
||||
|
||||
@SubConfigClass({tags: {client: true}})
|
||||
@SubConfigClass({tags: {client: true}, softReadonly: true})
|
||||
export class ClientThumbnailConfig {
|
||||
@ConfigProperty({
|
||||
type: 'unsignedInt', max: 100,
|
||||
@ -351,7 +350,7 @@ export enum NavigationLinkTypes {
|
||||
gallery = 1, faces, albums, search, url
|
||||
}
|
||||
|
||||
@SubConfigClass({tags: {client: true}})
|
||||
@SubConfigClass({tags: {client: true}, softReadonly: true})
|
||||
export class NavigationLinkConfig {
|
||||
@ConfigProperty({
|
||||
type: NavigationLinkTypes,
|
||||
@ -400,7 +399,7 @@ export class NavigationLinkConfig {
|
||||
}
|
||||
}
|
||||
|
||||
@SubConfigClass<TAGS>({tags: {client: true}})
|
||||
@SubConfigClass<TAGS>({tags: {client: true}, softReadonly: true})
|
||||
export class NavBarConfig {
|
||||
@ConfigProperty({
|
||||
tags: {
|
||||
@ -427,7 +426,7 @@ export class NavBarConfig {
|
||||
];
|
||||
}
|
||||
|
||||
@SubConfigClass<TAGS>({tags: {client: true, priority: ConfigPriority.advanced}})
|
||||
@SubConfigClass<TAGS>({tags: {client: true, priority: ConfigPriority.advanced}, softReadonly: true})
|
||||
export class ClientGalleryConfig {
|
||||
@ConfigProperty({
|
||||
tags: {
|
||||
@ -528,7 +527,7 @@ export class ClientGalleryConfig {
|
||||
defaultSlideshowSpeed: number = 5;
|
||||
}
|
||||
|
||||
@SubConfigClass({tags: {client: true}})
|
||||
@SubConfigClass({tags: {client: true}, softReadonly: true})
|
||||
export class ClientVideoConfig {
|
||||
@ConfigProperty({
|
||||
tags: {
|
||||
@ -562,7 +561,7 @@ export class ClientVideoConfig {
|
||||
|
||||
}
|
||||
|
||||
@SubConfigClass({tags: {client: true}})
|
||||
@SubConfigClass({tags: {client: true}, softReadonly: true})
|
||||
export class ClientPhotoConvertingConfig {
|
||||
@ConfigProperty({
|
||||
tags: {
|
||||
@ -584,7 +583,7 @@ export class ClientPhotoConvertingConfig {
|
||||
loadFullImageOnZoom: boolean = true;
|
||||
}
|
||||
|
||||
@SubConfigClass({tags: {client: true}})
|
||||
@SubConfigClass({tags: {client: true}, softReadonly: true})
|
||||
export class ClientPhotoConfig {
|
||||
@ConfigProperty({
|
||||
tags: {
|
||||
@ -605,7 +604,7 @@ export class ClientPhotoConfig {
|
||||
supportedFormats: string[] = ['gif', 'jpeg', 'jpg', 'jpe', 'png', 'webp', 'svg'];
|
||||
}
|
||||
|
||||
@SubConfigClass({tags: {client: true}})
|
||||
@SubConfigClass({tags: {client: true}, softReadonly: true})
|
||||
export class ClientGPXCompressingConfig {
|
||||
@ConfigProperty({
|
||||
tags: {
|
||||
@ -619,7 +618,7 @@ export class ClientGPXCompressingConfig {
|
||||
enabled: boolean = true;
|
||||
}
|
||||
|
||||
@SubConfigClass({tags: {client: true}})
|
||||
@SubConfigClass({tags: {client: true}, softReadonly: true})
|
||||
export class ClientMediaConfig {
|
||||
@ConfigProperty({
|
||||
tags: {
|
||||
@ -644,7 +643,7 @@ export class ClientMediaConfig {
|
||||
Photo: ClientPhotoConfig = new ClientPhotoConfig();
|
||||
}
|
||||
|
||||
@SubConfigClass({tags: {client: true}})
|
||||
@SubConfigClass({tags: {client: true}, softReadonly: true})
|
||||
export class ClientMetaFileConfig {
|
||||
@ConfigProperty({
|
||||
tags: {
|
||||
@ -693,7 +692,7 @@ export class ClientMetaFileConfig {
|
||||
supportedFormats: string[] = ['gpx', 'pg2conf', 'md'];
|
||||
}
|
||||
|
||||
@SubConfigClass({tags: {client: true}})
|
||||
@SubConfigClass({tags: {client: true}, softReadonly: true})
|
||||
export class ClientFacesConfig {
|
||||
@ConfigProperty({
|
||||
tags: {
|
||||
@ -728,7 +727,7 @@ export class ClientFacesConfig {
|
||||
readAccessMinRole: UserRoles = UserRoles.User;
|
||||
}
|
||||
|
||||
@SubConfigClass({tags: {client: true}})
|
||||
@SubConfigClass({tags: {client: true}, softReadonly: true})
|
||||
export class ClientServiceConfig {
|
||||
|
||||
@ConfigProperty({
|
||||
@ -783,7 +782,7 @@ export class ClientServiceConfig {
|
||||
customHTMLHead: string = '';
|
||||
}
|
||||
|
||||
@SubConfigClass({tags: {client: true}})
|
||||
@SubConfigClass({tags: {client: true}, softReadonly: true})
|
||||
export class ClientUserConfig {
|
||||
|
||||
@ConfigProperty<boolean, ClientConfig>({
|
||||
@ -794,7 +793,6 @@ export class ClientUserConfig {
|
||||
},
|
||||
tags: {
|
||||
name: $localize`Password protection`,
|
||||
priority: ConfigPriority.advanced,
|
||||
},
|
||||
description: $localize`Enables user management with login to password protect the gallery.`,
|
||||
})
|
||||
@ -812,7 +810,7 @@ export class ClientUserConfig {
|
||||
}
|
||||
|
||||
|
||||
@SubConfigClass<TAGS>({tags: {client: true}})
|
||||
@SubConfigClass<TAGS>({tags: {client: true}, softReadonly: true})
|
||||
export class ClientConfig {
|
||||
|
||||
@ConfigProperty()
|
||||
|
@ -103,6 +103,7 @@ import {GalleryStatisticComponent} from './ui/settings/gallery-statistic/gallery
|
||||
import { JobButtonComponent } from './ui/settings/workflow/button/job-button.settings.component';
|
||||
import { JobProgressComponent } from './ui/settings/workflow/progress/job-progress.settings.component';
|
||||
import {SettingsEntryComponent} from './ui/settings/template/settings-entry/settings-entry.component';
|
||||
import { UsersComponent } from './ui/settings/users/users.component';
|
||||
|
||||
@Injectable()
|
||||
export class MyHammerConfig extends HammerGestureConfig {
|
||||
@ -236,6 +237,7 @@ Marker.prototype.options.icon = iconDefault;
|
||||
StringifySearchQuery,
|
||||
FileDTOToPathPipe,
|
||||
PhotoFilterPipe,
|
||||
UsersComponent,
|
||||
],
|
||||
providers: [
|
||||
{provide: HTTP_INTERCEPTORS, useClass: CSRFInterceptor, multi: true},
|
||||
|
@ -76,17 +76,17 @@
|
||||
<div class="py-md-1 px-md-0"
|
||||
*ngFor="let s of contents;"
|
||||
[hidden]="!s.HasAvailableSettings">
|
||||
<button class="btn btn-link nav-link text-start p-0"
|
||||
(click)="viewportScroller.scrollToAnchor(s.ConfigPath)"
|
||||
>
|
||||
<span class="oi oi-{{s.icon}}"></span> {{s.Name}}
|
||||
</button>
|
||||
<button class="btn btn-link nav-link text-start ms-3 p-0"
|
||||
*ngFor="let n of s.nestedConfigs;"
|
||||
[hidden]="!n.visible()"
|
||||
(click)="viewportScroller.scrollToAnchor(n.id)">
|
||||
<span class="oi oi-{{n.icon}}"></span> {{n.name}}
|
||||
</button>
|
||||
<button class="btn btn-link nav-link text-start p-0"
|
||||
(click)="viewportScroller.scrollToAnchor(s.ConfigPath)"
|
||||
>
|
||||
<span class="oi oi-{{s.icon}}"></span> {{s.Name}}
|
||||
</button>
|
||||
<button class="btn btn-link nav-link text-start ms-3 p-0"
|
||||
*ngFor="let n of s.nestedConfigs;"
|
||||
[hidden]="!n.visible()"
|
||||
(click)="viewportScroller.scrollToAnchor(n.id)">
|
||||
<span class="oi oi-{{n.icon}}"></span> {{n.name}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -107,6 +107,7 @@
|
||||
<hr class="mt-2"/>
|
||||
<app-settings-gallery-statistic></app-settings-gallery-statistic>
|
||||
</ng-container>
|
||||
<app-settings-users *ngIf="cp=='Users'"></app-settings-users>
|
||||
</app-settings-template>
|
||||
</div>
|
||||
</div>
|
||||
|
81
src/frontend/app/ui/settings/users/users.component.html
Normal file
81
src/frontend/app/ui/settings/users/users.component.html
Normal file
@ -0,0 +1,81 @@
|
||||
<ng-container *ngIf="Enabled">
|
||||
<div class="row mt-2">
|
||||
<div class="col-auto">
|
||||
<h5 i18n>User list</h5>
|
||||
</div>
|
||||
<div class="col">
|
||||
<hr/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th i18n>Name</th>
|
||||
<th i18n>Role</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let user of users">
|
||||
<td>{{user.name}}</td>
|
||||
<td *ngIf="canModifyUser(user)">
|
||||
<select class="form-select" [(ngModel)]="user.role" (change)="updateRole(user)" required>
|
||||
<option *ngFor="let repository of userRoles" [value]="repository.key">
|
||||
{{repository.value}}
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
<td *ngIf="!canModifyUser(user)">
|
||||
{{user.role | stringifyRole}}
|
||||
</td>
|
||||
<td>
|
||||
<button [disabled]="!canModifyUser(user)" (click)="deleteUser(user)"
|
||||
[ngClass]="canModifyUser(user)? 'btn-danger':'btn-secondary'"
|
||||
class="btn float-end">
|
||||
<span class="oi oi-trash" aria-hidden="true" aria-label="Delete"></span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<button class="btn btn-primary float-end"
|
||||
(click)="initNewUser()" i18n>+ Add user
|
||||
</button>
|
||||
</ng-container>
|
||||
|
||||
<!-- Modal -->
|
||||
<div bsModal #userModal="bs-modal" class="modal fade" id="userModal" tabindex="-1" role="dialog"
|
||||
aria-labelledby="userModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="userModalLabel" i18n>Add new User</h5>
|
||||
<button type="button" class="btn-close" (click)="userModal.hide()" data-dismiss="modal" aria-label="Close">
|
||||
</button>
|
||||
</div>
|
||||
<form #NewUserForm="ngForm">
|
||||
<div class="modal-body">
|
||||
<input type="text" class="form-control" i18n-placeholder placeholder="Username"
|
||||
[(ngModel)]="newUser.name" name="name" required>
|
||||
<input type="password" class="form-control" i18n-placeholder placeholder="Password"
|
||||
[(ngModel)]="newUser.password" name="password" autocomplete="off" required>
|
||||
<select class="form-select" [(ngModel)]="newUser.role" name="role" required>
|
||||
<option *ngFor="let repository of userRoles" [value]="repository.key">{{repository.value}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" (click)="userModal.hide()" i18n>Close</button>
|
||||
<button type="button" class="btn btn-primary" data-dismiss="modal"
|
||||
(click)="addNewUser()"
|
||||
[disabled]="!NewUserForm.form.valid" i18n>Add User
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
110
src/frontend/app/ui/settings/users/users.component.ts
Normal file
110
src/frontend/app/ui/settings/users/users.component.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import {Component, OnInit, ViewChild} from '@angular/core';
|
||||
import {ModalDirective} from 'ngx-bootstrap/modal';
|
||||
import {UserDTO, UserRoles} from '../../../../../common/entities/UserDTO';
|
||||
import {AuthenticationService} from '../../../model/network/authentication.service';
|
||||
import {NavigationService} from '../../../model/navigation.service';
|
||||
import {NotificationService} from '../../../model/notification.service';
|
||||
import {Utils} from '../../../../../common/Utils';
|
||||
import {ErrorCodes, ErrorDTO} from '../../../../../common/entities/Error';
|
||||
import {UsersSettingsService} from './users.service';
|
||||
import {SettingsService} from '../settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-users',
|
||||
templateUrl: './users.component.html',
|
||||
styleUrls: ['./users.component.css']
|
||||
})
|
||||
export class UsersComponent implements OnInit {
|
||||
|
||||
@ViewChild('userModal', {static: false}) public childModal: ModalDirective;
|
||||
public newUser = {} as UserDTO;
|
||||
public userRoles: { key: number; value: string }[] = [];
|
||||
public users: UserDTO[] = [];
|
||||
public error: string = null;
|
||||
public inProgress = false;
|
||||
Changed = false;
|
||||
|
||||
|
||||
|
||||
constructor(
|
||||
private authService: AuthenticationService,
|
||||
private navigation: NavigationService,
|
||||
private userSettings: UsersSettingsService,
|
||||
private settingsService: SettingsService,
|
||||
private notification: NotificationService
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (
|
||||
!this.authService.isAuthenticated() ||
|
||||
this.authService.user.value.role < UserRoles.Admin
|
||||
) {
|
||||
this.navigation.toLogin();
|
||||
return;
|
||||
}
|
||||
this.userRoles = Utils.enumToArray(UserRoles)
|
||||
.filter((r) => r.key !== UserRoles.LimitedGuest)
|
||||
.filter((r) => r.key <= this.authService.user.value.role)
|
||||
.sort((a, b) => a.key - b.key);
|
||||
|
||||
this.getUsersList();
|
||||
}
|
||||
|
||||
canModifyUser(user: UserDTO): boolean {
|
||||
const currentUser = this.authService.user.value;
|
||||
if (!currentUser) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return currentUser.name !== user.name && currentUser.role >= user.role;
|
||||
}
|
||||
|
||||
get Enabled():boolean{
|
||||
return this.settingsService.settings.value.Users.authenticationRequired;
|
||||
}
|
||||
|
||||
|
||||
initNewUser(): void {
|
||||
this.newUser = {role: UserRoles.User} as UserDTO;
|
||||
this.childModal.show();
|
||||
}
|
||||
|
||||
async addNewUser(): Promise<void> {
|
||||
try {
|
||||
await this.userSettings.createUser(this.newUser);
|
||||
await this.getUsersList();
|
||||
this.childModal.hide();
|
||||
} catch (e) {
|
||||
const err: ErrorDTO = e;
|
||||
this.notification.error(
|
||||
err.message + ', ' + err.details,
|
||||
$localize`User creation error!`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async updateRole(user: UserDTO): Promise<void> {
|
||||
await this.userSettings.updateRole(user);
|
||||
await this.getUsersList();
|
||||
this.childModal.hide();
|
||||
}
|
||||
|
||||
async deleteUser(user: UserDTO): Promise<void> {
|
||||
await this.userSettings.deleteUser(user);
|
||||
await this.getUsersList();
|
||||
this.childModal.hide();
|
||||
}
|
||||
|
||||
private async getUsersList(): Promise<void> {
|
||||
try {
|
||||
this.users = await this.userSettings.getUsers();
|
||||
} catch (err) {
|
||||
this.users = [];
|
||||
if ((err as ErrorDTO).code !== ErrorCodes.USER_MANAGEMENT_DISABLED) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
31
src/frontend/app/ui/settings/users/users.service.ts
Normal file
31
src/frontend/app/ui/settings/users/users.service.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {UserDTO} from '../../../../../common/entities/UserDTO';
|
||||
import {NetworkService} from '../../../model/network/network.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UsersSettingsService {
|
||||
|
||||
constructor(private networkService: NetworkService) {
|
||||
}
|
||||
|
||||
public createUser(user: UserDTO): Promise<string> {
|
||||
return this.networkService.putJson('/user', {newUser: user});
|
||||
}
|
||||
|
||||
|
||||
public getUsers(): Promise<Array<UserDTO>> {
|
||||
return this.networkService.getJson('/user/list');
|
||||
}
|
||||
|
||||
public deleteUser(user: UserDTO): Promise<void> {
|
||||
return this.networkService.deleteJson('/user/' + user.id);
|
||||
}
|
||||
|
||||
public updateRole(user: UserDTO): Promise<void> {
|
||||
return this.networkService.postJson('/user/' + user.id + '/role', {
|
||||
newRole: user.role,
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user