1
0
mirror of https://github.com/xuthus83/pigallery2.git synced 2025-01-14 14:43:17 +08:00

improving config check and setting

This commit is contained in:
Braun Patrik 2017-07-13 23:39:09 +02:00
parent 46af62c93f
commit 3a72f5e3d2
41 changed files with 587 additions and 255 deletions

View File

@ -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 = <DataBaseConfig>req.body.databaseSettings;
const databaseSettings = <DataBaseConfig>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 = <DataBaseConfig>req.body.databaseSettings;
const databaseSettings = <DataBaseConfig>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(<MapConfig>req.body.settings);
Config.Client.Map = <MapConfig>req.body.settings;
//only updating explicitly set config (not saving config set by the diagnostics)
const original = Config.original();
original.Client.Map = <MapConfig>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(<MapConfig>req.body.settings);
return next();
} catch (err) {
return next(new Error(ErrorCodes.SETTINGS_ERROR, "Settings error: " + JSON.stringify(err, null, ' '), err));
}
}
}

View File

@ -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();
}

View File

@ -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<any>(err, null);
return res.json(message);

View File

@ -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 = <UserDTO>{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) {

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -4,5 +4,4 @@ export interface ISearchManager {
autocomplete(text: string): Promise<AutoCompleteItem[]>;
search(text: string, searchType: SearchTypes): Promise<SearchResultDTO>;
instantSearch(text: string): Promise<SearchResultDTO>;
isSupported(): boolean;
}

View File

@ -3,5 +3,4 @@ export interface ISharingManager {
findOne(filter: any): Promise<SharingDTO>;
createSharing(sharing: SharingDTO): Promise<SharingDTO>;
updateSharing(sharing: SharingDTO): Promise<SharingDTO>;
isSupported(): boolean;
}

View File

@ -15,10 +15,4 @@ export class SearchManager implements ISearchManager {
throw new Error("Method not implemented.");
}
isSupported(): boolean {
return false;
}
}

View File

@ -3,9 +3,6 @@ import {SharingDTO} from "../../../common/entities/SharingDTO";
export class SharingManager implements ISharingManager {
isSupported(): boolean {
return false;
}
findOne(filter: any): Promise<SharingDTO> {
throw new Error("not implemented");

View File

@ -86,7 +86,7 @@ export class MySQLConnection {
return true;
}
public static async init(): Promise<void> {
public static async init(): Promise<void> {
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) {
}
}
}

View File

@ -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();

View File

@ -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

View File

@ -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<PhotoMetadata> {

View File

@ -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
);
};

View File

@ -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'));
};
}

View File

@ -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.

View File

@ -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;
}
}

View File

@ -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<number>;
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: ""
};

View File

@ -23,5 +23,6 @@
<settings-usermanager *ngIf="userManagementEnable"></settings-usermanager>
<settings-database></settings-database>
<settings-map></settings-map>
</div>
</app-frame>

View File

@ -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},

View File

@ -22,7 +22,7 @@
<ng-content select="[navbar]"></ng-content>
<li class="divider-vertical">
</li>
<li>
<li *ngIf="authenticationRequired">
<p class="navbar-text" *ngIf="user.value">
<span class="glyphicon glyphicon-user" aria-hidden="true"></span> {{user.value.name}}</p>
</li>

View File

@ -24,7 +24,8 @@
<gallery-directory *ngFor="let directory of directories"
[directory]="directory"></gallery-directory>
</div>
<gallery-map *ngIf="isPhotoWithLocation" [photos]="_galleryService.content.value.directory.photos"></gallery-map>
<gallery-map *ngIf="isPhotoWithLocation && mapEnabled"
[photos]="_galleryService.content.value.directory.photos"></gallery-map>
<gallery-grid [photos]="_galleryService.content.value.directory.photos" [lightbox]="lightbox"></gallery-grid>
</div>
@ -42,7 +43,8 @@
</li>
</ol>
<gallery-map *ngIf="isPhotoWithLocation" [photos]="_galleryService.content.value.searchResult.photos"></gallery-map>
<gallery-map *ngIf="isPhotoWithLocation && mapEnabled"
[photos]="_galleryService.content.value.searchResult.photos"></gallery-map>
<div class="directories">
<gallery-directory *ngFor="let directory of directories"

View File

@ -34,6 +34,7 @@ export class GalleryComponent implements OnInit, OnDestroy {
timer: null
};
public countDown = null;
public mapEnabled = true;
constructor(public _galleryService: GalleryService,
private _authService: AuthenticationService,
@ -41,6 +42,7 @@ export class GalleryComponent implements OnInit, OnDestroy {
private shareService: ShareService,
private _route: ActivatedRoute,
private _navigation: NavigationService) {
this.mapEnabled = Config.Client.Map.enabled;
}
@ -67,7 +69,7 @@ export class GalleryComponent implements OnInit, OnDestroy {
return this._navigation.toLogin();
}
this.showSearchBar = Config.Client.Search.searchEnabled && this._authService.isAuthorized(UserRoles.Guest);
this.showSearchBar = Config.Client.Search.enabled && this._authService.isAuthorized(UserRoles.Guest);
this.showShare = Config.Client.Sharing.enabled && this._authService.isAuthorized(UserRoles.User);
this.subscription.content = this._galleryService.content.subscribe(this.onContentChange);

View File

@ -34,7 +34,7 @@ export class GalleryGridComponent implements OnChanges, AfterViewInit {
photosToRender: Array<GridPhoto> = [];
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;

View File

@ -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() {

View File

@ -76,7 +76,7 @@
</div>
</div>
<div id="map" *ngIf="hasGPS()">
<div id="map" *ngIf="hasGPS() && mapEnabled">
<agm-map
[disableDefaultUI]="true"
[zoomControl]="false"

View File

@ -1,5 +1,6 @@
import {Component, ElementRef, Input} from "@angular/core";
import {PhotoDTO} from "../../../../../common/entities/PhotoDTO";
import {Config} from "../../../../../common/config/public/Config";
@Component({
selector: 'info-panel',
@ -9,8 +10,10 @@ import {PhotoDTO} from "../../../../../common/entities/PhotoDTO";
export class InfoPanelLightboxComponent {
@Input() photo: PhotoDTO;
public mapEnabled = true;
constructor(public elementRef: ElementRef) {
this.mapEnabled = Config.Client.Map.enabled;
}
calcMpx() {

View File

@ -61,7 +61,7 @@ export class GallerySearchComponent {
}
public onSearch() {
if (Config.Client.Search.searchEnabled) {
if (Config.Client.Search.enabled) {
this._galleryService.search(this.searchText);
}
}

View File

@ -18,9 +18,11 @@ export class AuthenticationService {
constructor(private _userService: UserService) {
this.user = new BehaviorSubject(null);
console.log(ServerInject.user);
//picking up session..
if (this.isAuthenticated() == false && Cookie.get('pigallery2-session') != null) {
if (typeof ServerInject !== "undefined" && typeof ServerInject.user !== "undefined") {
console.log(ServerInject.user);
this.user.next(ServerInject.user);
}
this.getSessionUser();

View File

@ -0,0 +1,93 @@
import {OnDestroy, OnInit, ViewChild} from "@angular/core";
import {AuthenticationService} from "../../model/network/authentication.service";
import {UserRoles} from "../../../../common/entities/UserDTO";
import {Utils} from "../../../../common/Utils";
import {Error} from "../../../../common/entities/Error";
import {NotificationService} from "../../model/notification.service";
import {NavigationService} from "../../model/navigation.service";
import {ISettingsService} from "./abstract.settings.service";
export abstract class SettingsComponent<T> 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<T>,
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 = (<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;
}
}

View File

@ -0,0 +1,5 @@
export interface ISettingsService<T> {
getSettings(): Promise<T>;
updateSettings(settings: T): Promise<void>;
testSettings(settings: T): Promise<void> ;
}

View File

@ -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<DataBaseConfig> {
public settings: DataBaseConfig = <DataBaseConfig> {
type: DatabaseType.memory,
mysql: {}
};
inProgress = false;
private original: DataBaseConfig;
public types: Array<any> = [];
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 = (<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;
}
}

View File

@ -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<DataBaseConfig> {
return (await <Promise<IPrivateConfig>>this._networkService.getJson("/settings")).Server.database;
}
public updateSettings(settings): Promise<void> {
return this._networkService.putJson("/settings/database", {databaseSettings: settings});
public updateSettings(settings: DataBaseConfig): Promise<void> {
return this._networkService.putJson("/settings/database", {settings: settings});
}
public testSettings(settings): Promise<void> {
return this._networkService.postJson<void>("/settings/test/database", {databaseSettings: settings});
public testSettings(settings: DataBaseConfig): Promise<void> {
return this._networkService.postJson<void>("/settings/test/database", {settings: settings});
}
}

View File

@ -0,0 +1,11 @@
.title {
margin-left: -5px;
}
.btn {
margin-left: 10px;
}
.form-control {
margin: 5px 0;
}

View File

@ -0,0 +1,42 @@
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Map settings</h3>
</div>
<div class="panel-body">
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
<form #settingsForm="ngForm">
<p>
<bSwitch
name="enabled"
[switch-on-color]="'success'"
[switch-inverse]="'inverse'"
[switch-off-text]="'Disabled'"
[switch-on-text]="'Enabled'"
[switch-handle-width]="'100'"
[switch-label-width]="'20'"
[(ngModel)]="settings.enabled">
</bSwitch>
</p>
<input type="text" class="form-control" placeholder="Google api key" autofocus
[(ngModel)]="settings.googleApiKey"
[disabled]="!settings.enabled"
name="googleApiKey" required>
<span class="help-block">To show the images on a map, <a
href="https://developers.google.com/maps/documentation/javascript/get-api-key">google api key</a> is need</span>
</form>
<button class="btn btn-primary pull-right"
*ngIf="tested==false"
[disabled]="!settingsForm.form.valid || !changed || inProgress"
(click)="test()">Test
</button>
<button class="btn btn-success pull-right"
*ngIf="tested==true"
[disabled]="!settingsForm.form.valid || !changed || inProgress"
(click)="save()">Save
</button>
<button class="btn btn-default pull-right"
(click)="reset()">Reset
</button>
</div>
</div>

View File

@ -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<MapConfig> {
public settings: MapConfig = <MapConfig> {
enabled: true,
googleApiKey: ""
};
constructor(_authService: AuthenticationService,
_navigation: NavigationService,
_settingsSettings: MapSettingsService,
notification: NotificationService) {
super("Map", _authService, _navigation, _settingsSettings, notification);
}
}

View File

@ -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<MapConfig> {
return (await <Promise<IPrivateConfig>>this._networkService.getJson("/settings")).Client.Map;
}
public updateSettings(settings: MapConfig): Promise<void> {
return this._networkService.putJson("/settings/map", {settings: settings});
}
public testSettings(settings: MapConfig): Promise<void> {
return this._networkService.postJson<void>("/settings/test/map", {settings: settings});
}
}

View File

@ -7,6 +7,8 @@
<link rel="shortcut icon" href="assets/icon.png">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="../node_modules/ng2-toastr/bundles/ng2-toastr.min.css" rel="stylesheet"/>
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-switch/3.3.2/css/bootstrap3/bootstrap-switch.css">
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<script type="text/javascript" src="config_inject.js"></script>
</head>

View File

@ -7,3 +7,7 @@
margin-right: 0;
}
.bootstrap-switch-label {
margin-right: -4px;
margin-left: -4px;
}

View File

@ -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",