diff --git a/angular.json b/angular.json index 26cd924..1fcee51 100644 --- a/angular.json +++ b/angular.json @@ -74,6 +74,7 @@ "tsConfig": "frontend/tsconfig.spec.json", "scripts": [], "styles": [ + "./node_modules/bootstrap/dist/css/bootstrap.min.css", "node_modules/ngx-toastr/toastr.css", "node_modules/bootstrap/dist/css/bootstrap.css", "node_modules/open-iconic/font/css/open-iconic-bootstrap.css", @@ -131,4 +132,4 @@ "prefix": "app" } } -} +} \ No newline at end of file diff --git a/backend/middlewares/AdminMWs.ts b/backend/middlewares/AdminMWs.ts index 16bbcd2..502e63c 100644 --- a/backend/middlewares/AdminMWs.ts +++ b/backend/middlewares/AdminMWs.ts @@ -3,7 +3,7 @@ import {ErrorCodes, ErrorDTO} from '../../common/entities/Error'; import {ObjectManagers} from '../model/ObjectManagers'; import {Logger} from '../Logger'; import {SQLConnection} from '../model/sql/SQLConnection'; -import {DataBaseConfig, DatabaseType, IndexingConfig, ThumbnailConfig} from '../../common/config/private/IPrivateConfig'; +import {DataBaseConfig, DatabaseType, IndexingConfig, TaskConfig, ThumbnailConfig} from '../../common/config/private/IPrivateConfig'; import {Config} from '../../common/config/private/Config'; import {ConfigDiagnostics} from '../model/diagnostics/ConfigDiagnostics'; import {ClientConfig} from '../../common/config/public/ConfigClass'; @@ -440,6 +440,35 @@ export class AdminMWs { } + public static async updateTasksSettings(req: Request, res: Response, next: NextFunction) { + if ((typeof req.body === 'undefined') || (typeof req.body.settings === 'undefined')) { + return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'settings is needed')); + } + + try { + + // only updating explicitly set config (not saving config set by the diagnostics) + const settings: TaskConfig = req.body.settings; + const original = Config.original(); + await ConfigDiagnostics.testTasksConfig(settings, original); + + Config.Server.tasks = settings; + original.Server.tasks = settings; + original.save(); + + await ConfigDiagnostics.runDiagnostics(); + Logger.info(LOG_TAG, 'new config:'); + Logger.info(LOG_TAG, JSON.stringify(Config, null, '\t')); + return next(); + } catch (err) { + if (err instanceof Error) { + return next(new ErrorDTO(ErrorCodes.SETTINGS_ERROR, 'Settings error: ' + err.toString(), err)); + } + return next(new ErrorDTO(ErrorCodes.SETTINGS_ERROR, 'Settings error: ' + JSON.stringify(err, null, ' '), err)); + } + } + + public static startTask(req: Request, res: Response, next: NextFunction) { try { const id = req.params.id; diff --git a/backend/model/diagnostics/ConfigDiagnostics.ts b/backend/model/diagnostics/ConfigDiagnostics.ts index 3bfca04..5a97b72 100644 --- a/backend/model/diagnostics/ConfigDiagnostics.ts +++ b/backend/model/diagnostics/ConfigDiagnostics.ts @@ -3,6 +3,7 @@ import { DataBaseConfig, DatabaseType, IPrivateConfig, + TaskConfig, ThumbnailConfig, ThumbnailProcessingLib } from '../../../common/config/private/IPrivateConfig'; @@ -127,6 +128,10 @@ export class ConfigDiagnostics { } + static async testTasksConfig(faces: TaskConfig, config: IPrivateConfig) { + + } + static async testFacesConfig(faces: ClientConfig.FacesConfig, config: IPrivateConfig) { if (faces.enabled === true) { if (config.Server.database.type === DatabaseType.memory) { @@ -281,6 +286,17 @@ export class ConfigDiagnostics { Config.Client.Faces.enabled = false; } + + try { + await ConfigDiagnostics.testTasksConfig(Config.Server.tasks, Config); + } catch (ex) { + const err: Error = ex; + NotificationManager.warning('Some Tasks are not supported with these settings. Disabling temporally. ' + + 'Please adjust the config properly.', err.toString()); + Logger.warn(LOG_TAG, 'Some Tasks not supported with these settings, switching off..', err.toString()); + Config.Client.Faces.enabled = false; + } + try { await ConfigDiagnostics.testSharingConfig(Config.Client.Sharing, Config); } catch (ex) { diff --git a/backend/model/tasks/TaskManager.ts b/backend/model/tasks/TaskManager.ts index c4f5da0..2bdfe6f 100644 --- a/backend/model/tasks/TaskManager.ts +++ b/backend/model/tasks/TaskManager.ts @@ -3,6 +3,7 @@ import {TaskProgressDTO} from '../../../common/entities/settings/TaskProgressDTO import {ITask} from './ITask'; import {TaskRepository} from './TaskRepository'; import {Config} from '../../../common/config/private/Config'; +import {TaskTriggerType} from '../../../common/entities/task/TaskScheduleDTO'; export class TaskManager implements ITaskManager { @@ -31,6 +32,34 @@ export class TaskManager implements ITaskManager { return TaskRepository.Instance.getAvailableTasks(); } + public runSchedules(): void { + Config.Server.tasks.scheduled.forEach(schedule => { + let nextRun = null; + switch (schedule.trigger.type) { + case TaskTriggerType.scheduled: + nextRun = Date.now() - schedule.trigger.time; + break; + /*case TaskTriggerType.periodic: + + //TODo finish it + const getNextDayOfTheWeek = (dayOfWeek: number) => { + const refDate = new Date(); + refDate.setHours(0, 0, 0, 0); + refDate.setDate(refDate.getDate() + (dayOfWeek + 7 - refDate.getDay()) % 7); + return refDate; + }; + + nextRun = Date.now() - schedule.trigger.periodicity; + break;*/ + } + + if (nextRun != null) { + setTimeout(() => { + this.start(schedule.taskName, schedule.config); + }, nextRun); + } + }); + } protected findTask(taskName: string): ITask { return this.getAvailableTasks().find(t => t.Name === taskName); diff --git a/backend/routes/AdminRouter.ts b/backend/routes/AdminRouter.ts index 9ea2e77..42e2dc9 100644 --- a/backend/routes/AdminRouter.ts +++ b/backend/routes/AdminRouter.ts @@ -174,6 +174,12 @@ export class AdminRouter { AdminMWs.updateIndexingSettings, RenderingMWs.renderOK ); + app.put('/api/settings/tasks', + AuthenticationMWs.authenticate, + AuthenticationMWs.authorise(UserRoles.Admin), + AdminMWs.updateTasksSettings, + RenderingMWs.renderOK + ); } diff --git a/common/config/private/PrivateConfigClass.ts b/common/config/private/PrivateConfigClass.ts index bb337a7..a252e05 100644 --- a/common/config/private/PrivateConfigClass.ts +++ b/common/config/private/PrivateConfigClass.ts @@ -12,7 +12,7 @@ import * as path from 'path'; import {ConfigLoader} from 'typeconfig'; import {Utils} from '../../Utils'; import {UserRoles} from '../../entities/UserDTO'; -import {TaskScheduleDTO, TaskTriggerType} from '../../entities/task/TaskScheduleDTO'; +import {TaskScheduleDTO} from '../../entities/task/TaskScheduleDTO'; import {Config} from './Config'; /** @@ -65,28 +65,7 @@ export class PrivateConfigClass extends PublicConfigClass implements IPrivateCon listingLimit: 1000 }, tasks: { - scheduled: [ - { - priority: 1, - taskName: 'indexing', - config: null, - trigger: { - type: TaskTriggerType.periodic, - time: { - offset: 0, - repeat: 10 - } - } - }, - { - priority: 2, - taskName: 'Database reset', - config: null, - trigger: { - type: TaskTriggerType.never - } - } - ] + scheduled: [] } }; private ConfigLoader: any; diff --git a/common/entities/task/TaskScheduleDTO.ts b/common/entities/task/TaskScheduleDTO.ts index 500ef7a..b5277ad 100644 --- a/common/entities/task/TaskScheduleDTO.ts +++ b/common/entities/task/TaskScheduleDTO.ts @@ -12,15 +12,13 @@ export interface NeverTaskTrigger { export interface ScheduledTaskTrigger extends TaskTrigger { type: TaskTriggerType.scheduled; - time: number; + time: number; // data time } export interface PeriodicTaskTrigger extends TaskTrigger { type: TaskTriggerType.periodic; - time: { - offset: number, - repeat: number - }; + periodicity: number; // 1-7: week days 8+ every x days + atTime: number; // day time } export interface TaskScheduleDTO { diff --git a/frontend/app/app.module.ts b/frontend/app/app.module.ts index 8d20596..81298fe 100644 --- a/frontend/app/app.module.ts +++ b/frontend/app/app.module.ts @@ -83,6 +83,10 @@ import {ControlsLightboxComponent} from './ui/gallery/lightbox/controls/controls import {FacesSettingsComponent} from './ui/settings/faces/faces.settings.component'; import {TasksSettingsComponent} from './ui/settings/tasks/tasks.settings.component'; import {ScheduledTasksService} from './ui/settings/scheduled-tasks.service'; +import {TimepickerModule} from 'ngx-bootstrap/timepicker'; +import {TimeStampDatePickerComponent} from './ui/utils/timestamp-datepicker/datepicker.component'; +import {TimeStampTimePickerComponent} from './ui/utils/timestamp-timepicker/timepicker.component'; + @Injectable() @@ -141,7 +145,8 @@ export function translationsFactory(locale: string) { BsDropdownModule.forRoot(), SlimLoadingBarModule.forRoot(), BsDatepickerModule.forRoot(), - YagaModule + YagaModule, + TimepickerModule.forRoot() ], declarations: [AppComponent, LoginComponent, @@ -151,6 +156,8 @@ export function translationsFactory(locale: string) { // misc FrameComponent, LanguageComponent, + TimeStampDatePickerComponent, + TimeStampTimePickerComponent, // Gallery GalleryLightboxMediaComponent, GalleryPhotoLoadingComponent, diff --git a/frontend/app/ui/admin/admin.component.ts b/frontend/app/ui/admin/admin.component.ts index 8de25c5..93e58d1 100644 --- a/frontend/app/ui/admin/admin.component.ts +++ b/frontend/app/ui/admin/admin.component.ts @@ -14,7 +14,7 @@ import {Config} from '../../../../common/config/public/Config'; }) export class AdminComponent implements OnInit { - simplifiedMode = true; + simplifiedMode = false; text = { Advanced: 'Advanced', Simplified: 'Simplified' diff --git a/frontend/app/ui/settings/_abstract/abstract.settings.component.ts b/frontend/app/ui/settings/_abstract/abstract.settings.component.ts index 807dd5e..36c36f7 100644 --- a/frontend/app/ui/settings/_abstract/abstract.settings.component.ts +++ b/frontend/app/ui/settings/_abstract/abstract.settings.component.ts @@ -74,7 +74,8 @@ export abstract class SettingsComponent { + this.changed = !this.settingsSame(this.settings, this.original); + }, 0); + } + ngOnInit() { if (!this._authService.isAuthenticated() || this._authService.user.value.role < UserRoles.Admin) { @@ -98,9 +106,7 @@ export abstract class SettingsComponent { - setTimeout(() => { - this.changed = !this.settingsSame(this.settings, this.original); - }, 0); + this.testSettingChanges(); }); } diff --git a/frontend/app/ui/settings/basic/basic.settings.component.html b/frontend/app/ui/settings/basic/basic.settings.component.html index ea58d16..a5dea2e 100644 --- a/frontend/app/ui/settings/basic/basic.settings.component.html +++ b/frontend/app/ui/settings/basic/basic.settings.component.html @@ -1,7 +1,7 @@
- Basic settings + Basic settings*
diff --git a/frontend/app/ui/settings/database/database.settings.component.html b/frontend/app/ui/settings/database/database.settings.component.html index 8aa7816..3165857 100644 --- a/frontend/app/ui/settings/database/database.settings.component.html +++ b/frontend/app/ui/settings/database/database.settings.component.html @@ -1,6 +1,6 @@
- Database settings + Database settings*
diff --git a/frontend/app/ui/settings/faces/faces.settings.component.html b/frontend/app/ui/settings/faces/faces.settings.component.html index e3b7a4a..58d43be 100644 --- a/frontend/app/ui/settings/faces/faces.settings.component.html +++ b/frontend/app/ui/settings/faces/faces.settings.component.html @@ -2,7 +2,7 @@
- Faces settings + Faces settings*
- Folder indexing + Folder indexing*
diff --git a/frontend/app/ui/settings/map/map.settings.component.html b/frontend/app/ui/settings/map/map.settings.component.html index 1c6ea8d..58ed1aa 100644 --- a/frontend/app/ui/settings/map/map.settings.component.html +++ b/frontend/app/ui/settings/map/map.settings.component.html @@ -1,7 +1,7 @@
- Map settings + Map settings*
- Meta file settings + Meta file settings*
- Other settings + Other settings*
diff --git a/frontend/app/ui/settings/random-photo/random-photo.settings.component.html b/frontend/app/ui/settings/random-photo/random-photo.settings.component.html index dd7cd44..3406bf7 100644 --- a/frontend/app/ui/settings/random-photo/random-photo.settings.component.html +++ b/frontend/app/ui/settings/random-photo/random-photo.settings.component.html @@ -2,7 +2,7 @@
- Random Photo settings + Random Photo settings*
{ await this.getProgress(); this.timer = null; this.getProgressPeriodically(); - }, 5000); + }, repeatTime); } private incSubscribers() { diff --git a/frontend/app/ui/settings/search/search.settings.component.html b/frontend/app/ui/settings/search/search.settings.component.html index 73c9a1b..6f822fb 100644 --- a/frontend/app/ui/settings/search/search.settings.component.html +++ b/frontend/app/ui/settings/search/search.settings.component.html @@ -2,7 +2,7 @@
- Search settings + Search settings*
- Share settings + Share settings*
- Tasks + Tasks + *
@@ -24,6 +25,36 @@ [ngValue]="availableTask.Name">{{availableTask.Name}} +
+ + + + + + + +
+ +
diff --git a/frontend/app/ui/settings/tasks/tasks.settings.component.ts b/frontend/app/ui/settings/tasks/tasks.settings.component.ts index cdb04fa..c4486b4 100644 --- a/frontend/app/ui/settings/tasks/tasks.settings.component.ts +++ b/frontend/app/ui/settings/tasks/tasks.settings.component.ts @@ -8,19 +8,29 @@ import {SettingsComponent} from '../_abstract/abstract.settings.component'; import {I18n} from '@ngx-translate/i18n-polyfill'; import {ErrorDTO} from '../../../../../common/entities/Error'; import {ScheduledTasksService} from '../scheduled-tasks.service'; -import {TaskScheduleDTO} from '../../../../../common/entities/task/TaskScheduleDTO'; +import { + NeverTaskTrigger, + PeriodicTaskTrigger, + ScheduledTaskTrigger, + TaskScheduleDTO, + TaskTriggerType +} from '../../../../../common/entities/task/TaskScheduleDTO'; +import {Utils} from '../../../../../common/Utils'; @Component({ selector: 'app-settings-tasks', templateUrl: './tasks.settings.component.html', styleUrls: ['./tasks.settings.component.css', './../_abstract/abstract.settings.component.css'], - providers: [TasksSettingsService], + providers: [TasksSettingsService] }) export class TasksSettingsComponent extends SettingsComponent implements OnInit, OnDestroy, OnChanges { disableButtons = false; + taskTriggerType: { key: number, value: string }[]; + TaskTriggerType = TaskTriggerType; + periods: string[] = []; constructor(_authService: AuthenticationService, _navigation: NavigationService, @@ -37,7 +47,15 @@ export class TasksSettingsComponent extends SettingsComponent s.Server.tasks); this.hasAvailableSettings = !this.simplifiedMode; - + this.taskTriggerType = Utils.enumToArray(TaskTriggerType); + this.periods = [this.i18n('Monday'), + this.i18n('Tuesday'), + this.i18n('Wednesday'), + this.i18n('Thursday'), + this.i18n('Friday'), + this.i18n('Saturday'), + this.i18n('Sunday'), + this.i18n('day')]; } @@ -130,6 +148,34 @@ export class TasksSettingsComponent extends SettingsComponent{type: triggerType}; + switch (triggerType) { + case TaskTriggerType.scheduled: + (schedule.trigger).time = (Date.now()); + break; + + case TaskTriggerType.periodic: + (schedule.trigger).periodicity = null; + (schedule.trigger).atTime = null; + break; + } + } } diff --git a/frontend/app/ui/settings/thumbnail/thumbanil.settings.component.html b/frontend/app/ui/settings/thumbnail/thumbanil.settings.component.html index 2c89138..73dbe1e 100644 --- a/frontend/app/ui/settings/thumbnail/thumbanil.settings.component.html +++ b/frontend/app/ui/settings/thumbnail/thumbanil.settings.component.html @@ -1,7 +1,7 @@
- Thumbnail settings + Thumbnail settings*
diff --git a/frontend/app/ui/settings/video/video.settings.component.html b/frontend/app/ui/settings/video/video.settings.component.html index d84b659..5b89f3a 100644 --- a/frontend/app/ui/settings/video/video.settings.component.html +++ b/frontend/app/ui/settings/video/video.settings.component.html @@ -1,7 +1,7 @@
- Video settings + Video settings*
diff --git a/frontend/app/ui/utils/timestamp-datepicker/datepicker.component.ts b/frontend/app/ui/utils/timestamp-datepicker/datepicker.component.ts new file mode 100644 index 0000000..8fb5730 --- /dev/null +++ b/frontend/app/ui/utils/timestamp-datepicker/datepicker.component.ts @@ -0,0 +1,37 @@ +import {Component, EventEmitter, Input, Output} from '@angular/core'; + +@Component({ + selector: 'app-timestamp-datepicker', + templateUrl: './datepicker.component.html', +}) +export class TimeStampDatePickerComponent { + + timestampValue = 0; + @Output() timestampChange = new EventEmitter(); + + date: Date = new Date(); + @Input() name: string; + + @Input() + public get timestamp() { + return this.timestampValue; + } + + public set timestamp(val: number) { + this.date.setTime(val); + if (this.timestampValue === val) { + return; + } + this.timestampValue = val; + this.timestampChange.emit(this.timestampValue); + } + + onChange(date: Date | string) { + this.timestamp = (new Date(date)).getTime(); + } + + +} + + + diff --git a/frontend/app/ui/utils/timestamp-timepicker/timepicker.component.html b/frontend/app/ui/utils/timestamp-timepicker/timepicker.component.html new file mode 100644 index 0000000..626d61e --- /dev/null +++ b/frontend/app/ui/utils/timestamp-timepicker/timepicker.component.html @@ -0,0 +1,10 @@ + diff --git a/frontend/app/ui/utils/timestamp-timepicker/timepicker.component.ts b/frontend/app/ui/utils/timestamp-timepicker/timepicker.component.ts new file mode 100644 index 0000000..788ebc5 --- /dev/null +++ b/frontend/app/ui/utils/timestamp-timepicker/timepicker.component.ts @@ -0,0 +1,38 @@ +import {Component, EventEmitter, Input, Output} from '@angular/core'; + +@Component({ + selector: 'app-timestamp-timepicker', + templateUrl: './timepicker.component.html', +}) +export class TimeStampTimePickerComponent { + + timestampValue = 0; + @Output() timestampChange = new EventEmitter(); + + date: Date = new Date(); + + @Input() name: string; + + @Input() + public get timestamp() { + return this.timestampValue; + } + + public set timestamp(val: number) { + this.date.setTime(val); + if (this.timestampValue === val) { + return; + } + this.timestampValue = val; + this.timestampChange.emit(this.timestampValue); + } + + onChange(date: Date | string) { + this.timestamp = (new Date(date)).getTime(); + } + + +} + + + diff --git a/package-lock.json b/package-lock.json index 91820f7..2d51ecb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "pigallery2", - "version": "1.6.5", + "version": "1.7.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -4536,8 +4536,7 @@ "bootstrap": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.3.1.tgz", - "integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==", - "dev": true + "integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==" }, "boxen": { "version": "3.2.0", @@ -12243,9 +12242,9 @@ "dev": true }, "ngx-bootstrap": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ngx-bootstrap/-/ngx-bootstrap-5.1.0.tgz", - "integrity": "sha512-gHmIH1dZcZgbgu9Y88iPa8JaMkSM1QrU1zPDSJIw5TUNXVbwhvi5bzh2ttjvL88agyVWmTHM0mgyntPAgULxCQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ngx-bootstrap/-/ngx-bootstrap-4.3.0.tgz", + "integrity": "sha512-ZPS6V2yLEeqB/7KIlVohS8qUdtFa1bgUB/sSPWRcXqOWU3EKhORetZoXG6m2F5ILYDe5hwQvBEjdHPlEz2piOg==", "dev": true }, "ngx-clipboard": { diff --git a/package.json b/package.json index 9ca72b1..97d2c9b 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "dependencies": { "bcryptjs": "2.4.3", "body-parser": "1.19.0", + "bootstrap": "4.1.1", "cookie-parser": "1.4.4", "cookie-session": "2.0.0-beta.3", "ejs": "2.6.2", @@ -39,6 +40,7 @@ "jdataview": "2.5.0", "jimp": "0.6.4", "locale": "0.1.0", + "ngx-bootstrap": "^4.1.1", "npm-check-updates": "^3.1.20", "reflect-metadata": "0.1.13", "sqlite3": "4.0.9",