/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////

define('pgadmin.node.server', [
  'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone',
  'sources/pgadmin', 'pgadmin.browser',
  'pgadmin.server.supported_servers', 'pgadmin.user_management.current_user',
  'pgadmin.alertifyjs', 'pgadmin.backform',
  'sources/browser/server_groups/servers/model_validation',
  'pgadmin.browser.server.privilege',
], function(
  gettext, url_for, $, _, Backbone, pgAdmin, pgBrowser,
  supported_servers, current_user, Alertify, Backform,
  modelValidation
) {

  if (!pgBrowser.Nodes['server']) {
    var SSL_MODES = ['prefer', 'require', 'verify-ca', 'verify-full'];

    pgBrowser.SecLabelModel = pgBrowser.Node.Model.extend({
      defaults: {
        provider: undefined,
        label: undefined,
      },
      schema: [{
        id: 'provider', label: gettext('Provider'),
        type: 'text', editable: true,
        cellHeaderClasses:'width_percent_50',
      },{
        id: 'label', label: gettext('Security label'),
        type: 'text', editable: true,
        cellHeaderClasses:'override_label_class_font_size',
      }],
      validate: function() {
        this.errorModel.clear();
        if (_.isUndefined(this.get('label')) ||
          _.isNull(this.get('label')) ||
            String(this.get('label')).replace(/^\s+|\s+$/g, '') == '') {
          var errmsg = gettext('Security label must be specified.');
          this.errorModel.set('label', errmsg);
          return errmsg;
        }

        return null;
      },
    });

    pgAdmin.Browser.Nodes['server'] = pgAdmin.Browser.Node.extend({
      parent_type: 'server_group',
      type: 'server',
      dialogHelp: url_for('help.static', {'filename': 'server_dialog.html'}),
      label: gettext('Server'),
      canDrop: function(node){
        var serverOwner = node.user_id;
        if (serverOwner != current_user.id && !_.isUndefined(serverOwner))
          return false;
        return true;
      },
      dropAsRemove: true,
      dropPriority: 5,
      hasStatistics: true,
      hasCollectiveStatistics: true,
      can_expand: function(d) {
        return d && d.connected;
      },
      Init: function() {
        /* Avoid multiple registration of same menus */
        if (this.initialized)
          return;

        this.initialized = true;

        pgBrowser.add_menus([{
          name: 'create_server_on_sg', node: 'server_group', module: this,
          applies: ['object', 'context'], callback: 'show_obj_properties',
          category: 'create', priority: 1, label: gettext('Server...'),
          data: {action: 'create'}, icon: 'wcTabIcon icon-server', enable: 'canCreate',
        },{
          name: 'create_server', node: 'server', module: this,
          applies: ['object', 'context'], callback: 'show_obj_properties',
          category: 'create', priority: 3, label: gettext('Server...'),
          data: {action: 'create'}, icon: 'wcTabIcon icon-server', enable: 'canCreate',
        },{
          name: 'connect_server', node: 'server', module: this,
          applies: ['object', 'context'], callback: 'connect_server',
          category: 'connect', priority: 4, label: gettext('Connect Server'),
          icon: 'fa fa-link', enable : 'is_not_connected',
        },{
          name: 'disconnect_server', node: 'server', module: this,
          applies: ['object', 'context'], callback: 'disconnect_server',
          category: 'drop', priority: 5, label: gettext('Disconnect Server'),
          icon: 'fa fa-unlink', enable : 'is_connected',
        },{
          name: 'reload_configuration', node: 'server', module: this,
          applies: ['tools', 'context'], callback: 'reload_configuration',
          category: 'reload', priority: 6, label: gettext('Reload Configuration'),
          icon: 'fa fa-redo-alt', enable : 'enable_reload_config',
        },{
          name: 'restore_point', node: 'server', module: this,
          applies: ['tools', 'context'], callback: 'restore_point',
          category: 'restore', priority: 9, label: gettext('Add Named Restore Point...'),
          icon: 'fa fa-anchor', enable : 'is_applicable',
        },{
          name: 'change_password', node: 'server', module: this,
          applies: ['object'], callback: 'change_password',
          label: gettext('Change Password...'), priority: 10,
          icon: 'fa fa-lock', enable : 'is_connected',
        },{
          name: 'wal_replay_pause', node: 'server', module: this,
          applies: ['tools', 'context'], callback: 'pause_wal_replay',
          category: 'wal_replay_pause', priority: 7, label: gettext('Pause Replay of WAL'),
          icon: 'fa fa-pause-circle', enable : 'wal_pause_enabled',
        },{
          name: 'wal_replay_resume', node: 'server', module: this,
          applies: ['tools', 'context'], callback: 'resume_wal_replay',
          category: 'wal_replay_resume', priority: 8, label: gettext('Resume Replay of WAL'),
          icon: 'fa fa-play-circle', enable : 'wal_resume_enabled',
        },{
          name: 'clear_saved_password', node: 'server', module: this,
          applies: ['object', 'context'], callback: 'clear_saved_password',
          label: gettext('Clear Saved Password'), icon: 'fa fa-eraser',
          priority: 11,
          enable: function(node) {
            if (node && node._type === 'server' &&
              node.is_password_saved) {
              return true;
            }
            return false;
          },
        },{
          name: 'clear_sshtunnel_password', node: 'server', module: this,
          applies: ['object', 'context'], callback: 'clear_sshtunnel_password',
          label: gettext('Clear SSH Tunnel Password'), icon: 'fa fa-eraser',
          priority: 12,
          enable: function(node) {
            if (node && node._type === 'server' &&
              node.is_tunnel_password_saved) {
              return true;
            }
            return false;
          },
        }]);

        _.bindAll(this, 'connection_lost');
        pgBrowser.Events.on(
          'pgadmin:server:connection:lost', this.connection_lost
        );
      },
      is_not_connected: function(node) {
        return (node && node.connected != true);
      },
      canCreate: function(node){
        var serverOwner = node.user_id;
        if (serverOwner == current_user.id || _.isUndefined(serverOwner))
          return true;
        return false;

      },
      is_connected: function(node) {
        return (node && node.connected == true);
      },
      enable_reload_config: function(node) {
        // Must be connected & is Super user
        if (node && node._type == 'server' &&
          node.connected && node.user.is_superuser) {
          return true;
        }
        return false;
      },
      is_applicable: function(node) {
        // Must be connected & super user & not in recovery mode
        if (node && node._type == 'server' &&
          node.connected && node.user.is_superuser
            && node.in_recovery == false) {
          return true;
        }
        return false;
      },
      wal_pause_enabled: function(node) {
        // Must be connected & is Super user & in Recovery mode
        if (node && node._type == 'server' &&
          node.connected && node.user.is_superuser
            && node.in_recovery == true
            && node.wal_pause == false) {
          return true;
        }
        return false;
      },
      wal_resume_enabled: function(node) {
        // Must be connected & is Super user & in Recovery mode
        if (node && node._type == 'server' &&
          node.connected && node.user.is_superuser
            && node.in_recovery == true
            && node.wal_pause == true) {
          return true;
        }
        return false;
      },
      callbacks: {
        /* Connect the server */
        connect_server: function(args){
          var input = args || {},
            obj = this,
            t = pgBrowser.tree,
            i = input.item || t.selected(),
            d = i && i.length == 1 ? t.itemData(i) : undefined;

          if (d) {
            connect_to_server(obj, d, t, i, false);
          }
          return false;
        },
        /* Disconnect the server */
        disconnect_server: function(args, notify) {
          var input = args || {},
            obj = this,
            t = pgBrowser.tree,
            i = 'item' in input ? input.item : t.selected(),
            d = i && i.length == 1 ? t.itemData(i) : undefined;

          if (d) {
            notify = notify || _.isUndefined(notify) || _.isNull(notify);

            var disconnect = function() {
              $.ajax({
                url: obj.generate_url(i, 'connect', d, true),
                type:'DELETE',
              })
                .done(function(res) {
                  if (res.success == 1) {
                    Alertify.success(res.info);
                    d = t.itemData(i);
                    t.removeIcon(i);
                    d.connected = false;
                    if (d.shared && pgAdmin.server_mode == 'True'){
                      d.icon = 'icon-shared-server-not-connected';
                    }else{
                      d.icon = 'icon-server-not-connected';
                    }
                    t.addIcon(i, {icon: d.icon});
                    obj.callbacks.refresh.apply(obj, [null, i]);
                    if (pgBrowser.serverInfo && d._id in pgBrowser.serverInfo) {
                      delete pgBrowser.serverInfo[d._id];
                    }
                    else {
                      try {
                        Alertify.error(res.errormsg);
                      } catch (e) {
                        console.warn(e.stack || e);
                      }
                      t.unload(i);
                    }
                  }})
                .fail(function(xhr, status, error) {
                  Alertify.pgRespErrorNotify(xhr, error);
                  t.unload(i);
                });
            };

            if (notify) {
              Alertify.confirm(
                gettext('Disconnect server'),
                gettext('Are you sure you want to disconnect the server %s?', d.label),
                function() { disconnect(); },
                function() { return true;}
              ).set('labels', {
                ok: gettext('Ok'),
                cancel: gettext('Cancel'),
              });
            } else {
              disconnect();
            }
          }

          return false;
        },
        /* Connect the server (if not connected), before opening this node */
        beforeopen: function(item, data) {

          if(!data || data._type != 'server') {
            return false;
          }

          pgBrowser.tree.addIcon(item, {icon: data.icon});
          if (!data.connected) {
            connect_to_server(this, data, pgBrowser.tree, item, false);

            return false;
          }
          return true;
        },
        added: function(item, data) {

          pgBrowser.serverInfo = pgBrowser.serverInfo || {};
          pgBrowser.serverInfo[data._id] = _.extend({}, data);

          // Call added method of node.js
          pgAdmin.Browser.Node.callbacks.added.apply(this, arguments);

          if(data.was_connected) {
            fetch_connection_status(this, data, pgBrowser.tree, item);
          }
          return true;
        },
        /* Reload configuration */
        reload_configuration: function(args){
          var input = args || {},
            obj = this,
            t = pgBrowser.tree,
            i = input.item || t.selected(),
            d = i && i.length == 1 ? t.itemData(i) : undefined;

          if (d) {
            Alertify.confirm(
              gettext('Reload server configuration'),
              gettext('Are you sure you want to reload the server configuration on %s?', d.label),
              function() {
                $.ajax({
                  url: obj.generate_url(i, 'reload', d, true),
                  method:'GET',
                })
                  .done(function(res) {
                    if (res.data.status) {
                      Alertify.success(res.data.result);
                    }
                    else {
                      Alertify.error(res.data.result);
                    }
                  })
                  .fail(function(xhr, status, error) {
                    Alertify.pgRespErrorNotify(xhr, error);
                    t.unload(i);
                  });
              },
              function() { return true; }
            );
          }

          return false;
        },
        /* Add restore point */
        restore_point: function(args) {
          var input = args || {},
            obj = this,
            t = pgBrowser.tree,
            i = input.item || t.selected(),
            d = i && i.length == 1 ? t.itemData(i) : undefined;

          if (!d)
            return false;

          Alertify.prompt(
            gettext('Enter the name of the restore point to add'), '',
            // We will execute this function when user clicks on the OK button
            function(evt, value) {
              // If user has provided a value, send it to the server
              if(!_.isUndefined(value) && !_.isNull(value) && value !== ''
                && String(value).replace(/^\s+|\s+$/g, '') !== '') {
                $.ajax({
                  url: obj.generate_url(i, 'restore_point', d, true),
                  method:'POST',
                  data:{ 'value': JSON.stringify(value) },
                })
                  .done(function(res) {
                    Alertify.success(res.data.result, 10);
                  })
                  .fail(function(xhr, status, error) {
                    Alertify.pgRespErrorNotify(xhr, error);
                    t.unload(i);
                  });
              } else {
                evt.cancel = true;
                Alertify.error( gettext('Please enter a valid name.'), 10);
              }
            },
            // We will execute this function when user clicks on the Cancel
            // button.  Do nothing just close it.
            function(evt) { evt.cancel = false; }
          ).set({'title': gettext('Restore point name')});
        },

        /* Change password */
        change_password: function(args){
          var input = args || {},
            obj = this,
            t = pgBrowser.tree,
            i = input.item || t.selected(),
            d = i && i.length == 1 ? t.itemData(i) : undefined,
            url = obj.generate_url(i, 'change_password', d, true),
            is_pgpass_file_used = false,
            check_pgpass_url = obj.generate_url(i, 'check_pgpass', d, true);

          if (d) {
            if(!Alertify.changeServerPassword) {
              var newPasswordModel = Backbone.Model.extend({
                  defaults: {
                    user_name: undefined,
                    password: undefined,
                    newPassword: undefined,
                    confirmPassword: undefined,
                  },
                  validate: function() {
                    return null;
                  },
                }),
                passwordChangeFields = [{
                  name: 'user_name', label: gettext('User'),
                  type: 'text', readonly: true, control: 'input',
                },{
                  name: 'password', label: gettext('Current Password'),
                  type: 'password', disabled: function() { return is_pgpass_file_used; },
                  control: 'input', required: true,
                },{
                  name: 'newPassword', label: gettext('New Password'),
                  type: 'password', disabled: false, control: 'input',
                  required: true,
                },{
                  name: 'confirmPassword', label: gettext('Confirm Password'),
                  type: 'password', disabled: false, control: 'input',
                  required: true,
                }];


              Alertify.dialog('changeServerPassword' ,function factory() {
                return {
                  main: function(params) {
                    var title = gettext('Change Password');
                    this.set('title', title);
                    this.user_name = params.user.name;
                  },
                  setup:function() {
                    return {
                      buttons: [{
                        text: gettext('Cancel'), key: 27,
                        className: 'btn btn-secondary fa fa-times pg-alertify-button', attrs: {name: 'cancel'},
                      },{
                        text: gettext('OK'), key: 13, className: 'btn btn-primary fa fa-check pg-alertify-button',
                        attrs: {name:'submit'},
                      }],
                      // Set options for dialog
                      options: {
                        padding : !1,
                        overflow: !1,
                        modal:false,
                        resizable: true,
                        maximizable: true,
                        pinnable: false,
                        closableByDimmer: false,
                      },
                    };
                  },
                  hooks: {
                    // triggered when the dialog is closed
                    onclose: function() {
                      if (this.view) {
                        this.view.remove({data: true, internal: true, silent: true});
                      }
                    },
                  },
                  prepare: function() {
                    var self = this;
                    // Disable Ok button until user provides input
                    this.__internal.buttons[1].element.disabled = true;

                    var $container = $('<div class=\'change_password\'></div>'),
                      newpasswordmodel = new newPasswordModel(
                        {'user_name': self.user_name}
                      ),
                      view = this.view = new Backform.Form({
                        el: $container,
                        model: newpasswordmodel,
                        fields: passwordChangeFields,
                      });

                    view.render();

                    this.elements.content.appendChild($container.get(0));

                    // Listen to model & if filename is provided then enable Backup button
                    this.view.model.on('change', function() {
                      var that = this,
                        password = this.get('password'),
                        newPassword = this.get('newPassword'),
                        confirmPassword = this.get('confirmPassword');

                      // Only check password field if pgpass file is not available
                      if ((!is_pgpass_file_used &&
                        (_.isUndefined(password) || _.isNull(password) || password == '')) ||
                          _.isUndefined(newPassword) || _.isNull(newPassword) || newPassword == '' ||
                          _.isUndefined(confirmPassword) || _.isNull(confirmPassword) || confirmPassword == '') {
                        self.__internal.buttons[1].element.disabled = true;
                      } else if (newPassword != confirmPassword) {
                        self.__internal.buttons[1].element.disabled = true;

                        this.errorTimeout && clearTimeout(this.errorTimeout);
                        this.errorTimeout = setTimeout(function() {
                          that.errorModel.set('confirmPassword', gettext('Passwords do not match.'));
                        } ,400);
                      }else {
                        that.errorModel.clear();
                        self.__internal.buttons[1].element.disabled = false;
                      }
                    });
                  },
                  // Callback functions when click on the buttons of the Alertify dialogs
                  callback: function(e) {
                    if (e.button.element.name == 'submit') {
                      var self = this,
                        alertArgs =  this.view.model.toJSON();

                      e.cancel = true;

                      $.ajax({
                        url: url,
                        method:'POST',
                        data:{'data': JSON.stringify(alertArgs) },
                      })
                        .done(function(res) {
                          if (res.success) {
                          // Notify user to update pgpass file
                            if(is_pgpass_file_used) {
                              Alertify.alert(
                                gettext('Change Password'),
                                gettext('Please make sure to disconnect the server'
                                + ' and update the new password in the pgpass file'
                                  + ' before performing any other operation')
                              );
                            }

                            Alertify.success(res.info);
                            self.close();
                          } else {
                            Alertify.error(res.errormsg);
                          }
                        })
                        .fail(function(xhr, status, error) {
                          Alertify.pgRespErrorNotify(xhr, error);
                        });
                    }
                  },
                };
              });
            }

            // Call to check if server is using pgpass file or not
            $.ajax({
              url: check_pgpass_url,
              method:'GET',
            })
              .done(function(res) {
                if (res.success && res.data.is_pgpass) {
                  is_pgpass_file_used = true;
                }
                Alertify.changeServerPassword(d).resizeTo('40%','52%');
              })
              .fail(function(xhr, status, error) {
                Alertify.pgRespErrorNotify(xhr, error);
              });
          }

          return false;
        },

        /* Pause WAL Replay */
        pause_wal_replay: function(args) {
          var input = args || {},
            obj = this,
            t = pgBrowser.tree,
            i = input.item || t.selected(),
            d = i && i.length == 1 ? t.itemData(i) : undefined;

          if (!d)
            return false;

          $.ajax({
            url: obj.generate_url(i, 'wal_replay' , d, true),
            type:'DELETE',
            dataType: 'json',
          })
            .done(function(res) {
              if (res.success == 1) {
                Alertify.success(res.info);
                t.itemData(i).wal_pause=res.data.wal_pause;
                t.unload(i);
                t.setInode(i);
                t.deselect(i);
                // Fetch updated data from server
                setTimeout(function() {
                  t.select(i);
                }, 10);
              }
            })
            .fail(function(xhr, status, error) {
              Alertify.pgRespErrorNotify(xhr, error);
              t.unload(i);
            });
        },

        /* Resume WAL Replay */
        resume_wal_replay: function(args) {
          var input = args || {},
            obj = this,
            t = pgBrowser.tree,
            i = input.item || t.selected(),
            d = i && i.length == 1 ? t.itemData(i) : undefined;

          if (!d)
            return false;

          $.ajax({
            url: obj.generate_url(i, 'wal_replay' , d, true),
            type:'PUT',
            dataType: 'json',
          })
            .done(function(res) {
              if (res.success == 1) {
                Alertify.success(res.info);
                t.itemData(i).wal_pause=res.data.wal_pause;
                t.unload(i);
                t.setInode(i);
                t.deselect(i);
                // Fetch updated data from server
                setTimeout(function() {
                  t.select(i);
                }, 10);
              }
            })
            .fail(function(xhr, status, error) {
              Alertify.pgRespErrorNotify(xhr, error);
              t.unload(i);
            });
        },

        /* Cleat saved database server password */
        clear_saved_password: function(args){
          var input = args || {},
            obj = this,
            t = pgBrowser.tree,
            i = input.item || t.selected(),
            d = i && i.length == 1 ? t.itemData(i) : undefined;

          if (d) {
            Alertify.confirm(
              gettext('Clear saved password'),
              gettext('Are you sure you want to clear the saved password for server %s?', d.label),
              function() {
                $.ajax({
                  url: obj.generate_url(i, 'clear_saved_password', d, true),
                  method:'PUT',
                })
                  .done(function(res) {
                    if (res.success == 1) {
                      Alertify.success(res.info);
                      t.itemData(i).is_password_saved=res.data.is_password_saved;
                    }
                    else {
                      Alertify.error(res.info);
                    }
                  })
                  .fail(function(xhr, status, error) {
                    Alertify.pgRespErrorNotify(xhr, error);
                  });
              },
              function() { return true; }
            );
          }

          return false;
        },

        /* Reset stored ssh tunnel  password */
        clear_sshtunnel_password: function(args){
          var input = args || {},
            obj = this,
            t = pgBrowser.tree,
            i = input.item || t.selected(),
            d = i && i.length == 1 ? t.itemData(i) : undefined;

          if (d) {
            Alertify.confirm(
              gettext('Clear SSH Tunnel password'),
              gettext('Are you sure you want to clear the saved password of SSH Tunnel for server %s?', d.label),
              function() {
                $.ajax({
                  url: obj.generate_url(i, 'clear_sshtunnel_password', d, true),
                  method:'PUT',
                })
                  .done(function(res) {
                    if (res.success == 1) {
                      Alertify.success(res.info);
                      t.itemData(i).is_tunnel_password_saved=res.data.is_tunnel_password_saved;
                    }
                    else {
                      Alertify.error(res.info);
                    }
                  })
                  .fail(function(xhr, status, error) {
                    Alertify.pgRespErrorNotify(xhr, error);
                  });
              },
              function() { return true; }
            );
          }

          return false;
        },
      },
      model: pgAdmin.Browser.Node.Model.extend({
        defaults: {
          gid: undefined,
          id: undefined,
          name: '',
          sslmode: 'prefer',
          host: '',
          hostaddr: '',
          port: 5432,
          db: 'postgres',
          username: current_user.name,
          role: null,
          connect_now: true,
          password: undefined,
          save_password: false,
          db_res: '',
          passfile: undefined,
          sslcompression: false,
          sslcert: undefined,
          sslkey: undefined,
          sslrootcert: undefined,
          sslcrl: undefined,
          service: undefined,
          use_ssh_tunnel: 0,
          tunnel_host: undefined,
          tunnel_port: 22,
          tunnel_username: undefined,
          tunnel_identity_file: undefined,
          tunnel_password: undefined,
          tunnel_authentication: 0,
          save_tunnel_password: false,
          connect_timeout: 10,
        },
        // Default values!
        initialize: function(attrs, args) {
          var isNew = (_.size(attrs) === 0);

          if (isNew) {
            this.set({'gid': args.node_info['server_group']._id});
          }
          pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments);
        },
        schema: [{
          id: 'id', label: gettext('ID'), type: 'int', mode: ['properties'],
          visible: function(model){
            if (model.attributes.user_id != current_user.id && pgAdmin.server_mode == 'True')
              return false;
            return true;
          },
        },{
          id: 'name', label: gettext('Name'), type: 'text',
          mode: ['properties', 'edit', 'create'], disabled: 'isShared',
        },
        {
          id: 'gid', label: gettext('Server group'), type: 'int',
          control: 'node-list-by-id', node: 'server_group',
          mode: ['create', 'edit'], select2: {allowClear: false}, disabled: 'isShared',
        },
        {
          id: 'server_owner', label: gettext('Shared Server Owner'), type: 'text', mode: ['properties'],
          visible:function(model){
            var serverOwner = model.attributes.user_id;
            if (model.attributes.shared && serverOwner != current_user.id && pgAdmin.server_mode == 'True'){
              return true;
            }
            return false;

          },
        },
        {
          id: 'server_type', label: gettext('Server type'), type: 'options',
          mode: ['properties'], visible: 'isConnected',
          'options': supported_servers,
        },{
          id: 'connected', label: gettext('Connected?'), type: 'switch',
          mode: ['properties'], group: gettext('Connection'), 'options': {
            'onText':  gettext('True'), 'offText':  gettext('False'), 'size': 'mini',
          },
        },{
          id: 'version', label: gettext('Version'), type: 'text', group: null,
          mode: ['properties'], visible: 'isConnected',
        },{
          id: 'bgcolor', label: gettext('Background'), type: 'color',
          group: null, mode: ['edit', 'create'], disabled: 'isfgColorSet',
          deps: ['fgcolor'],
        },{
          id: 'fgcolor', label: gettext('Foreground'), type: 'color',
          group: null, mode: ['edit', 'create'], disabled: 'isConnected',
        },{
          id: 'connect_now', controlLabel: gettext('Connect now?'), type: 'checkbox',
          group: null, mode: ['create'],
        },{
          id: 'shared', label: gettext('Shared?'), type: 'switch',
          mode: ['properties', 'create', 'edit'], 'options': {'size': 'mini'},
          readonly: function(model){
            var serverOwner = model.attributes.user_id;
            if (!model.isNew() && serverOwner != current_user.id){
              return true;
            }
            return false;
          },visible: function(){
            if (current_user.is_admin && pgAdmin.server_mode == 'True')
              return true;

            return false;
          },
        },
        {
          id: 'comment', label: gettext('Comments'), type: 'multiline', group: null,
          mode: ['properties', 'edit', 'create'],
        },{
          id: 'host', label: gettext('Host name/address'), type: 'text', group: gettext('Connection'),
          mode: ['properties', 'edit', 'create'],disabled: 'isShared',
          control: Backform.InputControl.extend({
            onChange: function() {
              Backform.InputControl.prototype.onChange.apply(this, arguments);
              if (!this.model || !this.model.changed) {
                this.model.inform_text = undefined;
                return;
              }

              if(this.model.origSessAttrs.host != this.model.changed.host && !this.model.isNew() && this.model.get('connected'))
              {
                this.model.inform_text = gettext(
                  'To apply changes to the connection configuration, please disconnect from the server and then reconnect.'
                );
              } else {
                this.model.inform_text = undefined;
              }
            },
          }),
        },{
          id: 'port', label: gettext('Port'), type: 'int', group: gettext('Connection'),
          mode: ['properties', 'edit', 'create'], min: 1, max: 65535, disabled: 'isShared',
          control: Backform.InputControl.extend({
            onChange: function() {
              Backform.InputControl.prototype.onChange.apply(this, arguments);
              if (!this.model || !this.model.changed) {
                this.model.inform_text = undefined;
                return;
              }

              if(this.model.origSessAttrs.port != this.model.changed.port && !this.model.isNew() && this.model.get('connected'))
              {
                this.model.inform_text = gettext(
                  'To apply changes to the connection configuration, please disconnect from the server and then reconnect.'
                );
              } else {
                this.model.inform_text = undefined;
              }
            },
          }),
        },{
          id: 'db', label: gettext('Maintenance database'), type: 'text', group: gettext('Connection'),
          mode: ['properties', 'edit', 'create'], readonly: 'isConnected',disabled: 'isShared',
        },{
          id: 'username', label: gettext('Username'), type: 'text', group: gettext('Connection'),
          mode: ['properties', 'edit', 'create'],
          control: Backform.InputControl.extend({
            onChange: function() {
              Backform.InputControl.prototype.onChange.apply(this, arguments);
              if (!this.model || !this.model.changed) {
                this.model.inform_text = undefined;
                return;
              }

              if(this.model.origSessAttrs.username != this.model.changed.username && !this.model.isNew() && this.model.get('connected'))
              {
                this.model.inform_text = gettext(
                  'To apply changes to the connection configuration, please disconnect from the server and then reconnect.'
                );
              } else {
                this.model.inform_text = undefined;
              }
            },
          }),
        },{
          id: 'password', label: gettext('Password'), type: 'password', maxlength: null,
          group: gettext('Connection'), control: 'input', mode: ['create'], deps: ['connect_now'],
          visible: function(model) {
            return model.get('connect_now') && model.isNew();
          },
        },{
          id: 'save_password', controlLabel: gettext('Save password?'),
          type: 'checkbox', group: gettext('Connection'), mode: ['create'],
          deps: ['connect_now'], visible: function(model) {
            return model.get('connect_now') && model.isNew();
          },
          disabled: function() {
            if (!current_user.allow_save_password)
              return true;

            return false;
          },
        },{
          id: 'role', label: gettext('Role'), type: 'text', group: gettext('Connection'),
          mode: ['properties', 'edit', 'create'], readonly: 'isConnected',
        },{
          id: 'service', label: gettext('Service'), type: 'text',
          mode: ['properties', 'edit', 'create'], readonly: 'isConnected',
          group: gettext('Connection'),
        },{
          id: 'sslmode', label: gettext('SSL mode'), control: 'select2', group: gettext('SSL'),
          select2: {
            allowClear: false,
            minimumResultsForSearch: Infinity,
          },
          mode: ['properties', 'edit', 'create'], disabled: 'isConnected',
          'options': [
            {label: gettext('Allow'), value: 'allow'},
            {label: gettext('Prefer'), value: 'prefer'},
            {label: gettext('Require'), value: 'require'},
            {label: gettext('Disable'), value: 'disable'},
            {label: gettext('Verify-CA'), value: 'verify-ca'},
            {label: gettext('Verify-Full'), value: 'verify-full'},
          ],
        },{
          id: 'sslcert', label: gettext('Client certificate'), type: 'text',
          group: gettext('SSL'), mode: ['edit', 'create'],
          disabled: 'isSSL', readonly: 'isConnected', control: Backform.FileControl,
          dialog_type: 'select_file', supp_types: ['*'],
          deps: ['sslmode'],
        },{
          id: 'sslkey', label: gettext('Client certificate key'), type: 'text',
          group: gettext('SSL'), mode: ['edit', 'create'],
          disabled: 'isSSL', readonly: 'isConnected', control: Backform.FileControl,
          dialog_type: 'select_file', supp_types: ['*'],
          deps: ['sslmode'],
        },{
          id: 'sslrootcert', label: gettext('Root certificate'), type: 'text',
          group: gettext('SSL'), mode: ['edit', 'create'],
          disabled: 'isSSL', readonly: 'isConnected', control: Backform.FileControl,
          dialog_type: 'select_file', supp_types: ['*'],
          deps: ['sslmode'],
        },{
          id: 'sslcrl', label: gettext('Certificate revocation list'), type: 'text',
          group: gettext('SSL'), mode: ['edit', 'create'],
          disabled: 'isSSL', readonly: 'isConnected', control: Backform.FileControl,
          dialog_type: 'select_file', supp_types: ['*'],
          deps: ['sslmode'],
        },{
          id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch',
          mode: ['edit', 'create'], group: gettext('SSL'),
          'options': {'size': 'mini'},
          deps: ['sslmode'], disabled: 'isSSL', readonly: 'isConnected',
        },{
          id: 'sslcert', label: gettext('Client certificate'), type: 'text',
          group: gettext('SSL'), mode: ['properties'],
          deps: ['sslmode'],
          visible: function(model) {
            var sslcert = model.get('sslcert');
            return !_.isUndefined(sslcert) && !_.isNull(sslcert);
          },
        },{
          id: 'sslkey', label: gettext('Client certificate key'), type: 'text',
          group: gettext('SSL'), mode: ['properties'],
          deps: ['sslmode'],
          visible: function(model) {
            var sslkey = model.get('sslkey');
            return !_.isUndefined(sslkey) && !_.isNull(sslkey);
          },
        },{
          id: 'sslrootcert', label: gettext('Root certificate'), type: 'text',
          group: gettext('SSL'), mode: ['properties'],
          deps: ['sslmode'],
          visible: function(model) {
            var sslrootcert = model.get('sslrootcert');
            return !_.isUndefined(sslrootcert) && !_.isNull(sslrootcert);
          },
        },{
          id: 'sslcrl', label: gettext('Certificate revocation list'), type: 'text',
          group: gettext('SSL'), mode: ['properties'],
          deps: ['sslmode'],
          visible: function(model) {
            var sslcrl = model.get('sslcrl');
            return !_.isUndefined(sslcrl) && !_.isNull(sslcrl);
          },
        },{
          id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch',
          mode: ['properties'], group: gettext('SSL'),
          'options': {'size': 'mini'},
          deps: ['sslmode'], visible: function(model) {
            var sslmode = model.get('sslmode');
            return _.indexOf(SSL_MODES, sslmode) != -1;
          },
        },{
          id: 'use_ssh_tunnel', label: gettext('Use SSH tunneling'), type: 'switch',
          mode: ['properties', 'edit', 'create'], group: gettext('SSH Tunnel'),
          'options': {'size': 'mini'},
          disabled: function(model) {
            if (!pgAdmin.Browser.utils.support_ssh_tunnel) {
              setTimeout(function() {
                model.set('use_ssh_tunnel', 0);
              }, 10);

              return true;
            }

            return false;
          },
          readonly: 'isConnected',
        },{
          id: 'tunnel_host', label: gettext('Tunnel host'), type: 'text', group: gettext('SSH Tunnel'),
          mode: ['properties', 'edit', 'create'], deps: ['use_ssh_tunnel'],
          disabled: function(model) {
            return !model.get('use_ssh_tunnel');
          },
          readonly: 'isConnected',
        },{
          id: 'tunnel_port', label: gettext('Tunnel port'), type: 'int', group: gettext('SSH Tunnel'),
          mode: ['properties', 'edit', 'create'], deps: ['use_ssh_tunnel'], max: 65535,
          disabled: function(model) {
            return !model.get('use_ssh_tunnel');
          },
          readonly: 'isConnected',
        },{
          id: 'tunnel_username', label: gettext('Username'), type: 'text', group: gettext('SSH Tunnel'),
          mode: ['properties', 'edit', 'create'], deps: ['use_ssh_tunnel'],
          disabled: function(model) {
            return !model.get('use_ssh_tunnel');
          },
          readonly: 'isConnected',
        },{
          id: 'tunnel_authentication', label: gettext('Authentication'), type: 'switch',
          mode: ['properties', 'edit', 'create'], group: gettext('SSH Tunnel'),
          'options': {'onText':  gettext('Identity file'),
            'offText':  gettext('Password'), 'size': 'mini', width: '90'},
          deps: ['use_ssh_tunnel'],
          disabled: function(model) {
            return !model.get('use_ssh_tunnel');
          },
          readonly: 'isConnected',
        }, {
          id: 'tunnel_identity_file', label: gettext('Identity file'), type: 'text',
          group: gettext('SSH Tunnel'), mode: ['properties', 'edit', 'create'],
          control: Backform.FileControl, dialog_type: 'select_file', supp_types: ['*'],
          deps: ['tunnel_authentication', 'use_ssh_tunnel'],
          disabled: function(model) {
            let file = model.get('tunnel_identity_file');
            if (!model.get('tunnel_authentication') && file) {
              setTimeout(function() {
                model.set('tunnel_identity_file', null);
              }, 10);
            }
            return !model.get('tunnel_authentication') || !model.get('use_ssh_tunnel');
          },
        },{
          id: 'tunnel_password', label: gettext('Password'), type: 'password',
          group: gettext('SSH Tunnel'), control: 'input', mode: ['create'],
          deps: ['use_ssh_tunnel'],
          disabled: function(model) {
            return !model.get('use_ssh_tunnel');
          },
          readonly: 'isConnected',
        }, {
          id: 'save_tunnel_password', controlLabel: gettext('Save password?'),
          type: 'checkbox', group: gettext('SSH Tunnel'), mode: ['create'],
          deps: ['connect_now', 'use_ssh_tunnel'], visible: function(model) {
            return model.get('connect_now') && model.isNew();
          },
          disabled: function(model) {
            if (!current_user.allow_save_tunnel_password ||
              !model.get('use_ssh_tunnel'))
              return true;

            return false;
          },
        }, {
          id: 'hostaddr', label: gettext('Host address'), type: 'text', group: gettext('Advanced'),
          mode: ['properties', 'edit', 'create'], readonly: 'isConnected',
        },{
          id: 'db_res', label: gettext('DB restriction'), type: 'select2', group: gettext('Advanced'),
          mode: ['properties', 'edit', 'create'], readonly: 'isConnected', select2: {multiple: true, allowClear: false,
            tags: true, tokenSeparators: [','], first_empty: false, selectOnClose: true, emptyOptions: true},
        },{
          id: 'passfile', label: gettext('Password file'), type: 'text',
          group: gettext('Advanced'), mode: ['edit', 'create'],
          disabled: 'isValidLib', readonly: 'isConnected', control: Backform.FileControl,
          dialog_type: 'select_file', supp_types: ['*'],
        },{
          id: 'passfile', label: gettext('Password file'), type: 'text',
          group: gettext('Advanced'), mode: ['properties'],
          visible: function(model) {
            var passfile = model.get('passfile');
            return !_.isUndefined(passfile) && !_.isNull(passfile);
          },
        },{
          id: 'connect_timeout', label: gettext('Connection timeout (seconds)'),
          type: 'int', group: gettext('Advanced'),
          mode: ['properties', 'edit', 'create'], readonly: 'isConnected',
          min: 0,
        }],
        isVisible: function(model){
          var serverOwner = model.attributes.user_id;
          if (!model.isNew() && serverOwner != current_user.id){
            return false;
          }
          return true;

        },
        isShared: function(model){
          var serverOwner = model.attributes.user_id;
          if (!model.isNew() && serverOwner != current_user.id && model.attributes.shared){
            return true;
          }
          return false;
        },
        validate: function() {
          const validateModel = new modelValidation.ModelValidation(this);
          return validateModel.validate();
        },
        isConnected: function(model) {
          return model.get('connected');
        },
        isfgColorSet: function(model) {
          var bgcolor = model.get('bgcolor'),
            fgcolor = model.get('fgcolor');

          if(model.get('connected')) {
            return true;
          }
          // If fgcolor is set and bgcolor is not set then force bgcolor
          // to set as white
          if(_.isUndefined(bgcolor) || _.isNull(bgcolor) || !bgcolor) {
            if(fgcolor) {
              model.set('bgcolor', '#ffffff');
            }
          }

          return false;
        },
        isSSL: function(model) {
          var ssl_mode = model.get('sslmode');
          return _.indexOf(SSL_MODES, ssl_mode) == -1;
        },
        isValidLib: function() {
          // older version of libpq do not support 'passfile' parameter in
          // connect method, valid libpq must have version >= 100000
          return pgBrowser.utils.pg_libpq_version < 100000;
        },
      }),
      connection_lost: function(i, resp) {
        if (pgBrowser.tree) {
          var t = pgBrowser.tree,
            d = i && t.itemData(i),
            self = this;

          while (d && d._type != 'server') {
            i = t.parent(i);
            d = i && t.itemData(i);
          }

          if (i && d && d._type == 'server') {
            if (_.isUndefined(d.is_connecting) || !d.is_connecting) {
              d.is_connecting = true;

              var disconnect = function(_sid) {
                if (d._id == _sid) {
                  d.is_connecting = false;
                  // Stop listening to the connection cancellation event
                  pgBrowser.Events.off(
                    'pgadmin:server:connect:cancelled', disconnect
                  );

                  // Connection to the database will also be cancelled
                  pgBrowser.Events.trigger(
                    'pgadmin:database:connect:cancelled',_sid,
                    resp.data.database || d.db
                  );

                  // Make sure - the server is disconnected properly
                  pgBrowser.Events.trigger(
                    'pgadmin:server:disconnect',
                    {item: i, data: d}, false
                  );
                }
              };

              // Listen for the server connection cancellation event
              pgBrowser.Events.on(
                'pgadmin:server:connect:cancelled', disconnect
              );
              Alertify.confirm(
                gettext('Connection lost'),
                gettext('Would you like to reconnect to the database?'),
                function() {
                  connect_to_server(self, d, t, i, true);
                },
                function() {
                  d.is_connecting = false;
                  t.unload(i);
                  t.setInode(i);
                  t.addIcon(i, {icon: 'icon-database-not-connected'});
                  pgBrowser.Events.trigger(
                    'pgadmin:server:connect:cancelled', i, d, self
                  );
                  t.select(i);
                });
            }
          }
        }
      },
    });

    var connect_to_server = function(obj, data, tree, item, reconnect) {
    // Open properties dialog in edit mode
      const selectedTreeNode = tree.selected().length > 0 ? tree.selected() : tree.first();
      const selectedTreeNodeData = selectedTreeNode && selectedTreeNode.length === 1 ? tree.itemData(selectedTreeNode) : undefined;
      var server_url = obj.generate_url(item, 'obj', data, true);
      // Fetch the updated data
      $.get(server_url)
        .done(function(res) {
          if (res.shared && _.isNull(res.username) && data.user_id != current_user.id){
            if (selectedTreeNodeData._type == 'server' && !res.service){
              pgAdmin.Browser.Node.callbacks.show_obj_properties.call(
                pgAdmin.Browser.Nodes[tree.itemData(item)._type], {action: 'edit'}
              );
              data.is_connecting = false;
              tree.unload(item);
              tree.setInode(item);
              tree.addIcon(item, {icon: 'icon-shared-server-not-connected'});
              Alertify.info('Please enter the server details to connect to the server. This server is a shared server.');
            }else{
              data.is_connecting = false;
              tree.unload(item);
              tree.setInode(item);
              tree.addIcon(item, {icon: 'icon-shared-server-not-connected'});
            }
          }
          return;
        }).always(function(){
          data.is_connecting = false;
        });

      var wasConnected = reconnect || data.connected,
        onFailure = function(
          xhr, status, error, _node, _data, _tree, _item, _wasConnected
        ) {
          data.connected = false;

          // It should be attempt to reconnect.
          // Let's not change the status of the tree node now.
          if (!_wasConnected) {
            tree.setInode(_item);
            if (_data.shared && pgAdmin.server_mode == 'True'){
              tree.addIcon(_item, {icon: 'icon-shared-server-not-connected'});
            }else{
              tree.addIcon(_item, {icon: 'icon-server-not-connected'});
            }

          }

          Alertify.pgNotifier('error', xhr, error, function(msg) {
            setTimeout(function() {
              if (msg == 'CRYPTKEY_SET') {
                connect_to_server(_node, _data, _tree, _item, _wasConnected);
              } else {
                Alertify.dlgServerPass(
                  gettext('Connect to Server'),
                  msg, _node, _data, _tree, _item, _wasConnected
                ).resizeTo();
              }
            }, 100);
          });
        },
        onSuccess = function(res, node, _data, _tree, _item, _wasConnected) {
          if (res && res.data) {
            if (typeof res.data.icon == 'string') {
              _tree.removeIcon(_item);
              _data.icon = res.data.icon;
              _tree.addIcon(_item, {icon: _data.icon});
            }

            _.extend(_data, res.data);
            _data.is_connecting = false;

            var serverInfo = pgBrowser.serverInfo =
              pgBrowser.serverInfo || {};
            serverInfo[_data._id] = _.extend({}, _data);

            if (_data.version < 90500) {
              Alertify.warning(gettext('You have connected to a server version that is older ' +
                'than is supported by pgAdmin. This may cause pgAdmin to break in strange and ' +
                'unpredictable ways. Or a plague of frogs. Either way, you have been warned!') +
                '<br /><br />' +
                res.info, 0);
            } else {
              Alertify.success(res.info);
            }

            obj.trigger('connected', obj, _item, _data);

            // Generate the event that server is connected
            pgBrowser.Events.trigger(
              'pgadmin:server:connected', _data._id, _item, _data
            );
            // Generate the event that database is connected
            pgBrowser.Events.trigger(
              'pgadmin:database:connected', _data._id, _data.db, _item, _data
            );

            // We're not reconnecting
            if (!_wasConnected) {
              _tree.setInode(_item);
              _tree.deselect(_item);

              setTimeout(function() {
                _tree.select(_item);
                _tree.open(_item);
              }, 10);
            } else {
              // We just need to refresh the tree now.
              setTimeout(function() {
                node.callbacks.refresh.apply(node, [true]);
              }, 10);
            }
          }
        };

      // Ask Password and send it back to the connect server
      if (!Alertify.dlgServerPass) {
        Alertify.dialog('dlgServerPass', function factory() {
          return {
            main: function(
              title, message, node, _data, _tree, _item,
              _status, _onSuccess, _onFailure, _onCancel
            ) {
              this.set('title', title);
              this.message = message;
              this.tree = _tree;
              this.nodeData = _data;
              this.nodeItem = _item;
              this.node= node;
              this.connected = _status;
              this.onSuccess = _onSuccess || onSuccess;
              this.onFailure = _onFailure || onFailure;
              this.onCancel = _onCancel || onCancel;
            },
            setup:function() {
              return {
                buttons:[{
                  text: gettext('Cancel'), className: 'btn btn-secondary fa fa-times pg-alertify-button',
                  key: 27,
                },{
                  text: gettext('OK'), key: 13, className: 'btn btn-primary fa fa-check pg-alertify-button',
                }],
                focus: {element: '#password', select: true},
                options: {
                  modal: 0, resizable: false, maximizable: false, pinnable: false,
                },
              };
            },
            build:function() {},
            prepare:function() {
              this.setContent(this.message);
            },
            callback: function(closeEvent) {
              var _tree = this.tree,
                _item = this.nodeItem,
                _node = this.node,
                _data = this.nodeData,
                _status = this.connected,
                _onSuccess = this.onSuccess,
                _onFailure = this.onFailure,
                _onCancel = this.onCancel;

              if (closeEvent.button.text == gettext('OK')) {

                var _url = _node.generate_url(_item, 'connect', _data, true);

                if (!_status) {
                  _tree.setLeaf(_item);
                  _tree.removeIcon(_item);
                  _tree.addIcon(_item, {icon: 'icon-server-connecting'});
                }

                $.ajax({
                  type: 'POST',
                  timeout: 30000,
                  url: _url,
                  data: $('#frmPassword').serialize(),
                })
                  .done(function(res) {
                    return _onSuccess(
                      res, _node, _data, _tree, _item, _status
                    );
                  })
                  .fail(function(xhr, status, error) {
                    return _onFailure(
                      xhr, status, error, _node, _data, _tree, _item, _status
                    );
                  });
              } else {
                _onCancel && typeof(_onCancel) == 'function' &&
                  _onCancel(_tree, _item, _data, _status);
              }
            },
          };
        });
      }

      var onCancel = function(_tree, _item, _data, _status) {
        _data.is_connecting = false;
        _tree.unload(_item);
        _tree.setInode(_item);
        _tree.removeIcon(_item);
        if (_data.shared && pgAdmin.server_mode == 'True'){
          _tree.addIcon(_item, {icon: 'icon-shared-server-not-connected'});
        }else{
          _tree.addIcon(_item, {icon: 'icon-server-not-connected'});
        }
        obj.trigger('connect:cancelled', data._id, data.db, obj, _item, _data);
        pgBrowser.Events.trigger(
          'pgadmin:server:connect:cancelled', data._id, _item, _data, obj
        );
        pgBrowser.Events.trigger(
          'pgadmin:database:connect:cancelled', data._id, data.db, _item, _data, obj
        );
        if (_status) {
          _tree.select(_item);
        }
      };

      /* Wait till the existing request completes */
      if(data.is_connecting) {
        return;
      }
      data.is_connecting = true;
      tree.setLeaf(item);
      tree.removeIcon(item);
      tree.addIcon(item, {icon: 'icon-server-connecting'});
      var url = obj.generate_url(item, 'connect', data, true);
      $.post(url)
        .done(function(res) {
          if (res.success == 1) {
            return onSuccess(
              res, obj, data, tree, item, wasConnected
            );
          }
        })
        .fail(function(xhr, status, error) {
          return onFailure(
            xhr, status, error, obj, data, tree, item, wasConnected
          );
        })
        .always(function(){
          data.is_connecting = false;
        });
    };
    var fetch_connection_status = function(obj, data, tree, item) {
      var url = obj.generate_url(item, 'connect', data, true);

      tree.setLeaf(item);
      tree.removeIcon(item);
      tree.addIcon(item, {icon: 'icon-server-connecting'});
      $.get(url)
        .done(function(res) {
          tree.setInode(item);
          if (res && res.data) {
            if (typeof res.data.icon == 'string') {
              tree.removeIcon(item);
              data.icon = res.data.icon;
              tree.addIcon(item, {icon: data.icon});
            }
            _.extend(data, res.data);

            var serverInfo = pgBrowser.serverInfo = pgBrowser.serverInfo || {};
            serverInfo[data._id] = _.extend({}, data);

            if(data.errmsg) {
              Alertify.error(data.errmsg);
            }
          }
        })
        .fail(function(xhr, status, error) {
          tree.setInode(item);
          if (data.shared && pgAdmin.server_mode == 'True'){
            tree.addIcon(item, {icon: 'icon-shared-server-not-connected'});
          }else{
            tree.addIcon(item, {icon: 'icon-server-not-connected'});
          }
          Alertify.pgRespErrorNotify(xhr, error);
        });
    };
  }

  return pgBrowser.Nodes['server'];
});
