diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9e2d594def..1abeb1d63d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -62,6 +62,9 @@ jobs: - name: Run ShellCheck run: bundle exec rake test:shellcheck + - name: Check file encodings + run: bundle exec rake test:unix + - name: Run Zeitwerk check run: | cd apps/dashboard @@ -114,7 +117,6 @@ jobs: matrix: os: [ubuntu-22.04] dist: - - el7 - el8 - el9 - amzn2023 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 35b46e3517..bad647ee28 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,7 +23,7 @@ build-nightly: - bundle exec rake package:build[$OOD_PACKAGING_DIST,$OOD_PACKAGING_ARCH,true] parallel: matrix: - - OOD_PACKAGING_DIST: [el7, el8] + - OOD_PACKAGING_DIST: [el8] OOD_PACKAGING_ARCH: [x86_64, aarch64, ppc64le] OOD_PACKAGING_GPG_PRIVATE_KEY: /systems/osc_certs/gpg/ondemand/ondemand.sec - OOD_PACKAGING_DIST: [el9, debian-12] @@ -51,7 +51,7 @@ build: - bundle exec rake package:build[$OOD_PACKAGING_DIST,$OOD_PACKAGING_ARCH] parallel: matrix: - - OOD_PACKAGING_DIST: [el7, el8] + - OOD_PACKAGING_DIST: [el8] OOD_PACKAGING_ARCH: [x86_64, aarch64, ppc64le] OOD_PACKAGING_GPG_PRIVATE_KEY: /systems/osc_certs/gpg/ondemand/ondemand.sec - OOD_PACKAGING_DIST: [el9, debian-12] diff --git a/CHANGELOG.md b/CHANGELOG.md index 10e6ba96a0..906ab81534 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Admins can now set a `default_profile` in [3200](https://github.com/OSC/ondemand/pull/3200). - The files table now has a select all checkbox in [3212](https://github.com/OSC/ondemand/pull/3212). - Centers can now disable shell links at the app level in [3206](https://github.com/OSC/ondemand/pull/3206). +- Added a rake task to determine ensure unix file formats in [3227](https://github.com/OSC/ondemand/pull/3227). +- Batch Connect apps can now provide a completed.{md, html}.erb in [3269](https://github.com/OSC/ondemand/pull/3269). ### Fixed - Develop menu now correctly shows/hides when given a configuration in [2848](https://github.com/OSC/ondemand/pull/2848). @@ -48,6 +50,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - auto_modules can correctly set their label in [3139](https://github.com/OSC/ondemand/pull/3139). - Gnome desktops correctly work in [3188](https://github.com/OSC/ondemand/pull/3188). - `staged_root` is created as 700 to ensure it's writable by the user in [3202](https://github.com/OSC/ondemand/pull/3202). +- MOTD correclty shows up in custom pages in [3216](https://github.com/OSC/ondemand/pull/3216). +- Open OnDemand no longer builds el7 packages in [3232](https://github.com/OSC/ondemand/pull/3232). +- BatchConnect apps now use rsync with safer arguments in [3239](https://github.com/OSC/ondemand/pull/3239). ### Changed - Open OnDemand now requires NodeJS 18 and Ruby 3.1 on applicable platforms in [2885](https://github.com/OSC/ondemand/pull/2885). diff --git a/apps/dashboard/Gemfile b/apps/dashboard/Gemfile index 6cd4f78423..d99910df5e 100644 --- a/apps/dashboard/Gemfile +++ b/apps/dashboard/Gemfile @@ -21,6 +21,7 @@ gem 'sdoc', group: :doc, require: false group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug' + gem 'pry' gem 'climate_control', '~> 0.2' gem 'timecop', '~> 0.9' end diff --git a/apps/dashboard/Gemfile.lock b/apps/dashboard/Gemfile.lock index de8daf6391..bf84ff02ff 100644 --- a/apps/dashboard/Gemfile.lock +++ b/apps/dashboard/Gemfile.lock @@ -60,10 +60,11 @@ GEM minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) - addressable (2.8.5) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) autoprefixer-rails (10.2.5.1) execjs (> 0) + base64 (0.2.0) bootstrap_form (4.5.0) actionpack (>= 5.2) activemodel (>= 5.2) @@ -137,7 +138,7 @@ GEM method_source (1.0.0) mime-types (3.5.1) mime-types-data (~> 3.2015) - mime-types-data (3.2023.1003) + mime-types-data (3.2023.1205) mini_mime (1.1.5) mini_portile2 (2.8.5) minitest (5.20.0) @@ -173,12 +174,13 @@ GEM ood_support (0.0.5) pbs (2.2.1) ffi (~> 1.9, >= 1.9.6) - psych (5.1.1.1) + psych (5.1.2) stringio public_suffix (5.0.4) racc (1.7.3) rack (2.2.8) - rack-protection (3.1.0) + rack-protection (3.2.0) + base64 (>= 0.1.0) rack (~> 2.2, >= 2.2.4) rack-test (2.1.0) rack (>= 1.3) @@ -211,10 +213,10 @@ GEM rake (>= 12.2) thor (~> 1.0) rake (13.1.0) - rdoc (6.6.0) + rdoc (6.6.2) psych (>= 4.0.0) redcarpet (3.6.0) - regexp_parser (2.8.2) + regexp_parser (2.8.3) request_store (1.5.1) rack (>= 1.4) rest-client (2.1.0) @@ -234,16 +236,16 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) - sinatra (3.1.0) + sinatra (3.2.0) mustermann (~> 3.0) rack (~> 2.2, >= 2.2.4) - rack-protection (= 3.1.0) + rack-protection (= 3.2.0) tilt (~> 2.0) - sinatra-contrib (3.1.0) - multi_json + sinatra-contrib (3.2.0) + multi_json (>= 0.0.2) mustermann (~> 3.0) - rack-protection (= 3.1.0) - sinatra (= 3.1.0) + rack-protection (= 3.2.0) + sinatra (= 3.2.0) tilt (~> 2.0) sprockets (4.2.1) concurrent-ruby (~> 1.0) diff --git a/apps/dashboard/app/assets/images/favicon.ico b/apps/dashboard/app/assets/images/favicon.ico new file mode 100644 index 0000000000..d4a6dbb934 Binary files /dev/null and b/apps/dashboard/app/assets/images/favicon.ico differ diff --git a/apps/dashboard/app/assets/stylesheets/projects.scss b/apps/dashboard/app/assets/stylesheets/projects.scss index 40249a0409..7fedcd7c89 100644 --- a/apps/dashboard/app/assets/stylesheets/projects.scss +++ b/apps/dashboard/app/assets/stylesheets/projects.scss @@ -18,3 +18,11 @@ text-decoration: none !important; } } + +.job-info { + cursor: default; +} + +.job-info .job-info-description{ + display: block; +} diff --git a/apps/dashboard/app/controllers/apps_controller.rb b/apps/dashboard/app/controllers/apps_controller.rb index 9017f4ec03..313d2596b2 100644 --- a/apps/dashboard/app/controllers/apps_controller.rb +++ b/apps/dashboard/app/controllers/apps_controller.rb @@ -10,17 +10,6 @@ def index set_metadata_columns end - def featured - if OodSupport::Process.groups_changed? - redirect_to apps_restart_url - else - @title = nil - @groups = OodAppGroup.groups_for(apps: nav_usr_apps) - set_motd - set_my_quotas - end - end - def restart end diff --git a/apps/dashboard/app/controllers/files_controller.rb b/apps/dashboard/app/controllers/files_controller.rb index f737a4a57b..b71e581d59 100644 --- a/apps/dashboard/app/controllers/files_controller.rb +++ b/apps/dashboard/app/controllers/files_controller.rb @@ -13,7 +13,7 @@ def fs if @path.directory? @path.raise_if_cant_access_directory_contents - request.format = 'zip' if params[:download] + request.format = 'zip' if download? respond_to do |format| @@ -25,7 +25,12 @@ def fs response.headers['Cache-Control'] = 'no-store' if params[:can_download] # check to see if this directory can be downloaded as a zip - can_download, error_message = @path.can_download_as_zip? + can_download, error_message = if ::Configuration.download_enabled? + @path.can_download_as_zip? + else + [false, t('dashboard.files_download_not_enabled')] + end + render json: { can_download: can_download, error_message: error_message } else @files = @path.ls @@ -39,7 +44,11 @@ def fs # and we can avoid rescuing in a block so we can reintroduce # the block braces which is the Rails convention with the respond_to formats. format.zip do - can_download, error_message = @path.can_download_as_zip? + can_download, error_message = if ::Configuration.download_enabled? + @path.can_download_as_zip? + else + raise(StandardError, t('dashboard.files_download_not_enabled')) + end if can_download zipname = @path.basename.to_s.gsub('"', '\"') + '.zip' @@ -211,6 +220,10 @@ def posix_file? @path.is_a?(PosixFile) end + def download? + params[:download] + end + def uppy_upload_path # careful: # @@ -226,6 +239,8 @@ def uppy_upload_path end def show_file + raise(StandardError, t('dashboard.files_download_not_enabled')) unless ::Configuration.download_enabled? + if posix_file? send_posix_file else @@ -237,7 +252,7 @@ def send_posix_file type = Files.mime_type_by_extension(@path).presence || PosixFile.new(@path).mime_type # svgs aren't safe to view until we update our CSP - if params[:download] || type.to_s == 'image/svg+xml' + if download? || type.to_s == 'image/svg+xml' type = 'text/plain; charset=utf-8' if type.to_s == 'image/svg+xml' send_file @path, type: type else @@ -261,7 +276,7 @@ def send_remote_file end # svgs aren't safe to view until we update our CSP - download = params[:download] || type.to_s == "image/svg+xml" + download = download? || type.to_s == "image/svg+xml" type = "text/plain; charset=utf-8" if type.to_s == "image/svg+xml" response.set_header('X-Accel-Buffering', 'no') diff --git a/apps/dashboard/app/controllers/projects_controller.rb b/apps/dashboard/app/controllers/projects_controller.rb index bf029d032e..1db4279ef2 100644 --- a/apps/dashboard/app/controllers/projects_controller.rb +++ b/apps/dashboard/app/controllers/projects_controller.rb @@ -10,6 +10,8 @@ def show redirect_to(projects_path, alert: I18n.t('dashboard.jobs_project_not_found', project_id: project_id)) else @scripts = Script.all(@project.directory) + @valid_project = Script.clusters? + flash.now[:alert] = I18n.t("dashboard.jobs_project_invalid_configuration_clusters") unless @valid_project end end diff --git a/apps/dashboard/app/controllers/scripts_controller.rb b/apps/dashboard/app/controllers/scripts_controller.rb index fe93fdaae0..b03704de0c 100644 --- a/apps/dashboard/app/controllers/scripts_controller.rb +++ b/apps/dashboard/app/controllers/scripts_controller.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true - +require 'pry' # The controller for apps pages /dashboard/projects/:project_id/scripts class ScriptsController < ApplicationController @@ -45,7 +45,7 @@ def destroy end end - # POST /projects/:project_id/scripts/:id/save + # POST /projects/:project_id/:id/save # save the script after editing def save @script.update(save_script_params[:script]) @@ -90,7 +90,11 @@ def submit_script_params end def save_script_params - params.permit({ script: SAVE_SCRIPT_KEYS }, :project_id, :id) + params.permit({ script: SAVE_SCRIPT_KEYS + auto_environment_variable_params }, :project_id, :id) + end + + def auto_environment_variable_params + params[:script].keys.select { |key| key.match("auto_environment_variable") } end def find_project diff --git a/apps/dashboard/app/controllers/settings_controller.rb b/apps/dashboard/app/controllers/settings_controller.rb index 5638bce2eb..350712a0f6 100644 --- a/apps/dashboard/app/controllers/settings_controller.rb +++ b/apps/dashboard/app/controllers/settings_controller.rb @@ -2,11 +2,12 @@ # The Controller for user level settings /dashboard/settings. class SettingsController < ApplicationController + include UserSettingStore ALLOWED_SETTINGS = [:profile].freeze def update new_settings = read_settings(settings_param) - CurrentUser.update_user_settings(new_settings) unless new_settings.empty? + update_user_settings(new_settings) unless new_settings.empty? logger.info "settings: updated user settings to: #{new_settings}" respond_to do |format| diff --git a/apps/dashboard/app/helpers/application_helper.rb b/apps/dashboard/app/helpers/application_helper.rb index 838d696e18..f587098097 100644 --- a/apps/dashboard/app/helpers/application_helper.rb +++ b/apps/dashboard/app/helpers/application_helper.rb @@ -56,6 +56,14 @@ def fa_icon(icon, fa_style: 'fas', id: '', classes: 'app-icon', title: "FontAwes title: title, "aria-hidden": true) end + def favicon + if Rails.env.production? + favicon_link_tag('favicon.ico', href: @user_configuration.public_url.join('favicon.ico'), skip_pipeline: true) + else + favicon_link_tag('favicon.ico') + end + end + def app_icon_tag(app) if app.image_icon? image_tag app.icon_uri, class: 'app-icon', title: app.icon_path diff --git a/apps/dashboard/app/helpers/batch_connect/sessions_helper.rb b/apps/dashboard/app/helpers/batch_connect/sessions_helper.rb index 3c11a0fed1..e72899fd12 100644 --- a/apps/dashboard/app/helpers/batch_connect/sessions_helper.rb +++ b/apps/dashboard/app/helpers/batch_connect/sessions_helper.rb @@ -44,11 +44,12 @@ def session_view(session) concat content_tag(:div, cancel_or_delete(session), class: 'float-right') concat host(session) concat created(session) - concat session_time(session) + concat render_session_time(session) concat id(session) concat support_ticket(session) unless @user_configuration.support_ticket.empty? concat display_choices(session) safe_concat custom_info_view(session) if session.app.session_info_view + safe_concat completed_view(session) if session.app.session_completed_view && session.completed? end ) concat content_tag(:div) { yield } @@ -68,6 +69,10 @@ def custom_info_view(session) end end + def completed_view(session) + render(partial: 'batch_connect/sessions/card/completed_view', locals: { session: session }) + end + def created(session) render(partial: 'batch_connect/sessions/card/created', locals: { session: session }) end @@ -75,28 +80,23 @@ def created(session) def session_time(session) time_limit = session.info.wallclock_limit time_used = session.info.wallclock_time - - content_tag(:p) do - if session.starting? || session.running? - if time_limit && time_used - concat content_tag(:strong, t('dashboard.batch_connect_sessions_stats_time_remaining')) - concat " " - concat distance_of_time_in_words(time_limit - time_used, 0, false, :only => [:minutes, :hours], :accumulate_on => :hours) - elsif time_used - concat content_tag(:strong, t('dashboard.batch_connect_sessions_stats_time_used')) - concat " " - concat distance_of_time_in_words(time_used, 0, false, :only => [:minutes, :hours], :accumulate_on => :hours) - end - else # not starting or running - if time_limit - concat content_tag(:strong, t('dashboard.batch_connect_sessions_stats_time_requested')) - concat " " - concat distance_of_time_in_words(time_limit, 0, false, :only => [:minutes, :hours], :accumulate_on => :hours) - end + if session.starting? || session.running? + if time_limit && time_used + [t('dashboard.batch_connect_sessions_stats_time_remaining'), distance_of_time_in_words(time_limit - time_used, 0, false, :only => [:minutes, :hours], :accumulate_on => :hours)] + elsif time_used + [t('dashboard.batch_connect_sessions_stats_time_used'), distance_of_time_in_words(time_used, 0, false, :only => [:minutes, :hours], :accumulate_on => :hours)] + end + else + if time_limit + [t('dashboard.batch_connect_sessions_stats_time_requested'), distance_of_time_in_words(time_limit, 0, false, :only => [:minutes, :hours], :accumulate_on => :hours)] end end end + def render_session_time(session) + render(partial: 'batch_connect/sessions/card/session_time', locals: { session: session }) + end + def host(session) render(partial: 'batch_connect/sessions/card/host', locals: { session: session }) end diff --git a/apps/dashboard/app/helpers/scripts_helper.rb b/apps/dashboard/app/helpers/scripts_helper.rb index 92879bf762..b82a88add2 100644 --- a/apps/dashboard/app/helpers/scripts_helper.rb +++ b/apps/dashboard/app/helpers/scripts_helper.rb @@ -11,11 +11,15 @@ def create_editable_widget(form, attrib, format: nil) attrib.opts[:fixed] = false locals = { form: form, attrib: attrib, format: format, fixed: fixed_attribute } + binding.pry if attrib.id == "auto_environment_variable" + case widget when 'number_field' render(partial: editable_partial('editable_number'), locals: locals) when 'select' render(partial: editable_partial('editable_select'), locals: locals) + when 'key_value_pair' + render(partial: editable_partial('editable_key_value_pair'), locals: locals) else render(partial: editable_partial('generic'), locals: locals) end @@ -53,6 +57,11 @@ def auto_accounts_template create_editable_widget(script_form_double, attrib) end + def auto_environment_variable_template + attrib = SmartAttributes::AttributeFactory.build_auto_environment_variable + create_editable_widget(script_form_double, attrib) + end + # We need a form builder to build the template divs. These are # templates so that they are not a part of the _actual_ form (yet). # Otherwise you'd have required fields that you cannot actually edit diff --git a/apps/dashboard/app/javascript/dynamic_forms.js b/apps/dashboard/app/javascript/dynamic_forms.js index 544e8feabd..cce62fdcb2 100644 --- a/apps/dashboard/app/javascript/dynamic_forms.js +++ b/apps/dashboard/app/javascript/dynamic_forms.js @@ -709,6 +709,11 @@ function optionForFromToken(str) { } let optionForValue = mountainCaseWords(document.getElementById(optionForId).value); + // handle special case where the very first token here is a number. + // browsers expect a prefix of hyphens as if it's the next token. + if (optionForValue.match(/^\d/)) { + optionForValue = `-${optionForValue}`; + } hide = option.dataset[`optionFor${optionFor}${optionForValue}`] === 'false'; if (hide) { diff --git a/apps/dashboard/app/javascript/files/clip_board.js b/apps/dashboard/app/javascript/files/clip_board.js index 45249d9047..d8e230ccab 100755 --- a/apps/dashboard/app/javascript/files/clip_board.js +++ b/apps/dashboard/app/javascript/files/clip_board.js @@ -1,181 +1,181 @@ -import ClipboardJS from 'clipboard' -import Handlebars from 'handlebars'; -import {CONTENTID} from './data_table.js'; -import {EVENTNAME as SWAL_EVENTNAME} from './sweet_alert.js'; -import {EVENTNAME as FILEOPS_EVENTNAME} from './file_ops.js'; -import { csrfToken } from '../config.js'; - -export {EVENTNAME}; - -const EVENTNAME = { - clearClipboard: 'clearClipboard', - updateClipboard: 'updateClipboard', - updateClipboardView: 'updateClipboardView', -} - -jQuery(function () { - - var clipBoard = new ClipBoard(); - - $("#copy-move-btn").on("click", function () { - let table = $(CONTENTID).DataTable(); - let selection = table.rows({ selected: true }).data(); - - const eventData = { - selection: selection - }; - - $(CONTENTID).trigger(EVENTNAME.updateClipboard, eventData); - - }); - - - $(CONTENTID).on('success', function (e) { - $(e.trigger).tooltip({ title: 'Copied path to clipboard!', trigger: 'manual', placement: 'bottom' }).tooltip('show'); - setTimeout(() => $(e.trigger).tooltip('hide'), 2000); - e.clearSelection(); - }); - - $(CONTENTID).on('error', function (e) { - e.clearSelection(); - }); - - $(CONTENTID).on(EVENTNAME.clearClipboard, function (e, options) { - clipBoard.clearClipboard(); - clipBoard.updateViewForClipboard(); - }); - - $(CONTENTID).on(EVENTNAME.updateClipboard, function (e, options) { - if (options.selection.length == 0) { - const eventData = { - 'title': 'Select a file, files, or directory to copy or move.', - 'message': 'You have selected none.', - }; - - $(CONTENTID).trigger(SWAL_EVENTNAME.showError, eventData); - $(CONTENTID).trigger(EVENTNAME.clearClipbaord, eventData); - - } else { - clipBoard.updateClipboardFromSelection(options.selection); - clipBoard.updateViewForClipboard(); - } - }); - - $(CONTENTID).on(EVENTNAME.updateClipboardView, function (e, options) { - clipBoard.updateViewForClipboard(); - }); - - -}); - -class ClipBoard { - _clipBoard = null; - - constructor() { - this._clipBoard = new ClipboardJS('#copy-path'); - this.updateViewForClipboard(); - } - - getClipBoard() { - return this._clipBoard; - } - - clearClipboard() { - localStorage.removeItem('filesClipboard'); - } - - updateClipboardFromSelection(selection) { - - if (selection.length == 0) { - this.clearClipboard(); - } else { - let clipboardData = { - from: history.state.currentDirectory, - from_fs: history.state.currentFilesystem, - files: selection.toArray().map((f) => { - return { directory: f.type == 'd', name: f.name }; - }) - }; - - localStorage.setItem('filesClipboard', JSON.stringify(clipboardData)); - } - } - - - updateViewForClipboard() { - let clipboard = JSON.parse(localStorage.getItem('filesClipboard') || '{}'), - template_str = $('#clipboard-template').html(), - template = Handlebars.compile(template_str); - - $('#clipboard').html(template(clipboard)); - - $('#clipboard-clear').on("click", () => { - this.clearClipboard(); - this.updateViewForClipboard(); - }); - - $('#clipboard-move-to-dir').on("click", () => { - let clipboard = JSON.parse(localStorage.getItem('filesClipboard') || 'null'); - if (clipboard) { - clipboard.to = history.state.currentDirectory; - clipboard.to_fs = history.state.currentFilesystem; - - if (clipboard.from == clipboard.to) { - // No files are changed, so we just have to clear and update the clipboard - this.clearClipboard(); - this.updateViewForClipboard(); - } - else { - let files = {}; - clipboard.files.forEach((f) => { - files[`${clipboard.from}/${f.name}`] = `${history.state.currentDirectory}/${f.name}` - }); - - const eventData = { - 'files': files, - 'token': csrfToken(), - 'from_fs': clipboard.from_fs, - 'to_fs': clipboard.to_fs, - }; - - $(CONTENTID).trigger(FILEOPS_EVENTNAME.moveFile, eventData); - } - } - else { - console.error('files clipboard is empty'); - } - }); - - - $('#clipboard-copy-to-dir').on("click", () => { - let clipboard = JSON.parse(localStorage.getItem('filesClipboard') || 'null'); - - if (clipboard) { - clipboard.to = history.state.currentDirectory; - clipboard.to_fs = history.state.currentFilesystem; - - // files is a hashmap with keys of file current path and value as the corresponding files desired path - let files = {}; - - clipboard.files.forEach((f) => { - files[`${clipboard.from}/${f.name}`] = `${clipboard.to}/${f.name}`; - }); - - const eventData = { - 'files': files, - 'token': csrfToken(), - 'from_fs': clipboard.from_fs, - 'to_fs': clipboard.to_fs, - }; - - $(CONTENTID).trigger(FILEOPS_EVENTNAME.copyFile, eventData); - } - else { - console.error('files clipboard is empty'); - } - }); - - } - - -} +import ClipboardJS from 'clipboard' +import Handlebars from 'handlebars'; +import {CONTENTID} from './data_table.js'; +import {EVENTNAME as SWAL_EVENTNAME} from './sweet_alert.js'; +import {EVENTNAME as FILEOPS_EVENTNAME} from './file_ops.js'; +import { csrfToken } from '../config.js'; + +export {EVENTNAME}; + +const EVENTNAME = { + clearClipboard: 'clearClipboard', + updateClipboard: 'updateClipboard', + updateClipboardView: 'updateClipboardView', +} + +jQuery(function () { + + var clipBoard = new ClipBoard(); + + $("#copy-move-btn").on("click", function () { + let table = $(CONTENTID).DataTable(); + let selection = table.rows({ selected: true }).data(); + + const eventData = { + selection: selection + }; + + $(CONTENTID).trigger(EVENTNAME.updateClipboard, eventData); + + }); + + + $(CONTENTID).on('success', function (e) { + $(e.trigger).tooltip({ title: 'Copied path to clipboard!', trigger: 'manual', placement: 'bottom' }).tooltip('show'); + setTimeout(() => $(e.trigger).tooltip('hide'), 2000); + e.clearSelection(); + }); + + $(CONTENTID).on('error', function (e) { + e.clearSelection(); + }); + + $(CONTENTID).on(EVENTNAME.clearClipboard, function (e, options) { + clipBoard.clearClipboard(); + clipBoard.updateViewForClipboard(); + }); + + $(CONTENTID).on(EVENTNAME.updateClipboard, function (e, options) { + if (options.selection.length == 0) { + const eventData = { + 'title': 'Select a file, files, or directory to copy or move.', + 'message': 'You have selected none.', + }; + + $(CONTENTID).trigger(SWAL_EVENTNAME.showError, eventData); + $(CONTENTID).trigger(EVENTNAME.clearClipbaord, eventData); + + } else { + clipBoard.updateClipboardFromSelection(options.selection); + clipBoard.updateViewForClipboard(); + } + }); + + $(CONTENTID).on(EVENTNAME.updateClipboardView, function (e, options) { + clipBoard.updateViewForClipboard(); + }); + + +}); + +class ClipBoard { + _clipBoard = null; + + constructor() { + this._clipBoard = new ClipboardJS('#copy-path'); + this.updateViewForClipboard(); + } + + getClipBoard() { + return this._clipBoard; + } + + clearClipboard() { + localStorage.removeItem('filesClipboard'); + } + + updateClipboardFromSelection(selection) { + + if (selection.length == 0) { + this.clearClipboard(); + } else { + let clipboardData = { + from: history.state.currentDirectory, + from_fs: history.state.currentFilesystem, + files: selection.toArray().map((f) => { + return { directory: f.type == 'd', name: f.name }; + }) + }; + + localStorage.setItem('filesClipboard', JSON.stringify(clipboardData)); + } + } + + + updateViewForClipboard() { + let clipboard = JSON.parse(localStorage.getItem('filesClipboard') || '{}'), + template_str = $('#clipboard-template').html(), + template = Handlebars.compile(template_str); + + $('#clipboard').html(template(clipboard)); + + $('#clipboard-clear').on("click", () => { + this.clearClipboard(); + this.updateViewForClipboard(); + }); + + $('#clipboard-move-to-dir').on("click", () => { + let clipboard = JSON.parse(localStorage.getItem('filesClipboard') || 'null'); + if (clipboard) { + clipboard.to = history.state.currentDirectory; + clipboard.to_fs = history.state.currentFilesystem; + + if (clipboard.from == clipboard.to) { + // No files are changed, so we just have to clear and update the clipboard + this.clearClipboard(); + this.updateViewForClipboard(); + } + else { + let files = {}; + clipboard.files.forEach((f) => { + files[`${clipboard.from}/${f.name}`] = `${history.state.currentDirectory}/${f.name}` + }); + + const eventData = { + 'files': files, + 'token': csrfToken(), + 'from_fs': clipboard.from_fs, + 'to_fs': clipboard.to_fs, + }; + + $(CONTENTID).trigger(FILEOPS_EVENTNAME.moveFile, eventData); + } + } + else { + console.error('files clipboard is empty'); + } + }); + + + $('#clipboard-copy-to-dir').on("click", () => { + let clipboard = JSON.parse(localStorage.getItem('filesClipboard') || 'null'); + + if (clipboard) { + clipboard.to = history.state.currentDirectory; + clipboard.to_fs = history.state.currentFilesystem; + + // files is a hashmap with keys of file current path and value as the corresponding files desired path + let files = {}; + + clipboard.files.forEach((f) => { + files[`${clipboard.from}/${f.name}`] = `${clipboard.to}/${f.name}`; + }); + + const eventData = { + 'files': files, + 'token': csrfToken(), + 'from_fs': clipboard.from_fs, + 'to_fs': clipboard.to_fs, + }; + + $(CONTENTID).trigger(FILEOPS_EVENTNAME.copyFile, eventData); + } + else { + console.error('files clipboard is empty'); + } + }); + + } + + +} diff --git a/apps/dashboard/app/javascript/files/sweet_alert.js b/apps/dashboard/app/javascript/files/sweet_alert.js index 04aebb5144..b739e1a4d5 100644 --- a/apps/dashboard/app/javascript/files/sweet_alert.js +++ b/apps/dashboard/app/javascript/files/sweet_alert.js @@ -1,91 +1,91 @@ -import Swal from 'sweetalert2' -import {CONTENTID, EVENTNAME as DATATABLE_EVENTNAME} from './data_table.js'; - -export {EVENTNAME}; - -const EVENTNAME = { - showError: 'showError', - showInput: 'showInput', - showLoading: 'showLoading', - showPrompt: 'showPrompt', - closeSwal: 'closeSwal', -} - -let sweetAlert = null; - -jQuery(function() { - sweetAlert = new SweetAlert(); - $(CONTENTID).on(EVENTNAME.showError, function(e,options) { - sweetAlert.alertError(options.title, options.message); - }); - - $(CONTENTID).on(EVENTNAME.showPrompt, function(e,options) { - sweetAlert.alertError(options.title, options.message); - }); - - $(CONTENTID).on(EVENTNAME.showInput, function(e,options) { - sweetAlert.input(options); - }); - - $(CONTENTID).on(EVENTNAME.showLoading, function(e,options) { - sweetAlert.loading(options.message); - }); - - $(CONTENTID).on(EVENTNAME.closeSwal, function() { - sweetAlert.close(); - }); - -}); - -class SweetAlert { - - constructor() { - this.setMixin(); - } - - input(options) { - Swal.fire(options.inputOptions) - .then (function(result){ - if(result.isConfirmed) { - const eventData = { - result: result, - files: options.files ? options.files : null - }; - - $(CONTENTID).trigger(options.action, eventData); - } else { - $(CONTENTID).trigger(DATATABLE_EVENTNAME.reloadTable); - } - }); - } - - setMixin() { - Swal.mixIn = ({ - showClass: { - popup: 'swal2-noanimation', - backdrop: 'swal2-noanimation' - }, - hideClass: { - popup: '', - backdrop: '' - } - }); - } - - alertError(error_title, error_message) { - Swal.fire(error_title, error_message, 'error'); - } - - async loading(title) { - Swal.fire({ - title: title, - allowOutsideClick: false, - showConfirmButton: false, - willOpen: () => { Swal.showLoading() } - }); - } - - close() { - Swal.close(); - } -} +import Swal from 'sweetalert2' +import {CONTENTID, EVENTNAME as DATATABLE_EVENTNAME} from './data_table.js'; + +export {EVENTNAME}; + +const EVENTNAME = { + showError: 'showError', + showInput: 'showInput', + showLoading: 'showLoading', + showPrompt: 'showPrompt', + closeSwal: 'closeSwal', +} + +let sweetAlert = null; + +jQuery(function() { + sweetAlert = new SweetAlert(); + $(CONTENTID).on(EVENTNAME.showError, function(e,options) { + sweetAlert.alertError(options.title, options.message); + }); + + $(CONTENTID).on(EVENTNAME.showPrompt, function(e,options) { + sweetAlert.alertError(options.title, options.message); + }); + + $(CONTENTID).on(EVENTNAME.showInput, function(e,options) { + sweetAlert.input(options); + }); + + $(CONTENTID).on(EVENTNAME.showLoading, function(e,options) { + sweetAlert.loading(options.message); + }); + + $(CONTENTID).on(EVENTNAME.closeSwal, function() { + sweetAlert.close(); + }); + +}); + +class SweetAlert { + + constructor() { + this.setMixin(); + } + + input(options) { + Swal.fire(options.inputOptions) + .then (function(result){ + if(result.isConfirmed) { + const eventData = { + result: result, + files: options.files ? options.files : null + }; + + $(CONTENTID).trigger(options.action, eventData); + } else { + $(CONTENTID).trigger(DATATABLE_EVENTNAME.reloadTable); + } + }); + } + + setMixin() { + Swal.mixIn = ({ + showClass: { + popup: 'swal2-noanimation', + backdrop: 'swal2-noanimation' + }, + hideClass: { + popup: '', + backdrop: '' + } + }); + } + + alertError(error_title, error_message) { + Swal.fire(error_title, error_message, 'error'); + } + + async loading(title) { + Swal.fire({ + title: title, + allowOutsideClick: false, + showConfirmButton: false, + willOpen: () => { Swal.showLoading() } + }); + } + + close() { + Swal.close(); + } +} diff --git a/apps/dashboard/app/javascript/files/uppy_ops.js b/apps/dashboard/app/javascript/files/uppy_ops.js index c9ec671c7c..bd5c1053a2 100755 --- a/apps/dashboard/app/javascript/files/uppy_ops.js +++ b/apps/dashboard/app/javascript/files/uppy_ops.js @@ -1,181 +1,181 @@ -import { Uppy, BasePlugin } from '@uppy/core' -import Dashboard from '@uppy/dashboard' -import XHRUpload from '@uppy/xhr-upload' -import _ from 'lodash'; -import {CONTENTID, EVENTNAME as DATATABLE_EVENTNAME} from './data_table.js'; -import { maxFileSize, csrfToken, uppyLocale } from '../config.js'; - -let uppy = null; - -jQuery(function() { - - class EmptyDirCreator extends BasePlugin { - constructor (uppy, opts){ - super(uppy, opts) - this.id = this.opts.id || 'EmptyDirUploaderCatcher'; - this.type = 'acquirer'; - - this.empty_dirs = []; - this.last_entries = []; - - this.handleRootDrop = this.handleRootDrop.bind(this); - this.createEmptyDirs = this.createEmptyDirs.bind(this); - - this.uppy = uppy; - } - - - - handleRootDrop (e) { - // from https://github.com/transloadit/uppy/blob/7ce58beeb620df3df0640cb369f5d71e3d3f751f/packages/%40uppy/utils/src/getDroppedFiles/index.js - if (e.dataTransfer.items && e.dataTransfer.items[0] && 'webkitGetAsEntry' in e.dataTransfer.items[0]) { - // toArray https://github.com/transloadit/uppy/blob/7ce58beeb620df3df0640cb369f5d71e3d3f751f/packages/%40uppy/utils/src/toArray.js#L4 - let items = Array.prototype.slice.call(e.dataTransfer.items || [], 0); - let entries = items.map(i => i.webkitGetAsEntry()).filter(i => i); - - return Promise.all(entries.map(i => getEmptyDirs(i))).then((dirs) => { - this.empty_dirs = this.empty_dirs.concat(_.flattenDeep(dirs)); - - }); - } - //else we don't have access to directory information - } - - createEmptyDirs (ids) { - if(! this.uppy.getState().error){ // avoid creating empty dirs if error occurred during upload - - //TODO: error checking and reporting - return Promise.all(this.empty_dirs.map((d) => { - // "fullPath" should actually be the path relative to the current directory - let filename = _.trimStart(d.fullPath, '/'); - - return fetch(`${history.state.currentDirectoryUrl}/${encodeURI(filename)}?dir=true`, {method: 'put', headers: { 'X-CSRF-Token': csrfToken() }}) - //TODO: parse json response verify if there was an error creating directory and handle error - - })).then(() => this.empty_dirs = []); - } - } - - install () { - this.uppy.addPostProcessor(this.createEmptyDirs); - } - - uninstall () { - this.uppy.removePostProcessor(this.createEmptyDirs); - } - } - - uppy = new Uppy({ - restrictions: { - maxFileSize: maxFileSize(), - }, - onBeforeUpload: updateEndpoint, - locale: uppyLocale(), - }); - - uppy.use(EmptyDirCreator); - uppy.use(Dashboard, { - trigger: '#upload-btn', - fileManagerSelectionType: 'both', - disableThumbnailGenerator: true, - showLinkToFileUploadResult: false, - closeModalOnClickOutside: true, - closeAfterFinish: true, - allowMultipleUploads: false, - onRequestCloseModal: () => closeAndResetUppyModal(uppy), - note: 'Empty directories will be included in the upload only when a directory upload is initiated via drag and drop. This is because the File and Directory Entries API is available only on a drop event, not during an input change event.' - }); - uppy.use(XHRUpload, { - withCredentials: true, - fieldName: 'file', - limit: 1, - headers: { 'X-CSRF-Token': csrfToken() }, - timeout: 128 * 1000, - }); - - uppy.on('file-added', (file) => { - uppy.setFileMeta(file.id, { parent: history.state.currentDirectory }); - if(file.meta.relativePath == null && file.data.webkitRelativePath){ - uppy.setFileMeta(file.id, { relativePath: file.data.webkitRelativePath }); - } - }); - - uppy.on('complete', (result) => { - if(result.successful.length > 0){ - reloadTable(); - } - }); - - // https://stackoverflow.com/questions/6756583/prevent-browser-from-loading-a-drag-and-dropped-file - window.addEventListener("dragover",function(e){ - e = e || event; - e.preventDefault(); - },false); - - window.addEventListener("drop",function(e){ - e = e || event; - e.preventDefault(); - },false); - - $('#directory-contents').on('drop', function(e){ - this.classList.remove('dragover'); - // Prevent default behavior (Prevent file from being opened) - - // pass drop event to uppy dashboard - uppy.getPlugin('Dashboard').openModal().then(() => uppy.getPlugin('Dashboard').handleDrop(e.originalEvent)) - }); - - $('#directory-contents').on('dragover', function(e){ - this.classList.add('dragover'); - - // Prevent default behavior (Prevent file from being opened) - e.preventDefault(); - - // specifies what feedback will be shown to the user by setting the dropEffect attribute of the DataTransfer associated with the event - // too bad we can't show an indicator (no dragstart/end when dragging from OS to browser) - e.originalEvent.dataTransfer.dropEffect = 'copy'; - }); - - $('#directory-contents').on('dragleave', function(e){ - this.classList.remove('dragover'); - }); - -}); - -function closeAndResetUppyModal(uppy){ - uppy.getPlugin('Dashboard').closeModal(); - uppy.reset(); -} - -function getEmptyDirs(entry){ - return new Promise((resolve) => { - if(entry.isFile){ - resolve([]); - } - else{ - // getFilesAndDirectoriesFromDirectory has no return value, so turn this into a promise - getFilesAndDirectoriesFromDirectory(entry.createReader(), [], function(error){ console.error(error)}, { - onSuccess: (entries) => { - if(entries.length == 0){ - // this is an empty directory - resolve([entry]); - } - else{ - Promise.all(entries.map(e => getEmptyDirs(e))).then((dirs) => resolve(_.flattenDeep(dirs))); - } - } - }) - } - }); -} - -function updateEndpoint() { - uppy.getPlugin('XHRUpload').setOptions({ - endpoint: history.state.currentFilesUploadPath, - }); -} - -function reloadTable() { - $(CONTENTID).trigger(DATATABLE_EVENTNAME.reloadTable,{}); -} - +import { Uppy, BasePlugin } from '@uppy/core' +import Dashboard from '@uppy/dashboard' +import XHRUpload from '@uppy/xhr-upload' +import _ from 'lodash'; +import {CONTENTID, EVENTNAME as DATATABLE_EVENTNAME} from './data_table.js'; +import { maxFileSize, csrfToken, uppyLocale } from '../config.js'; + +let uppy = null; + +jQuery(function() { + + class EmptyDirCreator extends BasePlugin { + constructor (uppy, opts){ + super(uppy, opts) + this.id = this.opts.id || 'EmptyDirUploaderCatcher'; + this.type = 'acquirer'; + + this.empty_dirs = []; + this.last_entries = []; + + this.handleRootDrop = this.handleRootDrop.bind(this); + this.createEmptyDirs = this.createEmptyDirs.bind(this); + + this.uppy = uppy; + } + + + + handleRootDrop (e) { + // from https://github.com/transloadit/uppy/blob/7ce58beeb620df3df0640cb369f5d71e3d3f751f/packages/%40uppy/utils/src/getDroppedFiles/index.js + if (e.dataTransfer.items && e.dataTransfer.items[0] && 'webkitGetAsEntry' in e.dataTransfer.items[0]) { + // toArray https://github.com/transloadit/uppy/blob/7ce58beeb620df3df0640cb369f5d71e3d3f751f/packages/%40uppy/utils/src/toArray.js#L4 + let items = Array.prototype.slice.call(e.dataTransfer.items || [], 0); + let entries = items.map(i => i.webkitGetAsEntry()).filter(i => i); + + return Promise.all(entries.map(i => getEmptyDirs(i))).then((dirs) => { + this.empty_dirs = this.empty_dirs.concat(_.flattenDeep(dirs)); + + }); + } + //else we don't have access to directory information + } + + createEmptyDirs (ids) { + if(! this.uppy.getState().error){ // avoid creating empty dirs if error occurred during upload + + //TODO: error checking and reporting + return Promise.all(this.empty_dirs.map((d) => { + // "fullPath" should actually be the path relative to the current directory + let filename = _.trimStart(d.fullPath, '/'); + + return fetch(`${history.state.currentDirectoryUrl}/${encodeURI(filename)}?dir=true`, {method: 'put', headers: { 'X-CSRF-Token': csrfToken() }}) + //TODO: parse json response verify if there was an error creating directory and handle error + + })).then(() => this.empty_dirs = []); + } + } + + install () { + this.uppy.addPostProcessor(this.createEmptyDirs); + } + + uninstall () { + this.uppy.removePostProcessor(this.createEmptyDirs); + } + } + + uppy = new Uppy({ + restrictions: { + maxFileSize: maxFileSize(), + }, + onBeforeUpload: updateEndpoint, + locale: uppyLocale(), + }); + + uppy.use(EmptyDirCreator); + uppy.use(Dashboard, { + trigger: '#upload-btn', + fileManagerSelectionType: 'both', + disableThumbnailGenerator: true, + showLinkToFileUploadResult: false, + closeModalOnClickOutside: true, + closeAfterFinish: true, + allowMultipleUploads: false, + onRequestCloseModal: () => closeAndResetUppyModal(uppy), + note: 'Empty directories will be included in the upload only when a directory upload is initiated via drag and drop. This is because the File and Directory Entries API is available only on a drop event, not during an input change event.' + }); + uppy.use(XHRUpload, { + withCredentials: true, + fieldName: 'file', + limit: 1, + headers: { 'X-CSRF-Token': csrfToken() }, + timeout: 128 * 1000, + }); + + uppy.on('file-added', (file) => { + uppy.setFileMeta(file.id, { parent: history.state.currentDirectory }); + if(file.meta.relativePath == null && file.data.webkitRelativePath){ + uppy.setFileMeta(file.id, { relativePath: file.data.webkitRelativePath }); + } + }); + + uppy.on('complete', (result) => { + if(result.successful.length > 0){ + reloadTable(); + } + }); + + // https://stackoverflow.com/questions/6756583/prevent-browser-from-loading-a-drag-and-dropped-file + window.addEventListener("dragover",function(e){ + e = e || event; + e.preventDefault(); + },false); + + window.addEventListener("drop",function(e){ + e = e || event; + e.preventDefault(); + },false); + + $('#directory-contents').on('drop', function(e){ + this.classList.remove('dragover'); + // Prevent default behavior (Prevent file from being opened) + + // pass drop event to uppy dashboard + uppy.getPlugin('Dashboard').openModal().then(() => uppy.getPlugin('Dashboard').handleDrop(e.originalEvent)) + }); + + $('#directory-contents').on('dragover', function(e){ + this.classList.add('dragover'); + + // Prevent default behavior (Prevent file from being opened) + e.preventDefault(); + + // specifies what feedback will be shown to the user by setting the dropEffect attribute of the DataTransfer associated with the event + // too bad we can't show an indicator (no dragstart/end when dragging from OS to browser) + e.originalEvent.dataTransfer.dropEffect = 'copy'; + }); + + $('#directory-contents').on('dragleave', function(e){ + this.classList.remove('dragover'); + }); + +}); + +function closeAndResetUppyModal(uppy){ + uppy.getPlugin('Dashboard').closeModal(); + uppy.reset(); +} + +function getEmptyDirs(entry){ + return new Promise((resolve) => { + if(entry.isFile){ + resolve([]); + } + else{ + // getFilesAndDirectoriesFromDirectory has no return value, so turn this into a promise + getFilesAndDirectoriesFromDirectory(entry.createReader(), [], function(error){ console.error(error)}, { + onSuccess: (entries) => { + if(entries.length == 0){ + // this is an empty directory + resolve([entry]); + } + else{ + Promise.all(entries.map(e => getEmptyDirs(e))).then((dirs) => resolve(_.flattenDeep(dirs))); + } + } + }) + } + }); +} + +function updateEndpoint() { + uppy.getPlugin('XHRUpload').setOptions({ + endpoint: history.state.currentFilesUploadPath, + }); +} + +function reloadTable() { + $(CONTENTID).trigger(DATATABLE_EVENTNAME.reloadTable,{}); +} + diff --git a/apps/dashboard/app/javascript/path_selector/path_selector_data_table.js b/apps/dashboard/app/javascript/path_selector/path_selector_data_table.js index ed3caa8dc9..1811918d01 100644 --- a/apps/dashboard/app/javascript/path_selector/path_selector_data_table.js +++ b/apps/dashboard/app/javascript/path_selector/path_selector_data_table.js @@ -129,9 +129,8 @@ export class PathSelectorTable { // only reload table for directories. and correct last visited // if it's a file. if(pathType == 'f') { - const currentDir = this.getLastVisited(); - const fileName = url.split('/').slice(-1)[0]; - this.setLastVisited(`${currentDir}/${fileName}`); + const path = url.replace(this.filesPath, '').replaceAll('//','/'); + this.setLastVisited(path, pathType); } else { this.reloadTable(url); } @@ -154,14 +153,15 @@ export class PathSelectorTable { } selectPath(_event) { - const currentPath = this.getLastVisited(); + const last = this.getLastVisited(); const inputField = document.getElementById(this.inputFieldId); - inputField.value = currentPath; + inputField.value = last.path; $(`#${this.modalId}`).modal('hide'); } storageKey() { - return `${this.tableId}_last_visited`; + const underscore_path = window.location.pathname.replaceAll('/', '_'); + return `${this.tableId}${underscore_path}_last_visited`; } tableWrapper() { @@ -173,25 +173,35 @@ export class PathSelectorTable { getLastVisited() { const lastVisited = localStorage.getItem(this.storageKey()); if(lastVisited === null) { - return this.initialDirectory; + return { path: this.initialDirectory, type: 'd' }; } else { - return lastVisited; + return JSON.parse(lastVisited); } } - setLastVisited(path) { + setLastVisited(path, pathType = 'd') { + const item = { path: path, type: pathType }; if(path) { - localStorage.setItem(this.storageKey(), path); + localStorage.setItem(this.storageKey(), JSON.stringify(item)); } } initialUrl() { const last = this.getLastVisited(); + let path = undefined; + + // if the last visisted was a file, then set the initial + // url to the file's directory. + if(last.type == 'f') { + path = last.path.split('/').slice(0, -1).join('/'); + } else { + path = last.path; + } - if(last.startsWith('/')) { - return `${this.filesPath}${last}`; + if(path.startsWith('/')) { + return `${this.filesPath}${path}`; } else { - return `${this.filesPath}/${last}`; + return `${this.filesPath}/${path}`; } } diff --git a/apps/dashboard/app/javascript/projects.js b/apps/dashboard/app/javascript/projects.js index ec7fc44be6..8e40516387 100644 --- a/apps/dashboard/app/javascript/projects.js +++ b/apps/dashboard/app/javascript/projects.js @@ -23,7 +23,7 @@ function pollForJobInfo(element) { .then((response) => { if (!response.ok) { if(response.status === 404) { - // TODO + throw new Error('404 response while looking for job', { cause: response }); } else{ throw new Error('Not 2xx response while looking for job', { cause: response }); } @@ -39,14 +39,15 @@ function pollForJobInfo(element) { setTimeout(pollForJobInfo, 10000, element); } }) - .catch((error) => { - // TODO, show an error in the HTML + .catch((error) => { + element.innerHTML = jobInfoDiv(jobId, 'undetermined', error.message, 'Unable to find the job details'); }); } -function jobInfoDiv(jobId, state) { - return `
+function jobInfoDiv(jobId, state, stateTitle='', stateDescription='') { + return `
${jobId} - ${state.toUpperCase()} + ${state.toUpperCase()} + ${stateDescription}
`; } \ No newline at end of file diff --git a/apps/dashboard/app/javascript/script_edit.js b/apps/dashboard/app/javascript/script_edit.js index ed3b8024a0..3299cd27df 100644 --- a/apps/dashboard/app/javascript/script_edit.js +++ b/apps/dashboard/app/javascript/script_edit.js @@ -18,6 +18,10 @@ const newFieldData = { bc_num_slots: { label: "Nodes", help: "How many nodes the job will run on." + }, + auto_environment_variable: { + label: "Environment Variable", + help: "Add an environment variable." } } @@ -65,7 +69,8 @@ function updateNewFieldOptions(selectMenu) { const field = document.getElementById(`script_${newField}`); // if the field doesn't already exist, it's an option for a new field. - if(field === null) { + // TODO: maybe JS equiv of ALLOW_MULTIPLE_FIELDS.include?(newField) + if(field === null || newField == "auto_environment_variable") { const option = document.createElement("option"); option.value = newField; option.text = newFieldData[newField].label; @@ -143,11 +148,24 @@ function addInProgressField(event) { justAdded.find('[data-fixed-toggler]') .on('click', (event) => { toggleFixedField(event) }); + justAdded.find('[data-multiple-input="name"]') + .on('change', (event) => { updateMultiple(event) }); + const entireDiv = event.target.parentElement.parentElement.parentElement; entireDiv.remove(); enableNewFieldButton(); } +function updateMultiple(event) { + const group = event.target.parentElement.parentElement; + event.target.id += `_${event.target.value}` + event.target.name = `script[${event.target.id.replace("script_", "")}]`; + + let valueInput = group.children[1].children[1]; + valueInput.id = event.target.id.replace("_name", "") + "_value"; + valueInput.name = `script[${valueInput.id.replace("script_", "")}]`; +} + function fixExcludeBasedOnSelect(selectElement) { const excludeElementId = selectElement.dataset.excludeId; const selectOptions = Array.from(selectElement.options); diff --git a/apps/dashboard/app/lib/smart_attributes.rb b/apps/dashboard/app/lib/smart_attributes.rb index f60bc15c9e..247f293b1b 100644 --- a/apps/dashboard/app/lib/smart_attributes.rb +++ b/apps/dashboard/app/lib/smart_attributes.rb @@ -20,4 +20,5 @@ module SmartAttributes require 'smart_attributes/attributes/bc_queue' require 'smart_attributes/attributes/bc_vnc_idle' require 'smart_attributes/attributes/bc_vnc_resolution' + require 'smart_attributes/attributes/auto_environment_variable' end diff --git a/apps/dashboard/app/lib/smart_attributes/attributes/auto_environment_variable.rb b/apps/dashboard/app/lib/smart_attributes/attributes/auto_environment_variable.rb new file mode 100644 index 0000000000..96c10c17bf --- /dev/null +++ b/apps/dashboard/app/lib/smart_attributes/attributes/auto_environment_variable.rb @@ -0,0 +1,25 @@ +module SmartAttributes + class AttributeFactory + extend AccountCache + + def self.build_auto_environment_variable(opts = {}) + Attributes::AutoEnvironmentVariable.new('auto_environment_variable', opts) + end + end + + module Attributes + class AutoEnvironmentVariable < Attribute + def widget + 'key_value_pair' + end + + def label(*) + (opts[:label] || 'Environment Variable').to_s + end + + def options_to_serialize(fmt: nil) + opts[:job_environment] + end + end + end +end \ No newline at end of file diff --git a/apps/dashboard/app/models/batch_connect/app.rb b/apps/dashboard/app/models/batch_connect/app.rb index 8db4d6aa46..21289b725d 100644 --- a/apps/dashboard/app/models/batch_connect/app.rb +++ b/apps/dashboard/app/models/batch_connect/app.rb @@ -300,6 +300,14 @@ def session_info_view nil end + # Completed view used for session info if it exists + # @return [String, nil] session info + def session_completed_view + @session_completed_view ||= Pathname.new(root).glob("completed.{md,html}.erb").find(&:file?).try(:read) + rescue + nil + end + # Paths to custom javascript files # @return [Pathname] paths to custom javascript files that exist def custom_javascript_files diff --git a/apps/dashboard/app/models/batch_connect/session.rb b/apps/dashboard/app/models/batch_connect/session.rb index 9ab024ab6b..99f18f3cfd 100644 --- a/apps/dashboard/app/models/batch_connect/session.rb +++ b/apps/dashboard/app/models/batch_connect/session.rb @@ -53,6 +53,10 @@ def get_binding # Error message about failing to parse info view ERB template. # @return [String] error message attr_reader :render_info_view_error_message + + # Error message about failing to parse completed view ERB template. + # @return [String] error message + attr_reader :render_completed_view_error_message # Return parsed markdown from info.{md, html}.erb # @return [String, nil] return HTML if no error while parsing, else return nil @@ -64,6 +68,16 @@ def render_info_view nil end + # Return parsed markdown from completed.{md, html}.erb + # @return [String, nil] return HTML if no error while parsing, else return nil + def render_completed_view + @render_completed_view ||= OodAppkit.markdown.render(ERB.new(self.app.session_completed_view, nil, "-").result(binding)).html_safe if self.app.session_completed_view + rescue => e + @render_completed_view_error_message = "Error when rendering completed view: #{e.class} - #{e.message}" + Rails.logger.error(@render_completed_view_error_message) + nil + end + # Return the Batch Connect app from the session token # @return [BatchConnect::App] def app @@ -259,7 +273,7 @@ def stage(root, context: nil) staged_root.tap { |p| FileUtils.mkdir_p(p.to_s, mode: 0o0700) unless p.exist? } # Sync the template files over - oe, s = Open3.capture2e('rsync', '-av', '--exclude', '*.erb', "#{root}/", staged_root.to_s) + oe, s = Open3.capture2e('rsync', '-rlpv', '--exclude', '*.erb', "#{root}/", staged_root.to_s) raise oe unless s.success? # Output user submitted context attributes for debugging purposes diff --git a/apps/dashboard/app/models/script.rb b/apps/dashboard/app/models/script.rb index 15e678c295..4b8da5fd52 100644 --- a/apps/dashboard/app/models/script.rb +++ b/apps/dashboard/app/models/script.rb @@ -45,6 +45,11 @@ def from_yaml(file, project_dir) def next_id SecureRandom.alphanumeric(8).downcase end + + def clusters? + cluster_attribute = SmartAttributes::AttributeFactory.build('auto_batch_clusters', {}) + cluster_attribute.select_choices(hide_excludable: false).any? + end end def initialize(opts = {}) @@ -56,30 +61,44 @@ def initialize(opts = {}) @created_at = opts[:created_at] sm_opts = { form: opts[:form] || [], - attributes: opts[:attributes] || {} + attributes: opts[:attributes] || {}, + job_environment: opts[:job_environment] || {}, } add_required_fields(**sm_opts) + @smart_attributes = build_smart_attributes(**sm_opts) end - def build_smart_attributes(form: [], attributes: {}) - form.map do |form_item_id| + def build_smart_attributes(form: [], attributes: {}, job_environment: {}) + attrs = form.map do |form_item_id| attrs = attributes[form_item_id.to_sym].to_h.symbolize_keys cache_value = cached_values[form_item_id] attrs[:value] = cache_value if cache_value.present? SmartAttributes::AttributeFactory.build(form_item_id, attrs) end + + unless job_environment.blank? + attrs << SmartAttributes::AttributeFactory.build('auto_environment_variable', job_environment) + end + + attrs end def to_yaml - attributes = smart_attributes.each_with_object({}) do |sm, hash| + attributes = smart_attributes.reject do |attr| + attr.id == "auto_environment_variable" + end.each_with_object({}) do |sm, hash| hash[sm.id.to_s] = sm.options_to_serialize end.deep_stringify_keys + job_environment = smart_attributes.select { |attr| attr.id == "auto_environment_variable" }.first.try(:opts) || {} + hsh = { 'title' => title, 'created_at' => created_at } - hsh.merge!({ 'form' => smart_attributes.map { |sm| sm.id.to_s } }) + hsh.merge!({ 'form' => smart_attributes.map { |sm| sm.id.to_s unless sm.id.to_s == "auto_environment_variable" }.compact }) hsh.merge!({ 'attributes' => attributes }) + hsh.merge!({ 'job_environment' => job_environment.deep_stringify_keys }) unless job_environment.empty? + hsh.to_yaml end @@ -111,8 +130,12 @@ def respond_to_missing?(method_name, include_private = false) end def original_parameter(string) - match = /([\w_]+)_(?:min|max|exclude|fixed)/.match(string) - match[1] + if string.match('auto_environment_variable') + 'auto_environment_variable' + else + match = /([\w_]+)_(?:min|max|exclude|fixed)/.match(string) + match[1] + end end # Find attribute in list using the id of the attribute @@ -152,13 +175,14 @@ def destroy end def update(params) - # reset smart attributes becuase the user could have removed some fields + # reset smart attributes because the user could have removed some fields @smart_attributes = [] # deal with things that would be in the 'form' section first to initialize # the individual smart attributes update_form(params) update_attributes(params) + update_job_environment(params) end def submit(options) @@ -201,7 +225,8 @@ def self.script_form_file(script_path) # parameters you got from the controller that affect the attributes, not form. # i.e., mins & maxes you set in the form but get serialized to the 'attributes' section. def attribute_parameter?(name) - ['min', 'max', 'exclude', 'fixed'].any? { |postfix| name && name.end_with?("_#{postfix}") } + ['min', 'max', 'exclude', 'fixed'].any? { |postfix| name && name.end_with?("_#{postfix}") } || + (name && name.match?('auto_environment_variable')) end # update the 'form' portion of the yaml file given 'params' from the controller. @@ -231,6 +256,24 @@ def update_attributes(params) end end + def update_job_environment(params) + env_var_names = params.select { |k, _| k.match?('auto_environment_variable_name_') } + return if env_var_names.empty? + + env_var_values = params.select do |param| + param.match?(/\Aauto_environment_variable_(.*)_value\Z/) + end + + job_environment_hash = {} + + env_var_values.each do |key, value| + variable_name = key.match(/\Aauto_environment_variable_(.*)_value\Z/)[1] + job_environment_hash[variable_name.to_s] = value.to_s + end + + self[:auto_environment_variable] = SmartAttributes::AttributeFactory.build('auto_environment_variable', job_environment_hash) + end + def default_attributes(smart_attr_id) case smart_attr_id when 'auto_scripts' @@ -303,7 +346,7 @@ def adapter(cluster_id) OodAppkit.clusters[cluster_id] || raise(ClusterNotFound, "Job specifies nonexistent '#{cluster_id}' cluster id.") end - def add_required_fields(form: [], attributes: {}) + def add_required_fields(form: [], attributes: {}, job_environment: {}) add_cluster_to_form(form: form, attributes: attributes) add_script_to_form(form: form, attributes: attributes) end diff --git a/apps/dashboard/app/models/user_configuration.rb b/apps/dashboard/app/models/user_configuration.rb index df0da299be..be74c9c368 100644 --- a/apps/dashboard/app/models/user_configuration.rb +++ b/apps/dashboard/app/models/user_configuration.rb @@ -83,6 +83,7 @@ class UserConfiguration def initialize(request_hostname: nil) @config = ::Configuration.config @request_hostname = request_hostname.to_sym if request_hostname + @defined_profiles = config.fetch(:profiles, {}).keys add_property_methods end @@ -142,13 +143,13 @@ def support_ticket_service # The current user profile. Used to select the configuration properties. def profile - return user_settings[:profile_override].to_sym if user_settings[:profile_override] - if Configuration.host_based_profiles request_hostname - elsif user_settings[:profile] + elsif user_settings[:profile] && defined_profiles.include?(user_settings[:profile].to_sym) user_settings[:profile].to_sym - elsif Configuration.default_profile + elsif defined_profiles.include?(request_hostname) + request_hostname + elsif Configuration.default_profile && defined_profiles.include?(Configuration.default_profile.to_sym) Configuration.default_profile.to_sym end end @@ -183,5 +184,5 @@ def add_property_methods end end - attr_reader :config, :request_hostname + attr_reader :config, :defined_profiles, :request_hostname end diff --git a/apps/dashboard/app/views/active_jobs/_ganglia_graphs_table.html.erb b/apps/dashboard/app/views/active_jobs/_ganglia_graphs_table.html.erb index 9dfa270975..567eb6a741 100644 --- a/apps/dashboard/app/views/active_jobs/_ganglia_graphs_table.html.erb +++ b/apps/dashboard/app/views/active_jobs/_ganglia_graphs_table.html.erb @@ -1,15 +1,15 @@ - -
- -
-
-
- - - <% if !d.nodes.nil? %> - <% d.nodes.each do |node| %> - <%= render partial: 'active_jobs/job_details_node_view.html.erb', :locals => {:data => d, :node => node } %> - <% end %> - <% end %> - + +
+ +
+
+
+ + + <% if !d.nodes.nil? %> + <% d.nodes.each do |node| %> + <%= render partial: 'active_jobs/job_details_node_view.html.erb', :locals => {:data => d, :node => node } %> + <% end %> + <% end %> +
\ No newline at end of file diff --git a/apps/dashboard/app/views/apps/_app.html.erb b/apps/dashboard/app/views/apps/_app.html.erb deleted file mode 100644 index b0a0a68d41..0000000000 --- a/apps/dashboard/app/views/apps/_app.html.erb +++ /dev/null @@ -1,31 +0,0 @@ -<% app.links.each do |link| %> -
- <%= - link_to( - link.url.to_s, - class: "app-card", - data: { - toggle: "popover", - content: manifest_markdown(link.description), - html: true, - trigger: "hover", - title: link.title, - container: "body" - }, - target: link.new_tab? ? "_blank" : nil - ) do - %> -
- <%= icon_tag(link.icon_uri) %> - <%= content_tag(:p, link.title) %> - <%= - content_tag( - :p, - content_tag(:span, link.caption), - class: "text-muted mt-3", - ) if link.caption - %> -
- <% end %> -
-<% end %> diff --git a/apps/dashboard/app/views/apps/_catalog_link.html.erb b/apps/dashboard/app/views/apps/_catalog_link.html.erb deleted file mode 100644 index 07997e7609..0000000000 --- a/apps/dashboard/app/views/apps/_catalog_link.html.erb +++ /dev/null @@ -1,14 +0,0 @@ -

- <% if ENV['OOD_DASHBOARD_SUPPORT_URL'].present? %> - <%= t('dashboard.sharing_support_msg_html', support_url: ENV['OOD_DASHBOARD_SUPPORT_URL']) %> - <% end %> - - <% if ENV['OOD_APP_CATALOG_URL'].present? %> - <%= t( 'dashboard.sharing_catalog_msg_html', - catalog_link_tag: link_to( t('dashboard.sharing_catalog_title'), - ENV['OOD_APP_CATALOG_URL'] - ) - ) - %> - <% end %> -

diff --git a/apps/dashboard/app/views/apps/_group.html.erb b/apps/dashboard/app/views/apps/_group.html.erb deleted file mode 100644 index 6a036d9e5b..0000000000 --- a/apps/dashboard/app/views/apps/_group.html.erb +++ /dev/null @@ -1,5 +0,0 @@ -

<%= group.title %>

- -
- <%= render partial: "app", collection: group.apps %> -
diff --git a/apps/dashboard/app/views/apps/featured.html.erb b/apps/dashboard/app/views/apps/featured.html.erb deleted file mode 100644 index c8448d971a..0000000000 --- a/apps/dashboard/app/views/apps/featured.html.erb +++ /dev/null @@ -1,16 +0,0 @@ -
-
- <%= render partial: 'shared/welcome' %> - - <% if @nav_groups.any? %> -

<%= t('dashboard.sharing_welcome_msg_html') %>

- <% end %> - <%= render partial: "catalog_link" %> - - <%= render(partial: "group", collection: @groups) || t('dashboard.sharing_no_shared_apps_html') %> -
-
- <%# This assumes @motd_file responds to `to_partial_path` %> - <%= render @motd if @motd %> -
-
diff --git a/apps/dashboard/app/views/batch_connect/sessions/card/_completed_view.html.erb b/apps/dashboard/app/views/batch_connect/sessions/card/_completed_view.html.erb new file mode 100644 index 0000000000..d7b0f2489f --- /dev/null +++ b/apps/dashboard/app/views/batch_connect/sessions/card/_completed_view.html.erb @@ -0,0 +1,9 @@ +
+
+ <%= session.render_completed_view %> + <% if session.render_completed_view_error_message %> + + <% end %> +
diff --git a/apps/dashboard/app/views/batch_connect/sessions/card/_session_time.html.erb b/apps/dashboard/app/views/batch_connect/sessions/card/_session_time.html.erb new file mode 100644 index 0000000000..9a1f73c9f3 --- /dev/null +++ b/apps/dashboard/app/views/batch_connect/sessions/card/_session_time.html.erb @@ -0,0 +1,4 @@ +<% label, value = session_time(session) %> +

+ <%= label %> <%= value %> +

diff --git a/apps/dashboard/app/views/files/_copy_move_popup.html.erb b/apps/dashboard/app/views/files/_copy_move_popup.html.erb index 1c8efb6d9e..e1038a0da2 100755 --- a/apps/dashboard/app/views/files/_copy_move_popup.html.erb +++ b/apps/dashboard/app/views/files/_copy_move_popup.html.erb @@ -1,22 +1,22 @@ - + diff --git a/apps/dashboard/app/views/files/_default_label_error_messages.html.erb b/apps/dashboard/app/views/files/_default_label_error_messages.html.erb index 2dd50bf95b..b7a9a34fb6 100755 --- a/apps/dashboard/app/views/files/_default_label_error_messages.html.erb +++ b/apps/dashboard/app/views/files/_default_label_error_messages.html.erb @@ -1,24 +1,24 @@ - \ No newline at end of file diff --git a/apps/dashboard/app/views/files/_download_button.html.erb b/apps/dashboard/app/views/files/_download_button.html.erb new file mode 100644 index 0000000000..cfb42f21ef --- /dev/null +++ b/apps/dashboard/app/views/files/_download_button.html.erb @@ -0,0 +1,5 @@ +<%- if Configuration.download_enabled? -%> + +<%- end -%> diff --git a/apps/dashboard/app/views/files/_favorites.html.erb b/apps/dashboard/app/views/files/_favorites.html.erb index 4d819fe422..528e74101f 100755 --- a/apps/dashboard/app/views/files/_favorites.html.erb +++ b/apps/dashboard/app/views/files/_favorites.html.erb @@ -1,21 +1,21 @@ -
-
- - +
+
+ + diff --git a/apps/dashboard/app/views/files/_file_action_menu.html.erb b/apps/dashboard/app/views/files/_file_action_menu.html.erb index c8c558061f..cf386678e7 100755 --- a/apps/dashboard/app/views/files/_file_action_menu.html.erb +++ b/apps/dashboard/app/views/files/_file_action_menu.html.erb @@ -1,21 +1,23 @@ - + diff --git a/apps/dashboard/app/views/files/_inline_js.html.erb b/apps/dashboard/app/views/files/_inline_js.html.erb index 4619d5d957..4bae262bb1 100755 --- a/apps/dashboard/app/views/files/_inline_js.html.erb +++ b/apps/dashboard/app/views/files/_inline_js.html.erb @@ -6,7 +6,7 @@ history.replaceState({ currentDirectoryUrl: '<%= files_path(@filesystem, @path) %>', currentDirectoryUpdatedAt: '<%= Time.now.to_i %>', currentFilesPath: '<%= files_path(@filesystem, '/') %>', - currentFilesUploadPath: '<%= url_for(fs: @filesystem, action: 'upload') %>', + currentFilesUploadPath: '<%= url_for(fs: @filesystem, action: 'upload') if Configuration.upload_enabled? %>', currentFilesystem: '<%= @filesystem %>' }, null); diff --git a/apps/dashboard/app/views/files/_upload_button.html.erb b/apps/dashboard/app/views/files/_upload_button.html.erb new file mode 100644 index 0000000000..5d312a0063 --- /dev/null +++ b/apps/dashboard/app/views/files/_upload_button.html.erb @@ -0,0 +1,5 @@ +<%- if Configuration.upload_enabled? -%> + +<%- end -%> diff --git a/apps/dashboard/app/views/files/index.html.erb b/apps/dashboard/app/views/files/index.html.erb index 8a9b32d9eb..7838216ca4 100644 --- a/apps/dashboard/app/views/files/index.html.erb +++ b/apps/dashboard/app/views/files/index.html.erb @@ -9,8 +9,8 @@ - - + <%= render(partial: 'upload_button') %> + <%= render(partial: 'download_button') %> <% if Configuration.globus_endpoints %> <%= render partial: 'globus' %> <% end %> diff --git a/apps/dashboard/app/views/files/index.json.jbuilder b/apps/dashboard/app/views/files/index.json.jbuilder index 9f2d425ccc..860f84b224 100644 --- a/apps/dashboard/app/views/files/index.json.jbuilder +++ b/apps/dashboard/app/views/files/index.json.jbuilder @@ -3,7 +3,7 @@ json.url files_path(@filesystem, @path).to_s #TODO: support array of shell urls, along with the default shell url which could be above json.shell_url OodAppkit.shell.url(path: @path.to_s).to_s json.files_path files_path(@filesystem, '/') -json.files_upload_path url_for(fs: @filesystem, action: 'upload') +json.files_upload_path url_for(fs: @filesystem, action: 'upload') if Configuration.upload_enabled? json.filesystem @filesystem json.files @files do |f| diff --git a/apps/dashboard/app/views/layouts/application.html.erb b/apps/dashboard/app/views/layouts/application.html.erb index e3342da622..1bbf109d69 100644 --- a/apps/dashboard/app/views/layouts/application.html.erb +++ b/apps/dashboard/app/views/layouts/application.html.erb @@ -2,7 +2,7 @@ <%= content_for?(:title) ? yield(:title) : "Dashboard - #{@user_configuration.dashboard_title}" %> - <%= favicon_link_tag 'favicon.ico', href: @user_configuration.public_url.join('favicon.ico'), skip_pipeline: true %> + <%= favicon %> <%= javascript_include_tag 'application', nonce: true %> <% custom_javascript_paths.each do |path| %> diff --git a/apps/dashboard/app/views/layouts/editor.html.erb b/apps/dashboard/app/views/layouts/editor.html.erb index 45cf7c6154..34366d02fc 100644 --- a/apps/dashboard/app/views/layouts/editor.html.erb +++ b/apps/dashboard/app/views/layouts/editor.html.erb @@ -4,7 +4,7 @@ <%= "File Editor - #{@user_configuration.dashboard_title}" %><%= @path.nil? ? "" : " - #{@path.basename.to_s}" %> - <%= favicon_link_tag 'favicon.ico', href: @user_configuration.public_url.join('favicon.ico'), skip_pipeline: true %> + <%= favicon %> <%= javascript_include_tag 'application', nonce: true %> diff --git a/apps/dashboard/app/views/projects/show.html.erb b/apps/dashboard/app/views/projects/show.html.erb index ad565cd661..9b7c13e6e2 100644 --- a/apps/dashboard/app/views/projects/show.html.erb +++ b/apps/dashboard/app/views/projects/show.html.erb @@ -1,4 +1,8 @@ <%= javascript_include_tag 'projects', nonce: true %> +<%- + disabled = !@valid_project + disabled_class = disabled ? 'disabled' : '' +-%> diff --git a/apps/dashboard/app/views/scripts/edit.html.erb b/apps/dashboard/app/views/scripts/edit.html.erb index b8d5d3f009..782a4be638 100644 --- a/apps/dashboard/app/views/scripts/edit.html.erb +++ b/apps/dashboard/app/views/scripts/edit.html.erb @@ -30,3 +30,7 @@ + + \ No newline at end of file diff --git a/apps/dashboard/app/views/scripts/editable_form_fields/_editable_key_value_pair.html.erb b/apps/dashboard/app/views/scripts/editable_form_fields/_editable_key_value_pair.html.erb new file mode 100644 index 0000000000..14f7119539 --- /dev/null +++ b/apps/dashboard/app/views/scripts/editable_form_fields/_editable_key_value_pair.html.erb @@ -0,0 +1,15 @@ +<%- + field_id = "#{form.object_name}_#{attrib.id}" +-%> + +
+
+ <%= form.text_field :auto_environment_variable_name, data: { multiple_input: 'name' } %> + <%= form.text_field :auto_environment_variable_value, data: { multiple_input: 'value' } %> +
+ +
+
+ + <%= render(partial: 'scripts/editable_form_fields/edit_field_buttons', locals: { field_id: field_id }) %> +
\ No newline at end of file diff --git a/apps/dashboard/bin/recompile_js b/apps/dashboard/bin/recompile_js index d7f7177c42..9b576298ae 100755 --- a/apps/dashboard/bin/recompile_js +++ b/apps/dashboard/bin/recompile_js @@ -1,7 +1,12 @@ #!/bin/bash -export RAILS_ENV='development' -export RAILS_RELATIVE_URL_ROOT='/pun/dev/dashboard' +if [[ -n "$1" ]]; then + export RAILS_ENV='test' + export RAILS_RELATIVE_URL_ROOT='/' +else + export RAILS_ENV='development' + export RAILS_RELATIVE_URL_ROOT='/pun/dev/dashboard' +fi bin/rails assets:clobber bin/rails assets:precompile diff --git a/apps/dashboard/config/configuration_singleton.rb b/apps/dashboard/config/configuration_singleton.rb index c2671a3a0b..1ea77bc711 100644 --- a/apps/dashboard/config/configuration_singleton.rb +++ b/apps/dashboard/config/configuration_singleton.rb @@ -50,6 +50,8 @@ def boolean_configs :cancel_session_enabled => false, :hide_app_version => false, :motd_render_html => false, + :upload_enabled => true, + :download_enabled => true, }.freeze end diff --git a/apps/dashboard/config/initializers/locales.rb b/apps/dashboard/config/initializers/locales.rb index 47bfa29591..15f55f968c 100644 --- a/apps/dashboard/config/initializers/locales.rb +++ b/apps/dashboard/config/initializers/locales.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Setup locales by adding to the locale path, setting the default and setting fallbacks extra_locales = ::Configuration.locales_root.join('*.{yml,rb}') diff --git a/apps/dashboard/config/locales/en.yml b/apps/dashboard/config/locales/en.yml index cec98ab4f2..d56a09a509 100644 --- a/apps/dashboard/config/locales/en.yml +++ b/apps/dashboard/config/locales/en.yml @@ -209,6 +209,7 @@ en: recently_used_apps_title: 'Recently Used Apps' + files_download_not_enabled: "Downloading files is not enabled on this server." files_directory_download_error_modal_title: "Directory too large to download" files_directory_download_unauthorized: "You can only download a directory as zip that you have read and execute access to" files_directory_download_size_0: "The directory size is 0 and has no contents for download." @@ -235,6 +236,7 @@ en: jobs_project_validation_error: "Invalid Request. Please review the errors below" jobs_project_save_error: "Cannot save manifest to %{path}" jobs_project_generic_error: "There was an error processing your request: %{error}" + jobs_project_invalid_configuration_clusters: "An HPC cluster is required. Contact your administrator to add one to the system." jobs_scripts_created: "Script successfully created!" jobs_scripts_updated: "Script manifest updated!" diff --git a/apps/dashboard/config/routes.rb b/apps/dashboard/config/routes.rb index 09773ff424..d28a6bad9b 100644 --- a/apps/dashboard/config/routes.rb +++ b/apps/dashboard/config/routes.rb @@ -25,7 +25,7 @@ get "files/api/v1/:fs(/*filepath)" => "files#fs", :defaults => { :fs => 'fs', :format => 'html' }, :format => false put "files/api/v1/:fs/*filepath" => "files#update", :format => false, :defaults => { :fs => 'fs', :format => 'json' } end - post "files/upload/:fs" => "files#upload", :defaults => { :fs => 'fs' } + post "files/upload/:fs" => "files#upload", :defaults => { :fs => 'fs' } if Configuration.upload_enabled? get "files", to: redirect("files/fs#{Dir.home}") @@ -61,7 +61,6 @@ if Configuration.app_sharing_enabled? get "apps/restart" => "apps#restart" - get "apps/featured" => "apps#featured" end root "dashboard#index" diff --git a/apps/dashboard/test/application_system_test_case.rb b/apps/dashboard/test/application_system_test_case.rb index 4cb6581207..f233bf1312 100644 --- a/apps/dashboard/test/application_system_test_case.rb +++ b/apps/dashboard/test/application_system_test_case.rb @@ -2,9 +2,18 @@ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + DOWNLOAD_DIRECTORY = Rails.root.join('tmp', 'downloads') + driven_by :selenium, using: :headless_chrome, screen_size: [1400, 1400] do |options| # only chrome has support for browser logs - options.logging_prefs = { browser: 'ALL' } + options.logging_prefs = { browser: 'ALL' } + options.add_argument('--headless=new') + options.browser_version = 'stable' + + profile = Selenium::WebDriver::Chrome::Profile.new + profile['download.default_directory'] = DOWNLOAD_DIRECTORY + + options.profile = profile end Selenium::WebDriver.logger.level = :debug unless ENV['DEBUG'].nil? diff --git a/apps/dashboard/test/config/configuration_singleton_test.rb b/apps/dashboard/test/config/configuration_singleton_test.rb index c4328dfb83..5f6c452278 100644 --- a/apps/dashboard/test/config/configuration_singleton_test.rb +++ b/apps/dashboard/test/config/configuration_singleton_test.rb @@ -357,7 +357,7 @@ def no_config_env c = ConfigurationSingleton.new with_modified_env(no_config_env) do - c.boolean_configs.each do |config, default| + c.boolean_configs.take(5).each do |config, default| assert_equal default, c.send(config), "#{config} should have been #{default} through the default value." end end @@ -367,7 +367,7 @@ def no_config_env c = ConfigurationSingleton.new with_modified_env(no_config_env) do - c.string_configs.each do |config, default| + c.string_configs.take(5).each do |config, default| if default.nil? # assert_equal on nil is deprecated assert_nil c.send(config), "#{config} should have been nil through the default value." @@ -381,7 +381,7 @@ def no_config_env test 'boolean configs respond to env variables' do c = ConfigurationSingleton.new - c.boolean_configs.each do |config, default| + c.boolean_configs.take(5).each do |config, default| env_var = "OOD_#{config.upcase}" with_modified_env(no_config_env.merge({ env_var => (!default).to_s })) do assert_equal !default, c.send(config), "#{config} should have responded to ENV['#{env_var}']=#{ENV[env_var]}." @@ -392,7 +392,7 @@ def no_config_env test 'string configs respond to env variables' do c = ConfigurationSingleton.new - c.string_configs.each do |config, _| + c.string_configs.take(5).each do |config, _| env_var = "OOD_#{config.upcase}" other_string = 'some other string that can never be a real value 2073423rnabsdf0y3b4123kbasdoifgadf' with_modified_env(no_config_env.merge({ env_var => other_string })) do @@ -407,10 +407,10 @@ def no_config_env # write !defaults out other_string = 'another random string asdfn31-ndf12nadsnfsad[nf-5t2fwnasdfm' File.open("#{dir}/config.yml", 'w+') do |file| - cfg = ConfigurationSingleton.new.boolean_configs.each_with_object({}) do |(config, default), hsh| + cfg = ConfigurationSingleton.new.boolean_configs.take(5).each_with_object({}) do |(config, default), hsh| hsh[config.to_s] = !default end.merge( - ConfigurationSingleton.new.string_configs.each_with_object({}) do |(config, _), hsh| + ConfigurationSingleton.new.string_configs.take(5).each_with_object({}) do |(config, _), hsh| hsh[config.to_s] = other_string end ) @@ -418,10 +418,10 @@ def no_config_env end c = ConfigurationSingleton.new - c.boolean_configs.each do |config, default| + c.boolean_configs.take(5).each do |config, default| assert_equal !default, c.send(config), "#{config} should have been #{!default} through a fixture file." end - c.string_configs.each do |config, _| + c.string_configs.take(5).each do |config, _| assert_equal other_string, c.send(config), "#{config} should have been #{other_string} through a fixture file." end end @@ -430,21 +430,21 @@ def no_config_env test 'env variables have precedence in dynamic configs' do other_string = 'string in env variable' - env = ConfigurationSingleton.new.boolean_configs.map do |config, default| + env = ConfigurationSingleton.new.boolean_configs.take(5).map do |config, default| ["OOD_#{config.upcase}", default.to_s] end.concat( - ConfigurationSingleton.new.string_configs.map do |config, _| + ConfigurationSingleton.new.string_configs.take(5).map do |config, _| ["OOD_#{config.upcase}", other_string] end ).compact.to_h with_modified_env(config_fixtures.merge(env)) do c = ConfigurationSingleton.new - c.boolean_configs.each do |config, default| + c.boolean_configs.take(5).each do |config, default| env_var = "OOD_#{config.upcase}" assert_equal default, c.send(config), "#{config} should have responded to ENV['#{env_var}']=#{ENV[env_var]}." end - c.string_configs.each do |config, _| + c.string_configs.take(5).each do |config, _| assert_equal 'string in env variable', c.send(config), "#{config} should have been 'string in env variable'." end end @@ -452,10 +452,10 @@ def no_config_env # just to be sure, let's assert the opposite with a different env with_modified_env(config_fixtures) do c = ConfigurationSingleton.new - c.boolean_configs.each do |config, default| + c.boolean_configs.take(5).each do |config, default| assert_equal !default, c.send(config), "#{config} should have been #{!default} through a fixture file." end - c.string_configs.each do |config, _| + c.string_configs.take(5).each do |config, _| assert_equal 'string from file', c.send(config), "#{config} should have been 'string from file' through a fixture file." end end diff --git a/apps/dashboard/test/fixtures/sys_with_gateway_apps/bc_jupyter/form.yml b/apps/dashboard/test/fixtures/sys_with_gateway_apps/bc_jupyter/form.yml index feb7189f4b..b3c4faa121 100644 --- a/apps/dashboard/test/fixtures/sys_with_gateway_apps/bc_jupyter/form.yml +++ b/apps/dashboard/test/fixtures/sys_with_gateway_apps/bc_jupyter/form.yml @@ -164,6 +164,8 @@ attributes: data-min-bc-num-hours-for-cluster-oakley: 111, ] - [ 'Economics 8846', 'astronomy-with/other-characters/8846.31.4' ] + - [ '123ABC', '123ABC' ] + - [ '456def', '456def' ] classroom_size: widget: select options: @@ -179,6 +181,8 @@ attributes: data-option-for-classroom-astronomy-5678: false, data-option-for-classroom-astronomy-with/other-characters/8846.31.4: false, data-set-checkbox-test: 1, + data-option-for-classroom-123ABC: false, + data-option-for-classroom-456def: false, ] gpus: widget: number_field diff --git a/apps/dashboard/test/helpers/errors_helper_test.rb b/apps/dashboard/test/helpers/errors_helper_test.rb index 2b0c217f20..e79ad5f843 100644 --- a/apps/dashboard/test/helpers/errors_helper_test.rb +++ b/apps/dashboard/test/helpers/errors_helper_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ErrorsHelperTest < ActionView::TestCase diff --git a/apps/dashboard/test/integration/sessions_controller_test.rb b/apps/dashboard/test/integration/sessions_controller_test.rb index e85963ac64..d8c990100b 100644 --- a/apps/dashboard/test/integration/sessions_controller_test.rb +++ b/apps/dashboard/test/integration/sessions_controller_test.rb @@ -101,7 +101,7 @@ def setup value = '{"id":"1234","job_id":"1","created_at":1669139262,"token":"sys/token","title":"session title","cache_completed":true}' session = BatchConnect::Session.new.from_json(value) session.stubs(:status).returns(OodCore::Job::Status.new(state: :completed)) - session.stubs(:app).returns(stub(valid?: true, token: 'sys/token', attributes: [], session_info_view: nil, ssh_allow?: true)) + session.stubs(:app).returns(stub(valid?: true, token: 'sys/token', attributes: [], session_info_view: nil, session_completed_view: nil, ssh_allow?: true)) BatchConnect::Session.stubs(:all).returns([session]) get batch_connect_sessions_path diff --git a/apps/dashboard/test/integration/settings_controller_test.rb b/apps/dashboard/test/integration/settings_controller_test.rb index 0da571f732..5162c8ed5f 100644 --- a/apps/dashboard/test/integration/settings_controller_test.rb +++ b/apps/dashboard/test/integration/settings_controller_test.rb @@ -12,7 +12,7 @@ def setup @headers = { 'X-CSRF-Token' => @token } end - test "should call Configuration.update_user_settings when posting settings data" do + test "should save user_settings when posting settings data" do data = { settings: { profile: "test_profile" @@ -20,27 +20,37 @@ def setup } Dir.mktmpdir {|temp_data_dir| Configuration.stubs(:dataroot).returns(temp_data_dir) - CurrentUser.expects(:update_user_settings).with({ "profile" => "test_profile" }).once post settings_path, params: data, headers: @headers assert_response :redirect + assert_equal "test_profile", TestUserSettings.new.user_settings[:profile] } end - test "should not call Configuration.update_user_settings when no data" do - data = { settings: { } } - CurrentUser.expects(:update_user_settings).never + test "should not save user settings when no data" do + data = { settings: {} } - post settings_path, params: data, headers: @headers - assert_response :redirect + Dir.mktmpdir {|temp_data_dir| + Configuration.stubs(:dataroot).returns(temp_data_dir) + post settings_path, params: data, headers: @headers + assert_response :redirect + assert_nil TestUserSettings.new.user_settings[:profile] + } end - test "parameters outside the settings namespace should be ignored" do + test "should not save user_settings whne parameters are outside the settings namespace" do data = { profile: "root_value" } - CurrentUser.expects(:update_user_settings).never - post settings_path, params: data, headers: @headers - assert_response :redirect + Dir.mktmpdir {|temp_data_dir| + Configuration.stubs(:dataroot).returns(temp_data_dir) + post settings_path, params: data, headers: @headers + assert_response :redirect + assert_nil TestUserSettings.new.user_settings[:profile] + } + end + + class TestUserSettings + include UserSettingStore end end \ No newline at end of file diff --git a/apps/dashboard/test/models/script_test.rb b/apps/dashboard/test/models/script_test.rb index 087ff02bc2..efcdf548c2 100644 --- a/apps/dashboard/test/models/script_test.rb +++ b/apps/dashboard/test/models/script_test.rb @@ -13,6 +13,19 @@ class ScriptTest < ActiveSupport::TestCase assert target.send('attribute_parameter?', 'account_exclude') assert target.send('attribute_parameter?', 'account_fixed') end + + test 'clusters? return false when auto_batch_clusters returns no clusters' do + Configuration.stubs(:job_clusters).returns([]) + + assert_equal false, Script.clusters? + end + + test 'clusters? return true when auto_batch_clusters returns clusters' do + Configuration.stubs(:job_clusters).returns(OodCore::Clusters.load_file('test/fixtures/config/clusters.d')) + + assert_equal true, Script.clusters? + end + test 'creates script' do Dir.mktmpdir do |tmp| projects_path = Pathname.new(tmp) diff --git a/apps/dashboard/test/models/user_configuration_test.rb b/apps/dashboard/test/models/user_configuration_test.rb index a7b9fc90e9..68c7bed845 100644 --- a/apps/dashboard/test/models/user_configuration_test.rb +++ b/apps/dashboard/test/models/user_configuration_test.rb @@ -206,40 +206,54 @@ def teardown end end - test "profile should delegate to UserSettingsStore module when Configuration.host_based_profiles is false" do - Configuration.stubs(:host_based_profiles).returns(false) - target = UserConfiguration.new(request_hostname: "request_hostname") - target.update_user_settings({profile: "user_settings_profile_value"}) + test "profile - Configuration.host_based_profiles should enforce request_hostname as profile" do + Configuration.stubs(:config).returns({ profiles: { my_profile: {}, request_hostname_profile: {}, user_settings_profile: {} } }) + Configuration.stubs(:host_based_profiles).returns(true) + Configuration.stubs(:default_profile).returns('my_profile') + target = UserConfiguration.new(request_hostname: "request_hostname_profile") + target.update_user_settings({profile: "user_settings_profile"}) - assert_equal :user_settings_profile_value, target.profile + assert_equal :request_hostname_profile, target.profile end - test "profile should return default_profile when when Configuration.host_based_profiles is false and user profile is nil" do - Configuration.stubs(:host_based_profiles).returns(false) - Configuration.stubs(:default_profile).returns('myprofile') - target = UserConfiguration.new - target.update_user_settings({profile: nil}) + test "profile - UserSettingsStore should take precedence over other profile configurations when it is defined" do + Configuration.stubs(:config).returns({ profiles: { my_profile: {}, request_hostname_profile: {}, user_settings_profile: {} } }) + Configuration.stubs(:default_profile).returns('my_profile') + target = UserConfiguration.new(request_hostname: "request_hostname_profile") + target.update_user_settings({profile: "user_settings_profile"}) - assert_equal :myprofile, target.profile + assert_equal :user_settings_profile, target.profile end - test "profile should return nil when when Configuration.host_based_profiles is false, user profile is nil, and default_profile not set" do - Configuration.stubs(:host_based_profiles).returns(false) - target = UserConfiguration.new - target.update_user_settings({profile: nil}) + test "profile - Host Based profile should take precedence over other profile configurations when it is defined and UserSettingsStore profile is not defined" do + Configuration.stubs(:config).returns({ profiles: { my_profile: {}, request_hostname_profile: {} } }) + Configuration.stubs(:default_profile).returns('my_profile') + target = UserConfiguration.new(request_hostname: "request_hostname_profile") + target.update_user_settings({profile: "user_settings_profile"}) - assert_nil target.profile + assert_equal :request_hostname_profile, target.profile end - test "profile should delegate to request_hostname when Configuration.host_based_profiles is true" do - Configuration.stubs(:host_based_profiles).returns(true) - target = UserConfiguration.new(request_hostname: "request_hostname") + test "profile - Default profile should be selected when it is defined in configuration and no other profiles defined" do + Configuration.stubs(:config).returns({ profiles: { my_profile: {} } }) + Configuration.stubs(:default_profile).returns('my_profile') + target = UserConfiguration.new(request_hostname: "request_hostname_profile") + target.update_user_settings({profile: "user_settings_profile"}) - assert_equal :request_hostname, target.profile + assert_equal :my_profile, target.profile end - test "profile should return nil when when Configuration.host_based_profiles is true and request_hostname is nil" do - Configuration.stubs(:host_based_profiles).returns(true) + test "profile should return nil when no profiles are defined" do + Configuration.stubs(:config).returns({}) + Configuration.stubs(:default_profile).returns('my_profile') + target = UserConfiguration.new(request_hostname: "request_hostname_profile") + target.update_user_settings({profile: "user_settings_profile"}) + + assert_nil target.profile + end + + test "profile should return nil when no profile configuration are defined" do + Configuration.stubs(:config).returns({ profiles: { my_profile: {}, request_hostname_profile: {}, user_settings_profile: {} } }) target = UserConfiguration.new assert_nil target.profile diff --git a/apps/dashboard/test/system/batch_connect_test.rb b/apps/dashboard/test/system/batch_connect_test.rb index b47ba8ced0..dc998ba74b 100644 --- a/apps/dashboard/test/system/batch_connect_test.rb +++ b/apps/dashboard/test/system/batch_connect_test.rb @@ -624,6 +624,27 @@ def make_bc_app(dir, form) find("##{bc_ele_id('bc_email_on_started')}", visible: false) end + test 'options that start with numbers hide other options' do + visit new_batch_connect_session_context_url('sys/bc_jupyter') + + # defaults + assert_equal('physics_1234', find_value('classroom')) + assert_equal('small', find_value('classroom_size')) + assert_equal('', find_option_style('classroom_size', 'large')) + + # now change the classroom and see large dissappear + select('123ABC', from: bc_ele_id('classroom')) + assert_equal('display: none;', find_option_style('classroom_size', 'large')) + + # select the default, and it's back. + select('Physics 1234', from: bc_ele_id('classroom')) + assert_equal('', find_option_style('classroom_size', 'large')) + + # now change the lowercase classroom and see large dissappear again. + select('456def', from: bc_ele_id('classroom')) + assert_equal('display: none;', find_option_style('classroom_size', 'large')) + end + test 'options with numbers and slashes' do visit new_batch_connect_session_context_url('sys/bc_jupyter') @@ -1232,4 +1253,44 @@ def make_bc_app(dir, form) assert_equal("#{dir}/app/.hidden_dir", text_field.value) end end + + # test for bug https://github.com/OSC/ondemand/issues/3246 + test 'path selector can reselect files' do + Dir.mktmpdir do |dir| + "#{dir}/app".tap { |d| Dir.mkdir(d) } + SysRouter.stubs(:base_path).returns(Pathname.new(dir)) + stub_scontrol + stub_sacctmgr + stub_git("#{dir}/app") + + form = <<~HEREDOC + --- + cluster: + - owens + form: + - path + attributes: + path: + widget: 'path_selector' + directory: "#{Rails.root}" + HEREDOC + + Pathname.new("#{dir}/app/").join('form.yml').write(form) + base_id = 'batch_connect_session_context_path' + + visit new_batch_connect_session_context_url('sys/app') + click_on('Select Path') + + gem = find('span', exact_text: 'Gemfile') + gem_lock = find('span', exact_text: 'Gemfile.lock') + + gem.click + gem_lock.click + gem.click + + find("##{base_id}_path_selector_button").click + text_field = find("##{base_id}") + assert_equal("#{Rails.root}/Gemfile", text_field.value) + end + end end diff --git a/apps/dashboard/test/system/files_test.rb b/apps/dashboard/test/system/files_test.rb index ae3934b576..a792b1faf2 100644 --- a/apps/dashboard/test/system/files_test.rb +++ b/apps/dashboard/test/system/files_test.rb @@ -4,6 +4,11 @@ class FilesTest < ApplicationSystemTestCase MAX_WAIT = 120 + def setup + FileUtils.rm_rf(DOWNLOAD_DIRECTORY.to_s) + FileUtils.mkdir_p(DOWNLOAD_DIRECTORY.to_s) + end + test "visiting files app doesn't raise js errors" do visit files_url(Rails.root.to_s) @@ -182,6 +187,7 @@ class FilesTest < ApplicationSystemTestCase test 'uploading duplicate files' do Dir.mktmpdir do |dir| + File.stubs(:umask).returns(18) # ensure default umask is 644 upload_dir = File.join(dir, 'upload') FileUtils.mkpath(upload_dir) @@ -198,11 +204,11 @@ class FilesTest < ApplicationSystemTestCase find('tbody a', exact_text: File.basename(src_file), wait: MAX_WAIT) assert File.exist?(upload_file) assert_equal File.read(src_file), File.read(upload_file) - assert_equal File.stat(upload_file).mode, 33_188 # default 644 + assert_equal(33_188, File.stat(upload_file).mode) # default 644 # now change the permissions and verify `chmod 755 #{upload_file}` - assert_equal File.stat(upload_file).mode, 33_261 # now 755 + assert_equal(33_261, File.stat(upload_file).mode) # now 755 # add something more to the original file `echo 'and some more content' >> #{src_file}` @@ -214,7 +220,7 @@ class FilesTest < ApplicationSystemTestCase find('.uppy-StatusBar-actionBtn--upload', wait: MAX_WAIT).click find('tbody a', exact_text: File.basename(src_file), wait: MAX_WAIT) - # and it's still there, now with new content and it keesp the 755 permissions + # and it's still there, now with new content and it keeps the 755 permissions assert File.exist?(upload_file) assert_equal File.read(src_file), File.read(upload_file) assert_equal File.stat(upload_file).mode, 33_261 # still 755 @@ -255,9 +261,8 @@ class FilesTest < ApplicationSystemTestCase within_window edit_window do find('#editor').click - find('textarea.ace_text-input', visible: false).send_keys 'foobar' + find('textarea.ace_text-input', visible: false).send_keys('foobar') - find('.navbar-toggler').click find('#save-button').click end @@ -345,7 +350,7 @@ class FilesTest < ApplicationSystemTestCase end test 'can download hidden files and directories' do - zip_file = Rails.root.join('test_dir.zip') + zip_file = DOWNLOAD_DIRECTORY.join('test_dir.zip') File.delete(zip_file) if File.exist?(zip_file) Dir.mktmpdir do |dir| @@ -389,7 +394,7 @@ class FilesTest < ApplicationSystemTestCase end test 'cannot download files outside of allowlist' do - zip_file = Rails.root.join('allowed.zip') + zip_file = DOWNLOAD_DIRECTORY.join('allowed.zip') File.delete(zip_file) if File.exist?(zip_file) Dir.mktmpdir do |dir| diff --git a/apps/dashboard/test/system/jobs_app_test.rb b/apps/dashboard/test/system/jobs_app_test.rb index baa85cd6ef..28e2fba669 100644 --- a/apps/dashboard/test/system/jobs_app_test.rb +++ b/apps/dashboard/test/system/jobs_app_test.rb @@ -372,7 +372,7 @@ def add_bc_num_hours(project_id, script_id) new_field_id = 'add_new_field_select' actual_new_options = page.all("##{new_field_id} option").map(&:value).to_set - expected_new_options = ['bc_num_hours', 'auto_queues', 'bc_num_slots', 'auto_accounts'].to_set + expected_new_options = ['bc_num_hours', 'auto_queues', 'bc_num_slots', 'auto_accounts', 'auto_environment_variable'].to_set assert_equal expected_new_options, actual_new_options end end @@ -415,6 +415,30 @@ def add_bc_num_hours(project_id, script_id) find('#script_bc_num_hours_fixed').click find('#save_script_bc_num_hours').click + # add auto_environment_variable + click_on('Add new option') + select('Environment Variable', from: 'add_new_field_select') + click_on(I18n.t('dashboard.add')) + assert find('#script_auto_environment_variable_name') + + fill_in('script_auto_environment_variable_name', with: 'SOME_VARIABLE') + fill_in('script_auto_environment_variable_value', with: 'some_value') + + assert find('#script_auto_environment_variable_name_SOME_VARIABLE') + assert find('#script_auto_environment_variable_SOME_VARIABLE_value') + + # add multiple auto_environment_variables + click_on('Add new option') + select('Environment Variable', from: 'add_new_field_select') + click_on(I18n.t('dashboard.add')) + assert find('#script_auto_environment_variable_name') + + fill_in('script_auto_environment_variable_name', with: 'ANOTHER_VARIABLE') + fill_in('script_auto_environment_variable_value', with: 'some_other_value') + + assert find('#script_auto_environment_variable_name_ANOTHER_VARIABLE') + assert find('#script_auto_environment_variable_ANOTHER_VARIABLE_value') + # correctly saves click_on(I18n.t('dashboard.save')) success_message = I18n.t('dashboard.jobs_scripts_updated') @@ -459,9 +483,21 @@ def add_bc_num_hours(project_id, script_id) label: Number of hours help: '' required: true + job_environment: + SOME_VARIABLE: some_value + ANOTHER_VARIABLE: some_other_value HEREDOC + + file = File.read("#{dir}/projects/#{project_id}/.ondemand/scripts/#{script_id}/form.yml") - assert_equal(expected_yml, File.read("#{dir}/projects/#{project_id}/.ondemand/scripts/#{script_id}/form.yml")) + assert_equal(expected_yml, file) + + # correctly rebuilds form + find("[href='#{edit_script_path}']").click + + # shows all previously input fields + assert find('#script_auto_environment_variable_name_SOME_VARIABLE') + assert find('#script_auto_environment_variable_SOME_VARIABLE_value') end end diff --git a/apps/dashboard/test/system/remote_files_test.rb b/apps/dashboard/test/system/remote_files_test.rb index 0f055e1e21..f04bda2a01 100644 --- a/apps/dashboard/test/system/remote_files_test.rb +++ b/apps/dashboard/test/system/remote_files_test.rb @@ -357,7 +357,6 @@ class RemoteFilesTest < ApplicationSystemTestCase find('#editor').click find('textarea.ace_text-input', visible: false).send_keys 'foobar' - find('.navbar-toggler').click find('#save-button').click end diff --git a/apps/dashboard/yarn.lock b/apps/dashboard/yarn.lock index 24f6c8c55b..2f54c70811 100644 --- a/apps/dashboard/yarn.lock +++ b/apps/dashboard/yarn.lock @@ -440,9 +440,9 @@ buffer-from@^1.1.2: fsevents "~2.3.2" classnames@^2.2.6: - version "2.3.2" - resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" - integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== + version "2.5.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" + integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== clipboard@^2.0.8: version "2.0.11" @@ -929,9 +929,9 @@ popper.js@^1.16.1: integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ== preact@^10.5.13: - version "10.19.2" - resolved "https://registry.yarnpkg.com/preact/-/preact-10.19.2.tgz#841797620dba649aaac1f8be42d37c3202dcea8b" - integrity sha512-UA9DX/OJwv6YwP9Vn7Ti/vF80XL+YA5H2l7BpCtUr3ya8LWHFzpiO5R+N7dN16ujpIxhekRFuOOF82bXX7K/lg== + version "10.19.3" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.19.3.tgz#7a7107ed2598a60676c943709ea3efb8aaafa899" + integrity sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ== promise-queue@^2.2.5: version "2.2.5" @@ -969,9 +969,9 @@ retry@^0.10.0: integrity sha512-ZXUSQYTHdl3uS7IuCehYfMzKyIDBNoAuUblvy5oGO5UJSUTmStUUVPXbA9Qxd173Bgre53yCQczQuHgRWAdvJQ== sass@^1.50.0: - version "1.69.5" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.69.5.tgz#23e18d1c757a35f2e52cc81871060b9ad653dfde" - integrity sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ== + version "1.69.6" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.69.6.tgz#88ae1f93facc46d2da9b0bdd652d65068bcfa397" + integrity sha512-qbRr3k9JGHWXCvZU77SD2OTwUlC+gNT+61JOLcmLm+XqH4h/5D+p4IIsxvpkB89S9AwJOyb5+rWNpIucaFxSFQ== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" diff --git a/apps/myjobs/Gemfile.lock b/apps/myjobs/Gemfile.lock index 4f3a7df1aa..e003a2a6a0 100644 --- a/apps/myjobs/Gemfile.lock +++ b/apps/myjobs/Gemfile.lock @@ -60,7 +60,7 @@ GEM minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) - addressable (2.8.5) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) autoprefixer-rails (8.6.5) execjs diff --git a/apps/myjobs/app/models/workflow_file.rb b/apps/myjobs/app/models/workflow_file.rb index 0b900a6021..b046d66fc8 100644 --- a/apps/myjobs/app/models/workflow_file.rb +++ b/apps/myjobs/app/models/workflow_file.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class WorkflowFile def initialize(path, staged_dir) @path = Pathname.new(path.to_s) @@ -8,51 +10,47 @@ def initialize(path, staged_dir) # # Filter the files by file size, extension and content def suggested_script? - @suggested_script ||= (valid_size? && ( has_suggested_extensions? || starts_with_shebang_line? || has_resource_manager_directive?)) + @suggested_script ||= (valid_size? && (has_suggested_extensions? || starts_with_shebang_line? || has_resource_manager_directive?)) end - + # Return true if the file meets the basic requirements to be a job script def valid_script? @valid_script ||= valid_size? end - + # Return true if file name extention is in the recommended extension list def has_suggested_extensions? - suggested_job_script_file_extensions = [".sh", ".job", ".slurm", ".batch", ".qsub", ".sbatch", ".srun", ".bsub"] + suggested_job_script_file_extensions = ['.sh', '.job', '.slurm', '.batch', '.qsub', '.sbatch', '.srun', '.bsub'] suggested_job_script_file_extensions.include? File.extname(path) end - + # Return true if file starts with a shebang line def starts_with_shebang_line? - begin - (@path.open { |f| f.read(2) }) == "#!" - rescue - false - end + (@path.open { |f| f.read(2) }) == '#!' + rescue StandardError + false end # Return true if first 1000 bytes of file contain '#PBS' or '#SBATCH" or '#BSUB' or '#$' def has_resource_manager_directive? - begin - contents = @path.open { |f| f.read(1000) } - contents && (contents.include?("#PBS") || contents.include?("#SBATCH") || contents.include?('#BSUB') || contents.include?('#$')) - rescue - false - end + contents = @path.open { |f| f.read(1000) } + contents && (contents.include?('#PBS') || contents.include?('#SBATCH') || contents.include?('#BSUB') || contents.include?('#$')) + rescue StandardError + false end # Return true if file size is smaller than 65KB def valid_size? - @path.size.to_f/1024 <= Configuration.max_valid_script_size_kb - end - + @path.size.to_f / 1024 <= Configuration.max_valid_script_size_kb + end + # Return relative file path uses staged_dir as base def relative_path @path.relative_path_from(@staged_dir).to_s end def under_dotfile? - @path.ascend().to_a.any? { |entry| entry.basename.to_s.start_with?('.') } + @path.ascend.to_a.any? { |entry| entry.basename.to_s.start_with?('.') } end def path diff --git a/apps/myjobs/config/environments/development.rb b/apps/myjobs/config/environments/development.rb index 779d85efa1..b58baf7ced 100644 --- a/apps/myjobs/config/environments/development.rb +++ b/apps/myjobs/config/environments/development.rb @@ -1,4 +1,6 @@ -require "active_support/core_ext/integer/time" +# frozen_string_literal: true + +require 'active_support/core_ext/integer/time' Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. diff --git a/apps/myjobs/db/migrate/20160414202656_drop_table_templates.rb b/apps/myjobs/db/migrate/20160414202656_drop_table_templates.rb index ff6c04948c..912a35e4d1 100644 --- a/apps/myjobs/db/migrate/20160414202656_drop_table_templates.rb +++ b/apps/myjobs/db/migrate/20160414202656_drop_table_templates.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DropTableTemplates < ActiveRecord::Migration[4.2] def change drop_table :templates diff --git a/apps/shell/yarn.lock b/apps/shell/yarn.lock index bf349d4fbe..8259eab0bc 100644 --- a/apps/shell/yarn.lock +++ b/apps/shell/yarn.lock @@ -10,58 +10,58 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.22.13": - version "7.22.13" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" - integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" + integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== dependencies: - "@babel/highlight" "^7.22.13" + "@babel/highlight" "^7.23.4" chalk "^2.4.2" -"@babel/compat-data@^7.22.9": - version "7.23.2" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.2.tgz#6a12ced93455827037bfb5ed8492820d60fc32cc" - integrity sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ== +"@babel/compat-data@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" + integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== "@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.5": - version "7.23.2" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.2.tgz#ed10df0d580fff67c5f3ee70fd22e2e4c90a9f94" - integrity sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ== + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.7.tgz#4d8016e06a14b5f92530a13ed0561730b5c6483f" + integrity sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw== dependencies: "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.22.13" - "@babel/generator" "^7.23.0" - "@babel/helper-compilation-targets" "^7.22.15" - "@babel/helper-module-transforms" "^7.23.0" - "@babel/helpers" "^7.23.2" - "@babel/parser" "^7.23.0" + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.23.7" + "@babel/parser" "^7.23.6" "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.2" - "@babel/types" "^7.23.0" + "@babel/traverse" "^7.23.7" + "@babel/types" "^7.23.6" convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" - integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g== +"@babel/generator@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e" + integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw== dependencies: - "@babel/types" "^7.23.0" + "@babel/types" "^7.23.6" "@jridgewell/gen-mapping" "^0.3.2" "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" -"@babel/helper-compilation-targets@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52" - integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw== +"@babel/helper-compilation-targets@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" + integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== dependencies: - "@babel/compat-data" "^7.22.9" - "@babel/helper-validator-option" "^7.22.15" - browserslist "^4.21.9" + "@babel/compat-data" "^7.23.5" + "@babel/helper-validator-option" "^7.23.5" + browserslist "^4.22.2" lru-cache "^5.1.1" semver "^6.3.1" @@ -92,10 +92,10 @@ dependencies: "@babel/types" "^7.22.15" -"@babel/helper-module-transforms@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz#3ec246457f6c842c0aee62a01f60739906f7047e" - integrity sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw== +"@babel/helper-module-transforms@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" + integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== dependencies: "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-module-imports" "^7.22.15" @@ -122,43 +122,43 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-string-parser@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" - integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== +"@babel/helper-string-parser@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" + integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== "@babel/helper-validator-identifier@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== -"@babel/helper-validator-option@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040" - integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA== +"@babel/helper-validator-option@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" + integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== -"@babel/helpers@^7.23.2": - version "7.23.2" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.2.tgz#2832549a6e37d484286e15ba36a5330483cac767" - integrity sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ== +"@babel/helpers@^7.23.7": + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.7.tgz#eb543c36f81da2873e47b76ee032343ac83bba60" + integrity sha512-6AMnjCoC8wjqBzDHkuqpa7jAKwvMo4dC+lr/TFBz+ucfulO1XMpDnwWPGBNwClOKZ8h6xn5N81W/R5OrcKtCbQ== dependencies: "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.2" - "@babel/types" "^7.23.0" + "@babel/traverse" "^7.23.7" + "@babel/types" "^7.23.6" -"@babel/highlight@^7.22.13": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" - integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== +"@babel/highlight@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" + integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== dependencies: "@babel/helper-validator-identifier" "^7.22.20" chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" - integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.6.tgz#ba1c9e512bda72a47e285ae42aff9d2a635a9e3b" + integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -253,28 +253,28 @@ "@babel/parser" "^7.22.15" "@babel/types" "^7.22.15" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.23.2": - version "7.23.2" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" - integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== +"@babel/traverse@^7.1.0", "@babel/traverse@^7.23.7": + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.7.tgz#9a7bf285c928cb99b5ead19c3b1ce5b310c9c305" + integrity sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg== dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/generator" "^7.23.0" + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-function-name" "^7.23.0" "@babel/helper-hoist-variables" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.23.0" - "@babel/types" "^7.23.0" - debug "^4.1.0" + "@babel/parser" "^7.23.6" + "@babel/types" "^7.23.6" + debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.3.3": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" - integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.6", "@babel/types@^7.3.3": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.6.tgz#be33fdb151e1f5a56877d704492c240fc71c7ccd" + integrity sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg== dependencies: - "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-string-parser" "^7.23.4" "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" @@ -530,9 +530,9 @@ integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7": - version "7.20.3" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.3.tgz#d5625a50b6f18244425a1359a858c73d70340778" - integrity sha512-54fjTSeSHwfan8AyHWrKbfBWiEUrNTZsUwPTDSNaaP1QDQIZbeNUg3a59E9D+375MzUw/x1vx2/0F5LBz+AeYA== + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== dependencies: "@babel/parser" "^7.20.7" "@babel/types" "^7.20.7" @@ -541,64 +541,64 @@ "@types/babel__traverse" "*" "@types/babel__generator@*": - version "7.6.6" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.6.tgz#676f89f67dc8ddaae923f70ebc5f1fa800c031a8" - integrity sha512-66BXMKb/sUWbMdBNdMvajU7i/44RkrA3z/Yt1c7R5xejt8qh84iU54yUWCtm0QwGJlDcf/gg4zd/x4mpLAlb/w== + version "7.6.8" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" + integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== dependencies: "@babel/types" "^7.0.0" "@types/babel__template@*": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.3.tgz#db9ac539a2fe05cfe9e168b24f360701bde41f5f" - integrity sha512-ciwyCLeuRfxboZ4isgdNZi/tkt06m8Tw6uGbBSBgWrnnZGNXiEyM27xc/PjXGQLqlZ6ylbgHMnm7ccF9tCkOeQ== + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": - version "7.20.3" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.3.tgz#a971aa47441b28ef17884ff945d0551265a2d058" - integrity sha512-Lsh766rGEFbaxMIDH7Qa+Yha8cMVI3qAK6CHt3OR0YfxOIn5Z54iHiyDRycHrBqeIiqGa20Kpsv1cavfBKkRSw== + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.5.tgz#7b7502be0aa80cc4ef22978846b983edaafcd4dd" + integrity sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ== dependencies: "@babel/types" "^7.20.7" "@types/graceful-fs@^4.1.2": - version "4.1.8" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.8.tgz#417e461e4dc79d957dc3107f45fe4973b09c2915" - integrity sha512-NhRH7YzWq8WiNKVavKPBmtLYZHxNY19Hh+az28O/phfp68CF45pMFud+ZzJ8ewnxnC5smIdF3dqFeiSUQ5I+pw== + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== dependencies: "@types/node" "*" "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#fdfdd69fa16d530047d9963635bd77c71a08c068" - integrity sha512-zONci81DZYCZjiLe0r6equvZut0b+dBRPBN5kBDjsONnutYNtJMoWQ9uR2RkL1gLG9NMTzvf+29e5RFfPbeKhQ== + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== "@types/istanbul-lib-report@*": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.2.tgz#394798d5f727402eb5ec99eb9618ffcd2b7645a1" - integrity sha512-8toY6FgdltSdONav1XtUHl4LN1yTmLza+EuDazb/fEmRNCwjyqNVIQWs2IfC74IqjHkREs/nQ2FWq5kZU9IC0w== + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== dependencies: "@types/istanbul-lib-coverage" "*" "@types/istanbul-reports@^3.0.0": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.3.tgz#0313e2608e6d6955d195f55361ddeebd4b74c6e7" - integrity sha512-1nESsePMBlf0RPRffLZi5ujYh7IH1BWL4y9pr+Bn3cJBdxz+RTP8bUFljLz9HvzhhOSWKdyBZ4DIivdL6rvgZg== + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== dependencies: "@types/istanbul-lib-report" "*" "@types/node@*": - version "20.8.10" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.10.tgz#a5448b895c753ae929c26ce85cab557c6d4a365e" - integrity sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w== + version "20.10.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.6.tgz#a3ec84c22965802bf763da55b2394424f22bfbb5" + integrity sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw== dependencies: undici-types "~5.26.4" "@types/normalize-package-data@^2.4.0": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.3.tgz#291c243e4b94dbfbc0c0ee26b7666f1d5c030e2c" - integrity sha512-ehPtgRgaULsFG8x0NeYJvmyH1hmlfsNLujHe9dQEia/7MAJYdzMSi19JtchUHjmBA6XC/75dK55mzZH+RyieSg== + version "2.4.4" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901" + integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA== "@types/prettier@^2.0.0": version "2.7.3" @@ -606,19 +606,19 @@ integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== "@types/stack-utils@^2.0.0": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.2.tgz#01284dde9ef4e6d8cef6422798d9a3ad18a66f8b" - integrity sha512-g7CK9nHdwjK2n0ymT2CW698FuWJRIx+RP6embAzZ2Qi8/ilIrA1Imt2LVSeHUzKvpoi7BhmmQcXz95eS0f2JXw== + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== "@types/yargs-parser@*": - version "21.0.2" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.2.tgz#7bd04c5da378496ef1695a1008bf8f71847a8b8b" - integrity sha512-5qcvofLPbfjmBfKaLfj/+f+Sbd6pN4zl7w7VSVI5uz7m9QZTuB2aZAa2uo1wHFBNN2x6g/SoTkXmd8mQnQF2Cw== + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== "@types/yargs@^15.0.0": - version "15.0.17" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.17.tgz#bea870ba551b43831bfaa75de2e4a3849c39322b" - integrity sha512-cj53I8GUcWJIgWVTSVe2L7NJAB5XWGdsoMosVvUgv1jEnMbAcsbaCzt1coUcyi8Sda5PgTWAooG8jNyDTD+CWA== + version "15.0.19" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.19.tgz#328fb89e46109ecbdb70c295d96ff2f46dfd01b9" + integrity sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA== dependencies: "@types/yargs-parser" "*" @@ -654,9 +654,9 @@ acorn@^7.1.1: integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== acorn@^8.2.4: - version "8.11.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" - integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== agent-base@6: version "6.0.2" @@ -887,14 +887,14 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browserslist@^4.21.9: - version "4.22.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.1.tgz#ba91958d1a59b87dab6fed8dfbcb3da5e2e9c619" - integrity sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ== +browserslist@^4.22.2: + version "4.22.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.2.tgz#704c4943072bd81ea18997f3bd2180e89c77874b" + integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A== dependencies: - caniuse-lite "^1.0.30001541" - electron-to-chromium "^1.4.535" - node-releases "^2.0.13" + caniuse-lite "^1.0.30001565" + electron-to-chromium "^1.4.601" + node-releases "^2.0.14" update-browserslist-db "^1.0.13" bser@2.1.1: @@ -930,12 +930,13 @@ cache-base@^1.0.1: unset-value "^1.0.0" call-bind@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + version "1.0.5" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513" + integrity sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ== dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" + function-bind "^1.1.2" + get-intrinsic "^1.2.1" + set-function-length "^1.1.1" callsites@^3.0.0: version "3.1.0" @@ -952,10 +953,10 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001541: - version "1.0.30001559" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001559.tgz#95a982440d3d314c471db68d02664fb7536c5a30" - integrity sha512-cPiMKZgqgkg5LY3/ntGeLFUpi6tzddBNS58A4tnTgQw1zON7u2sZMU7SzOeVH4tj20++9ggL+V6FDOFMTaFFYA== +caniuse-lite@^1.0.30001565: + version "1.0.30001572" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001572.tgz#1ccf7dc92d2ee2f92ed3a54e11b7b4a3041acfa0" + integrity sha512-1Pbh5FLmn5y4+QhNyJE9j3/7dK44dGB83/ZMjv/qJk86TvDbjk0LosiZo0i0WB0Vx607qMX9jYrn1VLHCkN4rw== capture-exit@^2.0.0: version "2.0.0" @@ -1065,9 +1066,9 @@ combined-stream@^1.0.8: delayed-stream "~1.0.0" component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + version "1.3.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17" + integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ== concat-map@0.0.1: version "0.0.1" @@ -1173,7 +1174,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3: dependencies: ms "2.0.0" -debug@4, debug@^4.1.0, debug@^4.1.1: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -1200,6 +1201,15 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== +define-data-property@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.1.tgz#c35f7cd0ab09883480d12ac5cb213715587800b3" + integrity sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ== + dependencies: + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + define-property@^0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" @@ -1264,10 +1274,10 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.4.535: - version "1.4.574" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.574.tgz#6de04d7c6e244e5ffcae76d2e2a33b02cab66781" - integrity sha512-bg1m8L0n02xRzx4LsTTMbBPiUd9yIR+74iPtS/Ao65CuXvhVZHP0ym1kSdDG3yHFDXqHQQBKujlN1AQ8qZnyFg== +electron-to-chromium@^1.4.601: + version "1.4.616" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.616.tgz#4bddbc2c76e1e9dbf449ecd5da3d8119826ea4fb" + integrity sha512-1n7zWYh8eS0L9Uy+GskE0lkBUNK83cXTVJI0pU3mGprFsbfSdAc15VTFbo+A+Bq4pwstmL30AVcEU3Fo463lNg== emittery@^0.7.1: version "0.7.2" @@ -1574,11 +1584,6 @@ fsevents@^2.1.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - function-bind@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" @@ -1594,15 +1599,15 @@ get-caller-file@^2.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2: - version "1.2.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" - integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== +get-intrinsic@^1.0.2, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b" + integrity sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA== dependencies: - function-bind "^1.1.1" - has "^1.0.3" + function-bind "^1.1.2" has-proto "^1.0.1" has-symbols "^1.0.3" + hasown "^2.0.0" get-package-type@^0.1.0: version "0.1.0" @@ -1645,6 +1650,13 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + graceful-fs@^4.2.4: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" @@ -1677,6 +1689,13 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +has-property-descriptors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz#52ba30b6c5ec87fd89fa574bc1c39125c6f65340" + integrity sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg== + dependencies: + get-intrinsic "^1.2.2" + has-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" @@ -1718,13 +1737,6 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - hasown@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" @@ -1983,9 +1995,9 @@ isobject@^3.0.0, isobject@^3.0.1: integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" - integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== istanbul-lib-instrument@^4.0.3: version "4.0.3" @@ -2714,10 +2726,10 @@ node-pty@^1.0.0: dependencies: nan "^2.17.0" -node-releases@^2.0.13: - version "2.0.13" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" - integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== normalize-package-data@^2.5.0: version "2.5.0" @@ -2770,9 +2782,9 @@ object-copy@^0.1.0: kind-of "^3.0.3" object-inspect@^1.9.0: - version "1.12.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" - integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== + version "1.13.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" + integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== object-visit@^1.0.0: version "1.0.1" @@ -3196,6 +3208,16 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== +set-function-length@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.1.1.tgz#4bc39fafb0307224a33e106a7d35ca1218d659ed" + integrity sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ== + dependencies: + define-data-property "^1.1.1" + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" @@ -3799,9 +3821,9 @@ write-file-atomic@^3.0.0: typedarray-to-buffer "^3.1.5" ws@>=7.4.6: - version "8.14.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.1.tgz#4b9586b4f70f9e6534c7bb1d3dc0baa8b8cf01e0" - integrity sha512-4OOseMUq8AzRBI/7SLMUwO+FEDnguetSk7KMb1sHwvF2w2Wv5Hoj0nlifx8vtGsftE/jWHojPy8sMMzYLJ2G/A== + version "8.16.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4" + integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ== ws@^7.4.6: version "7.5.9" diff --git a/ood-portal-generator/spec/fixtures/ood-portal.conf.all b/ood-portal-generator/spec/fixtures/ood-portal.conf.all index 85197afce5..bb58d02d3e 100644 --- a/ood-portal-generator/spec/fixtures/ood-portal.conf.all +++ b/ood-portal-generator/spec/fixtures/ood-portal.conf.all @@ -67,6 +67,7 @@ Listen 8080 CustomLog "/path/to/my/logs/test.server.name_access_ssl.log" combined RewriteEngine On + RewriteCond %{HTTP_HOST} !^(foo.example.com(:8080)?)?$ [NC] RewriteCond %{HTTP_HOST} !^(test.proxy.name(:8080)?)?$ [NC] RewriteRule ^(.*) https://test.proxy.name:8080$1 [R=301,NE,L] diff --git a/ood-portal-generator/templates/ood-portal.conf.erb b/ood-portal-generator/templates/ood-portal.conf.erb index 630f7cb65b..0a2e1f2414 100644 --- a/ood-portal-generator/templates/ood-portal.conf.erb +++ b/ood-portal-generator/templates/ood-portal.conf.erb @@ -87,7 +87,9 @@ Listen <%= addr_port %> <%- if @servername && @use_rewrites -%> RewriteEngine On - RewriteCond %{HTTP_HOST} !^(<%= @proxy_server %>(:<%= @port %>)?)?$ [NC] + <%- (@server_aliases + [@proxy_server]).each do |server| -%> + RewriteCond %{HTTP_HOST} !^(<%= server %>(:<%= @port %>)?)?$ [NC] + <%- end -%> RewriteRule ^(.*) <%= @ssl ? "https" : "http" %>://<%= @proxy_server %>:<%= @port %>$1 [R=301,NE,L] <%- end -%> diff --git a/spec/e2e/e2e_helper.rb b/spec/e2e/e2e_helper.rb index afc9bb0644..32d42a073c 100644 --- a/spec/e2e/e2e_helper.rb +++ b/spec/e2e/e2e_helper.rb @@ -79,33 +79,15 @@ def codename end def packager - if apt? - 'DEBIAN_FRONTEND=noninteractive apt' - elsif host_inventory['platform'] == 'redhat' && host_inventory['platform_version'] =~ /^7/ - 'yum' - else - 'dnf' - end + apt? ? 'DEBIAN_FRONTEND=noninteractive apt' : 'dnf' end def apache_service - if apt? - 'apache2' - elsif host_inventory['platform'] == 'redhat' && host_inventory['platform_version'] =~ /^7/ - 'httpd24-httpd' - else - 'httpd' - end + apt? ? 'apache2' : 'httpd' end def apache_reload - if apt? - '/usr/sbin/apachectl graceful' - elsif host_inventory['platform'] == 'redhat' && host_inventory['platform_version'] =~ /^7/ - '/opt/rh/httpd24/root/usr/sbin/httpd-scl-wrapper $OPTIONS -k graceful' - else - '/usr/sbin/httpd $OPTIONS -k graceful' - end + apt? ? '/usr/sbin/apachectl graceful' : '/usr/sbin/httpd $OPTIONS -k graceful' end def apache_user @@ -137,13 +119,8 @@ def bootstrap_repos case host_inventory['platform'] when 'redhat' repos << 'epel-release' - case host_inventory['platform_version'] - when /^7/ - repos << 'centos-release-scl yum-plugin-priorities' - when /^(8|9)/ - on hosts, 'dnf -y module enable ruby:3.1' - on hosts, 'dnf -y module enable nodejs:18' - end + on hosts, 'dnf -y module enable ruby:3.1' + on hosts, 'dnf -y module enable nodejs:18' when 'ubuntu', 'debian' on hosts, 'apt-get update' end @@ -193,12 +170,7 @@ def install_ondemand on hosts, "[ -f /etc/yum.repos.d/ondemand-web.repo ] || #{packager} install -y #{release_rpm}" on hosts, "sed -i 's|ondemand/#{build_repo_version}/web|ondemand/build/#{build_repo_version}/web|g' /etc/yum.repos.d/ondemand-web.repo" - config_manager = if host_inventory['platform_version'] =~ /^7/ - 'yum-config-manager' - else - 'dnf config-manager' - end - on hosts, "#{config_manager} --save --setopt ondemand-web.exclude='ondemand ondemand-gems* ondemand-selinux'" + on hosts, "dnf config-manager --save --setopt ondemand-web.exclude='ondemand ondemand-gems* ondemand-selinux'" install_packages(['ondemand', 'ondemand-dex', 'ondemand-selinux']) elsif apt? install_packages(['wget']) @@ -229,13 +201,7 @@ def upload_portal_config(file) end def apache_conf_dir - if apt? - '/etc/apache2/sites-available' - elsif host_inventory['platform'] == 'redhat' && host_inventory['platform_version'] =~ /^7/ - '/opt/rh/httpd24/root/etc/httpd/conf.d' - else - '/etc/httpd/conf.d' - end + apt? ? '/etc/apache2/sites-available' : '/etc/httpd/conf.d' end def host_portal_config diff --git a/spec/e2e/nodesets/el7-aarch64.yml b/spec/e2e/nodesets/el7-aarch64.yml deleted file mode 100644 index f5c2d68a8a..0000000000 --- a/spec/e2e/nodesets/el7-aarch64.yml +++ /dev/null @@ -1,35 +0,0 @@ -HOSTS: - el7: - roles: - - agent - platform: el-7-x86_64 - hypervisor: docker - image: arm64v8/centos:7 - docker_preserve_image: true - docker_cmd: - - '/usr/sbin/init' - docker_image_commands: - - 'yum install -y yum-utils wget which cronie iproute initscripts strace' - - wget --no-check-certificate https://copr.fedorainfracloud.org/coprs/jsynacek/systemd-backports-for-centos-7/repo/epel-7/jsynacek-systemd-backports-for-centos-7-epel-7.repo -O /etc/yum.repos.d/jsynacek-systemd-centos-7.repo - - yum update -y systemd - docker_env: - - LANG=en_US.UTF-8 - - LANGUAGE=en_US.UTF-8 - - LC_ALL=en_US.UTF-8 - docker_container_name: 'ondemand-el7' - docker_port_bindings: - 22/tcp: - - HostPort: '2222' - HostIp: '127.0.0.1' - 8080/tcp: - - HostPort: '8080' - HostIp: '0.0.0.0' - 5556/tcp: - - HostPort: '5556' - HostIp: '0.0.0.0' -CONFIG: - log_level: debug - type: foss -ssh: - password: root - auth_methods: ["password"] diff --git a/spec/e2e/nodesets/el7.yml b/spec/e2e/nodesets/el7.yml deleted file mode 100644 index 406b9ab0e0..0000000000 --- a/spec/e2e/nodesets/el7.yml +++ /dev/null @@ -1,35 +0,0 @@ -HOSTS: - el7: - roles: - - agent - platform: el-7-x86_64 - hypervisor: docker - image: centos:7 - docker_preserve_image: true - docker_cmd: - - '/usr/sbin/init' - docker_image_commands: - - 'yum install -y yum-utils wget which cronie iproute initscripts strace' - - wget --no-check-certificate https://copr.fedorainfracloud.org/coprs/jsynacek/systemd-backports-for-centos-7/repo/epel-7/jsynacek-systemd-backports-for-centos-7-epel-7.repo -O /etc/yum.repos.d/jsynacek-systemd-centos-7.repo - - yum update -y systemd - docker_env: - - LANG=en_US.UTF-8 - - LANGUAGE=en_US.UTF-8 - - LC_ALL=en_US.UTF-8 - docker_container_name: 'ondemand-el7' - docker_port_bindings: - 22/tcp: - - HostPort: '2222' - HostIp: '127.0.0.1' - 8080/tcp: - - HostPort: '8080' - HostIp: '0.0.0.0' - 5556/tcp: - - HostPort: '5556' - HostIp: '0.0.0.0' -CONFIG: - log_level: debug - type: foss -ssh: - password: root - auth_methods: ["password"]