diff --git a/Gemfile b/Gemfile index e77ae876..5c6bbd31 100644 --- a/Gemfile +++ b/Gemfile @@ -35,6 +35,7 @@ gem 'uglifier', '>= 1.3.0' gem 'will_paginate', '~> 3.0' gem 'tubesock' gem 'faye-websocket' +gem 'nokogiri' group :development do gem 'better_errors', platform: :ruby diff --git a/Gemfile.lock b/Gemfile.lock index 268f8596..5feafdc7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,41 +2,40 @@ GEM remote: https://rubygems.org/ specs: ZenTest (4.11.0) - actionmailer (4.1.14) - actionpack (= 4.1.14) - actionview (= 4.1.14) + actionmailer (4.1.14.1) + actionpack (= 4.1.14.1) + actionview (= 4.1.14.1) mail (~> 2.5, >= 2.5.4) - actionpack (4.1.14) - actionview (= 4.1.14) - activesupport (= 4.1.14) + actionpack (4.1.14.1) + actionview (= 4.1.14.1) + activesupport (= 4.1.14.1) rack (~> 1.5.2) rack-test (~> 0.6.2) - actionview (4.1.14) - activesupport (= 4.1.14) + actionview (4.1.14.1) + activesupport (= 4.1.14.1) builder (~> 3.1) erubis (~> 2.7.0) - activemodel (4.1.14) - activesupport (= 4.1.14) + activemodel (4.1.14.1) + activesupport (= 4.1.14.1) builder (~> 3.1) - activerecord (4.1.14) - activemodel (= 4.1.14) - activesupport (= 4.1.14) + activerecord (4.1.14.1) + activemodel (= 4.1.14.1) + activesupport (= 4.1.14.1) arel (~> 5.0.0) - activerecord-jdbc-adapter (1.3.15) + activerecord-jdbc-adapter (1.3.19) activerecord (>= 2.2) - activerecord-jdbcpostgresql-adapter (1.3.15) - activerecord-jdbc-adapter (~> 1.3.15) + activerecord-jdbcpostgresql-adapter (1.3.19) + activerecord-jdbc-adapter (~> 1.3.19) jdbc-postgres (>= 9.1) - activesupport (4.1.14) + activesupport (4.1.14.1) i18n (~> 0.6, >= 0.6.9) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) thread_safe (~> 0.1) tzinfo (~> 1.1) + addressable (2.4.0) arel (5.0.1.20140414130214) - ast (2.0.0) - astrolabe (1.3.0) - parser (>= 2.2.0.pre.3, < 3.0) + ast (2.2.0) autotest-rails (4.2.1) ZenTest (~> 4.5) bcrypt (3.1.10) @@ -50,7 +49,7 @@ GEM bootstrap-will_paginate (0.0.10) will_paginate builder (3.2.2) - byebug (6.0.2) + byebug (8.2.2) capistrano (3.3.5) capistrano-stats (~> 1.1.0) i18n @@ -59,19 +58,20 @@ GEM capistrano-bundler (1.1.4) capistrano (~> 3.1) sshkit (~> 1.2) - capistrano-rails (1.1.2) + capistrano-rails (1.1.6) capistrano (~> 3.1) capistrano-bundler (~> 1.1) capistrano-rvm (0.1.2) capistrano (~> 3.0) sshkit (~> 1.2) capistrano-stats (1.1.1) - capistrano-upload-config (0.6.0) + capistrano-upload-config (0.7.0) capistrano (>= 3.0) - capistrano3-puma (0.9.0) + capistrano3-puma (1.2.1) capistrano (~> 3.0) puma (>= 2.6) - capybara (2.4.4) + capybara (2.6.2) + addressable mime-types (>= 1.16) nokogiri (>= 1.3.3) rack (>= 1.0.0) @@ -82,9 +82,9 @@ GEM activesupport (>= 3.2.0) json (>= 1.7) mime-types (>= 1.16) - childprocess (0.5.6) + childprocess (0.5.9) ffi (~> 1.0, >= 1.0.11) - codeclimate-test-reporter (0.4.7) + codeclimate-test-reporter (0.4.8) simplecov (>= 0.7.1, < 1.0.0) coderay (1.1.0) coffee-rails (4.0.1) @@ -93,13 +93,12 @@ GEM coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.9.1) - colorize (0.7.7) + coffee-script-source (1.10.0) concurrent-ruby (1.0.0) concurrent-ruby (1.0.0-java) concurrent-ruby-ext (1.0.0) concurrent-ruby (~> 1.0.0) - database_cleaner (1.4.1) + database_cleaner (1.5.1) debug_inspector (0.0.2) diff-lcs (1.2.5) docile (1.1.5) @@ -107,34 +106,34 @@ GEM excon (>= 0.38.0) json erubis (2.7.0) - eventmachine (1.0.8) - eventmachine (1.0.8-java) + eventmachine (1.0.9.1) + eventmachine (1.0.9.1-java) excon (0.45.4) - execjs (2.5.2) + execjs (2.6.0) factory_girl (4.5.0) activesupport (>= 3.0.0) - factory_girl_rails (4.5.0) + factory_girl_rails (4.6.0) factory_girl (~> 4.5.0) railties (>= 3.0.0) - faraday (0.9.1) + faraday (0.9.2) multipart-post (>= 1.2, < 3) - faye-websocket (0.10.0) + faye-websocket (0.10.2) eventmachine (>= 0.12.0) websocket-driver (>= 0.5.1) - ffi (1.9.8) - ffi (1.9.8-java) + ffi (1.9.10) + ffi (1.9.10-java) forgery (0.6.0) - highline (1.7.1) + highline (1.7.8) hike (1.2.3) i18n (0.7.0) - ims-lti (1.1.8) + ims-lti (1.1.10) builder oauth (~> 0.4.5) - jbuilder (2.2.13) - activesupport (>= 3.0.0, < 5) + jbuilder (2.4.1) + activesupport (>= 3.0.0, < 5.1) multi_json (~> 1.2) - jdbc-postgres (9.4.1200) - jquery-rails (3.1.2) + jdbc-postgres (9.4.1206) + jquery-rails (3.1.4) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) jquery-turbolinks (2.1.0) @@ -142,115 +141,117 @@ GEM turbolinks json (1.8.3) json (1.8.3-java) - jwt (1.4.1) - kramdown (1.6.0) + jwt (1.5.1) + kramdown (1.9.0) mail (2.6.3) mime-types (>= 1.16, < 3) method_source (0.8.2) mime-types (2.99) - mini_portile (0.6.2) - minitest (5.8.3) + mini_portile2 (2.0.0) + minitest (5.8.4) multi_json (1.11.2) multi_xml (0.5.5) multipart-post (2.0.0) net-scp (1.2.1) net-ssh (>= 2.6.5) - net-ssh (2.9.2) - newrelic_rpm (3.11.2.286) - nokogiri (1.6.6.2) - mini_portile (~> 0.6.0) - nokogiri (1.6.6.2-java) + net-ssh (3.0.2) + newrelic_rpm (3.14.3.313) + nokogiri (1.6.7.2) + mini_portile2 (~> 2.0.0.rc2) + nokogiri (1.6.7.2-java) nyan-cat-formatter (0.11) rspec (>= 2.99, >= 2.14.2, < 4) oauth (0.4.7) - oauth2 (1.0.0) + oauth2 (1.1.0) faraday (>= 0.8, < 0.10) - jwt (~> 1.0) + jwt (~> 1.0, < 1.5.2) multi_json (~> 1.3) multi_xml (~> 0.5) - rack (~> 1.2) - parser (2.2.0.3) - ast (>= 1.1, < 3.0) - pg (0.18.1) - polyamorous (1.2.0) + rack (>= 1.2, < 3) + parser (2.3.0.6) + ast (~> 2.2) + pg (0.18.4) + polyamorous (1.3.0) activerecord (>= 3.0) - powerpack (0.1.0) - pry (0.10.1) + powerpack (0.1.1) + pry (0.10.3) coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) - pry (0.10.1-java) + pry (0.10.3-java) coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) spoon (~> 0.0) puma (2.15.3) puma (2.15.3-java) - pundit (0.3.0) + pundit (1.1.0) activesupport (>= 3.0.0) rack (1.5.5) rack-test (0.6.3) rack (>= 1.0) - rails (4.1.14) - actionmailer (= 4.1.14) - actionpack (= 4.1.14) - actionview (= 4.1.14) - activemodel (= 4.1.14) - activerecord (= 4.1.14) - activesupport (= 4.1.14) + rails (4.1.14.1) + actionmailer (= 4.1.14.1) + actionpack (= 4.1.14.1) + actionview (= 4.1.14.1) + activemodel (= 4.1.14.1) + activerecord (= 4.1.14.1) + activesupport (= 4.1.14.1) bundler (>= 1.3.0, < 2.0) - railties (= 4.1.14) + railties (= 4.1.14.1) sprockets-rails (~> 2.0) - rails-i18n (4.0.4) - i18n (~> 0.6) + rails-i18n (4.0.8) + i18n (~> 0.7) railties (~> 4.0) - railties (4.1.14) - actionpack (= 4.1.14) - activesupport (= 4.1.14) + railties (4.1.14.1) + actionpack (= 4.1.14.1) + activesupport (= 4.1.14.1) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rainbow (2.0.0) - rake (10.4.2) - ransack (1.6.6) + rainbow (2.1.0) + rake (10.5.0) + ransack (1.7.0) actionpack (>= 3.0) activerecord (>= 3.0) activesupport (>= 3.0) i18n polyamorous (~> 1.2) - rdoc (4.2.0) - rspec (3.1.0) - rspec-core (~> 3.1.0) - rspec-expectations (~> 3.1.0) - rspec-mocks (~> 3.1.0) + rdoc (4.2.2) + json (~> 1.4) + rspec (3.4.0) + rspec-core (~> 3.4.0) + rspec-expectations (~> 3.4.0) + rspec-mocks (~> 3.4.0) rspec-autotest (1.0.0) rspec-core (>= 2.99.0.beta1, < 4.0.0) - rspec-core (3.1.7) - rspec-support (~> 3.1.0) - rspec-expectations (3.1.2) + rspec-core (3.4.2) + rspec-support (~> 3.4.0) + rspec-expectations (3.4.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.1.0) - rspec-mocks (3.1.3) - rspec-support (~> 3.1.0) - rspec-rails (3.1.0) - actionpack (>= 3.0) - activesupport (>= 3.0) - railties (>= 3.0) - rspec-core (~> 3.1.0) - rspec-expectations (~> 3.1.0) - rspec-mocks (~> 3.1.0) - rspec-support (~> 3.1.0) - rspec-support (3.1.2) - rubocop (0.30.0) - astrolabe (~> 1.3) - parser (>= 2.2.0.1, < 3.0) + rspec-support (~> 3.4.0) + rspec-mocks (3.4.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.4.0) + rspec-rails (3.4.2) + actionpack (>= 3.0, < 4.3) + activesupport (>= 3.0, < 4.3) + railties (>= 3.0, < 4.3) + rspec-core (~> 3.4.0) + rspec-expectations (~> 3.4.0) + rspec-mocks (~> 3.4.0) + rspec-support (~> 3.4.0) + rspec-support (3.4.1) + rubocop (0.37.2) + parser (>= 2.3.0.4, < 3.0) powerpack (~> 0.1) rainbow (>= 1.99.1, < 3.0) - ruby-progressbar (~> 1.4) - rubocop-rspec (1.2.2) + ruby-progressbar (~> 1.7) + unicode-display_width (~> 0.3) + rubocop-rspec (1.4.0) ruby-progressbar (1.7.5) - rubytree (0.9.4) + rubytree (0.9.7) json (~> 1.8) - structured_warnings (~> 0.1) + structured_warnings (~> 0.2) rubyzip (1.1.7) sass (3.2.19) sass-rails (4.0.5) @@ -261,17 +262,17 @@ GEM sdoc (0.4.1) json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) - selenium-webdriver (2.45.0) + selenium-webdriver (2.52.0) childprocess (~> 0.5) multi_json (~> 1.0) rubyzip (~> 1.0) websocket (~> 1.0) - simplecov (0.9.2) + simplecov (0.11.2) docile (~> 1.1.0) - multi_json (~> 1.0) - simplecov-html (~> 0.9.0) - simplecov-html (0.9.0) - slim (3.0.3) + json (~> 1.8) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.0) + slim (3.0.6) temple (~> 0.7.3) tilt (>= 1.3.3, < 2.1) slop (3.6.0) @@ -281,7 +282,7 @@ GEM oauth2 (>= 0.8.0) spoon (0.0.4) ffi - spring (1.3.4) + spring (1.6.3) sprockets (2.12.4) hike (~> 1.2) multi_json (~> 1.0) @@ -291,12 +292,11 @@ GEM actionpack (>= 3.0) activesupport (>= 3.0) sprockets (>= 2.8, < 4.0) - sshkit (1.7.1) - colorize (>= 0.7.0) + sshkit (1.8.1) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) structured_warnings (0.2.0) - temple (0.7.5) + temple (0.7.6) thor (0.19.1) thread_safe (0.3.5) thread_safe (0.3.5-java) @@ -308,21 +308,22 @@ GEM coffee-rails tzinfo (1.2.2) thread_safe (~> 0.1) - uglifier (2.7.1) + uglifier (2.7.2) execjs (>= 0.3.0) json (>= 1.8.0) - web-console (2.1.2) + unicode-display_width (0.3.1) + web-console (2.3.0) activemodel (>= 4.0) binding_of_caller (>= 0.7.2) railties (>= 4.0) sprockets-rails (>= 2.0, < 4.0) - websocket (1.2.1) - websocket-driver (0.6.2) + websocket (1.2.2) + websocket-driver (0.6.3) websocket-extensions (>= 0.1.0) - websocket-driver (0.6.2-java) + websocket-driver (0.6.3-java) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.2) - will_paginate (3.0.7) + will_paginate (3.1.0) xpath (2.0.0) nokogiri (~> 1.3) @@ -361,6 +362,7 @@ DEPENDENCIES jquery-turbolinks kramdown newrelic_rpm + nokogiri nyan-cat-formatter pg pry diff --git a/app/assets/javascripts/editor.js.erb b/app/assets/javascripts/editor.js.erb index b5eefbf5..2da4a37b 100644 --- a/app/assets/javascripts/editor.js.erb +++ b/app/assets/javascripts/editor.js.erb @@ -30,7 +30,7 @@ $(function() { numMessages = 0, turtlecanvas = $('#turtlecanvas'), prompt = $('#prompt'), - commands = ['input', 'write', 'turtle', 'turtlebatch', 'exit', 'timeout', 'status'], + commands = ['input', 'write', 'turtle', 'turtlebatch', 'render', 'exit', 'timeout', 'status'], streams = ['stdin', 'stdout', 'stderr']; var ENTER_KEY_CODE = 13; @@ -245,6 +245,18 @@ $(function() { } }; + var findOrCreateRenderElement = function(index) { + if ($('#render-' + index).isPresent()) { + return $('#render-' + index); + } else { + var element = $('
').attr('id', 'render-' + index); + $('#render').append(element); + return element; + } + }; + + + var getPanelClass = function(result) { if (result.stderr && !result.score) { return 'panel-danger'; @@ -1172,6 +1184,9 @@ $(function() { showCanvas(); handleTurtlebatchCommand(msg); break; + case 'render': + renderWebsocketOutput(msg); + break; case 'exit': killWebsocketAndContainer(); break; @@ -1185,6 +1200,11 @@ $(function() { } }; + var renderWebsocketOutput = function(msg){ + var element = findOrCreateRenderElement(0); + element.append(msg.data); + }; + var printWebsocketOutput = function(msg) { if (!msg.data) { return; diff --git a/app/assets/stylesheets/code_harbor_links.css.scss b/app/assets/stylesheets/code_harbor_links.css.scss new file mode 100644 index 00000000..5ca7b3ce --- /dev/null +++ b/app/assets/stylesheets/code_harbor_links.css.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the CodeHarborLinks controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/scaffolds.css.scss b/app/assets/stylesheets/scaffolds.css.scss new file mode 100644 index 00000000..6ec6a8ff --- /dev/null +++ b/app/assets/stylesheets/scaffolds.css.scss @@ -0,0 +1,69 @@ +body { + background-color: #fff; + color: #333; + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; +} + +p, ol, ul, td { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; +} + +pre { + background-color: #eee; + padding: 10px; + font-size: 11px; +} + +a { + color: #000; + &:visited { + color: #666; + } + &:hover { + color: #fff; + background-color: #000; + } +} + +div { + &.field, &.actions { + margin-bottom: 10px; + } +} + +#notice { + color: green; +} + +.field_with_errors { + padding: 2px; + background-color: red; + display: table; +} + +#error_explanation { + width: 450px; + border: 2px solid red; + padding: 7px; + padding-bottom: 0; + margin-bottom: 20px; + background-color: #f0f0f0; + h2 { + text-align: left; + font-weight: bold; + padding: 5px 5px 5px 15px; + font-size: 12px; + margin: -7px; + margin-bottom: 0px; + background-color: #c00; + color: #fff; + } + ul li { + font-size: 12px; + list-style: square; + } +} diff --git a/app/controllers/code_harbor_links_controller.rb b/app/controllers/code_harbor_links_controller.rb new file mode 100644 index 00000000..a4736f26 --- /dev/null +++ b/app/controllers/code_harbor_links_controller.rb @@ -0,0 +1,68 @@ +class CodeHarborLinksController < ApplicationController + include CommonBehavior + before_action :set_code_harbor_link, only: [:show, :edit, :update, :destroy] + + def authorize! + authorize(@code_harbor_link || @code_harbor_links) + end + private :authorize! + + # GET /code_harbor_links + # GET /code_harbor_links.json + def index + @code_harbor_links = CodeHarborLink.where(user_id: current_user.id).paginate(page: params[:page]) + authorize! + end + + # GET /code_harbor_links/1 + # GET /code_harbor_links/1.json + def show + authorize! + end + + # GET /code_harbor_links/new + def new + @code_harbor_link = CodeHarborLink.new + authorize! + end + + # GET /code_harbor_links/1/edit + def edit + authorize! + end + + # POST /code_harbor_links + # POST /code_harbor_links.json + def create + @code_harbor_link = CodeHarborLink.new(code_harbor_link_params) + @code_harbor_link.user = current_user + authorize! + create_and_respond(object: @code_harbor_link) + end + + # PATCH/PUT /code_harbor_links/1 + # PATCH/PUT /code_harbor_links/1.json + def update + update_and_respond(object: @code_harbor_link, params: code_harbor_link_params) + authorize! + end + + # DELETE /code_harbor_links/1 + # DELETE /code_harbor_links/1.json + def destroy + destroy_and_respond(object: @code_harbor_link) + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_code_harbor_link + @code_harbor_link = CodeHarborLink.find(params[:id]) + @code_harbor_link.user = current_user + authorize! + end + + # Never trust parameters from the scary internet, only allow the white list through. + def code_harbor_link_params + params.require(:code_harbor_link).permit(:oauth2token) + end +end diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb index cfe4d155..69d1f46f 100644 --- a/app/controllers/exercises_controller.rb +++ b/app/controllers/exercises_controller.rb @@ -11,6 +11,10 @@ class ExercisesController < ApplicationController before_action :set_file_types, only: [:create, :edit, :new, :update] before_action :set_teams, only: [:create, :edit, :new, :update] + skip_before_filter :verify_authenticity_token, only: [:import_proforma_xml] + skip_after_action :verify_authorized, only: [:import_proforma_xml] + skip_after_action :verify_policy_scoped, only: [:import_proforma_xml] + def authorize! authorize(@exercise || @exercises) end @@ -62,6 +66,58 @@ class ExercisesController < ApplicationController def edit end + def import_proforma_xml + begin + user = user_for_oauth2_request() + exercise = Exercise.new + request_body = request.body.read + exercise.from_proforma_xml(request_body) + exercise.user = user + saved = exercise.save + if saved + render :text => 'SUCCESS', :status => 200 + else + logger.info(exercise.errors.full_messages) + render :text => 'Invalid exercise', :status => 400 + end + rescue => error + if error.class == Hash + render :text => error.message, :status => error.status + else + raise error + render :text => '', :status => 500 + end + end + end + + def user_for_oauth2_request + authorizationHeader = request.headers['Authorization'] + if authorizationHeader == nil + raise ({status: 401, message: 'No Authorization header'}) + end + + oauth2Token = authorizationHeader.split(' ')[1] + if oauth2Token == nil || oauth2Token.size == 0 + raise ({status: 401, message: 'No token in Authorization header'}) + end + + user = user_by_code_harbor_token(oauth2Token) + if user == nil + raise ({status: 401, message: 'Unknown OAuth2 token'}) + end + + return user + end + private :user_for_oauth2_request + + def user_by_code_harbor_token(oauth2Token) + link = CodeHarborLink.where(:oauth2token => oauth2Token)[0] + if link != nil + return link.user + end + end + private :user_by_code_harbor_token + def exercise_params params[:exercise].permit(:description, :execution_environment_id, :file_id, :instructions, :public, :hide_file_tree, :team_id, :title, files_attributes: file_attributes).merge(user_id: current_user.id, user_type: current_user.class.name) end diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 3aa3adf4..8f26d986 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -111,6 +111,7 @@ class SubmissionsController < ApplicationController if result[:status] == :container_running socket = result[:socket] + command = result[:command] socket.on :message do |event| Rails.logger.info( Time.now.getutc.to_s + ": Docker sending: " + event.data) @@ -139,6 +140,11 @@ class SubmissionsController < ApplicationController Rails.logger.debug('Rescued parsing error, sent the received client data to docker:' + data) end end + + # Send command after all listeners are attached. + # Newline required to flush + socket.send command + "\n" + Rails.logger.info('Sent command: ' + command.to_s) else kill_socket(tubesock) end @@ -176,7 +182,24 @@ class SubmissionsController < ApplicationController for part in message.split("\n") self.parse_message(part,output_stream,socket,false) end + elsif(message.include? "")) + @buffer += message + parsed = {'cmd'=>'write','stream'=>output_stream,'data'=>@buffer} + socket.send_data JSON.dump(parsed) + #socket.send_data @buffer + @buffering = false + #Rails.logger.info('Sent complete buffer') + elsif(@buffering) + @buffer += message + #Rails.logger.info('Appending to buffer') else + #Rails.logger.info('else') parsed = {'cmd'=>'write','stream'=>output_stream,'data'=>message} socket.send_data JSON.dump(parsed) Rails.logger.info('parse_message sent: ' + JSON.dump(parsed)) diff --git a/app/helpers/code_harbor_links_helper.rb b/app/helpers/code_harbor_links_helper.rb new file mode 100644 index 00000000..d8e92ddf --- /dev/null +++ b/app/helpers/code_harbor_links_helper.rb @@ -0,0 +1,2 @@ +module CodeHarborLinksHelper +end diff --git a/app/models/code_harbor_link.rb b/app/models/code_harbor_link.rb new file mode 100644 index 00000000..2e219aa9 --- /dev/null +++ b/app/models/code_harbor_link.rb @@ -0,0 +1,13 @@ +class CodeHarborLink < ActiveRecord::Base + validates :oauth2token, presence: true + validates :user_id, presence: true + + belongs_to :internal_user, foreign_key: :user_id + alias_method :user, :internal_user + alias_method :user=, :internal_user= + + def to_s + oauth2token + end + +end diff --git a/app/models/exercise.rb b/app/models/exercise.rb index 658a8fb5..4a1e0486 100644 --- a/app/models/exercise.rb +++ b/app/models/exercise.rb @@ -1,3 +1,4 @@ +require 'nokogiri' require File.expand_path('../../../lib/active_model/validations/boolean_presence_validator', __FILE__) class Exercise < ActiveRecord::Base @@ -29,7 +30,11 @@ class Exercise < ActiveRecord::Base def average_percentage - (average_score / maximum_score * 100).round if average_score and maximum_score != 0.0 else 0 + if average_score and maximum_score != 0.0 + (average_score / maximum_score * 100).round + else + 0 + end end def average_score @@ -105,6 +110,54 @@ class Exercise < ActiveRecord::Base exercise end + def determine_file_role_from_proforma_file(task_node, file_node) + file_id = file_node.xpath('@id') + file_class = file_node.xpath('@class').first.value + comment = file_node.xpath('@comment').first.value + is_referenced_by_test = task_node.xpath("p:tests/p:test/p:filerefs/p:fileref[@id=#{file_id}]") + is_referenced_by_model_solution = task_node.xpath("p:model-solutions/p:model-solution/p:filerefs/p:fileref[@id=#{file_id}]") + if is_referenced_by_test && (file_class == 'internal') + return 'teacher_defined_test' + elsif is_referenced_by_model_solution && (file_class == 'internal') + return 'reference_implementation' + elsif (file_class == 'template') && (comment == 'main') + return 'main_file' + elsif (file_class == 'internal') && (comment == 'main') + end + return 'regular_file' + end + + def from_proforma_xml(xml_string) + # how to extract the proforma functionality into a different module in rails? + xml = Nokogiri::XML(xml_string) + xml.collect_namespaces + task_node = xml.xpath('/root/p:task') + description = task_node.xpath('p:description/text()')[0].content + self.attributes = { + title: task_node.xpath('p:meta-data/p:title/text()')[0].content, + description: description, + instructions: description + } + task_node.xpath('p:files/p:file').all? { |file| + file_name_split = file.xpath('@filename').first.value.split('.') + file_class = file.xpath('@class').first.value + role = determine_file_role_from_proforma_file(task_node, file) + feedback_message_nodes = task_node.xpath("p:tests/p:test/p:test-configuration/c:feedback-message/text()") + files.build({ + name: file_name_split.first, + content: file.xpath('text()').first.content, + read_only: false, + hidden: file_class == 'internal', + role: role, + feedback_message: (role == 'teacher_defined_test') ? feedback_message_nodes.first.content : nil, + file_type: FileType.where( + file_extension: ".#{file_name_split.second}" + ).take + }) + } + self.execution_environment_id = 1 + end + def generate_token self.token ||= SecureRandom.hex(4) end @@ -129,4 +182,5 @@ class Exercise < ActiveRecord::Base end end private :valid_main_file? + end diff --git a/app/policies/code_harbor_link_policy.rb b/app/policies/code_harbor_link_policy.rb new file mode 100644 index 00000000..8726c22a --- /dev/null +++ b/app/policies/code_harbor_link_policy.rb @@ -0,0 +1,3 @@ +class CodeHarborLinkPolicy < AdminOnlyPolicy + +end diff --git a/app/views/application/_navigation.html.slim b/app/views/application/_navigation.html.slim index 276723bf..4ab39e30 100644 --- a/app/views/application/_navigation.html.slim +++ b/app/views/application/_navigation.html.slim @@ -8,7 +8,7 @@ - if current_user.admin? li = link_to(t('breadcrumbs.dashboard.show'), admin_dashboard_path) li.divider - - models = [ExecutionEnvironment, Exercise, Consumer, ExternalUser, FileType, InternalUser, Submission, Team].sort_by { |model| model.model_name.human(count: 2) } + - models = [ExecutionEnvironment, Exercise, Consumer, CodeHarborLink, ExternalUser, FileType, InternalUser, Submission, Team].sort_by { |model| model.model_name.human(count: 2) } - models.each do |model| - if policy(model).index? li = link_to(model.model_name.human(count: 2), send(:"#{model.model_name.collection}_path")) diff --git a/app/views/code_harbor_links/_form.html.slim b/app/views/code_harbor_links/_form.html.slim new file mode 100644 index 00000000..f7b449ee --- /dev/null +++ b/app/views/code_harbor_links/_form.html.slim @@ -0,0 +1,6 @@ += form_for(@code_harbor_link) do |f| + = render('shared/form_errors', object: @code_harbor_link) + .form-group + = f.label(:oauth2token) + = f.text_field(:oauth2token, class: 'form-control', required: true) + .actions = render('shared/submit_button', f: f, object: @code_harbor_link) diff --git a/app/views/code_harbor_links/edit.html.slim b/app/views/code_harbor_links/edit.html.slim new file mode 100644 index 00000000..d1c7ea8f --- /dev/null +++ b/app/views/code_harbor_links/edit.html.slim @@ -0,0 +1,3 @@ +h1 = @code_harbor_link + += render('form') diff --git a/app/views/code_harbor_links/index.html.slim b/app/views/code_harbor_links/index.html.slim new file mode 100644 index 00000000..953985c4 --- /dev/null +++ b/app/views/code_harbor_links/index.html.slim @@ -0,0 +1,18 @@ +h1 = CodeHarborLink.model_name.human(count: 2) + +.table-responsive + table.table + thead + tr + th = t('activerecord.attributes.code_harbor_link.oauth2token') + th colspan=3 = t('shared.actions') + tbody + - @code_harbor_links.each do |code_harbor_link| + tr + td = code_harbor_link.oauth2token + td = link_to(t('shared.show'), code_harbor_link) + td = link_to(t('shared.edit'), edit_code_harbor_link_path(code_harbor_link)) + td = link_to(t('shared.destroy'), code_harbor_link, data: {confirm: t('shared.confirm_destroy')}, method: :delete) + += render('shared/pagination', collection: @code_harbor_links) +p = render('shared/new_button', model: CodeHarborLink) diff --git a/app/views/code_harbor_links/new.html.slim b/app/views/code_harbor_links/new.html.slim new file mode 100644 index 00000000..ef19a3e6 --- /dev/null +++ b/app/views/code_harbor_links/new.html.slim @@ -0,0 +1,3 @@ +h1 = t('shared.new_model', model: CodeHarborLink.model_name.human) + += render('form') diff --git a/app/views/code_harbor_links/show.html.slim b/app/views/code_harbor_links/show.html.slim new file mode 100644 index 00000000..b2d95342 --- /dev/null +++ b/app/views/code_harbor_links/show.html.slim @@ -0,0 +1,7 @@ +h1 + = @code_harbor_link + = render('shared/edit_button', object: @code_harbor_link) if policy(@code_harbor_link).edit? + +- %w[oauth2token].each do |attribute| + = row(label: "code_harbor_link.#{attribute}") do + = content_tag(:input, nil, class: 'form-control', readonly: true, value: @code_harbor_link.send(attribute)) diff --git a/config/locales/de.yml b/config/locales/de.yml index 91898861..5d0bc8b1 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1,6 +1,8 @@ de: activerecord: attributes: + code_harbor_link: + oauth2token: OAuth2 Token consumer: name: Name oauth_key: OAuth Key @@ -90,6 +92,9 @@ de: internal_user_ids: Mitglieder name: Name models: + code_harbor_link: + one: CodeHarbor-Link + other: CodeHarbor-Links consumer: one: Konsument other: Konsumenten diff --git a/config/locales/en.yml b/config/locales/en.yml index a4e7b616..38c556f5 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,6 +1,8 @@ en: activerecord: attributes: + code_harbor_link: + oauth2token: OAuth2 Token consumer: name: Name oauth_key: OAuth Key @@ -90,6 +92,9 @@ en: internal_user_ids: Members name: Name models: + code_harbor_link: + one: CodeHarbor Link + other: CodeHarbor Links consumer: one: Consumer other: Consumers diff --git a/config/routes.rb b/config/routes.rb index 24f6a0d9..72ccb489 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,7 @@ FILENAME_REGEXP = /[\w\.]+/ unless Kernel.const_defined?(:FILENAME_REGEXP) Rails.application.routes.draw do + resources :code_harbor_links resources :request_for_comments get '/my_request_for_comments', as: 'my_request_for_comments', to: 'request_for_comments#get_my_comment_requests' resources :comments, except: [:destroy] do @@ -40,6 +41,8 @@ Rails.application.routes.draw do resources :hints end + post '/import_proforma_xml' => 'exercises#import_proforma_xml' + resources :exercises do collection do match '', to: 'exercises#batch_update', via: [:patch, :put] diff --git a/db/migrate/20160204094409_create_code_harbor_links.rb b/db/migrate/20160204094409_create_code_harbor_links.rb new file mode 100644 index 00000000..b87d5c9d --- /dev/null +++ b/db/migrate/20160204094409_create_code_harbor_links.rb @@ -0,0 +1,9 @@ +class CreateCodeHarborLinks < ActiveRecord::Migration + def change + create_table :code_harbor_links do |t| + t.string :oauth2token + + t.timestamps + end + end +end diff --git a/db/migrate/20160204111716_add_user_to_code_harbor_link.rb b/db/migrate/20160204111716_add_user_to_code_harbor_link.rb new file mode 100644 index 00000000..8fa36ed1 --- /dev/null +++ b/db/migrate/20160204111716_add_user_to_code_harbor_link.rb @@ -0,0 +1,5 @@ +class AddUserToCodeHarborLink < ActiveRecord::Migration + def change + add_reference :code_harbor_links, :user, index: true, foreign_key: true + end +end diff --git a/db/schema.rb b/db/schema.rb index d895da3f..793c021a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,11 +11,20 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150922125415) do +ActiveRecord::Schema.define(version: 20160204111716) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "code_harbor_links", force: true do |t| + t.string "oauth2token" + t.datetime "created_at" + t.datetime "updated_at" + t.integer "user_id" + end + + add_index "code_harbor_links", ["user_id"], name: "index_code_harbor_links_on_user_id", using: :btree + create_table "comments", force: true do |t| t.integer "user_id" t.integer "file_id" diff --git a/lib/docker_client.rb b/lib/docker_client.rb index fc760981..80986377 100644 --- a/lib/docker_client.rb +++ b/lib/docker_client.rb @@ -21,14 +21,14 @@ class DockerClient end def self.clean_container_workspace(container) - container.exec(['bash', '-c', 'rm -rf ' + CONTAINER_WORKSPACE_PATH + '/*']) -=begin + # remove files when using transferral via Docker API archive_in (transmit) + #container.exec(['bash', '-c', 'rm -rf ' + CONTAINER_WORKSPACE_PATH + '/*']) + local_workspace_path = local_workspace_path(container) if local_workspace_path && Pathname.new(local_workspace_path).exist? Pathname.new(local_workspace_path).children.each{ |p| p.rmtree} #FileUtils.rmdir(Pathname.new(local_workspace_path)) end -=end end def command_substitutions(filename) @@ -96,9 +96,10 @@ class DockerClient #Rails.logger.info "docker_client: self.create_container with creation options:" #Rails.logger.info(container_creation_options(execution_environment)) container = Docker::Container.create(container_creation_options(execution_environment)) + # container.start sometimes creates the passed local_workspace_path on disk (depending on the setup). + # this is however not guaranteed and caused issues on the server already. Therefore create the necessary folders manually! local_workspace_path = generate_local_workspace_path - # container.start always creates the passed local_workspace_path on disk. Seems like we have to live with that, therefore we can also just create the empty folder ourselves. - # FileUtils.mkdir(local_workspace_path) + FileUtils.mkdir(local_workspace_path) container.start(container_start_options(execution_environment, local_workspace_path)) container.start_time = Time.now container.status = :created @@ -217,7 +218,7 @@ class DockerClient end #called when the user clicks the "Run" button - def execute_websocket_command(command, before_execution_block, output_consuming_block) + def open_websocket_connection(command, before_execution_block, output_consuming_block) @container = DockerContainerPool.get_container(@execution_environment) if @container @container.status = :executing @@ -230,10 +231,7 @@ class DockerClient end # TODO: catch exception if socket could not be created @socket ||= create_socket(@container) - # Newline required to flush - @socket.send command + "\n" - Rails.logger.info('Sent command: ' + command.to_s) - {status: :container_running, socket: @socket, container: @container} + {status: :container_running, socket: @socket, container: @container, command: command} else {status: :container_depleted} end @@ -295,8 +293,9 @@ class DockerClient Run commands by attaching a websocket to Docker. """ command = submission.execution_environment.run_command % command_substitutions(filename) - create_workspace_files = proc { create_workspace_files_transmit(container, submission) } - execute_websocket_command(command, create_workspace_files, block) + create_workspace_files = proc { create_workspace_files(container, submission) } + open_websocket_connection(command, create_workspace_files, block) + # actual run command is run in the submissions controller, after all listeners are attached. end def execute_test_command(submission, filename, &block) @@ -304,7 +303,7 @@ class DockerClient Stick to existing Docker API with exec command. """ command = submission.execution_environment.test_command % command_substitutions(filename) - create_workspace_files = proc { create_workspace_files_transmit(container, submission) } + create_workspace_files = proc { create_workspace_files(container, submission) } execute_command(command, create_workspace_files, block) end diff --git a/lib/docker_container_pool.rb b/lib/docker_container_pool.rb index 90357875..7f1f16fd 100644 --- a/lib/docker_container_pool.rb +++ b/lib/docker_container_pool.rb @@ -41,16 +41,17 @@ class DockerContainerPool @all_containers[execution_environment.id]+=[container] if(!@containers[execution_environment.id].include?(container)) @containers[execution_environment.id]+=[container] - Rails.logger.debug('Added container ' + container.to_s + ' to all_pool for execution environment ' + execution_environment.to_s + '. Containers in all_pool: ' + @all_containers[execution_environment.id].size.to_s) + #Rails.logger.debug('Added container ' + container.to_s + ' to all_pool for execution environment ' + execution_environment.to_s + '. Containers in all_pool: ' + @all_containers[execution_environment.id].size.to_s) else Rails.logger.info('failed trying to add existing container ' + container.to_s + ' to execution_environment ' + execution_environment.to_s) end end def self.create_container(execution_environment) + Rails.logger.info('trying to create container for execution environment: ' + execution_environment.to_s) container = DockerClient.create_container(execution_environment) container.status = 'available' - Rails.logger.debug('created container ' + container.to_s + ' for execution environment ' + execution_environment.to_s) + #Rails.logger.debug('created container ' + container.to_s + ' for execution environment ' + execution_environment.to_s) container end @@ -120,11 +121,11 @@ class DockerContainerPool if refill_count > 0 Rails.logger.info('Adding ' + refill_count.to_s + ' containers for execution_environment ' + execution_environment.name ) c = refill_count.times.map { create_container(execution_environment) } - Rails.logger.info('Created containers: ' + c.to_s ) + #Rails.logger.info('Created containers: ' + c.to_s ) @containers[execution_environment.id] += c @all_containers[execution_environment.id] += c - Rails.logger.debug('@containers for ' + execution_environment.name.to_s + ' (' + @containers.object_id.to_s + ') has the following content: '+ @containers[execution_environment.id].to_s) - Rails.logger.debug('@all_containers for ' + execution_environment.name.to_s + ' (' + @all_containers.object_id.to_s + ') has the following content: ' + @all_containers[execution_environment.id].to_s) + #Rails.logger.debug('@containers for ' + execution_environment.name.to_s + ' (' + @containers.object_id.to_s + ') has the following content: '+ @containers[execution_environment.id].to_s) + #Rails.logger.debug('@all_containers for ' + execution_environment.name.to_s + ' (' + @all_containers.object_id.to_s + ') has the following content: ' + @all_containers[execution_environment.id].to_s) end end diff --git a/lib/xikolo/client.rb b/lib/xikolo/client.rb index db48c91a..ecad50e7 100644 --- a/lib/xikolo/client.rb +++ b/lib/xikolo/client.rb @@ -34,6 +34,7 @@ class Xikolo::Client end def self.url + #todo: JanR: set an environment variable here, fallback value: http://open.hpi.de/api/ 'http://localhost:2000/api/' end