diff --git a/backend/middlewares/AdminMWs.ts b/backend/middlewares/AdminMWs.ts index f5497059..1d395d05 100644 --- a/backend/middlewares/AdminMWs.ts +++ b/backend/middlewares/AdminMWs.ts @@ -5,6 +5,8 @@ import {Logger} from "../Logger"; import {MySQLConnection} from "../model/mysql/MySQLConnection"; import {DataBaseConfig, DatabaseType} from "../../common/config/private/IPrivateConfig"; import {Config} from "../../common/config/private/Config"; +import {ConfigDiagnostics} from "../model/ConfigDiagnostics"; +import {MapConfig} from "../../common/config/public/ConfigClass"; const LOG_TAG = "[AdminMWs]"; @@ -13,18 +15,22 @@ export class AdminMWs { public static async updateDatabaseSettings(req: Request, res: Response, next: NextFunction) { - if ((typeof req.body === 'undefined') || (typeof req.body.databaseSettings === 'undefined')) { - return next(new Error(ErrorCodes.INPUT_ERROR, "databaseSettings is needed")); + if ((typeof req.body === 'undefined') || (typeof req.body.settings === 'undefined')) { + return next(new Error(ErrorCodes.INPUT_ERROR, "settings is needed")); } - const databaseSettings = req.body.databaseSettings; + const databaseSettings = req.body.settings; try { if (databaseSettings.type == DatabaseType.mysql) { await MySQLConnection.tryConnection(databaseSettings); } Config.Server.database = databaseSettings; - Config.save(); + //only updating explicitly set config (not saving config set by the diagnostics) + const original = Config.original(); + original.Server.database = databaseSettings; + original.save(); + await ConfigDiagnostics.runDiagnostics(); Logger.info(LOG_TAG, "new config:"); Logger.info(LOG_TAG, JSON.stringify(Config, null, '\t')); @@ -43,19 +49,55 @@ export class AdminMWs { public static async testDatabaseSettings(req: Request, res: Response, next: NextFunction) { - if ((typeof req.body === 'undefined') || (typeof req.body.databaseSettings === 'undefined')) { - return next(new Error(ErrorCodes.INPUT_ERROR, "databaseSettings is needed")); + if ((typeof req.body === 'undefined') || (typeof req.body.settings === 'undefined')) { + return next(new Error(ErrorCodes.INPUT_ERROR, "settings is needed")); } - const databaseSettings = req.body.databaseSettings; + const databaseSettings = req.body.settings; try { if (databaseSettings.type == DatabaseType.mysql) { - await MySQLConnection.tryConnection(databaseSettings); + await ConfigDiagnostics.testDatabase(databaseSettings); } return next(); } catch (err) { return next(new Error(ErrorCodes.SETTINGS_ERROR, "Settings error: " + JSON.stringify(err, null, ' '), err)); } } + + + public static async updateMapSettings(req: Request, res: Response, next: NextFunction) { + if ((typeof req.body === 'undefined') || (typeof req.body.settings === 'undefined')) { + return next(new Error(ErrorCodes.INPUT_ERROR, "settings is needed")); + } + + try { + await ConfigDiagnostics.testMapConfig(req.body.settings); + + Config.Client.Map = req.body.settings; + //only updating explicitly set config (not saving config set by the diagnostics) + const original = Config.original(); + original.Client.Map = req.body.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) { + return next(new Error(ErrorCodes.SETTINGS_ERROR, "Settings error: " + JSON.stringify(err, null, ' '), err)); + } + } + + public static async testMapSettings(req: Request, res: Response, next: NextFunction) { + if ((typeof req.body === 'undefined') || (typeof req.body.settings === 'undefined')) { + return next(new Error(ErrorCodes.INPUT_ERROR, "settings is needed")); + } + + try { + await ConfigDiagnostics.testMapConfig(req.body.settings); + return next(); + } catch (err) { + return next(new Error(ErrorCodes.SETTINGS_ERROR, "Settings error: " + JSON.stringify(err, null, ' '), err)); + } + } } diff --git a/backend/middlewares/GalleryMWs.ts b/backend/middlewares/GalleryMWs.ts index 96ba7577..2cf84e18 100644 --- a/backend/middlewares/GalleryMWs.ts +++ b/backend/middlewares/GalleryMWs.ts @@ -89,7 +89,7 @@ export class GalleryMWs { public static async search(req: Request, res: Response, next: NextFunction) { - if (Config.Client.Search.searchEnabled === false) { + if (Config.Client.Search.enabled === false) { return next(); } diff --git a/backend/middlewares/RenderingMWs.ts b/backend/middlewares/RenderingMWs.ts index fe68bca2..c2a0ee02 100644 --- a/backend/middlewares/RenderingMWs.ts +++ b/backend/middlewares/RenderingMWs.ts @@ -7,6 +7,7 @@ import {Config} from "../../common/config/private/Config"; import {PrivateConfigClass} from "../../common/config/private/PrivateConfigClass"; import {UserRoles} from "../../common/entities/UserDTO"; import {NotificationManager} from "../model/NotifocationManager"; +import {Logger} from "../Logger"; export class RenderingMWs { @@ -59,8 +60,17 @@ export class RenderingMWs { public static renderError(err: any, req: Request, res: Response, next: NextFunction): any { if (err instanceof Error) { - if (!(req.session.user && req.session.user.role >= UserRoles.Developer)) { - delete (err.details); + if (err.details) { + if (!(req.session.user && req.session.user.role >= UserRoles.Developer)) { + Logger.warn("Handled error:", err.details.toString() || err.details); + delete (err.details); + } else { + try { + err.details = err.details.toString() || err.details; + } catch (err) { + console.error(err); + } + } } let message = new Message(err, null); return res.json(message); diff --git a/backend/middlewares/user/AuthenticationMWs.ts b/backend/middlewares/user/AuthenticationMWs.ts index a540e8bb..7d119929 100644 --- a/backend/middlewares/user/AuthenticationMWs.ts +++ b/backend/middlewares/user/AuthenticationMWs.ts @@ -32,6 +32,24 @@ export class AuthenticationMWs { return null; } + + public static async tryAuthenticate(req: Request, res: Response, next: NextFunction) { + if (Config.Client.authenticationRequired === false) { + req.session.user = {name: "Admin", role: UserRoles.Admin}; + return next(); + } + try { + const user = await AuthenticationMWs.getSharingUser(req); + if (!!user) { + req.session.user = user; + return next(); + } + } catch (err) { + } + + return next(); + + } public static async authenticate(req: Request, res: Response, next: NextFunction) { if (Config.Client.authenticationRequired === false) { diff --git a/backend/model/ConfigDiagnostics.ts b/backend/model/ConfigDiagnostics.ts new file mode 100644 index 00000000..e7f370db --- /dev/null +++ b/backend/model/ConfigDiagnostics.ts @@ -0,0 +1,168 @@ +import {Config} from "../../common/config/private/Config"; +import { + DataBaseConfig, + DatabaseType, + ThumbnailConfig, + ThumbnailProcessingLib +} from "../../common/config/private/IPrivateConfig"; +import {Logger} from "../Logger"; +import {NotificationManager} from "./NotifocationManager"; +import {ProjectPath} from "../ProjectPath"; +import {MySQLConnection} from "./mysql/MySQLConnection"; +import * as fs from "fs"; +import {MapConfig, SearchConfig, SharingConfig} from "../../common/config/public/ConfigClass"; + +const LOG_TAG = "[ConfigDiagnostics]"; +export class ConfigDiagnostics { + + static async testDatabase(databaseConfig: DataBaseConfig) { + if (databaseConfig.type == DatabaseType.mysql) { + await MySQLConnection.tryConnection(databaseConfig); + } + } + + + static async testThumbnailLib(processingLibrary: ThumbnailProcessingLib) { + switch (processingLibrary) { + case ThumbnailProcessingLib.sharp: + const sharp = require("sharp"); + sharp(); + break; + case ThumbnailProcessingLib.gm: + const gm = require("gm"); + await new Promise((resolve, reject) => { + gm(ProjectPath.FrontendFolder + "/assets/icon.png").size((err, value) => { + if (!err) { + return reject(err); + } + }); + }); + break; + } + } + + static async testThumbnailFolder(folder: string) { + await new Promise((resolve, reject) => { + if (!fs.existsSync(folder)) { + reject("Thumbnail folder not exists: " + folder); + } + fs.access(folder, fs.constants.W_OK, function (err) { + if (err) { + reject({message: "Error during getting write access to temp folder", error: err}); + } + }); + resolve(); + }); + } + + static async testImageFolder(folder: string) { + await new Promise((resolve, reject) => { + if (!fs.existsSync(folder)) { + reject("Images folder not exists: " + folder); + } + fs.access(folder, fs.constants.R_OK, function (err) { + if (err) { + reject({message: "Error during getting read access to images folder", error: err}); + } + }); + resolve(); + }); + } + + + static async testThumbnailConfig(thumbnailConfig: ThumbnailConfig) { + await ConfigDiagnostics.testThumbnailLib(thumbnailConfig.processingLibrary); + await ConfigDiagnostics.testThumbnailFolder(thumbnailConfig.folder); + } + + + static async testSearchConfig(search: SearchConfig) { + if (search.enabled == true && Config.Server.database.type == DatabaseType.memory) { + throw "Memory Database do not support searching"; + } + } + + + static async testSharingConfig(sharing: SharingConfig) { + if (sharing.enabled == true && Config.Server.database.type == DatabaseType.memory) { + throw "Memory Database do not support sharing"; + } + } + + static async testMapConfig(map: MapConfig) { + if (map.enabled == true && (!map.googleApiKey || map.googleApiKey.length == 0)) { + throw "Maps need a valid google api key"; + } + } + + + static async runDiagnostics() { + + if (Config.Server.database.type == DatabaseType.mysql) { + try { + await ConfigDiagnostics.testDatabase(Config.Server.database); + } catch (err) { + Logger.warn(LOG_TAG, "[MYSQL error]", err); + Logger.warn(LOG_TAG, "Error during initializing mysql falling back to memory DB"); + NotificationManager.warning("Error during initializing mysql falling back to memory DB", err); + Config.setDatabaseType(DatabaseType.memory); + } + } + + if (Config.Server.thumbnail.processingLibrary != ThumbnailProcessingLib.Jimp) { + try { + await ConfigDiagnostics.testThumbnailLib(Config.Server.thumbnail.processingLibrary); + } catch (err) { + NotificationManager.warning("Thumbnail hardware acceleration is not possible." + + " '" + ThumbnailProcessingLib[Config.Server.thumbnail.processingLibrary] + "' node module is not found." + + " Falling back to JS based thumbnail generation", err); + Logger.warn(LOG_TAG, "[Thumbnail hardware acceleration] module error: ", err); + Logger.warn(LOG_TAG, "Thumbnail hardware acceleration is not possible." + + " '" + ThumbnailProcessingLib[Config.Server.thumbnail.processingLibrary] + "' node module is not found." + + " Falling back to JS based thumbnail generation"); + Config.Server.thumbnail.processingLibrary = ThumbnailProcessingLib.Jimp; + } + } + + try { + await ConfigDiagnostics.testThumbnailFolder(Config.Server.thumbnail.folder) + } catch (err) { + NotificationManager.error("Thumbnail folder error", err); + Logger.error(LOG_TAG, "Thumbnail folder error", err); + } + + + try { + await ConfigDiagnostics.testImageFolder(Config.Server.imagesFolder) + } catch (err) { + NotificationManager.error("Images folder error", err); + Logger.error(LOG_TAG, "Images folder error", err); + } + + + try { + await ConfigDiagnostics.testSearchConfig(Config.Client.Search); + } catch (err) { + NotificationManager.warning("Search is not supported with these settings, switching off..", err); + Logger.warn(LOG_TAG, "Search is not supported with these settings, switching off..", err); + Config.Client.Search.enabled = false; + } + + try { + await ConfigDiagnostics.testSharingConfig(Config.Client.Sharing); + } catch (err) { + NotificationManager.warning("Sharing is not supported with these settings, switching off..", err); + Logger.warn(LOG_TAG, "Sharing is not supported with these settings, switching off..", err); + Config.Client.Sharing.enabled = false; + } + + try { + await ConfigDiagnostics.testMapConfig(Config.Client.Map); + } catch (err) { + NotificationManager.warning("Maps is not supported with these settings, switching off..", err); + Logger.warn(LOG_TAG, "Maps is not supported with these settings, switching off..", err); + Config.Client.Map.enabled = false; + } + + } +} diff --git a/backend/model/ObjectManagerRepository.ts b/backend/model/ObjectManagerRepository.ts index 543dcb37..54836113 100644 --- a/backend/model/ObjectManagerRepository.ts +++ b/backend/model/ObjectManagerRepository.ts @@ -14,7 +14,8 @@ export class ObjectManagerRepository { private static _instance: ObjectManagerRepository = null; - public static InitMemoryManagers() { + public static async InitMemoryManagers() { + await ObjectManagerRepository.reset(); const GalleryManager = require("./memory/GalleryManager").GalleryManager; const UserManager = require("./memory/UserManager").UserManager; const SearchManager = require("./memory/SearchManager").SearchManager; @@ -26,6 +27,7 @@ export class ObjectManagerRepository { } public static async InitMySQLManagers() { + await ObjectManagerRepository.reset(); await MySQLConnection.init(); const GalleryManager = require("./mysql/GalleryManager").GalleryManager; const UserManager = require("./mysql/UserManager").UserManager; @@ -45,7 +47,8 @@ export class ObjectManagerRepository { return this._instance; } - public static reset() { + public static async reset() { + await MySQLConnection.close(); this._instance = null; } diff --git a/backend/model/interfaces/ISearchManager.ts b/backend/model/interfaces/ISearchManager.ts index e0b42060..d0c35d8b 100644 --- a/backend/model/interfaces/ISearchManager.ts +++ b/backend/model/interfaces/ISearchManager.ts @@ -4,5 +4,4 @@ export interface ISearchManager { autocomplete(text: string): Promise; search(text: string, searchType: SearchTypes): Promise; instantSearch(text: string): Promise; - isSupported(): boolean; } diff --git a/backend/model/interfaces/ISharingManager.ts b/backend/model/interfaces/ISharingManager.ts index 84ade18f..2ee39f2c 100644 --- a/backend/model/interfaces/ISharingManager.ts +++ b/backend/model/interfaces/ISharingManager.ts @@ -3,5 +3,4 @@ export interface ISharingManager { findOne(filter: any): Promise; createSharing(sharing: SharingDTO): Promise; updateSharing(sharing: SharingDTO): Promise; - isSupported(): boolean; } diff --git a/backend/model/memory/SearchManager.ts b/backend/model/memory/SearchManager.ts index a8d71725..b9314e9a 100644 --- a/backend/model/memory/SearchManager.ts +++ b/backend/model/memory/SearchManager.ts @@ -15,10 +15,4 @@ export class SearchManager implements ISearchManager { throw new Error("Method not implemented."); } - isSupported(): boolean { - return false; - } - - - } diff --git a/backend/model/memory/SharingManager.ts b/backend/model/memory/SharingManager.ts index cd444d04..9695c045 100644 --- a/backend/model/memory/SharingManager.ts +++ b/backend/model/memory/SharingManager.ts @@ -3,9 +3,6 @@ import {SharingDTO} from "../../../common/entities/SharingDTO"; export class SharingManager implements ISharingManager { - isSupported(): boolean { - return false; - } findOne(filter: any): Promise { throw new Error("not implemented"); diff --git a/backend/model/mysql/MySQLConnection.ts b/backend/model/mysql/MySQLConnection.ts index c6d1af78..20a9b418 100644 --- a/backend/model/mysql/MySQLConnection.ts +++ b/backend/model/mysql/MySQLConnection.ts @@ -86,7 +86,7 @@ export class MySQLConnection { return true; } - public static async init(): Promise { + public static async init(): Promise { const connection = await this.getConnection(); let userRepository = connection.getRepository(UserEntity); let admins = await userRepository.find({role: UserRoles.Admin}); @@ -100,5 +100,12 @@ export class MySQLConnection { } + public static async close() { + try { + await getConnection().close(); + } catch (err) { + } + } + } diff --git a/backend/model/mysql/SearchManager.ts b/backend/model/mysql/SearchManager.ts index d04e20e2..ef6d3402 100644 --- a/backend/model/mysql/SearchManager.ts +++ b/backend/model/mysql/SearchManager.ts @@ -8,10 +8,6 @@ import {PositionMetaData} from "../../../common/entities/PhotoDTO"; export class SearchManager implements ISearchManager { - isSupported(): boolean { - return true; - } - async autocomplete(text: string) { const connection = await MySQLConnection.getConnection(); diff --git a/backend/model/mysql/SharingManager.ts b/backend/model/mysql/SharingManager.ts index 0f4af0f4..26eb693e 100644 --- a/backend/model/mysql/SharingManager.ts +++ b/backend/model/mysql/SharingManager.ts @@ -7,10 +7,6 @@ import {PasswordHelper} from "../PasswordHelper"; export class SharingManager implements ISharingManager { - isSupported(): boolean { - return true; - } - private async removeExpiredLink() { const connection = await MySQLConnection.getConnection(); return connection diff --git a/backend/model/threading/DiskMangerWorker.ts b/backend/model/threading/DiskMangerWorker.ts index 82c7f1d5..0d80b181 100644 --- a/backend/model/threading/DiskMangerWorker.ts +++ b/backend/model/threading/DiskMangerWorker.ts @@ -9,24 +9,21 @@ import * as exif_parser from "exif-parser"; import {ProjectPath} from "../../ProjectPath"; const LOG_TAG = "[DiskManagerTask]"; - - export class DiskMangerWorker { private static isImage(fullPath: string) { - let imageMimeTypes = [ - 'bmp', - 'gif', - 'jpeg', 'jpg', 'jpe', - 'png', - 'tiff', 'tif', - 'webp', - 'ico', - 'tga' + const extensions = [ + '.bmp', + '.gif', + '.jpeg', '.jpg', '.jpe', + '.png', + '.tiff', '.tif', + '.webp', + '.ico', + '.tga' ]; - let extension = path.extname(fullPath).toLowerCase(); - - return imageMimeTypes.indexOf(extension) !== -1; + const extension = path.extname(fullPath).toLowerCase(); + return extensions.indexOf(extension) !== -1; } private static loadPhotoMetadata(fullPath: string): Promise { diff --git a/backend/routes/AdminRouter.ts b/backend/routes/AdminRouter.ts index 05b58ea8..356d62d6 100644 --- a/backend/routes/AdminRouter.ts +++ b/backend/routes/AdminRouter.ts @@ -48,6 +48,20 @@ export class AdminRouter { AdminMWs.testDatabaseSettings, RenderingMWs.renderOK ); + + app.put("/api/settings/map", + AuthenticationMWs.authenticate, + AuthenticationMWs.authorise(UserRoles.Admin), + AdminMWs.updateMapSettings, + RenderingMWs.renderOK + ); + + app.post("/api/settings/test/map", + AuthenticationMWs.authenticate, + AuthenticationMWs.authorise(UserRoles.Admin), + AdminMWs.testMapSettings, + RenderingMWs.renderOK + ); }; diff --git a/backend/routes/PublicRouter.ts b/backend/routes/PublicRouter.ts index e289583b..772e5c80 100644 --- a/backend/routes/PublicRouter.ts +++ b/backend/routes/PublicRouter.ts @@ -4,39 +4,42 @@ import * as _path from "path"; import {Utils} from "../../common/Utils"; import {Config} from "../../common/config/private/Config"; import {ProjectPath} from "../ProjectPath"; +import {AuthenticationMWs} from "../middlewares/user/AuthenticationMWs"; export class PublicRouter { public static route(app) { - app.use((req: Request, res: Response, next: NextFunction) => { - res.tpl = {}; + const renderIndex = (req: Request, res: Response) => { + res.sendFile(_path.resolve(__dirname, './../../dist/index.html')); + }; - res.tpl.user = null; - if (req.session.user) { - let user = Utils.clone(req.session.user); - delete user.password; - res.tpl.user = user; - } - res.tpl.clientConfig = Config.Client; + app.use( + (req: Request, res: Response, next: NextFunction) => { + res.tpl = {}; - return next(); - }); + res.tpl.user = null; + if (req.session.user) { + let user = Utils.clone(req.session.user); + delete user.password; + res.tpl.user = user; + } + res.tpl.clientConfig = Config.Client; + + return next(); + }); app.get('/config_inject.js', (req: Request, res: Response) => { res.render(_path.resolve(__dirname, './../../dist/config_inject.ejs'), res.tpl); }); - app.get(['/', '/login', "/gallery*", "/share*", "/admin", "/search*"], (req: Request, res: Response) => { - res.sendFile(_path.resolve(__dirname, './../../dist/index.html')); - }); + app.get(['/', '/login', "/gallery*", "/share*", "/admin", "/search*"], + AuthenticationMWs.tryAuthenticate, + renderIndex + ); app.use(_express.static(ProjectPath.FrontendFolder)); app.use('/node_modules', _express.static(_path.resolve(__dirname, './../../node_modules'))); app.use('/common', _express.static(_path.resolve(__dirname, './../../common'))); - const renderIndex = (req: Request, res: Response) => { - res.render(_path.resolve(__dirname, './../../dist/index.html')); - }; - } diff --git a/backend/server.ts b/backend/server.ts index d0c206d9..873e49b1 100644 --- a/backend/server.ts +++ b/backend/server.ts @@ -11,13 +11,12 @@ import {SharingRouter} from "./routes/SharingRouter"; import {ObjectManagerRepository} from "./model/ObjectManagerRepository"; import {Logger} from "./Logger"; import {Config} from "../common/config/private/Config"; -import {DatabaseType, ThumbnailProcessingLib} from "../common/config/private/IPrivateConfig"; +import {DatabaseType} from "../common/config/private/IPrivateConfig"; import {LoggerRouter} from "./routes/LoggerRouter"; -import {ProjectPath} from "./ProjectPath"; import {ThumbnailGeneratorMWs} from "./middlewares/thumbnail/ThumbnailGeneratorMWs"; import {DiskManager} from "./model/DiskManger"; import {NotificationRouter} from "./routes/NotificationRouter"; -import {NotificationManager} from "./model/NotifocationManager"; +import {ConfigDiagnostics} from "./model/ConfigDiagnostics"; const LOG_TAG = "[server]"; export class Server { @@ -34,7 +33,7 @@ export class Server { async init() { Logger.info(LOG_TAG, "running diagnostics..."); - await this.runDiagnostics(); + await ConfigDiagnostics.runDiagnostics(); Logger.info(LOG_TAG, "using config:"); Logger.info(LOG_TAG, JSON.stringify(Config, null, '\t')); @@ -67,6 +66,11 @@ export class Server { DiskManager.init(); ThumbnailGeneratorMWs.init(); + if (Config.Server.database.type == DatabaseType.mysql) { + await ObjectManagerRepository.InitMySQLManagers(); + } else { + await ObjectManagerRepository.InitMemoryManagers(); + } PublicRouter.route(this.app); @@ -93,87 +97,6 @@ export class Server { } - async runDiagnostics() { - - - if (Config.Server.database.type == DatabaseType.mysql) { - try { - await ObjectManagerRepository.InitMySQLManagers(); - } catch (err) { - Logger.warn(LOG_TAG, "[MYSQL error]", err); - Logger.warn(LOG_TAG, "Error during initializing mysql falling back to memory DB"); - - NotificationManager.warning("Error during initializing mysql falling back to memory DB", err); - Config.setDatabaseType(DatabaseType.memory); - await ObjectManagerRepository.InitMemoryManagers(); - } - } else { - await ObjectManagerRepository.InitMemoryManagers(); - } - - if (Config.Server.thumbnail.processingLibrary == ThumbnailProcessingLib.sharp) { - try { - const sharp = require("sharp"); - sharp(); - - } catch (err) { - NotificationManager.warning("Thumbnail hardware acceleration is not possible." + - " 'Sharp' node module is not found." + - " Falling back to JS based thumbnail generation", err); - Logger.warn(LOG_TAG, "[Thumbnail hardware acceleration] sharp module error: ", err); - Logger.warn(LOG_TAG, "Thumbnail hardware acceleration is not possible." + - " 'Sharp' node module is not found." + - " Falling back to JS based thumbnail generation"); - Config.Server.thumbnail.processingLibrary = ThumbnailProcessingLib.Jimp; - } - } - - - if (Config.Server.thumbnail.processingLibrary == ThumbnailProcessingLib.gm) { - try { - const gm = require("gm"); - gm(ProjectPath.FrontendFolder + "/assets/icon.png").size((err, value) => { - if (!err) { - return; - } - NotificationManager.warning("Thumbnail hardware acceleration is not possible." + - " 'gm' node module is not found." + - " Falling back to JS based thumbnail generation", err); - Logger.warn(LOG_TAG, "[Thumbnail hardware acceleration] gm module error: ", err); - Logger.warn(LOG_TAG, "Thumbnail hardware acceleration is not possible." + - " 'gm' node module is not found." + - " Falling back to JS based thumbnail generation"); - Config.Server.thumbnail.processingLibrary = ThumbnailProcessingLib.Jimp; - }); - - } catch (err) { - NotificationManager.warning("Thumbnail hardware acceleration is not possible." + - " 'gm' node module is not found." + - " Falling back to JS based thumbnail generation", err); - Logger.warn(LOG_TAG, "[Thumbnail hardware acceleration] gm module error: ", err); - Logger.warn(LOG_TAG, "Thumbnail hardware acceleration is not possible." + - " 'gm' node module is not found." + - " Falling back to JS based thumbnail generation"); - Config.Server.thumbnail.processingLibrary = ThumbnailProcessingLib.Jimp; - } - } - - if (Config.Client.Search.searchEnabled == true && - ObjectManagerRepository.getInstance().SearchManager.isSupported() == false) { - - NotificationManager.warning("Search is not supported with these settings, switching off.."); - Logger.warn(LOG_TAG, "Search is not supported with these settings, switching off.."); - Config.Client.Search.searchEnabled = false; - } - if (Config.Client.Sharing.enabled == true && - ObjectManagerRepository.getInstance().SharingManager.isSupported() == false) { - - NotificationManager.warning("Sharing is not supported with these settings, switching off.."); - Logger.warn(LOG_TAG, "Sharing is not supported with these settings, switching off.."); - Config.Client.Sharing.enabled = false; - } - } - /** * Event listener for HTTP server "error" event. diff --git a/common/config/private/PrivateConfigClass.ts b/common/config/private/PrivateConfigClass.ts index ac1730af..6897ef45 100644 --- a/common/config/private/PrivateConfigClass.ts +++ b/common/config/private/PrivateConfigClass.ts @@ -19,10 +19,10 @@ export class PrivateConfigClass extends PublicConfigClass implements IPrivateCon database: { type: DatabaseType.mysql, mysql: { - host: "localhost", - username: "root", - password: "root", - database: "pigallery2" + host: "", + username: "", + password: "", + database: "" } }, @@ -36,9 +36,10 @@ export class PrivateConfigClass extends PublicConfigClass implements IPrivateCon public setDatabaseType(type: DatabaseType) { this.Server.database.type = type; if (type === DatabaseType.memory) { - this.Client.Search.searchEnabled = false; + this.Client.Search.enabled = false; this.Client.Search.instantSearchEnabled = false; this.Client.Search.autocompleteEnabled = false; + this.Client.Sharing.enabled = false; } } diff --git a/common/config/public/ConfigClass.ts b/common/config/public/ConfigClass.ts index a6c37cbf..186470d7 100644 --- a/common/config/public/ConfigClass.ts +++ b/common/config/public/ConfigClass.ts @@ -1,26 +1,31 @@ -interface SearchConfig { - searchEnabled: boolean +export interface SearchConfig { + enabled: boolean instantSearchEnabled: boolean autocompleteEnabled: boolean } -interface SharingConfig { +export interface SharingConfig { enabled: boolean; passwordProtected: boolean; } +export interface MapConfig { + enabled: boolean; + googleApiKey: string; +} + export interface ClientConfig { applicationTitle: string; iconSize: number; thumbnailSizes: Array; Search: SearchConfig; Sharing: SharingConfig; + Map: MapConfig; concurrentThumbnailGenerations: number; enableCache: boolean; enableOnScrollRendering: boolean; enableOnScrollThumbnailPrioritising: boolean; authenticationRequired: boolean; - googleApiKey: string; publicUrl: string; } @@ -34,7 +39,7 @@ export class PublicConfigClass { thumbnailSizes: [200, 400, 600], iconSize: 30, Search: { - searchEnabled: true, + enabled: true, instantSearchEnabled: true, autocompleteEnabled: true }, @@ -42,12 +47,15 @@ export class PublicConfigClass { enabled: true, passwordProtected: true }, + Map: { + enabled: true, + googleApiKey: "" + }, concurrentThumbnailGenerations: 1, enableCache: false, enableOnScrollRendering: true, enableOnScrollThumbnailPrioritising: true, authenticationRequired: true, - googleApiKey: "", publicUrl: "" }; diff --git a/frontend/app/admin/admin.component.html b/frontend/app/admin/admin.component.html index a9475d1f..259807e5 100644 --- a/frontend/app/admin/admin.component.html +++ b/frontend/app/admin/admin.component.html @@ -23,5 +23,6 @@ + diff --git a/frontend/app/app.module.ts b/frontend/app/app.module.ts index 8435732e..6e8b82c5 100644 --- a/frontend/app/app.module.ts +++ b/frontend/app/app.module.ts @@ -41,17 +41,18 @@ import {DatabaseSettingsComponent} from "./settings/database/database.settings.c import {ToastModule} from "ng2-toastr/ng2-toastr"; import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; import {NotificationService} from "./model/notification.service"; - +import {JWBootstrapSwitchModule} from "jw-bootstrap-switch-ng2"; import {ClipboardModule} from "ngx-clipboard"; import {NavigationService} from "./model/navigation.service"; import {InfoPanelLightboxComponent} from "./gallery/lightbox/infopanel/info-panel.lightbox.gallery.component"; +import {MapSettingsComponent} from "./settings/map/map.settings.component"; @Injectable() export class GoogleMapsConfig { apiKey: string; constructor() { - this.apiKey = Config.Client.googleApiKey; + this.apiKey = Config.Client.Map.googleApiKey; } } @@ -63,6 +64,7 @@ export class GoogleMapsConfig { BrowserAnimationsModule, appRoutes, ClipboardModule, + JWBootstrapSwitchModule, ToastModule.forRoot(), ModalModule.forRoot(), AgmCoreModule.forRoot(), @@ -91,6 +93,7 @@ export class GoogleMapsConfig { //Settings UserMangerSettingsComponent, DatabaseSettingsComponent, + MapSettingsComponent, StringifyRole], providers: [ {provide: LAZY_MAPS_API_CONFIG, useClass: GoogleMapsConfig}, diff --git a/frontend/app/frame/frame.component.html b/frontend/app/frame/frame.component.html index c50e0515..303e5277 100644 --- a/frontend/app/frame/frame.component.html +++ b/frontend/app/frame/frame.component.html @@ -22,7 +22,7 @@
  • -
  • +
  • diff --git a/frontend/app/gallery/gallery.component.html b/frontend/app/gallery/gallery.component.html index 44698584..8d38966c 100644 --- a/frontend/app/gallery/gallery.component.html +++ b/frontend/app/gallery/gallery.component.html @@ -24,7 +24,8 @@ - + @@ -42,7 +43,8 @@ - +
    = []; containerWidth: number = 0; - private IMAGE_MARGIN = 2; + public IMAGE_MARGIN = 2; private TARGET_COL_COUNT = 5; private MIN_ROW_COUNT = 2; private MAX_ROW_COUNT = 5; diff --git a/frontend/app/gallery/grid/photo/photo.grid.gallery.component.ts b/frontend/app/gallery/grid/photo/photo.grid.gallery.component.ts index 006c4d49..d0b09b50 100644 --- a/frontend/app/gallery/grid/photo/photo.grid.gallery.component.ts +++ b/frontend/app/gallery/grid/photo/photo.grid.gallery.component.ts @@ -43,7 +43,7 @@ export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy { constructor(private thumbnailService: ThumbnailManagerService) { this.SearchTypes = SearchTypes; - this.searchEnabled = Config.Client.Search.searchEnabled; + this.searchEnabled = Config.Client.Search.enabled; } ngOnInit() { diff --git a/frontend/app/gallery/lightbox/infopanel/info-panel.lightbox.gallery.component.html b/frontend/app/gallery/lightbox/infopanel/info-panel.lightbox.gallery.component.html index 14b79bfe..40efd715 100644 --- a/frontend/app/gallery/lightbox/infopanel/info-panel.lightbox.gallery.component.html +++ b/frontend/app/gallery/lightbox/infopanel/info-panel.lightbox.gallery.component.html @@ -76,7 +76,7 @@
    -
    +
    implements OnInit, OnDestroy { + + @ViewChild('settingsForm') form; + public settings: T; + public inProgress = false; + private original: T; + public tested = false; + public error: string = null; + public changed: boolean = false; + private subscription; + + constructor(private name, + private _authService: AuthenticationService, + private _navigation: NavigationService, + protected _settingsService: ISettingsService, + private notification: NotificationService) { + } + + ngOnInit() { + if (!this._authService.isAuthenticated() || + this._authService.user.value.role < UserRoles.Admin) { + this._navigation.toLogin(); + return; + } + this.original = Utils.clone(this.settings); + this.getSettings(); + + this.subscription = this.form.valueChanges.subscribe((data) => { + this.changed = !Utils.equalsFilter(this.settings, this.original); + + this.tested = false; + }); + } + + ngOnDestroy() { + if (this.subscription != null) { + this.subscription.unsubscribe(); + } + } + + private async getSettings() { + const s = await this._settingsService.getSettings(); + this.original = Utils.clone(s); + this.settings = s; + this.tested = false; + this.changed = false; + } + + public reset() { + this.getSettings(); + } + + + public async test() { + this.inProgress = true; + try { + this.error = ""; + await this._settingsService.testSettings(this.settings); + this.tested = true; + } catch (err) { + console.log(err); + if (err.message) { + this.error = (err).message; + } + } + this.inProgress = false; + } + + public async save() { + if (!this.tested) { + return; + } + this.inProgress = true; + await this._settingsService.updateSettings(this.settings); + await this.getSettings(); + this.notification.success(this.name + ' settings saved', "Success"); + this.inProgress = false; + } + +} + + + diff --git a/frontend/app/settings/_abstract/abstract.settings.service.ts b/frontend/app/settings/_abstract/abstract.settings.service.ts new file mode 100644 index 00000000..acc757fc --- /dev/null +++ b/frontend/app/settings/_abstract/abstract.settings.service.ts @@ -0,0 +1,5 @@ +export interface ISettingsService { + getSettings(): Promise; + updateSettings(settings: T): Promise; + testSettings(settings: T): Promise ; +} diff --git a/frontend/app/settings/database/database.settings.component.ts b/frontend/app/settings/database/database.settings.component.ts index 8369b0a7..c6fd27a4 100644 --- a/frontend/app/settings/database/database.settings.component.ts +++ b/frontend/app/settings/database/database.settings.component.ts @@ -1,12 +1,11 @@ -import {Component, OnInit, ViewChild} from "@angular/core"; +import {Component} from "@angular/core"; import {AuthenticationService} from "../../model/network/authentication.service"; -import {Router} from "@angular/router"; -import {UserRoles} from "../../../../common/entities/UserDTO"; -import {DatabaseSettingsService} from "./database.settings.service"; import {DataBaseConfig, DatabaseType} from "../../../../common/config/private/IPrivateConfig"; import {Utils} from "../../../../common/Utils"; -import {Error} from "../../../../common/entities/Error"; import {NotificationService} from "../../model/notification.service"; +import {NavigationService} from "../../model/navigation.service"; +import {SettingsComponent} from "../_abstract/abstract.settings.component"; +import {DatabaseSettingsService} from "./database.settings.service"; @Component({ selector: 'settings-database', @@ -14,84 +13,28 @@ import {NotificationService} from "../../model/notification.service"; styleUrls: ['./database.settings.component.css'], providers: [DatabaseSettingsService], }) -export class DatabaseSettingsComponent implements OnInit { - @ViewChild('settingsForm') form; - +export class DatabaseSettingsComponent extends SettingsComponent { public settings: DataBaseConfig = { type: DatabaseType.memory, mysql: {} }; - inProgress = false; - private original: DataBaseConfig; public types: Array = []; public DatabaseType: any; - public tested = false; - public error: string = null; - public changed: boolean = false; - constructor(private _authService: AuthenticationService, - private _router: Router, - private _dbSettings: DatabaseSettingsService, - private notification: NotificationService) { - this.original = Utils.clone(this.settings); + constructor(_authService: AuthenticationService, + _navigation: NavigationService, + _dbSettings: DatabaseSettingsService, + notification: NotificationService) { + super("Database", _authService, _navigation, _dbSettings, notification); } ngOnInit() { - if (!this._authService.isAuthenticated() || - this._authService.user.value.role < UserRoles.Admin) { - this._router.navigate(['login']); - return; - } + super.ngOnInit(); this.types = Utils .enumToArray(DatabaseType); this.DatabaseType = DatabaseType; - this.getSettings(); - - this.form.valueChanges.subscribe((data) => { - this.changed = !Utils.equalsFilter(this.settings, this.original); - - this.tested = false; - }); } - private async getSettings() { - const s = await this._dbSettings.getSettings(); - this.original = Utils.clone(s); - this.settings = s; - this.tested = false; - this.changed = false; - } - - public reset() { - this.getSettings(); - } - - - public async test() { - this.inProgress = true; - try { - this.error = ""; - await this._dbSettings.testSettings(this.settings); - this.tested = true; - } catch (err) { - console.log(err); - if (err.message) { - this.error = (err).message; - } - } - this.inProgress = false; - } - - public async save() { - if (typeof this.settings.type == "undefined" || !this.tested) { - return; - } - this.inProgress = true; - await this._dbSettings.updateSettings(this.settings); - await this.getSettings(); - this.notification.success('Database settings saved', "Success"); - this.inProgress = false; - } } diff --git a/frontend/app/settings/database/database.settings.service.ts b/frontend/app/settings/database/database.settings.service.ts index d2cd68ea..46e4acd3 100644 --- a/frontend/app/settings/database/database.settings.service.ts +++ b/frontend/app/settings/database/database.settings.service.ts @@ -1,32 +1,21 @@ import {Injectable} from "@angular/core"; import {NetworkService} from "../../model/network/network.service"; import {DataBaseConfig, IPrivateConfig} from "../../../../common/config/private/IPrivateConfig"; -import {NavigationService} from "../../model/navigation.service"; -import {UserRoles} from "../../../../common/entities/UserDTO"; -import {AuthenticationService} from "../../model/network/authentication.service"; @Injectable() export class DatabaseSettingsService { - - - constructor(private _networkService: NetworkService, private _authService: AuthenticationService, private _navigation: NavigationService) { - - if (!this._authService.isAuthenticated() || - this._authService.user.value.role < UserRoles.Admin) { - this._navigation.toLogin(); - return; - } + constructor(private _networkService: NetworkService) { } public async getSettings(): Promise { return (await >this._networkService.getJson("/settings")).Server.database; } - public updateSettings(settings): Promise { - return this._networkService.putJson("/settings/database", {databaseSettings: settings}); + public updateSettings(settings: DataBaseConfig): Promise { + return this._networkService.putJson("/settings/database", {settings: settings}); } - public testSettings(settings): Promise { - return this._networkService.postJson("/settings/test/database", {databaseSettings: settings}); + public testSettings(settings: DataBaseConfig): Promise { + return this._networkService.postJson("/settings/test/database", {settings: settings}); } } diff --git a/frontend/app/settings/map/map.settings.component.css b/frontend/app/settings/map/map.settings.component.css new file mode 100644 index 00000000..86f118d8 --- /dev/null +++ b/frontend/app/settings/map/map.settings.component.css @@ -0,0 +1,11 @@ +.title { + margin-left: -5px; +} + +.btn { + margin-left: 10px; +} + +.form-control { + margin: 5px 0; +} diff --git a/frontend/app/settings/map/map.settings.component.html b/frontend/app/settings/map/map.settings.component.html new file mode 100644 index 00000000..ec1841af --- /dev/null +++ b/frontend/app/settings/map/map.settings.component.html @@ -0,0 +1,42 @@ +
    +
    +

    Map settings

    +
    +
    + +
    +

    + + +

    + + To show the images on a map, google api key is need + +
    + + + +
    +
    diff --git a/frontend/app/settings/map/map.settings.component.ts b/frontend/app/settings/map/map.settings.component.ts new file mode 100644 index 00000000..788717f6 --- /dev/null +++ b/frontend/app/settings/map/map.settings.component.ts @@ -0,0 +1,32 @@ +import {Component} from "@angular/core"; +import {MapConfig} from "../../../../common/config/public/ConfigClass"; +import {MapSettingsService} from "./map.settings.service"; +import {SettingsComponent} from "../_abstract/abstract.settings.component"; +import {AuthenticationService} from "../../model/network/authentication.service"; +import {NavigationService} from "../../model/navigation.service"; +import {NotificationService} from "../../model/notification.service"; + +@Component({ + selector: 'settings-map', + templateUrl: './map.settings.component.html', + styleUrls: ['./map.settings.component.css'], + providers: [MapSettingsService], +}) +export class MapSettingsComponent extends SettingsComponent { + public settings: MapConfig = { + enabled: true, + googleApiKey: "" + }; + + constructor(_authService: AuthenticationService, + _navigation: NavigationService, + _settingsSettings: MapSettingsService, + notification: NotificationService) { + super("Map", _authService, _navigation, _settingsSettings, notification); + } + + +} + + + diff --git a/frontend/app/settings/map/map.settings.service.ts b/frontend/app/settings/map/map.settings.service.ts new file mode 100644 index 00000000..9a415c48 --- /dev/null +++ b/frontend/app/settings/map/map.settings.service.ts @@ -0,0 +1,22 @@ +import {Injectable} from "@angular/core"; +import {NetworkService} from "../../model/network/network.service"; +import {MapConfig} from "../../../../common/config/public/ConfigClass"; +import {IPrivateConfig} from "../../../../common/config/private/IPrivateConfig"; + +@Injectable() +export class MapSettingsService { + constructor(private _networkService: NetworkService) { + } + + public async getSettings(): Promise { + return (await >this._networkService.getJson("/settings")).Client.Map; + } + + public updateSettings(settings: MapConfig): Promise { + return this._networkService.putJson("/settings/map", {settings: settings}); + } + + public testSettings(settings: MapConfig): Promise { + return this._networkService.postJson("/settings/test/map", {settings: settings}); + } +} diff --git a/frontend/index.html b/frontend/index.html index 32b4eeb7..f4750bed 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -7,6 +7,8 @@ + diff --git a/frontend/styles.css b/frontend/styles.css index a3199520..7d865824 100644 --- a/frontend/styles.css +++ b/frontend/styles.css @@ -7,3 +7,7 @@ margin-right: 0; } +.bootstrap-switch-label { + margin-right: -4px; + margin-left: -4px; +} diff --git a/package.json b/package.json index 8be88779..1c09d420 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "intl": "^1.2.5", "jasmine-core": "^2.6.4", "jasmine-spec-reporter": "~4.1.1", + "jw-bootstrap-switch-ng2": "^1.0.3", "karma": "^1.7.0", "karma-cli": "^1.0.1", "karma-coverage-istanbul-reporter": "^1.3.0",