mirror of
https://github.com/bpatrik/pigallery2.git
synced 2025-01-12 04:23:09 +02:00
Creating E-mail messenger job and e-mail messaging config #683
This commit is contained in:
parent
35340b2c04
commit
763e982d2d
19
package-lock.json
generated
19
package-lock.json
generated
@ -76,6 +76,7 @@
|
||||
"@types/leaflet.markercluster": "1.5.1",
|
||||
"@types/node": "18.15.0",
|
||||
"@types/node-geocoder": "4.2.0",
|
||||
"@types/nodemailer": "^6.4.9",
|
||||
"@types/sharp": "0.31.1",
|
||||
"@types/xml2js": "0.4.11",
|
||||
"@typescript-eslint/eslint-plugin": "5.54.1",
|
||||
@ -4892,6 +4893,15 @@
|
||||
"url": "https://opencollective.com/node-fetch"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/nodemailer": {
|
||||
"version": "6.4.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.9.tgz",
|
||||
"integrity": "sha512-XYG8Gv+sHjaOtUpiuytahMy2mM3rectgroNbs6R3djZEKmPNiIJwe9KqOJBGzKKnNZNKvnuvmugBgpq3w/S0ig==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||
@ -26718,6 +26728,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/nodemailer": {
|
||||
"version": "6.4.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.9.tgz",
|
||||
"integrity": "sha512-XYG8Gv+sHjaOtUpiuytahMy2mM3rectgroNbs6R3djZEKmPNiIJwe9KqOJBGzKKnNZNKvnuvmugBgpq3w/S0ig==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||
|
@ -46,7 +46,7 @@
|
||||
"image-size": "1.0.2",
|
||||
"locale": "0.1.0",
|
||||
"node-geocoder": "4.2.0",
|
||||
"nodemailer": "^6.9.4",
|
||||
"nodemailer": "6.9.4",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"sharp": "0.31.3",
|
||||
"ts-exif-parser": "0.2.2",
|
||||
@ -96,6 +96,7 @@
|
||||
"@types/leaflet.markercluster": "1.5.1",
|
||||
"@types/node": "18.15.0",
|
||||
"@types/node-geocoder": "4.2.0",
|
||||
"@types/nodemailer": "6.4.9",
|
||||
"@types/sharp": "0.31.1",
|
||||
"@types/xml2js": "0.4.11",
|
||||
"@typescript-eslint/eslint-plugin": "5.54.1",
|
||||
|
@ -9,6 +9,7 @@ import {SharingDTO} from '../../common/entities/SharingDTO';
|
||||
import {Utils} from '../../common/Utils';
|
||||
import {LoggerRouter} from '../routes/LoggerRouter';
|
||||
import {TAGS} from '../../common/config/public/ClientConfig';
|
||||
import {ConfigDiagnostics} from '../model/diagnostics/ConfigDiagnostics';
|
||||
|
||||
const forcedDebug = process.env['NODE_ENV'] === 'debug';
|
||||
|
||||
@ -108,6 +109,7 @@ export class RenderingMWs {
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const originalConf = await Config.original();
|
||||
await ConfigDiagnostics.checkEnvironment(originalConf);
|
||||
// These are sensitive information, do not send to the client side
|
||||
originalConf.Server.sessionSecret = null;
|
||||
const message = new Message<PrivateConfigClass>(
|
||||
|
@ -26,6 +26,8 @@ import {
|
||||
import {SearchQueryParser} from '../../../common/SearchQueryParser';
|
||||
import {SearchQueryTypes, TextSearch,} from '../../../common/entities/SearchQueryDTO';
|
||||
import {Utils} from '../../../common/Utils';
|
||||
import {createTransport} from 'nodemailer';
|
||||
import {EmailMessagingType, MessagingConfig} from '../../../common/config/private/MessagingConfig';
|
||||
|
||||
const LOG_TAG = '[ConfigDiagnostics]';
|
||||
|
||||
@ -79,6 +81,14 @@ export class ConfigDiagnostics {
|
||||
}
|
||||
}
|
||||
|
||||
private static async testEmailMessagingConfig(Messaging: MessagingConfig, config: PrivateConfigClass): Promise<void> {
|
||||
Logger.debug(LOG_TAG, 'Testing EmailMessaging config');
|
||||
|
||||
if(Messaging.Email.type === EmailMessagingType.sendmail && !Config.Environment.sendMailAvailable){
|
||||
throw new Error('sendmail e-mail sending method is not supported as the sendmail application cannot be found in the OS.')
|
||||
}
|
||||
}
|
||||
|
||||
static testVideoConfig(videoConfig: ServerVideoConfig,
|
||||
config: PrivateConfigClass): Promise<void> {
|
||||
Logger.debug(LOG_TAG, 'Testing video config with ffmpeg test');
|
||||
@ -285,15 +295,48 @@ export class ConfigDiagnostics {
|
||||
await ConfigDiagnostics.testSharingConfig(config.Sharing, config);
|
||||
await ConfigDiagnostics.testRandomPhotoConfig(config.Sharing, config);
|
||||
await ConfigDiagnostics.testMapConfig(config.Map);
|
||||
await ConfigDiagnostics.testEmailMessagingConfig(config.Messaging, config);
|
||||
|
||||
}
|
||||
|
||||
static async checkEnvironment(Config:PrivateConfigClass): Promise<void> {
|
||||
Logger.debug(LOG_TAG, 'Checking sendmail availability');
|
||||
const transporter = createTransport({
|
||||
sendmail: true,
|
||||
});
|
||||
try {
|
||||
Config.Environment.sendMailAvailable = await transporter.verify();
|
||||
} catch (e) {
|
||||
Config.Environment.sendMailAvailable = false;
|
||||
}
|
||||
if (!Config.Environment.sendMailAvailable) {
|
||||
Config.Messaging.Email.type = EmailMessagingType.SMTP;
|
||||
Logger.info(LOG_TAG, 'Sendmail is not available on the OS. You will need to use an SMTP server if you wish the app to send mails.');
|
||||
}
|
||||
}
|
||||
|
||||
static async runDiagnostics(): Promise<void> {
|
||||
|
||||
if (process.env['NODE_ENV'] === 'debug') {
|
||||
NotificationManager.warning('You are running the application with NODE_ENV=debug. This exposes a lot of debug information that can be a security vulnerability. Set NODE_ENV=production, when you finished debugging.');
|
||||
}
|
||||
|
||||
try {
|
||||
await ConfigDiagnostics.checkEnvironment(Config);
|
||||
} catch (ex) {
|
||||
const err: Error = ex;
|
||||
NotificationManager.error(
|
||||
'Error during checking environment',
|
||||
err.toString()
|
||||
);
|
||||
Logger.error(
|
||||
LOG_TAG,
|
||||
'Error during checking environment',
|
||||
err.toString()
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
await ConfigDiagnostics.testDatabase(Config.Database);
|
||||
} catch (ex) {
|
||||
@ -525,5 +568,23 @@ export class ConfigDiagnostics {
|
||||
);
|
||||
Config.Map.mapProvider = MapProviders.OpenStreetMap;
|
||||
}
|
||||
try {
|
||||
await ConfigDiagnostics.testEmailMessagingConfig(Config.Messaging, Config);
|
||||
} catch (ex) {
|
||||
const err: Error = ex;
|
||||
NotificationManager.warning(
|
||||
'Setting to SMTP method.',
|
||||
err.toString()
|
||||
);
|
||||
Logger.warn(
|
||||
LOG_TAG,
|
||||
'Setting to SMTP method.',
|
||||
err.toString()
|
||||
);
|
||||
Config.Messaging.Email.type = EmailMessagingType.SMTP;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -154,6 +154,7 @@ export abstract class Job<T extends Record<string, any> = Record<string, any>> i
|
||||
this.run();
|
||||
} catch (e) {
|
||||
Logger.error(LOG_TAG, e);
|
||||
this.Progress.State = JobProgressStates.failed;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -5,9 +5,18 @@ import {SortingMethods} from '../../../../common/entities/SortingMethods';
|
||||
import {DatePatternFrequency, DatePatternSearch, SearchQueryDTO, SearchQueryTypes} from '../../../../common/entities/SearchQueryDTO';
|
||||
import {ObjectManagers} from '../../ObjectManagers';
|
||||
import {PhotoEntity} from '../../database/enitites/PhotoEntity';
|
||||
import {EmailMediaMessenger} from '../../mediamessengers/EmailMediaMessenger';
|
||||
|
||||
|
||||
export class TopPickSendJob extends Job<{ searchQuery: SearchQueryDTO, sortBy: SortingMethods[], pickAmount: number }> {
|
||||
export class TopPickSendJob extends Job<{
|
||||
searchQuery: SearchQueryDTO,
|
||||
sortBy: SortingMethods[],
|
||||
pickAmount: number,
|
||||
emailTo: string,
|
||||
emailFrom: string,
|
||||
emailSubject: string,
|
||||
emailText: string,
|
||||
}> {
|
||||
public readonly Name = DefaultsJobs[DefaultsJobs['Top Pick Sending']];
|
||||
public readonly Supported: boolean = true;
|
||||
public readonly ConfigTemplate: ConfigTemplateEntry[] = [
|
||||
@ -33,6 +42,30 @@ export class TopPickSendJob extends Job<{ searchQuery: SearchQueryDTO, sortBy: S
|
||||
name: backendTexts.pickAmount.name,
|
||||
description: backendTexts.pickAmount.description,
|
||||
defaultValue: 5,
|
||||
}, {
|
||||
id: 'emailTo',
|
||||
type: 'email',
|
||||
name: backendTexts.emailTo.name,
|
||||
description: backendTexts.emailTo.description,
|
||||
defaultValue: '',
|
||||
}, {
|
||||
id: 'emailFrom',
|
||||
type: 'email',
|
||||
name: backendTexts.emailFrom.name,
|
||||
description: backendTexts.emailFrom.description,
|
||||
defaultValue: 'norelpy@pigallery2.com',
|
||||
}, {
|
||||
id: 'emailSubject',
|
||||
type: 'string',
|
||||
name: backendTexts.emailSubject.name,
|
||||
description: backendTexts.emailSubject.description,
|
||||
defaultValue: 'Latest photos for you',
|
||||
}, {
|
||||
id: 'emailText',
|
||||
type: 'string',
|
||||
name: backendTexts.emailText.name,
|
||||
description: backendTexts.emailText.description,
|
||||
defaultValue: 'I hand picked these photos just for you:',
|
||||
},
|
||||
];
|
||||
private status: 'Listing' | 'Sending' = 'Listing';
|
||||
@ -64,11 +97,20 @@ export class TopPickSendJob extends Job<{ searchQuery: SearchQueryDTO, sortBy: S
|
||||
this.Progress.log('Collecting Photos and videos to Send');
|
||||
this.Progress.Processed++;
|
||||
this.mediaList = await ObjectManagers.getInstance().SearchManager.getNMedia(this.config.searchQuery, this.config.sortBy, this.config.pickAmount);
|
||||
// console.log(this.mediaList);
|
||||
// console.log(this.mediaList);
|
||||
return false;
|
||||
}
|
||||
|
||||
private async stepSending(): Promise<boolean> {
|
||||
this.Progress.log('Sending emails');
|
||||
const messenger = new EmailMediaMessenger();
|
||||
await messenger.sendMedia({
|
||||
from: this.config.emailFrom,
|
||||
to: this.config.emailTo,
|
||||
subject: this.config.emailSubject,
|
||||
text: this.config.emailText
|
||||
}, this.mediaList);
|
||||
this.Progress.Processed++;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
43
src/backend/model/mediamessengers/EmailMediaMessenger.ts
Normal file
43
src/backend/model/mediamessengers/EmailMediaMessenger.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import {createTransport, Transporter} from 'nodemailer';
|
||||
import {MediaDTO} from '../../../common/entities/MediaDTO';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
import {EmailMessagingType} from '../../../common/config/private/MessagingConfig';
|
||||
|
||||
export class EmailMediaMessenger {
|
||||
transporter: Transporter;
|
||||
|
||||
constructor() {
|
||||
if (Config.Messaging.Email.type === EmailMessagingType.sendmail) {
|
||||
this.transporter = createTransport({
|
||||
sendmail: true
|
||||
});
|
||||
} else {
|
||||
this.transporter = createTransport({
|
||||
host: Config.Messaging.Email.smtp.host,
|
||||
port: Config.Messaging.Email.smtp.port,
|
||||
secure: Config.Messaging.Email.smtp.secure,
|
||||
requireTLS: Config.Messaging.Email.smtp.requireTLS,
|
||||
auth: {
|
||||
user: Config.Messaging.Email.smtp.user,
|
||||
pass: Config.Messaging.Email.smtp.password
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async sendMedia(mailSettings: {
|
||||
from: string,
|
||||
to: string,
|
||||
subject: string,
|
||||
text: string
|
||||
}, media: MediaDTO[]) {
|
||||
|
||||
return await this.transporter.sendMail({
|
||||
from: mailSettings.from,
|
||||
to: mailSettings.to,
|
||||
subject: mailSettings.subject,
|
||||
text: mailSettings.text + media.map(m => m.name).join(', ')
|
||||
});
|
||||
}
|
||||
}
|
@ -1,10 +1,14 @@
|
||||
export type backendText = number;
|
||||
export const backendTexts = {
|
||||
indexedFilesOnly: { name: 10, description: 12 },
|
||||
sizeToGenerate: { name: 20, description: 22 },
|
||||
indexChangesOnly: { name: 30, description: 32 },
|
||||
searchQuery: { name: 40, description: 42 },
|
||||
sortBy: { name: 40, description: 42 },
|
||||
pickAmount: { name: 40, description: 42 },
|
||||
indexedFilesOnly: {name: 10, description: 12},
|
||||
sizeToGenerate: {name: 20, description: 22},
|
||||
indexChangesOnly: {name: 30, description: 32},
|
||||
searchQuery: {name: 40, description: 42},
|
||||
sortBy: {name: 100, description: 102},
|
||||
pickAmount: {name: 50, description: 52},
|
||||
emailTo: {name: 60, description: 62},
|
||||
emailFrom: {name: 70, description: 72},
|
||||
emailSubject: {name: 80, description: 82},
|
||||
emailText: {name: 90, description: 92}
|
||||
|
||||
};
|
||||
|
109
src/common/config/private/MessagingConfig.ts
Normal file
109
src/common/config/private/MessagingConfig.ts
Normal file
@ -0,0 +1,109 @@
|
||||
/* eslint-disable @typescript-eslint/no-inferrable-types */
|
||||
import {SubConfigClass} from '../../../../node_modules/typeconfig/src/decorators/class/SubConfigClass';
|
||||
import {ConfigPriority, TAGS} from '../public/ClientConfig';
|
||||
import {ConfigProperty} from '../../../../node_modules/typeconfig/src/decorators/property/ConfigPropoerty';
|
||||
import {ServerConfig} from './PrivateConfig';
|
||||
|
||||
export enum EmailMessagingType {
|
||||
sendmail = 1,
|
||||
SMTP = 2,
|
||||
}
|
||||
|
||||
@SubConfigClass<TAGS>({softReadonly: true})
|
||||
export class EmailSMTPMessagingConfig {
|
||||
@ConfigProperty({
|
||||
tags: {
|
||||
name: $localize`Host`,
|
||||
priority: ConfigPriority.advanced,
|
||||
hint: 'smtp.example.com'
|
||||
},
|
||||
description: $localize`SMTP host server`
|
||||
})
|
||||
host: string = '';
|
||||
|
||||
@ConfigProperty({
|
||||
tags: {
|
||||
name: $localize`Port`,
|
||||
priority: ConfigPriority.advanced,
|
||||
},
|
||||
description: $localize`SMTP server's port`
|
||||
})
|
||||
port: number = 587;
|
||||
|
||||
@ConfigProperty({
|
||||
tags: {
|
||||
name: $localize`isSecure`,
|
||||
priority: ConfigPriority.advanced,
|
||||
},
|
||||
description: $localize`Is the connection secure. See https://nodemailer.com/smtp/#tls-options for more details`
|
||||
})
|
||||
secure: boolean = false;
|
||||
|
||||
@ConfigProperty({
|
||||
tags: {
|
||||
name: $localize`TLS required`,
|
||||
priority: ConfigPriority.advanced,
|
||||
},
|
||||
description: $localize`if this is true and secure is false then Nodemailer (used library in the background) tries to use STARTTLS. See https://nodemailer.com/smtp/#tls-options for more details`
|
||||
})
|
||||
requireTLS: boolean = true;
|
||||
|
||||
|
||||
@ConfigProperty({
|
||||
tags: {
|
||||
name: $localize`User`,
|
||||
priority: ConfigPriority.advanced,
|
||||
},
|
||||
description: $localize`User to connect to the SMTP server.`
|
||||
})
|
||||
user: string = '';
|
||||
|
||||
|
||||
@ConfigProperty({
|
||||
tags: {
|
||||
name: $localize`Password`,
|
||||
priority: ConfigPriority.advanced,
|
||||
},
|
||||
type: 'password',
|
||||
description: $localize`Password to connect to the SMTP server.`
|
||||
})
|
||||
password: string = '';
|
||||
|
||||
}
|
||||
|
||||
@SubConfigClass<TAGS>({softReadonly: true})
|
||||
export class EmailMessagingConfig {
|
||||
@ConfigProperty<EmailMessagingType, EmailMessagingConfig>({
|
||||
type: EmailMessagingType,
|
||||
tags:
|
||||
{
|
||||
name: $localize`Sending method`,
|
||||
priority: ConfigPriority.advanced,
|
||||
uiDisabled: (sc: EmailMessagingConfig, c: ServerConfig) => !c.Environment.sendMailAvailable
|
||||
} as TAGS,
|
||||
description: $localize`Sendmail uses the built in unix binary if available. STMP connects to any STMP server of your choice.`
|
||||
})
|
||||
type: EmailMessagingType = EmailMessagingType.sendmail;
|
||||
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
{
|
||||
name: $localize`SMTP`,
|
||||
relevant: (c: any) => c.type === EmailMessagingType.SMTP,
|
||||
}
|
||||
})
|
||||
smtp?: EmailSMTPMessagingConfig = new EmailSMTPMessagingConfig();
|
||||
|
||||
}
|
||||
|
||||
@SubConfigClass<TAGS>({softReadonly: true})
|
||||
export class MessagingConfig {
|
||||
@ConfigProperty({
|
||||
tags:
|
||||
{
|
||||
name: $localize`Email`,
|
||||
},
|
||||
description: $localize`The app uses Nodemailer in the background for sending e-mails. Refer to https://nodemailer.com/usage/ if some options are not clear.`
|
||||
})
|
||||
Email: EmailMessagingConfig = new EmailMessagingConfig();
|
||||
}
|
@ -30,6 +30,7 @@ import {DefaultsJobs} from '../../entities/job/JobDTO';
|
||||
import {SearchQueryDTO, SearchQueryTypes, TextSearch,} from '../../entities/SearchQueryDTO';
|
||||
import {SortingMethods} from '../../entities/SortingMethods';
|
||||
import {UserRoles} from '../../entities/UserDTO';
|
||||
import {MessagingConfig} from './MessagingConfig';
|
||||
|
||||
declare let $localize: (s: TemplateStringsArray) => string;
|
||||
|
||||
@ -395,7 +396,7 @@ export class ServerMetaFileConfig extends ClientMetaFileConfig {
|
||||
uiJob: [{
|
||||
job: DefaultsJobs[DefaultsJobs['GPX Compression']],
|
||||
relevant: (c) => c.MetaFile.GPXCompressing.enabled
|
||||
},{
|
||||
}, {
|
||||
job: DefaultsJobs[DefaultsJobs['Delete Compressed GPX']],
|
||||
relevant: (c) => c.MetaFile.GPXCompressing.enabled
|
||||
}]
|
||||
@ -1049,8 +1050,14 @@ export class ServerEnvironmentConfig {
|
||||
buildCommitHash: string | undefined;
|
||||
@ConfigProperty({volatile: true})
|
||||
isDocker: boolean | undefined;
|
||||
@ConfigProperty({
|
||||
volatile: true,
|
||||
description: 'App updates on start-up if sendmail binary is available'
|
||||
})
|
||||
sendMailAvailable: boolean | undefined;
|
||||
}
|
||||
|
||||
|
||||
@SubConfigClass<TAGS>({softReadonly: true})
|
||||
export class ServerConfig extends ClientConfig {
|
||||
|
||||
@ -1144,6 +1151,16 @@ export class ServerConfig extends ClientConfig {
|
||||
})
|
||||
Duplicates: ServerDuplicatesConfig = new ServerDuplicatesConfig();
|
||||
|
||||
@ConfigProperty({
|
||||
tags: {
|
||||
name: $localize`Messaging`,
|
||||
uiIcon: 'chat',
|
||||
githubIssue: 683
|
||||
} as TAGS,
|
||||
description: $localize`The App can send messages (like photos on the same day a year ago. aka: "Top Pick"). Here you can configure the delivery method.`
|
||||
})
|
||||
Messaging: MessagingConfig = new MessagingConfig();
|
||||
|
||||
@ConfigProperty({
|
||||
tags: {
|
||||
name: $localize`Jobs`,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {backendText} from '../../BackendTexts';
|
||||
|
||||
export type fieldType = 'string' | 'number' | 'boolean' | 'number-array' | 'SearchQuery' | 'sort-array';
|
||||
export type fieldType = 'string' | 'email' | 'number' | 'boolean' | 'number-array' | 'SearchQuery' | 'sort-array';
|
||||
|
||||
export enum DefaultsJobs {
|
||||
Indexing = 1,
|
||||
|
@ -4,6 +4,7 @@ export enum JobProgressStates {
|
||||
interrupted = 3,
|
||||
canceled = 4,
|
||||
finished = 5,
|
||||
failed = 6,
|
||||
}
|
||||
|
||||
export interface JobProgressLogDTO {
|
||||
|
@ -17,8 +17,34 @@ export class BackendtextService {
|
||||
return $localize`Only checks indexed files.`;
|
||||
case backendTexts.indexChangesOnly.name:
|
||||
return $localize`Index changes only`;
|
||||
case backendTexts.indexChangesOnly.description:
|
||||
return $localize`Only indexes a folder if it got changed.`;
|
||||
case backendTexts.searchQuery.name:
|
||||
return $localize`Search query`;
|
||||
case backendTexts.searchQuery.description:
|
||||
return $localize`Search query to list photos and videos.`;
|
||||
case backendTexts.sortBy.name:
|
||||
return $localize`Sorting`;
|
||||
case backendTexts.sortBy.description:
|
||||
return $localize`Sorts the photos and videos by this.`;
|
||||
case backendTexts.pickAmount.name:
|
||||
return $localize`Pick`;
|
||||
case backendTexts.pickAmount.description:
|
||||
return $localize`Number of photos and videos to pick.`;
|
||||
case backendTexts.emailTo.name:
|
||||
return $localize`E-mail to`;
|
||||
case backendTexts.emailTo.description:
|
||||
return $localize`E-mail address of the recipient.`;
|
||||
case backendTexts.emailFrom.name:
|
||||
return $localize`E-mail From`;
|
||||
case backendTexts.emailFrom.description:
|
||||
return $localize`E-mail sender address.`;
|
||||
case backendTexts.emailSubject.name:
|
||||
return $localize`Subject`;
|
||||
case backendTexts.emailSubject.description:
|
||||
return $localize`E-mail subject.`;
|
||||
case backendTexts.emailText.name:
|
||||
return $localize`Message`;
|
||||
case backendTexts.emailText.description:
|
||||
return $localize`E-mail text.`;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
@ -71,6 +71,10 @@
|
||||
let-confPath="confPath">
|
||||
<div class="alert alert-secondary" role="alert" *ngIf="rStates.description">
|
||||
{{rStates.description}}
|
||||
<a *ngIf="rStates.tags?.githubIssue"
|
||||
[href]="'https://github.com/bpatrik/pigallery2/issues/'+rStates.tags?.githubIssue">
|
||||
<ng-container i18n>See</ng-container>
|
||||
#{{rStates.tags?.githubIssue}}.</a>
|
||||
</div>
|
||||
<ng-container *ngFor="let ck of getKeys(rStates)">
|
||||
<ng-container *ngIf="!(rStates.value.__state[ck].shouldHide && rStates.value.__state[ck].shouldHide())">
|
||||
|
@ -115,6 +115,8 @@ export class JobProgressComponent implements OnDestroy, OnChanges {
|
||||
return $localize`interrupted`;
|
||||
case JobProgressStates.finished:
|
||||
return $localize`finished`;
|
||||
case JobProgressStates.failed:
|
||||
return $localize`failed`;
|
||||
default:
|
||||
return 'unknown state';
|
||||
}
|
||||
|
@ -0,0 +1,3 @@
|
||||
app-gallery-search-field {
|
||||
width: 100%;
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||
<div *ngFor="let schedule of sortedSchedules() as sortedSchedules; let i= index">
|
||||
<div class="card bg-body-tertiary mt-2 mb-2 no-changed-settings {{shouldIdent(schedule,sortedSchedules[i-1])? 'ms-4' : ''}}">
|
||||
<div
|
||||
class="card bg-body-tertiary mt-2 mb-2 no-changed-settings {{shouldIdent(schedule,sortedSchedules[i-1])? 'ms-4' : ''}}">
|
||||
<div class="card-header">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div (click)="showDetails[schedule.name]=!showDetails[schedule.name]">
|
||||
@ -175,6 +176,14 @@
|
||||
[(ngModel)]="schedule.config[configEntry.id]" required>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="'email'">
|
||||
<input type="email" class="form-control" [name]="configEntry.id+'_'+i"
|
||||
[id]="configEntry.id+'_'+i"
|
||||
placeholder="adress@domain.com"
|
||||
(ngModelChange)="onChange($event)"
|
||||
[(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"
|
||||
@ -189,6 +198,54 @@
|
||||
(ngModelChange)="setNumberArray(schedule.config,configEntry.id,$event); onChange($event);"
|
||||
[ngModel]="getNumberArray($any(schedule.config),configEntry.id)" required>
|
||||
</ng-container>
|
||||
|
||||
<app-gallery-search-field
|
||||
*ngSwitchCase="'SearchQuery'"
|
||||
[(ngModel)]="schedule.config[configEntry.id]"
|
||||
[id]="configEntry.id+'_'+i"
|
||||
[name]="configEntry.id+'_'+i"
|
||||
(change)="onChange($event)"
|
||||
placeholder="Search Query">
|
||||
</app-gallery-search-field>
|
||||
|
||||
|
||||
<ng-container *ngSwitchCase="'sort-array'">
|
||||
<ng-container *ngFor="let _ of AsSortArray(schedule.config[configEntry.id]); let j=index">
|
||||
<div class="row col-12 mt-1 m-0 p-0">
|
||||
<div class="col p-0">
|
||||
<select
|
||||
[id]="configEntry.id+'_'+i+'_'+j"
|
||||
[name]="configEntry.id+'_'+i+'_'+j"
|
||||
(ngModelChange)="onChange($event)"
|
||||
class="form-select" [(ngModel)]="AsSortArray(schedule.config[configEntry.id])[j]">
|
||||
<option *ngFor="let opt of SortingMethods" [ngValue]="opt.key">{{opt.value}}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
|
||||
</div>
|
||||
<ng-container>
|
||||
<div class="col-auto pe-0">
|
||||
<button class="btn btn-secondary float-end"
|
||||
[id]="'list_btn_'+configEntry.id+'_'+i+'_'+j"
|
||||
[name]="'list_btn_'+configEntry.id+'_'+i+'_'+j"
|
||||
(click)="removeSorting(schedule.config[configEntry.id],j)"><span
|
||||
class="oi oi-trash"></span>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container>
|
||||
<div class="col-12 p-0">
|
||||
<button class="btn btn-primary mt-1 float-end"
|
||||
[id]="'btn_add_'+configEntry.id+'_'+i"
|
||||
[name]="'btn_add_'+configEntry.id+'_'+i"
|
||||
(click)="AddNewSorting(schedule.config[configEntry.id])" i18n>+ Add
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
<small class="form-text text-muted">
|
||||
|
@ -19,6 +19,8 @@ import {
|
||||
ScheduledJobTriggerConfig
|
||||
} from '../../../../../common/config/private/PrivateConfig';
|
||||
import {ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator} from '@angular/forms';
|
||||
import {enumToTranslatedArray} from '../../EnumTranslations';
|
||||
import {SortingMethods} from '../../../../../common/entities/SortingMethods';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-workflow',
|
||||
@ -61,6 +63,9 @@ export class WorkflowComponent implements ControlValueAccessor, Validator, OnIni
|
||||
allowParallelRun: false,
|
||||
};
|
||||
|
||||
SortingMethods = enumToTranslatedArray(SortingMethods);
|
||||
|
||||
|
||||
error: string;
|
||||
|
||||
constructor(
|
||||
@ -278,4 +283,17 @@ export class WorkflowComponent implements ControlValueAccessor, Validator, OnIni
|
||||
this.onTouched = fn;
|
||||
}
|
||||
|
||||
|
||||
|
||||
AsSortArray(configElement: string | number | string[] | number[]): SortingMethods[] {
|
||||
return configElement as SortingMethods[];
|
||||
}
|
||||
|
||||
removeSorting(configElement: string | number | string[] | number[], j: number): void {
|
||||
(configElement as SortingMethods[]).splice(j);
|
||||
}
|
||||
|
||||
AddNewSorting(configElement: string | number | string[] | number[]): void {
|
||||
(configElement as SortingMethods[]).push(SortingMethods.ascDate)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user