Skip to content
Yvette Martinez edited this page Apr 12, 2022 · 11 revisions

All pages currently use Bootstrap 3.4 -- bumping up the version would require more work because bootstrap has updated some of the classes used on this website. For documentation see their docs: https://getbootstrap.com/docs/3.4/components/.

The templating engine used for front end is ejs. EJS can be a bit finicky when you start learning to use it because of the syntax so keep https://ejs.co on hand for documentation and help.

For information on how express works see: https://expressjs.com/en/4x/api.html#express

Some important req elements used throughout the website:

  • req.user: a copy of the user document from the users collection that is saved when a user is authenticated. This is used to ensure that a user has the correct permissions for certain buttons, pages or features.

Log In

Views

The main appearance of the log in page is rendered using /views/login.ejs. However, in the event that the authentication fails, a banner is rendered using the /views/wrong_pass.ejs.

Routes

All the routes used for the login page are located in /routes/auth.

  • router.get('/login'): renders the initial login page.
  • router.get('/logout'): logs the user out by destroying the session and redirecting to the initial login page.

The following pages all use wrong_pass.ejs and render a wrong password message based off the message passed in as a variable:

  • router.get('/login_attempt_1vn9ub480ng49'): email or password is incorrect
  • router.get('/login_attempt_2cn9rbu94gi4n'): GitHub ID is not in the database
  • router.get('/login_attempt_3poiux93jxm023'): LNGS credentials are incorrect
  • router.get('/login_attempt_4sowc37fbw0fjy3f'): LNGS credentials not in the database and therefore accounts must be linked

The following routes allow for authentication through Passport.js:

  • router.get('/github'): Is called when the GitHub button is clicked to login
  • router.post('/ldap'): Is called when the LDAP form in LNGSmodal is submitted with a username and password
  • router.post('/password'): Is called when the email form in localModal is submitted with a username and password

The following route creates the callback link necessary to complete GitHub authentication. This should match the callback link in the ENV variables and in the OAuth App on GitHub.

  • router.get('/github/callback')

API

All API calls that are used in the login process can be found in config/passport.js. To authenticate with GitHub (https://www.passportjs.org/packages/passport-github2/), email (https://www.passportjs.org/packages/passport-local/) or LNGS (https://www.passportjs.org/packages/passport-ldapauth/), the passport.use() function is used.

  • GitHub only: In the event that a user is able to authenticate with their GitHub account, @octokit/rest (https://octokit.github.io/rest.js/v18/) is used in order to ensure that the user is both part of XENON1T and XENONnT GitHub organizations.
  • If the authentication is successful, PopulateProfile() is called in order to populate the profile for the authenticated user. This will allow for their information to be saved and used throughout the session.
  • The MongoDB users collection is also queried to ensure that the email, GitHub or LNGS ID match with someone in our database. If not, their authentication fails.

Additional functions used in the login process:

  • logToFile(): if an error occurs during the login process, this function logs the error to the specified file

Profile

Shifts

Full Directory

Views

The main appearance for the full directory is located in views/fulldirectory.

Components

Some key components on this page are:

  • updateUserModal: modal that contains a form formUpdateUser which allows users to be edited. When the form is submitted it makes a POST request to the route as defined by openModal()
  • newUserModal: modal that contains a form formAddUser which allows users to be added. When the form is submitted it makes a POST request to /shifts/users/adduser

JavaScripts

The following javascript functions are found in /javascripts/directory_scripts.js:

Initializing Tables

InitializeTable(), InitializePrevTable(), TechTable(), and PrevTechTable() initializes the table that displays all the current XENONnT members, members who have left the collaboration, current engineers/tech and all the engineers and tech who have left, respectively.

All tables are built using DataTables and any dates that are displayed are formatted using momentjs.

Data
The data for tables are loaded in with an AJAX call using a POST request to a route which gets the necessary mongoDB data from the collection and returns it (see the Routes section for more information on how this functions).
Example code:

 ajax: {
      url: '/shifts/curr_table_info',
      type: 'POST',
 }

Tooltips
Tooltips that appear next to the 'explanations' row are built using JQuery UI tooltips.

Rendering Dates As mentioned above, momentjs is used in order to format dates. We specifically use the MMM YYYY format (displays as 'Jan 2020'). It is important that the date listed in the data is an ISODate. If the data gives an undefined value, we format the displayed values as an empty string. Otherwise, it is formatted in MMM YYYY format using the Atlantic/St.Helena timezone so all viewers see the data displayed in the same timezone. See code excerpt below.

render: function(data) {
          if (typeof(data) === 'undefined') {
            return '';
          }
          return moment(data).tz('Atlantic/St_Helena').format('MMM YYYY');
        }

Sorting Dates
Since dates are formatted into a MMM YYYY format (ex: 'Jan 2020'), they are displayed as strings on DataTables and therefore are sorted alphabetically instead of by date. In order to deal with this issue, a DataTables plugin to sort with momentjs is used.

$.fn.dataTable.moment( 'MMM YYYY' )

Grouping by Rows
The data is grouped by institute where each institute has a blue header row. This is built using the DataTables drawCallback function. The CSS is also added here to color the group header blue. See code excerpt below.

drawCallback: function(settings) {
      var api = this.api();
      var rows = api.rows({page: 'current'}).nodes();
      var last = null;
      
      api.column(groupColumn, {page: 'current'}).data().each(function(group,i) {
        if (last !== group) {
          $(rows).eq(i).before(
            '<tr class="group"><td colspan="9"><strong>' + group + 
              '</strong></td></tr>'
          )
          last = group;
        }
      })
    }

The institutes are then ordered by groups in ascending order.

order: [[groupColumn, 'asc']]

Edit button and modal
The rightmost column has a circular edit button which allows users with the correct permissions to edit the user in that row. The data of the current user is retrieved when the user clicks the button and if the user has the 'directory' permission or is a PI then the modal will open using the openModal function. Otherwise, an alert pops up saying they do not have the correct permissions.

$(tablediv + ' tbody').on('click', 'button', function(e) {
    e.preventDefault();
    var data = table.row($(this).parents('tr')).data();
    var user = data.current_user;
    if(user.groups.includes("directory") || (user.position === "PI")) {
      openModal(data, 'fulldirectory');
    } else {
      alert("Sorry, you don't have the correct permissions.");
    }
  });

Opening Edit Button Modal

The openModal() function takes in two parameters:

  • userInfo: The data of the user who is being updated
  • page: The page that this function was called on

Both parameters are used to create the URL assigned to the formUpdateUser action. See excerpt below.

document.getElementById('formUpdateUser').action = 
      'users/' + page + '/' + userInfo._id + '/updateContactInfoAdmin';

The function also autofills the values of the text boxes in the modal based off of the information in the database. See below for an example code excerpt of autofilling the position dropdown.

if (userInfo.position) {
  modal.find('.modal-body select[name="position"]').val(userInfo.position).prop('selected', true);
} else {
  modal.find('.modal-body select[name="position"]').val('default').prop('selected', true);
}

When the modal closes, all checkboxes and dropdowns must be cleared.

Text Validations

The following javascript functions are found in /javascripts/text-validations.js:
ValidateForm(elem, list): function that makes sure none of the text fields have an invalid class and that firest and last name fields are only in extended ASCII (uses isASCII() function). If the form does not pass all validations, error messages will show and the form will not submit.
Parameters

  • elem: the name of the form that is being validated
  • list: the list of names of elements that the ValiDate() function has checked ValiDate(elem): function that checks that date is in YYYY-MM-DD format for the text in the given element. If the date is valid, the "invalid" class will be removed but if its not valid, the "invalid" class will be added. Parameters
  • elem: the name of the element that will be validated

This javascript is actually in /javascripts/directory_scripts.js but I felt it grouped better with the validation functions.
suggestRemoveForm(elem): if the user types in a day before today in the text field for elem, they are shown a pop up asking them if they meant to remove a member and links to the remove member form. Note that these functions are suggestions and do not prevent the user from submitting the form. Parameters

  • elem: the name of the element that will be checked

Routes

Forms

Institutes

Clone this wiki locally