PHP Classes
elePHPant
Icontem

File: FrontEndScripts/Controllers/SiteEditorController.ts

Recommend this page to a friend!
  Classes of Aleksey Nemiro  >  PHP Small Server Administrator  >  FrontEndScripts/Controllers/SiteEditorController.ts  >  Download  
File: FrontEndScripts/Controllers/SiteEditorController.ts
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: PHP Small Server Administrator
Web panel for small Debian and Ubuntu servers
Author: By
Last change:
Date: 3 years ago
Size: 48,492 bytes
 

 

Contents

Class file image Download
?/*
 * Copyright  Aleksey Nemiro, 2016. All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
module SmallServerAdmin.Controllers {

  export class SiteEditorController implements Nemiro.IController {

    //#region ..properties.. 

    private LevelsList = ['Nginx', 'Apache', 'HTAN'];

    public Scope: any;
    public Context: Nemiro.AngularContext;

    /** SSA config. */
    public get Config(): Models.Config {
      return this.Scope.$parent.Config;
    }

    /** The site data. */
    public get Site(): Models.Site {
      return this.Scope.Site;
    }
    public set Site(value: Models.Site) {
      this.Scope.Site = value;
    }

    /**
     * The source site name.
     */
    public get SourceName(): string {
      return this.Scope.SourceName;
    }
    public set SourceName(value: string) {
      this.Scope.SourceName = value;
    }

    /** Loading indicator. */
    public get Loading(): boolean {
      return this.Scope.Loading;
    }
    public set Loading(value: boolean) {
      this.Scope.Loading = value;
    }

    public get Saving(): boolean {
      return this.Scope.Saving;
    }
    public set Saving(value: boolean) {
      this.Scope.Saving = value;
    }

    /** Reloading indicator. */
    public get Reloading(): boolean {
      return this.Scope.Reloading;
    }
    public set Reloading(value: boolean) {
      this.Scope.Reloading = value;
    }

    /** Gets or sets reloading items. */
    public get ReloadingItems(): Array<Models.ReloadingInfo> {
      return this.Scope.ReloadingItems;
    }
    public set ReloadingItems(value: Array<Models.ReloadingInfo>) {
      this.Scope.ReloadingItems = value;
    }

    /** Success result indicator. */
    public get Success(): boolean {
      return this.Scope.Success;
    }
    public set Success(value: boolean) {
      this.Scope.Success = value;
    }

    public get EditorChanged(): boolean {
      return this.Scope.EditorChanged;
    }
    public set EditorChanged(value: boolean) {
      this.Scope.EditorChanged = value;
      //console.log('EditorChanged', value);

      if (value) {
        var $this = this;
        this.Context.Timeout(() => {
          $this.Scope.EditorChanged = false;
          //console.log('EditorChanged', $this.Scope.EditorChanged);
        }, 500);
      }
    }

    /**
     * Selectec config to delete.
     */
    public get SelectecConfToDelete(): Models.SiteConf {
      return this.Scope.SelectecConfToDelete;
    }
    public set SelectecConfToDelete(value: Models.SiteConf) {
      this.Scope.SelectecConfToDelete = value;
    }

    public get CreateNew(): Models.SiteNew {
      return this.Scope.CreateNew;
    }
    public set CreateNew(value: Models.SiteNew) {
      this.Scope.CreateNew = value;
    }

    public get CreateNewNginx(): Models.SiteNewNginx {
      return this.Scope.CreateNewNginx;
    }
    public set CreateNewNginx(value: Models.SiteNewNginx) {
      this.Scope.CreateNewNginx = value;
    }

    public get CreateNewApache(): Models.SiteNewApache {
      return this.Scope.CreateNewApache;
    }
    public set CreateNewApache(value: Models.SiteNewApache) {
      this.Scope.CreateNewApache = value;
    }

    public get CreateNewHTAN(): Models.SiteNewHTAN {
      return this.Scope.CreateNewHTAN;
    }
    public set CreateNewHTAN(value: Models.SiteNewHTAN) {
      this.Scope.CreateNewHTAN = value;
    }

    public get ActiveTab(): any {
      return this.Scope.ActiveTab;
    }
    public set ActiveTab(value: any) {
      this.Scope.ActiveTab = value;
    }

    public get CanAddConf(): boolean {
      return this.Scope.CanAddConf;
    }
    public set CanAddConf(value: boolean) {
      this.Scope.CanAddConf = value;
    }

    public get Folders(): Array<Models.FileSystemItem> {
      return this.Scope.Folders;
    }
    public set Folders(value: Array<Models.FileSystemItem>) {
      this.Scope.Folders = value;
    }

    public get SelectedFolder(): Models.FileSystemItem {
      return this.Scope.SelectedFolder;
    }
    public set SelectedFolder(value: Models.FileSystemItem) {
      this.Scope.SelectedFolder = value;
    }

    public get SelectFolderTarget(): ISelectPath {
      return this.Scope.SelectFolderTarget;
    }
    public set SelectFolderTarget(value: ISelectPath) {
      this.Scope.SelectFolderTarget = value;
    }

    public get SelectedFolderToDelete(): Models.FileSystemItem {
      return this.Scope.SelectedFolderToDelete;
    }
    public set SelectedFolderToDelete(value: Models.FileSystemItem) {
      this.Scope.SelectedFolderToDelete = value;
    }

    public get NewFolderName(): string {
      return this.Scope.NewFolderName;
    }
    public set NewFolderName(value: string) {
      this.Scope.NewFolderName = value;
    }

    public get NewFolderOwnerName(): string {
      return this.Scope.NewFolderOwnerName;
    }
    public set NewFolderOwnerName(value: string) {
      this.Scope.NewFolderOwnerName = value;
    }

    public get CreationFolder(): boolean {
      return this.Scope.CreationFolder;
    }
    public set CreationFolder(value: boolean) {
      this.Scope.CreationFolder = value;
    }

    public get AvailableLevels(): Array<string> {
      return this.Scope.AvailableLevels;
    }
    public set AvailableLevels(value: Array<string>) {
      this.Scope.AvailableLevels = value;
    }

    public get SelectedConfToAdd(): string {
      return this.Scope.SelectedConfToAdd;
    }
    public set SelectedConfToAdd(value: string) {
      this.Scope.SelectedConfToAdd = value;
    }

    public get Help(): any {
      return this.Scope.Help;
    }
    public set Help(value: any) {
      this.Scope.Help = value;
    }

    private ConfirmDeleteConfDialog: Nemiro.UI.Dialog;
    private CreateConfigDialog: Nemiro.UI.Dialog;
    private SelectFolderDialog: Nemiro.UI.Dialog;
    private CreateFolderDialog: Nemiro.UI.Dialog;
    private ConfirmToDeleteFolder: Nemiro.UI.Dialog;
    private ReloadingDialog: Nemiro.UI.Dialog;

    //#endregion
    //#region ..constructor..

    constructor(context: Nemiro.AngularContext) {
      var $this = this;
      $this.Context = context;
      $this.Scope = $this.Context.Scope;
      $this.SelectedFolder = undefined;
      $this.Help = { IsOpened: false };

      $this.LevelsList = ['Nginx'];

      if ($this.Config.WebServer.indexOf('apache') != -1) {
        $this.LevelsList.push('Apache');
      }

      if ($this.Config.HtanEnabled) {
        $this.LevelsList.push('HTAN');
      }

      $this.ConfirmDeleteConfDialog = Nemiro.UI.Dialog.CreateFromElement($('#confirmToDeleteConf'));
      $this.ConfirmToDeleteFolder = Nemiro.UI.Dialog.CreateFromElement($('#confirmToDeleteFolder'));

      $this.CreateConfigDialog = Nemiro.UI.Dialog.CreateFromElement($('#createConfig'));
      $this.SelectFolderDialog = Nemiro.UI.Dialog.CreateFromElement($('#selectFolder'));
      $this.CreateFolderDialog = Nemiro.UI.Dialog.CreateFromElement($('#createFolder'));
      $this.CreateFolderDialog.DisableOverlayClose = true;

      $this.ReloadingDialog = Nemiro.UI.Dialog.CreateFromElement($('#reloading'));
      $this.ReloadingDialog.DisableOverlayClose = true;
      $this.ReloadingDialog.DisplayCloseButton = true;

      $this.Scope.Create = () => {
        $this.Create($this);
      }

      $this.Scope.ShowSelectFolder = (target: ISelectPath) => {
        $this.SelectFolderTarget = target;
        $this.Folders = new Array<Models.FileSystemItem>();
        $this.SelectedFolder = undefined;
        $this.GetFolders($this);
        $this.SelectFolderDialog.Show();
      }

      $this.Scope.CreateDefault = () => {
        $this.CreateDefault($this);
      }

      $this.Scope.ConfirmDeleteConf = (item: Models.SiteConf) => {
        $this.SelectecConfToDelete = item;
        $this.ConfirmDeleteConfDialog.Show();
      }

      $this.Scope.DeleteConf = () => {
        $this.DeleteConf($this);
      }

      $this.Scope.SelectConfToAdd =  (level: string) => {
        $this.SelectedConfToAdd = level;
      }

      $this.Scope.PrepareAddConf = () => {
        $this.PrepareAddConf($this);
      }

      $this.Scope.ReinitCurrentConf = () => {
        if ($this.SelectedConfToAdd === undefined || $this.SelectedConfToAdd == null || $this.SelectedConfToAdd == '') {
          return;
        }

        $this['CreateNew' + $this.SelectedConfToAdd] = null;
        $this.PrepareAddConf($this);
      }

      $this.Scope.AddConf = () => {
        $this.AddConf($this);
      }

      $this.Scope.ConfEnabledClick = (item: Models.SiteConf) => {
        //console.log('ConfEnabledClick', item);
        item.Enabled = !item.Enabled;
      }

      $this.Scope.SelectNginxEventLog =  (value: number) => {
        $this.CreateNewNginx.EventLogs = value;
      }

      $this.Scope.SelectApacheEventLog =  (value: number) => {
        $this.CreateNewApache.EventLogs = value;
      }

      $this.Scope.TabClick = (tab: string) => {
        $this.ActiveTab = {};
        $this.ActiveTab[tab] = true;
        $this.EditorChanged = true;
      }

      $this.Scope.ToggleFolder = (node: Models.FileSystemItem, expanded) => {
        //console.log('ToggleFolder', node, expanded);
        if (expanded && (node.Children == undefined || node.Children == null)) {
          if (node.Loading === undefined || !node.Loading) {
            $this.GetFolders($this, node);
          }
        } else if (!expanded) {
          node.Children = null;
        }
      }

      $this.Scope.ShowSelectedFolder = (node: Models.FileSystemItem, selected, $parentNode: Models.FileSystemItem) => {
        $this.SelectedFolder = node;
        $this.SelectedFolder.Parent = $parentNode;
        //$this.CreateNew.RootPath = node.Path;
      }

      $this.Scope.SelectFolderOptions = {
        isLeaf: (node: Models.FileSystemItem) => {
          return false;
        },
        isSelectable: (node: Models.FileSystemItem) => {
          return node.Loading === undefined || !node.Loading;
        },
        nodeChildren: 'Children'
      };

      $this.Scope.ShowCreateFolder = () => {
        //$this.SelectFolderDialog.Close();
        $this.NewFolderName = '';
        $this.CreateFolderDialog.Show();
      }

      $this.Scope.CreateFolder = () => {
        $this.CreateFolder($this);
      }

      $this.Scope.RenameFolder = (node: Models.FileSystemItem) => {
        $this.RenameFolder($this, node);
      }

      $this.Scope.ShowConfirmToDeleteFolder = (node: Models.FileSystemItem) => {
        $this.SelectedFolderToDelete = node;
        $this.ConfirmToDeleteFolder.Show();
      }

      $this.Scope.DeleteFolder = () => {
        $this.DeleteFolder($this);
      }

      $this.Scope.SelectPath = () => {
        $this.SelectFolderTarget.SelectedPath = $this.SelectedFolder.Path;
        $this.SelectFolderDialog.Close();
      }

      $this.Scope.Save = (needReloading: boolean) => {
        $this.Save($this, needReloading);
      }

      $this.Scope.SiteStatusChanged = () => {
        console.log('SiteStatusChanged');
        if ($this.Site.IsEnabled && $this.AvailableLevels.indexOf('Nginx') == -1) {
          for (var i = 0; i < $this.Site.Conf.length; i++) {
            if ($this.Site.Conf[i].Level == 'Nginx') {
              $this.Site.Conf[i].Enabled = true;
              return;
            }
          }
        }
      }
      // load site
      $this.Load($this);
    }

    //#endregion
    //#region ..methods..

    /**
     * Loads site data from the server.
     */
    private Load($this: SiteEditorController): void {
      $this = $this || this;
      $this.Loading = true;

      if ($this.Context.Location.search().name === undefined || $this.Context.Location.search().name == null || $this.Context.Location.search().name == '') {
        $this.Loading = false;
        $this.CreateNew = new Models.SiteNew();
        $this.CreateConfigDialog.Show();
        return;
      }

      $this.CreateNew = null;

      // create request
      var apiRequest = new ApiRequest<Models.Site>($this.Context, 'Sites.GetSite', { name: $this.Context.Location.search().name });

      // handler successful response to a request to api
      apiRequest.SuccessCallback = (response) => {
        $this.Scope.TabClick(response.data.Conf[0].Level);
        $this.Site = response.data;
        $this.SourceName = $this.Site.Name;
        $this.UpdateCanAddConf($this);
      };

      apiRequest.CompleteCallback = () => {
        $this.Loading = false;
      }

      // execute
      apiRequest.Execute();
    }

    private DeleteConf($this: SiteEditorController): void {
      $this.Site.Levels.splice($this.Site.Levels.indexOf($this.SelectecConfToDelete.Level), 1);
      $this.Site.Conf.splice($this.Site.Conf.indexOf($this.SelectecConfToDelete), 1);
      $this.ConfirmDeleteConfDialog.Close();
      $this.SelectecConfToDelete = null;
      $this.UpdateCanAddConf($this);
    }

    private Create($this: SiteEditorController): void {
      console.log('Create');

      $this.Site = new Models.Site();

      $this.Site.Name = $this.CreateNew.Domain;
      $this.Site.IsEnabled = true;
      $this.Site.Levels = new Array<string>();
      $this.Site.Conf = new Array<Models.SiteConf>();

      // nginx
      $this.SelectedConfToAdd = 'Nginx';
      $this.CreateNewNginx = new Models.SiteNewNginx();
      $this.CreateNewNginx.Domain = $this.CreateNew.Domain;
      $this.CreateNewNginx.SelectedPath = $this.CreateNew.SelectedPath;
      $this.CreateNewNginx.PhpMode = $this.CreateNew.PhpMode;
      $this.CreateNewNginx.AspNetMode = $this.CreateNew.AspNetMode;
      $this.CreateNewNginx.AspNetFastCgiProcessCount = $this.CreateNew.AspNetFastCgiProcessCount;
      $this.CreateNewNginx.AspNetSocket = $this.CreateNew.AspNetSocket;

      $this.AddConf($this);

      // apache
      if ($this.CreateNew.AspNetMode == 'MOD' || $this.CreateNew.PhpMode == 'MOD') {
        $this.SelectedConfToAdd = 'Apache';
        $this.CreateNewApache = new Models.SiteNewApache();
        $this.CreateNewApache.Domain = $this.CreateNew.Domain;
        $this.CreateNewApache.SelectedPath = $this.CreateNew.SelectedPath;

        if ($this.CreateNew.AspNetMode != 'Off') {
          $this.CreateNewApache.AspNetVersion = $this.CreateNew.AspNetVersion;
          $this.CreateNewApache.AspNetIOMapForAll = $this.CreateNew.AspNetIOMapForAll;
          $this.CreateNewApache.AspNetDebug = $this.CreateNew.AspNetDebug;
        }

        $this.AddConf($this);
      }

      // htan
      if ($this.CreateNew.AspNetMode == 'FASTCGI') {
        $this.SelectedConfToAdd = 'HTAN';
        $this.CreateNewHTAN = new Models.SiteNewHTAN();
        $this.CreateNewHTAN.Domain = $this.CreateNew.Domain;
        $this.CreateNewHTAN.SelectedPath = $this.CreateNew.SelectedPath;
        $this.CreateNewHTAN.FastCGI = new Array<Models.SiteHTANItem>();

        var hostParts = $this.CreateNew.Domain.split('.');
        if (hostParts.length > 1) { hostParts.pop(); }
        var hostFirst = hostParts.join('.');
        var hostLast = (hostParts = $this.CreateNew.Domain.split('.'))[hostParts.length - 1];
        var address = Nemiro.Utility.Replace(hostFirst, '\\.', '_') + '_aspnet';

        if ($this.CreateNew.AspNetFastCgiProcessCount == 1) {
          if ($this.CreateNew.AspNetSocket == 'TCP') {
            address = '127.0.0.1:' + $this.Config.AspNetFastCgiPort;
          } else {
            address = 'unix:/tmp/' + $this.CreateNew.Domain;
          }
        }

        var item = new Models.SiteHTANItem(address);
        item.AspNetVersion = $this.CreateNew.AspNetVersion;
        item.Enabled = true;
        item.Socket = address;
        $this.CreateNewHTAN.FastCGI.push(item);

        $this.AddConf($this);
      }

      $this.Scope.TabClick('Nginx');

      $this.CreateConfigDialog.Close();
    }

    private CreateDefault($this: SiteEditorController): void {
      $this.Site = new Models.Site();
      $this.Site.Name = 'New site';
      $this.Site.IsEnabled = true;
      $this.Site.Levels = new Array<string>();
      $this.Site.Conf = new Array<Models.SiteConf>();

      // nginx
      $this.SelectedConfToAdd = 'Nginx';
      $this.CreateNewNginx = new Models.SiteNewNginx();
      $this.CreateNewNginx.Domain = $this.CreateNew.Domain;
      $this.CreateNewNginx.SelectedPath = $this.CreateNew.SelectedPath;
      $this.CreateNewNginx.PhpMode = $this.CreateNew.PhpMode;
      $this.CreateNewNginx.AspNetMode = $this.CreateNew.AspNetMode;
      $this.CreateNewNginx.AspNetFastCgiProcessCount = $this.CreateNew.AspNetFastCgiProcessCount;
      $this.CreateNewNginx.AspNetSocket = $this.CreateNew.AspNetSocket;

      $this.AddConf($this);

      $this.Scope.TabClick('Nginx');
    }

    private GetFolders($this: SiteEditorController, parent?: Models.FileSystemItem): void {
      if ($this.Folders === undefined || $this.Folders == null || $this.Folders.length <= 0) {
        $this.Loading = true;
      }

      var path = '/';

      if (parent !== undefined && parent != null) {
        path = parent.Path;
        parent.Loading = true;
      }
      
      // create request
      var apiRequest = new ApiRequest<Array<Models.FileSystemItem>>($this.Context, 'Sites.GetFolders', { path: path });

      // handler successful response to a request to api
      apiRequest.SuccessCallback = (response) => {
        //$this.Context.Timeout(() => {
          if (parent === undefined || parent == null) {
            $this.Folders = response.data;
          } else {
            parent.Children = response.data;
          }
        //});
      };

      apiRequest.CompleteCallback = () => {
        $this.Loading = false;
        if (parent !== undefined && parent != null) {
          parent.Loading = false;
        }
      }

      // execute
      apiRequest.Execute();
    }

    private CreateFolder($this: SiteEditorController): void {
      $this.CreationFolder = true;

      // create request
      var apiRequest = new ApiRequest<any>($this.Context, 'Sites.CreateFolder', { path: $this.SelectedFolder.Path, name: $this.NewFolderName, owner: $this.NewFolderOwnerName });

      // handler successful response to a request to api
      apiRequest.SuccessCallback = (response) => {
        $this.GetFolders($this, $this.SelectedFolder);
        $this.CreateFolderDialog.Close();
      };

      apiRequest.CompleteCallback = () => {
        $this.CreationFolder = false;
      }

      // execute
      apiRequest.Execute();

    }

    private RenameFolder($this: SiteEditorController, node: Models.FileSystemItem): void {
      node.Loading = true;

      // create request
      var apiRequest = new ApiRequest<any>($this.Context, 'Sites.RenameFolder', { path: node.Path, name: node.NewName });

      // handler successful response to a request to api
      apiRequest.SuccessCallback = (response) => {
        node.Path = response.data.Path;
        node.Name = node.NewName;
        node.RenameMode = false;
      };

      apiRequest.CompleteCallback = () => {
        node.Loading = false;
      }

      // execute
      apiRequest.Execute();
    }

    private DeleteFolder($this: SiteEditorController): void {
      $this.SelectedFolderToDelete.Loading = true;

      // create request
      var apiRequest = new ApiRequest<any>($this.Context, 'Sites.DeleteFolder', { path: $this.SelectedFolderToDelete.Path });

      // handler successful response to a request to api
      apiRequest.SuccessCallback = (response) => {
        $this.ConfirmToDeleteFolder.Close();
        $this.GetFolders($this, $this.SelectedFolderToDelete.Parent);
      };

      apiRequest.CompleteCallback = () => {
        $this.SelectedFolderToDelete.Loading = false;
      }

      // execute
      apiRequest.Execute();
    }

    private Save($this: SiteEditorController, needReloading: boolean): void {
      if (Nemiro.Utility.NextInvalidField($('#siteForm'))) {
        return;
      }

      $this.Saving = true;

      // create request
      var apiRequest = new ApiRequest<any>($this.Context, 'Sites.SaveSite', { 'SourceName': $this.SourceName, 'Site': $this.Site, 'IsNew': $this.CreateNew != null });
      
      // handler successful response to a request to api
      apiRequest.SuccessCallback = (response) => {
        $this.Context.Window.location.hash = '#?name=' + $this.Site.Name;
        $this.CreateNew = null;
        $this.Success = true;

        // reloading services
        if (needReloading) {
          // get services to reload
          $this.ReloadingItems = new Array<Models.ReloadingInfo>();

          for (var i = 0; i < $this.Site.Conf.length; i++) {
            $this.ReloadingItems.push(new Models.ReloadingInfo($this.Site.Conf[i].Level));

            if ($this.Site.Conf[i].Level == 'Nginx') {
              if ($this.Site.Conf[i].Source.indexOf('php') != -1) {
                $this.ReloadingItems.push(new Models.ReloadingInfo('PHP-FPM'));
              }
            }
          }

          $this.ReloadServices($this);
        } else {
          $this.Context.Timeout(() => {
            $this.Load($this);
          });
        }
      };

      apiRequest.CompleteCallback = () => {
        $this.Saving = false;
      }

      // execute
      apiRequest.Execute();
    }

    private ReloadServices($this: SiteEditorController): void {
      if ($this.ReloadingItems === undefined || $this.ReloadingItems == null || $this.ReloadingItems.length <= 0) {
        Nemiro.UI.Dialog.Alert(App.Resources.NoServicesToReload, App.Resources.Error);
        return;
      }

      $this.Reloading = true;
      $this.ReloadingDialog.Show();

      for (var i = 0; i < $this.ReloadingItems.length; i++) {
        $this.ReloadService($this, $this.ReloadingItems[i]);
      }
    }

    private ReloadService($this: SiteEditorController, reloadingItem: Models.ReloadingInfo): void {
      if (reloadingItem.Status != '' && reloadingItem.Status != 'Waiting') {
        console.log('Service "' + reloadingItem.Name + '" already processing.');
        return;
      }

      console.log('Reloading "' + reloadingItem.Name + '"...');

      reloadingItem.Status = 'Processing';

      // create request
      var apiRequest = new ApiRequest<any>($this.Context, 'Sites.ReloadServices', { 'services': reloadingItem.Name });

      // handler successful response to a request to api
      apiRequest.SuccessCallback = (response) => {
        reloadingItem.Status = 'Success';
      };

      apiRequest.ErrorCallback = (response) => {
        if (response.status == 502 || response.status == 504) {
          $this.Context.Timeout($this.WaitingServerResponse($this, reloadingItem), 1000);
        } else {
          var exceptionMessage = ApiRequest.GetExceptionMessage((<any>response).data);

          if (exceptionMessage != null) {
            exceptionMessage = '<pre>' + exceptionMessage + '</pre>';
          } else {
            exceptionMessage = '';
          }

          Nemiro.UI.Dialog.Alert(Nemiro.Utility.Format(App.Resources.CannotReloadService, [reloadingItem.Name]) + exceptionMessage, App.Resources.Error + ' ' + response.status);
          reloadingItem.Status = 'Error';
        }
      }

      apiRequest.CompleteCallback = () => {
        $this.UpdateReloadingStatus($this);
      }

      // execute
      apiRequest.Execute();
    }

    private WaitingServerResponse($this: SiteEditorController, reloadingItem: Models.ReloadingInfo): any {
      ApiRequest.Echo($this.Context, (response) => {
        if (response.status != 200) {
          Nemiro.UI.Dialog.Alert(Nemiro.Utility.Format(App.Resources.CannotReloadService, [reloadingItem.Name]), App.Resources.Error + ' ' + response.status);
          reloadingItem.Status = 'Error';
        } else {
          reloadingItem.Status = 'Success';
        }

        $this.UpdateReloadingStatus($this);
      }, 5);
    }

    private UpdateReloadingStatus($this: SiteEditorController): void {
      for (var i = 0; i < $this.ReloadingItems.length; i++) {
        if ($this.ReloadingItems[i].Status == '' || $this.ReloadingItems[i].Status == 'Waiting' || $this.ReloadingItems[i].Status == 'Processing') {
          $this.Reloading = true;
          return;
        }
      }

      $this.Reloading = false;
    }

    private PrepareAddConf($this: SiteEditorController): void {
      console.log('PrepareAddConf', $this.AvailableLevels);

      var host = '%DOMAIN HERE%';
      var path = '%PATH HERE%';

      if ($this.CreateNewNginx === undefined || $this.CreateNewNginx == null) {
        if ($this.AvailableLevels.indexOf('Apache') == -1) {
          // has apache, extract host and root path
          var r_host = /ServerName(\s+)(\S+)/i;
          var r_root = /DocumentRoot(\s+)(\S+)/i;

          var conf = $this.GetConf($this, 'Apache');

          if (conf != null) {
            var arr = r_host.exec(conf);

            if (arr != null && arr.length >= 2 && arr[2] != '') {
              host = arr[2];
            }

            arr = r_root.exec(conf);

            if (arr != null && arr.length >= 2 && arr[2] != '') {
              path = arr[2];
            }
          }
        }

        if (host == '%DOMAIN HERE%' || host == '') {
          host = $this.Site.Name;
        }

        $this.CreateNewNginx = new Models.SiteNewNginx();
        $this.CreateNewNginx.Domain = host;
        $this.CreateNewNginx.SelectedPath = path;
      }

      if ($this.CreateNewApache === undefined || $this.CreateNewApache == null) {
        if ($this.AvailableLevels.indexOf('Nginx') == -1) {
          // has nginx, extract host and root path
          var r_host = /server_name(\s+)([^;]+)/i;
          var r_root = /root(\s+)([^;]+)/i;

          var conf = $this.GetConf($this, 'Nginx');

          if (conf != null) {
            // masked \#
            conf = conf.replace(/(\\\#)/g, String.fromCharCode(1));
            // remove comments
            conf = conf.replace(/(\#([^\r\n]*))/g, '');
            // restore \#
            conf = conf.replace(/\x01/g, '\#');

            var arr = r_host.exec(conf);

            if (arr != null && arr.length >= 2 && arr[2] != '') {
              host = arr[2];
            }

            arr = r_root.exec(conf);

            if (arr != null && arr.length >= 2 && arr[2] != '') {
              path = arr[2];
            }
          } 
        }

        if (host == '%DOMAIN HERE%' || host == '') {
          host = $this.Site.Name;
        }

        $this.CreateNewApache = new Models.SiteNewApache();
        $this.CreateNewApache.Domain = host;
        $this.CreateNewApache.SelectedPath = path;
      }

      if ($this.CreateNewHTAN === undefined || $this.CreateNewHTAN == null) {
        var fastCgiList = new Array<Models.SiteHTANItem>();

        if ($this.AvailableLevels.indexOf('Nginx') == -1) {
          var r_host = /server_name(\s+)([^;]+)/i;
          var r_root = /root(\s+)([^;]+)/i;
          var r_fastcgi_pass = /fastcgi_pass(\s+)([^;]+)/gi;
          var r_fastcgi_pass_sgl = /fastcgi_pass(\s+)([^;]+)/i;

          var conf = $this.GetConf($this, 'Nginx');
          console.log('conf', conf);
          if (conf != null) {
            // masked \#
            conf = conf.replace(/(\\\#)/g, String.fromCharCode(1));
            // remove comments
            conf = conf.replace(/(\#([^\r\n]*))/g, '');
            // restore \#
            conf = conf.replace(/\x01/g, '\#');

            var arr = r_host.exec(conf);

            if (arr != null && arr.length >= 2 && arr[2] != '') {
              host = arr[2];
            }

            arr = r_root.exec(conf);

            if (arr != null && arr.length >= 2 && arr[2] != '') {
              path = arr[2];
            }

            var arr2 = conf.match(r_fastcgi_pass);
            if (arr2 != null) {
              for (var i = 0; i < arr2.length; i++) {
                arr = r_fastcgi_pass_sgl.exec(arr2[i]);
                // console.log('fastcgi_pass', i, arr2[i], arr);
                fastCgiList.push(new Models.SiteHTANItem(arr[2]));
              }
            }

            console.log('fastCgiList', fastCgiList);
          }
        }

        if (host == '%DOMAIN HERE%' || host == '') {
          host = $this.Site.Name;
        }

        $this.CreateNewHTAN = new Models.SiteNewHTAN();
        $this.CreateNewHTAN.Domain = host;
        $this.CreateNewHTAN.SelectedPath = path;
        $this.CreateNewHTAN.FastCGI = fastCgiList;
      }

      // only one available conf
      if ($this.AvailableLevels.length == 1) {
        // select it
        $this.SelectedConfToAdd = $this.AvailableLevels[0];
      } else {
        if ($this.SelectedConfToAdd !== undefined && $this.SelectedConfToAdd != null && $this.SelectedConfToAdd != '' && $this.AvailableLevels.indexOf($this.SelectedConfToAdd) == -1) {
          $this.SelectedConfToAdd = '';
        }
      }
    }

    private AddConf($this: SiteEditorController): void {
      console.log('AddConf', $this.SelectedConfToAdd, $this['CreateNew' + $this.SelectedConfToAdd]);

      if ($this.SelectedConfToAdd == 'Nginx') {
        var logPath = Nemiro.Utility.DirectoryName($this.CreateNewNginx.SelectedPath) + '/' + $this.Config.LogFolderName;

        var hostParts = $this.CreateNewNginx.Domain.split('.');
        if (hostParts.length > 1) { hostParts.pop(); }
        var hostFirst = hostParts.join('.');
        var hostLast = (hostParts = $this.CreateNewNginx.Domain.split('.'))[hostParts.length - 1];

        var upstreams = '';

        var conf = 'server {\n';
        conf += `  root ${$this.CreateNewNginx.SelectedPath};` + '\n';
        conf += `  server_name ${$this.CreateNewNginx.Domain};` + '\n';

        if (($this.CreateNewNginx.EventLogs & 1) == 1) {
          conf += `  error_log ${logPath}/nginx_${$this.CreateNewNginx.Domain}_error.log error` + '\n';
        }

        if (($this.CreateNewNginx.EventLogs & 2) == 2) {
          conf += `  access_log ${logPath}/nginx_${$this.CreateNewNginx.Domain}_access.log` + '\n';
        }

        //conf += '\n';

        //conf += '  location / {\n';
        conf += '  index ';

        if ($this.CreateNewNginx.PhpMode != 'Off') {
          conf += 'index.php ';
        }

        if ($this.CreateNewNginx.AspNetMode != 'Off') {
          conf += 'Default.aspx Index.aspx ';
        }

        conf += 'index.html index.htm;\n';
        //conf += '  }\n\n';
        conf += '\n';

        if ($this.CreateNewNginx.PhpMode != 'Off') {
          // PHP
          var upstreamName = Nemiro.Utility.Replace(hostFirst, '\\.', '_') + '_php';

          conf += $this.MakeNginxPhpConf
          (
            $this.CreateNewNginx.Domain,
            $this.CreateNewNginx.PhpMode,
            $this.CreateNewNginx.PhpFastCgiProcessCount,
            $this.CreateNewNginx.PhpSocket,
            upstreamName,
            $this.Config.PhpFastCgiPort
          );
          conf += '\n';

          if ($this.CreateNewNginx.PhpMode == 'FPM' && $this.CreateNewNginx.PhpFastCgiProcessCount > 1) {
            if (upstreams.length > 0) {
              upstreams += '\n';
            }

            upstreams += $this.MakeNginxUpstream
            (
              upstreamName,
              $this.CreateNewNginx.PhpFastCgiProcessCount,
              $this.CreateNewNginx.PhpSocket,
              (hostFirst + '-{0}.' + hostLast),
              $this.Config.PhpFastCgiPort
            );
          }
        }

        if ($this.CreateNewNginx.AspNetMode != 'Off') {
          // ASP.NET
          var upstreamName = Nemiro.Utility.Replace(hostFirst, '\\.', '_') + '_aspnet';

          conf += $this.MakeNginxAspNetConf
          (
            $this.CreateNewNginx.Domain,
            $this.CreateNewNginx.AspNetMode,
            $this.CreateNewNginx.AspNetFastCgiProcessCount,
            $this.CreateNewNginx.AspNetSocket,
            upstreamName,
            $this.Config.AspNetFastCgiPort
          );
          conf += '\n';

          if ($this.CreateNewNginx.AspNetMode == 'FASTCGI' && $this.CreateNewNginx.AspNetFastCgiProcessCount > 1) {
            if (upstreams.length > 0) {
              upstreams += '\n';
            }

            upstreams += $this.MakeNginxUpstream
            (
              upstreamName,
              $this.CreateNewNginx.AspNetFastCgiProcessCount,
              $this.CreateNewNginx.AspNetSocket,
              (hostFirst + '-{0}.' + hostLast),
              $this.Config.AspNetFastCgiPort
            );
          }
        }

        // other
        conf += '  # Static files caching\n';
        conf += '  location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {\n';
        conf += '    expires max;\n';
        conf += '    log_not_found off;\n';
        conf += '    access_log off;\n';
        conf += '  }\n\n';

        /*
        if ($this.CreateNewNginx.PhpMode != 'Off') {
          conf += '  # Disable viewing .htaccess and .htpassword\n';
          conf += '  location ~ /\.ht {\n';
          conf += '    deny all;\n';
          conf += '  }\n\n';
        }
        */
        /*
        if ($this.CreateNewNginx.AspNetMode != 'Off') {
          conf += '  # disable viewing .config\n';
          conf += '  location ~ /\.config {\n';
          conf += '    deny all;\n';
          conf += '  }\n\n';
        }*/
        // --

        conf += '}';

        if (upstreams.length > 0) {
          conf = upstreams + '\n' + conf;
        }

        $this.Site.Conf.push(new Models.SiteConf('Nginx', conf));
        $this.Site.Levels.push('Nginx');

        $this.CreateNewNginx = null;
      }
      else if ($this.SelectedConfToAdd == 'Apache') {
        var logPath = Nemiro.Utility.DirectoryName($this.CreateNewApache.SelectedPath) + '/' + $this.Config.LogFolderName;
        var conf = `<VirtualHost ${$this.Config.ApacheHost}:${$this.Config.ApachePort}>` + '\n';
        conf += `  DocumentRoot ${$this.CreateNewApache.SelectedPath}` + '\n';
        conf += `  ServerName ${$this.CreateNewApache.Domain}` + '\n';

        if (($this.CreateNewApache.EventLogs & 1) == 1) {
          conf += `  ErrorLog ${logPath}/apache_${$this.CreateNewApache.Domain}_error.log error` + '\n';
        }

        if (($this.CreateNewApache.EventLogs & 2) == 2) {
          conf += `  CustomLog ${logPath}/apache_${$this.CreateNewApache.Domain}_access.log` + ' common\n';
        }

        if ($this.CreateNewApache.AspNetVersion != 'Off') {
          conf += '\n';
          conf += $this.MakeApacheAspNetHeaders($this.CreateNewApache.AspNetVersion, $this.CreateNewApache.Domain, $this.CreateNewApache.SelectedPath, $this.CreateNewApache.AspNetIOMapForAll, $this.CreateNewApache.AspNetDebug);
          conf += '\n';
          conf += $this.MakeApacheAspNetLocation($this.CreateNewApache.Domain);
        }
        else {
          conf += '\n';
          conf += $this.MakeApacheDefaultLocation();
        }

        // mono-ctrl
        if ($this.CreateNewApache.MonoCtrl) {
          conf += '\n';
          conf += '  <Location /mono>\n';
          conf += '    SetHandler mono-ctrl\n';
          conf += '    AllowOverride All\n';
          conf += '    Order Allow,Deny\n';
          conf += '    Allow from all\n';
          conf += '    Require all granted\n';
          conf += '  </Location>\n';
        }

        // svn
        if ($this.CreateNewApache.WebDavSvn) {
          conf += '\n';
          conf += '  # You can specify custom home directory of site\n';
          conf += '  # for browse list of repositories:\n';
          conf += '  # <Directory /usr/share/svn-web>\n';
          conf += '  #  AllowOverride All\n';
          conf += '  #  Allow from all\n';
          conf += '  #  Require all granted\n';
          conf += '  # </Directory>\n\n';

          conf += '  <Location /svn>\n';
          conf += '    DAV svn\n';
          conf += '    # Enter the path where your repositories are located:\n';
          conf += '    SVNParentPath /var/svn/\n';
          conf += '    SVNListParentPath On\n';
          conf += '    # You can make your own styles for browsing a repositories:\n';
          conf += '    # SVNIndexXSLT /usr/share/svn-web/index.xsl\n\n';

          conf += '    # If you want to restrict access, you can enable authorization:\n';
          conf += '    # AuthType Basic\n';
          conf += '    # AuthName "Subversion Repository"\n';
          conf += '    # Enter the path to the password file\n';
          conf += '    # AuthUserFile /etc/apache2/dav_svn.passwd\n';
          conf += '    # Require valid-user\n\n';

          conf += '    # Specify a file of access rules to the repositories:\n';
          conf += '    # <IfModule mod_authz_svn.c>\n';
          conf += '    #  AuthzSVNAccessFile /etc/apache2/dav_svn.authz\n';
          conf += '    # </IfModule>\n';
          conf += '  </Location>\n';
        }

        conf += '</VirtualHost>';

        $this.Site.Conf.push(new Models.SiteConf('Apache', conf));
        $this.Site.Levels.push('Apache');

        $this.CreateNewApache = null;
      }
      else if ($this.SelectedConfToAdd == 'HTAN') {
        // get nginx config for parse upstreams
        var nginx = $this.GetConf($this, 'Nginx');
        if (nginx != null) {
          // masked \#
          nginx = nginx.replace(/(\\\#)/g, String.fromCharCode(1));
          // remove comments
          nginx = nginx.replace(/(\#([^\r\n]*))/g, '');
          // restore \#
          nginx = nginx.replace(/\x01/g, '\#');
        }
        // --

        // make htan config
        var commands = '', items = '';
        var commandKyes = [], commandNames = [];
        for (var i = 0; i < $this.CreateNewHTAN.FastCGI.length; i++) {
          var item = $this.CreateNewHTAN.FastCGI[i];
          if (!item.Enabled) {
            continue;
          }

          // make command key and name
          var key = item.AspNetVersion + ';' + (item.User != 'root' ? item.User : '') + ';' + (item.Group != 'root' ? item.Group : '');
          var name = 'command-' + (commandKyes.length + 1); // name for new command

          // search command by key
          if (commandKyes.indexOf(key) != -1) {
            // name for existing command
            name = commandNames[commandKyes.indexOf(key)];
          }

          // make items
          if (item.Socket.indexOf(':') == -1) {
            // is upstream name, parse nginx
            if (nginx != null) {
              // extract upstream
              var upstream = new RegExp('upstream(\\s+)' + item.Socket + '(\\s*)\\{([^\\}]+?)\\}', 'm').exec(nginx);
              if (upstream == null) {
                if (items.length > 0) { items += '\n'; }
                items += '    <!--Error: Upstream "' + item.Socket + '" not found in the Nginx config-->';
              } else {
                // parse addresses
                var arr = upstream[3].match(/server(\s+)([^\s\;]+)/g);
                if (arr == null) {
                  items += '    <!--Error: Cannot parse upstream "' + item.Socket + '"-->';
                } else {
                  for (var j = 0; j < arr.length; j++) {
                    if (items.length > 0) { items += '\n'; }
                    items += '    <add address="' + /server(\s+)([^\s\;]+)/.exec(arr[j])[2] + '" command="' + name + '" />';
                  }
                }
              }
            } else {
              if (items.length > 0) { items += '\n'; }
              items += '    <!--Error: Nginx is required for upstream-->';
            }
          }
          else {
            // single address
            if (items.length > 0) { items += '\n'; }
            items += '    <add address="' + item.Socket + '" command="' + name + '" />';
          }

          if (commandKyes.indexOf(key) != -1) {
            continue;
          }

          // make command
          if (commands.length > 0) { commands += '\n'; }

          commands += '    <add\n';
          commands += '      name="' + name + '"\n';

          if (item.User != '') {
            commands += '      user="' + item.User + '"\n';
          }

          if (item.Group != '') {
            commands += '      group="' + item.Group + '"\n';
          }

          var exec = '/usr/bin/fastcgi-mono-server4';

          if (item.AspNetVersion == '1.0') {
            exec = '/usr/bin/fastcgi-mono-server';
          }
          else if (item.AspNetVersion == '2.0') {
            exec = '/usr/bin/fastcgi-mono-server2';
          }

          commands += '      exec="' + exec + '"\n';
          commands += `      arguments="/applications=/:${$this.CreateNewHTAN.SelectedPath} /socket={socket} /multiplex=True /verbose=True"` + '\n'; //  /printlog=True

          commands += '    />';

          // add command key and name to existing commands
          commandNames.push(name);
          commandKyes.push(key);
        }

        var conf = '';
        conf += '<configuration>\n';
        conf += '  <fastCGI>\n';
        conf += items + '\n';
        conf += '  </fastCGI>\n';
        conf += '  <commands>\n';
        conf += commands + '\n';
        conf += '  </commands>\n';
        conf += '</configuration>';

        $this.Site.Conf.push(new Models.SiteConf('HTAN', conf));
        $this.Site.Levels.push('HTAN');

        $this.CreateNewHTAN = null;
      }

      $this.UpdateCanAddConf($this);

      $this.Scope.TabClick($this.Site.Conf[$this.Site.Conf.length - 1].Level);
    }

    private MakeNginxUpstream(name: string, processes: number, socket: string, unixName: string, port: number): string {
      var result = 'upstream ' + name + ' {';

      for (var i = 0; i < processes; i++) {
        if (result.length > 0) {
          result += '\n';
        }

        if (socket == 'TCP') {
          result += '  server 127.0.0.1:' + (port + i) + ';';
        } else {
          result += '  server unix:/tmp/' + Nemiro.Utility.Format(unixName, [i + 1]) + ';';
        }
      }
      result += '\n}\n';

      return result;
    }

    private MakeNginxPhpConf(host: string, mode: string, processes: number, socket: string, upstream: string, port: number): string {
      var conf = '';

      if (mode == 'FPM') {
        conf += '  # PHP-FPM\n';
        conf += '  location ~ \\.php$ {\n';
        if (processes == 1) {
          if (socket == 'TCP') {
            conf += '    fastcgi_pass  127.0.0.1:' + port.toString() + ';\n';
          } else {
            conf += '    fastcgi_pass  unix:/var/run/php5-fpm.sock;\n'; //TODO //unix:/tmp/' + host + ';\n';
          }
        } else {
          conf += '    fastcgi_pass  ' + upstream + ';\n';
        }
        
        conf += '    fastcgi_index index.php;\n\n';
        conf += '    include       fastcgi_params;\n\n';
        conf += '    fastcgi_split_path_info ^(.+\\.php)(/.+)$;\n';
        conf += '    fastcgi_param PATH_INFO       $fastcgi_path_info;\n';
        conf += '    fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;\n';
        conf += '    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n';
        conf += '  }\n';
      } else {
        conf += '  # PHP to Apache\n';
        conf += '  location ~ \\.php$ {\n';
        conf += '    proxy_set_header X-Real-IP  $remote_addr;\n';
        conf += '    proxy_set_header X-Forwarded-For $remote_addr;\n';
        conf += '    proxy_set_header Host $host;\n';
        conf += `    proxy_pass http://${this.Config.ApacheHost}:${this.Config.ApachePort};` + '\n';
        conf += '  }\n';
      }

      return conf;
    }

    private MakeNginxAspNetConf(host: string, mode: string, processes: number, socket: string, upstream: string, port: number): string {
      var conf = '';
      if (mode == 'FASTCGI') {
        conf += '  # ASP.NET FastCGI\n';
        conf += '  location / {\n'; // ~ \.(aspx|asmx|ashx|axd|asax|ascx|cshtml|vbhtml|soap|rem|axd|cs|vb|config|dll)$

        //conf += '    proxy_set_header X-Real-IP  $remote_addr;\n';
        //conf += '    proxy_set_header X-Forwarded-For $remote_addr;\n';

        if (processes == 1) {
          if (socket == 'TCP') {
            conf += '    fastcgi_pass  127.0.0.1:' + port.toString() + ';\n';
          } else {
            conf += '    fastcgi_pass  unix:/tmp/' + host + ';\n';
          }
        } else {
          conf += '    fastcgi_pass  ' + upstream + ';\n';
        }
        
        conf += '    include       fastcgi_params;\n';
        conf += '    # fastcgi_intercept_errors on; # disable asp.net error handlers\n';
        conf += '    fastcgi_param PATH_INFO $fastcgi_path_info;\n';
        conf += '    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n';
        conf += '  }\n';
      } else {
        conf += '  # ASP.NET to Apache\n';
        conf += '  location / {\n'; // ~ \.(aspx|asmx|ashx|axd|asax|ascx|cshtml|vbhtml|soap|rem|axd|cs|vb|config|dll)$
        conf += '    proxy_set_header X-Real-IP  $remote_addr;\n';
        conf += '    proxy_set_header X-Forwarded-For $remote_addr;\n';
        conf += '    proxy_set_header Host $host;\n';
        conf += '    proxy_pass http://127.0.0.1:8080;\n';
        conf += '  }\n';
      }

      return conf;
    }

    private MakeApacheAspNetHeaders(version: string, host: string, root: string, ioMapForAll: boolean, debug: boolean): string {
      var monoServer = 'mod-mono-server4';

      if (version == '2.0') {
        monoServer = 'mod-mono-server2';
      }
      else if (version == '1.0') {
        monoServer = 'mod-mono-server';
      }

      var conf = '';

      conf += `  MonoServerPath ${host} "/usr/bin/${monoServer}"` + '\n';

      if (debug) {
        conf += `  MonoDebug ${host}` + '\n';
      }

      if (ioMapForAll) {
        conf += `  MonoSetEnv ${host} MONO_IOMAP=all` + '\n';
      }

      conf += `  MonoApplications ${host} "/:${root}"` + '\n';

      return conf;
    }

    private MakeApacheAspNetLocation(host: string): string {
      var conf = '';

      conf += '  <Location />\n';
      conf += '    Options FollowSymLinks Indexes\n';
      conf += '    AllowOverride All\n';
      conf += '    Order Allow,Deny\n';
      conf += '    Allow from all\n';
      conf += '    Require all granted\n';
      conf += '    DirectoryIndex index.html index.htm\n';
      conf += `    MonoSetServerAlias ${host}` + '\n';
      conf += '    SetHandler mono\n';
      conf += '    SetOutputFilter DEFLATE\n';
      conf += '    SetEnvIfNoCase Request_URI ".(?:gif|jpe?g|png|js|css|txt|map|ttf|otf|woff|woff2|eot|svg)$" no$\n';
      conf += '    DirectoryIndex Default.aspx default.aspx Index.aspx index.aspx Index.cshtml index.cshtml Index.vbhtml index.vbhtml\n';
      conf += '  </Location>\n';

      return conf;
    }

    private MakeApacheDefaultLocation(): string {
      var conf = '';

      conf += '  <Location />\n';
      // conf += '    Options FollowSymLinks Indexes\n';
      conf += '    AllowOverride All\n';
      conf += '    Order Allow,Deny\n';
      conf += '    Allow from all\n';
      conf += '    Require all granted\n';
      conf += '    DirectoryIndex index.html index.htm index.php\n';
      conf += '  </Location>\n';

      return conf;
    }

    private GetConf($this: SiteEditorController, level: string): string {
      for (var i = 0; i < $this.Site.Conf.length; i++) {
        if ($this.Site.Conf[i].Level.toLowerCase() == level.toLowerCase()) {
          return $this.Site.Conf[i].Source;
        }
      }

      return null;
    }

    private UpdateCanAddConf($this: SiteEditorController): void {
      var result = [];

      angular.forEach($this.LevelsList, (item) => {
        if ($this.Site.Levels.indexOf(item) == -1) {
          result.push(item);
        }
      });

      $this.AvailableLevels = result;

      console.log('Levels', $this.Site.Levels, $this.AvailableLevels);

      $this.CanAddConf = ($this.AvailableLevels.length > 0);

      if ($this.Site.Conf.length <= 0) {
        $this.PrepareAddConf($this);
      }
    }

    //#endregion

  }

}