Merge branch 'dev_jupyter' of http://bdgit.educoder.net/Hjqreturn/educoder into dev_jupyter

chromesetting
guange 5 years ago
commit 6596073b26

@ -103,3 +103,6 @@ gem 'diffy'
# oauth2
gem 'omniauth', '~> 1.9.0'
gem 'omniauth-oauth2', '~> 1.6.0'
# global var
gem 'request_store'

@ -8,6 +8,9 @@ class Admins::ShixunsController < Admins::BaseController
@pending_shixuns = shixuns.where(status:1).size
@processed_shixuns = shixuns.where(status:2).size
@closed_shixuns = shixuns.where(status:3).size
@none_public_shixuns = shixuns.where(public:0).size
@pending_public_shixuns = shixuns.where(public:1).size
@processed_pubic_shixuns = shixuns.where(public:2).size
@shixuns_type_check = MirrorRepository.pluck(:type_name,:id)
@params_page = params[:page] || 1
@shixuns = paginate shixuns.preload(:user,:challenges)

@ -303,7 +303,7 @@ class ApplicationController < ActionController::Base
current_domain_session = session[:"#{default_yun_session}"]
if current_domain_session
# existing session
(User.active.find(current_domain_session) rescue nil)
User.current = (User.active.find(current_domain_session) rescue nil)
elsif autologin_user = try_to_autologin
autologin_user
elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?

@ -29,7 +29,7 @@ class CoursesController < ApplicationController
:informs, :update_informs, :online_learning, :update_task_position, :tasks_list,
:join_excellent_course, :export_couser_info, :export_member_act_score, :new_informs,
:delete_informs, :change_member_role, :course_groups, :join_course_group, :statistics,
:work_score, :act_score]
:work_score, :act_score, :calculate_all_shixun_scores]
before_action :user_course_identity, except: [:join_excellent_course, :index, :create, :new, :apply_to_join_course,
:search_course_list, :get_historical_course_students, :mine, :search_slim, :board_list]
before_action :teacher_allowed, only: [:update, :destroy, :settings, :search_teacher_candidate,
@ -48,7 +48,7 @@ class CoursesController < ApplicationController
before_action :validate_page_size, only: :mine
before_action :course_tasks, only: [:tasks_list, :update_task_position]
before_action :validate_inform_params, only: [:update_informs, :new_informs]
before_action :course_member_allowed, only: [:statistics, :work_score, :act_score]
before_action :course_member_allowed, only: [:statistics, :work_score, :act_score, :calculate_all_shixun_scores]
if RUBY_PLATFORM =~ /linux/
require 'simple_xlsx_reader'
@ -1334,6 +1334,16 @@ class CoursesController < ApplicationController
end
end
# 计算课堂所有已发布的实训作业成绩
def calculate_all_shixun_scores
tip_exception(-1, "课堂已结束") if @course.is_end
shixun_homeworks = @course.homework_commons.homework_published.where(homework_type: 4)
shixun_homeworks.includes(:homework_challenge_settings, :published_settings, :homework_commons_shixun).each do |homework|
homework.update_homework_work_score
end
normal_status(0, "更新成功")
end
def search_slim
courses = current_user.manage_courses.not_deleted.processing

@ -1,8 +1,8 @@
class HacksController < ApplicationController
before_action :require_login, except: [:index]
before_action :find_hack, only: [:edit, :update, :publish, :start, :update_set, :delete_set, :destroy]
before_action :find_hack, only: [:edit, :update, :publish, :start, :update_set, :delete_set, :destroy, :cancel_publish]
before_action :require_teacher_identity, only: [:create]
before_action :require_auth_identity, only: [:update, :edit, :publish, :update_set, :delete_set, :destroy]
before_action :require_auth_identity, only: [:update, :edit, :publish, :update_set, :delete_set, :destroy, :cancel_publish]
# 开启编程,如果第一次开启,创建一条记录,如果已经开启过的话,直接返回标识即可
@ -99,6 +99,12 @@ class HacksController < ApplicationController
render_ok
end
# 取消发布
def cancel_publish
@hack.update_attribute(:status, 0)
render_ok
end
# 发布列表
def unpulished_list
limit = params[:limit] || 16

@ -1,410 +1,411 @@
class MyshixunsController < ApplicationController
before_action :require_login, :check_auth, :except => [:training_task_status, :code_runinng_message]
before_action :find_myshixun, :except => [:training_task_status, :code_runinng_message]
before_action :find_repo_name, :except => [:training_task_status, :code_runinng_message]
skip_before_action :verify_authenticity_token, :only => [:html_content]
## TPI关卡列表
def challenges
# @challenges = Challenge.where(shixun_id: params[:shixun_id])
@shixun = @myshixun.shixun
@games = @myshixun.games.includes(:challenge).reorder("challenges.position")
@identity = current_user.game_identity(@games.first)
end
# For Admin
# 强制重置实训
# 前段需要按照操作过程提示
def reset_my_game
unless (current_user.admin? || current_user.id == @myshixun.user_id)
tip_exception("403", "")
end
begin
ActiveRecord::Base.transaction do
begin
@shixun = Shixun.select(:id, :identifier, :challenges_count).find(@myshixun.shixun_id)
@myshixun.destroy!
StudentWork.where(:myshixun_id => @myshixun.id).update_all(myshixun_id: 0, work_status: 0, work_score: nil,
final_score: nil, efficiency: 0, eff_score: 0, calculation_time: nil, cost_time: 0, compelete_status: 0)
rescue Exception => e
logger.error("######reset_my_game_failed:#{e.message}")
raise("ActiveRecord::RecordInvalid")
end
end
# 删除版本库
GitService.delete_repository(repo_path: @repo_path) unless @shixun.is_choice_type?
rescue Exception => e
if e.message != "ActiveRecord::RecordInvalid"
logger.error("######delete_repository_error-:#{e.message}")
end
raise "delete_repository_error:#{e.message}"
end
end
# 代码运行中的信息接口
# 这个方法是中间层主动调用的点击评测后中间层会发送参数过来告诉目前Pod的启动情况一次评测会调用两次请求
def code_runinng_message
begin
jsonTestDetails = JSON.parse(params[:jsonTestDetails])
game_id = jsonTestDetails['buildID']
message = jsonTestDetails['textMsg']
if game_id.present? && message.present?
game = Game.find game_id
msg = game.run_code_message
# 只有评测中的game才会创建和更新代码评测中的信息
if game.status == 1 || game.status == 2
if msg.blank?
RunCodeMessage.create!(:game_id => game_id, :status => 1, :message => message)
else
msg.update_attributes(:status => (msg.status + 1), :message => message)
end
end
render :json => {:data => "success"}
end
rescue Exception => e
render :json => {:data => "failed, exception_message: #{e}"}
end
end
# 中间层评测接口
# taskId 即返回的game id
# 返回结果params [:stauts] 0 表示成功,其它则失败
# msg 错误信息
# output 为测试用户编译输出结果
# myshixun:status 1为完成实训
# @jenkins: caseId对应test_set的positionpassed: 1表示成功0表示失败
# resubmit 1表示已通关后重新评测0表示非重新评测
# retry_status 0初始值1重新评测失败2重新评测成功
# tpiRepoPath 中间层图片的workspace路径
# params[:jsonTestDetails] = '{"buildID":"19284","compileSuccess":"1",
# "msg":[{"caseId":"1","expectedOutput":"MSAyIDMNCg","input":"MiAzIDE","output":"MSAyIDMNCg","passed":"1"},
# {"caseId":"2","expectedOutput":"LTMgMSA2DQo","input":"LTMgNiAx","output":"LTMgMSA2DQo","passed":"1"},
# {"caseId":"3","expectedOutput":"LTcgLTUgLTMNCg","input":"LTcgLTMgLTU","output":"LTcgLTUgLTMNCg","passed":"1"}],
# "outPut":"Y29tcGlsZSBzdWNjZXNzZnVsbHk","resubmit":"","status":"0"}'
# params[:timeCost] = '{"evaluateEnd":"2017-11-24 11:04:37","pull":"0.086",
# "createPod":"1.610","evaluateAllTime":2820,"evaluateStart":"2017-11-24 11:04:35","execute":"0.294"}'
# params[:pics] = "a.png,b.png,c.png"
def training_task_status
ActiveRecord::Base.transaction do
begin
t1 = Time.now
uid_logger_dubug("@@@222222#{params[:jsonTestDetails]}")
jsonTestDetails = JSON.parse(params[:jsonTestDetails])
timeCost = JSON.parse(params[:timeCost])
brige_end_time = Time.parse(timeCost['evaluateEnd']) if timeCost['evaluateEnd'].present?
return_back_time = format("%.3f", ( t1.to_f - brige_end_time.to_f)).to_f
status = jsonTestDetails['status']
game_id = jsonTestDetails['buildID']
sec_key = jsonTestDetails['sec_key']
uid_logger_dubug("training_task_status start-#{game_id}-1#{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
resubmit = jsonTestDetails['resubmit']
outPut = tran_base64_decode64(jsonTestDetails['outPut'])
jenkins_testsets = jsonTestDetails['msg']
compile_success = jsonTestDetails['compileSuccess']
# message = Base64.decode64(params[:msg]) unless params[:msg].blank?
game = Game.find(game_id)
myshixun = game.myshixun
challenge = game.challenge
# test_sets = challenge.test_sets
if challenge.picture_path.present?
#pics = params[:files]
pics = params[:tpiRepoPath]
game.update_column(:picture_path, pics)
end
max_query_index = game.outputs ? (game.outputs.first.try(:query_index).to_i + 1) : 1
test_set_score = 0
unless jenkins_testsets.blank?
jenkins_testsets.each_with_index do |j_test_set, i|
actual_output = tran_base64_decode64(j_test_set['output'])
#ts_time += j_test_set['testSetTime'].to_i
# is_public = test_sets.where(:position => j_test_set['caseId']).first.try(:is_public)
ts_time = format("%.2f", j_test_set['testSetTime'].to_f/1000000000).to_f if j_test_set['testSetTime']
ts_mem = format("%.2f", j_test_set['testSetMem'].to_f/1024/1024).to_f if j_test_set['testSetMem']
Output.create!(:code => status, :game_id => game_id, :out_put => outPut, :test_set_position => j_test_set['caseId'],
:actual_output => actual_output, :result => j_test_set['passed'].to_i, :query_index => max_query_index,
:compile_success => compile_success.to_i, :sec_key => sec_key, :ts_time => ts_time, :ts_mem => ts_mem)
# 如果设置了按测试集给分,则需要统计测试集的分值
if challenge.test_set_score && j_test_set['passed'].to_i == 1
test_set_score += challenge.test_sets.where(:position => j_test_set['caseId']).pluck(:score).first
end
end
end
record = EvaluateRecord.where(:identifier => sec_key).first
answer_deduction_percentage = (100 - game.answer_deduction) / 100.to_f # 查看答案后剩余分数的百分比.
# answer_deduction是查看答案的扣分比例
# status0表示评测成功
if status == "0"
if resubmit.present?
game.update_attributes!(:retry_status => 2, :resubmit_identifier => resubmit)
challenge.path.split("").each do |path|
game_passed_code(path.try(:strip), myshixun, game_id)
end
else
game.update_attributes!(:status => 2,
:end_time => Time.now,
:accuracy => format("%.4f", 1.0 / game.query_index))
myshixun.update_attributes!(:status => 1) if game.had_done == 1
challenge.path.split("").each do |path|
game_passed_code(path.try(:strip), myshixun, game_id)
end
# 如果是已经发布的实训,则需要给出相应的奖励
if challenge.shixun.try(:status) > 1
score = (challenge.score * answer_deduction_percentage).to_i
if score > 0
reward_attrs = { container_id: game.id, container_type: 'Game', score: score }
RewardGradeService.call(game.user, reward_attrs)
RewardExperienceService.call(game.user, reward_attrs)
end
# 需要扣除查看答案的分数
game.update_attributes!(:final_score => score)
end
# 更新实训关联的作品分数 TODO: 更新作品分数
# HomeworksService.new.update_myshixun_work_score myshixun
end
# 如果过关了下一关的状态是3为开启则需要把状态改成1已开启
# next_game = game.next_game
next_game = game.next_game(myshixun.shixun_id, game.myshixun_id, challenge.position)
next_game.update_column(:status, 0) if next_game.present? && next_game.status == 3
# status == "-1" 表示返回结果错误
else
if resubmit.present?
game.update_attributes!(:retry_status => 1, :resubmit_identifier => resubmit)
else
# 评测没通关则,测试集对的个数给分,并且还要扣除用户是否查看答案的值
test_set_percentage = test_set_score / 100.to_f # 测试集得分比
score = (challenge.score * test_set_percentage * answer_deduction_percentage).to_i
# 如果分数比上次多,则更新成绩
game_update =
if game.final_score < score
{final_score: score, status: 0}
else
{status: 0}
end
game.update_attributes!(game_update)
end
end
test_cases_time = format("%.3f", (Time.now.to_f - t1.to_f)).to_f
if record.present?
consume_time = format("%.3f", (Time.now - record.created_at)).to_f
record.update_attributes!(:consume_time => consume_time, :git_pull => timeCost['pull'] , :create_pod => timeCost['createPod'],
:pod_execute => timeCost['execute'], :test_cases => test_cases_time,
:brige => timeCost['evaluateAllTime'], :return_back => return_back_time)
end
sucess_status
# rescue Exception => e
# tip_exception(e.message)
# uid_logger_error("training_task_status error: #{e}")
# raise ActiveRecord::Rollback
end
end
end
# 连接webssh
def open_webssh
username = edu_setting('webssh_username')
password = edu_setting('webssh_password')
old_time = Time.now.to_i
begin
shixun_tomcat = edu_setting('tomcat_webssh')
uri = "#{shixun_tomcat}/bridge/webssh/getConnectInfo"
# 由于中间层采用混合云的方式因为local参数表示在有文件生成的实训是在本地生成还是在其他云端生成评测文件
params = {tpiID:@myshixun.id, podType:@myshixun.shixun.try(:webssh), local: @myshixun.shixun.show_type != -1,
containers:(Base64.urlsafe_encode64(shixun_container_limit @myshixun.shixun))}
res = uri_post uri, params
if res && res['code'].to_i != 0
tip_exception("实训云平台繁忙繁忙等级92")
end
render :json => {:host => res['address'],
:port => res['port'],
:ws_url => res['ws_address'],
:username => username,
:password => password,
:game_id => @myshixun.id,
:webssh_url => "#{shixun_tomcat}/bridge"}
rescue Exception => e
logger.error(e)
render :json => {:error => e.try(:message)}
ensure
use_time = Time.now.to_i - old_time
logger.info "open_webssh tpiID #{@myshixun.id} use time #{use_time}"
end
end
include GitCommon
# -----Repository
# TODO: 之类需要一个resubmit参数,但是是关于games.
def update_file
begin
@hide_code = Shixun.where(id: @myshixun.shixun_id).pluck(:hide_code).first
tip_exception("技术平台为空!") if @myshixun.mirror_name.blank?
path = params[:path].strip unless params[:path].blank?
game_id = params[:game_id]
game = Game.find(game_id)
@content_modified = 0
# params[:evaluate] 实训评测时更新必须给的参数,需要依据该参数做性能统计,其它类型的更新可以跳过
# 自动保存的时候evaluate为0点评测的时候为1
if params[:evaluate] == 1
exec_time = game.challenge.try(:exec_time)
@sec_key = generate_identifier(EvaluateRecord, 12)
record = EvaluateRecord.create!(:user_id => current_user.id, :shixun_id => @myshixun.shixun_id, :game_id => game_id,
:identifier => @sec_key, :exec_time => exec_time)
uid_logger_dubug("-- game build: file update #{@sec_key}, record id is #{record.id}, time is **** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
end
# 隐藏代码文件 和 VNC的都不需要走版本库
unless @hide_code || (@myshixun.shixun&.vnc_evaluate && params[:evaluate].present?)
# 远程版本库文件内容
last_content = GitService.file_content(repo_path: @repo_path, path: path)["content"]
content =
if python_file?(path)
params[:content].gsub(/\t/, ' ').gsub(/ /, ' ')
else
params[:content]
end
uid_logger_dubug("###11222333####{content}")
uid_logger_dubug("###222333####{last_content}")
if content != last_content
@content_modified = 1
author_name = current_user.real_name
author_email = current_user.git_mail
message = params[:evaluate] == 0 ? "System automatically submitted" : "User submitted"
uid_logger_dubug("112233#{author_name}")
uid_logger_dubug("112233#{author_email}")
@content = GitService.update_file(repo_path: @repo_path,
file_path: path,
message: message,
content: content,
author_name: author_name,
author_email: author_email)
end
end
if game.status == 2
@resubmit = Time.now.to_i
end
# 评测时间记录
if record.present?
consume_time = format("%.3f", (Time.now.to_f - record.created_at.to_f)).to_f
record.update_attributes!(:file_update => consume_time)
end
rescue Exception => e
uid_logger_error(e.message)
tip_exception("文件内容更新异常,请稍后重试")
end
end
# 渲染实训代码
# educodercss: 字符串以 分隔存储的是版本库css的路径
# educoderscript: 字符串以 分隔存储的是版本库js的路径
# contents html实训的整体内容
def html_content
@contents = params[:contents] || ""
edu_css = params[:educodercss]
edu_js = params[:educoderscript]
if @contents.present?
@contents = @contents.gsub("w3equalsign", "=").gsub("w3scrw3ipttag", "script").gsub("edulink", "link").html_safe
end
# css
if edu_css.present?
css_path = edu_css.split(",")
css_path.each do |path|
file_content = git_fle_content(@repo_path, path)["content"]
file_content = tran_base64_decode64(file_content) unless file_content.blank?
@contents = @contents.sub(/EDUCODERCSS/, "<style>#{file_content}</style>")
end
end
# js
if edu_js.present?
js_path = edu_js.split(",")
js_path.each do |path|
file_content = git_fle_content(@repo_path, path)["content"]
file_content = tran_base64_decode64(file_content) unless file_content.blank?
@contents = @contents.sub(/EDUCODERJS/, "<script>#{file_content}</script>")
end
end
respond_to do |format|
format.json
format.html{render :layout => false}
end
end
# 最新可以用的并发测试接口
def sigle_mul_test
codes = %W(1 2 3 4 5 6 7 8 9 A B C D E F G H J K L N M O P Q R S T U V W X Y Z)
begin
identifiers = Myshixun.where(:shixun_id => params[:shixun_id].split(",")).pluck(:identifier)
ide = identifiers[rand(identifiers.length)]
myshixun = Myshixun.where(:identifier => ide).first
game = myshixun.games.last
logger.warn("###2mul test game_build start ")
identifier = game.try(:identifier)
if game.status == 2
code = codes.sample(8).join
resubmit = "#{code}_#{myshixun.id}"
end
logger.warn("###3mul test game_build start ...")
EvaluateRecord.create!(:user_id => myshixun.user_id, :shixun_id => myshixun.shixun.id, :game_id => game.id)
redirect_to "/api/games/#{identifier}/game_build?resubmit=#{resubmit}&content_modified=0&first=1"
rescue Exception => e
logger.error("mul test failed ===> #{e.message}")
end
end
def sync_code
shixun_tomcat = edu_setting('cloud_bridge')
begin
git_myshixun_url = repo_ip_url @myshixun.repo_path
git_shixun_url = repo_ip_url @myshixun.shixun.try(:repo_path)
git_myshixun_url = Base64.urlsafe_encode64(git_myshixun_url)
git_shixun_url = Base64.urlsafe_encode64(git_shixun_url)
# todo: identifier 是以前的密码,用来验证的,新版如果不需要,和中间层协调更改.
params = {tpiID: "#{@myshixun.try(:id)}", tpiGitURL: "#{git_myshixun_url}", tpmGitURL: "#{git_shixun_url}",
identifier: "xinhu1ji2qu3"}
uri = "#{shixun_tomcat}/bridge/game/resetTpmRepository"
res = uri_post uri, params
if (res && res['code'] != 0)
tip_exception("实训云平台繁忙繁忙等级95")
end
shixun_new_commit = GitService.commits(repo_path: @myshixun.shixun.repo_path).first["id"]
@myshixun.update_attributes!(commit_id: shixun_new_commit, reset_time: @myshixun.shixun.try(:reset_time))
# 更新完成后,弹框则隐藏不再提示
@myshixun.update_column(:system_tip, false)
render_ok
rescue Exception => e
tip_exception("立即更新代码失败!#{e.message}")
end
end
# -----End
private
def find_myshixun
@myshixun = Myshixun.find_by!(identifier: params[:identifier])
end
def find_repo_name
@repo_path = @myshixun.try(:repo_path)
@path = params[:path]
end
def python_file?(path)
false if path.blank?
path.to_s.split(".").last.downcase == "py"
end
end
class MyshixunsController < ApplicationController
before_action :require_login, :check_auth, :except => [:training_task_status, :code_runinng_message]
before_action :find_myshixun, :except => [:training_task_status, :code_runinng_message]
before_action :find_repo_name, :except => [:training_task_status, :code_runinng_message]
skip_before_action :verify_authenticity_token, :only => [:html_content]
## TPI关卡列表
def challenges
# @challenges = Challenge.where(shixun_id: params[:shixun_id])
@shixun = @myshixun.shixun
@games = @myshixun.games.includes(:challenge).reorder("challenges.position")
@identity = current_user.game_identity(@games.first)
end
# For Admin
# 强制重置实训
# 前段需要按照操作过程提示
def reset_my_game
unless (current_user.admin? || current_user.id == @myshixun.user_id)
tip_exception("403", "")
end
begin
ActiveRecord::Base.transaction do
begin
@shixun = Shixun.select(:id, :identifier, :challenges_count).find(@myshixun.shixun_id)
@myshixun.destroy!
StudentWork.where(:myshixun_id => @myshixun.id).update_all(myshixun_id: 0, work_status: 0, work_score: nil,
final_score: nil, efficiency: 0, eff_score: 0, calculation_time: nil, cost_time: 0, compelete_status: 0)
rescue Exception => e
logger.error("######reset_my_game_failed:#{e.message}")
raise("ActiveRecord::RecordInvalid")
end
end
# 删除版本库
GitService.delete_repository(repo_path: @repo_path) unless @shixun.is_choice_type?
rescue Exception => e
if e.message != "ActiveRecord::RecordInvalid"
logger.error("######delete_repository_error-:#{e.message}")
end
raise "delete_repository_error:#{e.message}"
end
end
# 代码运行中的信息接口
# 这个方法是中间层主动调用的点击评测后中间层会发送参数过来告诉目前Pod的启动情况一次评测会调用两次请求
def code_runinng_message
begin
jsonTestDetails = JSON.parse(params[:jsonTestDetails])
game_id = jsonTestDetails['buildID']
message = jsonTestDetails['textMsg']
if game_id.present? && message.present?
game = Game.find game_id
msg = game.run_code_message
# 只有评测中的game才会创建和更新代码评测中的信息
if game.status == 1 || game.status == 2
if msg.blank?
RunCodeMessage.create!(:game_id => game_id, :status => 1, :message => message)
else
msg.update_attributes(:status => (msg.status + 1), :message => message)
end
end
render :json => {:data => "success"}
end
rescue Exception => e
render :json => {:data => "failed, exception_message: #{e}"}
end
end
# 中间层评测接口
# taskId 即返回的game id
# 返回结果params [:stauts] 0 表示成功,其它则失败
# msg 错误信息
# output 为测试用户编译输出结果
# myshixun:status 1为完成实训
# @jenkins: caseId对应test_set的positionpassed: 1表示成功0表示失败
# resubmit 1表示已通关后重新评测0表示非重新评测
# retry_status 0初始值1重新评测失败2重新评测成功
# tpiRepoPath 中间层图片的workspace路径
# params[:jsonTestDetails] = '{"buildID":"19284","compileSuccess":"1",
# "msg":[{"caseId":"1","expectedOutput":"MSAyIDMNCg","input":"MiAzIDE","output":"MSAyIDMNCg","passed":"1"},
# {"caseId":"2","expectedOutput":"LTMgMSA2DQo","input":"LTMgNiAx","output":"LTMgMSA2DQo","passed":"1"},
# {"caseId":"3","expectedOutput":"LTcgLTUgLTMNCg","input":"LTcgLTMgLTU","output":"LTcgLTUgLTMNCg","passed":"1"}],
# "outPut":"Y29tcGlsZSBzdWNjZXNzZnVsbHk","resubmit":"","status":"0"}'
# params[:timeCost] = '{"evaluateEnd":"2017-11-24 11:04:37","pull":"0.086",
# "createPod":"1.610","evaluateAllTime":2820,"evaluateStart":"2017-11-24 11:04:35","execute":"0.294"}'
# params[:pics] = "a.png,b.png,c.png"
def training_task_status
ActiveRecord::Base.transaction do
begin
t1 = Time.now
uid_logger_dubug("@@@222222#{params[:jsonTestDetails]}")
jsonTestDetails = JSON.parse(params[:jsonTestDetails])
timeCost = JSON.parse(params[:timeCost])
brige_end_time = Time.parse(timeCost['evaluateEnd']) if timeCost['evaluateEnd'].present?
return_back_time = format("%.3f", ( t1.to_f - brige_end_time.to_f)).to_f
status = jsonTestDetails['status']
game_id = jsonTestDetails['buildID']
sec_key = jsonTestDetails['sec_key']
uid_logger_dubug("training_task_status start-#{game_id}-1#{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
resubmit = jsonTestDetails['resubmit']
outPut = tran_base64_decode64(jsonTestDetails['outPut'])
jenkins_testsets = jsonTestDetails['msg']
compile_success = jsonTestDetails['compileSuccess']
# message = Base64.decode64(params[:msg]) unless params[:msg].blank?
game = Game.find(game_id)
myshixun = game.myshixun
challenge = game.challenge
# test_sets = challenge.test_sets
if challenge.picture_path.present?
#pics = params[:files]
pics = params[:tpiRepoPath]
game.update_column(:picture_path, pics)
end
max_query_index = game.outputs ? (game.outputs.first.try(:query_index).to_i + 1) : 1
test_set_score = 0
unless jenkins_testsets.blank?
jenkins_testsets.each_with_index do |j_test_set, i|
actual_output = tran_base64_decode64(j_test_set['output'])
#ts_time += j_test_set['testSetTime'].to_i
# is_public = test_sets.where(:position => j_test_set['caseId']).first.try(:is_public)
ts_time = format("%.2f", j_test_set['testSetTime'].to_f/1000000000).to_f if j_test_set['testSetTime']
ts_mem = format("%.2f", j_test_set['testSetMem'].to_f/1024/1024).to_f if j_test_set['testSetMem']
Output.create!(:code => status, :game_id => game_id, :out_put => outPut, :test_set_position => j_test_set['caseId'],
:actual_output => actual_output, :result => j_test_set['passed'].to_i, :query_index => max_query_index,
:compile_success => compile_success.to_i, :sec_key => sec_key, :ts_time => ts_time, :ts_mem => ts_mem)
# 如果设置了按测试集给分,则需要统计测试集的分值
if challenge.test_set_score && j_test_set['passed'].to_i == 1
test_set_score += challenge.test_sets.where(:position => j_test_set['caseId']).pluck(:score).first
end
end
end
record = EvaluateRecord.where(:identifier => sec_key).first
answer_deduction_percentage = (100 - game.answer_deduction) / 100.to_f # 查看答案后剩余分数的百分比.
# answer_deduction是查看答案的扣分比例
# status0表示评测成功
if status == "0"
if resubmit.present?
game.update_attributes!(:retry_status => 2, :resubmit_identifier => resubmit)
challenge.path.split("").each do |path|
game_passed_code(path.try(:strip), myshixun, game_id)
end
else
game.update_attributes!(:status => 2,
:end_time => Time.now,
:accuracy => format("%.4f", 1.0 / game.query_index))
myshixun.update_attributes!(:status => 1) if game.had_done == 1
challenge.path.split("").each do |path|
game_passed_code(path.try(:strip), myshixun, game_id)
end
# 如果是已经发布的实训,则需要给出相应的奖励
if challenge.shixun.try(:status) > 1
score = (challenge.score * answer_deduction_percentage).to_i
if score > 0
reward_attrs = { container_id: game.id, container_type: 'Game', score: score }
RewardGradeService.call(game.user, reward_attrs)
RewardExperienceService.call(game.user, reward_attrs)
end
# 需要扣除查看答案的分数
game.update_attributes!(:final_score => score)
end
# 更新实训关联的作品分数 TODO: 更新作品分数
# HomeworksService.new.update_myshixun_work_score myshixun
end
# 如果过关了下一关的状态是3为开启则需要把状态改成1已开启
# next_game = game.next_game
next_game = game.next_game(myshixun.shixun_id, game.myshixun_id, challenge.position)
next_game.update_column(:status, 0) if next_game.present? && next_game.status == 3
# status == "-1" 表示返回结果错误
else
if resubmit.present?
game.update_attributes!(:retry_status => 1, :resubmit_identifier => resubmit)
else
# 评测没通关则,测试集对的个数给分,并且还要扣除用户是否查看答案的值
test_set_percentage = test_set_score / 100.to_f # 测试集得分比
score = (challenge.score * test_set_percentage * answer_deduction_percentage).to_i
# 如果分数比上次多,则更新成绩
game_update =
if game.final_score < score
{final_score: score, status: 0}
else
{status: 0}
end
game.update_attributes!(game_update)
end
end
test_cases_time = format("%.3f", (Time.now.to_f - t1.to_f)).to_f
if record.present?
consume_time = format("%.3f", (Time.now - record.created_at)).to_f
record.update_attributes!(:consume_time => consume_time, :git_pull => timeCost['pull'] , :create_pod => timeCost['createPod'],
:pod_execute => timeCost['execute'], :test_cases => test_cases_time,
:brige => timeCost['evaluateAllTime'], :return_back => return_back_time)
end
sucess_status
# rescue Exception => e
# tip_exception(e.message)
# uid_logger_error("training_task_status error: #{e}")
# raise ActiveRecord::Rollback
end
end
end
# 连接webssh
def open_webssh
username = edu_setting('webssh_username')
password = edu_setting('webssh_password')
old_time = Time.now.to_i
begin
shixun_tomcat = edu_setting('tomcat_webssh')
uri = "#{shixun_tomcat}/bridge/webssh/getConnectInfo"
# 由于中间层采用混合云的方式因为local参数表示在有文件生成的实训是在本地生成还是在其他云端生成评测文件
local = @myshixun.shixun.challenges.where.not(show_type: -1).count == 0
params = {tpiID:@myshixun.id, podType:@myshixun.shixun.try(:webssh), local: local,
containers:(Base64.urlsafe_encode64(shixun_container_limit @myshixun.shixun))}
res = uri_post uri, params
if res && res['code'].to_i != 0
tip_exception("实训云平台繁忙繁忙等级92")
end
render :json => {:host => res['address'],
:port => res['port'],
:ws_url => res['ws_address'],
:username => username,
:password => password,
:game_id => @myshixun.id,
:webssh_url => "#{shixun_tomcat}/bridge"}
rescue Exception => e
logger.error(e)
render :json => {:error => e.try(:message)}
ensure
use_time = Time.now.to_i - old_time
logger.info "open_webssh tpiID #{@myshixun.id} use time #{use_time}"
end
end
include GitCommon
# -----Repository
# TODO: 之类需要一个resubmit参数,但是是关于games.
def update_file
begin
@hide_code = Shixun.where(id: @myshixun.shixun_id).pluck(:hide_code).first
tip_exception("技术平台为空!") if @myshixun.mirror_name.blank?
path = params[:path].strip unless params[:path].blank?
game_id = params[:game_id]
game = Game.find(game_id)
@content_modified = 0
# params[:evaluate] 实训评测时更新必须给的参数,需要依据该参数做性能统计,其它类型的更新可以跳过
# 自动保存的时候evaluate为0点评测的时候为1
if params[:evaluate] == 1
exec_time = game.challenge.try(:exec_time)
@sec_key = generate_identifier(EvaluateRecord, 12)
record = EvaluateRecord.create!(:user_id => current_user.id, :shixun_id => @myshixun.shixun_id, :game_id => game_id,
:identifier => @sec_key, :exec_time => exec_time)
uid_logger_dubug("-- game build: file update #{@sec_key}, record id is #{record.id}, time is **** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
end
# 隐藏代码文件 和 VNC的都不需要走版本库
unless @hide_code || (@myshixun.shixun&.vnc_evaluate && params[:evaluate].present?)
# 远程版本库文件内容
last_content = GitService.file_content(repo_path: @repo_path, path: path)["content"]
content =
if python_file?(path)
params[:content].gsub(/\t/, ' ').gsub(/ /, ' ')
else
params[:content]
end
uid_logger_dubug("###11222333####{content}")
uid_logger_dubug("###222333####{last_content}")
if content != last_content
@content_modified = 1
author_name = current_user.real_name
author_email = current_user.git_mail
message = params[:evaluate] == 0 ? "System automatically submitted" : "User submitted"
uid_logger_dubug("112233#{author_name}")
uid_logger_dubug("112233#{author_email}")
@content = GitService.update_file(repo_path: @repo_path,
file_path: path,
message: message,
content: content,
author_name: author_name,
author_email: author_email)
end
end
if game.status == 2
@resubmit = Time.now.to_i
end
# 评测时间记录
if record.present?
consume_time = format("%.3f", (Time.now.to_f - record.created_at.to_f)).to_f
record.update_attributes!(:file_update => consume_time)
end
rescue Exception => e
uid_logger_error(e.message)
tip_exception("文件内容更新异常,请稍后重试")
end
end
# 渲染实训代码
# educodercss: 字符串以 分隔存储的是版本库css的路径
# educoderscript: 字符串以 分隔存储的是版本库js的路径
# contents html实训的整体内容
def html_content
@contents = params[:contents] || ""
edu_css = params[:educodercss]
edu_js = params[:educoderscript]
if @contents.present?
@contents = @contents.gsub("w3equalsign", "=").gsub("w3scrw3ipttag", "script").gsub("edulink", "link").html_safe
end
# css
if edu_css.present?
css_path = edu_css.split(",")
css_path.each do |path|
file_content = git_fle_content(@repo_path, path)["content"]
file_content = tran_base64_decode64(file_content) unless file_content.blank?
@contents = @contents.sub(/EDUCODERCSS/, "<style>#{file_content}</style>")
end
end
# js
if edu_js.present?
js_path = edu_js.split(",")
js_path.each do |path|
file_content = git_fle_content(@repo_path, path)["content"]
file_content = tran_base64_decode64(file_content) unless file_content.blank?
@contents = @contents.sub(/EDUCODERJS/, "<script>#{file_content}</script>")
end
end
respond_to do |format|
format.json
format.html{render :layout => false}
end
end
# 最新可以用的并发测试接口
def sigle_mul_test
codes = %W(1 2 3 4 5 6 7 8 9 A B C D E F G H J K L N M O P Q R S T U V W X Y Z)
begin
identifiers = Myshixun.where(:shixun_id => params[:shixun_id].split(",")).pluck(:identifier)
ide = identifiers[rand(identifiers.length)]
myshixun = Myshixun.where(:identifier => ide).first
game = myshixun.games.last
logger.warn("###2mul test game_build start ")
identifier = game.try(:identifier)
if game.status == 2
code = codes.sample(8).join
resubmit = "#{code}_#{myshixun.id}"
end
logger.warn("###3mul test game_build start ...")
EvaluateRecord.create!(:user_id => myshixun.user_id, :shixun_id => myshixun.shixun.id, :game_id => game.id)
redirect_to "/api/games/#{identifier}/game_build?resubmit=#{resubmit}&content_modified=0&first=1"
rescue Exception => e
logger.error("mul test failed ===> #{e.message}")
end
end
def sync_code
shixun_tomcat = edu_setting('cloud_bridge')
begin
git_myshixun_url = repo_ip_url @myshixun.repo_path
git_shixun_url = repo_ip_url @myshixun.shixun.try(:repo_path)
git_myshixun_url = Base64.urlsafe_encode64(git_myshixun_url)
git_shixun_url = Base64.urlsafe_encode64(git_shixun_url)
# todo: identifier 是以前的密码,用来验证的,新版如果不需要,和中间层协调更改.
params = {tpiID: "#{@myshixun.try(:id)}", tpiGitURL: "#{git_myshixun_url}", tpmGitURL: "#{git_shixun_url}",
identifier: "xinhu1ji2qu3"}
uri = "#{shixun_tomcat}/bridge/game/resetTpmRepository"
res = uri_post uri, params
if (res && res['code'] != 0)
tip_exception("实训云平台繁忙繁忙等级95")
end
shixun_new_commit = GitService.commits(repo_path: @myshixun.shixun.repo_path).first["id"]
@myshixun.update_attributes!(commit_id: shixun_new_commit, reset_time: @myshixun.shixun.try(:reset_time))
# 更新完成后,弹框则隐藏不再提示
@myshixun.update_column(:system_tip, false)
render_ok
rescue Exception => e
tip_exception("立即更新代码失败!#{e.message}")
end
end
# -----End
private
def find_myshixun
@myshixun = Myshixun.find_by!(identifier: params[:identifier])
end
def find_repo_name
@repo_path = @myshixun.try(:repo_path)
@path = params[:path]
end
def python_file?(path)
false if path.blank?
path.to_s.split(".").last.downcase == "py"
end
end

@ -18,16 +18,16 @@ class ShixunsController < ApplicationController
before_action :find_repo_name, only: [:repository, :commits, :file_content, :update_file, :shixun_exec, :copy,
:add_file, :jupyter_exec]
before_action :allowed, only: [:update, :close, :update_propaedeutics, :settings, :publish,
before_action :allowed, only: [:update, :close, :update_propaedeutics, :settings, :publish, :apply_public,
:shixun_members_added, :change_manager, :collaborators_delete,
:cancel_publish, :add_collaborators, :add_file]
:cancel_apply_public, :cancel_publish, :add_collaborators, :add_file]
before_action :portion_allowed, only: [:copy]
before_action :special_allowed, only: [:send_to_course, :search_user_courses]
## 获取课程列表
def index
@shixuns = current_laboratory.shixuns.unhidden
@shixuns = current_laboratory.shixuns.unhidden.publiced
## 方向
if params[:tag_level].present? && params[:tag_id].present?
@ -365,12 +365,7 @@ class ShixunsController < ApplicationController
end
def create
begin
@shixun = CreateShixunService.call(current_user, shixun_params, params)
rescue => e
logger_error("shixun_create_error: #{e.message}")
tip_exception("创建实训失败!")
end
@shixun = CreateShixunService.call(current_user, shixun_params, params)
end
# 保存jupyter到版本库
@ -730,44 +725,48 @@ class ShixunsController < ApplicationController
@status = 0
@position = []
begin
if @shixun.challenges.count == 0
@status = 4
else
@shixun.challenges.each do |challenge|
if challenge.challenge_tags.count == 0
@status = 3
@position << challenge.position
unless @shixun.is_jupyter?
if @shixun.challenges.count == 0
@status = 4
else
@shixun.challenges.each do |challenge|
if challenge.challenge_tags.count == 0
@status = 3
@position << challenge.position
end
end
end
unfinish_challenge = @shixun.challenges.where(:st => 0, :path => nil)
if unfinish_challenge.count > 0 && !@shixun.is_choice_type?
@status = 2
@pos = []
unfinish_challenge.each do |challenge|
@pos << challenge.position
unfinish_challenge = @shixun.challenges.where(:st => 0, :path => nil)
if unfinish_challenge.count > 0 && !@shixun.is_choice_type?
@status = 2
@pos = []
unfinish_challenge.each do |challenge|
@pos << challenge.position
end
end
end
end
if @status == 0
@shixun.update_attributes!(:status => 1)
apply = ApplyAction.where(:container_type => "ApplyShixun", :container_id => @shixun.id).order("created_at desc").first
if apply && apply.status == 0
@status = 0
else
ApplyAction.create(:container_type => "ApplyShixun", :container_id => @shixun.id, :user_id => current_user.id, :status => 0)
#begin
# status = Trustie::Sms.send(mobile: '18711011226', send_type:'publish_shixun' , name: '管理员')
#rescue => e
# Rails.logger.error "发送验证码出错: #{e}"
#end
@status = 1
end
@shixun.update_attributes!(:status => 2)
end
rescue Exception => e
logger.error("pushlish game #{e}")
end
end
def apply_public
tip_exception(-1, "请先发布实训再申请公开") if @shixun.status != 2
ActiveRecord::Base.transaction do
@shixun.update_attributes!(public: 1)
apply = ApplyAction.where(:container_type => "ApplyShixun", :container_id => @shixun.id).order("created_at desc").first
if apply && apply.status == 0
@status = 0
else
ApplyAction.create(:container_type => "ApplyShixun", :container_id => @shixun.id, :user_id => current_user.id, :status => 0)
end
end
normal_status(0, "申请成功")
end
# 设置私密版本库的在tpm中的目录
def set_secret_dir
raise("设置路径不能为空") if params[:secret_dir_path].blank?
@ -905,14 +904,24 @@ class ShixunsController < ApplicationController
:disposition => 'attachment' #inline can open in browser
end
# 撤销申请公开
def cancel_apply_public
tip_exception("实训已经公开,无法撤销") if @shixun.public == 2
ActiveRecord::Base.transaction do
apply = ApplyAction.where(:container_type => "ApplyShixun", :container_id => @shixun.id).order("created_at desc").first
if apply && apply.status == 0
apply.update_attributes!(status: 3)
apply.tidings&.destroy_all
end
@shixun.update_column(:public, 0)
end
normal_status(0, "成功撤销申请")
end
# 撤销发布
def cancel_publish
tip_exception("实训已经发布,无法撤销") if @shixun.status == 2
apply = ApplyAction.where(:container_type => "ApplyShixun", :container_id => @shixun.id).order("created_at desc").first
if apply && apply.status == 0
apply.update_attribute(:status, 3)
apply.tidings.destroy_all
end
tip_exception("请先撤销申请公开,再撤销发布") if @shixun.public == 1
tip_exception("实训已经公开,无法撤销") if @shixun.public == 2
@shixun.update_column(:status, 0)
end

@ -27,6 +27,17 @@ module ShixunsHelper
end
end
def shixun_public_status shixun
case shixun.try(:public)
when 0,nil
"未公开"
when 1
"待审核"
when 2
"已公开"
end
end
# 已完成实训所获得的经验值
def myshixun_exp myshixun
score = 0

@ -119,6 +119,12 @@ class Game < ApplicationRecord
# self.outputs.pluck(:query_index).first
#end
# 是否查看了答案(通关的是否在通关前看的答案)
def view_answer
answer_exists = Grade.exists?("container_type = 'Answer' and container_id = #{id} and created_at < '#{end_time}'")
answer_open != 0 ? (status == 2 ? answer_exists : true) : false
end
# 用户关卡得分
def get_user_final_score

@ -38,12 +38,20 @@ class Laboratory < ApplicationRecord
find_by_identifier(subdomain)
end
def self.current=(laboratory)
Thread.current[:current_laboratory] = laboratory
# def self.current=(laboratory)
# Thread.current[:current_laboratory] = laboratory
# end
#
# def self.current
# Thread.current[:current_laboratory] ||= Laboratory.find(1)
# end
def self.current=(user)
RequestStore.store[:current_laboratory] = user
end
def self.current
Thread.current[:current_laboratory] ||= Laboratory.find(1)
RequestStore.store[:current_laboratory] ||= User.anonymous
end
def shixuns

@ -88,9 +88,10 @@ class Myshixun < ApplicationRecord
self.games.select{|game| game.status == 2}.size
end
# 查看答案的关卡数
# 查看答案的关卡数,只统计通关前看的关卡
def view_answer_count
self.games.select{|game| game.status == 2 && game.answer_open != 0}.size
answer_ids = user.grades.joins("join games on grades.container_id = games.id").where("container_type = 'Answer' and games.status=2 and games.end_time > grades.created_at").pluck(:container_id)
self.games.select{|game| game.status == 2 && game.answer_open != 0 && answer_ids.include?(game.id)}.size
end
# 通关时间

@ -3,6 +3,7 @@ class Shixun < ApplicationRecord
attr_accessor :page_no #管理员页面 实训配置更新状态时需要接受page_no参数
# status: 0编辑 1申请发布 2正式发布 3关闭 -1软删除
# public: 0未公开 1申请公开 2公开
# hide_code 隐藏代码窗口
# code_hidden: 隐藏代码目录
# task_pass: 跳关
@ -79,6 +80,7 @@ class Shixun < ApplicationRecord
scope :published_closed, lambda{ where(status: [2, 3]) }
scope :none_closed, lambda{ where(status: [0, 1, 2]) }
scope :unhidden, lambda{ where(hidden: 0, status: 2) }
scope :publiced, lambda{ where(public: 2) }
scope :field_for_recommend, lambda{ select([:id, :name, :identifier, :myshixuns_count]) }
scope :find_by_ids,lambda{|k| where(id:k)}

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

@ -540,12 +540,20 @@ class User < ApplicationRecord
mail.present?
end
# def self.current=(user)
# Thread.current[:current_user] = user
# end
#
# def self.current
# Thread.current[:current_user] ||= User.anonymous
# end
def self.current=(user)
Thread.current[:current_user] = user
RequestStore.store[:current_user] = user
end
def self.current
Thread.current[:current_user] ||= User.anonymous
RequestStore.store[:current_user] ||= User.anonymous
end
def self.anonymous

@ -21,7 +21,17 @@ class Admins::ShixunQuery < ApplicationQuery
[0,1,2,3]
end
public =
case params[:public]
when "editing" then [0]
when "pending" then [1]
when "processed" then [2]
else
[0,1,2]
end
all_shixuns = all_shixuns.where(status: status) if status.present?
all_shixuns = all_shixuns.where(public: public) if public.present?
if params[:tag].present?
all_shixuns = all_shixuns.joins(:mirror_repositories).where("mirror_repositories.id = ?",params[:tag].to_i)

@ -10,7 +10,7 @@ class Admins::ShixunAuths::AgreeApplyService < ApplicationService
def call
ActiveRecord::Base.transaction do
apply.update!(status: 1, dealer_id: user.id)
shixun.update!(status: 2, publish_time: Time.now)
shixun.update!(public: 2, publish_time: Time.now)
# 奖励金币、经验
reward_grade_and_experience!

@ -10,7 +10,7 @@ class Admins::ShixunAuths::RefuseApplyService < ApplicationService
def call
ActiveRecord::Base.transaction do
shixun.update!(status: 0)
shixun.update!(public: 0)
apply.update!(status: 2, reason: reason, dealer_id: user.id)
deal_tiding!

@ -45,4 +45,4 @@ class GitService
end
end
end
end

@ -89,7 +89,7 @@ module JupyterService
raise("获取文件内容失败:#{response.status}")
end
content = response.body
content = response.body.force_encoding('utf-8')
c = GitService.update_file(repo_path: shixun.repo_path,
file_path: "01.ipynb",
@ -117,7 +117,7 @@ module JupyterService
raise("获取文件内容失败:#{response.status}")
end
content = response.body
content = response.body.force_encoding('utf-8')
c = GitService.update_file(repo_path: myshixun.repo_path,
file_path: "01.ipynb",

@ -30,7 +30,6 @@ class SearchService < ApplicationService
model_options = {
includes: modal_name.searchable_includes
}
model_options.deep_merge!(where: { status: 2 }) if modal_name == Shixun
model_options.deep_merge!(extra_options)
model_options.deep_merge!(default_options)
@ -40,7 +39,7 @@ class SearchService < ApplicationService
def extra_options
case params[:type].to_s.strip
when 'shixun' then
{ where: { id: Laboratory.current.shixuns.pluck(:id) } }
{ where: { id: Laboratory.current.shixuns.where(public: 2, status: 2, fork_from: nil).or(Laboratory.current.shixuns.where(status: 2, id: User.current.shixuns)).pluck(:id) } }
when 'subject' then
{ where: { id: Laboratory.current.subjects.pluck(:id) } }
when 'course' then

@ -25,7 +25,7 @@ class ShixunSearchService < ApplicationService
else
none_shixun_ids = ShixunSchool.where("school_id != #{User.current.school_id}").pluck(:shixun_id)
@shixuns = @shixuns.where.not(id: none_shixun_ids).where(hidden: 0)
@shixuns = @shixuns.where.not(id: none_shixun_ids).where(hidden: 0, status: 2, public: 2).or(@shixuns.where(id: current_user.shixuns))
end
end

@ -14,34 +14,39 @@ class CreateShixunService < ApplicationService
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)
begin
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
return shixun
rescue => e
Rails.logger.error("shixun_create_error: #{e.message}")
raise("创建实训失败!")
end
end

@ -4,25 +4,31 @@
<div class="box search-form-container shixuns-list-form">
<%= form_tag(admins_shixuns_path, method: :get, class: 'form-inline search-form',id:"shixuns-search-form",remote:true) do %>
<div class="form-group mr-2">
<div class="form-group">
<label for="status">状态:</label>
<% status_options = [['全部', ''], ["编辑中(#{@editing_shixuns})", "editing"], ["待审核(#{@pending_shixuns})", 'pending'], ["已发布(#{@processed_shixuns})", 'processed'],["已关闭(#{@closed_shixuns})",'closed']] %>
<%= select_tag(:status, options_for_select(status_options), class: 'form-control') %>
</div>
<div class="form-group">
<label for="status">公开:</label>
<% public_options = [['全部', ''], ["未公开(#{@none_public_shixuns})", "editing"], ["待审核(#{@pending_public_shixuns})", 'pending'], ["已公开(#{@processed_pubic_shixuns})", 'processed']] %>
<%= select_tag(:public, options_for_select(public_options), class: 'form-control') %>
</div>
<div class="form-group mr-2">
<label for="tag-choosed">技术平台:</label>
<%= select_tag(:tag, options_for_select(@shixuns_type_check.unshift(["",nil])), class: 'form-control',id:"tag-choosed") %>
</div>
<div class="form-group ml-3">
<div class="form-group">
<label>搜索类型:</label>
<% auto_trial_options = [['创建者姓名', 0], ['实训名称', 1], ['学校名称', 2]] %>
<%= select_tag(:search_type, options_for_select(auto_trial_options), class: 'form-control') %>
</div>
<%= text_field_tag(:keyword, params[:keyword], class: 'form-control col-sm-2 ml-3', placeholder: '输入关键字搜索') %>
<%= submit_tag('搜索', class: 'btn btn-primary ml-3','data-disable-with': '搜索中...') %>
<%= link_to "清除",admins_shixuns_path,class: "btn btn-default",id:"shixuns-clear-search",'data-disable-with': '清除中...' %>
<%= text_field_tag(:keyword, params[:keyword], class: 'form-control col-sm-2', placeholder: '输入关键字搜索') %>
<%= submit_tag('搜索', class: 'btn btn-primary','data-disable-with': '搜索中...') %>
<%= link_to "清除", admins_shixuns_path,class: "btn btn-default",id:"shixuns-clear-search",'data-disable-with': '清除中...' %>
<% end %>
<a href="javascript:void(0)" class="btn btn-primary" id="shixuns-export" data-disable-with = '导出中...'>导出</a>
</div>

@ -2,14 +2,15 @@
<thead class="thead-light">
<th width="4%">序号</th>
<th width="8%">ID</th>
<th width="28%" class="text-left">实训名称</th>
<th width="24%" class="text-left">实训名称</th>
<th width="8%">技术平台</th>
<th width="5%">Fork源</th>
<th width="5%">实践</th>
<th width="5%">选择</th>
<th width="6%">状态</th>
<th width="6%">公开</th>
<th width="7%">创建者</th>
<th width="13%"><%= sort_tag('创建于', name: 'created_at', path: admins_shixuns_path) %></th>
<th width="11%"><%= sort_tag('创建于', name: 'created_at', path: admins_shixuns_path) %></th>
<th width="5%">单测</th>
<th width="6%">操作</th>
</thead>
@ -33,6 +34,7 @@
<td><%= shixun.challenges.where(:st => 0).size %></td>
<td><%= shixun.challenges.where(:st => 1).size %></td>
<td class="shixuns-status-<%= shixun.status %>"><%= shixun_authentication_status shixun %></td>
<td class="shixuns-status-<%= shixun.public %>"><%= shixun_public_status shixun %></td>
<td><%= link_to shixun.user.try(:real_name),"/users/#{shixun.user.try(:login)}",target:'_blank' %></td>
<td><%= format_time shixun.created_at %></td>
<td class="homepage_teacher">

@ -1,5 +1,5 @@
json.hack do
json.(@hack, :name, :difficult, :time_limit, :description, :score, :identifier)
json.(@hack, :name, :difficult, :time_limit, :description, :score, :identifier, :status)
json.language @hack.language
json.username @hack.user.real_name
json.code @my_hack.code

@ -8,6 +8,6 @@ end
json.hacks_count @hacks_count
json.hacks_list do
json.array! @hacks do |hack|
json.(hack,:identifier, :name , :hack_user_lastest_codes_count, :difficult, :passed_rate, :category)
json.(hack,:identifier, :name , :hack_user_lastest_codes_count, :difficult, :passed_rate, :category, :open_or_not, :status)
end
end

@ -17,3 +17,5 @@ json.score_info shixun.shixun_preference_info # todo: 这块可以改成只
json.is_jupyter shixun.is_jupyter
# 用于是否显示导航栏中的'背景知识'
json.propaedeutics shixun.propaedeutics.present?
json.public shixun.public

@ -42,7 +42,7 @@ if @shixun
json.challenge_comment challenge_comment&.comment
json.challenge_comment_hidden @user_course_identity < Course::STUDENT ? challenge_comment&.hidden_comment : nil
json.comment_id challenge_comment&.id
json.view_answer game ? game.answer_open != 0 : 0
json.view_answer game ? game.view_answer : false
end
end

@ -55,6 +55,7 @@ Rails.application.routes.draw do
end
member do
post :publish
post :cancel_publish
get :start
post :update_set
delete :delete_set
@ -273,7 +274,9 @@ Rails.application.routes.draw do
post :send_to_course
delete :collaborators_delete
get :cancel_publish
get :cancel_apply_public
get :publish
get :apply_public
get :shixun_exec
post :review_shixun
get :review_newest_record
@ -440,6 +443,7 @@ Rails.application.routes.draw do
get 'statistics'
post :inform_up
post :inform_down
get :calculate_all_shixun_scores
end
collection do

@ -0,0 +1,5 @@
class AddPublicStatusToShixun < ActiveRecord::Migration[5.2]
def change
add_column :shixuns, :public, :integer, default: 0
end
end

@ -0,0 +1,9 @@
class MigrateShixunStatus < ActiveRecord::Migration[5.2]
def change
# 平台上所有已发布且未隐藏的实训都设为公开
Shixun.unhidden.update_all(public: 2)
# 所有已申请发布的实训状态都改为已发布,申请发布改为申请公开
Shixun.where(status: 1, id: ApplyAction.where(container_type: 'ApplyShixun', status: 0).pluck(:container_id)).update_all(status: 2, public: 1)
end
end

@ -326,7 +326,7 @@ module.exports = {
comments: false
},
compress: {
drop_debugger: true,
drop_debugger: false,
drop_console: false
}
}

File diff suppressed because one or more lines are too long

@ -617,7 +617,11 @@ class App extends Component {
{/* jupyter */}
<Route path="/tasks/:identifier/jupyter/"
component={JupyterTPI}
render={
(props) => {
return (<JupyterTPI {...this.props} {...props} {...this.state}/>)
}
}
/>
<Route path="/tasks/:stageId" component={IndexWrapperComponent}/>

@ -35,7 +35,7 @@ if (isDev) {
// 老师
// debugType="teacher";
// 学生
//debugType="student";
// debugType="student";
window._debugType = debugType;
export function initAxiosInterceptors(props) {
@ -51,8 +51,8 @@ export function initAxiosInterceptors(props) {
// proxy = "https://testeduplus2.educoder.net"
//proxy="http://47.96.87.25:48080"
proxy="https://pre-newweb.educoder.net"
proxy="https://test-newweb.educoder.net"
proxy="https://test-jupyterweb.educoder.net/"
proxy="https://test-newweb.educoder.net"
proxy="https://test-jupyterweb.educoder.net"
//proxy="http://192.168.2.63:3001"
// 在这里使用requestMap控制避免用户通过双击等操作发出重复的请求
@ -109,15 +109,15 @@ export function initAxiosInterceptors(props) {
}
//
// console.log(config);
if (config.method === "post") {
if (requestMap[config.url] === true) { // 避免重复的请求 导致页面f5刷新 也会被阻止 显示这个方法会影响到定制信息
// console.log(config);
// console.log(JSON.parse(config));
// console.log(config.url);
// console.log("被阻止了是重复请求=================================");
return false;
}
}
// if (config.method === "post") {
// if (requestMap[config.url] === true) { // 避免重复的请求 导致页面f5刷新 也会被阻止 显示这个方法会影响到定制信息
// // console.log(config);
// // console.log(JSON.parse(config));
// // console.log(config.url);
// // console.log("被阻止了是重复请求=================================");
// return false;
// }
// }
// 非file_update请求
if (config.url.indexOf('update_file') === -1) {
requestMap[config.url] = true;

@ -4,12 +4,12 @@
* @Github:
* @Date: 2019-12-10 09:03:48
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-10 09:05:41
* @LastEditTime: 2019-12-12 10:53:47
*/
import { Icon } from 'antd';
const MyIcon = Icon.createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/font_1535266_ss6796i6f6j.js'
scriptUrl: '//at.alicdn.com/t/font_1535266_i4ilpm93kp.js'
});
export default MyIcon;

@ -19,7 +19,8 @@ class NewShixunModel extends Component{
order:'desc',
diff:0,
limit:15,
sort:"myshixuns_count"
sort:"myshixuns_count",
belongtoindex:0,
}
}
componentDidMount() {
@ -161,7 +162,17 @@ class NewShixunModel extends Component{
})
let{status,order,diff,limit,sort}=this.state;
if(this.props.type==='shixuns'){
this.getdatalist(1,value,status,undefined,order,diff,limit)
this.getdatalist(1,value,status,undefined,order,diff,limit);
if(value==="all"){
this.setState({
belongtoindex:0
})
}else{
this.setState({
belongtoindex:1
})
}
}else{
this.getdatalist(1,value,undefined,undefined,order,undefined,limit,undefined,sort)
}
@ -322,6 +333,7 @@ class NewShixunModel extends Component{
this.getdatalist(page,type,status,keyword,order,diff,limit)
}
updatepathlist=(sorts,orders)=>{
let{page,type,keyword,order,diff,limit,status,sort}=this.state;
let seartorders;
@ -352,7 +364,7 @@ class NewShixunModel extends Component{
}
render() {
let {diff,Grouplist,status,shixun_list,shixuns_count,page,type,order,sort}=this.state;
let {diff,Grouplist,status,shixun_list,shixuns_count,page,type,order,sort,belongtoindex}=this.state;
// let {visible,patheditarry}=this.props;
// console.log(Grouplist)
// console.log(allGrouplist)
@ -446,7 +458,7 @@ class NewShixunModel extends Component{
<Spin spinning={this.state.isspinning}>
<div className={"clearfix educontent pr mb60shixun"}>
<div className={"square-list clearfix"}>
<div className={"square-list clearfix verticallayout"}>
<div className="newshixunheadersear">
<div style={{height:"53px"}}></div>
@ -472,7 +484,36 @@ class NewShixunModel extends Component{
onSearch={ (value)=>this.setdatafuns(value)} />
</div>
<div className="clearfix font-12 mt30">
{this.props.type==='shixuns'?
<div className="clearfix sortinxdirection mt30 intermediatecenterysls">
<p className="nandu">筛选</p>
<p className={belongtoindex===0?"clickbutstwo ml13":"clickbutstwos ml13"} onClick={()=>this.belongto("all")}>全部实训</p>
<p className={belongtoindex===1?"clickbutstwo ml20":"clickbutstwos ml20"} onClick={()=>this.belongto("mine")}>我的实训</p>
</div>:""
}
{/*{this.props.type==='shixuns'? <Dropdown overlay={menus}>*/}
{/* <a className="ant-dropdown-link color-grey-6">*/}
{/* {diff===0?"难度":diff===1?"初级":diff===2?"中级":diff===3?"高级":diff===4?"顶级":""}<Icon type="down" className={"color-grey-6"}/>*/}
{/* </a>*/}
{/*</Dropdown>:""}*/}
{this.props.type==='shixuns'?
<div className="clearfix sortinxdirection mt20 intermediatecenterysls">
<p className="nandu">难度</p>
<p className={diff===0?"clickbuts ml13":"clickbutst ml13"} onClick={()=>this.DropdownClick(0)}>全部</p>
<p className={diff===1?"clickbuts ml30":"clickbutst ml30"} onClick={()=>this.DropdownClick(1)}>初级</p>
<p className={diff===2?"clickbuts ml30":"clickbutst ml30"} onClick={()=>this.DropdownClick(2)}>中级</p>
<p className={diff===3?"clickbuts ml30":"clickbutst ml30"} onClick={()=>this.DropdownClick(3)}>高级</p>
<p className={diff===4?"clickbuts ml30":"clickbutst ml30"} onClick={()=>this.DropdownClick(4)}>顶级</p>
</div>:""
}
<div className={this.props.type==='shixuns'?"clearfix font-12 mt20":"clearfix font-12 mt30"}>
<div className="font-12 ml5 fl">
@ -509,17 +550,18 @@ class NewShixunModel extends Component{
</a>
</Dropdown>:"":""}
{this.props.type==='shixuns'? <Dropdown overlay={menus}>
<a className="ant-dropdown-link color-grey-6">
{diff===0?"难度":diff===1?"初级":diff===2?"中级":diff===3?"高级":diff===4?"顶级":""}<Icon type="down" className={"color-grey-6"}/>
</a>
</Dropdown>:""}
</div>
<div className="font-12 alltopiscright ml25 fr">
{/*<span className={"fr pointer color-grey-3"} onClick={()=>this.props.hideNewShixunModelType()}>返回</span>*/}
<span className={type==="mine"?"fr topcsactive pointer color-grey-3 color-blue":"fr pointer color-grey-3"} onClick={()=>this.belongto("mine")}>我的{this.props.type==='shixuns'?'实训':"课程"}</span>
<span className={type==="all"?"fr mr30 topcsactive pointer color-grey-3 color-blue":"fr mr30 pointer color-grey-3"} onClick={()=>this.belongto("all")}>全部{this.props.type==='shixuns'?'实训':"课程"}</span>
<div className="font-12 alltopiscright ml25 fl">
{this.props.type==='shixuns'?"":
<span className={type==="mine"?"fr topcsactive pointer color-grey-3 color-blue":"fr pointer color-grey-3"} onClick={()=>this.belongto("mine")}>我的课程</span>
}
{this.props.type==='shixuns'?"":
<span className={type==="all"?"fr mr30 topcsactive pointer color-grey-3 color-blue":"fr mr30 pointer color-grey-3"} onClick={()=>this.belongto("all")}>全部课程</span>
}
</div>
</div>
@ -539,26 +581,26 @@ class NewShixunModel extends Component{
className="fl task-hide edu-txt-left mt3"
name="shixun_homework[]"
></Checkbox>
<a target="_blank" href={this.props.type==='shixuns'?`/shixuns/${item.identifier}/challenges`:`/paths/${item.id}`} className="ml15 fl font-16 color-dark maxwidth1100"
dangerouslySetInnerHTML={{__html: item.title}}
>
</a>
{
this.props.type==='shixuns'?
(
item.is_jupyter===true?
<div className="myysljupyter fl ml15 mt3 intermediatecenter">
<div className="myysljupyter fl ml20 mt3 intermediatecenter">
<p className="myysljupytertest">
Jupyter
</p>
</div>
:""
:""
)
:""
}
<a target="_blank" href={this.props.type==='shixuns'?`/shixuns/${item.identifier}/challenges`:`/paths/${item.id}`} className="ml15 fl font-16 color-dark maxwidth1100"
dangerouslySetInnerHTML={{__html: item.title}}
>
</a>
<div className="cl"></div>
<style>
{

@ -386,16 +386,19 @@
margin: 0 auto;
}
.myysljupyter{
width:48px;
height:22px;
background:#FF6802;
border-radius:2px 10px 10px 2px;
width:54px;
height:24px;
text-align: center;
border-radius:5px;
border:1px solid #FF6802;
margin-top: 4px;
}
.myysljupytertest{
width:39px;
width:54px;
height:16px;
line-height:16px;
font-size:12px;
color:#FFFFFF;
color:#FF6802;
line-height:16px;
}
.intermediatecenter{
@ -404,3 +407,110 @@
align-items: center;
justify-content: center;
}
/* 中间居中 */
.intermediatecenter{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
/* 简单居中 */
.intermediatecenterysls{
display: flex;
align-items: center;
}
.spacearound{
display: flex;
justify-content: space-around;
}
.spacebetween{
display: flex;
justify-content: space-between;
}
/* 头顶部居中 */
.topcenter{
display: -webkit-flex;
flex-direction: column;
align-items: center;
}
/* x轴正方向排序 */
/* 一 二 三 四 五 六 七 八 */
.sortinxdirection{
display: flex;
flex-direction:row;
}
/* x轴反方向排序 */
/* 八 七 六 五 四 三 二 一 */
.xaxisreverseorder{
display: flex;
flex-direction:row-reverse;
}
/* 垂直布局 正方向*/
/*
*/
.verticallayout{
display: flex;
flex-direction:column;
}
/* 垂直布局 反方向*/
.reversedirection{
display: flex;
flex-direction:column-reverse;
}
.nandu{
width: 42px;
height: 19px;
font-size: 14px;
color: #000000;
line-height: 19px;
margin-left: 6px;
}
.clickbuts{
text-align: center;
width: 60px;
height: 32px;
background: #4CACFF;
border-radius: 16px;
line-height: 30px;
color: #FFFFFF;
cursor:pointer;
}
.clickbutst{
height:19px;
font-size:14px;
color:#505050;
line-height:19px;
cursor:pointer;
}
.clickbutstwo{
text-align: center;
width: 85px;
height: 32px;
background: #4CACFF;
border-radius: 16px;
line-height: 30px;
color: #FFFFFF;
cursor:pointer;
}
.clickbutstwos{
height:19px;
font-size:14px;
color:#505050;
line-height:19px;
cursor:pointer;
}

@ -483,8 +483,8 @@ class GraduationTasksnew extends Component {
</style>
<Upload {...uploadProps} className="upload_1 ml5">
<Button className="uploadBtn">
<Icon type="upload"/> 上传附件
</Button>
<Icon type="upload"/> 上传附件
</Button>
(单个文件150M以内)
</Upload>

@ -1,16 +1,18 @@
import React, {Component} from 'react';
import {
Button,
} from 'antd';
class Bottomsubmit extends Component {
constructor(props) {
super(props)
this.state = {
}
this.state = {}
}
cannelfun=()=>{
window.location.href=this.props.url
cannelfun = () => {
// window.location.href=
this.props.history.replace(this.props.url);
}
@ -29,8 +31,11 @@ class Bottomsubmit extends Component {
</style>
<div className="clearfix bor-bottom-greyE edu-back-white orderingbox newshixunbottombtn">
<div className=" edu-txt-center padding13-30">
<button type="button" className="ant-btn mr20 newshixunmode backgroundFFF" onClick={()=>this.cannelfun()}><span> </span></button>
<button type="button" className="ant-btn newshixunmode mr40 ant-btn-primary" type="primary" htmlType="submit" onClick={()=>this.props.onSubmits()}><span> </span></button>
<button type="button" className="ant-btn mr20 newshixunmode backgroundFFF" onClick={() => this.cannelfun()}>
<span> </span></button>
<Button type="button" className="ant-btn newshixunmode mr40 ant-btn-primary" type="primary"
htmlType="submit" onClick={() => this.props.onSubmits()}
loading={this.props.loadings}><span>{this.props.bottomvalue===undefined?"确 定":this.props.bottomvalue}</span></Button>
</div>
</div>
</div>
@ -40,7 +45,6 @@ class Bottomsubmit extends Component {
}
export default Bottomsubmit;

@ -29,6 +29,9 @@ render() {
body{
overflow: hidden !important;
}
.ant-modal-body {
padding: 20px 40px;
}
`
}
</style>:""}

@ -1,14 +1,10 @@
import React, {Component} from 'react';
import {Redirect} from 'react-router';
import {BrowserRouter as Router, Route, Link, Switch} from "react-router-dom";
import PropTypes from 'prop-types';
import {Rating, Progress} from "@icedesign/base";
import {Modal, Input, Radio, Pagination, message, Spin, Icon, Tooltip, Rate} from 'antd';
import {Modal, Input, Radio, Pagination, message, Spin, Icon, Tooltip, Button,Popover} from 'antd';
import AccountProfile from "../user/AccountProfile";
@ -298,7 +294,10 @@ class TPMBanner extends Component {
ModalCancel = () => {
this.setState({
Modalstype: false
Modalstype: false,
Modalstopval: "",
modalsMidval:undefined,
ModalsBottomval:"",
})
}
ModalSave = () => {
@ -311,16 +310,53 @@ class TPMBanner extends Component {
console.log(error)
});
}
cancel_publish = () => {
this.setState({
Modalstype: true,
Modalstopval: "是否确认撤销发布?",
modalsMidval:"撤销发布后,学员将无法进行练习,若您新增关",
ModalsBottomval:"卡,学员需要重新体验课程",
ModalCancel: this.ModalCancel,
ModalSave: this.ModalSave,
})
}
openpublic=()=>{
let id = this.props.match.params.shixunId;
let url = `/shixuns/${id}/apply_public.json`;
axios.get(url).then((response) => {
if(response.data.status===0){
window.location.reload()
}
}).catch((error) => {
console.log(error)
});
}
ModalhidenpublicSave=()=>{
let id = this.props.match.params.shixunId;
let url = `/shixuns/${id}/cancel_apply_public.json`;
axios.get(url).then((response) => {
if(response.data.status===0){
window.location.reload()
}
}).catch((error) => {
console.log(error)
});
}
hidenpublic=()=>{
this.setState({
Modalstype: true,
Modalstopval: "是否确认撤销公开?",
modalsMidval:" ",
ModalsBottomval:" ",
ModalCancel: this.ModalCancel,
ModalSave: this.ModalhidenpublicSave,
})
}
/*
* 申请发布按钮
* */
@ -334,12 +370,17 @@ class TPMBanner extends Component {
} else {
evaluation_set_position = response.data.evaluation_set_position
}
this.setState({
Issuevisible: true,
tag_position: response.data.tag_position,
evaluation_set_position: evaluation_set_position,
publishboxstatus: response.data.status,
})
if(response.data.status===0){
window.location.reload()
}else{
this.setState({
Issuevisible: true,
tag_position: response.data.tag_position,
evaluation_set_position: evaluation_set_position,
publishboxstatus: response.data.status,
})
}
}).catch((error) => {
console.log(error)
});
@ -448,7 +489,7 @@ class TPMBanner extends Component {
})
// this.shixunexec(response.data.message+".json")
} else if (response.data.status === -1) {
console.log(response)
} else if (response.data.status === -3) {
this.setState({
shixunsmessage: response.data.message,
@ -520,7 +561,7 @@ class TPMBanner extends Component {
})
// this.shixunexec(response.data.message+".json")
} else if (response.data.status === -1) {
console.log(response)
} else if (response.data.status === -3) {
this.setState({
shixunsmessage: response.data.message,
@ -699,6 +740,7 @@ class TPMBanner extends Component {
modalCancel={this.state.ModalCancel}
modalSave={this.state.ModalSave}
modalsBottomval={this.state.ModalsBottomval}
modalsMidval={this.state.modalsMidval}
loadtype={this.state.Loadtype}
/> : ""}
@ -748,82 +790,86 @@ class TPMBanner extends Component {
</li>
</ul>
<div className="pr fl" id="commentsStar" onMouseOver={() => this.showonMouseOver()}
onMouseOut={() => this.hideonMouseOut()}>
<div className={"color-grey-c ml15"} style={{color: "#Fff", textAlign: "center"}}>学员评分</div>
<div className="rateYo">
<MyRate allowHalf defaultValue={star_info[0]} disabled/>
</div>
<div id="ratePanel" className="showratePanel" style={{"width": "530px"}}
onMouseOut={() => this.hideonMouseOut()}>
<div className="pr">
<span className="rateTrangle"></span>
<div className="pr clearfix ratePanelContent" style={{height: '177px'}}>
<div className="fl totalScore">
<div>
{
this.props.is_jupyter===false?
<div className="pr fl" id="commentsStar" onMouseOver={() => this.showonMouseOver()}
onMouseOut={() => this.hideonMouseOut()}>
<div className={"color-grey-c ml15"} style={{color: "#Fff", textAlign: "center"}}>学员评分</div>
<div className="rateYo">
<MyRate allowHalf defaultValue={star_info[0]} disabled/>
</div>
<div id="ratePanel" className="showratePanel" style={{"width": "530px"}}
onMouseOut={() => this.hideonMouseOut()}>
<div className="pr">
<span className="rateTrangle"></span>
<div className="pr clearfix ratePanelContent" style={{height: '177px'}}>
<div className="fl totalScore">
<div>
<span
className="font-24 color-yellow-ff lineh-20 mb10 ml20">{star_infos[0]}</span>
<span className="displayblock">总评分</span>
<div className="rateYo">
{showradios === true ?
<MyRate allowHalf defaultValue={star_info[0]} disabled/>
: ""}
</div>
</div>
</div>
<div className="fr" style={{width: '375px'}}>
<div className="clearfix">
<div className="rateYo fl mt3">
{showradios === true ?
<MyRate allowHalf defaultValue={5} disabled/>
: ""}
</div>
<Progress percent={star_infos[1]} showInfo={false}></Progress>
<span className="fr ml10 color-grey-6 font-12 mt4">{star_infos[1]}%</span>
</div>
<div className="clearfix">
<div className="rateYo fl mt3">
{showradios === true ?
<MyRate allowHalf defaultValue={4} disabled/>
: ""}
</div>
<Progress percent={star_infos[2]} showInfo={false}></Progress>
<span className="fr ml10 color-grey-6 font-12 mt4">{star_infos[2]}%</span>
</div>
<div className="clearfix">
<div className="rateYo fl mt3">
{showradios === true ?
<MyRate allowHalf defaultValue={3} disabled/>
: ""}
</div>
<Progress percent={star_infos[3]} showInfo={false}></Progress>
<span className="fr ml10 color-grey-6 font-12 mt4">{star_infos[3]}%</span>
</div>
<div className="clearfix">
<div className="rateYo fl mt3">
{showradios === true ?
<MyRate allowHalf defaultValue={2} disabled/>
: ""}
</div>
<Progress percent={star_infos[4]} showInfo={false}></Progress>
<span className="fr ml10 color-grey-6 font-12 mt4">{star_infos[4]}%</span>
</div>
<div className="clearfix">
<div className="rateYo fl mt3">
{showradios === true ?
<MyRate allowHalf defaultValue={1} disabled/>
: ""}
</div>
<Progress percent={star_infos[5]} showInfo={false}></Progress>
<span className="fr ml10 color-grey-6 font-12 mt4">{star_infos[5]}%</span>
</div>
</div>
</div>
</div>
</div>
className="font-24 color-yellow-ff lineh-20 mb10 ml20">{star_infos[0]}</span>
<span className="displayblock">总评分</span>
<div className="rateYo">
{showradios === true ?
<MyRate allowHalf defaultValue={star_info[0]} disabled/>
: ""}
</div>
</div>
</div>
<div className="fr" style={{width: '375px'}}>
<div className="clearfix">
<div className="rateYo fl mt3">
{showradios === true ?
<MyRate allowHalf defaultValue={5} disabled/>
: ""}
</div>
<Progress percent={star_infos[1]} showInfo={false}></Progress>
<span className="fr ml10 color-grey-6 font-12 mt4">{star_infos[1]}%</span>
</div>
<div className="clearfix">
<div className="rateYo fl mt3">
{showradios === true ?
<MyRate allowHalf defaultValue={4} disabled/>
: ""}
</div>
<Progress percent={star_infos[2]} showInfo={false}></Progress>
<span className="fr ml10 color-grey-6 font-12 mt4">{star_infos[2]}%</span>
</div>
<div className="clearfix">
<div className="rateYo fl mt3">
{showradios === true ?
<MyRate allowHalf defaultValue={3} disabled/>
: ""}
</div>
<Progress percent={star_infos[3]} showInfo={false}></Progress>
<span className="fr ml10 color-grey-6 font-12 mt4">{star_infos[3]}%</span>
</div>
<div className="clearfix">
<div className="rateYo fl mt3">
{showradios === true ?
<MyRate allowHalf defaultValue={2} disabled/>
: ""}
</div>
<Progress percent={star_infos[4]} showInfo={false}></Progress>
<span className="fr ml10 color-grey-6 font-12 mt4">{star_infos[4]}%</span>
</div>
<div className="clearfix">
<div className="rateYo fl mt3">
{showradios === true ?
<MyRate allowHalf defaultValue={1} disabled/>
: ""}
</div>
<Progress percent={star_infos[5]} showInfo={false}></Progress>
<span className="fr ml10 color-grey-6 font-12 mt4">{star_infos[5]}%</span>
</div>
</div>
</div>
</div>
</div>
</div>
:""
}
</div>
{
startbtn === false && shixunsDetails.shixun_status != -1 ?
@ -903,8 +949,22 @@ class TPMBanner extends Component {
{/*}*/}
{shixunsDetails.shixun_status === 0 && this.props.identity < 5 ?
<a onClick={this.applyrelease} className="fr user_default_btn user_blue_btn mr20 font-18 height39"
id="challenge_begin">申请发布</a> : ""
<Popover
content={
<pre className={"pd20"}>
<div>您编辑完成后可以马上使用到自</div>
<div className={"wechatcenter mt10"}>己的课堂和实训课程哦</div>
<div className={"wechatcenter mt15"}> <Button type="primary" >我知道了</Button></div>
</pre>
}
trigger="click"
placement="bottom"
visible={false}
onVisibleChange={this.handleVisibleChange}
>
<a onClick={this.applyrelease} className="fr user_default_btn user_blue_btn mr20 font-18 height39"
id="challenge_begin">发布</a>
</Popover>: ""
}
<Modal
@ -967,7 +1027,19 @@ class TPMBanner extends Component {
</Modal>
{shixunsDetails.shixun_status === 1 && this.props.identity < 5 ?
{shixunsDetails.shixun_status === 2 && shixunsDetails.public===0 && this.props.identity < 5 ?
<Button type="primary" ghost id="challenge_begin" onClick={this.openpublic} className="fr user_default_btn user_blue_btn mr20 font-18 height39">
申请公开
</Button>: ""
}
{shixunsDetails.shixun_status === 2 && shixunsDetails.public===1 && this.props.identity < 5 ?
<Button type="primary" ghost id="challenge_begin" onClick={this.hidenpublic} className="fr user_default_btn user_blue_btn mr20 font-18 height39">
撤销公开
</Button>: ""
}
{shixunsDetails.shixun_status === 2 && shixunsDetails.public===0 && this.props.identity < 5 ?
<a onClick={this.cancel_publish} className="fr user_default_btn user_blue_btn mr20 font-18 height39"
id="challenge_begin">撤销发布</a> : ""
}

@ -7,7 +7,7 @@ import TPMRightSection from './component/TPMRightSection';
import TPMNav from './component/TPMNav';
import axios from 'axios';
import './tpmmodel/tpmmodel.css'
import {getUploadActionUrl} from 'educoder';
import {getUploadActionUrl,appendFileSizeToUploadFileAll} from 'educoder';
import moment from 'moment';
const confirm = Modal.confirm;
@ -16,13 +16,12 @@ class TPMDataset extends Component {
constructor(props) {
super(props)
this.state = {
datas: [0, 1, 2, 3, 4, 5],
value: undefined,
columns: [
{
title: '文件',
dataIndex: 'number',
key: 'number',
dataIndex: 'title',
key: 'title',
align: 'left',
className: " font-14 wenjiantit",
width: '220px',
@ -34,8 +33,8 @@ class TPMDataset extends Component {
},
{
title: '最后修改时间',
dataIndex: 'number',
key: 'number',
dataIndex: 'timedata',
key: 'timedata',
align: 'center',
className: "edu-txt-center font-14 zuihoushijian",
width: '150px',
@ -47,8 +46,8 @@ class TPMDataset extends Component {
},
{
title: '最后修改人',
dataIndex: 'number',
key: 'number',
dataIndex: 'author',
key: 'author',
align: 'center',
className: "edu-txt-center font-14 ",
render: (text, record) => (
@ -59,8 +58,8 @@ class TPMDataset extends Component {
},
{
title: '文件大小',
dataIndex: 'number',
key: 'number',
dataIndex: 'filesize',
key: 'filesize',
align: 'center',
className: "edu-txt-center font-14 ",
render: (text, record) => (
@ -76,14 +75,20 @@ class TPMDataset extends Component {
mylistansum:30,
collaboratorList:[],
fileList:[],
fileListimg:[],
file:null,
datalist:[],
data_sets_count:0,
selectedRowKeysdata:[],
loadingstate:false,
checked: false,
}
}
componentDidMount() {
this.setState({
loadingstate:true,
})
this.getdatas()
}
@ -98,18 +103,20 @@ class TPMDataset extends Component {
datas.push(i);
}
this.setState({
selectedRowKeysdata:mydata,
selectedRowKeys: datas,
checked:true,
})
// console.log(mydata);
// console.log(datas);
} else {
this.setState({
selectedRowKeysdata:[],
selectedRowKeys: [],
checked:false,
})
}
}
@ -143,11 +150,26 @@ class TPMDataset extends Component {
collaboratorList: response.data,
data_sets_count:response.data.data_sets_count,
datalist:datalists,
selectedRowKeysdata:[],
selectedRowKeys: [],
checked:false,
});
}
}
setTimeout(() => {
this.setState({
loadingstate:false,
})
}, 500)
}).catch((error)=>{
setTimeout(() => {
this.setState({
loadingstate:false,
})
}, 500)
console.log(error)
});
@ -181,34 +203,39 @@ class TPMDataset extends Component {
collaboratorList: response.data,
data_sets_count:response.data.data_sets_count,
datalist:datalists,
selectedRowKeysdata:[],
selectedRowKeys: [],
checked:false,
});
}
}
setTimeout(() => {
this.setState({
loadingstate:false,
})
}, 500)
}).catch((error)=>{
setTimeout(() => {
this.setState({
loadingstate:false,
})
}, 500)
console.log(error)
});
}
showModal = (id, status) => {
};
handleOk = (id, editid) => {
};
handleCancel = (e) => {
};
paginationonChanges = (pageNumber) => {
// //console.log('Page: ');
this.setState({
page: pageNumber,
loadingstate:true,
})
this.getdatastwo(pageNumber,10);
}
onSelectChange = (selectedRowKeys, selectedRows) => {
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
this.setState(
@ -234,40 +261,34 @@ class TPMDataset extends Component {
if (index % 2 === 1) className = 'dark-row';
return className;
}
// 附件相关 START
handleChange = (info) => {
if(info.file.status === 'uploading' || info.file.status === 'done' || info.file.status === 'removed') {
let {fileList} = this.state;
if (info.file.status === 'uploading' || info.file.status === 'done' || info.file.status === 'removed') {
console.log("handleChange1fileLists");
// if(fileList.length===0){
let fileLists = info.fileList;
console.log(fileLists);
this.setState({
// fileList:appendFileSizeToUploadFileAll(fileList),
fileList: fileLists,
deleteisnot: false
});
}
if(info.file.status === 'done'){
//done 成功就会调用这个方法
this.getdatas();
// this.props.showNotification(`上传文件成功`);
}else if(info.file.status === 'removed'){
// this.props.showNotification(`上传文件失败`);
if(info.file.status == "done" || info.file.status == "uploading" || info.file.status === 'removed'){
let fileList = info.fileList;
// try {
// for(var list of fileList ){
// console.log(list)
// this.state.fileListimg.push(list);
// }
// }catch (e) {
//
// }
this.setState({
fileList: appendFileSizeToUploadFileAll(fileList),
}else if(info.file.status === 'uploading'){
// this.props.showNotification(`正在上传文件中`);
});
}
if(info.file.status === 'done'){
this.setState({
fileListimg:this.state.fileListimg,
})
this.getdatas();
}
}
}
onAttachmentRemove = (file) => {
debugger
// debugger
if(!file.percent || file.percent == 100){
confirm({
title: '确定要删除这个附件吗?',
@ -288,28 +309,48 @@ class TPMDataset extends Component {
}
deleteRemovedata(){
debugger
console.log("删除");
console.log(this.state.selectedRowKeysdata);
const url = `/attachments/destroy_files.json`;
axios.delete(url, {
id:this.state.selectedRowKeysdata,
})
.then((response) => {
if (response.data) {
const { status } = response.data;
if (status == 0) {
this.props.showNotification(`删除成功`);
this.getdatas()
}
}
})
.catch(function (error) {
console.log(error);
});
if(this.state.selectedRowKeysdata===undefined || this.state.selectedRowKeysdata===null ||this.state.selectedRowKeysdata.length===0){
this.props.showNotification(`请选择要删除的文件`);
return
}
confirm({
title: '确定要删除文件吗?',
okText: '确定',
cancelText: '取消',
// content: 'Some descriptions',
onOk: () => {
const url = `/attachments/destroy_files.json`;
axios.delete(url,
{ params: {
id:this.state.selectedRowKeysdata,
}}
)
.then((response) => {
if (response.data) {
const { status } = response.data;
if (status == 0) {
this.props.showNotification(`删除成功`);
this.getdatas()
}
}
})
.catch(function (error) {
console.log(error);
});
},
onCancel() {
console.log('Cancel');
},
});
}
deleteAttachment = (file) => {
console.log(file);
// console.log(file);
let id=file.response ==undefined ? file.id : file.response.id
const url = `/attachements/destroy_files.json`
axios.delete(url, {
@ -343,7 +384,7 @@ class TPMDataset extends Component {
render() {
const {tpmLoading, shixun, user, match} = this.props;
const {columns, datas, page, limit, selectedRowKeys,mylistansum,fileList,datalist,data_sets_count} = this.state;
const {columns, page, limit, selectedRowKeys,mylistansum,fileList,datalist,data_sets_count,loadingstate,fileListimg} = this.state;
const rowSelection = {
selectedRowKeys,
onChange: this.onSelectChange,
@ -357,50 +398,31 @@ class TPMDataset extends Component {
width: 600,
fileList,
data:{
attachtype: 2,
attachtype: 2,
container_id:this.props.match.params.shixunId,
container_type: "Shixun",
},
},
multiple: true,
// https://github.com/ant-design/ant-design/issues/15505
// showUploadList={false},然后外部拿到 fileList 数组自行渲染列表。
// showUploadList: false,
action: `${getUploadActionUrl()}`,
action: `${getUploadActionUrl()}`,
showUploadList:false,
onChange: this.handleChange,
onRemove: this.onAttachmentRemove,
beforeUpload: (file, fileList) => {
if (this.state.fileList.length >= 1) {
return false
}
// console.log('beforeUpload', file.name);
const isLt150M = file.size / 1024 / 1024 < 50;
beforeUpload: (file) => {
//上传前的操作
console.log('beforeUpload', file.name);
console.log("TPMDatasetTPMDatasetTPMDataset");
console.log(fileListimg);
const isLt150M = file.size / 1024 / 1024 < 150;
if (!isLt150M) {
// this.props.showNotification(`文件大小必须小于50MB`);
notification.open(
{
message: '提示',
description:
'文件大小必须小于50MB',
}
)
}
if(this.state.file !== undefined){
console.log("763")
this.setState({
file:file
})
}else {
this.setState({
file:file
})
this.props.showNotification('文件大小必须小于150MB!');
}
console.log("handleChange2");
return isLt150M;
},
}
};
return (
<React.Fragment>
<div className="tpmComment educontent clearfix mt30 mb80">
@ -417,9 +439,11 @@ class TPMDataset extends Component {
<div className="padding20 edu-back-white mt20 " style={{minHeight: '463px'}}>
<div className="sortinxdirection">
<div className="tpmwidth">
<Checkbox onChange={this.mysonChange}>全选</Checkbox>
</div>
<div className="tpmwidth">
<Checkbox checked={this.state.checked} onChange={this.mysonChange}>全选</Checkbox>
</div>
<div className="tpmwidth xaxisreverseorder">
<style>
{
@ -430,7 +454,7 @@ class TPMDataset extends Component {
`
}
</style>
<div className="deletebuttom intermediatecenter "> <Upload {...uploadProps}><p className="deletebuttomtest">
<div className="deletebuttom intermediatecenter "> <Upload {...uploadProps}><p className="deletebuttomtest" type="upload">
上传文件</p> </Upload></div>
{
@ -517,6 +541,8 @@ class TPMDataset extends Component {
className="mysjysltable4"
rowSelection={rowSelection}
rowClassName={this.rowClassName}
loading={loadingstate}
/>
</div>
}

@ -20,9 +20,9 @@ import TPMRepositoryComponent from './TPMRepositoryComponent';
import TPMRepositoryCommits from './shixunchild/Repository/TPMRepositoryCommits';
//import TPMsettings from './TPMsettings/TPMsettings';
import TPMsettings from './TPMsettings/TPMsettings';
import TPMsettings from './TPMsettings/oldTPMsettings';
//import TPMsettings from './TPMsettings/oldTPMsettings';
import TPMChallengeComponent from './TPMChallengeContainer';
import TPMPropaedeuticsComponent from './TPMPropaedeuticsComponent';
@ -153,7 +153,7 @@ class TPMIndex extends Component {
componentDidMount = () => {
let id = this.props.match.params.shixunId;
console.log('props', this.props);
// console.log('props', this.props);
// let collaborators = `/shixuns/` + id + `/propaedeutics.json`;
//
// axios.get(collaborators).then((response) => {
@ -288,6 +288,7 @@ class TPMIndex extends Component {
<TPMBanner
{...this.props}
{...this.state}
is_jupyter={this.state. is_jupyter}
></TPMBanner>
}
{/*筛选*/}

@ -1,11 +1,15 @@
import React, { Component } from 'react';
import MonacoEditor from 'react-monaco-editor';
import React, {Component} from 'react';
//MonacoDiffEditor 对比模式
import {Input, Select, Radio, Checkbox, Popconfirm, message, Modal,Icon,DatePicker,Breadcrumb,Upload,Button,notification, Tooltip} from 'antd';
// import "antd/dist/antd.css";
import {
Badge,
Select,
Radio,
Checkbox,
Modal,
DatePicker,
Button,
} from 'antd';
import locale from 'antd/lib/date-picker/locale/zh_CN';
@ -15,12 +19,9 @@ import axios from 'axios';
import './css/TPMsettings.css';
import { getImageUrl, toPath, getUrl ,appendFileSizeToUploadFileAll, getUploadActionUrl} from 'educoder';
import {handleDateStrings} from "./oldTPMsettings";
let origin = getUrl();
let path = getUrl("/editormd/lib/")
import Bottomsubmit from "../../modals/Bottomsubmit";
const $ = window.$;
@ -31,7 +32,6 @@ let currentValue;
const Option = Select.Option;
const RadioGroup = Radio.Group;
const confirm = Modal.confirm;
function range(start, end) {
const result = [];
@ -40,6 +40,7 @@ function range(start, end) {
}
return result;
}
function disabledDateTime() {
return {
// disabledHours: () => range(0, 24).splice(4, 20),
@ -57,119 +58,325 @@ export default class Shixuninformation extends Component {
constructor(props) {
super(props)
this.state = {
can_copy: false,
use_scope: 0,
opening_time: null,
opentime: false,
oldscope_partment: [],
scope_partment: [],
loading: false
}
}
componentDidMount() {
if (this.props.data) {
if (this.props.data.shixun) {
this.setState({
can_copy: this.props.data && this.props.data.shixun.can_copy === undefined ? false : this.props.data && this.props.data.shixun.can_copy,
use_scope: this.props.data && this.props.data.shixun.use_scope,
opening_time: this.props.data && this.props.data.shixun.opening_time,
opentime: !this.props.data && this.props.data.shixun.opening_time ? false : true,
oldscope_partment: this.props.data && this.props.data.shixun.scope_partment,
})
}
}
let departmentsUrl = `/shixuns/departments.json`;
axios.get(departmentsUrl).then((response) => {
if (response.status === 200) {
if (response.data.message === undefined) {
this.setState({
departmentslist: response.data.shools_name
});
}
}
}).catch((error) => {
console.log(error)
});
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.data != this.props.data) {
if (this.props.data) {
if (this.props.data.shixun) {
this.setState({
can_copy: this.props.data && this.props.data.shixun.can_copy === undefined ? false : this.props.data && this.props.data.shixun.can_copy,
use_scope: this.props.data && this.props.data.shixun.use_scope,
opening_time: this.props.data && this.props.data.shixun.opening_time,
opentime: !this.props.data && this.props.data.shixun.opening_time ? false : true,
oldscope_partment: this.props.data && this.props.data.shixun.scope_partment,
})
}
}
}
}
onChangeTimePicker =(value, dateString)=> {
onChangeTimePicker = (value, dateString) => {
this.setState({
opening_time: dateString === "" ? "" : handleDateStrings(dateString)
})
}
onSubmits = () => {
this.setState({
opening_time: dateString=== ""?"":moment(handleDateStrings(dateString))
loading: true
})
let {can_copy, use_scope, scope_partment, opening_time} = this.state;
let id = this.props.match.params.shixunId;
let url = `/shixuns/${id}/update_permission_setting.json`;
axios.post(url,
{
scope_partment: scope_partment,
shixun: {
can_copy: can_copy,
use_scope: use_scope,
opening_time: opening_time
}
}
).then((response) => {
if (response.data.status === -1) {
} else {
this.props.getdatas("3")
this.props.showNotification("权限配置保存成功!")
this.setState({
loading: false
})
}
}).catch((error) => {
this.setState({
loading: false
})
})
}
CheckboxonChange = (e) => {
this.setState({
can_copy: e.target.checked
})
}
SelectOpenpublic = (e) => {
this.setState({
use_scope: e.target.value
});
}
shixunScopeInput = (e) => {
let {scope_partment, oldscope_partment} = this.state;
let datalist = scope_partment;
if (datalist === undefined) {
datalist = []
}
datalist.push(e)
let scopetype = false;
scope_partment.map((item, key) => {
if (item === e) {
scopetype = true
}
})
oldscope_partment.map((item, key) => {
if (item === e) {
scopetype = true
}
})
if (scopetype === false) {
this.setState({
scope_partment: datalist
});
} else {
this.props.showNotification("请勿指定相同的单位")
}
}
shixunsfetch = (value, callback) => {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
currentValue = value;
function fake() {
let departmentsUrl = `/shixuns/departments.json?q=` + currentValue;
axios.get(departmentsUrl).then((response) => {
callback(response.data.shools_name);
}).catch((error) => {
console.log(error)
});
}
timeout = setTimeout(fake, 300);
}
shixunHandleSearch = (value) => {
this.shixunsfetch(value, departmentslist => this.setState({departmentslist}));
}
deleteScopeInput = (key) => {
let {scope_partment} = this.state;
let datalist = scope_partment;
datalist.splice(key, 1);
this.setState({
scope_partment: datalist
});
}
setopentime = (e) => {
this.setState({
opentime: e.target.checked
})
}
render() {
let {can_copy}=this.state;
let options;
if (this.props.departmentslist != undefined) {
options = this.props.departmentslist.map((d, k) => {
let options;
if (this.state.departmentslist != undefined) {
options = this.state.departmentslist.map((d, k) => {
return (
<Option key={d} id={k}>{d}</Option>
)
})
}
const dateFormat = 'YYYY-MM-DD HH:mm';
return (
<div className="educontent mb50 edu-back-white padding10-20">
<div className="clearfix ml30">
<span className="color-grey-6 mt5 fl" style={{minWidth: '95px'}}>复制:</span>
<span className="fl mt5">
<Checkbox checked={this.props.data&&this.props.data.shixun.can_copy === undefined ? false : this.props.data&&this.props.data.shixun.can_copy} onChange={this.props.data&&this.props.data.shixun.can_copy}></Checkbox>
<label style={{top:'6px'}} className="color-grey-9 ml10">(勾选则允许已认证的教师复制该实训)</label>
</span>
</div>
<div>
<div className="educontent mb200 edu-back-white padding10-20 pdb30 mb50">
<div className="clearfix ml40">
<span className="color-grey-6 mt5 fl font-16 ml20" style={{minWidth: '45px'}}>复制:</span>
<span className="fl mt8 ml13">
<Checkbox
checked={this.state.can_copy}
onChange={this.CheckboxonChange}></Checkbox>
<label style={{top: '6px'}} className="color-grey-9 ml10">选中则允许已职业认证的教师复制该实训</label>
</span>
</div>
<div className="edu-back-white mb10 padding40-20" style={{display:this.props.identity===1?"block":this.props.data&&this.props.data.shixun.status===2&&this.props.data&&this.props.data.shixun.use_scope===0||this.props.data&&this.props.data.shixun.status===1&&this.props.data&&this.props.data.shixun.use_scope===0?"none":"block"}}>
<p className="color-grey-6 font-16 mb30">公开程度</p>
<RadioGroup onChange={this.SelectOpenpublic} value={this.props.data&&this.props.data.use_scope}>
<Radio className="radioStyle" value={0}><span>对所有公开</span> <span className="color-grey-9">()</span></Radio>
<Radio className="radioStyle" value={1}><span>对指定单位公开</span> <span className="color-grey-9">()</span></Radio>
</RadioGroup>
<div className="clearfix none" id="unit-all" style={{display: this.props.scopetype === false ? 'none' : 'block'}}>
<div className="fl ml25">
<div className="fl" id="unit-input-part" style={{width:'100%'}}>
<div id="person-unit" className="fl pr mr10">
<div className="shixunScopeInput fl" >
<Select
style={{width:'200px'}}
placeholder="请输入并选择单位名称"
onChange={(value)=>this.shixunScopeInput(value)}
onSearch={this.shixunHandleSearch}
showSearch
defaultActiveFirstOption={false}
showArrow={false}
filterOption={false}
notFoundContent={null}
className={this.props.scope_partmenttype===true?"bor-red":""}
>
{options}
</Select>
<div className="edu-back-white mb10 ml30 mt20">
{this.props.data && this.props.data.shixun.use_scope === 0 && this.props.data && this.props.data.shixun.status === 2 ? "" :
<div>
<span className="color-grey-6 mt5 fl font-16" style={{minWidth: '45px'}}>公开程度:</span>
<span className="fl mt8 ml20">
<RadioGroup onChange={this.SelectOpenpublic} value={this.state.use_scope}>
<Radio className="radioStyle" value={0}><span>对所有单位公开</span> <span
className="color-grey-9">实训发布后所有用户可见</span></Radio>
<Radio className="radioStyle" value={1}><span>对指定单位公开</span> <span
className="color-grey-9">实训发布后仅对下方指定单位的用户可见</span></Radio>
</RadioGroup>
<div className="clearfix" id="unit-all"
style={{display: this.state.use_scope === 0 ? 'none' : 'block'}}>
<div className="fl ml25">
<div className="fl" id="unit-input-part" style={{width: '100%'}}>
<div id="person-unit" className="fl pr mr10">
<div className="shixunScopeInput fl">
<Select
style={{width: '200px'}}
placeholder="请输入并选择单位名称"
onChange={(value) => this.shixunScopeInput(value)}
onSearch={this.shixunHandleSearch}
showSearch
value={this.state.scope_partment}
defaultActiveFirstOption={false}
showArrow={false}
filterOption={false}
notFoundContent={null}
className={this.props.scope_partmenttype === true ? "bor-red" : ""}
>
{options}
</Select>
</div>
<span className="color-grey-9 openrenyuan">请通过搜索并选中单位名称进行添加</span>
</div>
<span className="color-grey-9">(搜索并选中添加单位名称)</span>
</div>
</div>
<div style={{width:'100%'}}>
<div className="mt20 clearfix" id="task_tag_content">
{
this.props.scope_partment===undefined?"":this.props.scope_partment.map((item,key)=>{
return(
<li className="task_tag_span" key={key}><span>{item}</span>
<a style={{ color: 'rgba(0,0,0,.25)' }}
onClick={(key)=>this.deleteScopeInput(key)}
>
{this.props.identity===1?"x":this.state.status===2&&this.props.scope_partment===this.props.scope_partments||this.state.status===1&&this.props.scope_partment===this.props.scope_partments?"":"×"}
</a>
</li>
)
})
}
<div style={{width: '100%'}}>
<div className="mt20 clearfix" id="task_tag_content">
{
this.state.oldscope_partment.map((item, key) => {
return (
<li key={key} className={"fl mr20"}>
<Button type="primary" ghost className={"Permanentban "}>
{item}
</Button>
</li>
)
})
}
{
this.state.scope_partment === undefined ? "" : this.state.scope_partment.map((item, key) => {
return (
<li key={key} className={"fl mr20"}>
<Badge count={"x"} onClick={(key) => this.deleteScopeInput(key)}>
<Button type="primary" ghost className={"Permanentban "}>
{item}
</Button>
</Badge>
</li>
)
})
}
</div>
</div>
<span
className={this.props.scope_partmenttype === true ? "color-orange ml20 fl" : "color-orange ml20 fl none"}
id="public_unit_notice">
<i className="fa fa-exclamation-circle mr3"></i>
请选择需要公开的单位
</span>
</div>
</div>
<span className={this.props.scope_partmenttype===true?"color-orange ml20 fl":"color-orange ml20 fl none"} id="public_unit_notice">
<i className="fa fa-exclamation-circle mr3"></i>
请选择需要公开的单位
</span>
<div className="clearfix mt20 ml30">
<span className="color-grey-6 mt5 fl" style={{minWidth: '95px'}}>开启时间:</span>
<span className="fl mt5">
<DatePicker
showToday={false}
showTime={{ format: 'HH:mm' }}
format="YYYY-MM-DD HH:mm"
width={178}
locale={locale}
disabledTime={disabledDateTime}
disabledDate={disabledDate}
placeholder="请选择开启时间"
value={this.props.shixun.opening_time===null||this.props.shixun.opening_time===""?"":moment(this.props.shixun.opening_time, dateFormat)}
onChange={this.onChangeTimePicker}
dropdownClassName="hideDisable"
/>
<label style={{top:'6px'}} className="color-grey-9 ml10" >为空则学员在实训发布后能随时开启实训挑战否则学员在开启时间后才能开启实训挑战</label>
</span>
</div>
</span>
</div>}
<div className="clearfix mt20">
<span className="color-grey-6 mt5 fl font-16" style={{minWidth: '45px'}}>开启时间:</span>
<span className="fl mt8 ml20">
<Checkbox
checked={this.state.opentime === undefined ? false : this.state.opentime}
onChange={this.setopentime}></Checkbox>
<label style={{top: '6px'}}
className="color-grey-9 ml10">选中则学员在指定的开启时间后才能开启学习不选中则学员在实训发布后能立即开启学习</label>
<div className={"both"}></div>
{this.state.opentime === false ? "" : <div className="mt20 ml25">
<DatePicker
showToday={false}
showTime={{format: 'HH:mm'}}
format="YYYY-MM-DD HH:mm"
width={178}
locale={locale}
disabledTime={disabledDateTime}
disabledDate={disabledDate}
placeholder="请输入开启时间"
value={this.state.opening_time === null || this.state.opening_time === "" ? "" : moment(this.state.opening_time, dateFormat)}
onChange={this.onChangeTimePicker}
dropdownClassName="hideDisable"
/>
</div>}
</span>
</div>
</div>
</div>
{this.props.identity < 5 ?
<Bottomsubmit {...this.props} {...this.state} url={`/shixuns/${this.props.match.params.shixunId}/challenges`}
onSubmits={this.onSubmits} loadings={this.state.loading} bottomvalue={ this.props.shixunsDetails&&this.props.shixunsDetails.is_jupyter === true?"确 定":"下一步"} /> : ""}
</div>
);
}

@ -1,30 +1,345 @@
import React, { Component } from 'react';
import React, {Component} from 'react';
import {Input, Select, Radio, Checkbox, Popconfirm, message, Modal,Icon,DatePicker,Breadcrumb,Upload,Button,notification, Tooltip,Tabs} from 'antd';
import {
Radio,
Checkbox,
} from 'antd';
import axios from 'axios';
import './css/TPMsettings.css';
import { getImageUrl, toPath, getUrl ,appendFileSizeToUploadFileAll, getUploadActionUrl} from 'educoder';
import Bottomsubmit from "../../modals/Bottomsubmit";
const RadioGroup = Radio.Group;
export default class Shixuninformation extends Component {
constructor(props) {
super(props)
this.state = {
vnc: false,
hide_code: false,
is_secret_repository: false,
code_hidden: false,
forbid_copy: false,
test_set_permission: true,
task_pass: true,
websshshow: false,
multi_webssh: false,
opensshRadio: null,
loading: false
}
}
componentDidMount() {
if (this.props.data ) {
if (this.props.data.shixun) {
this.setState({
vnc: this.props.data && this.props.data.shixun.vnc,
code_hidden: this.props.data && this.props.data.shixun.code_hidden,
forbid_copy: this.props.data && this.props.data.shixun.forbid_copy,
hide_code: this.props.data && this.props.data.shixun.hide_code,
task_pass: this.props.data && this.props.data.shixun.task_pass,
test_set_permission: this.props.data && this.props.data.shixun.test_set_permission,
is_secret_repository: this.props.data && this.props.data.shixun.is_secret_repository,
websshshow: this.props.data && this.props.data.shixun.webssh === 0 ? false : true,
multi_webssh: this.props.data && this.props.data.shixun.multi_webssh,
opensshRadio: this.props.data && this.props.data.shixun.webssh === 0 ? null : this.props.data && this.props.data.shixun.webssh,
})
// if(this.props.data && this.props.data.shixun.status===0){
// this.setState({
// task_pass:true
// })
// }
}
}
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.data != this.props.data) {
if (this.props.data) {
if (this.props.data.shixun) {
this.setState({
vnc: this.props.data && this.props.data.shixun.vnc,
code_hidden: this.props.data && this.props.data.shixun.code_hidden,
forbid_copy: this.props.data && this.props.data.shixun.forbid_copy,
hide_code: this.props.data && this.props.data.shixun.hide_code,
task_pass: this.props.data && this.props.data.shixun.task_pass,
test_set_permission: this.props.data && this.props.data.shixun.test_set_permission,
is_secret_repository: this.props.data && this.props.data.shixun.is_secret_repository,
websshshow: this.props.data && this.props.data.shixun.webssh === 0 ? false : true,
multi_webssh: this.props.data && this.props.data.shixun.multi_webssh,
opensshRadio: this.props.data && this.props.data.shixun.webssh === 0 ? null : this.props.data && this.props.data.shixun.webssh,
})
// if(this.props.data && this.props.data.shixun.status===0){
// this.setState({
// task_pass:true
// })
// }
render() {
}
}
}
}
onSubmits = () => {
this.setState({
loading: true
})
let id = this.props.match.params.shixunId;
let url = `/shixuns/${id}/update_permission_setting.json`;
axios.post(url,
{
shixun: {
code_hidden: this.state.code_hidden,
forbid_copy: this.state.forbid_copy,
hide_code: this.state.hide_code,
multi_webssh: this.state.multi_webssh,
task_pass: this.state.task_pass,
test_set_permission: this.state.test_set_permission,
vnc: this.state.vnc,
webssh: this.state.websshshow === false ? 0 : this.state.opensshRadio,
},
is_secret_repository: this.state.is_secret_repository
}
).then((response) => {
if (response.data.status === -1) {
} else {
this.props.getdatas()
this.props.showNotification("学习页面设置保存成功!")
this.setState({
loading: false
})
}
}).catch((error) => {
this.setState({
loading: false
})
})
}
Checkvnc = () => {
console.log(this.state.vnc)
if (this.state.vnc === false) {
this.setState({
hide_code: false,
is_secret_repository: false,
code_hidden: false,
forbid_copy: false,
multi_webssh: false,
websshshow: false,
})
}
this.setState({
vnc: !this.state.vnc
})
}
Checkhide_code = () => {
if (this.state.hide_code === false) {
this.setState({
is_secret_repository: false
})
}
this.setState({
hide_code: !this.state.hide_code
})
}
Checkis_secret_repository = () => {
this.setState({
is_secret_repository: !this.state.is_secret_repository
})
}
Checkcode_hidden = () => {
this.setState({
code_hidden: !this.state.code_hidden
})
}
Checkforbid_copy = () => {
this.setState({
forbid_copy: !this.state.forbid_copy
})
}
Checktask_pass = () => {
this.setState({
task_pass: !this.state.task_pass
})
}
Checktest_set_permission = () => {
this.setState({
test_set_permission: !this.state.test_set_permission
})
}
Checkwebsshshow = () => {
if (this.state.websshshow === false) {
this.setState({
vnc: false,
opensshRadio: 1
})
} else {
this.setState({
multi_webssh: false,
opensshRadio: null
})
}
this.setState({
websshshow: !this.state.websshshow
})
}
Checkmulti_webssh = () => {
this.setState({
multi_webssh: !this.state.multi_webssh
})
}
opensshRadio = (e) => {
if (e.target.value === 1) {
this.setState({
multi_webssh: false
})
} else {
this.setState({
multi_webssh: true
})
}
this.setState({
opensshRadio: e.target.value
});
}
render() {
// console.log(this.props)
return (
<div className="educontent mb50 edu-back-white padding10-20">
1111
<div>
<div className="educontent mb200 edu-back-white padding10-20 pdb30 mb50">
{this.state.websshshow === true ? "" : <div className="clearfix mb20">
<span className="color-grey-6 mt5 fl font-16" style={{minWidth: '45px'}}>开启图形化界面</span>
<span className="fl mt8">
<Checkbox
checked={this.state.vnc}
onChange={this.Checkvnc}></Checkbox>
<label style={{top: '6px'}} className="color-grey-9 ml10">选中则给学员的实践任务启动Ubuntu系统的图形化界面</label>
</span>
</div>}
{this.state.vnc === true ? "" : <div className="clearfix mb20">
<span className="color-grey-6 mt5 fl font-16 ml64" style={{minWidth: '45px'}}>命令行</span>
<span className="fl mt8">
<Checkbox
checked={this.state.websshshow}
onChange={this.Checkwebsshshow}></Checkbox>
<label style={{top: '6px'}} className="color-grey-9 ml10">选中则给学员的实践任务提供命令行窗口</label>
</span>
</div>}
{this.state.vnc === true ? "" : this.state.websshshow === true ? <div>
<span className="fl ml160">
<RadioGroup onChange={this.opensshRadio} value={this.state.opensshRadio}>
<Radio className="radioStyle font-14" value={1}><span>命令行练习窗口</span> <span
className="color-grey-9">选中则给学员提供用于练习操作的命令行命令行的操作不会对学生的实验环境造成影响</span></Radio>
<Radio className="radioStyle font-14" value={2}><span>命令行评测窗口</span> <span
className="color-grey-9">选中则给学员提供用于评测操作的命令行命令行的操作可以对学生的实验环境产生影响</span></Radio>
</RadioGroup>
</span>
{this.state.opensshRadio === 2 ? <span className="fl ml180">
<div className="clearfix mb20">
<span className="fl mt8 ml5">
<Checkbox
checked={this.state.multi_webssh}
onChange={this.Checkmulti_webssh}></Checkbox>
<label style={{top: '6px'}} className="color-grey-9 ml10">
<span className="color-grey-6 font-14" style={{minWidth: '45px'}}>多个命令行窗口</span>
选中则允许学员同时开启多个命令行窗口</label>
</span>
</div>
</span> : ""}
</div> : ""}
{this.state.vnc === true ? "" : <div className="clearfix mb20">
<span className="color-grey-6 mt5 fl font-16 ml17" style={{minWidth: '45px'}}>隐藏代码窗口</span>
<span className="fl mt8">
<Checkbox
checked={this.state.hide_code}
onChange={this.Checkhide_code}></Checkbox>
<label style={{top: '6px'}} className="color-grey-9 ml10">选中则学员页面不显示代码窗口</label>
</span>
</div>}
{this.state.vnc === true || this.state.hide_code === true ? "" : <div className="clearfix mb20">
<span className="color-grey-6 mt5 fl font-16 ml33" style={{minWidth: '45px'}}>公开版本库</span>
<span className="fl mt8">
<Checkbox
checked={this.state.is_secret_repository}
onChange={this.Checkis_secret_repository}></Checkbox>
<label style={{top: '6px'}} className="color-grey-9 ml10">选中则允许学员修改版本库中的全部文件</label>
</span>
</div>}
{this.state.vnc === true ? "" : <div className="clearfix mb20">
<span className="color-grey-6 mt5 fl font-16 ml17" style={{minWidth: '45px'}}>隐藏代码目录</span>
<span className="fl mt8">
<Checkbox
checked={this.state.code_hidden}
onChange={this.Checkcode_hidden}></Checkbox>
<label style={{top: '6px'}} className="color-grey-9 ml10">选中则学员页面不显示版本库目录</label>
</span>
</div>}
{this.state.vnc === true ? "" : <div className="clearfix mb20">
<span className="color-grey-6 mt5 fl font-16 ml17" style={{minWidth: '45px'}}>禁用复制粘贴</span>
<span className="fl mt8">
<Checkbox
checked={this.state.forbid_copy}
onChange={this.Checkforbid_copy}></Checkbox>
<label style={{top: '6px'}} className="color-grey-9 ml10">选中则学员页面不允许使用复制和粘贴功能</label>
</span>
</div>}
<div className="clearfix mb20">
<span className="color-grey-6 mt5 fl font-16 ml80" style={{minWidth: '45px'}}>跳关</span>
<span className="fl mt8">
<Checkbox
checked={this.state.task_pass}
onChange={this.Checktask_pass}></Checkbox>
<label style={{top: '6px'}} className="color-grey-9 ml10">选中则允许学员跳关学习实训关卡任务</label>
</span>
</div>
<div className="clearfix mb20">
<span className="color-grey-6 mt5 fl font-16 ml32s" style={{minWidth: '45px'}}>测试集解锁</span>
<span className="fl mt8">
<Checkbox
checked={this.state.test_set_permission}
onChange={this.Checktest_set_permission}></Checkbox>
<label style={{top: '6px'}} className="color-grey-9 ml10">选中则允许学员允许学员通过金币解锁查看隐藏测试集的内容</label>
</span>
</div>
</div>
{this.props.identity < 5 ?
<Bottomsubmit {...this.props} {...this.state} url={`/shixuns/${this.props.match.params.shixunId}/challenges`}
onSubmits={this.onSubmits} loadings={this.state.loading}/> : ""}
</div>
);
}

@ -2,9 +2,12 @@ import React, {Component} from 'react';
import {
Button,
Tabs
Tabs,
Modal
} from 'antd';
import './css/TPMsettings.css';
import TopShixuninformation from './Shixuninformation';
import Configuration from './Configuration';
@ -13,8 +16,6 @@ import LearningSettings from './LearningSettings';
import axios from 'axios';
import './css/TPMsettings.css';
const {TabPane} = Tabs;
// 处理整点 半点
@ -24,70 +25,125 @@ export default class TPMsettings extends Component {
constructor(props) {
super(props)
this.state = {
activeKeys:"1"
}
}
componentDidMount() {
this.getdatas("1")
}
let id = this.props.match.params.shixunId;
getdatas = (key) => {
let id = this.props.match.params.shixunId;
let Url = `/shixuns/` + id + `/settings.json`;
axios.get(Url).then((response) => {
// alert(response.data.shixun.choice_standard_scripts)
if (response.status === 200) {
this.setState({
data:response.data
})
if (response.data.shixun.multi_webssh === true) {
this.setState({
SelectTheCommandtype: true
})
} else {
this.setState({
SelectTheCommandtype: false
})
}
if (response.data.shixun.scope_partment.length > 0) {
this.setState({
scopetype: true
})
if(response.data){
if (response.data.shixun&&response.data.shixun.scope_partment.length > 0) {
this.setState({
scopetype: true
})
}
}
this.setState({
data: response.data
})
}
});
if(key==="3"&&this.props.shixunsDetails&&this.props.shixunsDetails.is_jupyter === true){
this.props.history.replace(`/shixuns/${this.props.match.params.shixunId}/challenges`);
}else{
if(key){
this.setState({
activeKeys:key
})
}else{
this.props.history.replace(`/shixuns/${this.props.match.params.shixunId}/challenges`);
}
let departmentsUrl = `/shixuns/departments.json`;
axios.get(departmentsUrl).then((response) => {
if (response.status === 200) {
if (response.data.message === undefined) {
this.setState({
departmentslist: response.data.shools_name
});
}
}
}
operateshixuns = (value) => {
this.setState({
operateshixunstype: true,
delType: value
})
}
hideoperateshixuns = () => {
this.setState({
operateshixunstype: false
})
}
shixunsdel = () => {
let id = this.props.match.params.shixunId;
let cul = `/shixuns/` + id + `.json`;
axios.delete(cul).then((response) => {
if (response.data.status === 1) {
this.props.showSnackbar("操作成功");
this.setState({
operateshixunstype: false,
});
// window.location.href = "/shixuns";
this.props.history.replace( "/shixuns/");
}
}).catch((error) => {
console.log(error)
});
})
}
shixunsclose = () => {
let id = this.props.match.params.shixunId;
let cul = `/shixuns/` + id + `/close.json`;
axios.post(cul).then((response) => {
if (response.data.status === 1) {
this.props.showSnackbar("操作成功");
this.setState({
operateshixunstype: false,
});
// window.location.href = "/shixuns/" + id + "/challenges";
this.props.history.replace( "/shixuns/" + id + "/challenges");
}
}).catch((error) => {
console.log(error)
})
}
callback = (key) => {
this.setState({
activeKeys:key
})
}
render() {
let showtabs = this.props.shixunsDetails === undefined ? "" : this.props.shixunsDetails&&this.props.shixunsDetails.is_jupyter === true ? "" : "学习页面设置"
render() {
// let a="isvnc";
// let b="isVNC";
// console.log(a.indexOf("vnc"))
// console.log(b.indexOf("vnc"))
// console.log( this.props.shixunsDetails === undefined ? "" : this.props.shixunsDetails.is_jupyter === false ? "学习页面设置" : "")
return (
<div>
<style>
{
`
<div>
<style>
{
`
.ant-tabs-bar{
width: 1200px;
margin: 0 auto;
@ -95,39 +151,81 @@ export default class TPMsettings extends Component {
border-bottom: 0px;
margin-top: 30px!important;
}
.ant-tabs-nav{
margin-left:20px;
}
.ant-tabs-nav .ant-tabs-tab {
font-size: 16px;
}
`
}
</style>
<Tabs activeKey={this.state.activeKeys} onChange={this.callback} animated={false} tabBarExtraContent={
<div className={"mb20 mr20"}>
{
this.props.identity < 5 && this.state.data && this.state.data.shixun.status == 0 ?
<Button type="primary" ghost className={"Permanentban mr20"} onClick={() => this.operateshixuns(1)}>
删除实训
</Button>
: ""
}
</style>
<Tabs tabBarExtraContent={
<div className={"mb20 mr20"}>
<Button type="primary" ghost className={"Permanentban"}>
永久关闭
</Button>
<Button type="primary" ghost className={"Permanentban ml20"}>
删除实训
</Button>
</div>
}>
<TabPane tab="基本信息" key="1">
<TopShixuninformation
{...this.state}
{...this.props}
/>
</TabPane>
<TabPane tab="权限配置" key="2">
<Configuration
{...this.state}
{...this.props}
/>
</TabPane>
<TabPane tab="学习页面设置" key="3">
{
this.props.identity == 1 && this.state.data && this.state.data.shixun.status == 2 ?
<Button type="primary" ghost className={"Permanentban mr20"} onClick={() => this.operateshixuns(1)}>
删除实训
</Button> : ""
}
{
this.props.identity === 1 && this.state.data && this.state.data.shixun.status == 2 ?
<Button type="primary" ghost className={"Permanentban"} onClick={() => this.operateshixuns(2)}>
永久关闭
</Button> : ""
}
</div>
}>
<TabPane tab="基本信息" key="1">
<TopShixuninformation
{...this.state}
{...this.props}
getdatas={(key) => this.getdatas(key)}
/>
</TabPane>
<TabPane tab="权限配置" key="2">
<Configuration
{...this.state}
{...this.props}
getdatas={(key) => this.getdatas(key)}
/>
</TabPane>
{this.props.shixunsDetails === undefined ? "" : this.props.shixunsDetails&&this.props.shixunsDetails.is_jupyter === false ?
<TabPane tab={showtabs} key="3">
<LearningSettings
{...this.state}
{...this.props}
getdatas={(key) => this.getdatas(key)}
/>
</TabPane>
</Tabs>
</div>
</TabPane>:"" }
</Tabs>
<Modal
keyboard={false}
title="提示"
visible={this.state.operateshixunstype}
closable={false}
footer={null}
>
<div className="task-popup-content">
{this.state.delType === 1 ? <p className="task-popup-text-center font-16 pb20">是否确认删除 </p> :
<p className="task-popup-text-center font-16 pb20">关闭后,<br/>用户不能再开始挑战了是否确认关闭 </p>}
</div>
<div className="task-popup-submit clearfix">
<a onClick={this.hideoperateshixuns} className="task-btn fl">取消</a>
{this.state.delType === 1 ? <a className="task-btn task-btn-orange fr" onClick={this.shixunsdel}>确定</a> :
<a className="task-btn task-btn-orange fr" onClick={this.shixunsclose}>确定</a>}
</div>
</Modal>
</div>
);
}

@ -125,4 +125,29 @@ a.newuse_scope-btn {
.ant-tabs-extra-content{
margin-top: 18px;
}
.pdb30{
padding-bottom: 30px;
}
.openrenyuan{
margin-top: 5px !important;
display: inline-block;
}
.ml81{
margin-left:81px;
}
.ml32s{
margin-left: 32px;
}
.ml64{
margin-left: 64px;
}
.ml160{
margin-left: 160px;
}

File diff suppressed because it is too large Load Diff

@ -5,9 +5,9 @@ import { BrowserRouter as Router, Route, Link } from "react-router-dom";
class TPMNav extends Component {
render() {
console.log("componentDidMount");
console.log("TPMNavTPMNavTPMNavTPMNav");
console.log(this.props);
// console.log("componentDidMount");
// console.log("TPMNavTPMNavTPMNavTPMNav");
// console.log(this.props);
const { user, match, shixun, secret_repository,is_jupyter} = this.props;
let isAdminOrCreator = false;

@ -4,46 +4,165 @@
* @Github:
* @Date: 2019-12-11 08:35:23
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-12 09:26:17
* @LastEditTime: 2019-12-12 20:19:48
*/
import './index.scss';
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import SplitPane from 'react-split-pane';
import { Button } from 'antd';
import { Button, Modal } from 'antd';
import {
connect
} from 'react-redux';
import UserInfo from '../../developer/components/userInfo';
import actions from '../../../redux/actions';
import LeftPane from './leftPane';
import RightPane from './rightPane';
function JupyterTPI (props) {
// 获取 identifier 值
const {
match: {
params = {}
},
url,
loading, // 保存按钮状态
dataSets, // 数据集
jupyter_info,
getJupyterInfo,
syncJupyterCode,
jupyter_tpi_url_state,
// getJupyterTpiDataSet,
getJupyterTpiUrl,
saveJupyterTpi,
changeLoadingState,
changeGetJupyterUrlState
} = props;
const {identifier} = params;
const [userInfo, setUserInfo] = useState({});
const [jupyterInfo, setJupyterInfo] = useState({});
const [updateTip, setUpdateTip] = useState(true);
const [myIdentifier, setMyIdentifier] = useState('');
useEffect(() => {
// 获取数据集
/* jupyter TPI
* 获取 用户信息,
* 实训的 identifier, 状态 名称 是否被修改等信息
*/
getJupyterInfo(identifier);
}, [identifier]);
useEffect(() => {
// 设置jupyter信息
setJupyterInfo(jupyter_info || {});
const {user, tpm_modified, myshixun_identifier} = jupyter_info;
if (user) {
setUserInfo(user);
}
if (myshixun_identifier) {
setMyIdentifier(myshixun_identifier);
}
// 同步代码
if (tpm_modified && updateTip && myshixun_identifier) {
setUpdateTip(false);
Modal.confirm({
title: '更新通知',
content: (<div className="update_notice">
<p className="update_txt">关卡任务的代码文件有更新啦</p>
<p className="update_txt">更新操作将保留已完成的评测记录和成绩</p>
<p className="update_txt">还未完成评测的任务代码请自行保存</p>
</div>),
okText: '确定',
cancelText: '取消',
onOk () {
syncJupyterCode(myshixun_identifier, '同步成功');
}
})
}
}, [props]);
// 重置实训
const handleClickResetTpi = () => {
Modal.confirm({
title: '重置实训',
content: (
<p style={{ lineHeight: '24px' }}>
你在本文件中修改的内容将丢失,<br />
是否确定重新加载初始代码
</p>
),
okText: '确定',
cancelText: '取消',
onOk () {
console.log('调用重置代码....', myIdentifier);
if (myIdentifier) {
syncJupyterCode(myIdentifier, '重置成功');
}
}
})
}
// 退出实训
const handleClickQuitTpi = () => {
// console.log(jupyterInfo);
const { identifier } = jupyterInfo;
props.history.push(`/shixuns/${identifier}/challenges`);
}
// 重新获取 jupyter url
const handleOnReloadUrl = (id) => {
// console.log('jupyter 信息: ', jupyterInfo);
// 改变加载状态值
changeGetJupyterUrlState(-1);
getJupyterTpiUrl({identifier: myIdentifier});
}
// 保存代码
const handleOnSave = () => {
// 改变按钮状态
changeLoadingState(true);
saveJupyterTpi();
}
// 获取jupyter地址
}, []);
return (
<div className="jupyter_area">
<div className="jupyter_header">
<UserInfo userInfo={{}} />
<UserInfo userInfo={userInfo} />
<p className="jupyter_title">
<span className="title_desc">MySQL数据库编程开发实训(基础篇)</span>
<span className="title_time">时间</span>
<span className="title_desc" style={{ marginTop: '20px' }}>{jupyterInfo.name}</span>
<span className="title_time"></span>
</p>
<p className="jupyter_btn">
{/* sync | poweroff */}
<Button className="btn_common" type="link" icon="sync">重置实训</Button>
<Button className="btn_common" type="link" icon="poweroff">退出实训</Button>
<Button
className="btn_common"
type="link"
icon="sync"
onClick={handleClickResetTpi}
>重置实训</Button>
<Button
className="btn_common"
type="link"
icon="poweroff"
onClick={handleClickQuitTpi}
>退出实训</Button>
</p>
</div>
<div className="jupyter_ctx">
<SplitPane split="vertical" minSize={350} maxSize={-350} defaultSize="30%">
<div className={'split-pane-left'}>
左侧内容
<LeftPane dataSets={dataSets} />
</div>
<SplitPane split="vertical" defaultSize="100%" allowResize={false}>
<div>右侧内容</div>
<RightPane
identifier={myIdentifier}
status={jupyter_tpi_url_state}
url={url}
loading={loading}
onReloadUrl={handleOnReloadUrl}
onSave={handleOnSave}
/>
<div />
</SplitPane>
</SplitPane>
@ -53,17 +172,31 @@ function JupyterTPI (props) {
}
const mapStateToProps = (state) => {
const {jupyter_tpi_url, jupyter_data_set, jupyter_identifier} = state.jupyterReducer;
const {
jupyter_info,
jupyter_tpi_url,
jupyter_data_set,
jupyter_tpi_url_state
} = state.jupyterReducer;
const { loading } = state.commonReducer;
return {
loading,
jupyter_info,
url: jupyter_tpi_url,
dataSets: jupyter_data_set,
identifier: jupyter_identifier
jupyter_tpi_url_state
};
}
const mapDispatchToProps = (dispatch) => ({
getJupyterTpiDataSet: (identifier) => dispatch(actions.getJupyterTpiDataSet(identifier)),
getJupyterTpiUrl: (identifier) => dispatch(actions.getJupyterTpiUrl(identifier))
changeGetJupyterUrlState: (status) => dispatch(actions.changeGetJupyterUrlState(status)),
getJupyterInfo: (identifier) => dispatch(actions.getJupyterInfo(identifier)),
// 重置代码
syncJupyterCode: (identifier, msg) => dispatch(actions.syncJupyterCode(identifier, msg)),
// getJupyterTpiDataSet: (identifier) => dispatch(actions.getJupyterTpiDataSet(identifier)),
getJupyterTpiUrl: (identifier) => dispatch(actions.getJupyterTpiUrl(identifier)),
saveJupyterTpi: () => dispatch(actions.saveJupyterTpi()),
changeLoadingState: (flag) => dispatch(actions.changeLoadingState(flag))
});
export default connect(

@ -55,7 +55,7 @@
height: 60px;
line-height: 60px;
background-color: #070F1A;
padding-left: 30px;
.jupyter_title{
display: flex;
flex-direction: column;
@ -78,9 +78,13 @@
right: 10px;
top: 14px;
.btn_common{
color: #888;
}
.btn_common:hover{
// background-color: #29BD8B;
color: #29BD8B;
// color: #29BD8B;
color: #1890ff;
}
}
}
@ -90,4 +94,12 @@
position: relative;
height: calc(100vh - 60px);
}
.update_notice{
text-align: center;
.update_txt{
line-height: 18px;
font-size: 14px;
}
}
}

@ -0,0 +1,84 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-12-12 10:34:03
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-12 20:18:46
*/
import './index.scss';
import React, { useState, useEffect } from 'react';
import {Icon, Empty} from 'antd';
import MyIcon from '../../../../common/components/MyIcon';
function LeftPane (props) {
// 获取数据集
const { dataSets = [] } = props;
const emptyCtx = (
<div className="jupyter_empty">
<Empty />
</div>
);
// const listCtx = ;
const [renderCtx, setRenderCtx] = useState(() => (emptyCtx));
useEffect(() => {
if (dataSets.length > 0) {
console.log('数据集的个数: ', dataSets.length);
const oList = dataSets.map((item, i) => {
return (
<li className="jupyter_item" key={`key_${i}`}>
<Icon type="file-text" className="jupyter_icon"/>
<span className="jupyter_name">{item.title}</span>
</li>
);
});
const oUl = (
<ul className="jupyter_data_list">
{ oList }
</ul>
);
setRenderCtx(oUl);
}
}, [props]);
// 渲染数据集
// const renderList = () => {
// // 空数据
// if (dataSets.length === 0) {
// return <div className="jupyter_empty">
// <Empty />
// </div>
// } else {
// // 渲染列表
// const oList = dataSets.map((item, i) => {
// return (
// <li className="jupyter_item" key={`key_${i}`}>
// <Icon type="file-text" className="jupyter_icon"/>
// <span className="jupyter_name">{item.title}</span>
// </li>
// );
// });
// return (
// <ul className="jupyter_data_list">
// { oList }
// </ul>
// );
// }
// }
return (
<div className="jupyter_data_sets_area">
<h2 className="jupyter_h2_title">
<MyIcon type="iconwenti" className="jupyter_data_icon"/> 数据集
</h2>
{ renderCtx }
</div>
)
}
export default LeftPane;

@ -0,0 +1,57 @@
.jupyter_data_sets_area{
height: 100%;
.jupyter_h2_title{
height: 44px;
line-height: 44px;
background-color: #EEEEEE;
padding: 0 30px;
.jupyter_data_icon{
// color: #7286ff;
color: #1890ff;
font-size: 24px;
position: relative;
top: 2px;
transform: scale(1.5);
margin-right: 5px;
}
}
.jupyter_data_list,
.jupyter_empty{
height: calc(100vh - 104px);
overflow-y: auto;
}
.jupyter_data_list{
.jupyter_item{
line-height:45px;
border-bottom: 1px solid rgba(238,238,238, 1);
padding: 0 30px 0 60px;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
cursor: pointer;
transition: .3s;
&:hover{
background-color: rgba(235, 235, 235, .3);
}
.jupyter_icon{
color: rgb(74, 188, 125);
font-size: 16px;
transform: scale(1.2);
margin-right: 5px;
}
.jupyter_name{
color: #000;
font-size: 16px;
}
}
}
.jupyter_empty{
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
}

@ -0,0 +1,91 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-12-12 15:04:20
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-13 11:25:22
*/
import './index.scss';
import React, { useEffect, useState } from 'react';
import { Spin, Button } from 'antd';
function RightPane (props) {
const {
status,
url,
onReloadUrl,
onSave,
loading
} = props;
const [renderCtx, setRenderCtx] = useState(() => loadInit);
// 重新获取 url
const handleClickReload = () => {
onReloadUrl && onReloadUrl();
}
const loadInit = (
<div className="jupyter_loading_init">
<Spin tip="加载中..."></Spin>
</div>
);
const loadError = (
<div className="jupyter_load_url_error">
<span className="iconfont icon-jiazaishibai1 icon-error"></span>
<p className="jupyter_error_txt">
实训加载失败
<span
className="jupyter_reload"
onClick={handleClickReload}
>重新加载</span>
</p>
</div>
);
// 保存
const handleClickSubmit = () => {
console.log('调用了保存接口....');
onSave && onSave();
}
useEffect(() => {
if (status === -1) {
setRenderCtx(() => loadInit);
} else if (status === 0 && url) {
setRenderCtx(() => (
<div className="jupyter_result">
<div className="jupyter_iframe">
<iframe
title=" "
width="100%"
height="100%"
src={url}
className='jupyter_iframe_style'
></iframe>
</div>
<div className="jupyter_submit">
<Button
loading={loading}
type="primary"
onClick={handleClickSubmit}
>保存</Button>
</div>
</div>
));
} else {
setRenderCtx(() => loadError);
}
}, [status, url, loading]);
return (
<div className="jupyter_right_pane_area">
{ renderCtx }
</div>
)
}
export default RightPane;

@ -0,0 +1,74 @@
.jupyter_right_pane_area{
position: relative;
height: calc(100vh - 60px);
// background: pink;
.jupyter_load_url_error,
.jupyter_loading_init{
display: flex;
position: relative;
align-items: center;
justify-content: center;
height: 100%;
&::before{
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
content: '';
}
}
.jupyter_loading_init{
&::before{
background-color: rgba(0,0,0,.2);
}
}
.jupyter_load_url_error{
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
// &::before{
// background-color: rgba(0,0,0,.2);
// }
.jupyter_error_txt{
position: relative;
z-index: 1;
font-size: 12px;
.jupyter_reload{
cursor: pointer;
color: #1890ff;
}
}
.icon-error{
position: relative;
color: #DCE0E6;
transform: scale(5);
top: -35px;
}
}
.jupyter_result{
height: 100%;
.jupyter_iframe{
height: calc(100% - 56px);
// background: pink;
.jupyter_iframe_style{
border: none;
outline: none;
}
}
.jupyter_submit{
display: flex;
align-items: center;
height: 56px;
justify-content: flex-end;
padding-right: 30px;
}
}
}

@ -4,7 +4,7 @@ import {TPMIndexHOC} from '../TPMIndexHOC';
import {SnackbarHOC} from 'educoder';
import {Select, Radio, Input, Modal, Button, Form, Tooltip, Upload, Icon,notification} from 'antd';
import {Select, Radio, Input, Modal, Button, Form, Tooltip, Upload, Icon, notification} from 'antd';
import axios from 'axios';
@ -32,6 +32,7 @@ class Newshixuns extends Component {
run_method: undefined,
postapplyvisible: undefined,
fileList: [],
Radiovalue:"1"
}
}
@ -85,9 +86,15 @@ class Newshixuns extends Component {
this.setState({
Radiovalue: e.target.value,
});
this.props.form.setFieldsValue({
is_jupyter: e.target.value,
});
};
handleSubmit = (e) => {
this.setState({
loading: true
})
const mdContnet = this.contentMdRef.current.getValue().trim();
this.props.form.validateFieldsAndScroll((err, values) => {
if (!err) {
@ -110,19 +117,21 @@ class Newshixuns extends Component {
// window.open("/shixuns/"+response.data.shixun_identifier+"/challenges");
} else {
this.setState({
bottonloading: false
loading: true
})
}
}).catch((error) => {
console.log(error)
this.setState({
bottonloading: false
loading: true
})
})
}
});
this.setState({
loading: false
})
};
Selectthestudent = (value) => {
this.props.form.setFieldsValue({
@ -145,7 +154,7 @@ class Newshixuns extends Component {
});
let newlist = ""
e.map((item, key) => {
if(item.props.name!=""){
if (item.props.name != "") {
newlist = newlist + `${item.props.name}`
}
})
@ -316,7 +325,7 @@ class Newshixuns extends Component {
// 附件相关 START
handleChange = (info) => {
if(info.file.status === 'uploading' || info.file.status === 'done' || info.file.status === 'removed') {
if (info.file.status === 'uploading' || info.file.status === 'done' || info.file.status === 'removed') {
let {fileList} = this.state;
if (info.file.status === 'uploading' || info.file.status === 'done' || info.file.status === 'removed') {
@ -334,7 +343,7 @@ class Newshixuns extends Component {
}
onAttachmentRemove = (file) => {
if(!file.percent || file.percent == 100){
if (!file.percent || file.percent == 100) {
Modal.confirm({
title: '确定要删除这个附件吗?',
okText: '确定',
@ -355,13 +364,12 @@ class Newshixuns extends Component {
deleteAttachment = (file) => {
console.log(file);
let id=file.response ==undefined ? file.id : file.response.id
let id = file.response == undefined ? file.id : file.response.id
const url = `/attachments/${id}.json`
axios.delete(url, {
})
axios.delete(url, {})
.then((response) => {
if (response.data) {
const { status } = response.data;
const {status} = response.data;
if (status == 0) {
// console.log('--- success')
@ -372,7 +380,7 @@ class Newshixuns extends Component {
newFileList.splice(index, 1);
return {
fileList: newFileList,
deleteisnot:true
deleteisnot: true
};
});
}
@ -461,7 +469,7 @@ class Newshixuns extends Component {
>
{getFieldDecorator('name', {
rules: [{
required: true, message: '请输入选题名称',
required: true, message: '请输入名称',
}, {
max: 60, message: '请输入名称最大限制60个字符',
}, {
@ -503,8 +511,8 @@ class Newshixuns extends Component {
>
<Option value={1}>初级</Option>
<Option value={2}>中级</Option>
<Option value={3}>高级</Option>
<Option value={4}></Option>
<Option value={3}>高级</Option>
<Option value={4}></Option>
</Select>
</div>
@ -531,16 +539,36 @@ class Newshixuns extends Component {
defaultOpen={false}
>
{
newshixunlist === undefined ? "" : newshixunlist.main_type.map((item, key) => {
return (
<Option value={item.id} key={key} name={item.description}>
<Tooltip placement="right"
title={item.description === "" ? "无描述" : item.description}>
{item.type_name}
</Tooltip>
</Option>
)
})
newshixunlist === undefined ? "" : this.state.Radiovalue==="2"?newshixunlist.main_type.map((item, key) => {
let itemtype=item.type_name.toLowerCase().indexOf('jupyter'.toLowerCase())
if(itemtype>-1){
return (
<Option value={item.id} key={key} name={item.description}>
<Tooltip placement="right"
title={item.description === "" ? "无描述" : item.description}>
{item.type_name}
</Tooltip>
</Option>
)
}
}):""
}
{
newshixunlist === undefined ? "" : this.state.Radiovalue==="1"?newshixunlist.main_type.map((item, key) => {
let itemtype=item.type_name.toLowerCase().indexOf('jupyter'.toLowerCase())
if(itemtype===-1){
return (
<Option value={item.id} key={key} name={item.description}>
<Tooltip placement="right"
title={item.description === "" ? "无描述" : item.description}>
{item.type_name}
</Tooltip>
</Option>
)
}
}):""
}
</Select>
@ -567,7 +595,7 @@ class Newshixuns extends Component {
<div className=" fl pr mr20">
<Select placeholder="请选择小类别"
mode="multiple"
style={{width: 180}}
style={{width: 280}}
onChange={this.sub_type}
defaultOpen={false}
className={"Selectlittle"}
@ -589,12 +617,12 @@ class Newshixuns extends Component {
)}
<span className="fl ml20 color-grey lineh-20">
<div>
{this.state.mainvalues === undefined && this.state.subvalues === undefined||this.state.mainvalues === "" && this.state.subvalues === "" ? "" :
<div className={"font-12"} style={{'max-width':'700px'}}>
{`${this.state.mainvalues===undefined||this.state.mainvalues=== ""?"":`已安装软件:`+this.state.mainvalues}`}
{`${this.state.subvalues===undefined||this.state.subvalues=== ""?"":this.state.mainvalues===undefined||this.state.mainvalues=== ""?`已安装软件:`+this.state.subvalues:this.state.subvalues}`}
{`${this.state.mainvalues===undefined||this.state.mainvalues=== ""?"":`说明:添加了`+this.state.mainvalues}${this.state.subvalues===undefined||this.state.subvalues=== ""?"":
this.state.mainvalues===undefined||this.state.mainvalues=== ""?`说明:添加了`+this.state.subvalues:this.state.subvalues}`}
{this.state.mainvalues === undefined && this.state.subvalues === undefined || this.state.mainvalues === "" && this.state.subvalues === "" ? "" :
<div className={"font-12"} style={{'max-width': '600px'}}>
{`${this.state.mainvalues === undefined || this.state.mainvalues === "" ? "" : `已安装软件:` + this.state.mainvalues}`}
{`${this.state.subvalues === undefined || this.state.subvalues === "" ? "" : this.state.mainvalues === undefined || this.state.mainvalues === "" ? `已安装软件:` + this.state.subvalues : this.state.subvalues}`}
{`${this.state.mainvalues === undefined || this.state.mainvalues === "" ? "" : `说明:添加了` + this.state.mainvalues}${this.state.subvalues === undefined || this.state.subvalues === "" ? "" :
this.state.mainvalues === undefined || this.state.mainvalues === "" ? `说明:添加了` + this.state.subvalues : this.state.subvalues}`}
</div>}
</div>
@ -704,7 +732,7 @@ class Newshixuns extends Component {
</div>
</div>
</div>
<Bottomsubmit url={"/shixuns"} onSubmits={() => this.handleSubmit()}/>
<Bottomsubmit {...this.props} {...this.state} url={"/shixuns"} onSubmits={() => this.handleSubmit()} loadings={this.state.loading}/>
</div>
);

@ -392,9 +392,9 @@ a.white-btn.use_scope-btn:hover{
border-color: #096dd9;
}
.ant-btn:hover, .ant-btn:focus, .ant-btn:active, .ant-btn.active{
background-color: #4CACFF;
}
/*.ant-btn:hover, .ant-btn:focus, .ant-btn:active, .ant-btn.active{*/
/* background-color: #4CACFF;*/
/*}*/
.newViewAfter .ant-input{
line-height: 40px !important;

@ -13,7 +13,7 @@ import { getImageUrl ,markdownToHTML, configShareForCustom} from 'educoder'
import { CircularProgress } from 'material-ui/Progress';
import { Modal, Spin, Tooltip ,message,Icon} from 'antd';
import LoadingSpin from '../../../../common/LoadingSpin';
import 'antd/lib/pagination/style/index.css';
import '../shixunchildCss/Challenges.css'
@ -34,6 +34,8 @@ class Challengesjupyter extends Component {
jupyter_port:0,
jupyter_url:null,
username:"",
booljupyterurls:false,
loading:false,
}
}
@ -54,102 +56,106 @@ class Challengesjupyter extends Component {
}
}
}).catch((error) => {
console.log(error)
//console.log(error)
});
}
componentDidMount() {
setTimeout(this.ChallengesList(), 1000);
let id = this.props.match.params.shixunId;
let ChallengesURL = `/jupyters/get_info_with_tpm.json`;
let datas={
identifier:id,
}
axios.get(ChallengesURL, {params: datas}).then((response) => {
debugger
if (response.data.status === 403||response.data.status === 401||response.data.status === 500) {
setTimeout(() => {
this.setState({
booljupyterurls:true,
})
}, 600)
}else{
// console.log("componentDidMountChallengesjupyter");
// console.log(response.data);
if(response.data.status===0){
this.setState({
jupyter_url:response.data.url,
jupyter_port:response.data.port,
})
setTimeout(() => {
this.setState({
jupyter_url:response.data.url,
jupyter_port:response.data.port,
booljupyterurls:true,
})
}, 800)
}else{
setTimeout(() => {
this.setState({
booljupyterurls:true,
})
}, 600)
}
}
}).catch((error) => {
console.log(error)
});
}
updatamakedown = (id) => {
}
// 关卡的上移下移操作
operations = (sumid, type) => {
}
delOperations = (sumid) => {
}
clonedelOperationss = () => {
}
delOperationss = () => {
}
startgameid=(id)=>{
}
hidestartshixunsreplace=(url)=>{
}
//编辑实训题目选择题
EditTraining=(type, ids, path)=>{
}).catch((error) => {
setTimeout(() => {
this.setState({
booljupyterurls:true,
})
}, 600)
});
}
//开始实战按钮
startshixunCombat = (type, ids, id) => {
updatamakedowns = () => {
this.setState({
loading:true,
booljupyterurls:false
})
let id = this.props.match.params.shixunId;
let ChallengesURL = `/jupyters/get_info_with_tpm.json`;
let datas={
identifier:id,
}
axios.get(ChallengesURL, {params: datas}).then((response) => {
if (response.data.status === 403||response.data.status === 401||response.data.status === 500) {
setTimeout(() => {
this.setState({
booljupyterurls:true,
})
}, 600)
}else{
if(response.data.status===0){
setTimeout(() => {
this.setState({
jupyter_url:response.data.url,
jupyter_port:response.data.port,
booljupyterurls:true,
})
}, 800)
this.setState({
})
}else{
setTimeout(() => {
this.setState({
booljupyterurls:true,
})
}, 600)
}
}
}).catch((error) => {
setTimeout(() => {
this.setState({
booljupyterurls:true,
})
}, 600)
}
hidestartshixunCombattype=()=>{
});
}
hideAccountProfile=()=>{
};
modifyjupyter=()=>{
// console.log("propsysl");
// console.log(propsysl);
let id=this.props.match.params.shixunId;
var jupyter_port="";
try{
@ -170,42 +176,13 @@ class Challengesjupyter extends Component {
}
}).catch((error) => {
})
}
sendToken=()=>{
// console.log("sendToken");
// const iframe = document.getElementById('iframe');
// console.log("modifyjupyter");
// const frameWindow = iframe.contentWindow;
// console.log("frameWindow");
// console.log(frameWindow);
}
render() {
let{ChallengesDataList}=this.state;
let{ChallengesDataList,booljupyterurls}=this.state;
let id = this.props.match.params.shixunId;
// var deptObjs=document.getElementById("IFRAMEID").contentWindow.document.getElementById("TAGID");
// //判断此元素是否存在
// if(deptObjs!=null){
// //设置该元素的样式或其他属性
// deptObjs.setAttribute('style',' height: 20px !important;'); //!important用来提升指定样式条目的应用优先权
// }
// var submitObj = document.getElementById('submit');
// if(submitObj){
// submitObj.style.color = 'green';
// }
// const dom = document.getElementById('shutdown');
// ReactDOM.unmountComponentAtNode(dom)
// // window.$('#picture_display').hide();
// window.$('.data-tip-right').hide()
// window.onload=()=>{
// debugger
// var _iframe = document.getElementById('header');
// console.log(_iframe);
// // .contentWindow.document.getElementById('shutdown_widget') //get iframe下的id
// // _iframe.style.display= "none"; //修改样式
// }
const is_teacher = this.props&&this.props.current_user&&this.props.current_user.is_teacher?this.props.current_user.is_teacher:false;
@ -231,6 +208,23 @@ class Challengesjupyter extends Component {
<div className={"markdown-body"} dangerouslySetInnerHTML={{__html: markdownToHTML(ChallengesDataList.description).replace(/▁/g,"▁▁▁")}}></div>
}
</p>
{
booljupyterurls===true?
(
this.state.jupyter_url === null?
<div className="mt50 intermediatecenter juplbool">
<span className="icon iconfontysl icon-jiazaishibai1"></span>
<p className="intermediatecenter sortinxdirection mt5 juplboolp"><p className="colorbluetest">加载实训出错是否</p><p className="colorbluetwo" onClick={()=>this.updatamakedowns()}></p></p>
</div>
:""
)
:""
}
<style>
{
`
@ -250,6 +244,8 @@ class Challengesjupyter extends Component {
display: flex;
flex-direction:row-reverse;
}
;
}
`
}
</style>
@ -262,7 +258,7 @@ class Challengesjupyter extends Component {
<div className="sortinxdirection mt60">
<div className="renwuxiangssi sortinxdirection">
<div><p className="renwuxiangqdiv">任务详情</p></div>
<div><p className="renwuxiangqdivtest ml24">请将实训题目写在下方</p></div>
<div><p className="renwuxiangqdivtest ml1 shixunbingbaocun">请将实训题目写在下方并保存</p></div>
</div>
<div className="renwuxiangssit xaxisreverseorder">
<div className="challenbaocun" onClick={() => this.modifyjupyter(this.state)}><p
@ -293,18 +289,24 @@ class Challengesjupyter extends Component {
#header{
visibility:hidden;
}
`
}
</style>
{
is_teacher===true?
<div className="mt35">
{/*https://48888.jupyter.educoder.net/tree?*/}
<div className="pb47">
{
this.state.jupyter_url===null || this.state.jupyter_url===undefined?
""
(
booljupyterurls===false?
<LoadingSpin></LoadingSpin>
:""
)
:
<iframe src={this.state.jupyter_url}
sandbox="allow-same-origin allow-scripts allow-top-navigation " scrolling="no" id="frame"

@ -31,6 +31,7 @@
height:30px;
background:#29BD8B;
border-radius:3px;
cursor:pointer
}
.challenbaocuntest{
width:103px;
@ -39,7 +40,7 @@
color:#FFFFFF;
text-align: center;
line-height:30px;
cursor:default
cursor:pointer
}
.renwuxiangqdiv{
width:72px;
@ -64,3 +65,41 @@
.pb47{
padding-bottom: 47px;
}
.colorbluetwo{
color: #1E8FFD;
font-size: 12px;
cursor:pointer;
margin-right: 18px;
}
.colorbluetest{
color: #06101A;
font-size: 12px;
cursor:default
}
.shixunbingbaocun{
font-size:14px;
color:#888888;
}
.intermediatecenter{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.iconfontysl{
vertical-align:middle;
font-family:"iconfont" !important;
font-style:normal;
-webkit-font-smoothing: antialiased;
-webkit-text-stroke-width: 0.2px;
font-size: 100px;
color: #F5F5F5;
}
.juplbool{
position: relative;
}
.juplboolp{
position: absolute;
bottom: 21px;
}

@ -275,10 +275,10 @@ render() {
}
<div className="fl pr shaiAllItem mt1">
<li className={this.state.diff===0?"shaiItems shixun_repertoire active":"shaiItems shixun_repertoire"} onClick={()=>this.diff_search(0)}>全部难度</li>
<li className={this.state.diff===1?"shaiItems shixun_repertoire active":"shaiItems shixun_repertoire"} onClick={()=>this.diff_search(1)}>初级学员</li>
<li className={this.state.diff===2?"shaiItems shixun_repertoire active":"shaiItems shixun_repertoire"} onClick={()=>this.diff_search(2)}>中级学员</li>
<li className={this.state.diff===3?"shaiItems shixun_repertoire active":"shaiItems shixun_repertoire"} onClick={()=>this.diff_search(3)}>高级学员</li>
<li className={this.state.diff===4?"shaiItems shixun_repertoire active":"shaiItems shixun_repertoire"} onClick={()=>this.diff_search(4)}>顶级学员</li>
<li className={this.state.diff===1?"shaiItems shixun_repertoire active":"shaiItems shixun_repertoire"} onClick={()=>this.diff_search(1)}>初级</li>
<li className={this.state.diff===2?"shaiItems shixun_repertoire active":"shaiItems shixun_repertoire"} onClick={()=>this.diff_search(2)}>中级</li>
<li className={this.state.diff===3?"shaiItems shixun_repertoire active":"shaiItems shixun_repertoire"} onClick={()=>this.diff_search(3)}>高级</li>
<li className={this.state.diff===4?"shaiItems shixun_repertoire active":"shaiItems shixun_repertoire"} onClick={()=>this.diff_search(4)}>高级</li>
</div>
</div>

@ -66,6 +66,8 @@
height:30px;
background:#C4C4C4;
border-radius:3px;
cursor:pointer
}
.deletebutomtext{
width:28px;
@ -73,12 +75,14 @@
font-size:14px;
color:#FFFFFF;
line-height:19px;
cursor:pointer
}
.deletebuttom{
width:85px;
height:30px;
background:#29BD8B;
border-radius:3px;
cursor:pointer
}
.deletebuttomtest{
width:56px;
@ -86,6 +90,8 @@
font-size:14px;
color:#FFFFFF;
line-height:19px;
cursor:pointer
}
.tpmwidth{
width: 50%;
@ -111,6 +117,8 @@
height:30px;
background:#FF5555;
border-radius:3px;
cursor:pointer
}
.light-row{
background: #F7F7F8;

@ -54,6 +54,9 @@ const types = {
GET_JUPYTER_DATA_SETS: 'GET_JUPYTER_DATA_SETS', // jupyter 数据集
GET_JUPYTER_TPI_URL: 'GET_JUPYTER_TPI_URL', // 获取 jupyter url
SAVE_JUPYTER_IDENTIFIER: 'SAVE_JUPYTER_IDENTIFIER', // 保存jupyter identifier
SAVE_JUPYTER_INFO: 'SAVE_JUPYTER_INFO', // 保存 jupyter 信息
CHANGE_JUPYTER_URL_STATE: 'CHANGE_JUPYTER_URL_STATE', // 获取url返回的状态值
SAVE_JUPYTER_TPI: 'SAVE_JUPYTER_TPI', // 保存 jupyter tpi
}
export default types;

@ -64,7 +64,11 @@ import {
import {
getJupyterTpiDataSet,
getJupyterTpiUrl
getJupyterTpiUrl,
getJupyterInfo,
syncJupyterCode,
changeGetJupyterUrlState,
saveJupyterTpi
} from './jupyter';
export default {
@ -111,6 +115,10 @@ export default {
saveEditorCodeForDetail,
// jupyter
getJupyterTpiDataSet,
getJupyterTpiUrl
getJupyterTpiUrl,
getJupyterInfo,
syncJupyterCode,
changeGetJupyterUrlState,
saveJupyterTpi
// isUpdateCodeCtx
}

@ -4,31 +4,81 @@
* @Github:
* @Date: 2019-12-12 09:01:30
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-12 09:30:53
* @LastEditTime: 2019-12-12 17:58:38
*/
import types from "./actionTypes";
import { fetchJupyterTpiDataSet, fetchJupyterTpiUrl } from "../../services/jupyterServer";
import { message } from 'antd';
import {
fetchJupyterTpiDataSet,
fetchJupyterTpiUrl,
fetchJupyterInfo,
fetchSyncJupyterCode,
fetchSaveJupyterTpi
} from "../../services/jupyterServer";
// 获取 jupyter 相关信息
export const getJupyterInfo = (id) => {
return (dispatch) => {
fetchJupyterInfo(id).then(res => {
if (res.data.status === 401) return;
if (res.status === 200) {
const { data } = res;
console.log(data);
if (data.status === 0) {
dispatch({
type: types.SAVE_JUPYTER_INFO,
payload: data
});
const { identifier, myshixun_identifier } = data;
// 调用获取数据集接口
dispatch(getJupyterTpiDataSet(identifier));
// 调用获取url接口
dispatch(getJupyterTpiUrl({identifier: myshixun_identifier}));
}
}
})
}
}
// 获取 jupyter tpi 数据集
export const getJupyterTpiDataSet = (identifier) => {
return (dispatch) => {
fetchJupyterTpiDataSet(identifier).then(res => {
if (res.data.status === 401) return; // 用户未登录
console.log('数据集:', res);
if (res.status === 200) {
const {data_sets} = res.data;
dispatch({
type: types.GET_JUPYTER_DATA_SETS,
payload: data_sets
});
}
});
}
}
// 获取 jupyter tpi 地址
export const getJupyterTpiUrl = (identifier) => {
return (dispatch) => {
fetchJupyterTpiUrl(identifier).then(res => {
export const getJupyterTpiUrl = (obj) => {
return (dispatch, getState) => {
const {jupyter_info} = getState().jupyterReducer;
console.log(obj.identifier, jupyter_info.myshixun_identifier);
if (!obj.identifier && !jupyter_info.myshixun_identifier) return;
const id = obj.identifier || jupyter_info.myshixun_identifier;
fetchJupyterTpiUrl({identifier: id}).then(res => {
if (res.data.status === 401) return; // 用户未登录
console.log('获取url', res);
if (res.status === 200) {
const { status, url = '', port } = res.data;
dispatch({
type: types.GET_JUPYTER_TPI_URL,
payload: {
status,
url,
port
}
})
}
})
}
}
// 保存 jupyter identifer
export const saveJupyterIdentifier = (identifier) => {
return {
@ -36,3 +86,54 @@ export const saveJupyterIdentifier = (identifier) => {
payload: identifier
}
}
// 重置代码
export const syncJupyterCode = (identifier, msg) => {
return (dispatch) => {
fetchSyncJupyterCode(identifier).then(res => {
// console.log('同步代码成功: ', res);
if (res.data.status === 401) return;
if (res.status === 200) {
const {status} = res.data
if (status === 0) message.success(msg);
}
})
}
}
// 改变状态值
export const changeGetJupyterUrlState = (status) => {
return {
type: types.CHANGE_JUPYTER_URL_STATE,
payload: status
}
}
// 保存 jupyter tpi
export const saveJupyterTpi = () => {
return (dispatch, getState) => {
const { jupyter_tpi_code, jupyter_info }= getState().jupyterReducer;
// console.log(jupyter_info.myshixun_identifier, jupyter_tpi_code);
if (!jupyter_info.myshixun_identifier) return;
const params = {
identifier: jupyter_info.myshixun_identifier,
jupyter_port: jupyter_tpi_code
};
console.log(params);
fetchSaveJupyterTpi(params).then(res => {
dispatch({
type: types.LOADING_STATUS,
payload: false
});
if (res.status === 200) {
const { data } = res;
if (data.status === 0) {
message.success('保存成功!')
}
}
}).catch(() => {
dispatch({
type: types.LOADING_STATUS,
payload: false
});
});
}
}

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-27 16:27:09
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-03 11:00:19
* @LastEditTime: 2019-12-12 17:36:51
*/
import types from "../actions/actionTypes";

@ -4,14 +4,17 @@
* @Github:
* @Date: 2019-12-12 09:01:39
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-12 09:29:49
* @LastEditTime: 2019-12-12 17:23:54
*/
import types from "../actions/actionTypes";
const initState = {
jupyter_tpi_url: '',
jupyter_info: {}, // 保存用户信息及实训相关的内容
jupyter_data_set: [],
jupyter_identifier: ''
jupyter_identifier: '',
jupyter_tpi_url_state: -1, // 获取 url 状态值: 0 成功, 其它 失败
jupyter_tpi_code: '' // 端口号
};
const JupyterReducer = (state = initState, action) => {
@ -22,15 +25,28 @@ const JupyterReducer = (state = initState, action) => {
jupyter_data_set: action.payload
}
case types.GET_JUPYTER_TPI_URL:
const {url, status, port} = action.payload;
return {
...state,
jupyter_tpi_url: action.payload
jupyter_tpi_url: url,
jupyter_tpi_url_state: status,
jupyter_tpi_code: port
}
case types.SAVE_JUPYTER_IDENTIFIER:
return {
...state,
jupyter_identifier: action.payload
}
case types.SAVE_JUPYTER_INFO:
return {
...state,
jupyter_info: action.payload
}
case types.CHANGE_JUPYTER_URL_STATE:
return {
...state,
jupyter_tpi_url_state: action.payload
}
default:
return {
...state

@ -215,7 +215,7 @@ class SearchPage extends Component{
type==="shixun"?
(
item.is_jupyter===true?
<div className="jupytertext intermediatecenter ml15"><p className="jupytertextp">Jupyter</p></div>
<div className="jupytertext intermediatecenter ml20"><p className="jupytertextp">Jupyter</p></div>
:""
)
:""

@ -135,17 +135,18 @@
.jupytertext{
width:54px;
height:24px;
background:#FF6802;
border-radius:2px 10px 10px 2px;
margin-top: 2px;
text-align: center;
border-radius:5px;
border:1px solid #FF6802;
margin-top: 4px;
}
.jupytertextp{
width:39px;
width:54px;
height:16px;
line-height:16px;
font-size:12px;
color:#FFFFFF;
color:#FF6802;
line-height:16px;
}
/* x轴正方向排序 */

@ -4,10 +4,15 @@
* @Github:
* @Date: 2019-12-12 09:07:07
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-12 09:10:58
* @LastEditTime: 2019-12-12 17:46:17
*/
import axios from 'axios';
// 获取 jupyter实训相关的内容
export async function fetchJupyterInfo (identifier) {
const url = `/tasks/${identifier}/jupyter.json`;
return axios.get(url);
}
// 获取数据集
export async function fetchJupyterTpiDataSet (identifier) {
const url = `/shixuns/${identifier}/jupyter_data_sets.json`;
@ -18,3 +23,13 @@ export async function fetchJupyterTpiUrl (params) {
const url = `/jupyters/get_info_with_tpi.json`;
return axios.get(url, { params });
}
// 同步代码功能
export async function fetchSyncJupyterCode (identifier) {
const url = `/myshixuns/${identifier}/sync_code.json`;
return axios.post(url);
}
// jupyter 保存
export async function fetchSaveJupyterTpi (params) {
const url = `/jupyters/save_with_tpi.json`;
return axios.get(url, { params });
}

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save