mirror of
https://github.com/bpatrik/pigallery2.git
synced 2024-12-23 01:27:14 +02:00
Cleaning up config #569
This commit is contained in:
parent
875d120df8
commit
c191549270
@ -100,9 +100,10 @@ import {GallerySortingService} from './ui/gallery/navigator/sorting.service';
|
||||
import {FilterService} from './ui/gallery/filter/filter.service';
|
||||
import {TemplateComponent} from './ui/settings/template/template.component';
|
||||
import {AbstractSettingsService} from './ui/settings/_abstract/abstract.settings.service';
|
||||
import { WorkflowComponent } from './ui/settings/workflow/workflow.component';
|
||||
import {WorkflowComponent} from './ui/settings/workflow/workflow.component';
|
||||
import {JobProgressComponent} from './ui/settings/jobs/progress/job-progress.settings.component';
|
||||
import {JobButtonComponent} from './ui/settings/jobs/button/job-button.settings.component';
|
||||
import {GalleryStatisticComponent} from './ui/settings/gallery-statistic/gallery-statistic.component';
|
||||
|
||||
@Injectable()
|
||||
export class MyHammerConfig extends HammerGestureConfig {
|
||||
@ -222,24 +223,8 @@ Marker.prototype.options.icon = iconDefault;
|
||||
TemplateComponent,
|
||||
JobProgressComponent,
|
||||
JobButtonComponent,
|
||||
/* UserMangerSettingsComponent,
|
||||
DatabaseSettingsComponent,
|
||||
MapSettingsComponent,
|
||||
ThumbnailSettingsComponent,
|
||||
VideoSettingsComponent,
|
||||
PhotoSettingsComponent,
|
||||
MetaFileSettingsComponent,
|
||||
SearchSettingsComponent,
|
||||
ShareSettingsComponent,
|
||||
RandomPhotoSettingsComponent,
|
||||
FacesSettingsComponent,
|
||||
AlbumsSettingsComponent,
|
||||
OtherSettingsComponent,
|
||||
IndexingSettingsComponent,
|
||||
JobsSettingsComponent,
|
||||
JobProgressComponent,
|
||||
JobButtonComponent,
|
||||
PreviewSettingsComponent,*/
|
||||
WorkflowComponent,
|
||||
GalleryStatisticComponent,
|
||||
|
||||
// Pipes
|
||||
StringifyRole,
|
||||
@ -252,7 +237,6 @@ Marker.prototype.options.icon = iconDefault;
|
||||
StringifySearchQuery,
|
||||
FileDTOToPathPipe,
|
||||
PhotoFilterPipe,
|
||||
WorkflowComponent,
|
||||
],
|
||||
providers: [
|
||||
{provide: HTTP_INTERCEPTORS, useClass: CSRFInterceptor, multi: true},
|
||||
|
@ -91,7 +91,14 @@
|
||||
#tmpl
|
||||
icon="list"
|
||||
[ConfigPath]="cp"
|
||||
[hidden]="!tmpl.HasAvailableSettings"></app-settings-template>
|
||||
[hidden]="!tmpl.HasAvailableSettings">
|
||||
<ng-container
|
||||
*ngIf="cp=='Indexing'">
|
||||
<br/>
|
||||
<hr class="mt-2"/>
|
||||
<app-settings-gallery-statistic></app-settings-gallery-statistic>
|
||||
</ng-container>
|
||||
</app-settings-template>
|
||||
<!-- <app-settings-template #setting #server
|
||||
icon="list"
|
||||
[ConfigPath]="'Server'"
|
||||
|
@ -1,42 +0,0 @@
|
||||
<form #settingsForm="ngForm" class="form-horizontal">
|
||||
<div class="card mb-4">
|
||||
<h5 class="card-header">
|
||||
<span class="oi oi-{{icon}}"></span> {{Name}}
|
||||
<div class="switch-wrapper">
|
||||
<bSwitch
|
||||
class="switch"
|
||||
name="enabled"
|
||||
switch-on-color="success"
|
||||
[switch-inverse]="true"
|
||||
switch-off-text="Disabled"
|
||||
switch-on-text="Enabled"
|
||||
i18n-switch-off-text
|
||||
i18n-switch-on-text
|
||||
[switch-disabled]="inProgress"
|
||||
[switch-handle-width]="100"
|
||||
[switch-label-width]="20"
|
||||
[(ngModel)]="states.enabled.value">
|
||||
</bSwitch>
|
||||
</div>
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||
|
||||
<ng-container i18n>Shows albums tab in the top bar and enables creating saved searches.</ng-container>
|
||||
<br/>
|
||||
<ng-container i18n>Note: custom albums are not supported, see:</ng-container>
|
||||
<a href="https://github.com/bpatrik/pigallery2/issues/301">#301</a>
|
||||
|
||||
|
||||
<button class="btn btn-success float-end"
|
||||
[disabled]="!settingsForm.form.valid || !changed || inProgress"
|
||||
(click)="save()" i18n>Save
|
||||
</button>
|
||||
<button class="btn btn-secondary float-end"
|
||||
[disabled]=" !changed || inProgress"
|
||||
(click)="reset()" i18n>Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
@ -1,41 +0,0 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {AlbumsSettingsService} from './albums.settings.service';
|
||||
import {SettingsComponentDirective} from '../_abstract/abstract.settings.component';
|
||||
import {AuthenticationService} from '../../../model/network/authentication.service';
|
||||
import {NavigationService} from '../../../model/navigation.service';
|
||||
import {NotificationService} from '../../../model/notification.service';
|
||||
import {ClientAlbumConfig} from '../../../../../common/config/public/ClientConfig';
|
||||
import {SettingsService} from '../settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-albums',
|
||||
templateUrl: './albums.settings.component.html',
|
||||
styleUrls: [
|
||||
'./albums.settings.component.css',
|
||||
'../_abstract/abstract.settings.component.css',
|
||||
],
|
||||
providers: [AlbumsSettingsService],
|
||||
})
|
||||
export class AlbumsSettingsComponent extends SettingsComponentDirective<ClientAlbumConfig> {
|
||||
constructor(
|
||||
authService: AuthenticationService,
|
||||
navigation: NavigationService,
|
||||
settingsService: AlbumsSettingsService,
|
||||
notification: NotificationService,
|
||||
globalSettingsService: SettingsService
|
||||
) {
|
||||
super(
|
||||
$localize`Albums`,
|
||||
'grid-two-up',
|
||||
authService,
|
||||
navigation,
|
||||
settingsService,
|
||||
notification,
|
||||
globalSettingsService,
|
||||
(s) => s.Album
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,27 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NetworkService } from '../../../model/network/network.service';
|
||||
import { SettingsService } from '../settings.service';
|
||||
import { AbstractSettingsService } from '../_abstract/abstract.settings.service';
|
||||
import { ClientAlbumConfig } from '../../../../../common/config/public/ClientConfig';
|
||||
|
||||
@Injectable()
|
||||
export class AlbumsSettingsService extends AbstractSettingsService<ClientAlbumConfig> {
|
||||
constructor(
|
||||
private networkService: NetworkService,
|
||||
settingsService: SettingsService
|
||||
) {
|
||||
super(settingsService);
|
||||
}
|
||||
|
||||
public isSupported(): boolean {
|
||||
return this.settingsService.settings.value.Map.enabled === true;
|
||||
}
|
||||
|
||||
hasAvailableSettings(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public updateSettings(settings: ClientAlbumConfig): Promise<void> {
|
||||
return this.networkService.putJson('/settings/albums', { settings });
|
||||
}
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
<form #settingsForm="ngForm" class="form-horizontal">
|
||||
<div class="card mb-4">
|
||||
<h5 class="card-header">
|
||||
<span class="oi oi-{{icon}}"></span> {{Name}}
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||
|
||||
<app-settings-entry
|
||||
name="Type"
|
||||
[optionMap]="dbTypesMap"
|
||||
[ngModel]="states.type"
|
||||
i18n-name
|
||||
[required]="true">
|
||||
<small *ngIf="states.type.value == DatabaseType.mysql"
|
||||
class="form-text text-muted" i18n>Install manually mysql node module to use mysql (npm install mysql)
|
||||
</small>
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
<app-settings-entry
|
||||
name="Database folder"
|
||||
description="All file-based data will be stored here (sqlite database, user database in case of memory db, job history data)"
|
||||
[ngModel]="states.dbFolder"
|
||||
i18n-name i18n-description
|
||||
[dockerWarning]="(settingsService.Settings | async).Environment.isDocker"
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
<ng-container *ngIf="states.type.value == DatabaseType.mysql">
|
||||
|
||||
|
||||
<app-settings-entry
|
||||
name="Host"
|
||||
[ngModel]="states.mysql.host"
|
||||
i18n-name
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="Port"
|
||||
[ngModel]="states.mysql.port"
|
||||
i18n-name
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
<app-settings-entry
|
||||
name="Database"
|
||||
[ngModel]="states.mysql.database"
|
||||
i18n-name
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="Username"
|
||||
[ngModel]="states.mysql.username"
|
||||
placeholder="username"
|
||||
i18n-name
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
<app-settings-entry
|
||||
name="Password"
|
||||
[ngModel]="states.mysql.password"
|
||||
placeholder="password"
|
||||
i18n-name
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
</ng-container>
|
||||
|
||||
|
||||
<button class="btn btn-success float-end"
|
||||
[disabled]="!settingsForm.form.valid || !changed || inProgress"
|
||||
(click)="save()" i18n>Save
|
||||
</button>
|
||||
<button class="btn btn-secondary float-end"
|
||||
[disabled]=" !changed || inProgress"
|
||||
(click)="reset()" i18n>Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
@ -1,63 +0,0 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {AuthenticationService} from '../../../model/network/authentication.service';
|
||||
import {Utils} from '../../../../../common/Utils';
|
||||
import {NotificationService} from '../../../model/notification.service';
|
||||
import {NavigationService} from '../../../model/navigation.service';
|
||||
import {SettingsComponentDirective} from '../_abstract/abstract.settings.component';
|
||||
import {DatabaseSettingsService} from './database.settings.service';
|
||||
import {
|
||||
DatabaseType,
|
||||
ServerDataBaseConfig,
|
||||
} from '../../../../../common/config/private/PrivateConfig';
|
||||
import {SettingsService} from '../settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-database',
|
||||
templateUrl: './database.settings.component.html',
|
||||
styleUrls: [
|
||||
'./database.settings.component.css',
|
||||
'../_abstract/abstract.settings.component.css',
|
||||
],
|
||||
providers: [DatabaseSettingsService],
|
||||
})
|
||||
export class DatabaseSettingsComponent
|
||||
extends SettingsComponentDirective<ServerDataBaseConfig>
|
||||
implements OnInit {
|
||||
public types = Utils.enumToArray(DatabaseType);
|
||||
public DatabaseType = DatabaseType;
|
||||
|
||||
constructor(
|
||||
authService: AuthenticationService,
|
||||
navigation: NavigationService,
|
||||
settingsService: DatabaseSettingsService,
|
||||
notification: NotificationService,
|
||||
globalSettingsService: SettingsService
|
||||
) {
|
||||
super(
|
||||
$localize`Database`,
|
||||
'list',
|
||||
authService,
|
||||
navigation,
|
||||
settingsService,
|
||||
notification,
|
||||
globalSettingsService,
|
||||
(s) => s.Database
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit();
|
||||
}
|
||||
|
||||
dbTypesMap = (v: { key: number; value: string }) => {
|
||||
if (v.key === DatabaseType.sqlite) {
|
||||
v.value += ' ' + $localize`(recommended)`;
|
||||
} else if (v.value === DatabaseType[DatabaseType.memory]) {
|
||||
v.value += ' ' + $localize`(deprecated, will be removed)`;
|
||||
}
|
||||
return v;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,19 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NetworkService } from '../../../model/network/network.service';
|
||||
import { AbstractSettingsService } from '../_abstract/abstract.settings.service';
|
||||
import { SettingsService } from '../settings.service';
|
||||
import { ServerDataBaseConfig } from '../../../../../common/config/private/PrivateConfig';
|
||||
|
||||
@Injectable()
|
||||
export class DatabaseSettingsService extends AbstractSettingsService<ServerDataBaseConfig> {
|
||||
constructor(
|
||||
private networkService: NetworkService,
|
||||
settingsService: SettingsService
|
||||
) {
|
||||
super(settingsService);
|
||||
}
|
||||
|
||||
public updateSettings(settings: ServerDataBaseConfig): Promise<void> {
|
||||
return this.networkService.putJson('/settings/database', { settings });
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
.panel-info {
|
||||
text-align: center;
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
<form #settingsForm="ngForm">
|
||||
<div class="card mb-4"
|
||||
[ngClass]="states.enabled.value && !settingsService.isSupported()?'panel-warning':''">
|
||||
<h5 class="card-header">
|
||||
<span class="oi oi-{{icon}}"></span> {{Name}}
|
||||
<div class="switch-wrapper"
|
||||
[class.changed-settings]="states.enabled.value != states.enabled.default">
|
||||
<bSwitch
|
||||
class="switch"
|
||||
name="enabled"
|
||||
switch-on-color="success"
|
||||
[switch-inverse]="true"
|
||||
switch-off-text="Disabled"
|
||||
switch-on-text="Enabled"
|
||||
i18n-switch-off-text
|
||||
i18n-switch-on-text
|
||||
[switch-disabled]="inProgress || (!states.enabled.value && !settingsService.isSupported())"
|
||||
[switch-handle-width]="100"
|
||||
[switch-label-width]="20"
|
||||
[(ngModel)]="states.enabled.value">
|
||||
</bSwitch>
|
||||
</div>
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||
|
||||
<ng-container *ngIf="states.enabled.value || settingsService.isSupported()">
|
||||
|
||||
|
||||
<app-settings-entry
|
||||
name="Override keywords"
|
||||
[ngModel]="states.keywordsToPersons"
|
||||
description="If a photo has the same face (person) name and keyword, the app removes the duplicate, keeping the face only."
|
||||
i18n-name i18n-description>
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="Face starring right"
|
||||
[ngModel]="states.writeAccessMinRole"
|
||||
description="Required minimum right to star (favourite) a face."
|
||||
i18n-name i18n-description>
|
||||
</app-settings-entry>
|
||||
|
||||
</ng-container>
|
||||
|
||||
<div class="panel-info" *ngIf="(!states.enabled.value && !settingsService.isSupported())" i18n>
|
||||
Faces are not supported with these settings.
|
||||
</div>
|
||||
<button class="btn btn-success float-end"
|
||||
[disabled]="!settingsForm.form.valid || !changed || inProgress"
|
||||
(click)="save()" i18n>Save
|
||||
</button>
|
||||
<button class="btn btn-secondary float-end"
|
||||
[disabled]=" !changed || inProgress"
|
||||
(click)="reset()" i18n>Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
@ -1,48 +0,0 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {SettingsComponentDirective} from '../_abstract/abstract.settings.component';
|
||||
import {AuthenticationService} from '../../../model/network/authentication.service';
|
||||
import {NavigationService} from '../../../model/navigation.service';
|
||||
import {NotificationService} from '../../../model/notification.service';
|
||||
import {FacesSettingsService} from './faces.settings.service';
|
||||
import {Utils} from '../../../../../common/Utils';
|
||||
import {UserRoles} from '../../../../../common/entities/UserDTO';
|
||||
import {ClientFacesConfig} from '../../../../../common/config/public/ClientConfig';
|
||||
import {SettingsService} from '../settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-faces',
|
||||
templateUrl: './faces.settings.component.html',
|
||||
styleUrls: [
|
||||
'./faces.settings.component.css',
|
||||
'../_abstract/abstract.settings.component.css',
|
||||
],
|
||||
providers: [FacesSettingsService],
|
||||
})
|
||||
export class FacesSettingsComponent extends SettingsComponentDirective<ClientFacesConfig> {
|
||||
public readonly 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);
|
||||
|
||||
constructor(
|
||||
authService: AuthenticationService,
|
||||
navigation: NavigationService,
|
||||
settingsService: FacesSettingsService,
|
||||
notification: NotificationService,
|
||||
globalSettingsService: SettingsService
|
||||
) {
|
||||
super(
|
||||
$localize`Faces`,
|
||||
'people',
|
||||
authService,
|
||||
navigation,
|
||||
settingsService,
|
||||
notification,
|
||||
globalSettingsService,
|
||||
(s) => s.Faces
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,32 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NetworkService } from '../../../model/network/network.service';
|
||||
import { SettingsService } from '../settings.service';
|
||||
import { AbstractSettingsService } from '../_abstract/abstract.settings.service';
|
||||
import { ClientFacesConfig } from '../../../../../common/config/public/ClientConfig';
|
||||
import { DatabaseType } from '../../../../../common/config/private/PrivateConfig';
|
||||
|
||||
@Injectable()
|
||||
export class FacesSettingsService extends AbstractSettingsService<ClientFacesConfig> {
|
||||
constructor(
|
||||
private networkService: NetworkService,
|
||||
settingsService: SettingsService
|
||||
) {
|
||||
super(settingsService);
|
||||
}
|
||||
|
||||
hasAvailableSettings(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public isSupported(): boolean {
|
||||
return (
|
||||
this.settingsService.settings.value.Database.type !==
|
||||
DatabaseType.memory &&
|
||||
this.settingsService.settings.value.Search.enabled === true
|
||||
);
|
||||
}
|
||||
|
||||
public updateSettings(settings: ClientFacesConfig): Promise<void> {
|
||||
return this.networkService.putJson('/settings/faces', { settings });
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<div class="row statics">
|
||||
<div class="col-md-2 col-12" i18n>
|
||||
Statistic:
|
||||
</div>
|
||||
<div class="col-md-2 col-6">
|
||||
<span class="oi oi-folder" title="Folders" i18n-title aria-hidden="true"> </span>
|
||||
{{settingsService.statistic.value ? settingsService.statistic.value.directories : '...'}}
|
||||
</div>
|
||||
<div class="col-md-2 col-6">
|
||||
<span class="oi oi-camera-slr" title="Photos" i18n-title aria-hidden="true"> </span>
|
||||
{{settingsService.statistic.value ? settingsService.statistic.value.photos : '...'}}
|
||||
</div>
|
||||
<div class="col-md-2 col-6">
|
||||
<span class="oi oi-video" title="Videos" i18n-title aria-hidden="true"> </span>
|
||||
{{settingsService.statistic.value ? settingsService.statistic.value.videos : '...'}}
|
||||
|
||||
</div>
|
||||
<div class="col-md-2 col-6">
|
||||
<span class="oi oi-people" title="Persons" i18n-title aria-hidden="true"> </span>
|
||||
{{settingsService.statistic.value ? settingsService.statistic.value.persons : '...'}}
|
||||
</div>
|
||||
<div class="col-md-2 col-6">
|
||||
<span class="oi oi-pie-chart" title="Size" i18n-title aria-hidden="true"> </span>
|
||||
{{settingsService.statistic.value ? (settingsService.statistic.value.diskUsage | fileSize) : '...'}}
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,19 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {SettingsService} from '../settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-gallery-statistic',
|
||||
templateUrl: './gallery-statistic.component.html',
|
||||
styleUrls: ['./gallery-statistic.component.css']
|
||||
})
|
||||
export class GalleryStatisticComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
public settingsService: SettingsService
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
.buttons-row {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.statics span.oi {
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.progress-details {
|
||||
font-weight: bold;
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
<form #settingsForm="ngForm" class="form-horizontal">
|
||||
<div class="card mb-4">
|
||||
<h5 class="card-header">
|
||||
<span class="oi oi-{{icon}}"></span> {{Name}}
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||
|
||||
<ng-container *ngIf="configPriority>0">
|
||||
|
||||
<app-settings-entry
|
||||
name="Index cache timeout [ms]"
|
||||
description="If there was no indexing in this time, it reindexes. (skipped if indexes are in DB and sensitivity is low)"
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.cachedFolderTimeout"
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
<app-settings-entry
|
||||
name="Folder reindexing sensitivity"
|
||||
[ngModel]="states.reIndexingSensitivity"
|
||||
description="Set the reindexing sensitivity. High value check the folders for change more often."
|
||||
i18n-description i18n-name
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="Exclude Folder List"
|
||||
i18n-name
|
||||
placeholder="/media/images/family/private;private;family/private"
|
||||
[ngModel]="states.excludeFolderList">
|
||||
<small class="form-text text-muted">
|
||||
<ng-container i18n>Folders to exclude from indexing</ng-container>
|
||||
<br/>
|
||||
<ng-container
|
||||
i18n>';' separated strings. If an entry starts with '/' it is treated as an absolute path. If it doesn't
|
||||
start with '/' but contains a '/', the path is relative to the image directory. If it doesn't contain a
|
||||
'/', any folder with this name will be excluded.
|
||||
</ng-container>
|
||||
</small>
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="Exclude File List"
|
||||
i18n-name
|
||||
placeholder=".ignore;.pg2ignore"
|
||||
[ngModel]="states.excludeFileList">
|
||||
<small class="form-text text-muted">
|
||||
<ng-container i18n>Files that mark a folder to be excluded from indexing</ng-container>
|
||||
<br/>
|
||||
<ng-container
|
||||
i18n>';' separated strings. Any folder that contains a file with this name will be excluded from
|
||||
indexing.
|
||||
</ng-container>
|
||||
</small>
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
<button class="btn btn-success float-end"
|
||||
[disabled]="!settingsForm.form.valid || !changed || inProgress"
|
||||
(click)="save()" i18n>Save
|
||||
</button>
|
||||
<button class="btn btn-secondary float-end"
|
||||
[disabled]=" !changed || inProgress"
|
||||
(click)="reset()" i18n>Reset
|
||||
</button>
|
||||
<br/>
|
||||
<hr/>
|
||||
</ng-container>
|
||||
|
||||
<div class="alert alert-secondary" role="alert">
|
||||
<ng-container i18n>If you add a new folder to your gallery, the site indexes it automatically.</ng-container>
|
||||
<ng-container i18n>If you would like to trigger indexing manually, click index button.</ng-container>
|
||||
<br/>
|
||||
(
|
||||
<ng-container i18n>Note: search only works among the indexed directories</ng-container>
|
||||
)
|
||||
</div>
|
||||
|
||||
|
||||
<app-settings-job-progress [progress]="Progress"></app-settings-job-progress>
|
||||
|
||||
<app-settings-job-button #indexingButton
|
||||
[soloRun]="true"
|
||||
(jobError)="error=$event"
|
||||
[allowParallelRun]="false"
|
||||
[jobName]="indexingJobName"
|
||||
[config]="Config"></app-settings-job-button>
|
||||
|
||||
|
||||
<app-settings-job-button class="ms-md-2 mt-2 mt-md-0"
|
||||
[danger]="true"
|
||||
[soloRun]="true"
|
||||
(jobError)="error=$event"
|
||||
[allowParallelRun]="false"
|
||||
[disabled]="indexingButton.Running"
|
||||
[jobName]="resetJobName"></app-settings-job-button>
|
||||
|
||||
|
||||
<hr/>
|
||||
<div class="row statics">
|
||||
<div class="col-md-2 col-12" i18n>
|
||||
Statistic:
|
||||
</div>
|
||||
<div class="col-md-2 col-6">
|
||||
<span class="oi oi-folder" title="Folders" i18n-title aria-hidden="true"> </span>
|
||||
{{settingsService.statistic.value ? settingsService.statistic.value.directories : '...'}}
|
||||
</div>
|
||||
<div class="col-md-2 col-6">
|
||||
<span class="oi oi-camera-slr" title="Photos" i18n-title aria-hidden="true"> </span>
|
||||
{{settingsService.statistic.value ? settingsService.statistic.value.photos : '...'}}
|
||||
</div>
|
||||
<div class="col-md-2 col-6">
|
||||
<span class="oi oi-video" title="Videos" i18n-title aria-hidden="true"> </span>
|
||||
{{settingsService.statistic.value ? settingsService.statistic.value.videos : '...'}}
|
||||
|
||||
</div>
|
||||
<div class="col-md-2 col-6">
|
||||
<span class="oi oi-people" title="Persons" i18n-title aria-hidden="true"> </span>
|
||||
{{settingsService.statistic.value ? settingsService.statistic.value.persons : '...'}}
|
||||
</div>
|
||||
<div class="col-md-2 col-6">
|
||||
<span class="oi oi-pie-chart" title="Size" i18n-title aria-hidden="true"> </span>
|
||||
{{settingsService.statistic.value ? (settingsService.statistic.value.diskUsage | fileSize) : '...'}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
@ -1,162 +0,0 @@
|
||||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||
import {IndexingSettingsService} from './indexing.settings.service';
|
||||
import {AuthenticationService} from '../../../model/network/authentication.service';
|
||||
import {NavigationService} from '../../../model/navigation.service';
|
||||
import {NotificationService} from '../../../model/notification.service';
|
||||
import {ErrorDTO} from '../../../../../common/entities/Error';
|
||||
import {SettingsComponentDirective} from '../_abstract/abstract.settings.component';
|
||||
import {Utils} from '../../../../../common/Utils';
|
||||
import {ScheduledJobsService} from '../scheduled-jobs.service';
|
||||
import {
|
||||
DefaultsJobs,
|
||||
JobDTOUtils,
|
||||
} from '../../../../../common/entities/job/JobDTO';
|
||||
import {
|
||||
JobProgressDTO,
|
||||
JobProgressStates,
|
||||
} from '../../../../../common/entities/job/JobProgressDTO';
|
||||
import {
|
||||
ReIndexingSensitivity,
|
||||
ServerIndexingConfig,
|
||||
} from '../../../../../common/config/private/PrivateConfig';
|
||||
import {SettingsService} from '../settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-indexing',
|
||||
templateUrl: './indexing.settings.component.html',
|
||||
styleUrls: [
|
||||
'./indexing.settings.component.css',
|
||||
'../_abstract/abstract.settings.component.css',
|
||||
],
|
||||
providers: [IndexingSettingsService],
|
||||
})
|
||||
export class IndexingSettingsComponent
|
||||
extends SettingsComponentDirective<
|
||||
ServerIndexingConfig,
|
||||
IndexingSettingsService
|
||||
>
|
||||
implements OnInit, OnDestroy {
|
||||
types: { key: number; value: string }[] = [];
|
||||
JobProgressStates = JobProgressStates;
|
||||
readonly indexingJobName = DefaultsJobs[DefaultsJobs.Indexing];
|
||||
readonly resetJobName = DefaultsJobs[DefaultsJobs['Database Reset']];
|
||||
|
||||
constructor(
|
||||
authService: AuthenticationService,
|
||||
navigation: NavigationService,
|
||||
settingsService: IndexingSettingsService,
|
||||
public jobsService: ScheduledJobsService,
|
||||
notification: NotificationService,
|
||||
globalSettingsService: SettingsService
|
||||
) {
|
||||
super(
|
||||
$localize`Folder indexing`,
|
||||
'pie-chart',
|
||||
authService,
|
||||
navigation,
|
||||
settingsService,
|
||||
notification,
|
||||
globalSettingsService,
|
||||
(s) => s.Indexing
|
||||
);
|
||||
}
|
||||
|
||||
get Config(): any {
|
||||
return {indexChangesOnly: true};
|
||||
}
|
||||
|
||||
get Progress(): JobProgressDTO {
|
||||
return this.jobsService.progress.value[
|
||||
JobDTOUtils.getHashName(DefaultsJobs[DefaultsJobs.Indexing], this.Config)
|
||||
];
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
super.ngOnDestroy();
|
||||
this.jobsService.unsubscribeFromProgress();
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
super.ngOnInit();
|
||||
this.jobsService.subscribeToProgress();
|
||||
this.types = Utils.enumToArray(ReIndexingSensitivity);
|
||||
this.types.forEach((v) => {
|
||||
switch (v.value) {
|
||||
case 'low':
|
||||
v.value = $localize`low`;
|
||||
break;
|
||||
case 'medium':
|
||||
v.value = $localize`medium`;
|
||||
break;
|
||||
case 'high':
|
||||
v.value = $localize`high`;
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async index(): Promise<boolean> {
|
||||
this.inProgress = true;
|
||||
this.error = '';
|
||||
try {
|
||||
await this.jobsService.start(
|
||||
DefaultsJobs[DefaultsJobs.Indexing],
|
||||
this.Config
|
||||
);
|
||||
this.notification.info($localize`Folder indexing started`);
|
||||
this.inProgress = false;
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
if (err.message) {
|
||||
this.error = (err as ErrorDTO).message;
|
||||
}
|
||||
}
|
||||
|
||||
this.inProgress = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
async cancelIndexing(): Promise<boolean> {
|
||||
this.inProgress = true;
|
||||
this.error = '';
|
||||
try {
|
||||
await this.jobsService.stop(DefaultsJobs[DefaultsJobs.Indexing]);
|
||||
this.notification.info($localize`Folder indexing interrupted`);
|
||||
this.inProgress = false;
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
if (err.message) {
|
||||
this.error = (err as ErrorDTO).message;
|
||||
}
|
||||
}
|
||||
|
||||
this.inProgress = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
async resetDatabase(): Promise<boolean> {
|
||||
this.inProgress = true;
|
||||
this.error = '';
|
||||
try {
|
||||
await this.jobsService.start(
|
||||
DefaultsJobs[DefaultsJobs['Database Reset']]
|
||||
);
|
||||
this.notification.info($localize`Resetting database`);
|
||||
this.inProgress = false;
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
if (err.message) {
|
||||
this.error = (err as ErrorDTO).message;
|
||||
}
|
||||
}
|
||||
|
||||
this.inProgress = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,59 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NetworkService } from '../../../model/network/network.service';
|
||||
import { SettingsService } from '../settings.service';
|
||||
import { AbstractSettingsService } from '../_abstract/abstract.settings.service';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { StatisticDTO } from '../../../../../common/entities/settings/StatisticDTO';
|
||||
import { ScheduledJobsService } from '../scheduled-jobs.service';
|
||||
import { DefaultsJobs } from '../../../../../common/entities/job/JobDTO';
|
||||
import { first } from 'rxjs/operators';
|
||||
import {
|
||||
DatabaseType,
|
||||
ServerIndexingConfig,
|
||||
} from '../../../../../common/config/private/PrivateConfig';
|
||||
|
||||
@Injectable()
|
||||
export class IndexingSettingsService extends AbstractSettingsService<ServerIndexingConfig> {
|
||||
public statistic: BehaviorSubject<StatisticDTO>;
|
||||
|
||||
constructor(
|
||||
private networkService: NetworkService,
|
||||
private jobsService: ScheduledJobsService,
|
||||
settingsService: SettingsService
|
||||
) {
|
||||
super(settingsService);
|
||||
this.statistic = new BehaviorSubject(null);
|
||||
settingsService.settings.pipe(first()).subscribe(() => {
|
||||
if (this.isSupported()) {
|
||||
this.loadStatistic();
|
||||
}
|
||||
});
|
||||
this.jobsService.onJobFinish.subscribe((jobName: string) => {
|
||||
if (
|
||||
jobName === DefaultsJobs[DefaultsJobs.Indexing] ||
|
||||
jobName === DefaultsJobs[DefaultsJobs['Database Reset']]
|
||||
) {
|
||||
if (this.isSupported()) {
|
||||
this.loadStatistic();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public updateSettings(settings: ServerIndexingConfig): Promise<void> {
|
||||
return this.networkService.putJson('/settings/indexing', { settings });
|
||||
}
|
||||
|
||||
public isSupported(): boolean {
|
||||
return (
|
||||
this.settingsService.settings.value.Database.type !==
|
||||
DatabaseType.memory
|
||||
);
|
||||
}
|
||||
|
||||
async loadStatistic(): Promise<void> {
|
||||
this.statistic.next(
|
||||
await this.networkService.getJson<StatisticDTO>('/admin/statistic')
|
||||
);
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
|
||||
.card {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.job-control-button {
|
||||
margin-top: -5px;
|
||||
margin-bottom: -5px;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.separator {
|
||||
width: 2px;
|
||||
}
|
@ -1,269 +0,0 @@
|
||||
<form #settingsForm="ngForm" class="form-horizontal">
|
||||
<div class="card mb-4">
|
||||
<h5 class="card-header">
|
||||
<span class="oi oi-{{icon}}"></span> {{Name}}
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||
|
||||
<div *ngFor="let schedule of sortedSchedules() as schedules; let i= index">
|
||||
<div class="card bg-light {{shouldIdent(schedule,schedules[i-1])? 'ms-4' : ''}}">
|
||||
<div class="card-header">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="clickable"
|
||||
(click)="showDetails[schedule.name]=!showDetails[schedule.name]">
|
||||
<span class="oi oi-chevron-{{showDetails[schedule.name] ? 'bottom' : 'right'}}"></span>
|
||||
{{schedule.name}} @<!--
|
||||
-->
|
||||
<ng-container [ngSwitch]="schedule.trigger.type">
|
||||
<ng-container *ngSwitchCase="JobTriggerType.periodic">
|
||||
<ng-container i18n>every</ng-container>
|
||||
{{periods[$any(schedule.trigger).periodicity]}} {{atTimeLocal($any(schedule.trigger).atTime) | date:"HH:mm (z)"}}
|
||||
</ng-container>
|
||||
<ng-container
|
||||
*ngSwitchCase="JobTriggerType.scheduled">{{$any(schedule.trigger).time | date:"medium"}}</ng-container>
|
||||
<ng-container *ngSwitchCase="JobTriggerType.never" i18n>never</ng-container>
|
||||
<ng-container *ngSwitchCase="JobTriggerType.after">
|
||||
<ng-container i18n>after</ng-container>
|
||||
: {{$any(schedule.trigger).afterScheduleName}}
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-danger job-control-button ms-0" (click)="remove(schedule)"><span
|
||||
class="oi oi-trash"></span>
|
||||
</button>
|
||||
<app-settings-job-button class="job-control-button ms-md-2 mt-2 mt-md-0"
|
||||
(jobError)="error=$event"
|
||||
[allowParallelRun]="schedule.allowParallelRun"
|
||||
[jobName]="schedule.jobName" [config]="schedule.config"
|
||||
[shortName]="true"></app-settings-job-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="card-body" [hidden]="!showDetails[schedule.name]">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-12">
|
||||
<div class="mb-1 row">
|
||||
<label class="col-md-2 control-label" [for]="'jobName'+i" i18n>Job:</label>
|
||||
<div class="col-md-4">
|
||||
{{backendTextService.getJobName(schedule.jobName)}}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<app-settings-job-button class="float-end"
|
||||
[jobName]="schedule.jobName"
|
||||
[allowParallelRun]="schedule.allowParallelRun"
|
||||
(jobError)="error=$event"
|
||||
[config]="schedule.config"></app-settings-job-button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-1 row">
|
||||
<label class="col-md-2 control-label" [for]="'repeatType'+i" i18n>Periodicity:</label>
|
||||
<div class="col-md-10">
|
||||
<select class="form-select" [(ngModel)]="schedule.trigger.type"
|
||||
(ngModelChange)="jobTriggerTypeChanged($event,schedule)"
|
||||
[name]="'repeatType'+i" required>
|
||||
<option *ngFor="let jobTrigger of JobTriggerTypeMap"
|
||||
[ngValue]="jobTrigger.key">{{jobTrigger.value}}
|
||||
</option>
|
||||
</select>
|
||||
<small class="form-text text-muted"
|
||||
i18n>Set the time to run the job.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 row" *ngIf="schedule.trigger.type == JobTriggerType.after">
|
||||
<label class="col-md-2 control-label" [for]="'triggerAfter'+i" i18n>After:</label>
|
||||
<div class="col-md-10">
|
||||
<select class="form-select" [(ngModel)]="schedule.trigger.afterScheduleName"
|
||||
[name]="'triggerAfter'+i" required>
|
||||
<ng-container *ngFor="let sch of states.scheduled.value">
|
||||
<option *ngIf="sch.name !== schedule.name"
|
||||
[ngValue]="sch.name">{{sch.name}}
|
||||
</option>
|
||||
</ng-container>
|
||||
</select>
|
||||
<small class="form-text text-muted"
|
||||
i18n>The job will run after that job finishes.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mb-3 row" *ngIf="schedule.trigger.type == JobTriggerType.scheduled">
|
||||
<label class="col-md-2 control-label" [for]="'triggerTime'+i" i18n>At:</label>
|
||||
<div class="col-md-10">
|
||||
<app-timestamp-datepicker
|
||||
[name]="'triggerTime'+i"
|
||||
(timestampChange)="onOptionChange()"
|
||||
[(timestamp)]="schedule.trigger.time"></app-timestamp-datepicker>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 row" *ngIf="schedule.trigger.type == JobTriggerType.periodic">
|
||||
<label class="col-md-2 control-label" [for]="'periodicity'+i" i18n>At:</label>
|
||||
<div class="col-md-10">
|
||||
<select
|
||||
class="form-select" [(ngModel)]="schedule.trigger.periodicity"
|
||||
[name]="'periodicity' + i"
|
||||
required>
|
||||
<option *ngFor="let period of periods; let i = index"
|
||||
[ngValue]="i">
|
||||
<ng-container i18n>every</ng-container>
|
||||
{{period}}
|
||||
</option>
|
||||
</select>
|
||||
<app-timestamp-timepicker
|
||||
[name]="'atTime'+i"
|
||||
(timestampChange)="onOptionChange()"
|
||||
[(timestamp)]="schedule.trigger.atTime"></app-timestamp-timepicker>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 row">
|
||||
<label class="col-md-2 control-label" [for]="'allowParallelRun'+i" i18n>Allow parallel run</label>
|
||||
<div class="col-md-10">
|
||||
<bSwitch
|
||||
class="switch"
|
||||
[name]="'allowParallelRun'+'_'+i"
|
||||
[id]="'allowParallelRun'+'_'+i"
|
||||
switch-on-color="primary"
|
||||
[switch-inverse]="true"
|
||||
switch-off-text="Disabled"
|
||||
switch-on-text="Enabled"
|
||||
i18n-switch-off-text i18n-switch-on-text
|
||||
[switch-handle-width]="100"
|
||||
[switch-label-width]="20"
|
||||
[(ngModel)]="schedule.allowParallelRun">
|
||||
</bSwitch>
|
||||
<small class="form-text text-muted"
|
||||
i18n>Enables the job to start even if another job is already running.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="getConfigTemplate(schedule.jobName) ">
|
||||
<hr/>
|
||||
<div *ngFor="let configEntry of getConfigTemplate(schedule.jobName)">
|
||||
|
||||
<div class="mb-3 row">
|
||||
<label class="col-md-2 control-label"
|
||||
[for]="configEntry.id+'_'+i">{{backendTextService.get(configEntry.name)}}:</label>
|
||||
<div class="col-md-10">
|
||||
<ng-container [ngSwitch]="configEntry.type">
|
||||
<ng-container *ngSwitchCase="'boolean'">
|
||||
<bSwitch
|
||||
class="switch"
|
||||
[name]="configEntry.id+'_'+i"
|
||||
[id]="configEntry.id+'_'+i"
|
||||
switch-on-color="primary"
|
||||
[switch-inverse]="true"
|
||||
switch-off-text="Disabled"
|
||||
switch-on-text="Enabled"
|
||||
i18n-switch-off-text i18n-switch-on-text
|
||||
[switch-handle-width]="100"
|
||||
[switch-label-width]="20"
|
||||
[(ngModel)]="schedule.config[configEntry.id]">
|
||||
</bSwitch>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="'string'">
|
||||
<input type="text" class="form-control" [name]="configEntry.id+'_'+i"
|
||||
[id]="configEntry.id+'_'+i"
|
||||
[(ngModel)]="schedule.config[configEntry.id]" required>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="'number'">
|
||||
<input type="number" class="form-control" [name]="configEntry.id+'_'+i"
|
||||
[id]="configEntry.id+'_'+i"
|
||||
[(ngModel)]="schedule.config[configEntry.id]" required>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="'number-array'">
|
||||
<input type="text" class="form-control"
|
||||
[name]="configEntry.id+'_'+i"
|
||||
[id]="configEntry.id+'_'+i"
|
||||
(ngModelChange)="setNumberArray(schedule.config,configEntry.id,$event)"
|
||||
[ngModel]="getNumberArray(schedule.config,configEntry.id)" required>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<small class="form-text text-muted">
|
||||
<ng-container *ngIf="configEntry.type == 'number-array'" i18n>';' separated integers.
|
||||
</ng-container>
|
||||
{{backendTextService.get(configEntry.description)}}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<app-settings-job-progress
|
||||
class="card-footer bg-transparent"
|
||||
*ngIf="getProgress(schedule)"
|
||||
[progress]="getProgress(schedule)">
|
||||
</app-settings-job-progress>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-success float-end"
|
||||
[disabled]="!settingsForm.form.valid || !changed || inProgress"
|
||||
(click)="save()" i18n>Save
|
||||
</button>
|
||||
<button class="btn btn-secondary float-end"
|
||||
[disabled]=" !changed || inProgress"
|
||||
(click)="reset()" i18n>Reset
|
||||
</button>
|
||||
<button class="btn btn-primary float-end"
|
||||
(click)="prepareNewJob()" i18n>+ Add Job
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
<!-- Modal -->
|
||||
<div bsModal #jobModal="bs-modal" class="modal fade" id="jobModal" tabindex="-1" role="dialog"
|
||||
aria-labelledby="jobModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="jobModalLabel" i18n>Add new job</h5>
|
||||
<button type="button" class="btn-close" (click)="jobModal.hide()" data-dismiss="modal" aria-label="Close">
|
||||
</button>
|
||||
</div>
|
||||
<form #jobModalForm="ngForm">
|
||||
<div class="modal-body">
|
||||
<select class="form-select" (change)="jobTypeChanged(newSchedule)" [(ngModel)]="newSchedule.jobName"
|
||||
name="newJobName" required>
|
||||
<option *ngFor="let availableJob of settingsService.availableJobs | async"
|
||||
[ngValue]="availableJob.Name">{{backendTextService.getJobName(availableJob.Name)}}
|
||||
</option>
|
||||
</select>
|
||||
<small class="form-text text-muted"
|
||||
i18n>Select a job to schedule.
|
||||
</small>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" (click)="jobModal.hide()" i18n>Close</button>
|
||||
<button type="button" class="btn btn-primary" data-dismiss="modal"
|
||||
(click)="addNewJob()"
|
||||
[disabled]="!jobModalForm.form.valid" i18n>Add Job
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,262 +0,0 @@
|
||||
import {Component, OnChanges, OnDestroy, OnInit, ViewChild,} from '@angular/core';
|
||||
import {JobsSettingsService} from './jobs.settings.service';
|
||||
import {AuthenticationService} from '../../../model/network/authentication.service';
|
||||
import {NavigationService} from '../../../model/navigation.service';
|
||||
import {NotificationService} from '../../../model/notification.service';
|
||||
import {SettingsComponentDirective} from '../_abstract/abstract.settings.component';
|
||||
import {ScheduledJobsService} from '../scheduled-jobs.service';
|
||||
import {
|
||||
AfterJobTrigger,
|
||||
JobScheduleDTO,
|
||||
JobScheduleDTOUtils,
|
||||
JobTriggerType,
|
||||
NeverJobTrigger,
|
||||
PeriodicJobTrigger,
|
||||
ScheduledJobTrigger,
|
||||
} from '../../../../../common/entities/job/JobScheduleDTO';
|
||||
import {ConfigTemplateEntry} from '../../../../../common/entities/job/JobDTO';
|
||||
import {ModalDirective} from 'ngx-bootstrap/modal';
|
||||
import {JobProgressDTO, JobProgressStates,} from '../../../../../common/entities/job/JobProgressDTO';
|
||||
import {BackendtextService} from '../../../model/backendtext.service';
|
||||
import {ServerJobConfig} from '../../../../../common/config/private/PrivateConfig';
|
||||
import {ConfigPriority} from '../../../../../common/config/public/ClientConfig';
|
||||
import {SettingsService} from '../settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-jobs',
|
||||
templateUrl: './jobs.settings.component.html',
|
||||
styleUrls: [
|
||||
'./jobs.settings.component.css',
|
||||
'../_abstract/abstract.settings.component.css',
|
||||
],
|
||||
providers: [JobsSettingsService],
|
||||
})
|
||||
export class JobsSettingsComponent
|
||||
extends SettingsComponentDirective<ServerJobConfig, JobsSettingsService>
|
||||
implements OnInit, OnDestroy, OnChanges {
|
||||
@ViewChild('jobModal', {static: false}) public jobModal: ModalDirective;
|
||||
disableButtons = false;
|
||||
JobTriggerTypeMap: { key: number; value: string }[];
|
||||
JobTriggerType = JobTriggerType;
|
||||
periods: string[] = [];
|
||||
showDetails: { [key: string]: boolean } = {};
|
||||
JobProgressStates = JobProgressStates;
|
||||
newSchedule: JobScheduleDTO = {
|
||||
name: '',
|
||||
config: null,
|
||||
jobName: '',
|
||||
trigger: {
|
||||
type: JobTriggerType.never,
|
||||
},
|
||||
allowParallelRun: false,
|
||||
};
|
||||
|
||||
constructor(
|
||||
authService: AuthenticationService,
|
||||
navigation: NavigationService,
|
||||
settingsService: JobsSettingsService,
|
||||
public jobsService: ScheduledJobsService,
|
||||
public backendTextService: BackendtextService,
|
||||
notification: NotificationService,
|
||||
globalSettingsService: SettingsService
|
||||
) {
|
||||
super(
|
||||
$localize`Jobs`,
|
||||
'project',
|
||||
authService,
|
||||
navigation,
|
||||
settingsService,
|
||||
notification,
|
||||
globalSettingsService,
|
||||
(s) => s.Jobs
|
||||
);
|
||||
|
||||
this.hasAvailableSettings = this.configPriority > ConfigPriority.basic;
|
||||
this.JobTriggerTypeMap = [
|
||||
{key: JobTriggerType.after, value: $localize`after`},
|
||||
{key: JobTriggerType.never, value: $localize`never`},
|
||||
{key: JobTriggerType.periodic, value: $localize`periodic`},
|
||||
{key: JobTriggerType.scheduled, value: $localize`scheduled`},
|
||||
];
|
||||
this.periods = [
|
||||
$localize`Monday`, // 0
|
||||
$localize`Tuesday`, // 1
|
||||
$localize`Wednesday`, // 2
|
||||
$localize`Thursday`,
|
||||
$localize`Friday`,
|
||||
$localize`Saturday`,
|
||||
$localize`Sunday`,
|
||||
$localize`day`,
|
||||
]; // 7
|
||||
}
|
||||
|
||||
atTimeLocal(atTime: number): Date {
|
||||
const d = new Date();
|
||||
d.setUTCHours(Math.floor(atTime / 60));
|
||||
d.setUTCMinutes(Math.floor(atTime % 60));
|
||||
return d;
|
||||
}
|
||||
|
||||
getConfigTemplate(JobName: string): ConfigTemplateEntry[] {
|
||||
const job = this.settingsService.availableJobs.value.find(
|
||||
(t) => t.Name === JobName
|
||||
);
|
||||
if (job && job.ConfigTemplate && job.ConfigTemplate.length > 0) {
|
||||
return job.ConfigTemplate;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit();
|
||||
this.jobsService.subscribeToProgress();
|
||||
this.settingsService.getAvailableJobs().catch(console.error);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
super.ngOnDestroy();
|
||||
this.jobsService.unsubscribeFromProgress();
|
||||
}
|
||||
|
||||
remove(schedule: JobScheduleDTO): void {
|
||||
this.states.scheduled.value.splice(
|
||||
this.states.scheduled.value.indexOf(schedule),
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
jobTypeChanged(schedule: JobScheduleDTO): void {
|
||||
const job = this.settingsService.availableJobs.value.find(
|
||||
(t) => t.Name === schedule.jobName
|
||||
);
|
||||
schedule.config = schedule.config || {};
|
||||
if (job.ConfigTemplate) {
|
||||
job.ConfigTemplate.forEach(
|
||||
(ct) => (schedule.config[ct.id] = ct.defaultValue)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
prepareNewJob(): void {
|
||||
const jobName = this.settingsService.availableJobs.value[0].Name;
|
||||
this.newSchedule = {
|
||||
name: 'new job',
|
||||
jobName,
|
||||
config: {},
|
||||
trigger: {
|
||||
type: JobTriggerType.never,
|
||||
},
|
||||
allowParallelRun: false,
|
||||
};
|
||||
|
||||
const job = this.settingsService.availableJobs.value.find(
|
||||
(t) => t.Name === jobName
|
||||
);
|
||||
this.newSchedule.config = this.newSchedule.config || {};
|
||||
if (job.ConfigTemplate) {
|
||||
job.ConfigTemplate.forEach(
|
||||
(ct) => (this.newSchedule.config[ct.id] = ct.defaultValue)
|
||||
);
|
||||
}
|
||||
|
||||
this.jobModal.show();
|
||||
}
|
||||
|
||||
jobTriggerTypeChanged(
|
||||
triggerType: JobTriggerType,
|
||||
schedule: JobScheduleDTO
|
||||
): void {
|
||||
schedule.trigger = {type: triggerType} as NeverJobTrigger;
|
||||
switch (triggerType) {
|
||||
case JobTriggerType.scheduled:
|
||||
(schedule.trigger as unknown as ScheduledJobTrigger).time = Date.now();
|
||||
break;
|
||||
|
||||
case JobTriggerType.periodic:
|
||||
(schedule.trigger as unknown as PeriodicJobTrigger).periodicity = null;
|
||||
(schedule.trigger as unknown as PeriodicJobTrigger).atTime = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setNumberArray(configElement: any, id: string, value: string): void {
|
||||
value = value.replace(new RegExp(',', 'g'), ';');
|
||||
value = value.replace(new RegExp(' ', 'g'), ';');
|
||||
configElement[id] = value
|
||||
.split(';')
|
||||
.map((s: string) => parseInt(s, 10))
|
||||
.filter((i: number) => !isNaN(i) && i > 0);
|
||||
}
|
||||
|
||||
getNumberArray(configElement: any, id: string): string {
|
||||
return configElement[id].join('; ');
|
||||
}
|
||||
|
||||
public shouldIdent(curr: JobScheduleDTO, prev: JobScheduleDTO): boolean {
|
||||
return (
|
||||
curr &&
|
||||
curr.trigger.type === JobTriggerType.after &&
|
||||
prev &&
|
||||
prev.name === curr.trigger.afterScheduleName
|
||||
);
|
||||
}
|
||||
|
||||
public sortedSchedules(): JobScheduleDTO[] {
|
||||
return (this.states.scheduled.value as JobScheduleDTO[])
|
||||
.slice()
|
||||
.sort((a: JobScheduleDTO, b: JobScheduleDTO) => {
|
||||
return (
|
||||
this.getNextRunningDate(a, this.states.scheduled.value) -
|
||||
this.getNextRunningDate(b, this.states.scheduled.value)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
addNewJob(): void {
|
||||
const jobName = this.newSchedule.jobName;
|
||||
const count = this.states.scheduled.value.filter(
|
||||
(s: JobScheduleDTO) => s.jobName === jobName
|
||||
).length;
|
||||
this.newSchedule.name =
|
||||
count === 0
|
||||
? jobName
|
||||
: this.backendTextService.getJobName(jobName) + ' ' + (count + 1);
|
||||
this.states.scheduled.value.push(this.newSchedule);
|
||||
this.jobModal.hide();
|
||||
}
|
||||
|
||||
getProgress(schedule: JobScheduleDTO): JobProgressDTO {
|
||||
return this.jobsService.getProgress(schedule);
|
||||
}
|
||||
|
||||
private getNextRunningDate(
|
||||
sch: JobScheduleDTO,
|
||||
list: JobScheduleDTO[],
|
||||
depth = 0
|
||||
): number {
|
||||
if (depth > list.length) {
|
||||
return 0;
|
||||
}
|
||||
if (sch.trigger.type === JobTriggerType.never) {
|
||||
return (
|
||||
list
|
||||
.map((s) => s.name)
|
||||
.sort()
|
||||
.indexOf(sch.name) * -1
|
||||
);
|
||||
}
|
||||
if (sch.trigger.type === JobTriggerType.after) {
|
||||
const parent = list.find(
|
||||
(s) => s.name === (sch.trigger as AfterJobTrigger).afterScheduleName
|
||||
);
|
||||
if (parent) {
|
||||
return this.getNextRunningDate(parent, list, depth + 1) + 0.001;
|
||||
}
|
||||
}
|
||||
const d = JobScheduleDTOUtils.getNextRunningDate(new Date(), sch);
|
||||
return d !== null ? d.getTime() : 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,38 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NetworkService } from '../../../model/network/network.service';
|
||||
import { SettingsService } from '../settings.service';
|
||||
import { AbstractSettingsService } from '../_abstract/abstract.settings.service';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { JobDTO } from '../../../../../common/entities/job/JobDTO';
|
||||
import { ServerJobConfig } from '../../../../../common/config/private/PrivateConfig';
|
||||
|
||||
@Injectable()
|
||||
export class JobsSettingsService extends AbstractSettingsService<ServerJobConfig> {
|
||||
public availableJobs: BehaviorSubject<JobDTO[]>;
|
||||
|
||||
constructor(
|
||||
private networkService: NetworkService,
|
||||
settingsService: SettingsService
|
||||
) {
|
||||
super(settingsService);
|
||||
this.availableJobs = new BehaviorSubject([]);
|
||||
}
|
||||
|
||||
public updateSettings(settings: ServerJobConfig): Promise<void> {
|
||||
return this.networkService.putJson('/settings/jobs', { settings });
|
||||
}
|
||||
|
||||
hasAvailableSettings(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public isSupported(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public async getAvailableJobs(): Promise<void> {
|
||||
this.availableJobs.next(
|
||||
await this.networkService.getJson<JobDTO[]>('/admin/jobs/available')
|
||||
);
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
.custom-layer-info {
|
||||
margin-top: -2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.custom-layer-container {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
@ -1,109 +0,0 @@
|
||||
<form #settingsForm="ngForm" class="form-horizontal">
|
||||
<div class="card mb-4">
|
||||
<h5 class="card-header">
|
||||
<span class="oi oi-{{icon}}"></span> {{Name}}
|
||||
<div class="switch-wrapper">
|
||||
<bSwitch
|
||||
class="switch"
|
||||
name="enabled"
|
||||
switch-on-color="success"
|
||||
[switch-inverse]="true"
|
||||
switch-off-text="Disabled"
|
||||
switch-on-text="Enabled"
|
||||
i18n-switch-off-text
|
||||
i18n-switch-on-text
|
||||
[switch-disabled]="inProgress"
|
||||
[switch-handle-width]="100"
|
||||
[switch-label-width]="20"
|
||||
[(ngModel)]="states.enabled.value">
|
||||
</bSwitch>
|
||||
</div>
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||
<ng-container *ngIf="states.enabled.value">
|
||||
|
||||
|
||||
<app-settings-entry
|
||||
name="Use image markers"
|
||||
description="Map will use thumbnail images as markers instead of the default pin."
|
||||
i18n-name i18n-description=""
|
||||
[ngModel]="states.useImageMarkers">
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
<app-settings-entry
|
||||
name="Map provider"
|
||||
i18n-name
|
||||
[ngModel]="states.mapProvider"
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
<div class="container custom-layer-container" *ngIf="states.mapProvider.value === MapProviders.Custom">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th i18n>Name</th>
|
||||
<th i18n>Tile Url*</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr *ngFor="let layer of states.customLayers.value; let i = index">
|
||||
<td><input type="text" class="form-control" placeholder="Street"
|
||||
[(ngModel)]="layer.name"
|
||||
[name]="'tileName-'+i" [id]="'tileName-'+i" required></td>
|
||||
<td>
|
||||
<input type="text" class="form-control" placeholder="http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
[(ngModel)]="layer.url"
|
||||
[name]="'tileUrl-'+i" [id]="'tileUrl-'+i" required>
|
||||
</td>
|
||||
<td>
|
||||
<button [disabled]="states.customLayers.value.length == 1" (click)="removeLayer(layer)"
|
||||
[ngClass]="states.customLayers.value.length > 1? 'btn-danger':'btn-secondary'"
|
||||
class="btn float-end">
|
||||
<span class="oi oi-trash" aria-hidden="true" aria-label="Delete"></span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="row justify-content-end">
|
||||
<small class="form-text text-muted custom-layer-info">
|
||||
<ng-container i18n>*The map module will use these urls to fetch the map tiles.</ng-container>
|
||||
</small>
|
||||
</div>
|
||||
<div class="row justify-content-end">
|
||||
<button class="btn btn-primary"
|
||||
(click)="addNewLayer()" i18n>+ Add Layer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-settings-entry
|
||||
*ngIf="states.mapProvider.value === MapProviders.Mapbox"
|
||||
name="Mapbox access token"
|
||||
i18n-name
|
||||
placeholder="Mapbox access token"
|
||||
[ngModel]="states.mapboxAccessToken"
|
||||
[required]="true">
|
||||
<small class="form-text text-muted">
|
||||
<ng-container i18n>MapBox needs an access token to work, create one at</ng-container>
|
||||
<a href="https://www.mapbox.com">https://www.mapbox.com</a>.
|
||||
</small>
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
</ng-container>
|
||||
<button class="btn btn-success float-end"
|
||||
[disabled]="!settingsForm.form.valid || !changed || inProgress"
|
||||
(click)="save()" i18n>Save
|
||||
</button>
|
||||
<button class="btn btn-secondary float-end"
|
||||
[disabled]=" !changed || inProgress"
|
||||
(click)="reset()" i18n>Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
@ -1,65 +0,0 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {MapSettingsService} from './map.settings.service';
|
||||
import {SettingsComponentDirective} from '../_abstract/abstract.settings.component';
|
||||
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 {
|
||||
ClientMapConfig,
|
||||
MapLayers,
|
||||
MapProviders,
|
||||
} from '../../../../../common/config/public/ClientConfig';
|
||||
import {SettingsService} from '../settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-map',
|
||||
templateUrl: './map.settings.component.html',
|
||||
styleUrls: [
|
||||
'./map.settings.component.css',
|
||||
'../_abstract/abstract.settings.component.css',
|
||||
],
|
||||
providers: [MapSettingsService],
|
||||
})
|
||||
export class MapSettingsComponent extends SettingsComponentDirective<ClientMapConfig> {
|
||||
public mapProviders: { key: number; value: string }[] = [];
|
||||
public MapProviders = MapProviders;
|
||||
|
||||
constructor(
|
||||
authService: AuthenticationService,
|
||||
navigation: NavigationService,
|
||||
settingsService: MapSettingsService,
|
||||
notification: NotificationService,
|
||||
globalSettingsService: SettingsService
|
||||
) {
|
||||
super(
|
||||
$localize`Map`,
|
||||
'map-marker',
|
||||
authService,
|
||||
navigation,
|
||||
settingsService,
|
||||
notification,
|
||||
globalSettingsService,
|
||||
(s) => s.Map
|
||||
);
|
||||
|
||||
this.mapProviders = Utils.enumToArray(MapProviders);
|
||||
}
|
||||
|
||||
addNewLayer(): void {
|
||||
this.states.customLayers.value.push({
|
||||
name: 'Layer-' + this.states.customLayers.value.length,
|
||||
url: '',
|
||||
});
|
||||
}
|
||||
|
||||
removeLayer(layer: MapLayers): void {
|
||||
this.states.customLayers.value.splice(
|
||||
this.states.customLayers.value.indexOf(layer),
|
||||
1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,23 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NetworkService } from '../../../model/network/network.service';
|
||||
import { SettingsService } from '../settings.service';
|
||||
import { AbstractSettingsService } from '../_abstract/abstract.settings.service';
|
||||
import { ClientMapConfig } from '../../../../../common/config/public/ClientConfig';
|
||||
|
||||
@Injectable()
|
||||
export class MapSettingsService extends AbstractSettingsService<ClientMapConfig> {
|
||||
constructor(
|
||||
private networkService: NetworkService,
|
||||
settingsService: SettingsService
|
||||
) {
|
||||
super(settingsService);
|
||||
}
|
||||
|
||||
hasAvailableSettings(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public updateSettings(settings: ClientMapConfig): Promise<void> {
|
||||
return this.networkService.putJson('/settings/map', { settings });
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
<form #settingsForm="ngForm" class="form-horizontal">
|
||||
<div class="card mb-4">
|
||||
<h5 class="card-header">
|
||||
<span class="oi oi-{{icon}}"></span> {{Name}}
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||
|
||||
|
||||
<app-settings-entry
|
||||
name="Markdown files"
|
||||
description="Reads *.md files in a directory and shows the next to the map."
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.markdown">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="*.pg2conf files"
|
||||
description="Reads *.pg2conf files (You can use it for custom sorting and save search (albums))."
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.pg2conf">
|
||||
</app-settings-entry>
|
||||
|
||||
<hr/>
|
||||
|
||||
<app-settings-entry
|
||||
name="*.gpx files"
|
||||
description="Reads *.gpx files and renders them on the map."
|
||||
i18n-description i18n-name
|
||||
[disabled]="!(settingsService.Settings | async).Map.enabled"
|
||||
[ngModel]="states.gpx">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="*.gpx compression"
|
||||
description="Enables *.gpx file compression."
|
||||
link="https://github.com/bpatrik/pigallery2/issues/504"
|
||||
linkText="See 504."
|
||||
i18n-description i18n-name
|
||||
[disabled]="!(settingsService.Settings | async).Map.enabled || !states.gpx.value"
|
||||
[ngModel]="states.GPXCompressing.enabled">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="OnTheFly *.gpx compression"
|
||||
description="Enables on the fly *.gpx compression."
|
||||
i18n-description i18n-name
|
||||
[disabled]="!(settingsService.Settings | async).Map.enabled || !states.GPXCompressing.enabled.value || !states.gpx.value"
|
||||
[ngModel]="states.server.GPXCompressing.onTheFly">
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
|
||||
<button class="btn btn-success float-end"
|
||||
[disabled]="!settingsForm.form.valid || !changed || inProgress"
|
||||
(click)="save()" i18n>Save
|
||||
</button>
|
||||
<button class="btn btn-secondary float-end"
|
||||
[disabled]=" !changed || inProgress"
|
||||
(click)="reset()" i18n>Reset
|
||||
</button>
|
||||
|
||||
<div [hidden]="!states.GPXCompressing.enabled.value || !states.gpx.value">
|
||||
<app-settings-job-button class="mt-2 mt-md-0 float-left"
|
||||
[soloRun]="true"
|
||||
(jobError)="error=$event"
|
||||
[allowParallelRun]="false"
|
||||
[jobName]="jobName"></app-settings-job-button>
|
||||
|
||||
<ng-container *ngIf="Progress != null">
|
||||
<br/>
|
||||
<hr/>
|
||||
<app-settings-job-progress [progress]="Progress"></app-settings-job-progress>
|
||||
</ng-container>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
@ -1,56 +0,0 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {MetaFileSettingsService} from './metafile.settings.service';
|
||||
import {SettingsComponentDirective} from '../_abstract/abstract.settings.component';
|
||||
import {AuthenticationService} from '../../../model/network/authentication.service';
|
||||
import {NavigationService} from '../../../model/navigation.service';
|
||||
import {NotificationService} from '../../../model/notification.service';
|
||||
import {ClientMetaFileConfig, ClientPhotoConfig} from '../../../../../common/config/public/ClientConfig';
|
||||
import {ServerMetaFileConfig, ServerPhotoConfig} from '../../../../../common/config/private/PrivateConfig';
|
||||
import {DefaultsJobs, JobDTOUtils} from '../../../../../common/entities/job/JobDTO';
|
||||
import {JobProgressDTO, JobProgressStates} from '../../../../../common/entities/job/JobProgressDTO';
|
||||
import {ScheduledJobsService} from '../scheduled-jobs.service';
|
||||
import {SettingsService} from '../settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-meta-file',
|
||||
templateUrl: './metafile.settings.component.html',
|
||||
styleUrls: [
|
||||
'./metafile.settings.component.css',
|
||||
'../_abstract/abstract.settings.component.css',
|
||||
],
|
||||
providers: [MetaFileSettingsService],
|
||||
})
|
||||
export class MetaFileSettingsComponent extends SettingsComponentDirective<ServerMetaFileConfig> {
|
||||
constructor(
|
||||
authService: AuthenticationService,
|
||||
navigation: NavigationService,
|
||||
settingsService: MetaFileSettingsService,
|
||||
notification: NotificationService,
|
||||
public jobsService: ScheduledJobsService,
|
||||
globalSettingsService: SettingsService
|
||||
) {
|
||||
super(
|
||||
$localize`Meta file`,
|
||||
'file',
|
||||
authService,
|
||||
navigation,
|
||||
settingsService,
|
||||
notification,
|
||||
globalSettingsService,
|
||||
(s) => (s.MetaFile)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
readonly jobName = DefaultsJobs[DefaultsJobs['GPX Compression']];
|
||||
|
||||
|
||||
get Progress(): JobProgressDTO {
|
||||
return this.jobsService.progress.value[
|
||||
JobDTOUtils.getHashName(DefaultsJobs[DefaultsJobs['GPX Compression']])
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,28 +0,0 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {NetworkService} from '../../../model/network/network.service';
|
||||
import {SettingsService} from '../settings.service';
|
||||
import {AbstractSettingsService} from '../_abstract/abstract.settings.service';
|
||||
import {ClientMetaFileConfig} from '../../../../../common/config/public/ClientConfig';
|
||||
import {ServerMetaFileConfig} from '../../../../../common/config/private/PrivateConfig';
|
||||
|
||||
@Injectable()
|
||||
export class MetaFileSettingsService extends AbstractSettingsService<ServerMetaFileConfig> {
|
||||
constructor(
|
||||
private networkService: NetworkService,
|
||||
settingsService: SettingsService
|
||||
) {
|
||||
super(settingsService);
|
||||
}
|
||||
|
||||
public isSupported(): boolean {
|
||||
return this.settingsService.settings.value.Map.enabled === true;
|
||||
}
|
||||
|
||||
hasAvailableSettings(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public updateSettings(settings: ServerMetaFileConfig): Promise<void> {
|
||||
return this.networkService.putJson('/settings/metafile', {settings});
|
||||
}
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
<form #settingsForm="ngForm" class="form-horizontal">
|
||||
<div class="card mb-4">
|
||||
<h5 class="card-header">
|
||||
<span class="oi oi-{{icon}}"></span> {{Name}}
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong i18n>Error: </strong>{{error}}</div>
|
||||
|
||||
|
||||
<p class="title" i18n>Threads:</p>
|
||||
|
||||
|
||||
<app-settings-entry
|
||||
name="Threading"
|
||||
description="Runs directory scanning in a different thread."
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.Server.enabled">
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
<app-settings-entry
|
||||
name="Thumbnail threads"
|
||||
description="Number of threads that are used to generate thumbnails. If auto, number of CPU cores -1 threads will be used."
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.Server.thumbnailThreads"
|
||||
[options]="threads"
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
<hr/>
|
||||
<p class="title" i18n>Misc:</p>
|
||||
|
||||
<app-settings-entry
|
||||
name="Scroll based thumbnail generation"
|
||||
description="Those thumbnails get higher priority that are visible on the screen."
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.Client.enableOnScrollThumbnailPrioritising">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="Lazy image rendering"
|
||||
description="Shows only the required amount of photos at once. Renders more if page bottom is reached."
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.Client.enableOnScrollRendering">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="Cache"
|
||||
description="Caches directory contents and search results for better performance."
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.Client.enableCache">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="Caption first naming"
|
||||
description="Show the caption (IPTC 120) tags from the EXIF data instead of the filenames."
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.Client.captionFirstNaming">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="Download Zip"
|
||||
description="[Experimental: does not work for searches] Enable download zip of a directory contents"
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.Client.enableDownloadZip">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="Directory flattening"
|
||||
description="[Experimental: won't work if the gallery multiple folders with the same path] Adds a button to flattens the file structure, by listing the content of all subdirectories."
|
||||
link="https://github.com/bpatrik/pigallery2/issues/174"
|
||||
linkText="See 174."
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.Client.enableDirectoryFlattening">
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
<hr/>
|
||||
<p class="title" i18n>Navigation bar:</p>
|
||||
|
||||
<app-settings-entry
|
||||
name="Show item count"
|
||||
description="Show the number of items (photos) in the folder."
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.Client.NavBar.showItemCount"
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
<app-settings-entry
|
||||
name="Default photo sorting method"
|
||||
i18n-name
|
||||
[ngModel]="states.Client.defaultPhotoSortingMethod"
|
||||
[optionMap]="sortingMap"
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="Default photo sorting method for search results"
|
||||
i18n-name
|
||||
[ngModel]="states.Client.defaultSearchSortingMethod"
|
||||
link="https://github.com/bpatrik/pigallery2/issues/566"
|
||||
linkText="See 566."
|
||||
[optionMap]="sortingMap"
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="Sort directories by date"
|
||||
description="If enabled, directories will be sorted by date, like photos, otherwise by name. Directory date is the last modification time of that directory not the creation date of the oldest photo."
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.Client.enableDirectorySortingByDate"
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="Custom HTML Head"
|
||||
description="Injects the content of this between the <head></head> HTML tags of the app. (You can use it add analytics or custom code to the app)"
|
||||
[ngModel]="states.Client.customHTMLHead"
|
||||
i18n-name>
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
<button class="btn btn-success float-end"
|
||||
[disabled]="!settingsForm.form.valid || !changed || inProgress"
|
||||
(click)="save()" i18n>Save
|
||||
</button>
|
||||
<button class="btn btn-secondary float-end"
|
||||
[disabled]=" !changed || inProgress"
|
||||
(click)="reset()" i18n>Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</form>
|
@ -1,76 +0,0 @@
|
||||
import {Component, OnChanges} from '@angular/core';
|
||||
import {SettingsComponentDirective} from '../_abstract/abstract.settings.component';
|
||||
import {AuthenticationService} from '../../../model/network/authentication.service';
|
||||
import {NavigationService} from '../../../model/navigation.service';
|
||||
import {NotificationService} from '../../../model/notification.service';
|
||||
import {OtherSettingsService} from './other.settings.service';
|
||||
import {Utils} from '../../../../../common/Utils';
|
||||
import {SortingMethods} from '../../../../../common/entities/SortingMethods';
|
||||
import {StringifySortingMethod} from '../../../pipes/StringifySortingMethod';
|
||||
import {ConfigPriority} from '../../../../../common/config/public/ClientConfig';
|
||||
import {SettingsService} from '../settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-other',
|
||||
templateUrl: './other.settings.component.html',
|
||||
styleUrls: [
|
||||
'./other.settings.component.css',
|
||||
'../_abstract/abstract.settings.component.css',
|
||||
],
|
||||
providers: [OtherSettingsService],
|
||||
})
|
||||
export class OtherSettingsComponent
|
||||
extends SettingsComponentDirective<any>
|
||||
implements OnChanges {
|
||||
types: { key: number; value: string }[] = [];
|
||||
threads: { key: number; value: string }[] = [
|
||||
{key: 0, value: 'auto'},
|
||||
].concat(Utils.createRange(1, 100).map((v) => ({key: v, value: '' + v})));
|
||||
sortingMap: any;
|
||||
|
||||
constructor(
|
||||
authService: AuthenticationService,
|
||||
navigation: NavigationService,
|
||||
settingsService: OtherSettingsService,
|
||||
notification: NotificationService,
|
||||
private formatter: StringifySortingMethod,
|
||||
globalSettingsService: SettingsService
|
||||
) {
|
||||
super(
|
||||
$localize`Other`,
|
||||
'target',
|
||||
authService,
|
||||
navigation,
|
||||
settingsService,
|
||||
notification,
|
||||
globalSettingsService,
|
||||
(s) => ({
|
||||
Server: s.Gallery,
|
||||
})
|
||||
);
|
||||
this.sortingMap = (v: { key: number; value: string }) => {
|
||||
v.value = this.formatter.transform(v.key);
|
||||
return v;
|
||||
};
|
||||
this.types = Utils.enumToArray(SortingMethods);
|
||||
this.hasAvailableSettings = this.configPriority > ConfigPriority.basic;
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.hasAvailableSettings = this.configPriority > ConfigPriority.basic;
|
||||
}
|
||||
|
||||
public async save(): Promise<boolean> {
|
||||
const val = await super.save();
|
||||
if (val === true) {
|
||||
this.notification.info(
|
||||
$localize`Restart the server to apply the new settings`,
|
||||
$localize`Info`
|
||||
);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,18 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NetworkService } from '../../../model/network/network.service';
|
||||
import { AbstractSettingsService } from '../_abstract/abstract.settings.service';
|
||||
import { SettingsService } from '../settings.service';
|
||||
|
||||
@Injectable()
|
||||
export class OtherSettingsService extends AbstractSettingsService<any> {
|
||||
constructor(
|
||||
private networkService: NetworkService,
|
||||
settingsService: SettingsService
|
||||
) {
|
||||
super(settingsService);
|
||||
}
|
||||
|
||||
public updateSettings(settings: any): Promise<void> {
|
||||
return this.networkService.putJson('/settings/other', { settings });
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
.buttons-row {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
<form #settingsForm="ngForm" class="form-horizontal">
|
||||
<div class="card mb-4">
|
||||
<h5 class="card-header">
|
||||
<span class="oi oi-{{icon}}"></span> {{Name}}
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||
|
||||
<p class="title" i18n>Photo converting:</p>
|
||||
|
||||
<app-settings-entry
|
||||
name="Converting"
|
||||
description="Downsizes photos for faster preview loading. (Zooming in to the photo loads the original)."
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.client.Converting.enabled">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="On the fly converting"
|
||||
description="Converts photos on the fly, when they are requested."
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.server.Converting.onTheFly"
|
||||
[disabled]="!states.client.Converting.enabled.value">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="Resolution"
|
||||
description="The shorter edge of the converted photo will be scaled down to this, while keeping the aspect ratio."
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.server.Converting.resolution"
|
||||
[options]="resolutions"
|
||||
[disabled]="!states.client.Converting.enabled.value"
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="Load full resolution image on zoom"
|
||||
description="Enables loading the full resolution image on zoom in the ligthbox (preview)."
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.client.loadFullImageOnZoom"
|
||||
[disabled]="!states.client.Converting.enabled.value">
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
<button class="btn btn-success float-end"
|
||||
[disabled]="!settingsForm.form.valid || !changed || inProgress"
|
||||
(click)="save()" i18n>Save
|
||||
</button>
|
||||
<button class="btn btn-secondary float-end"
|
||||
[disabled]=" !changed || inProgress"
|
||||
(click)="reset()" i18n>Reset
|
||||
</button>
|
||||
|
||||
<div [hidden]="!states.client.Converting.enabled.value">
|
||||
<app-settings-job-button class="mt-2 mt-md-0 float-left"
|
||||
[soloRun]="true"
|
||||
(jobError)="error=$event"
|
||||
[allowParallelRun]="false"
|
||||
[jobName]="jobName"></app-settings-job-button>
|
||||
|
||||
<ng-container *ngIf="Progress != null">
|
||||
<br/>
|
||||
<hr/>
|
||||
<app-settings-job-progress [progress]="Progress"></app-settings-job-progress>
|
||||
</ng-container>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
@ -1,72 +0,0 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {PhotoSettingsService} from './photo.settings.service';
|
||||
import {SettingsComponentDirective} from '../_abstract/abstract.settings.component';
|
||||
import {AuthenticationService} from '../../../model/network/authentication.service';
|
||||
import {NavigationService} from '../../../model/navigation.service';
|
||||
import {NotificationService} from '../../../model/notification.service';
|
||||
import {ScheduledJobsService} from '../scheduled-jobs.service';
|
||||
import {
|
||||
DefaultsJobs,
|
||||
JobDTOUtils,
|
||||
} from '../../../../../common/entities/job/JobDTO';
|
||||
import {
|
||||
JobProgressDTO,
|
||||
JobProgressStates,
|
||||
} from '../../../../../common/entities/job/JobProgressDTO';
|
||||
import {ServerPhotoConfig} from '../../../../../common/config/private/PrivateConfig';
|
||||
import {ClientPhotoConfig} from '../../../../../common/config/public/ClientConfig';
|
||||
import {SettingsService} from '../settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-photo',
|
||||
templateUrl: './photo.settings.component.html',
|
||||
styleUrls: [
|
||||
'./photo.settings.component.css',
|
||||
'../_abstract/abstract.settings.component.css',
|
||||
],
|
||||
providers: [PhotoSettingsService],
|
||||
})
|
||||
export class PhotoSettingsComponent extends SettingsComponentDirective<ServerPhotoConfig> {
|
||||
readonly resolutionTypes = [720, 1080, 1440, 2160, 4320];
|
||||
resolutions: { key: number; value: string }[] = [];
|
||||
|
||||
readonly jobName = DefaultsJobs[DefaultsJobs['Photo Converting']];
|
||||
|
||||
constructor(
|
||||
authService: AuthenticationService,
|
||||
navigation: NavigationService,
|
||||
settingsService: PhotoSettingsService,
|
||||
public jobsService: ScheduledJobsService,
|
||||
notification: NotificationService,
|
||||
globalSettingsService: SettingsService
|
||||
) {
|
||||
super(
|
||||
$localize`Photo`,
|
||||
'camera-slr',
|
||||
authService,
|
||||
navigation,
|
||||
settingsService,
|
||||
notification,
|
||||
globalSettingsService,
|
||||
(s) => s.Media.Photo
|
||||
);
|
||||
const currentRes =
|
||||
settingsService.Settings.value.Media.Photo.Converting.resolution;
|
||||
if (this.resolutionTypes.indexOf(currentRes) === -1) {
|
||||
this.resolutionTypes.push(currentRes);
|
||||
}
|
||||
this.resolutions = this.resolutionTypes.map((e) => ({
|
||||
key: e,
|
||||
value: e + 'px',
|
||||
}));
|
||||
}
|
||||
|
||||
get Progress(): JobProgressDTO {
|
||||
return this.jobsService.progress.value[
|
||||
JobDTOUtils.getHashName(DefaultsJobs[DefaultsJobs['Photo Converting']])
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,20 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NetworkService } from '../../../model/network/network.service';
|
||||
import { SettingsService } from '../settings.service';
|
||||
import { AbstractSettingsService } from '../_abstract/abstract.settings.service';
|
||||
import { ServerPhotoConfig } from '../../../../../common/config/private/PrivateConfig';
|
||||
import { ClientPhotoConfig } from '../../../../../common/config/public/ClientConfig';
|
||||
|
||||
@Injectable()
|
||||
export class PhotoSettingsService extends AbstractSettingsService<ServerPhotoConfig> {
|
||||
constructor(
|
||||
private networkService: NetworkService,
|
||||
settingsService: SettingsService
|
||||
) {
|
||||
super(settingsService);
|
||||
}
|
||||
|
||||
public updateSettings(settings: ServerPhotoConfig): Promise<void> {
|
||||
return this.networkService.putJson('/settings/photo', { settings });
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
<form #settingsForm="ngForm" class="form-horizontal">
|
||||
<div class="card mb-4">
|
||||
<h5 class="card-header">
|
||||
<span class="oi oi-{{icon}}"></span> {{Name}}
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||
|
||||
<app-settings-entry
|
||||
name="Preview Filter query"
|
||||
description="Filters the sub-folders with this search query"
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.SearchQuery"
|
||||
[typeOverride]="'SearchQuery'"
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="Preview Sorting"
|
||||
description="If multiple preview is available sorts them by these methods and selects the first one."
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.Sorting"
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
<button class="btn btn-success float-end"
|
||||
[disabled]="!settingsForm.form.valid || !changed || inProgress"
|
||||
(click)="save()" i18n>Save
|
||||
</button>
|
||||
<button class="btn btn-secondary float-end"
|
||||
[disabled]=" !changed || inProgress"
|
||||
(click)="reset()" i18n>Reset
|
||||
</button>
|
||||
|
||||
|
||||
<app-settings-job-button class="mt-2 mt-md-0 float-left"
|
||||
#previewFillingButton
|
||||
[soloRun]="true"
|
||||
(jobError)="error=$event"
|
||||
[jobName]="jobName"
|
||||
[allowParallelRun]="false"
|
||||
[config]="Config"></app-settings-job-button>
|
||||
|
||||
|
||||
<app-settings-job-button class="ms-md-2 mt-2 mt-md-0"
|
||||
[danger]="true"
|
||||
[soloRun]="true"
|
||||
(jobError)="error=$event"
|
||||
[allowParallelRun]="false"
|
||||
[disabled]="previewFillingButton.Running"
|
||||
[jobName]="resetJobName"></app-settings-job-button>
|
||||
|
||||
<ng-container *ngIf="Progress != null">
|
||||
<br/>
|
||||
<hr/>
|
||||
<app-settings-job-progress [progress]="Progress"></app-settings-job-progress>
|
||||
</ng-container>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
@ -1,71 +0,0 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {SettingsComponentDirective} from '../_abstract/abstract.settings.component';
|
||||
import {AuthenticationService} from '../../../model/network/authentication.service';
|
||||
import {NavigationService} from '../../../model/navigation.service';
|
||||
import {NotificationService} from '../../../model/notification.service';
|
||||
import {
|
||||
DefaultsJobs,
|
||||
JobDTOUtils,
|
||||
} from '../../../../../common/entities/job/JobDTO';
|
||||
import {ScheduledJobsService} from '../scheduled-jobs.service';
|
||||
import {
|
||||
JobProgressDTO,
|
||||
JobProgressStates,
|
||||
} from '../../../../../common/entities/job/JobProgressDTO';
|
||||
import {ServerPreviewConfig} from '../../../../../common/config/private/PrivateConfig';
|
||||
import {PreviewSettingsService} from './preview.settings.service';
|
||||
import {SettingsService} from '../settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-preview',
|
||||
templateUrl: './preview.settings.component.html',
|
||||
styleUrls: [
|
||||
'./preview.settings.component.css',
|
||||
'../_abstract/abstract.settings.component.css',
|
||||
],
|
||||
providers: [PreviewSettingsService],
|
||||
})
|
||||
export class PreviewSettingsComponent
|
||||
extends SettingsComponentDirective<ServerPreviewConfig>
|
||||
implements OnInit {
|
||||
JobProgressStates = JobProgressStates;
|
||||
readonly jobName = DefaultsJobs[DefaultsJobs['Preview Filling']];
|
||||
readonly resetJobName = DefaultsJobs[DefaultsJobs['Preview Reset']];
|
||||
|
||||
constructor(
|
||||
authService: AuthenticationService,
|
||||
navigation: NavigationService,
|
||||
settingsService: PreviewSettingsService,
|
||||
notification: NotificationService,
|
||||
public jobsService: ScheduledJobsService,
|
||||
globalSettingsService: SettingsService
|
||||
) {
|
||||
super(
|
||||
$localize`Preview`,
|
||||
'image',
|
||||
authService,
|
||||
navigation,
|
||||
settingsService,
|
||||
notification,
|
||||
globalSettingsService,
|
||||
(s) => s.Preview
|
||||
);
|
||||
}
|
||||
|
||||
get Config(): unknown {
|
||||
return {};
|
||||
}
|
||||
|
||||
get Progress(): JobProgressDTO {
|
||||
return this.jobsService.progress.value[
|
||||
JobDTOUtils.getHashName(this.jobName, this.Config)
|
||||
];
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,23 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NetworkService } from '../../../model/network/network.service';
|
||||
import { AbstractSettingsService } from '../_abstract/abstract.settings.service';
|
||||
import { SettingsService } from '../settings.service';
|
||||
import { ServerPreviewConfig } from '../../../../../common/config/private/PrivateConfig';
|
||||
|
||||
@Injectable()
|
||||
export class PreviewSettingsService extends AbstractSettingsService<ServerPreviewConfig> {
|
||||
constructor(
|
||||
private networkService: NetworkService,
|
||||
settingsService: SettingsService
|
||||
) {
|
||||
super(settingsService);
|
||||
}
|
||||
|
||||
hasAvailableSettings(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public updateSettings(settings: ServerPreviewConfig): Promise<void> {
|
||||
return this.networkService.putJson('/settings/preview', { settings });
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
<form #settingsForm="ngForm">
|
||||
<div class="card mb-4"
|
||||
[ngClass]="states.enabled.value && !settingsService.isSupported()?'panel-warning':''">
|
||||
<h5 class="card-header">
|
||||
<span class="oi oi-{{icon}}"></span> {{Name}}
|
||||
<div class="switch-wrapper">
|
||||
<bSwitch
|
||||
class="switch"
|
||||
name="enabled"
|
||||
switch-on-color="success"
|
||||
[switch-inverse]="true"
|
||||
switch-off-text="Disabled"
|
||||
switch-on-text="Enabled"
|
||||
i18n-switch-off-text
|
||||
i18n-switch-on-text
|
||||
[switch-disabled]="inProgress || !settingsService.isSupported()"
|
||||
[switch-handle-width]="100"
|
||||
[switch-label-width]="20"
|
||||
[(ngModel)]="states.enabled.value">
|
||||
</bSwitch>
|
||||
</div>
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||
|
||||
<ng-container *ngIf="states.enabled.value || settingsService.isSupported()">
|
||||
|
||||
<div class="alert alert-secondary" role="alert">
|
||||
<ng-container i18n>
|
||||
This feature enables you to generate 'random photo' urls.
|
||||
That URL returns a photo random selected from your gallery.
|
||||
You can use the url with 3rd party application like random changing desktop background.
|
||||
</ng-container>
|
||||
<br/>
|
||||
<ng-container i18n>
|
||||
Note: With the current implementation, random link also requires login. See:
|
||||
</ng-container>
|
||||
<a href="https://github.com/bpatrik/pigallery2/issues/392">#392</a>
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
<div class="panel-info" *ngIf="(!states.enabled.value && !settingsService.isSupported())" i18n>
|
||||
Random Photo is not supported with these settings
|
||||
</div>
|
||||
<button class="btn btn-success float-end"
|
||||
[disabled]="!settingsForm.form.valid || !changed || inProgress"
|
||||
(click)="save()" i18n>Save
|
||||
</button>
|
||||
<button class="btn btn-secondary float-end"
|
||||
[disabled]=" !changed || inProgress"
|
||||
(click)="reset()" i18n>Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
@ -1,41 +0,0 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {SettingsComponentDirective} from '../_abstract/abstract.settings.component';
|
||||
import {AuthenticationService} from '../../../model/network/authentication.service';
|
||||
import {NavigationService} from '../../../model/navigation.service';
|
||||
import {NotificationService} from '../../../model/notification.service';
|
||||
import {RandomPhotoSettingsService} from './random-photo.settings.service';
|
||||
import {ClientRandomPhotoConfig} from '../../../../../common/config/public/ClientConfig';
|
||||
import {SettingsService} from '../settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-random-photo',
|
||||
templateUrl: './random-photo.settings.component.html',
|
||||
styleUrls: [
|
||||
'./random-photo.settings.component.css',
|
||||
'../_abstract/abstract.settings.component.css',
|
||||
],
|
||||
providers: [RandomPhotoSettingsService],
|
||||
})
|
||||
export class RandomPhotoSettingsComponent extends SettingsComponentDirective<ClientRandomPhotoConfig> {
|
||||
constructor(
|
||||
authService: AuthenticationService,
|
||||
navigation: NavigationService,
|
||||
settingsService: RandomPhotoSettingsService,
|
||||
notification: NotificationService,
|
||||
globalSettingsService: SettingsService
|
||||
) {
|
||||
super(
|
||||
$localize`Random Photo`,
|
||||
'random',
|
||||
authService,
|
||||
navigation,
|
||||
settingsService,
|
||||
notification,
|
||||
globalSettingsService,
|
||||
(s) => s.RandomPhoto
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,31 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NetworkService } from '../../../model/network/network.service';
|
||||
import { SettingsService } from '../settings.service';
|
||||
import { AbstractSettingsService } from '../_abstract/abstract.settings.service';
|
||||
import { DatabaseType } from '../../../../../common/config/private/PrivateConfig';
|
||||
import { ClientSearchConfig } from '../../../../../common/config/public/ClientConfig';
|
||||
|
||||
@Injectable()
|
||||
export class RandomPhotoSettingsService extends AbstractSettingsService<ClientSearchConfig> {
|
||||
constructor(
|
||||
private networkService: NetworkService,
|
||||
settingsService: SettingsService
|
||||
) {
|
||||
super(settingsService);
|
||||
}
|
||||
|
||||
public hasAvailableSettings(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public isSupported(): boolean {
|
||||
return (
|
||||
this.settingsService.settings.value.Database.type !==
|
||||
DatabaseType.memory
|
||||
);
|
||||
}
|
||||
|
||||
public updateSettings(settings: ClientSearchConfig): Promise<void> {
|
||||
return this.networkService.putJson('/settings/randomPhoto', { settings });
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
.panel-info {
|
||||
text-align: center;
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
<form #settingsForm="ngForm">
|
||||
<div class="card mb-4"
|
||||
[ngClass]="states.enabled.value && !settingsService.isSupported()?'panel-warning':''">
|
||||
<h5 class="card-header">
|
||||
<span class="oi oi-{{icon}}"></span> {{Name}}
|
||||
<div class="switch-wrapper">
|
||||
<bSwitch
|
||||
class="switch"
|
||||
name="enabled"
|
||||
switch-on-color="success"
|
||||
[switch-inverse]="true"
|
||||
switch-off-text="Disabled"
|
||||
switch-on-text="Enabled"
|
||||
i18n-switch-off-text
|
||||
i18n-switch-on-text
|
||||
[switch-disabled]="inProgress || !settingsService.isSupported()"
|
||||
[switch-handle-width]="100"
|
||||
[switch-label-width]="20"
|
||||
[(ngModel)]="states.enabled.value">
|
||||
</bSwitch>
|
||||
</div>
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||
|
||||
<ng-container *ngIf="states.enabled.value || settingsService.isSupported()">
|
||||
|
||||
|
||||
<app-settings-entry
|
||||
name="Autocomplete"
|
||||
description="Show hints while typing search query."
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.AutoComplete.enabled">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="Maximum media result"
|
||||
description="Maximum number of photos and videos that listed in one search result"
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.maxMediaResult"
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="List directories"
|
||||
description="Search will also return with directories"
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.listDirectories">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="List metafiles"
|
||||
description="Search also returns with metafiles from directories that contain a media file of the matched search result"
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.listMetafiles">
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
</ng-container>
|
||||
<div class="panel-info" *ngIf="(!states.enabled.value && !settingsService.isSupported())" i18n>
|
||||
Search is not supported with these settings
|
||||
</div>
|
||||
<button class="btn btn-success float-end"
|
||||
[disabled]="!settingsForm.form.valid || !changed || inProgress"
|
||||
(click)="save()" i18n>Save
|
||||
</button>
|
||||
<button class="btn btn-secondary float-end"
|
||||
[disabled]=" !changed || inProgress"
|
||||
(click)="reset()" i18n>Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
@ -1,41 +0,0 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {SettingsComponentDirective} from '../_abstract/abstract.settings.component';
|
||||
import {AuthenticationService} from '../../../model/network/authentication.service';
|
||||
import {NavigationService} from '../../../model/navigation.service';
|
||||
import {NotificationService} from '../../../model/notification.service';
|
||||
import {SearchSettingsService} from './search.settings.service';
|
||||
import {ClientSearchConfig} from '../../../../../common/config/public/ClientConfig';
|
||||
import {SettingsService} from '../settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-search',
|
||||
templateUrl: './search.settings.component.html',
|
||||
styleUrls: [
|
||||
'./search.settings.component.css',
|
||||
'../_abstract/abstract.settings.component.css',
|
||||
],
|
||||
providers: [SearchSettingsService],
|
||||
})
|
||||
export class SearchSettingsComponent extends SettingsComponentDirective<ClientSearchConfig> {
|
||||
constructor(
|
||||
authService: AuthenticationService,
|
||||
navigation: NavigationService,
|
||||
settingsService: SearchSettingsService,
|
||||
notification: NotificationService,
|
||||
globalSettingsService: SettingsService
|
||||
) {
|
||||
super(
|
||||
$localize`Search`,
|
||||
'magnifying-glass',
|
||||
authService,
|
||||
navigation,
|
||||
settingsService,
|
||||
notification,
|
||||
globalSettingsService,
|
||||
(s) => s.Search
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,31 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NetworkService } from '../../../model/network/network.service';
|
||||
import { SettingsService } from '../settings.service';
|
||||
import { AbstractSettingsService } from '../_abstract/abstract.settings.service';
|
||||
import { DatabaseType } from '../../../../../common/config/private/PrivateConfig';
|
||||
import { ClientSearchConfig } from '../../../../../common/config/public/ClientConfig';
|
||||
|
||||
@Injectable()
|
||||
export class SearchSettingsService extends AbstractSettingsService<ClientSearchConfig> {
|
||||
constructor(
|
||||
private networkService: NetworkService,
|
||||
settingsService: SettingsService
|
||||
) {
|
||||
super(settingsService);
|
||||
}
|
||||
|
||||
hasAvailableSettings(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public isSupported(): boolean {
|
||||
return (
|
||||
this.settingsService.settings.value.Database.type !==
|
||||
DatabaseType.memory
|
||||
);
|
||||
}
|
||||
|
||||
public updateSettings(settings: ClientSearchConfig): Promise<void> {
|
||||
return this.networkService.putJson('/settings/search', { settings });
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {BehaviorSubject} from 'rxjs';
|
||||
import {BehaviorSubject, first} from 'rxjs';
|
||||
import {NetworkService} from '../../model/network/network.service';
|
||||
|
||||
import {WebConfig} from '../../../../common/config/private/WebConfig';
|
||||
@ -7,7 +7,9 @@ import {WebConfigClassBuilder} from 'typeconfig/src/decorators/builders/WebConfi
|
||||
import {ConfigPriority} from '../../../../common/config/public/ClientConfig';
|
||||
import {CookieNames} from '../../../../common/CookieNames';
|
||||
import {CookieService} from 'ngx-cookie-service';
|
||||
import {JobDTO} from '../../../../common/entities/job/JobDTO';
|
||||
import {DefaultsJobs, JobDTO} from '../../../../common/entities/job/JobDTO';
|
||||
import {StatisticDTO} from '../../../../common/entities/settings/StatisticDTO';
|
||||
import {ScheduledJobsService} from './scheduled-jobs.service';
|
||||
|
||||
@Injectable()
|
||||
export class SettingsService {
|
||||
@ -15,9 +17,12 @@ export class SettingsService {
|
||||
public settings: BehaviorSubject<WebConfig>;
|
||||
private fetchingSettings = false;
|
||||
public availableJobs: BehaviorSubject<JobDTO[]>;
|
||||
public statistic: BehaviorSubject<StatisticDTO>;
|
||||
|
||||
constructor(private networkService: NetworkService,
|
||||
private jobsService: ScheduledJobsService,
|
||||
private cookieService: CookieService) {
|
||||
this.statistic = new BehaviorSubject(null);
|
||||
this.availableJobs = new BehaviorSubject([]);
|
||||
this.settings = new BehaviorSubject<WebConfig>(new WebConfig());
|
||||
this.getSettings().catch(console.error);
|
||||
@ -25,9 +30,21 @@ export class SettingsService {
|
||||
if (this.cookieService.check(CookieNames.configPriority)) {
|
||||
this.configPriority =
|
||||
parseInt(this.cookieService.get(CookieNames.configPriority));
|
||||
|
||||
}
|
||||
|
||||
|
||||
this.settings.pipe(first()).subscribe(() => {
|
||||
this.loadStatistic();
|
||||
});
|
||||
this.jobsService.onJobFinish.subscribe((jobName: string) => {
|
||||
if (
|
||||
jobName === DefaultsJobs[DefaultsJobs.Indexing] ||
|
||||
jobName === DefaultsJobs[DefaultsJobs['Database Reset']]
|
||||
) {
|
||||
this.loadStatistic();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public async getAvailableJobs(): Promise<void> {
|
||||
@ -60,6 +77,11 @@ export class SettingsService {
|
||||
this.configPriority.toString(),
|
||||
365 * 50
|
||||
);
|
||||
}
|
||||
|
||||
async loadStatistic(): Promise<void> {
|
||||
this.statistic.next(
|
||||
await this.networkService.getJson<StatisticDTO>('/admin/statistic')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
.panel-info {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.share-settings-save-buttons .btn {
|
||||
margin-bottom: 10px;
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
<form #settingsForm="ngForm">
|
||||
<div class="card mb-4"
|
||||
[ngClass]="states.enabled.value && !settingsService.isSupported()?'panel-warning':''">
|
||||
<h5 class="card-header">
|
||||
<span class="oi oi-{{icon}}"></span> {{Name}}
|
||||
<div class="switch-wrapper">
|
||||
<bSwitch
|
||||
class="switch"
|
||||
name="enabled"
|
||||
switch-on-color="success"
|
||||
[switch-inverse]="true"
|
||||
switch-off-text="Disabled"
|
||||
switch-on-text="Enabled"
|
||||
i18n-switch-off-text
|
||||
i18n-switch-on-text
|
||||
[switch-disabled]="inProgress || !settingsService.isSupported()"
|
||||
[switch-handle-width]="100"
|
||||
[switch-label-width]="20"
|
||||
[(ngModel)]="states.enabled.value">
|
||||
</bSwitch>
|
||||
</div>
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||
|
||||
<ng-container *ngIf="states.enabled.value || settingsService.isSupported()">
|
||||
|
||||
<app-settings-entry
|
||||
name="Password protected"
|
||||
description="Enables password protected sharing links."
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.passwordProtected"
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
</ng-container>
|
||||
<div class="panel-info" *ngIf="(!states.enabled.value && !settingsService.isSupported())" i18n>
|
||||
Sharing is not supported with these settings
|
||||
</div>
|
||||
|
||||
<div class="share-settings-save-buttons">
|
||||
<button class="btn btn-success float-end"
|
||||
[disabled]="!settingsForm.form.valid || !changed || inProgress"
|
||||
(click)="save()" i18n>Save
|
||||
</button>
|
||||
<button class="btn btn-secondary float-end"
|
||||
[disabled]=" !changed || inProgress"
|
||||
(click)="reset()" i18n>Reset
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
<hr/>
|
||||
<p class="title" i18n>Shared links:</p>
|
||||
|
||||
<ng-container *ngIf="shares && shares.length >0">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th i18n>Key</th>
|
||||
<th i18n>Folder</th>
|
||||
<th i18n>Creator</th>
|
||||
<th i18n>Expires</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let share of shares">
|
||||
<td>{{share.sharingKey}}</td>
|
||||
<td>{{share.path}}</td>
|
||||
<td>{{share.creator.name}}</td>
|
||||
<td>{{share.expires | date}}</td>
|
||||
<td>
|
||||
<button (click)="deleteSharing(share)" class="btn btn-danger float-end">
|
||||
<span class="oi oi-trash" aria-hidden="true" aria-label="Delete"></span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!shares || shares.length == 0">
|
||||
<div class="panel-info" i18n>
|
||||
No sharing was created.
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
@ -1,65 +0,0 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {SettingsComponentDirective} from '../_abstract/abstract.settings.component';
|
||||
import {AuthenticationService} from '../../../model/network/authentication.service';
|
||||
import {NavigationService} from '../../../model/navigation.service';
|
||||
import {NotificationService} from '../../../model/notification.service';
|
||||
import {ShareSettingsService} from './share.settings.service';
|
||||
import {ClientSharingConfig} from '../../../../../common/config/public/ClientConfig';
|
||||
import {SharingDTO} from '../../../../../common/entities/SharingDTO';
|
||||
import {SettingsService} from '../settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-share',
|
||||
templateUrl: './share.settings.component.html',
|
||||
styleUrls: [
|
||||
'./share.settings.component.css',
|
||||
'../_abstract/abstract.settings.component.css',
|
||||
],
|
||||
providers: [ShareSettingsService],
|
||||
})
|
||||
export class ShareSettingsComponent
|
||||
extends SettingsComponentDirective<ClientSharingConfig, ShareSettingsService>
|
||||
implements OnInit {
|
||||
public shares: SharingDTO[] = [];
|
||||
|
||||
constructor(
|
||||
authService: AuthenticationService,
|
||||
navigation: NavigationService,
|
||||
settingsService: ShareSettingsService,
|
||||
notification: NotificationService,
|
||||
globalSettingsService: SettingsService
|
||||
) {
|
||||
super(
|
||||
$localize`Share`,
|
||||
'share',
|
||||
authService,
|
||||
navigation,
|
||||
settingsService,
|
||||
notification,
|
||||
globalSettingsService,
|
||||
(s) => s.Sharing
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit();
|
||||
this.getSharingList();
|
||||
}
|
||||
|
||||
async deleteSharing(sharing: SharingDTO): Promise<void> {
|
||||
await this.settingsService.deleteSharing(sharing);
|
||||
await this.getSharingList();
|
||||
}
|
||||
|
||||
private async getSharingList(): Promise<void> {
|
||||
try {
|
||||
this.shares = await this.settingsService.getSharingList();
|
||||
} catch (err) {
|
||||
this.shares = [];
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,44 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NetworkService } from '../../../model/network/network.service';
|
||||
import { SettingsService } from '../settings.service';
|
||||
import { AbstractSettingsService } from '../_abstract/abstract.settings.service';
|
||||
import { ClientSharingConfig } from '../../../../../common/config/public/ClientConfig';
|
||||
import { SharingDTO } from '../../../../../common/entities/SharingDTO';
|
||||
import { DatabaseType } from '../../../../../common/config/private/PrivateConfig';
|
||||
|
||||
@Injectable()
|
||||
export class ShareSettingsService extends AbstractSettingsService<ClientSharingConfig> {
|
||||
constructor(
|
||||
private networkService: NetworkService,
|
||||
settingsService: SettingsService
|
||||
) {
|
||||
super(settingsService);
|
||||
}
|
||||
|
||||
hasAvailableSettings(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public isSupported(): boolean {
|
||||
return (
|
||||
this.settingsService.settings.value.Database.type !==
|
||||
DatabaseType.memory &&
|
||||
this.settingsService.settings.value.Users.authenticationRequired === true
|
||||
);
|
||||
}
|
||||
|
||||
public updateSettings(settings: ClientSharingConfig): Promise<void> {
|
||||
return this.networkService.putJson('/settings/share', { settings });
|
||||
}
|
||||
|
||||
public getSharingList(): Promise<SharingDTO[]> {
|
||||
if (!this.settingsService.settings.value.Sharing.enabled) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
return this.networkService.getJson('/share/list');
|
||||
}
|
||||
|
||||
public deleteSharing(sharing: SharingDTO): Promise<void> {
|
||||
return this.networkService.deleteJson('/share/' + sharing.sharingKey);
|
||||
}
|
||||
}
|
@ -34,14 +34,19 @@
|
||||
*ngIf="states.value.enabled === false">
|
||||
{{Name}} <span i18n>config is not supported with these settings.</span>
|
||||
</div>
|
||||
<button class="btn btn-success float-end"
|
||||
[disabled]="settingsForm.form.invalid || !changed || inProgress"
|
||||
(click)="save()" i18n>Save
|
||||
</button>
|
||||
<button class="btn btn-secondary float-end"
|
||||
[disabled]=" !changed || inProgress"
|
||||
(click)="reset()" i18n>Reset
|
||||
</button>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<button class="btn btn-success float-end"
|
||||
[disabled]="settingsForm.form.invalid || !changed || inProgress"
|
||||
(click)="save()" i18n>Save
|
||||
</button>
|
||||
<button class="btn btn-secondary float-end"
|
||||
[disabled]=" !changed || inProgress"
|
||||
(click)="reset()" i18n>Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,74 +0,0 @@
|
||||
<form #settingsForm="ngForm" class="form-horizontal">
|
||||
<div class="card mb-4">
|
||||
<h5 class="card-header">
|
||||
<span class="oi oi-{{icon}}"></span> {{Name}}
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||
|
||||
|
||||
<app-settings-entry
|
||||
name="Thumbnail Quality"
|
||||
description="High quality may be slow."
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.server.qualityPriority">
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
<app-settings-entry
|
||||
name="Icon size"
|
||||
description="Icon size (used on maps)."
|
||||
i18n-description i18n-name
|
||||
[ngModel]="states.client.iconSize"
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
<app-settings-entry
|
||||
name="Thumbnail sizes"
|
||||
i18n-name
|
||||
placeholder="240; 480"
|
||||
[ngModel]="states.client.thumbnailSizes"
|
||||
[required]="true">
|
||||
<small class="form-text text-muted">
|
||||
<ng-container i18n>Size of the thumbnails.</ng-container>
|
||||
<br/>
|
||||
<ng-container i18n>The best matching size will be generated. (More sizes give better quality, but use more
|
||||
storage and CPU to render.)
|
||||
</ng-container>
|
||||
<br/>
|
||||
<ng-container i18n>';' separated integers. If size is 240, that shorter side of the thumbnail will have 160
|
||||
pixels.
|
||||
</ng-container>
|
||||
</small>
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
<button class="btn btn-success float-end"
|
||||
[disabled]="!settingsForm.form.valid || !changed || inProgress"
|
||||
(click)="save()" i18n>Save
|
||||
</button>
|
||||
<button class="btn btn-secondary float-end"
|
||||
[disabled]=" !changed || inProgress"
|
||||
(click)="reset()" i18n>Reset
|
||||
</button>
|
||||
|
||||
|
||||
<app-settings-job-button class="mt-2 mt-md-0 float-left"
|
||||
[soloRun]="true"
|
||||
(jobError)="error=$event"
|
||||
[jobName]="jobName"
|
||||
[allowParallelRun]="false"
|
||||
[config]="Config"></app-settings-job-button>
|
||||
|
||||
|
||||
<ng-container *ngIf="Progress != null">
|
||||
<br/>
|
||||
<hr/>
|
||||
<app-settings-job-progress [progress]="Progress"></app-settings-job-progress>
|
||||
</ng-container>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
@ -1,65 +0,0 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {SettingsComponentDirective} from '../_abstract/abstract.settings.component';
|
||||
import {AuthenticationService} from '../../../model/network/authentication.service';
|
||||
import {NavigationService} from '../../../model/navigation.service';
|
||||
import {NotificationService} from '../../../model/notification.service';
|
||||
import {ThumbnailSettingsService} from './thumbnail.settings.service';
|
||||
import {DefaultsJobs, JobDTOUtils,} from '../../../../../common/entities/job/JobDTO';
|
||||
import {ScheduledJobsService} from '../scheduled-jobs.service';
|
||||
import {JobProgressDTO, JobProgressStates,} from '../../../../../common/entities/job/JobProgressDTO';
|
||||
import {ServerThumbnailConfig} from '../../../../../common/config/private/PrivateConfig';
|
||||
import {ClientThumbnailConfig} from '../../../../../common/config/public/ClientConfig';
|
||||
import {SettingsService} from '../settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-thumbnail',
|
||||
templateUrl: './thumbnail.settings.component.html',
|
||||
styleUrls: [
|
||||
'./thumbnail.settings.component.css',
|
||||
'../_abstract/abstract.settings.component.css',
|
||||
],
|
||||
providers: [ThumbnailSettingsService],
|
||||
})
|
||||
export class ThumbnailSettingsComponent
|
||||
extends SettingsComponentDirective<ServerThumbnailConfig>
|
||||
implements OnInit {
|
||||
JobProgressStates = JobProgressStates;
|
||||
readonly jobName = DefaultsJobs[DefaultsJobs['Thumbnail Generation']];
|
||||
|
||||
constructor(
|
||||
authService: AuthenticationService,
|
||||
navigation: NavigationService,
|
||||
settingsService: ThumbnailSettingsService,
|
||||
notification: NotificationService,
|
||||
public jobsService: ScheduledJobsService,
|
||||
globalSettingsService: SettingsService
|
||||
) {
|
||||
super(
|
||||
$localize`Thumbnail`,
|
||||
'image',
|
||||
authService,
|
||||
navigation,
|
||||
settingsService,
|
||||
notification,
|
||||
globalSettingsService,
|
||||
(s) => s.Media.Thumbnail
|
||||
);
|
||||
}
|
||||
|
||||
get Config(): { sizes: number[] } {
|
||||
return {sizes: [this.states.client.thumbnailSizes.original[0]]};
|
||||
}
|
||||
|
||||
get Progress(): JobProgressDTO {
|
||||
return this.jobsService.progress.value[
|
||||
JobDTOUtils.getHashName(this.jobName, this.Config)
|
||||
];
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,23 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NetworkService } from '../../../model/network/network.service';
|
||||
import { AbstractSettingsService } from '../_abstract/abstract.settings.service';
|
||||
import { SettingsService } from '../settings.service';
|
||||
import { ServerThumbnailConfig } from '../../../../../common/config/private/PrivateConfig';
|
||||
|
||||
@Injectable()
|
||||
export class ThumbnailSettingsService extends AbstractSettingsService<ServerThumbnailConfig> {
|
||||
constructor(
|
||||
private networkService: NetworkService,
|
||||
settingsService: SettingsService
|
||||
) {
|
||||
super(settingsService);
|
||||
}
|
||||
|
||||
hasAvailableSettings(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public updateSettings(settings: ServerThumbnailConfig): Promise<void> {
|
||||
return this.networkService.putJson('/settings/thumbnail', { settings });
|
||||
}
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
<div class="card mb-4">
|
||||
<h5 class="card-header">
|
||||
<span class="oi oi-{{icon}}"></span> {{Name}}
|
||||
<div class="switch-wrapper">
|
||||
<bSwitch
|
||||
class="switch"
|
||||
name="enabled"
|
||||
switch-on-color="success"
|
||||
[switch-inverse]="true"
|
||||
[switch-off-text]="text.Disabled"
|
||||
[switch-on-text]="text.Enabled"
|
||||
[switch-handle-width]="100"
|
||||
[switch-label-width]="20"
|
||||
[switch-disabled]="inProgress"
|
||||
[(ngModel)]="enabled"
|
||||
(changeState)="switched($event)">
|
||||
</bSwitch>
|
||||
</div>
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||
|
||||
<ng-container *ngIf="enabled">
|
||||
<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>
|
||||
<div class="panel-info" *ngIf="!enabled" i18n>
|
||||
To protect the site with password / have login enable this.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 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>
|
@ -1,160 +0,0 @@
|
||||
import {Component, OnInit, ViewChild} from '@angular/core';
|
||||
import {AuthenticationService} from '../../../model/network/authentication.service';
|
||||
import {UserDTO, UserRoles} from '../../../../../common/entities/UserDTO';
|
||||
import {Utils} from '../../../../../common/Utils';
|
||||
import {UserManagerSettingsService} from './usermanager.settings.service';
|
||||
import {ModalDirective} from 'ngx-bootstrap/modal';
|
||||
import {NavigationService} from '../../../model/navigation.service';
|
||||
import {NotificationService} from '../../../model/notification.service';
|
||||
import {ErrorCodes, ErrorDTO} from '../../../../../common/entities/Error';
|
||||
import {ISettingsComponent} from '../_abstract/ISettingsComponent';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-usermanager',
|
||||
templateUrl: './usermanager.settings.component.html',
|
||||
styleUrls: [
|
||||
'./usermanager.settings.component.css',
|
||||
'../_abstract/abstract.settings.component.css',
|
||||
],
|
||||
providers: [UserManagerSettingsService],
|
||||
})
|
||||
export class UserMangerSettingsComponent implements OnInit, ISettingsComponent {
|
||||
@ViewChild('userModal', {static: false}) public childModal: ModalDirective;
|
||||
public newUser = {} as UserDTO;
|
||||
public userRoles: { key: number; value: string }[] = [];
|
||||
public users: UserDTO[] = [];
|
||||
public enabled = true;
|
||||
public error: string = null;
|
||||
public inProgress = false;
|
||||
Name: string;
|
||||
HasAvailableSettings = true;
|
||||
Changed = false;
|
||||
|
||||
icon = 'person';
|
||||
text = {
|
||||
Enabled: 'Enabled',
|
||||
Disabled: 'Disabled',
|
||||
Low: 'Low',
|
||||
High: 'High',
|
||||
};
|
||||
|
||||
constructor(
|
||||
private authService: AuthenticationService,
|
||||
private navigation: NavigationService,
|
||||
private userSettings: UserManagerSettingsService,
|
||||
private notification: NotificationService
|
||||
) {
|
||||
this.Name = $localize`Password protection`;
|
||||
this.text.Enabled = $localize`Enabled`;
|
||||
this.text.Disabled = $localize`Disabled`;
|
||||
this.text.Low = $localize`Low`;
|
||||
this.text.High = $localize`High`;
|
||||
}
|
||||
|
||||
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.getSettings();
|
||||
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;
|
||||
}
|
||||
|
||||
async switched(event: {
|
||||
previousValue: false;
|
||||
currentValue: true;
|
||||
}): Promise<void> {
|
||||
this.inProgress = true;
|
||||
this.error = '';
|
||||
this.enabled = event.currentValue;
|
||||
try {
|
||||
await this.userSettings.updateSettings(this.enabled);
|
||||
await this.getSettings();
|
||||
if (this.enabled === true) {
|
||||
this.notification.success(
|
||||
$localize`Password protection enabled`,
|
||||
$localize`Success`
|
||||
);
|
||||
this.notification.info($localize`Server restart is recommended.`);
|
||||
this.getUsersList();
|
||||
} else {
|
||||
this.notification.success(
|
||||
$localize`Password protection disabled`,
|
||||
$localize`Success`
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
if (err.message) {
|
||||
this.error = (err as ErrorDTO).message;
|
||||
}
|
||||
}
|
||||
this.inProgress = false;
|
||||
}
|
||||
|
||||
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 getSettings(): Promise<void> {
|
||||
this.enabled = await this.userSettings.getSettings();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,39 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { UserDTO } from '../../../../../common/entities/UserDTO';
|
||||
import { NetworkService } from '../../../model/network/network.service';
|
||||
|
||||
import { WebConfig } from '../../../../../common/config/private/WebConfig';
|
||||
|
||||
@Injectable()
|
||||
export class UserManagerSettingsService {
|
||||
constructor(private networkService: NetworkService) {}
|
||||
|
||||
public createUser(user: UserDTO): Promise<string> {
|
||||
return this.networkService.putJson('/user', { newUser: user });
|
||||
}
|
||||
|
||||
public async getSettings(): Promise<boolean> {
|
||||
return (await this.networkService.getJson<Promise<WebConfig>>('/settings'))
|
||||
.Users.authenticationRequired;
|
||||
}
|
||||
|
||||
public updateSettings(settings: boolean): Promise<void> {
|
||||
return this.networkService.putJson('/settings/authentication', {
|
||||
settings,
|
||||
});
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
@ -1,160 +0,0 @@
|
||||
<form #settingsForm="ngForm" class="form-horizontal">
|
||||
<div class="card mb-4">
|
||||
<h5 class="card-header">
|
||||
<span class="oi oi-{{icon}}"></span> {{Name}}
|
||||
<div class="switch-wrapper">
|
||||
<bSwitch
|
||||
class="switch"
|
||||
name="enabled"
|
||||
switch-on-color="success"
|
||||
[switch-inverse]="true"
|
||||
switch-off-text="Disabled"
|
||||
switch-on-text="Enabled"
|
||||
i18n-switch-off-text
|
||||
i18n-switch-on-text
|
||||
[switch-disabled]="inProgress"
|
||||
[switch-handle-width]="100"
|
||||
[switch-label-width]="20"
|
||||
[(ngModel)]="states.client.enabled.value">
|
||||
</bSwitch>
|
||||
</div>
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||
|
||||
<div class="alert alert-secondary" role="alert">
|
||||
<ng-container i18n>Video support uses ffmpeg. ffmpeg and ffprobe binaries need to be available in the PATH or
|
||||
the
|
||||
@ffmpeg-installer/ffmpeg and @ffprobe-installer/ffprobe optional node packages need to be installed.
|
||||
</ng-container>
|
||||
</div>
|
||||
<hr/>
|
||||
|
||||
<p class="title" i18n>Video transcoding:</p>
|
||||
<div class="alert alert-secondary" role="alert">
|
||||
<ng-container i18n>To ensure smooth video playback, video transcoding is recommended to a lower bit rate than
|
||||
the
|
||||
server's upload rate.
|
||||
</ng-container>
|
||||
<ng-container i18n>The transcoded videos will be save to the thumbnail folder.</ng-container>
|
||||
<ng-container i18n>You can trigger the transcoding manually, but you can also create an automatic encoding job
|
||||
in
|
||||
advanced settings mode.
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<app-settings-entry
|
||||
name="Format"
|
||||
i18n-name
|
||||
[ngModel]="states.server.transcoding.format"
|
||||
(ngModelChange)="formatChanged($event)"
|
||||
[options]="formats"
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="Codec"
|
||||
i18n-name
|
||||
[ngModel]="states.server.transcoding.codec"
|
||||
[options]="codecs[states.server.transcoding.format.value]"
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="Resolution"
|
||||
description="The height of the output video will be scaled down to this, while keeping the aspect ratio."
|
||||
i18n-name i18n-description
|
||||
[ngModel]="states.server.transcoding.resolution"
|
||||
(change)="updateBitRate()"
|
||||
[options]="resolutions"
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
<app-settings-entry
|
||||
name="FPS"
|
||||
description="Target frame per second (fps) of the output video will be scaled down this this."
|
||||
i18n-name i18n-description
|
||||
[ngModel]="states.server.transcoding.fps"
|
||||
(change)="updateBitRate()"
|
||||
[options]="fps"
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
<div class="mb-3 row"
|
||||
[class.changed-settings]="states.server.transcoding.bitRate.value !== states.server.transcoding.bitRate.default">
|
||||
<label class="col-md-2 control-label" for="bitRate" i18n>Bit rate</label>
|
||||
<div class="col-md-10">
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control" placeholder="2"
|
||||
id="bitRate"
|
||||
min="0"
|
||||
step="0.1"
|
||||
max="1000"
|
||||
[(ngModel)]="bitRate"
|
||||
name="bitRate" required>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">mbps</span>
|
||||
</div>
|
||||
</div>
|
||||
<small class="form-text text-muted" i18n>Target bit rate of the output video will be scaled down this this.
|
||||
This should be less than the
|
||||
upload rate of your home server.</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<app-settings-entry
|
||||
name="CRF"
|
||||
description="The range of the Constant Rate Factor (CRF) scale is 0–51, where 0 is lossless, 23 is the default, and 51 is worst quality possible."
|
||||
i18n-name i18n-description
|
||||
[ngModel]="states.server.transcoding.crf"
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="Preset"
|
||||
description="A preset is a collection of options that will provide a certain encoding speed to compression ratio. A slower preset will provide better compression (compression is quality per filesize)."
|
||||
i18n-name i18n-description
|
||||
[ngModel]="states.server.transcoding.preset"
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
<app-settings-entry
|
||||
name="Custom Options"
|
||||
description="; separated values. It will be sent to ffmpeg as it is, as custom options."
|
||||
placeholder="-pass 2; -minrate 1M; -maxrate 1M; -bufsize 2M"
|
||||
i18n-name i18n-description
|
||||
[ngModel]="states.server.transcoding.customOptions"
|
||||
[allowSpaces]="true"
|
||||
[required]="true">
|
||||
</app-settings-entry>
|
||||
|
||||
|
||||
<button class="btn btn-success float-end"
|
||||
[disabled]="!settingsForm.form.valid || !changed || inProgress"
|
||||
(click)="save()" i18n>Save
|
||||
</button>
|
||||
<button class="btn btn-secondary float-end"
|
||||
[disabled]=" !changed || inProgress"
|
||||
(click)="reset()" i18n>Reset
|
||||
</button>
|
||||
|
||||
<app-settings-job-button class="mt-2 mt-md-0 float-left"
|
||||
[soloRun]="true"
|
||||
(jobError)="error=$event"
|
||||
[allowParallelRun]="false"
|
||||
[jobName]="jobName"></app-settings-job-button>
|
||||
|
||||
<ng-container *ngIf="Progress != null">
|
||||
<br/>
|
||||
<hr/>
|
||||
<app-settings-job-progress [progress]="Progress"></app-settings-job-progress>
|
||||
</ng-container>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
@ -1,142 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { VideoSettingsService } from './video.settings.service';
|
||||
import { SettingsComponentDirective } from '../_abstract/abstract.settings.component';
|
||||
import { AuthenticationService } from '../../../model/network/authentication.service';
|
||||
import { NavigationService } from '../../../model/navigation.service';
|
||||
import { NotificationService } from '../../../model/notification.service';
|
||||
import { ScheduledJobsService } from '../scheduled-jobs.service';
|
||||
import {
|
||||
DefaultsJobs,
|
||||
JobDTOUtils,
|
||||
} from '../../../../../common/entities/job/JobDTO';
|
||||
import {
|
||||
JobProgressDTO,
|
||||
JobProgressStates,
|
||||
} from '../../../../../common/entities/job/JobProgressDTO';
|
||||
import {
|
||||
ServerVideoConfig,
|
||||
videoCodecType,
|
||||
videoFormatType,
|
||||
videoResolutionType,
|
||||
} from '../../../../../common/config/private/PrivateConfig';
|
||||
import { ClientVideoConfig } from '../../../../../common/config/public/ClientConfig';
|
||||
import {SettingsService} from '../settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-video',
|
||||
templateUrl: './video.settings.component.html',
|
||||
styleUrls: [
|
||||
'./video.settings.component.css',
|
||||
'../_abstract/abstract.settings.component.css',
|
||||
],
|
||||
providers: [VideoSettingsService],
|
||||
})
|
||||
export class VideoSettingsComponent extends SettingsComponentDirective<ServerVideoConfig> {
|
||||
readonly resolutionTypes: videoResolutionType[] = [
|
||||
360, 480, 720, 1080, 1440, 2160, 4320,
|
||||
];
|
||||
|
||||
resolutions: { key: number; value: string }[] = [];
|
||||
codecs: { [key: string]: { key: videoCodecType; value: videoCodecType }[] } =
|
||||
{
|
||||
webm: ['libvpx', 'libvpx-vp9'].map((e: videoCodecType) => ({
|
||||
key: e,
|
||||
value: e,
|
||||
})),
|
||||
mp4: ['libx264', 'libx265'].map((e: videoCodecType) => ({
|
||||
key: e,
|
||||
value: e,
|
||||
})),
|
||||
};
|
||||
formats: { key: videoFormatType; value: videoFormatType }[] = [
|
||||
'mp4',
|
||||
'webm',
|
||||
].map((e: videoFormatType) => ({ key: e, value: e }));
|
||||
fps = [24, 25, 30, 48, 50, 60].map((e) => ({ key: e, value: e }));
|
||||
|
||||
JobProgressStates = JobProgressStates;
|
||||
readonly jobName = DefaultsJobs[DefaultsJobs['Video Converting']];
|
||||
|
||||
constructor(
|
||||
authService: AuthenticationService,
|
||||
navigation: NavigationService,
|
||||
settingsService: VideoSettingsService,
|
||||
public jobsService: ScheduledJobsService,
|
||||
notification: NotificationService,
|
||||
globalSettingsService: SettingsService
|
||||
) {
|
||||
super(
|
||||
$localize`Video`,
|
||||
'video',
|
||||
authService,
|
||||
navigation,
|
||||
settingsService,
|
||||
notification,
|
||||
globalSettingsService,
|
||||
(s) => s.Media.Video
|
||||
);
|
||||
|
||||
const currentRes =
|
||||
settingsService.Settings.value.Media.Video.transcoding.resolution;
|
||||
if (this.resolutionTypes.indexOf(currentRes) === -1) {
|
||||
this.resolutionTypes.push(currentRes);
|
||||
}
|
||||
this.resolutions = this.resolutionTypes.map((e) => ({
|
||||
key: e,
|
||||
value: e + 'px',
|
||||
}));
|
||||
}
|
||||
|
||||
get Progress(): JobProgressDTO {
|
||||
return this.jobsService.progress.value[
|
||||
JobDTOUtils.getHashName(DefaultsJobs[DefaultsJobs['Video Converting']])
|
||||
];
|
||||
}
|
||||
|
||||
get bitRate(): number {
|
||||
return this.states.server.transcoding.bitRate.value / 1024 / 1024;
|
||||
}
|
||||
|
||||
set bitRate(value: number) {
|
||||
this.states.server.transcoding.bitRate.value = Math.round(
|
||||
value * 1024 * 1024
|
||||
);
|
||||
}
|
||||
|
||||
getRecommendedBitRate(resolution: number, fps: number): number {
|
||||
let bitRate = 1024 * 1024;
|
||||
if (resolution <= 360) {
|
||||
bitRate = 1024 * 1024;
|
||||
} else if (resolution <= 480) {
|
||||
bitRate = 2.5 * 1024 * 1024;
|
||||
} else if (resolution <= 720) {
|
||||
bitRate = 5 * 1024 * 1024;
|
||||
} else if (resolution <= 1080) {
|
||||
bitRate = 8 * 1024 * 1024;
|
||||
} else if (resolution <= 1440) {
|
||||
bitRate = 16 * 1024 * 1024;
|
||||
} else {
|
||||
bitRate = 40 * 1024 * 1024;
|
||||
}
|
||||
|
||||
if (fps > 30) {
|
||||
bitRate *= 1.5;
|
||||
}
|
||||
|
||||
return bitRate;
|
||||
}
|
||||
|
||||
updateBitRate(): void {
|
||||
this.states.server.transcoding.bitRate.value = this.getRecommendedBitRate(
|
||||
this.states.server.transcoding.resolution.value,
|
||||
this.states.server.transcoding.fps.value
|
||||
);
|
||||
}
|
||||
|
||||
formatChanged(format: videoFormatType): void {
|
||||
this.states.server.transcoding.codec.value = this.codecs[format][0].key;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,20 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NetworkService } from '../../../model/network/network.service';
|
||||
import { SettingsService } from '../settings.service';
|
||||
import { AbstractSettingsService } from '../_abstract/abstract.settings.service';
|
||||
import { ClientVideoConfig } from '../../../../../common/config/public/ClientConfig';
|
||||
import { ServerVideoConfig } from '../../../../../common/config/private/PrivateConfig';
|
||||
|
||||
@Injectable()
|
||||
export class VideoSettingsService extends AbstractSettingsService<ServerVideoConfig> {
|
||||
constructor(
|
||||
private networkService: NetworkService,
|
||||
settingsService: SettingsService
|
||||
) {
|
||||
super(settingsService);
|
||||
}
|
||||
|
||||
public updateSettings(settings: ServerVideoConfig): Promise<void> {
|
||||
return this.networkService.putJson('/settings/video', { settings });
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user