Merge remote-tracking branch 'origin/jupyter' into jupyter

chromesetting
杨树明 5 years ago
commit 46cb3f7d4e

11
.gitignore vendored

@ -6,6 +6,7 @@
# Ignore bundler config. # Ignore bundler config.
/.bundle /.bundle
/bundle
# Ignore lock config file # Ignore lock config file
*.lock *.lock
@ -70,3 +71,13 @@ vendor/bundle/
/workspace /workspace
/log /log
/public/admin /public/admin
/mysql_data
.generators
.rakeTasks
db/bak/
docker/
educoder.sql
redis_data/
Dockerfile

@ -68,6 +68,9 @@ class AttachmentsController < ApplicationController
@attachment.author_id = current_user.id @attachment.author_id = current_user.id
@attachment.disk_directory = month_folder @attachment.disk_directory = month_folder
@attachment.cloud_url = remote_path @attachment.cloud_url = remote_path
@attachment.container_id = conversion_container_id(params[:container_id], params[:container_type])
@attachment.container_type = params[:container_type]
@attachment.attachtype = params[:attachtype]
@attachment.save! @attachment.save!
else else
logger.info "文件已存在id = #{@attachment.id}, filename = #{@attachment.filename}" logger.info "文件已存在id = #{@attachment.id}, filename = #{@attachment.filename}"
@ -91,6 +94,22 @@ class AttachmentsController < ApplicationController
end end
end end
# 多文件删除
def destroy_files
files = Attachment.where(id: params[:id])
begin
files.each do |file|
file_path = absolute_path(local_path(file))
file.destroy!
delete_file(file_path)
end
render_ok
rescue => e
uid_logger_error(e.message)
tip_exception(e.message)
end
end
private private
def find_file def find_file
@file = @file =
@ -196,4 +215,12 @@ class AttachmentsController < ApplicationController
end end
end end
end end
def conversion_container_id id, type
if id.is_a?(String) && type == 'Shixun'
Shixun.find_by_identifier(id).id
else
id
end
end
end end

@ -13,6 +13,9 @@ class ChallengesController < ApplicationController
include ShixunsHelper include ShixunsHelper
include ChallengesHelper include ChallengesHelper
# 新建实践题 # 新建实践题
def new def new
@position = @shixun.challenges.count + 1 @position = @shixun.challenges.count + 1
@ -160,6 +163,8 @@ class ChallengesController < ApplicationController
@shixun.increment!(:visits) @shixun.increment!(:visits)
end end
def show def show
@tab = params[:tab].nil? ? 1 : params[:tab].to_i @tab = params[:tab].nil? ? 1 : params[:tab].to_i
challenge_num = @shixun.challenges_count challenge_num = @shixun.challenges_count

@ -0,0 +1,92 @@
require 'net/http'
class JupytersController < ApplicationController
include JupyterService
before_action :shixun
def open
#打开tpm - juypter接口
shixun = @shixun
shixun_tomcat = edu_setting('cloud_bridge')
uri = "#{shixun_tomcat}/bridge/jupyter/get"
tpiID = "tpm#{shixun.id}"
params = {tpiID: tpiID, :containers => "#{Base64.urlsafe_encode64(shixun_container_limit(shixun))}"}
logger.info "test_juypter: uri->#{uri}, params->#{params}"
res = uri_post uri, params
logger.info "test_juypter: #{res}"
render plain: "https://#{res['port']}.jupyter.educoder.net/notebooks/data/workspace/myshixun_#{tpiID}/01.ipynb"
end
def open1
## 打开tpi
game = Game.find(2170158)
shixun = game.myshixun.shixun
shixun_tomcat = edu_setting('cloud_bridge')
uri = "#{shixun_tomcat}/bridge/jupyter/get"
tpiID = game.myshixun.id
params = {tpiID: tpiID, :containers => "#{Base64.urlsafe_encode64(shixun_container_limit(shixun))}"}
res = uri_post uri, params
logger.info "test_juypter: #{res}"
if res && res['code'].to_i != 0
raise("实训云平台繁忙繁忙等级99")
end
repo_save_path = game.myshixun.repo_save_path
render plain: "https://#{res['port']}.jupyter.educoder.net/notebooks/data/workspace/myshixun_#{tpiID}/#{repo_save_path}/01.ipynb"
end
def test
render plain: 'test'
end
def save()
# 保存01.ipy
author_name = current_user.real_name
author_email = current_user.git_mail
message = "User submitted"
#https://47526.jupyter.educoder.net/nbconvert/notebook/data/workspace/myshixun_570461/f2ef5p798r20191210163135/01.ipynb?download=true
src_url = URI("https://47519.jupyter.educoder.net/nbconvert/notebook/data/workspace/myshixun_tpm3575/01.ipynb?download=true")
response = Net::HTTP.get_response(src_url)
if response.code.to_i != 200
raise("获取文件内容失败:#{response.code}")
end
content = response.body
c = GitService.update_file(repo_path: @shixun.repo_path,
file_path: "01.ipynb",
message: message,
content: content,
author_name: author_name,
author_email: author_email)
render plain: 'save: #{c.size}'
end
private
def shixun
@shixun = Shixun.find(3575)
end
end

@ -3,6 +3,7 @@ class ShixunsController < ApplicationController
include ApplicationHelper include ApplicationHelper
include ElasticsearchAble include ElasticsearchAble
include CoursesHelper include CoursesHelper
include JupyterService
before_action :require_login, :check_auth, except: [:download_file, :index, :menus, :show, :show_right, :ranking_list, before_action :require_login, :check_auth, except: [:download_file, :index, :menus, :show, :show_right, :ranking_list,
:discusses, :collaborators, :fork_list, :propaedeutics] :discusses, :collaborators, :fork_list, :propaedeutics]
@ -24,6 +25,10 @@ class ShixunsController < ApplicationController
before_action :special_allowed, only: [:send_to_course, :search_user_courses] before_action :special_allowed, only: [:send_to_course, :search_user_courses]
helper_method :jupyter_url_with_shixun, :jupyter_port_with_shixun
## 获取课程列表 ## 获取课程列表
def index def index
@shixuns = current_laboratory.shixuns.unhidden @shixuns = current_laboratory.shixuns.unhidden
@ -364,76 +369,112 @@ class ShixunsController < ApplicationController
end end
def create def create
# 评测脚本的一些操作 begin
main_type, sub_type = params[:main_type], params[:small_type] @shixun = CreateShixunService.call(current_user, shixun_params, params)
mirror = MirrorScript.where(:mirror_repository_id => main_type) rescue => e
logger_error("shixun_create_error: #{e.message}")
identifier = generate_identifier Shixun, 8 tip_exception("创建实训失败!")
@shixun = Shixun.new(shixun_params)
@shixun.identifier = identifier
@shixun.user_id = current_user.id
@shixun.reset_time, @shixun.modify_time = Time.now, Time.now
if sub_type.blank?
shixun_script = mirror.first.try(:script)
else
main_mirror = MirrorRepository.find(main_type).type_name
sub_mirror = MirrorRepository.where(id: sub_type).pluck(:type_name)
if main_mirror == "Java" && sub_mirror.include?("Mysql")
shixun_script = mirror.last.try(:script)
else
shixun_script = mirror.first.try(:script)
shixun_script = modify_shixun_script @shixun, shixun_script
end
end end
end
ActiveRecord::Base.transaction do # 保存jupyter到版本库
begin def update_jupyter
@shixun.save! jupyter_save_with_shixun(@shixun, params[:jupyter_port])
# shixun_info关联ß end
ShixunInfo.create!(shixun_id: @shixun.id, evaluate_script: shixun_script, description: params[:description])
# 实训的公开范围
if params[:scope_partment].present?
arr = []
ids = School.where(:name => params[:scope_partment]).pluck(:id).uniq
ids.each do |id|
arr << { :school_id => id, :shixun_id => @shixun.id }
end
ShixunSchool.create!(arr)
end
# 实训合作者 def update
@shixun.shixun_members.create!(user_id: current_user.id, role: 1) # 镜像方面
mirror_ids = MirrorRepository.where(id: params[:main_type])
# 镜像-实训关联表 .or( MirrorRepository.where(id: params[:sub_type])).pluck(:id).uniq
ShixunMirrorRepository.create!(:shixun_id => @shixun.id, :mirror_repository_id => main_type.to_i) if main_type.present? old_mirror_ids = @shixun.shixun_mirror_repositories
# 实训主镜像服务配置 .where(mirror_repository_id: params[:main_type])
ShixunServiceConfig.create!(:shixun_id => @shixun.id, :mirror_repository_id => main_type.to_i) .or(@shixun.shixun_mirror_repositories.where(mirror_repository_id: params[:sub_type]))
if sub_type.present? .pluck(:mirror_repository_id).uniq
sub_type.each do |mirror| new_mirror_id = (mirror_ids - old_mirror_ids).map{|id| {mirror_repository_id: id}} # 转换成数组hash方便操作
ShixunMirrorRepository.create!(:shixun_id => @shixun.id, :mirror_repository_id => mirror) logger.info("##########new_mirror_id: #{new_mirror_id}")
# 实训子镜像服务配置 logger.info("##########old_mirror_ids: #{old_mirror_ids}")
name = MirrorRepository.find_by(id: mirror).try(:name) #查看镜像是否有名称,如果没有名称就不用服务配置 logger.info("##########mirror_ids: #{mirror_ids}")
ShixunServiceConfig.create!(:shixun_id => @shixun.id, :mirror_repository_id => mirror) if name.present? # 服务配置方面
service_create_params = service_config_params[:shixun_service_configs]
.select{|config| !old_mirror_ids.include?(config[:mirror_repository_id]) &&
MirrorRepository.find(config[:mirror_repository_id]).name.present?}
service_update_params = service_config_params[:shixun_service_configs]
.select{|config| old_mirror_ids.include?(config[:mirror_repository_id])}
logger.info("#########service_create_params: #{service_create_params}")
logger.info("#########service_update_params: #{service_update_params}")
begin
ActiveRecord::Base.transaction do
@shixun.update_attributes(shixun_params)
@shixun.shixun_info.update_attributes(shixun_info_params)
# 镜像变动
@shixun.shixun_mirror_repositories.where.not(mirror_repository_id: old_mirror_ids).destroy_all
@shixun.shixun_mirror_repositories.create!(new_mirror_id)
# 镜像变动要更换服务配置
@shixun.shixun_service_configs.where.not(mirror_repository_id: old_mirror_ids).destroy_all
@shixun.shixun_service_configs.create!(service_create_params)
service_update_params&.map do |service|
smr = @shixun.shixun_service_configs.find_by(mirror_repository_id: service[:mirror_repository_id])
smr.update_attributes(service)
end
# 添加第二仓库(管理员权限)
if current_user.admin_or_business?
if params[:is_secret_repository]
add_secret_repository if @shixun.shixun_secret_repository.blank?
else
# 如果有仓库,就要删
if @shixun.shixun_secret_repository&.repo_name
@shixun.shixun_secret_repository.lock!
GitService.delete_repository(repo_path: @shixun.shixun_secret_repository.repo_path)
@shixun.shixun_secret_repository.destroy
end
end end
end end
end
rescue => e
uid_logger_error(e.message)
tip_exception("基本信息更新失败:#{e.message}")
end
end
# 创建版本库 # 实训权限设置
repo_path = repo_namespace(User.current.login, @shixun.identifier) def update_permission_setting
GitService.add_repository(repo_path: repo_path) # 查找需要增删的高校id
# todo: 为什么保存的时候要去除后面的.git呢?? school_id = School.where(:name => params[:scope_partment]).pluck(:id)
@shixun.update_column(:repo_name, repo_path.split(".")[0]) old_school_ids = @shixun.shixun_schools.pluck(:school_id)
school_params = (school_id - old_school_ids).map{|id| {school_id: id}}
begin
ActiveRecord::Base.transaction do
@shixun.update_attributes!(shixun_params)
@shixun.shixun_schools.where.not(school_id: school_id).destroy_all if school_id.present?
@shixun.shixun_schools.create!(school_params)
end
rescue => e
uid_logger_error("实训权限设置失败--------#{e.message}")
tip_exception("实训权限设置失败")
end
end
# 将实训标志为该云上实验室建立 # 实训学习页面设置
Laboratory.current.laboratory_shixuns.create!(shixun: @shixun, ownership: true) def update_learn_setting
rescue Exception => e begin
uid_logger_error(e.message) ActiveRecord::Base.transaction do
tip_exception("实训创建失败") @shixun.update_attributes!(shixun_params)
raise ActiveRecord::Rollback
end end
rescue => e
uid_logger_error("实训学习页面设置失败--------#{e.message}")
tip_exception("实训学习页面设置失败")
end end
end end
# Jupyter数据集
def jupyter_data_sets
page = params[:page] || 1
limit = params[:limit] || 10
data_sets = @shixun.jupyter_data_sets
@data_count = data_sets.count
@data_sets= data_sets.page(page).per(limit)
end
def apply_shixun_mirror def apply_shixun_mirror
form_params = params.permit(*%i[language runtime run_method attachment_id]) form_params = params.permit(*%i[language runtime run_method attachment_id])
form = ApplyShixunMirrorForm.new(form_params) form = ApplyShixunMirrorForm.new(form_params)
@ -462,61 +503,6 @@ class ShixunsController < ApplicationController
tip_exception("申请失败") tip_exception("申请失败")
end end
def update
ActiveRecord::Base.transaction do
begin
@shixun.shixun_mirror_repositories.destroy_all
if params[:main_type].present?
ShixunMirrorRepository.create(:shixun_id => @shixun.id, :mirror_repository_id => params[:main_type].to_i)
end
if params[:small_type].present?
params[:small_type].each do |mirror|
ShixunMirrorRepository.create(:shixun_id => @shixun.id, :mirror_repository_id => mirror)
end
end
@shixun.update_attributes(shixun_params)
@shixun.shixun_info.update_attributes(shixun_info_params)
@shixun.shixun_schools.delete_all
# scope_partment: 高校的名称
if params[:scope_partment].present?
arr = []
ids = School.where(:name => params[:scope_partment]).pluck(:id).uniq
ids.each do |id|
arr << { :school_id => id, :shixun_id => @shixun.id }
end
ShixunSchool.create!(arr)
end
# 超级管理员和运营人员才能保存 中间层服务器pod信息的配置
# 如果镜像改动了,则也需要更改
mirror = @shixun.shixun_service_configs.map(&:mirror_repository_id).sort
new_mirror = params[:shixun_service_configs].map{|c| c[:mirror_repository_id]}.sort
if current_user.admin? || current_user.business? || (mirror != new_mirror)
@shixun.shixun_service_configs.destroy_all
service_config_params[:shixun_service_configs].each do |config|
name = MirrorRepository.find_by_id(config[:mirror_repository_id])&.name
# 不保存没有镜像的配置
@shixun.shixun_service_configs.create!(config) if name.present?
end
end
# 添加第二仓库
if params[:is_secret_repository]
add_secret_repository if @shixun.shixun_secret_repository.blank?
else
# 如果有仓库,就要删
if @shixun.shixun_secret_repository&.repo_name
@shixun.shixun_secret_repository.lock!
GitService.delete_repository(repo_path: @shixun.shixun_secret_repository.repo_path)
@shixun.shixun_secret_repository.destroy
end
end
rescue Exception => e
uid_logger_error("实训保存失败--------#{e.message}")
tip_exception("实训保存失败")
raise ActiveRecord::Rollback
end
end
end
# 永久关闭实训 # 永久关闭实训
def close def close
@ -552,6 +538,8 @@ class ShixunsController < ApplicationController
# @evaluate_scirpt = @shixun.evaluate_script || "无" # @evaluate_scirpt = @shixun.evaluate_script || "无"
end end
# 获取脚本内容 # 获取脚本内容
def get_script_contents def get_script_contents
mirrir_script = MirrorScript.find(params[:script_id]) mirrir_script = MirrorScript.find(params[:script_id])
@ -1021,10 +1009,9 @@ class ShixunsController < ApplicationController
private private
def shixun_params def shixun_params
raise("实训名称不能为空") if params[:shixun][:name].blank?
params.require(:shixun).permit(:name, :trainee, :webssh, :can_copy, :use_scope, :vnc, :test_set_permission, params.require(:shixun).permit(:name, :trainee, :webssh, :can_copy, :use_scope, :vnc, :test_set_permission,
:task_pass, :multi_webssh, :opening_time, :mirror_script_id, :code_hidden, :task_pass, :multi_webssh, :opening_time, :mirror_script_id, :code_hidden,
:hide_code, :forbid_copy, :vnc_evaluate, :code_edit_permission) :hide_code, :forbid_copy, :vnc_evaluate, :code_edit_permission, :is_jupyter)
end end
def validate_review_shixun_params def validate_review_shixun_params
@ -1033,8 +1020,6 @@ private
end end
def shixun_info_params def shixun_info_params
raise("实训描述不能为空") if params[:shixun_info][:description].blank?
raise("评测脚本不能为空") if params[:shixun_info][:evaluate_script].blank?
params.require(:shixun_info).permit(:description, :evaluate_script) params.require(:shixun_info).permit(:description, :evaluate_script)
end end

@ -204,7 +204,7 @@ class SubjectsController < ApplicationController
def add_shixun_to_stage def add_shixun_to_stage
identifier = generate_identifier Shixun, 8 identifier = generate_identifier Shixun, 8
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
@shixun = Shixun.create!(name: params[:name], user_id: current_user.id, identifier: identifier) @shixun = Shixun.create!(name: params[:name], user_id: current_user.id, identifier: identifier, is_jupyter: params[:is_jupyter])
# 添加合作者 # 添加合作者
@shixun.shixun_members.create!(user_id: current_user.id, role: 1) @shixun.shixun_members.create!(user_id: current_user.id, role: 1)
# 创建长字段 # 创建长字段

@ -4,4 +4,7 @@ module ChallengesHelper
str.gsub(/\A\r/, "\r\r") str.gsub(/\A\r/, "\r\r")
end end
end end

@ -28,6 +28,11 @@ class Myshixun < ApplicationRecord
"#{self.repo_name}.git" "#{self.repo_name}.git"
end end
def repo_save_path
self.repo_name.split('/').last
end
def is_complete? def is_complete?
self.status == 1 self.status == 1
end end

@ -52,7 +52,8 @@ module Searchable::Shixun
challenges_count: challenges_count, challenges_count: challenges_count,
study_count: myshixuns_count, study_count: myshixuns_count,
star: averge_star, star: averge_star,
level: shixun_level level: shixun_level,
is_jupyter: is_jupyter
} }
end end

@ -9,6 +9,7 @@ class Shixun < ApplicationRecord
# webssh 0不开启webssh1开启练习模式; 2开启评测模式 # webssh 0不开启webssh1开启练习模式; 2开启评测模式
# trainee 实训的难度 # trainee 实训的难度
# vnc: VCN实训是否用于评测 # vnc: VCN实训是否用于评测
validates_presence_of :name, message: "实训名称不能为空"
has_many :challenges, -> {order("challenges.position asc")}, dependent: :destroy has_many :challenges, -> {order("challenges.position asc")}, dependent: :destroy
has_many :challenge_tags, through: :challenges has_many :challenge_tags, through: :challenges
has_many :myshixuns, :dependent => :destroy has_many :myshixuns, :dependent => :destroy
@ -54,6 +55,8 @@ class Shixun < ApplicationRecord
has_many :laboratory_shixuns, dependent: :destroy has_many :laboratory_shixuns, dependent: :destroy
belongs_to :laboratory, optional: true belongs_to :laboratory, optional: true
# Jupyter数据集,附件
has_many :jupyter_data_sets, ->{where(attachtype: 2)}, class_name: 'Attachment', as: :container, dependent: :destroy
scope :search_by_name, ->(keyword) { where("name like ? or description like ? ", scope :search_by_name, ->(keyword) { where("name like ? or description like ? ",
"%#{keyword}%", "%#{keyword}%") } "%#{keyword}%", "%#{keyword}%") }

@ -1,7 +1,7 @@
class ShixunInfo < ApplicationRecord class ShixunInfo < ApplicationRecord
belongs_to :shixun belongs_to :shixun
validates_uniqueness_of :shixun_id validates_uniqueness_of :shixun_id
validates_presence_of :shixun_id validates_presence_of :shixun_id, :description
after_commit :create_diff_record after_commit :create_diff_record

@ -2,4 +2,5 @@ class ShixunMirrorRepository < ApplicationRecord
belongs_to :shixun belongs_to :shixun
belongs_to :mirror_repository belongs_to :mirror_repository
validates_uniqueness_of :shixun_id, :scope => :mirror_repository_id validates_uniqueness_of :shixun_id, :scope => :mirror_repository_id
validates_presence_of :shixun_id, :mirror_repository_id
end end

@ -1,4 +1,6 @@
class ShixunServiceConfig < ApplicationRecord class ShixunServiceConfig < ApplicationRecord
belongs_to :shixun belongs_to :shixun
belongs_to :mirror_repository belongs_to :mirror_repository
validates_presence_of :shixun_id, :mirror_repository_id
end end

@ -0,0 +1,119 @@
#coding=utf-8
module JupyterService
def _open_shixun_jupyter(shixun)
if shixun.is_jupyter?
shixun_tomcat = edu_setting('cloud_bridge')
uri = "#{shixun_tomcat}/bridge/jupyter/get"
tpiID = "tpm#{shixun.id}"
params = {tpiID: tpiID, :containers => "#{Base64.urlsafe_encode64(shixun_container_limit(shixun))}"}
logger.info "test_juypter: uri->#{uri}, params->#{params}"
res = uri_post uri, params
if res && res['code'].to_i != 0
raise("实训云平台繁忙繁忙等级99")
end
logger.info "test_juypter: #{res}"
@shixun_jupyter_port = res['port']
return "https://#{res['port']}.jupyter.educoder.net/notebooks/data/workspace/myshixun_#{tpiID}/01.ipynb"
end
end
def jupyter_url_with_shixun(shixun)
#打开tpm - juypter接口
_open_shixun_jupyter(shixun)
end
def jupyter_port_with_shixun(shixun)
if @shixun_jupyter_port.to_i <=0
_open_shixun_jupyter(shixun)
end
@shixun_jupyter_port
end
def jupyter_url_with_game(game)
## 打开tpi
shixun = game.myshixun.shixun
if shixun.is_jupyter?
shixun_tomcat = edu_setting('cloud_bridge')
uri = "#{shixun_tomcat}/bridge/jupyter/get"
tpiID = game.myshixun.id
params = {tpiID: tpiID, :containers => "#{Base64.urlsafe_encode64(shixun_container_limit(shixun))}"}
res = uri_post uri, params
logger.info "test_juypter: #{res}"
if res && res['code'].to_i != 0
raise("实训云平台繁忙繁忙等级99")
end
repo_save_path = game.myshixun.repo_save_path
"https://#{res['port']}.jupyter.educoder.net/notebooks/data/workspace/myshixun_#{tpiID}/#{repo_save_path}/01.ipynb"
end
end
def jupyter_save_with_shixun(shixun,jupyter_port)
author_name = current_user.real_name
author_email = current_user.git_mail
message = "User submitted"
tpiID = "tpm#{shixun.id}"
#https://47526.jupyter.educoder.net/nbconvert/notebook/data/workspace/myshixun_570461/f2ef5p798r20191210163135/01.ipynb?download=true
src_url = URI("https://#{jupyter_port}.jupyter.educoder.net/nbconvert/notebook/data/workspace/myshixun_#{tpiID}/01.ipynb?download=true")
response = Net::HTTP.get_response(src_url)
if response.code.to_i != 200
raise("获取文件内容失败:#{response.code}")
end
content = response.body
c = GitService.update_file(repo_path: @shixun.repo_path,
file_path: "01.ipynb",
message: message,
content: content,
author_name: author_name,
author_email: author_email)
return c.size
end
def jupyter_save_with_game(game,jupyter_port)
author_name = current_user.real_name
author_email = current_user.git_mail
message = "User submitted"
tpiID = game.myshixun.id
repo_save_path = game.myshixun.repo_save_path
src_url = URI("https://#{jupyter_port}.jupyter.educoder.net/nbconvert/notebook/data/workspace/myshixun_#{tpiID}/#{repo_save_path}/01.ipynb?download=true")
response = Net::HTTP.get_response(src_url)
if response.code.to_i != 200
raise("获取文件内容失败:#{response.code}")
end
content = response.body
c = GitService.update_file(repo_path: game.myshixun.repo_path,
file_path: "01.ipynb",
message: message,
content: content,
author_name: author_name,
author_email: author_email)
return c.size
end
end

@ -0,0 +1,104 @@
class CreateShixunService < ApplicationService
attr_reader :user, :params, :permit_params
def initialize(user, permit_params, params)
@user = user
@params = params
@permit_params = permit_params
end
def call
shixun = Shixun.new(permit_params)
identifier = Util::UUID.generate_identifier(Shixun, 8)
shixun.identifier= identifier
shixun.user_id = user.id
main_mirror = MirrorRepository.find params[:main_type]
sub_mirrors = MirrorRepository.where(id: params[:sub_type])
ActiveRecord::Base.transaction do
shixun.save!
# 获取脚本内容
shixun_script = get_shixun_script(shixun, main_mirror, sub_mirrors)
# 创建额外信息
ShixunInfo.create!(shixun_id: shixun.id, evaluate_script: shixun_script, description: params[:description])
# 创建合作者
shixun.shixun_members.create!(user_id: user.id, role: 1)
# 创建镜像
ShixunMirrorRepository.create!(:shixun_id => shixun.id, :mirror_repository_id => main_mirror.id)
# 创建主服务配置
ShixunServiceConfig.create!(:shixun_id => shixun.id, :mirror_repository_id => main_mirror.id)
# 创建子镜像相关数据(实训镜像关联表,子镜像服务配置)
sub_mirrors.each do |sub|
ShixunMirrorRepository.create!(:shixun_id => shixun.id, :mirror_repository_id => sub.id)
# 实训子镜像服务配置
name = sub.name #查看镜像是否有名称,如果没有名称就不用服务配置
ShixunServiceConfig.create!(:shixun_id => shixun.id, :mirror_repository_id => sub.id) if name.present?
end
# 创建版本库
repo_path = repo_namespace(user.login, shixun.identifier)
GitService.add_repository(repo_path: repo_path)
shixun.update_column(:repo_name, repo_path.split(".")[0])
# 如果是云上实验室,创建相关记录
if !Laboratory.current.main_site?
Laboratory.current.laboratory_shixuns.create!(shixun: shixun, ownership: true)
end
return shixun
end
end
private
def get_shixun_script shixun, main_mirror, sub_mirrors
if !shixun.is_jupyter?
mirror = main_mirror.mirror_scripts
if main_mirror.blank?
modify_shixun_script shixun, mirror.first&.(:script)
else
sub_name = sub_mirrors.pluck(:type_name)
if main_mirror.type_name == "Java" && sub_name.include?("Mysql")
mirror.last.try(:script)
else
shixun_script = mirror.first&.script
modify_shixun_script shixun, shixun_script
end
end
end
end
def modify_shixun_script shixun, script
if script.present?
source_class_name = []
challenge_program_name = []
shixun.challenges.map(&:exec_path).each do |exec_path|
challenge_program_name << "\"#{exec_path}\""
if shixun.main_mirror_name == "Java"
if exec_path.nil? || exec_path.split("src/")[1].nil?
source = "\"\""
else
source = "\"#{exec_path.split("src/")[1].split(".java")[0]}\""
end
logger.info("----source: #{source}")
source_class_name << source.gsub("/", ".") if source.present?
elsif shixun.main_mirror_name.try(:first) == "C#"
if exec_path.nil? || exec_path.split(".")[1].nil?
source = "\"\""
else
source = "\"#{exec_path.split(".")[0]}.exe\""
end
source_class_name << source if source.present?
end
end
script = if script.include?("sourceClassName") && script.include?("challengeProgramName")
script.gsub(/challengeProgramNames=\(.*\)/,"challengeProgramNames=\(#{challenge_program_name.reject(&:blank?).join(" ")}\)").gsub(/sourceClassNames=\(.*\)/, "sourceClassNames=\(#{source_class_name.reject(&:blank?).join(" ")}\)")
else
script.gsub(/challengeProgramNames=\(.*\)/,"challengeProgramNames=\(#{challenge_program_name.reject(&:blank?).join(" ")}\)").gsub(/sourceClassNames=\(.*\)/, "sourceClassNames=\(#{challenge_program_name.reject(&:blank?).join(" ")}\)")
end
end
return script
end
# 版本库目录空间
def repo_namespace(user, shixun_identifier)
"#{user}/#{shixun_identifier}.git"
end
end

@ -4,8 +4,10 @@ json.description @shixun.description
json.power @editable json.power @editable
json.shixun_identifier @shixun.identifier json.shixun_identifier @shixun.identifier
json.shixun_status @shixun.status json.shixun_status @shixun.status
json.is_jupyter @shixun.is_jupyter?
json.allow_skip @shixun.task_pass json.allow_skip @shixun.task_pass
# 列表 # 列表
if @challenges.present? if @challenges.present?
json.challenge_list @challenges do |challenge| json.challenge_list @challenges do |challenge|

@ -13,6 +13,7 @@ json.array! shixuns do |shixun|
json.identifier shixun.identifier json.identifier shixun.identifier
json.name shixun.name json.name shixun.name
json.status shixun.status json.status shixun.status
json.is_jupyter shixun.is_jupyter?
json.power (current_user.shixun_permission(shixun)) # 现在首页只显示已发布的实训 json.power (current_user.shixun_permission(shixun)) # 现在首页只显示已发布的实训
# REDO: 局部缓存 # REDO: 局部缓存
json.tag_name @tag_name_map&.fetch(shixun.id, nil) || shixun.tag_repertoires.first.try(:name) json.tag_name @tag_name_map&.fetch(shixun.id, nil) || shixun.tag_repertoires.first.try(:name)

@ -14,5 +14,6 @@ json.stu_num shixun.myshixuns_count
json.experience shixun.all_score json.experience shixun.all_score
json.diffcult diff_to_s(shixun.trainee) json.diffcult diff_to_s(shixun.trainee)
json.score_info shixun.shixun_preference_info # todo: 这块可以改成只显示实训的平均分,不用每次都去取每种星的分数了。 json.score_info shixun.shixun_preference_info # todo: 这块可以改成只显示实训的平均分,不用每次都去取每种星的分数了。
json.is_jupyter shixun.is_jupyter
# 用于是否显示导航栏中的'背景知识' # 用于是否显示导航栏中的'背景知识'
json.propaedeutics shixun.propaedeutics.present? json.propaedeutics shixun.propaedeutics.present?

@ -0,0 +1,10 @@
json.data_sets do
json.array! @data_sets do |set|
json.id set.id
json.title set.title
json.author set.author.real_name
json.created_on set.created_on
json.filesize number_to_human_size(set.filesize)
end
end
json.data_sets_count @data_count

@ -2,6 +2,7 @@ json.shixun do
json.status @shixun.status json.status @shixun.status
json.name @shixun.name json.name @shixun.name
json.description @shixun.description json.description @shixun.description
json.is_jupyter @shixun.is_jupyter
# 镜像大小类别 # 镜像大小类别
json.main_type @main_type json.main_type @main_type
@ -41,6 +42,9 @@ json.shixun do
json.(config, :cpu_limit, :lower_cpu_limit, :memory_limit, :request_limit, :mirror_repository_id) json.(config, :cpu_limit, :lower_cpu_limit, :memory_limit, :request_limit, :mirror_repository_id)
end end
end end
json.jupyter_url jupyter_url(@shixun)
end end

@ -3,3 +3,5 @@ json.identity User.current.shixun_identity(@shixun)
json.power @power json.power @power
json.partial! 'shixuns/top', locals: { shixun: @shixun, current_myshixun: @current_myshixun } json.partial! 'shixuns/top', locals: { shixun: @shixun, current_myshixun: @current_myshixun }
json.secret_repository @shixun.shixun_secret_repository.present? json.secret_repository @shixun.shixun_secret_repository.present?
json.jupyter_url jupyter_url_with_shixun(@shixun)
json.jupyter_port jupyter_port_with_shixun(@shixun)

@ -6,4 +6,5 @@ json.name shixun.name
json.status shixun.status json.status shixun.status
json.human_status shixun.human_status json.human_status shixun.human_status
json.challenges_count shixun.challenges_count json.challenges_count shixun.challenges_count
json.finished_challenges_count @finished_challenges_count_map&.fetch(shixun.id, 0) || shixun.finished_challenges_count(user) json.finished_challenges_count @finished_challenges_count_map&.fetch(shixun.id, 0) || shixun.finished_challenges_count(user)
json.is_jupyter shixun.is_jupyter

@ -1,3 +1,3 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
load Gem.bin_path('bundler', 'bundle') load Gem.bin_path('bundler', 'bundle')

@ -1,4 +1,4 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
APP_PATH = File.expand_path('../config/application', __dir__) APP_PATH = File.expand_path('../config/application', __dir__)
require_relative '../config/boot' require_relative '../config/boot'
require 'rails/commands' require 'rails/commands'

@ -1,4 +1,6 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
require_relative '../config/boot' require_relative '../config/boot'
require 'rake'
Rake.application.run
require 'rake'
Rake.application.run

@ -9,6 +9,16 @@ Rails.application.routes.draw do
get 'auth/qq/callback', to: 'oauth/qq#create' get 'auth/qq/callback', to: 'oauth/qq#create'
get 'auth/failure', to: 'oauth/base#auth_failure' get 'auth/failure', to: 'oauth/base#auth_failure'
resources :jupyters do
collection do
get :open
get :open1
get :test
get :save
end
end
resources :edu_settings resources :edu_settings
scope '/api' do scope '/api' do
get 'home/index' get 'home/index'
@ -35,6 +45,9 @@ Rails.application.routes.draw do
end end
end end
resources :hacks, path: :problems, param: :identifier do resources :hacks, path: :problems, param: :identifier do
collection do collection do
get :unpulished_list get :unpulished_list
@ -229,6 +242,7 @@ Rails.application.routes.draw do
end end
member do member do
post :update_jupyter
post :copy post :copy
get :propaedeutics get :propaedeutics
get :show_right get :show_right
@ -261,6 +275,9 @@ Rails.application.routes.draw do
get :shixun_exec get :shixun_exec
post :review_shixun post :review_shixun
get :review_newest_record get :review_newest_record
post :update_permission_setting
post :update_learn_setting
get :jupyter_data_sets
end end
resources :challenges do resources :challenges do
@ -744,7 +761,11 @@ Rails.application.routes.draw do
resources :poll_bank_questions resources :poll_bank_questions
resources :attachments resources :attachments do
collection do
delete :destroy_files
end
end
resources :schools do resources :schools do
member do member do

@ -0,0 +1,5 @@
class AddIsJupyterForShixuns < ActiveRecord::Migration[5.2]
def change
add_column :shixuns, :is_jupyter, :boolean, :default => false
end
end

@ -0,0 +1,17 @@
class AddIndexForShixunSecretRepositories < ActiveRecord::Migration[5.2]
def change
shixun_ids = ShixunSecretRepository.pluck(:shixun_id).uniq
shixuns = Shixun.where(id: shixun_ids)
shixuns.find_each do |shixun|
id = shixun.shixun_secret_repository.id
shixun_secret_repositories = ShixunSecretRepository.where(shixun_id: shixun.id).where.not(id: id)
shixun_secret_repositories.destroy_all
end
remove_index :shixun_secret_repositories, :shixun_id
add_index :shixun_secret_repositories, :shixun_id, unique: true
end
end

@ -0,0 +1,35 @@
version: '3'
services:
mysql:
image: mysql:5.7.17
command: --sql-mode=""
restart: always
volumes:
- ./mysql_data/:/var/lib/mysql
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: 123456789
MYSQL_DATABASE: educoder
redis:
image: redis:3.2
container_name: redis
restart: always
ports:
- "6379:6379"
volumes:
- ./redis_data:/data
web:
image: guange/educoder:latest
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 4000 -b '0.0.0.0'"
stdin_open: true
tty: true
volumes:
- .:/app
ports:
- "4000:4000"
depends_on:
- mysql
- redis

Binary file not shown.

@ -25,7 +25,7 @@ class TPMDataset extends Component {
key: 'number', key: 'number',
align: 'left', align: 'left',
className: " font-14 wenjiantit", className: " font-14 wenjiantit",
width: '200px', width: '220px',
render: (text, record) => ( render: (text, record) => (
<div> <div>
{record.title} {record.title}
@ -405,8 +405,7 @@ class TPMDataset extends Component {
<div className="padding20 edu-back-white mt20 " style={{minHeight: '463px'}}> <div className="padding20 edu-back-white mt20 " style={{minHeight: '463px'}}>
<div className="sortinxdirection"> <div className="sortinxdirection">
<div className="tpmwidth"> <div className="tpmwidth">
{ data_sets_count>0? <Checkbox onChange={this.mysonChange}>全选</Checkbox>
<Checkbox onChange={this.mysonChange}>全选</Checkbox>:""}
</div> </div>
<div className="tpmwidth xaxisreverseorder"> <div className="tpmwidth xaxisreverseorder">
<style> <style>
@ -471,19 +470,31 @@ class TPMDataset extends Component {
} }
`}</style> `}</style>
{data_sets_count===0? {data_sets_count===0?
<style> <div className="edu-table edu-back-white ysltableowss">
{ <style>
` {
`
.ant-table-tbody{ .ant-table-tbody{
display:none; display:none;
} }
.ant-table-placeholder{
display:none;
}
.ant-table table { .ant-table table {
border-bottom: 1px solid #eeeeee !important; border-bottom: 1px solid #eeeeee !important;
} }
` `
} }
</style> </style>
<Table
columns={columns}
pagination={false}
className="mysjysltable4"
rowSelection={rowSelection}
rowClassName={this.rowClassName}
/>
</div>
: :
<div className="edu-table edu-back-white ysltableowss"> <div className="edu-table edu-back-white ysltableowss">
<Table <Table

@ -39,6 +39,7 @@
color:#FFFFFF; color:#FFFFFF;
text-align: center; text-align: center;
line-height:30px; line-height:30px;
cursor:default
} }
.renwuxiangqdiv{ .renwuxiangqdiv{
width:72px; width:72px;

@ -95,7 +95,7 @@
} }
.wenjiantit{ .wenjiantit{
width: 300px; width: 220px;
} }
.zuihoushijian{ .zuihoushijian{
width: 125px; width: 125px;

Loading…
Cancel
Save