Merge branches 'dev_aliyun' and 'dev_jupyter' of https://bdgit.educoder.net/Hjqreturn/educoder into dev_jupyter

chromesetting
杨树明 5 years ago
commit d341889aae

11
.gitignore vendored

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

@ -283,7 +283,7 @@ class ApplicationController < ActionController::Base
# 测试版前端需求
logger.info("subdomain:#{request.subdomain}")
if request.subdomain == "test-newweb"
if request.subdomain != "www"
if params[:debug] == 'teacher' #todo 为了测试,记得讲debug删除
User.current = User.find 81403
elsif params[:debug] == 'student'

@ -57,7 +57,6 @@ class AttachmentsController < ApplicationController
@attachment = Attachment.where(disk_filename: disk_filename,
author_id: current_user.id,
cloud_url: remote_path).first
if @attachment.blank?
@attachment = Attachment.new
@attachment.filename = upload_file.original_filename
@ -68,6 +67,7 @@ class AttachmentsController < ApplicationController
@attachment.author_id = current_user.id
@attachment.disk_directory = month_folder
@attachment.cloud_url = remote_path
@attachment.disk_directory = save_path
@attachment.save!
else
logger.info "文件已存在id = #{@attachment.id}, filename = #{@attachment.filename}"
@ -196,4 +196,5 @@ class AttachmentsController < ApplicationController
end
end
end
end

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

@ -1,15 +1,18 @@
class GamesController < ApplicationController
before_action :require_login, :check_auth
before_action :find_game
before_action :find_game, except: [:jupyter]
before_action :find_shixun, only: [:show, :answer, :rep_content, :choose_build, :game_build, :game_status]
before_action :allowed
before_action :allowed, except: [:jupyter]
#require 'iconv'
include GamesHelper
include ApplicationHelper
def show
uid_logger("--games show start")
# 防止评测中途ajaxE被取消;3改成0是为了处理首次进入下一关的问题
@ -88,6 +91,22 @@ class GamesController < ApplicationController
end
end
def jupyter
# Jupyter没有challenge
@myshixun = Myshixun.find_by_identifier params[:identifier]
unless current_user.id == @myshixun.user_id || current_user.admin_or_business?
raise Educoder::TipException.new(403, "..")
end
@shixun = @myshixun.shixun
# 判断tpm是否修改了
begin
@tpm_modified = @myshixun.repository_is_modified(@shixun.repo_path) # 判断TPM和TPI的版本库是否被改了
rescue
uid_logger("实训平台繁忙繁忙等级81")
end
end
def reset_vnc_link
begin
# 删除vnc的pod

@ -0,0 +1,46 @@
class JupytersController < ApplicationController
include JupyterService
before_action :shixun, only: [:open, :open1, :test, :save]
def save_with_tpi
myshixun = Myshixun.find_by(identifier: params[:identifier])
jupyter_save_with_game(myshixun, params[:jupyter_port])
render json: {status: 0}
end
def save_with_tpm
shixun = Shixun.find_by(identifier: params[:identifier])
jupyter_save_with_shixun(shixun, params[:jupyter_port])
render json: {status: 0}
end
def get_info_with_tpi
myshixun = Myshixun.find_by(identifier: params[:identifier])
url = jupyter_url_with_game(myshixun)
port = jupyter_port_with_game(myshixun)
render json: {status: 0, url: url, port: port}
end
def get_info_with_tpm
shixun = Shixun.find_by(identifier: params[:identifier])
url = jupyter_url_with_shixun(shixun)
port = jupyter_port_with_shixun(shixun)
render json: {status: 0, url: url, port: port}
end
def reset_with_tpi
myshixun = Myshixun.find_by(identifier: params[:identifier])
info = jupyter_tpi_reset(myshixun)
render json: {status: 0, url: info[:url], port: info[:port]}
end
def reset_with_tpm
shixun = Shixun.find_by(identifier: params[:identifier])
info = jupyter_tpm_reset(shixun)
render json: {status: 0, url: info[:url], port: info[:port]}
end
end

@ -366,6 +366,31 @@ class MyshixunsController < ApplicationController
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

@ -6,16 +6,17 @@ class ShixunsController < ApplicationController
before_action :require_login, :check_auth, except: [:download_file, :index, :menus, :show, :show_right, :ranking_list,
:discusses, :collaborators, :fork_list, :propaedeutics]
before_action :check_account, only: [:new, :create, :shixun_exec]
before_action :check_account, only: [:new, :create, :shixun_exec, :jupyter_exec]
before_action :find_shixun, except: [:index, :new, :create, :menus, :get_recommend_shixuns,
:propaedeutics, :departments, :apply_shixun_mirror,
:get_mirror_script, :download_file, :shixun_list, :batch_send_to_course]
before_action :shixun_access_allowed, except: [:index, :new, :create, :menus, :get_recommend_shixuns,
:propaedeutics, :departments, :apply_shixun_mirror,
:propaedeutics, :departments, :apply_shixun_mirror, :jupyter_exec,
:get_mirror_script, :download_file, :shixun_list, :batch_send_to_course]
before_action :find_repo_name, only: [:repository, :commits, :file_content, :update_file, :shixun_exec, :copy, :add_file]
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, :apply_public,
:shixun_members_added, :change_manager, :collaborators_delete,
@ -364,73 +365,158 @@ class ShixunsController < ApplicationController
end
def create
# 评测脚本的一些操作
main_type, sub_type = params[:main_type], params[:small_type]
mirror = MirrorScript.where(:mirror_repository_id => main_type)
identifier = generate_identifier Shixun, 8
@shixun = Shixun.new(shixun_params)
@shixun.identifier = identifier
@shixun.user_id = current_user.id
@shixun.reset_time, @shixun.modify_time = Time.now, Time.now
if sub_type.blank?
shixun_script = mirror.first.try(:script)
else
main_mirror = MirrorRepository.find(main_type).type_name
sub_mirror = MirrorRepository.where(id: sub_type).pluck(:type_name)
if main_mirror == "Java" && sub_mirror.include?("Mysql")
shixun_script = mirror.last.try(:script)
else
shixun_script = mirror.first.try(:script)
shixun_script = modify_shixun_script @shixun, shixun_script
end
end
@shixun = CreateShixunService.call(current_user, shixun_params, params)
end
ActiveRecord::Base.transaction do
begin
@shixun.save!
# shixun_info关联ß
ShixunInfo.create!(shixun_id: @shixun.id, evaluate_script: shixun_script, description: params[:description])
# 实训的公开范围
if params[:scope_partment].present?
arr = []
ids = School.where(:name => params[:scope_partment]).pluck(:id).uniq
ids.each do |id|
arr << { :school_id => id, :shixun_id => @shixun.id }
end
ShixunSchool.create!(arr)
end
# 保存jupyter到版本库
def update_jupyter
jupyter_save_with_shixun(@shixun, params[:jupyter_port])
end
# 实训合作者
@shixun.shixun_members.create!(user_id: current_user.id, role: 1)
# 镜像-实训关联表
ShixunMirrorRepository.create!(:shixun_id => @shixun.id, :mirror_repository_id => main_type.to_i) if main_type.present?
# 实训主镜像服务配置
ShixunServiceConfig.create!(:shixun_id => @shixun.id, :mirror_repository_id => main_type.to_i)
if sub_type.present?
sub_type.each do |mirror|
ShixunMirrorRepository.create!(:shixun_id => @shixun.id, :mirror_repository_id => mirror)
# 实训子镜像服务配置
name = MirrorRepository.find_by(id: mirror).try(:name) #查看镜像是否有名称,如果没有名称就不用服务配置
ShixunServiceConfig.create!(:shixun_id => @shixun.id, :mirror_repository_id => mirror) if name.present?
def update
# 镜像方面
mirror_ids = MirrorRepository.where(id: params[:main_type])
.or( MirrorRepository.where(id: params[:sub_type])).pluck(:id).uniq
old_mirror_ids = @shixun.shixun_mirror_repositories
.where(mirror_repository_id: params[:main_type])
.or(@shixun.shixun_mirror_repositories.where(mirror_repository_id: params[:sub_type]))
.pluck(:mirror_repository_id).uniq
new_mirror_id = (mirror_ids - old_mirror_ids).map{|id| {mirror_repository_id: id}} # 转换成数组hash方便操作
logger.info("##########new_mirror_id: #{new_mirror_id}")
logger.info("##########old_mirror_ids: #{old_mirror_ids}")
logger.info("##########mirror_ids: #{mirror_ids}")
# 服务配置方面
service_create_params = service_config_params[:shixun_service_configs]
.select{|config| !old_mirror_ids.include?(config[:mirror_repository_id]) &&
MirrorRepository.find(config[:mirror_repository_id]).name.present?}
service_update_params = service_config_params[:shixun_service_configs]
.select{|config| old_mirror_ids.include?(config[:mirror_repository_id])}
logger.info("#########service_create_params: #{service_create_params}")
logger.info("#########service_update_params: #{service_update_params}")
begin
ActiveRecord::Base.transaction do
@shixun.update_attributes(shixun_params)
@shixun.shixun_info.update_attributes(shixun_info_params)
# 镜像变动
@shixun.shixun_mirror_repositories.where.not(mirror_repository_id: old_mirror_ids).destroy_all
@shixun.shixun_mirror_repositories.create!(new_mirror_id)
# 镜像变动要更换服务配置
@shixun.shixun_service_configs.where.not(mirror_repository_id: old_mirror_ids).destroy_all
@shixun.shixun_service_configs.create!(service_create_params)
service_update_params&.map do |service|
smr = @shixun.shixun_service_configs.find_by(mirror_repository_id: service[:mirror_repository_id])
smr.update_attributes(service)
end
# 添加第二仓库(管理员权限)
if current_user.admin_or_business?
if params[:is_secret_repository]
add_secret_repository if @shixun.shixun_secret_repository.blank?
else
# 如果有仓库,就要删
if @shixun.shixun_secret_repository&.repo_name
@shixun.shixun_secret_repository.lock!
GitService.delete_repository(repo_path: @shixun.shixun_secret_repository.repo_path)
@shixun.shixun_secret_repository.destroy
end
end
end
end
rescue => e
uid_logger_error(e.message)
tip_exception("基本信息更新失败:#{e.message}")
end
end
# 实训权限设置
def update_permission_setting
# 查找需要增删的高校id
school_id = School.where(:name => params[:scope_partment]).pluck(:id)
old_school_ids = @shixun.shixun_schools.pluck(:school_id)
school_params = (school_id - old_school_ids).map{|id| {school_id: id}}
begin
ActiveRecord::Base.transaction do
@shixun.update_attributes!(shixun_params)
@shixun.shixun_schools.where.not(school_id: school_id).destroy_all if school_id.present?
@shixun.shixun_schools.create!(school_params)
end
rescue => e
uid_logger_error("实训权限设置失败--------#{e.message}")
tip_exception("实训权限设置失败")
end
end
# 创建版本库
repo_path = repo_namespace(User.current.login, @shixun.identifier)
GitService.add_repository(repo_path: repo_path)
# todo: 为什么保存的时候要去除后面的.git呢??
@shixun.update_column(:repo_name, repo_path.split(".")[0])
# 实训学习页面设置
def update_learn_setting
begin
ActiveRecord::Base.transaction do
@shixun.update_attributes!(shixun_params)
end
rescue => e
uid_logger_error("实训学习页面设置失败--------#{e.message}")
tip_exception("实训学习页面设置失败")
end
end
# 将实训标志为该云上实验室建立
Laboratory.current.laboratory_shixuns.create!(shixun: @shixun, ownership: true)
rescue Exception => e
uid_logger_error(e.message)
tip_exception("实训创建失败")
raise ActiveRecord::Rollback
# Jupyter数据集
def get_data_sets
page = params[:page] || 1
limit = params[:limit] || 10
data_sets = @shixun.data_sets
@data_count = data_sets.count
@data_sets= data_sets.page(page).per(limit)
@absolute_folder = edu_setting('shixun_folder')
end
# 实训测试集附件
def upload_data_sets
upload_file = params["file"]
raise "未上传文件" unless upload_file
folder = edu_setting('shixun_folder')
raise "存储目录未定义" unless folder.present?
rep_name = @shixun.data_sets.pluck(:filename).include?(upload_file.original_filename)
raise "文件名已经存在\"#{upload_file.original_filename}\", 请删除后再上传" if rep_name
tpm_folder = params[:identifier] # 这个是实训的identifier
save_path = File.join(folder, tpm_folder)
ext = file_ext(upload_file.original_filename)
local_path, digest = file_save_to_local(save_path, upload_file.tempfile, ext)
content_type = upload_file.content_type.presence || 'application/octet-stream'
disk_filename = local_path[save_path.size + 1, local_path.size]
@attachment = Attachment.where(disk_filename: disk_filename,
author_id: current_user.id).first
if @attachment.blank?
@attachment = Attachment.new
@attachment.filename = upload_file.original_filename
@attachment.disk_filename = local_path[save_path.size + 1, local_path.size]
@attachment.filesize = upload_file.tempfile.size
@attachment.content_type = content_type
@attachment.digest = digest
@attachment.author_id = current_user.id
@attachment.disk_directory = tpm_folder
@attachment.cloud_url = remote_path
@attachment.container_id = @shixun.id
@attachment.container_type = @shixun.class.name
@attachment.attachtype = 2
@attachment.save!
else
logger.info "文件已存在id = #{@attachment.id}, filename = #{@attachment.filename}"
end
render_ok
end
# 多文件删除
def destroy_data_sets
files = Attachment.where(id: params[:id])
shixun_folder= edu_setting("shixun_folder")
begin
files.each do |file|
file_path = "#{shixun_folder}/#{file.relative_path_filename}"
delete_file(file_path)
end
files.destroy_all
render_ok
rescue => e
uid_logger_error(e.message)
tip_exception(e.message)
end
end
@ -462,61 +548,6 @@ class ShixunsController < ApplicationController
tip_exception("申请失败")
end
def update
ActiveRecord::Base.transaction do
begin
@shixun.shixun_mirror_repositories.destroy_all
if params[:main_type].present?
ShixunMirrorRepository.create(:shixun_id => @shixun.id, :mirror_repository_id => params[:main_type].to_i)
end
if params[:small_type].present?
params[:small_type].each do |mirror|
ShixunMirrorRepository.create(:shixun_id => @shixun.id, :mirror_repository_id => mirror)
end
end
@shixun.update_attributes(shixun_params)
@shixun.shixun_info.update_attributes(shixun_info_params)
@shixun.shixun_schools.delete_all
# scope_partment: 高校的名称
if params[:scope_partment].present?
arr = []
ids = School.where(:name => params[:scope_partment]).pluck(:id).uniq
ids.each do |id|
arr << { :school_id => id, :shixun_id => @shixun.id }
end
ShixunSchool.create!(arr)
end
# 超级管理员和运营人员才能保存 中间层服务器pod信息的配置
# 如果镜像改动了,则也需要更改
mirror = @shixun.shixun_service_configs.map(&:mirror_repository_id).sort
new_mirror = params[:shixun_service_configs].map{|c| c[:mirror_repository_id]}.sort
if current_user.admin? || current_user.business? || (mirror != new_mirror)
@shixun.shixun_service_configs.destroy_all
service_config_params[:shixun_service_configs].each do |config|
name = MirrorRepository.find_by_id(config[:mirror_repository_id])&.name
# 不保存没有镜像的配置
@shixun.shixun_service_configs.create!(config) if name.present?
end
end
# 添加第二仓库
if params[:is_secret_repository]
add_secret_repository if @shixun.shixun_secret_repository.blank?
else
# 如果有仓库,就要删
if @shixun.shixun_secret_repository&.repo_name
@shixun.shixun_secret_repository.lock!
GitService.delete_repository(repo_path: @shixun.shixun_secret_repository.repo_path)
@shixun.shixun_secret_repository.destroy
end
end
rescue Exception => e
uid_logger_error("实训保存失败--------#{e.message}")
tip_exception("实训保存失败")
raise ActiveRecord::Rollback
end
end
end
# 永久关闭实训
def close
@ -552,6 +583,8 @@ class ShixunsController < ApplicationController
# @evaluate_scirpt = @shixun.evaluate_script || "无"
end
# 获取脚本内容
def get_script_contents
mirrir_script = MirrorScript.find(params[:script_id])
@ -708,112 +741,40 @@ class ShixunsController < ApplicationController
end
end
# def shixun_exec
# if is_shixun_opening?
# tip_show_exception(-3, "#{@shixun.opening_time.strftime('%Y-%m-%d %H:%M:%S')}")
# end
# current_myshixun = @shixun.current_myshixun(current_user.id)
#
# min_challenges = @shixun.challenges.pluck(:id , :st)
# # 因为读写分离有延迟所以如果是重置来的请求可以先跳过重置过来的params[:reset]为1
# if current_myshixun && params[:reset] != "1"
# games = current_myshixun.games
# # 如果TPM和TPI的管卡数不相等或者关卡顺序错了说明实训被极大的改动需要重置,实训发布前打过的实训都需要重置
# if is_shixun_reset?(games, min_challenges, current_myshixun)
# # 这里页面弹框要收到 当前用户myshixun的identifier.
# tip_show_exception("/myshixuns/#{current_myshixun.try(:identifier)}/reset_my_game")
# end
#
#
# if current_myshixun.repo_name.nil?
# g = Gitlab.client
# repo_name = g.project(current_myshixun.gpid).try(:path_with_namespace)
# current_myshixun.update_column(:repo_name, repo_name)
# end
#
# # 如果存在实训,则直接进入实训
# # 如果实训允许跳关传参params[:challenge_id]跳入具体的关卡
# @current_task =
# if params[:challenge_id]
# game = games.where(challenge_id: params[:challenge_id]).take
# if @shixun.task_pass || game.status != 3
# game
# else
# current_myshixun.current_task(games)
# end
# else
# current_myshixun.current_task(games)
# end
# else
# # 如果未创建关卡一定不能开启实训否则TPI没法找到当前的关卡
# if @shixun.challenges_count == 0
# tip_exception("开启实战前请先创建实训关卡")
# end
#
# # 判断实训是否全为选择题
# is_choice_type = (min_challenges.size == min_challenges.select{|challenge| challenge.last == 1}.count)
# if !is_choice_type
# commit = GitService.commits(repo_path: @repo_path).try(:first)
# uid_logger("First comit########{commit}")
# tip_exception("开启实战前请先在版本库中提交代码") if commit.blank?
# commit_id = commit["id"]
# end
#
# begin
# ActiveRecord::Base.transaction do
# begin
# myshixun_identifier = generate_identifier Myshixun, 10
# myshixun_params = {user_id: current_user.id, identifier: myshixun_identifier,
# modify_time: @shixun.modify_time, reset_time: @shixun.reset_time,
# onclick_time: Time.now, commit_id: commit_id}
# @myshixun = @shixun.myshixuns.create!(myshixun_params)
# # 其它创建关卡等操作
# challenges = @shixun.challenges
# # 之所以增加user_id是为了方便统计查询性能
# game_attrs = %i[challenge_id myshixun_id status user_id open_time identifier modify_time created_at updated_at]
# Game.bulk_insert(*game_attrs) do |worker|
# base_attr = {myshixun_id: @myshixun.id, user_id: @myshixun.user_id}
# challenges.each_with_index do |challenge, index|
# status = (index == 0 ? 0 : 3)
# game_identifier = generate_identifier(Game, 12)
# worker.add(base_attr.merge(challenge_id: challenge.id, status: status, open_time: Time.now,
# identifier: game_identifier, modify_time: challenge.modify_time))
# end
# end
# @current_task = @myshixun.current_task(@myshixun.games)
# rescue Exception => e
# logger.error("------ActiveRecord::RecordInvalid: #{e.message}")
# raise("ActiveRecord::RecordInvalid")
# end
# end
# # 如果实训是纯选择题则不需要去fork仓库以及中间层的相关操作了
# ActiveRecord::Base.transaction do
# unless is_choice_type
# # fork仓库
# cloud_bridge = edu_setting('cloud_bridge')
# project_fork(@myshixun, @repo_path, current_user.login)
# rep_url = Base64.urlsafe_encode64(repo_ip_url @repo_path)
# uid_logger("start openGameInstance")
# uri = "#{cloud_bridge}/bridge/game/openGameInstance"
# logger.info("end openGameInstance")
# params = {tpiID: "#{@myshixun.id}", tpmGitURL: rep_url, tpiRepoName: @myshixun.repo_name.split("/").last}
# uid_logger("openGameInstance params is #{params}")
# interface_post uri, params, 83, "实训云平台繁忙繁忙等级83"
# end
# end
# rescue Exception => e
# logger.info("shixun_exec error: #{e.message}")
# if e.message != "ActiveRecord::RecordInvalid"
# logger.error("##########project_fork error #{e.message}")
# @myshixun.destroy!
# end
# raise "实训云平台繁忙繁忙等级81"
# end
# end
# end
# gameID 及实训ID
# status: 0 , 1 申请过, 2实训关卡路径未填 3 实训标签未填, 4 实训未创建关卡
# jupyter开启挑战
def jupyter_exec
begin
if is_shixun_opening?
tip_show_exception(-3, "#{@shixun.opening_time.strftime('%Y-%m-%d %H:%M:%S')}")
end
current_myshixun = @shixun.current_myshixun(current_user.id)
if current_myshixun
@myshixun = current_myshixun
else
commit = GitService.commits(repo_path: @repo_path).try(:first)
uid_logger("First comit########{commit}")
tip_exception("开启实战前请先在版本库中提交代码") if commit.blank?
commit_id = commit["id"]
cloud_bridge = edu_setting('cloud_bridge')
myshixun_identifier = generate_identifier Myshixun, 10
ActiveRecord::Base.transaction do
@myshixun = @shixun.myshixuns.create!(user_id: current_user.id, identifier: myshixun_identifier,
modify_time: @shixun.modify_time, reset_time: @shixun.reset_time,
onclick_time: Time.now, commit_id: commit_id)
# fork仓库
project_fork(@myshixun, @repo_path, current_user.login)
rep_url = Base64.urlsafe_encode64(repo_ip_url @repo_path)
uri = "#{cloud_bridge}/bridge/game/openGameInstance"
params = {tpiID: "#{@myshixun.id}", tpmGitURL: rep_url, tpiRepoName: @myshixun.repo_name.split("/").last}
interface_post uri, params, 83, "实训云平台繁忙繁忙等级83"
end
end
rescue => e
uid_logger_error(e.message)
tip_exception("实训云平台繁忙繁忙等级81")
end
end
def publish
@status = 0
@position = []
@ -1035,10 +996,9 @@ class ShixunsController < ApplicationController
private
def shixun_params
raise("实训名称不能为空") if params[:shixun][:name].blank?
params.require(:shixun).permit(:name, :trainee, :webssh, :can_copy, :use_scope, :vnc, :test_set_permission,
:task_pass, :multi_webssh, :opening_time, :mirror_script_id, :code_hidden,
:hide_code, :forbid_copy, :vnc_evaluate, :code_edit_permission)
:hide_code, :forbid_copy, :vnc_evaluate, :code_edit_permission, :is_jupyter)
end
def validate_review_shixun_params
@ -1047,8 +1007,6 @@ private
end
def shixun_info_params
raise("实训描述不能为空") if params[:shixun_info][:description].blank?
raise("评测脚本不能为空") if params[:shixun_info][:evaluate_script].blank?
params.require(:shixun_info).permit(:description, :evaluate_script)
end
@ -1116,4 +1074,35 @@ private
ShixunSecretRepository.create!(repo_name: repo_path.split(".")[0], shixun_id: @shixun.id)
end
def file_save_to_local(save_path, temp_file, ext)
unless Dir.exists?(save_path)
FileUtils.mkdir_p(save_path) ##不成功这里会抛异常
end
digest = md5_file(temp_file)
digest = "#{digest}_#{(Time.now.to_f * 1000).to_i}"
local_file_path = File.join(save_path, digest) + ext
save_temp_file(temp_file, local_file_path)
[local_file_path, digest]
end
def save_temp_file(temp_file, save_file_path)
File.open(save_file_path, 'wb') do |f|
temp_file.rewind
while (buffer = temp_file.read(8192))
f.write(buffer)
end
end
end
def file_ext(file_name)
ext = ''
exts = file_name.split(".")
if exts.size > 1
ext = ".#{exts.last}"
end
ext
end
end

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

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

@ -33,6 +33,10 @@ class Attachment < ApplicationRecord
File.join(File.join(Rails.root, "files"), disk_directory.to_s, disk_filename.to_s)
end
def relative_path_filename
File.join(disk_directory.to_s, disk_filename.to_s)
end
def title
title = filename
if container && container.is_a?(StudentWork) && author_id != User.current.id

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

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

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

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

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

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

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

@ -0,0 +1,188 @@
#coding=utf-8
module JupyterService
def _open_shixun_jupyter(shixun)
if shixun.is_jupyter?
shixun_tomcat = edu_setting('cloud_bridge')
uri = "#{shixun_tomcat}/bridge/jupyter/get"
tpiID = "tpm#{shixun.id}"
params = {tpiID: tpiID, identifier: shixun.identifier, :containers => "#{Base64.urlsafe_encode64(shixun_container_limit(shixun))}"}
logger.info "test_juypter: uri->#{uri}, params->#{params}"
res = uri_post uri, params
if res && res['code'].to_i != 0
raise("实训云平台繁忙繁忙等级99")
end
logger.info "test_juypter: #{res}"
@shixun_jupyter_port = res['port']
return "https://#{res['port']}.jupyter.educoder.net/notebooks/data/workspace/myshixun_#{tpiID}/01.ipynb"
end
end
def jupyter_url_with_shixun(shixun)
#打开tpm - juypter接口
_open_shixun_jupyter(shixun)
end
def jupyter_port_with_shixun(shixun)
if @shixun_jupyter_port.to_i <=0
_open_shixun_jupyter(shixun)
end
@shixun_jupyter_port
end
def _open_game_jupyter(myshixun)
## 打开tpi
shixun = myshixun.shixun
if shixun.is_jupyter?
shixun_tomcat = edu_setting('cloud_bridge')
uri = "#{shixun_tomcat}/bridge/jupyter/get"
tpiID = myshixun.id
params = {tpiID: tpiID, identifier: shixun.identifier, :containers => "#{Base64.urlsafe_encode64(shixun_container_limit(shixun))}"}
res = uri_post uri, params
logger.info "test_juypter: #{res}"
if res && res['code'].to_i != 0
raise("实训云平台繁忙繁忙等级99")
end
@game_jupyter_port = res['port']
repo_save_path = myshixun.repo_save_path
"https://#{res['port']}.jupyter.educoder.net/notebooks/data/workspace/myshixun_#{tpiID}/#{repo_save_path}/01.ipynb"
end
end
def jupyter_url_with_game(myshixun)
_open_game_jupyter(myshixun)
end
def jupyter_port_with_game(myshixun)
if @game_jupyter_port.to_i <=0
_open_game_jupyter(myshixun)
end
@game_jupyter_port
end
def jupyter_save_with_shixun(shixun,jupyter_port)
author_name = current_user.real_name
author_email = current_user.git_mail
message = "User submitted"
tpiID = "tpm#{shixun.id}"
#https://47526.jupyter.educoder.net/nbconvert/notebook/data/workspace/myshixun_570461/f2ef5p798r20191210163135/01.ipynb?download=true
src_url = "https://#{jupyter_port}.jupyter.educoder.net/nbconvert/notebook/data/workspace/myshixun_#{tpiID}/01.ipynb?download=true"
response = Faraday.get(src_url)
if response.status.to_i != 200
raise("获取文件内容失败:#{response.status}")
end
content = response.body.force_encoding('utf-8')
c = GitService.update_file(repo_path: shixun.repo_path,
file_path: "01.ipynb",
message: message,
content: content,
author_name: author_name,
author_email: author_email)
return c.size
end
def jupyter_save_with_game(myshixun,jupyter_port)
author_name = current_user.real_name
author_email = current_user.git_mail
message = "User submitted"
tpiID = myshixun.id
repo_save_path = myshixun.repo_save_path
src_url = "https://#{jupyter_port}.jupyter.educoder.net/nbconvert/notebook/data/workspace/myshixun_#{tpiID}/#{repo_save_path}/01.ipynb?download=true"
response = Faraday.get(src_url)
if response.status.to_i != 200
raise("获取文件内容失败:#{response.status}")
end
content = response.body.force_encoding('utf-8')
c = GitService.update_file(repo_path: myshixun.repo_path,
file_path: "01.ipynb",
message: message,
content: content,
author_name: author_name,
author_email: author_email)
return c.size
end
##重置jupyter环境
def jupyter_tpi_reset(myshixun)
jupyter_delete_tpi(myshixun)
url = jupyter_url_with_game(myshixun)
port = jupyter_port_with_game(myshixun)
{url: url, port: port}
end
## 重置tpm环境
def jupyter_tpm_reset(shixun)
jupyter_delete_tpm(shixun)
url = jupyter_url_with_shixun(shixun)
port = jupyter_port_with_shixun(shixun)
{url: url, port: port}
end
# 删除pod
def jupyter_delete_tpi(myshixun)
myshixun_id = myshixun.id
digest = myshixun.identifier + edu_setting('bridge_secret_key')
digest_key = Digest::SHA1.hexdigest("#{digest}")
begin
shixun_tomcat = edu_setting('cloud_bridge')
uri = "#{shixun_tomcat}/bridge/jupyter/delete"
Rails.logger.info("#{current_user} => cloese_jupyter digest is #{digest}")
params = {:tpiID => myshixun_id, :digestKey => digest_key, :identifier => myshixun.identifier}
res = uri_post uri, params
if res && res['code'].to_i != 0
raise("实训云平台繁忙繁忙等级110")
end
end
end
def jupyter_delete_tpm(shixun)
tpiID = "tpm#{shixun.id}"
digest = shixun.identifier + edu_setting('bridge_secret_key')
digest_key = Digest::SHA1.hexdigest("#{digest}")
begin
shixun_tomcat = edu_setting('cloud_bridge')
uri = "#{shixun_tomcat}/bridge/jupyter/delete"
Rails.logger.info("#{current_user} => cloese_jupyter digest is #{digest}")
params = {:tpiID => tpiID, :digestKey => digest_key, :identifier => shixun.identifier}
res = uri_post uri, params
if res && res['code'].to_i != 0
raise("实训云平台繁忙繁忙等级110")
end
end
end
end

@ -0,0 +1,109 @@
class CreateShixunService < ApplicationService
attr_reader :user, :params, :permit_params
def initialize(user, permit_params, params)
@user = user
@params = params
@permit_params = permit_params
end
def call
shixun = Shixun.new(permit_params)
identifier = Util::UUID.generate_identifier(Shixun, 8)
shixun.identifier= identifier
shixun.user_id = user.id
main_mirror = MirrorRepository.find params[:main_type]
sub_mirrors = MirrorRepository.where(id: params[:sub_type])
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
rescue => e
Rails.logger.error("shixun_create_error: #{e.message}")
raise("创建实训失败!")
end
end
private
def get_shixun_script shixun, main_mirror, sub_mirrors
if !shixun.is_jupyter?
mirror = main_mirror.mirror_scripts
if main_mirror.blank?
modify_shixun_script shixun, mirror.first&.(:script)
else
sub_name = sub_mirrors.pluck(:type_name)
if main_mirror.type_name == "Java" && sub_name.include?("Mysql")
mirror.last.try(:script)
else
shixun_script = mirror.first&.script
modify_shixun_script shixun, shixun_script
end
end
end
end
def modify_shixun_script shixun, script
if script.present?
source_class_name = []
challenge_program_name = []
shixun.challenges.map(&:exec_path).each do |exec_path|
challenge_program_name << "\"#{exec_path}\""
if shixun.main_mirror_name == "Java"
if exec_path.nil? || exec_path.split("src/")[1].nil?
source = "\"\""
else
source = "\"#{exec_path.split("src/")[1].split(".java")[0]}\""
end
logger.info("----source: #{source}")
source_class_name << source.gsub("/", ".") if source.present?
elsif shixun.main_mirror_name.try(:first) == "C#"
if exec_path.nil? || exec_path.split(".")[1].nil?
source = "\"\""
else
source = "\"#{exec_path.split(".")[0]}.exe\""
end
source_class_name << source if source.present?
end
end
script = if script.include?("sourceClassName") && script.include?("challengeProgramName")
script.gsub(/challengeProgramNames=\(.*\)/,"challengeProgramNames=\(#{challenge_program_name.reject(&:blank?).join(" ")}\)").gsub(/sourceClassNames=\(.*\)/, "sourceClassNames=\(#{source_class_name.reject(&:blank?).join(" ")}\)")
else
script.gsub(/challengeProgramNames=\(.*\)/,"challengeProgramNames=\(#{challenge_program_name.reject(&:blank?).join(" ")}\)").gsub(/sourceClassNames=\(.*\)/, "sourceClassNames=\(#{challenge_program_name.reject(&:blank?).join(" ")}\)")
end
end
return script
end
# 版本库目录空间
def repo_namespace(user, shixun_identifier)
"#{user}/#{shixun_identifier}.git"
end
end

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

@ -0,0 +1,7 @@
json.user do
json.partial! 'users/user', user: current_user
end
json.(@shixun, :id, :identifier, :status, :name)
json.myshixun_identifier @myshixun.identifier
json.tpm_modified @tpm_modified

@ -19,4 +19,4 @@ else
json.has_answer @has_answer
json.choose_test_cases @choose_test_cases
json.chooses @chooses
end
end

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

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

@ -0,0 +1,11 @@
json.data_sets do
json.array! @data_sets do |set|
json.id set.id
json.title set.title
json.author set.author.real_name
json.created_on set.created_on
json.filesize number_to_human_size(set.filesize)
json.file_path "#{@absolute_folder}/#{set.relative_path_filename}"
end
end
json.data_sets_count @data_count

@ -0,0 +1 @@
json.identifier @myshixun.identifier

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

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

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

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

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

@ -9,6 +9,8 @@ Rails.application.routes.draw do
get 'auth/qq/callback', to: 'oauth/qq#create'
get 'auth/failure', to: 'oauth/base#auth_failure'
resources :edu_settings
scope '/api' do
get 'home/index'
@ -23,6 +25,17 @@ Rails.application.routes.draw do
put 'commons/unhidden', to: 'commons#unhidden'
delete 'commons/delete', to: 'commons#delete'
resources :jupyters do
collection do
get :save_with_tpi
get :save_with_tpm
get :get_info_with_tpi
get :get_info_with_tpm
get :reset_with_tpi
get :reset_with_tpm
end
end
resources :memos do
member do
post :sticky_or_cancel
@ -35,6 +48,9 @@ Rails.application.routes.draw do
end
end
resources :hacks, path: :problems, param: :identifier do
collection do
get :unpulished_list
@ -171,6 +187,7 @@ Rails.application.routes.draw do
post :html_content
get :open_webssh
get :challenges
post :sync_code
end
collection do
get :sigle_mul_test
@ -206,6 +223,7 @@ Rails.application.routes.draw do
get :check_test_sets
get :unlock_choose_answer
get :get_choose_answer
get :jupyter
end
collection do
@ -264,6 +282,12 @@ Rails.application.routes.draw do
get :shixun_exec
post :review_shixun
get :review_newest_record
post :update_permission_setting
post :update_learn_setting
get :get_data_sets
get :jupyter_exec
post :upload_data_sets
delete :destroy_data_sets
end
resources :challenges do
@ -748,7 +772,11 @@ Rails.application.routes.draw do
resources :poll_bank_questions
resources :attachments
resources :attachments do
collection do
delete :destroy_files
end
end
resources :schools do
member do

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

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

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

Binary file not shown.

@ -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

@ -1,114 +1,114 @@
'use strict';
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'development';
process.env.NODE_ENV = 'development';
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', err => {
throw err;
});
// Ensure environment variables are read.
require('../config/env');
const fs = require('fs');
const chalk = require('chalk');
const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const clearConsole = require('react-dev-utils/clearConsole');
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
const {
choosePort,
createCompiler,
prepareProxy,
prepareUrls,
} = require('react-dev-utils/WebpackDevServerUtils');
const openBrowser = require('react-dev-utils/openBrowser');
const paths = require('../config/paths');
const config = require('../config/webpack.config.dev');
const createDevServerConfig = require('../config/webpackDevServer.config');
const useYarn = fs.existsSync(paths.yarnLockFile);
const isInteractive = process.stdout.isTTY;
const portSetting = require(paths.appPackageJson).port
if ( portSetting ) {
process.env.port = portSetting
}
// Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
process.exit(1);
}
// Tools like Cloud9 rely on this.
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3007;
const HOST = process.env.HOST || '0.0.0.0';
if (process.env.HOST) {
console.log(
chalk.cyan(
`Attempting to bind to HOST environment variable: ${chalk.yellow(
chalk.bold(process.env.HOST)
)}`
)
);
console.log(
`If this was unintentional, check that you haven't mistakenly set it in your shell.`
);
console.log(`Learn more here: ${chalk.yellow('http://bit.ly/2mwWSwH')}`);
console.log();
}
// We attempt to use the default port but if it is busy, we offer the user to
// run on a different port. `choosePort()` Promise resolves to the next free port.
choosePort(HOST, DEFAULT_PORT)
.then(port => {
if (port == null) {
// We have not found a port.
return;
}
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
const appName = require(paths.appPackageJson).name;
const urls = prepareUrls(protocol, HOST, port);
// Create a webpack compiler that is configured with custom messages.
const compiler = createCompiler(webpack, config, appName, urls, useYarn);
// Load proxy config
const proxySetting = require(paths.appPackageJson).proxy;
console.log('-------------------------proxySetting:', proxySetting)
const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
// Serve webpack assets generated by the compiler over a web sever.
const serverConfig = createDevServerConfig(
proxyConfig,
urls.lanUrlForConfig
);
const devServer = new WebpackDevServer(compiler, serverConfig);
// Launch WebpackDevServer.
devServer.listen(port, HOST, err => {
if (err) {
return console.log(err);
}
if (isInteractive) {
clearConsole();
}
console.log(chalk.cyan('Starting the development server...\n'));
openBrowser(urls.localUrlForBrowser);
});
['SIGINT', 'SIGTERM'].forEach(function(sig) {
process.on(sig, function() {
devServer.close();
process.exit();
});
});
})
.catch(err => {
if (err && err.message) {
console.log(err.message);
}
process.exit(1);
});
'use strict';
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'development';
process.env.NODE_ENV = 'development';
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', err => {
throw err;
});
// Ensure environment variables are read.
require('../config/env');
const fs = require('fs');
const chalk = require('chalk');
const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const clearConsole = require('react-dev-utils/clearConsole');
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
const {
choosePort,
createCompiler,
prepareProxy,
prepareUrls,
} = require('react-dev-utils/WebpackDevServerUtils');
const openBrowser = require('react-dev-utils/openBrowser');
const paths = require('../config/paths');
const config = require('../config/webpack.config.dev');
const createDevServerConfig = require('../config/webpackDevServer.config');
const useYarn = fs.existsSync(paths.yarnLockFile);
const isInteractive = process.stdout.isTTY;
const portSetting = require(paths.appPackageJson).port
if ( portSetting ) {
process.env.port = portSetting
}
// Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
process.exit(1);
}
// Tools like Cloud9 rely on this.
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3007;
const HOST = process.env.HOST || '0.0.0.0';
if (process.env.HOST) {
console.log(
chalk.cyan(
`Attempting to bind to HOST environment variable: ${chalk.yellow(
chalk.bold(process.env.HOST)
)}`
)
);
console.log(
`If this was unintentional, check that you haven't mistakenly set it in your shell.`
);
console.log(`Learn more here: ${chalk.yellow('http://bit.ly/2mwWSwH')}`);
console.log();
}
// We attempt to use the default port but if it is busy, we offer the user to
// run on a different port. `choosePort()` Promise resolves to the next free port.
choosePort(HOST, DEFAULT_PORT)
.then(port => {
if (port == null) {
// We have not found a port.
return;
}
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
const appName = require(paths.appPackageJson).name;
const urls = prepareUrls(protocol, HOST, port);
// Create a webpack compiler that is configured with custom messages.
const compiler = createCompiler(webpack, config, appName, urls, useYarn);
// Load proxy config
const proxySetting = require(paths.appPackageJson).proxy;
console.log('-------------------------proxySetting:', proxySetting)
const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
// Serve webpack assets generated by the compiler over a web sever.
const serverConfig = createDevServerConfig(
proxyConfig,
urls.lanUrlForConfig
);
const devServer = new WebpackDevServer(compiler, serverConfig);
// Launch WebpackDevServer.
devServer.listen(port, HOST, err => {
if (err) {
return console.log(err);
}
if (isInteractive) {
clearConsole();
}
console.log(chalk.cyan('Starting the development server...\n'));
openBrowser(urls.localUrlForBrowser);
});
['SIGINT', 'SIGTERM'].forEach(function(sig) {
process.on(sig, function() {
devServer.close();
process.exit();
});
});
})
.catch(err => {
if (err && err.message) {
console.log(err.message);
}
process.exit(1);
});

@ -316,6 +316,11 @@ const RecordDetail = Loadable({
loader: () => import('./modules/developer/recordDetail'),
loading: Loading
});
// jupyter tpi
const JupyterTPI = Loadable({
loader: () => import('./modules/tpm/jupyter'),
loading: Loading
});
// //个人竞赛报名
// const PersonalCompetit = Loadable({
// loader: () => import('./modules/competition/personal/PersonalCompetit.js'),
@ -358,7 +363,7 @@ class App extends Component {
mydisplay:true,
})
};
disableVideoContextMenu = () => {
window.$( "body" ).on( "mousedown", "video", function(event) {
if(event.which === 3) {
@ -577,14 +582,14 @@ class App extends Component {
<Route path="/users/:username"
render={
(props) => {
return (<InfosIndex {...this.props} {...props} {...this.state} />)
}
}></Route>
<Route path="/banks"
render={
(props) => {
(props) => {
return (<BanksIndex {...this.props} {...props} {...this.state} />)
}
}></Route>
@ -610,12 +615,21 @@ class App extends Component {
<Route path="/shixuns/new" component={Newshixuns}>
</Route>
{/* jupyter */}
<Route path="/tasks/:identifier/jupyter/"
render={
(props) => {
return (<JupyterTPI {...this.props} {...props} {...this.state}/>)
}
}
/>
<Route path="/tasks/:stageId" component={IndexWrapperComponent}/>
<Route path="/shixuns/:shixunId" component={TPMIndexComponent}>
</Route>
{/*列表页*/}
{/*列表页 实训项目列表*/}
<Route path="/shixuns" component={TPMShixunsIndexComponent}/>
@ -637,8 +651,8 @@ class App extends Component {
<Route path="/moop_cases"render={
(props) => (<MoopCases {...this.props} {...props} {...this.state} />)
}/>
<Route path="/forums"
<Route path="/forums"
render={
(props)=>(<ForumsIndexComponent {...this.props} {...props} {...this.state}></ForumsIndexComponent>)
}
@ -671,7 +685,7 @@ class App extends Component {
(props)=>(<Ecs {...this.props} {...props} {...this.state}></Ecs>)
}/>
<Route path="/problems/new/:id?"
<Route path="/problems/new/:id?"
render={
(props) => {
return (<NewOrEditTask {...this.props} {...props} {...this.state} />)
@ -679,11 +693,11 @@ class App extends Component {
}
/>
<Route
path="/problems/:id/edit"
path="/problems/:id/edit"
render={
(props) => (<NewOrEditTask {...this.props} {...props} {...this.state} />)
} />
<Route path="/myproblems/record_detail/:id"
<Route path="/myproblems/record_detail/:id"
render={
(props) => (<RecordDetail {...this.props} {...props} {...this.state} />)
}
@ -703,7 +717,7 @@ class App extends Component {
(props)=>(<ShixunsHome {...this.props} {...props} {...this.state}></ShixunsHome>)
}
/>
<Route component={Shixunnopage}/>
<Route component={Shixunnopage}/>
</Switch>
</Router>
@ -825,4 +839,4 @@ moment.defineLocale('zh-cn', {
doy: 4 // The week that contains Jan 4th is the first week of the year.
}
});
export default SnackbarHOC()(App) ;
export default SnackbarHOC()(App) ;

@ -13,10 +13,10 @@ function locationurl(list){
if (window.location.port === "3007") {
} else {
window.location.replace(list)
window.location.href(list)
}
}
let hashTimeout
let hashTimeout
// TODO 开发期多个身份切换
let debugType =""
@ -35,7 +35,7 @@ if (isDev) {
// 老师
//ebugType="teacher";
// 学生
//debugType="student";
// debugType="student";
window._debugType = debugType;
export function initAxiosInterceptors(props) {
@ -51,7 +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-newweb.educoder.net"
proxy="https://test-jupyterweb.educoder.net"
//proxy="http://192.168.2.63:3001"
// 在这里使用requestMap控制避免用户通过双击等操作发出重复的请求
@ -88,7 +89,6 @@ export function initAxiosInterceptors(props) {
url = `${config.url}`;
}
}
if(`${config[0]}`!=`true`){
if (window.location.port === "3007") {
// if (url.indexOf('.json') == -1) {
@ -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;
@ -224,7 +224,7 @@ export function initAxiosInterceptors(props) {
return Promise.reject(error);
});
// -----------------------------------------------------------------------------------
}

@ -1,8 +1,8 @@
export function isDev() {
return window.location.port === "3007";
}
// const isMobile
export const isMobile = (/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(navigator.userAgent.toLowerCase()));
// const isWeiXin = (/MicroMessenger/i.test(navigator.userAgent.toLowerCase()));
export function isDev() {
return window.location.port === "3007";
}
// const isMobile
export const isMobile = (/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(navigator.userAgent.toLowerCase()));
// const isWeiXin = (/MicroMessenger/i.test(navigator.userAgent.toLowerCase()));

@ -31,7 +31,7 @@ export function getUrl(path, goTest) {
// testbdweb.educoder.net testbdweb.trustie.net
// const local = goTest ? 'https://testeduplus2.educoder.net' : 'http://localhost:3000'
// const local = 'https://testeduplus2.educoder.net'
const local = 'https://test-newweb.educoder.net'
const local = 'https://test-jupyterweb.educoder.net'
if (isDev) {
return `${local}${path?path:''}`
}
@ -55,6 +55,10 @@ export function getUrl2(path, goTest) {
export function getUploadActionUrl(path, goTest) {
return `${getUrl()}/api/attachments.json${isDev ? `?debug=${window._debugType || 'admin'}` : ''}`
}
export function getUploadActionUrltwo(id) {
return `${getUrl()}/api/shixuns/${id}/upload_data_sets.json${isDev ? `?debug=${window._debugType || 'admin'}` : ''}`
}
export function getUploadActionUrlOfAuth(id) {
return `${getUrl()}/api/users/accounts/${id}/auth_attachment.json${isDev ? `?debug=${window._debugType || 'admin'}` : ''}`
}
@ -85,4 +89,4 @@ export function htmlEncode(str) {
s = s.replace(/\'/g, "&#39;");//IE下不支持实体名称
s = s.replace(/\"/g, "&quot;");
return s;
}
}

@ -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;

@ -3,10 +3,10 @@
// export { default as OrderStateUtil } from '../routes/Order/components/OrderStateUtil';
export { getImageUrl as getImageUrl, getUrl as getUrl, getUrl2 as getUrl2, setImagesUrl as setImagesUrl
, getUploadActionUrl as getUploadActionUrl, getUploadActionUrlOfAuth as getUploadActionUrlOfAuth
, getUploadActionUrl as getUploadActionUrl,getUploadActionUrltwo as getUploadActionUrltwo , getUploadActionUrlOfAuth as getUploadActionUrlOfAuth
, getTaskUrlById as getTaskUrlById, TEST_HOST ,htmlEncode as htmlEncode } from './UrlTool';
export { default as queryString } from './UrlTool2';
export { SnackbarHOC as SnackbarHOC } from './SnackbarHOC';
export { trigger as trigger, on as on, off as off
@ -31,7 +31,7 @@ export { trace_collapse, trace, debug, info, warn, error, trace_c, debug_c, info
export { EDU_ADMIN, EDU_BUSINESS, EDU_SHIXUN_MANAGER, EDU_SHIXUN_MEMBER, EDU_CERTIFICATION_TEACHER
, EDU_GAME_MANAGER, EDU_TEACHER, EDU_NORMAL} from './Const'
export { default as AttachmentList } from './components/attachment/AttachmentList'
export { themes, ThemeContext } from './context/ThemeContext'

@ -41,17 +41,17 @@ const styles = MUIDialogStyleUtil.getTwoButtonStyle()
// 主题自定义
const theme = createMuiTheme({
palette: {
primary: {
primary: {
main: '#4CACFF',
contrastText: 'rgba(255, 255, 255, 0.87)'
},
},
secondary: { main: '#4CACFF' }, // This is just green.A700 as hex.
},
});
const testSetsExpandedArrayInitVal = [false, false, false, false, false,
false, false, false, false, false,
false, false, false, false, false,
const testSetsExpandedArrayInitVal = [false, false, false, false, false,
false, false, false, false, false,
false, false, false, false, false,
false, false, false, false, false]
window.__fetchAllFlag = false; // 是否调用过fetchAll TODO 如何多次使用provider
@ -70,7 +70,7 @@ class TPIContextProvider extends Component {
this.readGameAnswer = this.readGameAnswer.bind(this)
this.praisePlus = this.praisePlus.bind(this)
this.onGamePassed = this.onGamePassed.bind(this)
this.onPathChange = this.onPathChange.bind(this)
@ -80,7 +80,7 @@ class TPIContextProvider extends Component {
this.onShowUpdateDialog = this.onShowUpdateDialog.bind(this)
this.updateDialogClose = this.updateDialogClose.bind(this)
// this.showEffectDisplay();
this.state = {
@ -142,7 +142,7 @@ class TPIContextProvider extends Component {
// request
// var shixunId = this.props.match.params.shixunId;
var stageId = this.props.match.params.stageId;
window.__fetchAllFlag = false;
this.fetchAll(stageId);
this.costTimeInterval = window.setInterval(()=> {
@ -192,7 +192,7 @@ class TPIContextProvider extends Component {
onGamePassed(passed) {
const { game } = this.state
// 随便给个分以免重新评测时又出现评星组件注意目前game.star没有显示在界面上如果有则不能这么做
// game.star = 6;
// game.star = 6;
this.setState({
game: update(game, {star: { $set: 6 }}),
currentGamePassed: !!passed
@ -253,14 +253,14 @@ pop_box_new(htmlvalue, 480, 182);
const { praise_count, praise } = response.data;
// challenge.praise_count = praise_tread_count;
// challenge.user_praise = praise;
this.setState({ challenge: update(challenge,
this.setState({ challenge: update(challenge,
{
praise_count: { $set: praise_count },
user_praise: { $set: praise },
})
})
}
})
.catch(function (error) {
console.log(error);
@ -286,11 +286,11 @@ pop_box_new(htmlvalue, 480, 182);
}
const { myshixun } = this.state;
// myshixun.system_tip = false;
challenge.path = path;
const newChallenge = this.handleChallengePath(challenge);
this.setState({ challenge: newChallenge,
this.setState({ challenge: newChallenge,
myshixun: update(myshixun, {system_tip: { $set: false }}),
})
}
@ -313,7 +313,7 @@ pop_box_new(htmlvalue, 480, 182);
newResData2OldResData(newResData) {
newResData.latest_output = newResData.last_compile_output
// newResData.power
// newResData.power
newResData.record = newResData.record_onsume_time
// 老版用的hide_code
@ -329,7 +329,7 @@ pop_box_new(htmlvalue, 480, 182);
newResData.output_sets.test_sets = newResData.test_sets // JSON.stringify()
newResData.output_sets.test_sets_count = newResData.test_sets_count
// newResData.output_sets.had_passed_testsests_error_count = newResData.sets_error_count
newResData.output_sets.had_passed_testsests_error_count = newResData.test_sets_count
newResData.output_sets.had_passed_testsests_error_count = newResData.test_sets_count
- newResData.sets_error_count
// allowed_hidden_testset
// sets_error_count
@ -354,8 +354,8 @@ pop_box_new(htmlvalue, 480, 182);
let output_sets = resData.output_sets;
if (resData.st === 0) { // 代码题
challenge = this.handleChallengePath(challenge)
const mirror_name = (resData.mirror_name && resData.mirror_name.join)
const mirror_name = (resData.mirror_name && resData.mirror_name.join)
? resData.mirror_name.join(';') : (resData.mirror_name || '');
if (mirror_name.indexOf('Html') !== -1) {
challenge.isHtml = true;
@ -364,7 +364,7 @@ pop_box_new(htmlvalue, 480, 182);
challenge.isWeb = true;
} else if (mirror_name.indexOf('Android') !== -1) {
challenge.isAndroid = true;
}
}
if (output_sets && output_sets.test_sets && typeof output_sets.test_sets == 'string') {
const test_sets_array = JSON.parse("[" + output_sets.test_sets + "]");
@ -378,7 +378,7 @@ pop_box_new(htmlvalue, 480, 182);
const $ = window.$
window.setTimeout(()=>{
var lens = $("#choiceRepositoryView textarea").length;
for(var i = 1; i <= lens; i++){
window.editormd.markdownToHTML("choose_subject_" + i, {
htmlDecode: "style,script,iframe", // you can filter tags decode
@ -404,14 +404,14 @@ pop_box_new(htmlvalue, 480, 182);
game.isPassThrough = true
}
resData.game = game;
const { tpm_cases_modified, tpm_modified, tpm_script_modified, myshixun } = resData;
if (myshixun.system_tip) {
// system_tip为true的时候 不弹框提示用户更新
resData.showUpdateDialog = false
} else {
let needUpdateScript = (tpm_modified || tpm_script_modified) && challenge.st === 0;
resData.showUpdateDialog = needUpdateScript || tpm_cases_modified
resData.showUpdateDialog = needUpdateScript || tpm_cases_modified
}
/**
@ -458,7 +458,7 @@ pop_box_new(htmlvalue, 480, 182);
// const EDU_NORMAL = 7 // 普通用户
/**
/**
EDU_ADMIN = 1 # 超级管理员
EDU_BUSINESS = 2 # 运营人员
EDU_SHIXUN_MANAGER = 3 # 实训管理员
@ -467,7 +467,7 @@ pop_box_new(htmlvalue, 480, 182);
EDU_GAME_MANAGER = 6 # TPI的创建者
EDU_TEACHER = 7 # 平台老师,但是未认证
EDU_NORMAL = 8 # 普通用户
*/
*/
// myshixun_manager power is_teacher
resData.power = 0
@ -495,8 +495,8 @@ pop_box_new(htmlvalue, 480, 182);
} else if (resData.user.identity === EDU_TEACHER) {
// resData.is_teacher = true
} else if (resData.user.identity === EDU_NORMAL) {
}
}
return resData
}
@ -524,7 +524,7 @@ pop_box_new(htmlvalue, 480, 182);
loading: true,
currentGamePassed: false, // 切换game时重置passed字段
})
// test
// var data = {"st":0,"discusses_count":0,"game_count":3,"record_onsume_time":0.36,"prev_game":null,"next_game":"7p9xwo2hklqv","praise_count":0,"user_praise":false,"time_limit":20,"tomcat_url":"http://47.96.157.89","is_teacher":false,"myshixun_manager":true,"game":{"id":2192828,"myshixun_id":580911,"user_id":57844,"created_at":"2019-09-03T15:50:49.000+08:00","updated_at":"2019-09-03T15:51:05.000+08:00","status":2,"final_score":0,"challenge_id":10010,"open_time":"2019-09-03T15:50:49.000+08:00","identifier":"hknvz4oaw825","answer_open":0,"end_time":"2019-09-03T15:51:04.000+08:00","retry_status":0,"resubmit_identifier":null,"test_sets_view":false,"picture_path":null,"accuracy":1.0,"modify_time":"2019-09-03T15:23:33.000+08:00","star":0,"cost_time":14,"evaluate_count":1,"answer_deduction":0},"challenge":{"id":10010,"shixun_id":3516,"subject":"1.1 列表操作","position":1,"task_pass":"[TOC]\n\n---\n\n####任务描述\n\n\n数据集a包含1-10共10个整数请以a为输入数据编写python程序实现如下功能\n①\t用2种方法输出a中所有奇数\n②\t输出大于3小于7的偶数\n③\t用2种方法输出[1,2,3,…10,11,…20]\n④\t输出a的最大值、最小值。\n⑤\t用2种方法输出[10,9,…2,1]\n⑥\t输出[1,2,3,1,2,3,1,2,3,1,2,3]\n\n\n####相关知识\n\n\n请自行学习相关知识\n\n\n---\n开始你的任务吧祝你成功","score":100,"path":"1-1-stu.py","st":0,"web_route":null,"modify_time":"2019-09-03T15:23:33.000+08:00","exec_time":20,"praises_count":0},"shixun":{"id":3516,"name":"作业1——Python程序设计","user_id":77620,"gpid":null,"visits":23,"created_at":"2019-09-03T14:18:17.000+08:00","updated_at":"2019-09-03T15:58:16.000+08:00","status":0,"language":null,"authentication":false,"identifier":"6lzjig58","trainee":1,"major_id":null,"webssh":2,"homepage_show":false,"hidden":false,"fork_from":null,"can_copy":true,"modify_time":"2019-09-03T14:18:17.000+08:00","reset_time":"2019-09-03T14:18:17.000+08:00","publish_time":null,"closer_id":null,"end_time":null,"git_url":null,"vnc":null,"myshixuns_count":3,"challenges_count":3,"use_scope":0,"mirror_script_id":20,"image_text":null,"code_hidden":false,"task_pass":true,"exec_time":20,"test_set_permission":true,"sigle_training":false,"hide_code":false,"multi_webssh":false,"excute_time":null,"repo_name":"p09218567/6lzjig58","averge_star":5.0,"opening_time":null,"users_count":1,"forbid_copy":false,"pod_life":0},"myshixun":{"id":580911,"shixun_id":3516,"is_public":true,"user_id":57844,"gpid":null,"created_at":"2019-09-03T15:50:49.000+08:00","updated_at":"2019-09-03T15:59:04.000+08:00","status":0,"identifier":"k36hm4rwav","commit_id":"f25e1713882156480fc45ce0af57eff395a5037f","modify_time":"2019-09-03T14:18:17.000+08:00","reset_time":"2019-09-03T14:18:17.000+08:00","system_tip":false,"git_url":null,"onclick_time":"2019-09-03T15:50:49.000+08:00","repo_name":"p53276410/k36hm4rwav20190903155049"},"user":{"user_id":57844,"login":"p53276410","name":"文振乾","grade":24624,"identity":1,"image_url":"avatars/User/57844","school":"EduCoder团队"},"tpm_modified":true,"tpm_cases_modified":false,"mirror_name":["Python3.6"],"has_answer":false,"test_sets":[{"is_public":true,"result":true,"input":"","output":"result of a:\n[1, 3, 5, 7, 9]\n[1, 3, 5, 7, 9]\nresult of b:\n[2, 4, 6, 8, 10]\nresult of c:\n[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]\n[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]\nresult of d:\nThe minimum is:1\nThe maxium is:10\nresult of e:\n[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]\n[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\nresult of f:\n[10, 9, 8, 10, 9, 8, 10, 9, 8, 10, 9, 8]\n","actual_output":"result of a:\r\n[1, 3, 5, 7, 9]\r\n[1, 3, 5, 7, 9]\r\nresult of b:\r\n[2, 4, 6, 8, 10]\r\nresult of c:\r\n[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]\r\n[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]\r\nresult of d:\r\nThe minimum is:1\r\nThe maxium is:10\r\nresult of e:\r\n[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]\r\n[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\r\nresult of f:\r\n[10, 9, 8, 10, 9, 8, 10, 9, 8, 10, 9, 8]\r\n","compile_success":1,"ts_time":0.05,"ts_mem":8.77}],"allowed_unlock":true,"last_compile_output":"compile successfully","test_sets_count":1,"sets_error_count":0}
// data.test_sets[0].actual_output = data.test_sets[0].actual_output.replace(/\r\n/g, '\n')
@ -534,7 +534,7 @@ pop_box_new(htmlvalue, 480, 182);
// data.vnc_url= "http://47.96.157.89:41158/vnc_lite.html?password=headless"
// this._handleResponseData(data)
// return
// return
axios.get(url, {
// https://stackoverflow.com/questions/48861290/the-value-of-the-access-control-allow-origin-header-in-the-response-must-not-b
@ -550,7 +550,7 @@ pop_box_new(htmlvalue, 480, 182);
return;
}
if (response.data.status == 404) {
// 如果第一次发生404则隔1s后再调用一次本接口因为ucloud主从同步可能有延迟
// 如果第一次发生404则隔1s后再调用一次本接口因为ucloud主从同步可能有延迟
if (!noTimeout) {
setTimeout(() => {
this.fetchAll(stageId, true)
@ -562,12 +562,12 @@ pop_box_new(htmlvalue, 480, 182);
}
this._handleResponseData(response.data)
})
.catch(function (error) {
console.log(error);
});
}
readGameAnswer(resData) {
@ -583,7 +583,7 @@ pop_box_new(htmlvalue, 480, 182);
grade: resData.grade
})
}
}
closeTaskResultLayer() {
this.setState({
@ -605,7 +605,7 @@ pop_box_new(htmlvalue, 480, 182);
currentGamePassed = true;
this._updateCostTime(true, true);
}
this.setState({
@ -618,14 +618,14 @@ pop_box_new(htmlvalue, 480, 182);
currentPassedGameGainGold: gold,
currentPassedGameGainExperience: experience,
})
}
}
initDisplayInterval = () => {
const challenge = this.state.challenge
if (this.showWebDisplayButtonTimeout) {
window.clearTimeout(this.showWebDisplayButtonTimeout)
}
this.showWebDisplayButtonTimeout = window.setTimeout(() => {
this.setState({ challenge: update(challenge,
this.setState({ challenge: update(challenge,
{
showWebDisplayButton: { $set: false },
})
@ -650,7 +650,7 @@ pop_box_new(htmlvalue, 480, 182);
this.displayInterval = null
return;
}
remain -= 1;
}, 1000)
}
@ -716,7 +716,7 @@ pop_box_new(htmlvalue, 480, 182);
const currentGamePassed = this.props.game !== 2 && status === 2
// 评测通过了立即同步costTime
currentGamePassed && this._updateCostTime(true, true);
@ -738,7 +738,7 @@ pop_box_new(htmlvalue, 480, 182);
// const test_sets_array = JSON.parse("[" + output_sets.test_sets + "]");
// output_sets.test_sets_array = test_sets_array;
// }
// 检查是否编译通过
let compileSuccess = false;
if (test_sets && test_sets.length) {
@ -754,7 +754,7 @@ pop_box_new(htmlvalue, 480, 182);
if (currentGamePassed) {
game.status = 2;
// game.isPassThrough = true
game.next_game = next_game;
game.next_game = next_game;
} else {
this.showDialog({
contentText: <div>
@ -764,7 +764,7 @@ pop_box_new(htmlvalue, 480, 182);
isSingleButton: true
})
}
this.setState({
testSetsExpandedArray: testSetsExpandedArrayInitVal.slice(0), // 重置测试集展开状态
@ -775,12 +775,12 @@ pop_box_new(htmlvalue, 480, 182);
output_sets,
game,
next_game,
latest_output: last_compile_output,
record: record_consume_time,
grade,
had_done,
})
}
resetTestSetsExpandedArray = () => {
@ -809,15 +809,15 @@ pop_box_new(htmlvalue, 480, 182);
output_sets = Object.assign({}, output_sets);
// const test_sets_array = JSON.parse("[" + response.data.test_sets + "]");
output_sets.test_sets_array = response.data.test_sets;
this.setState({
this.setState({
output_sets: output_sets,
grade: this.state.grade + deltaScore,
game : update(game, {test_sets_view: { $set: true }}),
testSetsExpandedArray: testSetsExpandedArrayInitVal.slice(0)
game : update(game, {test_sets_view: { $set: true }}),
testSetsExpandedArray: testSetsExpandedArrayInitVal.slice(0)
})
this.handleGdialogClose();
}
})
.catch(function (error) {
console.log(error);
@ -841,10 +841,10 @@ pop_box_new(htmlvalue, 480, 182);
})
}
/*
/*
TODO 写成HOC组件更好复用
全局的Dialog this.props.showDialog调用即可
@param contentText dialog显示的提示文本
@param contentText dialog显示的提示文本
@param callback 确定按钮回调方法
@param moreButtonsRender 除了确定取消按钮外的其他按钮
@param okButtonText 确定按钮显示文本 继续查看
@ -908,13 +908,13 @@ pop_box_new(htmlvalue, 480, 182);
match: this.props.match
}}
>
>
<Dialog
id="tpi-dialog"
open={this.state.gDialogOpen}
disableEscapeKeyDown={true}
onClose={() => this.handleGdialogClose()}
>
>
<DialogTitle id="alert-dialog-title">{"提示"}</DialogTitle>
<DialogContent id="dialog-content">
<DialogContentText id="alert-dialog-description" style={{textAlign: 'center'}}>
@ -930,7 +930,7 @@ pop_box_new(htmlvalue, 480, 182);
>知道啦</a>
</div> :
<React.Fragment>
<Button onClick={() => this.handleGdialogClose()} color="primary"
<Button onClick={() => this.handleGdialogClose()} color="primary"
className={`${classes.button} ${classes.buttonGray} ${classes.borderRadiusNone}`}>
关闭
</Button>
@ -938,7 +938,7 @@ pop_box_new(htmlvalue, 480, 182);
onClick={() => this.onGdialogOkBtnClick() } color="primary" autoFocus>
{ this.okButtonText ? this.okButtonText : '确定' }
</Button>
</React.Fragment> }
</React.Fragment> }
{this.moreButtonsRender && this.moreButtonsRender()}
</DialogActions>
</Dialog>

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

@ -494,7 +494,7 @@ class NewShixunModel extends Component{
<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>
<p className={belongtoindex===1?"clickbutstwo ml20":"clickbutstwos ml20"} onClick={()=>this.belongto("mine")}>我的实训</p>
</div>:""
}
{/*{this.props.type==='shixuns'? <Dropdown overlay={menus}>*/}
@ -581,11 +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 ml20 mt3 intermediatecenter">
<p className="myysljupytertest">
Jupyter
</p>
</div>
:""
)
:""
}
<div className="cl"></div>
<style>
{

@ -385,6 +385,29 @@
.newshixunmodels{
margin: 0 auto;
}
.myysljupyter{
width:54px;
height:24px;
text-align: center;
border-radius:5px;
border:1px solid #FF6802;
margin-top: 4px;
}
.myysljupytertest{
width:54px;
height:16px;
line-height:16px;
font-size:12px;
color:#FF6802;
line-height:16px;
}
.intermediatecenter{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
/* 中间居中 */
.intermediatecenter{

@ -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>

@ -4005,7 +4005,7 @@ class Listofworksstudentone extends Component {
height: 58px;
}
.ysltableows .ant-table-thead > tr > th, .ant-table-tbody > tr > td {
padding: 9px;
padding: 9px;
}
`
}

@ -14,7 +14,7 @@ function UserInfo (props) {
const {image_url, name} = props.userInfo;
return (
<div className={'avator_nicker'}>
<img alt="用户头像" className={'student_img'} src={getImageUrl(`images/${image_url}` || 'images/educoder/headNavLogo.png?1526520218')} />
<img style={{ display: image_url ? 'inline-block' : 'none'}} alt="用户头像" className={'student_img'} src={getImageUrl(`images/${image_url}` || 'images/educoder/headNavLogo.png?1526520218')} />
<span className={'student_nicker'}>
{name || ''}
</span>

@ -1,11 +1,14 @@
.banner-wrap{
width: 100%;
height: 300px;
background-image: url(/static/media/path.e39ba7de.png);
background-color: #000a4f;
/* background-size: cover; */
background-position: center;
background-repeat: no-repeat;
// background-image: url(/static/media/path.e39ba7de.png);
// background: #000a4f url(../../images/oj//oj_banner.jpg) none center;
// background-color: #000a4f;
// /* background-size: cover; */
// background-position: center;
// background-repeat: no-repeat;
background: rgb(0, 1, 35) url(../../images/oj/oj_banner.jpg) no-repeat center;
background-size: cover;
}
.developer-list{

@ -334,6 +334,33 @@ class ShixunsHome extends Component {
.square-Item:nth-child(4n+0) {
margin-right: 25px;
}
.tag-org{
position: absolute;
left: 0px;
top: 20px;
}
.tag-org-name{
width:66px;
height:28px;
background:#FF6802;
width:66px;
height:28px;
border-radius:0px 20px 20px 0px;
}
.tag-org-name-test{
width:45px;
height:23px;
font-size:14px;
color:#FFFFFF;
line-height:19px;
margin-right: 6px;
}
.intermediatecenter{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
`
}
</style>
@ -345,7 +372,13 @@ class ShixunsHome extends Component {
<span className="tag-name"> {item.tag_name}</span>
{/*<img style={{display:item.tag_name===null?"none":'block'}} src={require(`./tag2.png`)}/>*/}
</div>
{
item.is_jupyter===true?
<div className="tag-org">
<p className="tag-org-name intermediatecenter"> <span className="tag-org-name-test">Jupyter</span></p>
{/*<img style={{display:'block',height: '28px'}} src={require(`./shixunCss/tag2.png`)}/>*/}
</div>
:""}
<div className={item.power === false ? "closeSquare" : "none"}>
<img src={getImageUrl("images/educoder/icon/lockclose.svg")}
className="mt80 mb25"/>

@ -0,0 +1,54 @@
import React, {Component} from 'react';
import {
Button,
} from 'antd';
class Bottomsubmit extends Component {
constructor(props) {
super(props)
this.state = {}
}
cannelfun = () => {
// window.location.href=
this.props.history.replace(this.props.url);
}
render() {
return (
<div>
<style>
{
`
.newFooter{
display:none;
}
`
}
</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()}
loading={this.props.loadings}><span>{this.props.bottomvalue===undefined?"确 定":this.props.bottomvalue}</span></Button>
</div>
</div>
</div>
);
}
}
export default Bottomsubmit;

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

@ -23,7 +23,7 @@ import { isThisSecond } from 'date-fns';
monaco.editor.defineTheme('myCoolTheme', {
base: 'vs', // vs、vs-dark、hc-black
inherit: true,
rules: [
rules: [
{ token: 'green', background: 'FF0000', foreground: '00FF00', fontStyle: 'italic'},
{ token: 'red', foreground: 'FF0000' , fontStyle: 'bold underline'},
{ background: '#121c23' },
@ -37,7 +37,7 @@ monaco.editor.defineTheme('myCoolTheme', {
// 'editor.selectionHighlightBorder': '#ffffff',
// 'input.border': '#ffffff',
'editor.lineHighlightBorder': '#222c34', // .current-line
// 'editor.selectionBackground': '#FFFF0030',
// 'editor.selectionHighlightBackground' :'#0000FFFF',
}
@ -60,7 +60,7 @@ function loadMonacoResouce(callback) {
// https://github.com/Microsoft/monaco-editor/issues/82
// window.require = { paths: { 'vs': '../node_modules/monaco-editor/min/vs' } };
// window.require = { paths: { 'vs': `${_url_origin}${prefix}/js/monaco/vs` } };
// $('head').append($('<link rel="stylesheet" type="text/css" />')
// .attr('href', `${_url_origin}${prefix}/js/monaco/vs/editor/editor.main.css`));
@ -68,7 +68,7 @@ function loadMonacoResouce(callback) {
// cache: true
// });
// $.when(
// // $.getScript( `${_url_origin}${prefix}/js/monaco/vs/language/typescript/tsMode.js` ),
// // $.getScript( `${_url_origin}${prefix}/js/monaco/vs/basic-languages/javascript/javascript.js` ),
// $.getScript( `${_url_origin}${prefix}/js/monaco/vs/basic-languages/python/python.js` ),
@ -79,7 +79,7 @@ function loadMonacoResouce(callback) {
// $.getScript( `${_url_origin}${prefix}/js/monaco/vs/editor/editor.main.js` ),
// $.Deferred(function( deferred ){
// console.log('Deferred')
// // TODO 暂时放这里
// $( deferred.resolve );
// checkIfLoaded(callback);
@ -126,7 +126,7 @@ function checkIfLoaded (callback) {
}
/*
language
language
javascript css less scss html typescript
java ruby vb r python php perl go cpp csharp
sql pgsql mysql
@ -155,7 +155,7 @@ function checkIfLoaded (callback) {
version: 3,
singleLineStringErrors: false
},
*/
*/
const mirror2LanguageMap = {
'JFinal': 'java',
'Java': 'java',
@ -194,10 +194,10 @@ function getLanguageByMirrorName(mirror_name) {
let notCallCodeMirrorOnChangeFlag = false;
/**
props :
/**
props :
mirror_name 决定语言
isEditablePath
isEditablePath
repositoryCode
codemirrorDidMount
@ -218,7 +218,7 @@ class TPIMonaco extends Component {
autoCompleteSwitch: fromStore('autoCompleteSwitch', true),
}
}
componentDidUpdate(prevProps, prevState, snapshot) {
const { mirror_name } = this.props
const editor_monaco = this.editor_monaco;
@ -234,20 +234,20 @@ class TPIMonaco extends Component {
} else {
editor_monaco.updateOptions({readOnly: true})
}
} else if (editor_monaco && prevProps.codeLoading === true && this.props.codeLoading === false
} else if (editor_monaco && prevProps.codeLoading === true && this.props.codeLoading === false
) {
if (this.props.repositoryCode != editor_monaco.getValue()) {
// newProps.repositoryCode !== this.props.repositoryCode &&
// newProps.repositoryCode !== this.props.repositoryCode &&
notCallCodeMirrorOnChangeFlag = true;
// 重要setState(因获取代码、重置代码等接口引起的调用)调用引起的变化才需要setValue
editor_monaco.setValue(this.props.repositoryCode)
}
// 代码没变也需要layout可能从命令行自动切回了代码tab
editor_monaco.layout();
// Clears the editor's undo history.
// TODO
// extend_editor.clearHistory()
@ -258,7 +258,7 @@ class TPIMonaco extends Component {
// 注意销毁不然会出现不能编辑的bug
this.editor_monaco && this.editor_monaco.dispose()
}
componentDidMount() {
checkIfLoaded(() => {
let value = [
@ -290,7 +290,7 @@ class TPIMonaco extends Component {
// http://testeduplus2.educoder.net/react/build/static/node_modules/_monaco-editor@0.15.6@monaco-editor/esm/vs/editor/common/config/commonEditorConfig.js
// https://github.com/Microsoft/monaco-editor/issues/29
scrollBeyondLastLine: false,
language: lang,
// language: 'css',
@ -304,7 +304,7 @@ class TPIMonaco extends Component {
window.editor_monaco = editor;
this.editor_monaco = editor
// editor.setPosition({ lineNumber: 2, column: 30 });
// editor.model.onDidChangeContent((event) => {
@ -323,27 +323,27 @@ class TPIMonaco extends Component {
this.props.onRepositoryCodeUpdate && this.props.onRepositoryCodeUpdate(editor.getValue())
})
this.props.codemirrorDidMount && this.props.codemirrorDidMount()
if(this.props.shixun && this.props.shixun.forbid_copy == true) {
// 禁用粘贴
// https://github.com/Microsoft/monaco-editor/issues/100
window.editor_monaco.onDidPaste( (a, b, c) => { window.__pastePosition = a })
window.addEventListener('paste', (a, b, c) => {
const selection = window.editor_monaco.getSelection();
const range = new monaco.Range(
window.__pastePosition.startLineNumber || selection.endLineNumber,
window.__pastePosition.startColumn || selection.endColumn,
window.__pastePosition.endLineNumber || selection.endLineNumber,
window.__pastePosition.endColumn || selection.endColumn,);
window.addEventListener('paste', (a, b, c) => {
const selection = window.editor_monaco.getSelection();
const range = new monaco.Range(
window.__pastePosition.startLineNumber || selection.endLineNumber,
window.__pastePosition.startColumn || selection.endColumn,
window.__pastePosition.endLineNumber || selection.endLineNumber,
window.__pastePosition.endColumn || selection.endColumn,);
window.editor_monaco.executeEdits('', [{range, text: ''}] )
})
})
// 禁用复制
window.editor_monaco.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_C, () => null);
window.editor_monaco.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_V, () => null);
}
setTimeout(() => {
editor.layout();
@ -352,23 +352,23 @@ class TPIMonaco extends Component {
window.editor_monaco.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S, () => {
this.props.doFileUpdateRequestOnCodeMirrorBlur();
return false;
return false;
});
window.editor_monaco.onDidBlurEditorWidget(() => {
window.editor_monaco.onDidBlurEditorWidget(() => {
this.props.onEditBlur && this.props.onEditBlur();
})
})
})
// window.document.onkeydown = (e) => {
// window.document.onkeydown = (e) => {
// e = window.event || e;
// if(e.keyCode== 83 && e.ctrlKey){
// if(e.keyCode== 83 && e.ctrlKey){
// /*延迟兼容FF浏览器 */
// // setTimeout(function(){
// // alert('ctrl+s');
// // },1);
// // alert('ctrl+s');
// // },1);
// this.props.doFileUpdateRequestOnCodeMirrorBlur();
// return false;
// }
// return false;
// }
// };
}
onFontSizeChange = (value) => {
@ -390,7 +390,7 @@ class TPIMonaco extends Component {
render() {
const { repositoryCode, showSettingDrawer, settingDrawerOpen } = this.props;
const { cmFontSize } = this.state;
return (
<React.Fragment>
<Drawer
@ -400,10 +400,10 @@ class TPIMonaco extends Component {
width={260}
open={settingDrawerOpen}
onClose={() => showSettingDrawer( false )}
>
<TPICodeSetting {...this.props} {...this.state}
<TPICodeSetting {...this.props} {...this.state}
onFontSizeChange={this.onFontSizeChange}
onCodeModeChange={this.onCodeModeChange}
onAutoCompleteSwitchChange={this.onAutoCompleteSwitchChange}
@ -436,7 +436,7 @@ export var EDITOR_DEFAULTS = {
wordWrap: 'off',
wordWrapColumn: 80,
wordWrapMinified: true,
wrappingIndent: 1
wrappingIndent: 1
wordWrapBreakBeforeCharacters: '([{‘“〈《「『【〔([{「£¥$£¥+',
wordWrapBreakAfterCharacters: ' \t})]?|&,;¢°′″‰℃、。。、¢,.:;?!%・・ゝゞヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻ァィゥェォャュョッー”〉》」』】〕)]}」',
wordWrapBreakObtrusiveCharacters: '.',
@ -555,4 +555,4 @@ export var EDITOR_DEFAULTS = {
};
*/
*/

@ -1,12 +1,13 @@
import React, { Component } from 'react';
import {getImageUrl} from 'educoder';
import {Modal,Input} from 'antd';
import {Modal,Input,Form,Radio} from 'antd';
class Addshixuns extends Component {
constructor(props) {
super(props);
this.state = {
shixunname:undefined,
shixunzero:false
shixunzero:false,
is_jupyter:"1"
}
}
@ -52,12 +53,22 @@ class Addshixuns extends Component {
})
return
}
this.props.Setaddshixuns(shixunname);
let is_jupyter=this.state.is_jupyter==="1"?false:true
this.props.Setaddshixuns(shixunname,is_jupyter);
this.props.modalCancel();
}
GrouponChange = e => {
this.setState({
is_jupyter: e.target.value,
});
};
render() {
const formItemLayout = {
labelCol: { span: 4 },
wrapperCol: { span: 14 },
};
return(
<Modal
className={this.props.className}
@ -81,6 +92,14 @@ class Addshixuns extends Component {
</style>:""}
<div className="task-popup-content">
<Form {...formItemLayout}>
<Form.Item label="实训类型">
<Radio.Group value={this.state.is_jupyter} onChange={this.GrouponChange}>
<Radio value="1">普通实训</Radio>
<Radio value="2">jupyter实训</Radio>
</Radio.Group>
</Form.Item>
</Form>
<p className="task-popup-text-center font-16">
<span style={{ "line-height":"30px"}}>实训名称</span>
<span><Input style={{ width:"80%"}} className="yslzxueshisy " placeholder="请输入60字以内的实训名称" onChange={this.handleChange} addonAfter={String(this.state.shixunname===undefined?0:this.state.shixunname.length)+"/60"} maxLength={60} />

@ -320,7 +320,7 @@ class DetailCardsEditAndAdd extends Component{
})
}
Getaddshixuns=(value)=>{
Getaddshixuns=(value,is_jupyter)=>{
let {
shixuns_listeditlist,
shixuns_listedit,
@ -329,7 +329,8 @@ class DetailCardsEditAndAdd extends Component{
let list=shixuns_listeditlist
let url='/paths/add_shixun_to_stage.json';
axios.post(url,{
name:value
name:value,
is_jupyter:is_jupyter
}).then((response) => {
if(response){
if(response.data){
@ -383,7 +384,7 @@ class DetailCardsEditAndAdd extends Component{
{this.state.Addshixunstype===true?<Addshixuns
modalCancel={this.cardsModalcancel}
Setaddshixuns={(value)=>this.Getaddshixuns(value)}
Setaddshixuns={(value,is_jupyter)=>this.Getaddshixuns(value,is_jupyter)}
{...this.props}
{...this.state}
/>:""}

@ -320,7 +320,7 @@ class DetailCardsEditAndEdit extends Component{
notification.open(data);
}
Getaddshixuns=(value)=>{
Getaddshixuns=(value,is_jupyter)=>{
let {
shixuns_listeditlist,
shixuns_listedit,
@ -329,7 +329,8 @@ class DetailCardsEditAndEdit extends Component{
let list=shixuns_listeditlist
let url='/paths/add_shixun_to_stage.json';
axios.post(url,{
name:value
name:value,
is_jupyter:is_jupyter
}).then((response) => {
if(response){
if(response.data){
@ -383,7 +384,7 @@ class DetailCardsEditAndEdit extends Component{
</Modals>
{this.state.Addshixunstype===true?<Addshixuns
modalCancel={this.cardsModalcancel}
Setaddshixuns={(value)=>this.Getaddshixuns(value)}
Setaddshixuns={(value,is_jupyter)=>this.Getaddshixuns(value,is_jupyter)}
{...this.props}
{...this.state}
/>:""}

@ -212,6 +212,7 @@ class Audit_situationComponent extends Component {
user={user}
shixun={shixun}
{...this.props}
is_jupyter={this.props.is_jupyter}
></TPMNav>
<div className="padding20 edu-back-white mt20" style={{minHeight: '640px'}}>

File diff suppressed because it is too large Load Diff

@ -1,54 +1,63 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import { CircularProgress } from 'material-ui/Progress';
import './TPMShixunDiscuss.css'
import Challenges from './shixunchild/Challenges/Challenges'
import TPMRightSection from './component/TPMRightSection'
import TPMNav from './component/TPMNav'
class TPMChallenge extends Component {
constructor(props) {
super(props)
}
render() {
const { loadingContent, shixun, user, match
} = this.props;
return (
<React.Fragment>
<div className="educontent clearfix mt30 mb80">
<div className="with65 fl edu-back-white" >
<TPMNav
match={match}
user={user}
shixun={shixun}
{...this.props}
></TPMNav>
<Challenges
{...this.props}
/>
</div>
<div className="with35 fr pl20">
<TPMRightSection
{...this.props}
/>
</div>
</div>
</React.Fragment>
);
}
}
export default TPMChallenge;
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import { CircularProgress } from 'material-ui/Progress';
import './TPMShixunDiscuss.css'
import Challenges from './shixunchild/Challenges/Challenges'
import Challengesjupyter from './shixunchild/Challenges/Challengesjupyter'
import TPMRightSection from './component/TPMRightSection'
import TPMNav from './component/TPMNav'
class TPMChallenge extends Component {
constructor(props) {
super(props)
}
render() {
const { loadingContent, shixun, user, match,jupyterbool,is_jupyter
} = this.props;
return (
<React.Fragment>
<div className="educontent clearfix mt30 mb80">
<div className="with65 fl edu-back-white" >
<TPMNav
match={match}
user={user}
shixun={shixun}
{...this.props}
is_jupyter={this.props.is_jupyter}
></TPMNav>
{
is_jupyter===true?
<Challengesjupyter
{...this.props}
/>
:
<Challenges
{...this.props}
/>
}
</div>
<div className="with35 fr pl20">
<TPMRightSection
{...this.props}
/>
</div>
</div>
</React.Fragment>
);
}
}
export default TPMChallenge;

@ -15,13 +15,16 @@ class TPMChallengeContainer extends Component {
render() {
const { tpmLoading } = this.props;
const user = this.props.current_user;
// console.log("TPMChallengeContainerTPMChallengeContainer");
// console.log(this.props);
return (
return (
<React.Fragment>
{ tpmLoading ? <div style={{ minHeight: '886px'}}></div> :
<TPMChallenge
{...this.props}
is_jupyter={this.props.is_jupyter}
>
</TPMChallenge>
}

@ -1,53 +1,54 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import { CircularProgress } from 'material-ui/Progress';
import './TPMShixunDiscuss.css'
import Collaborators from './shixunchild/Collaborators/Collaborators'
import TPMRightSection from './component/TPMRightSection'
import TPMNav from './component/TPMNav'
class TPMCollaborators extends Component {
constructor(props) {
super(props)
}
render() {
const { loadingContent, creator, shixun, myshixun, recommend_shixuns, current_user, watched,
aboutFocus, user, match
} = this.props;
return (
<React.Fragment>
<div className="educontent clearfix mt30 mb80">
<div className="with65 fl edu-back-white" >
<TPMNav
match={match}
user={user}
shixun={shixun}
{...this.props}
></TPMNav>
<Collaborators
{...this.props}
/>
</div>
<div className="with35 fr pl20">
<TPMRightSection
{...this.props}
/>
</div>
</div>
</React.Fragment>
);
}
}
export default TPMCollaborators;
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import { CircularProgress } from 'material-ui/Progress';
import './TPMShixunDiscuss.css'
import Collaborators from './shixunchild/Collaborators/Collaborators'
import TPMRightSection from './component/TPMRightSection'
import TPMNav from './component/TPMNav'
class TPMCollaborators extends Component {
constructor(props) {
super(props)
}
render() {
const { loadingContent, creator, shixun, myshixun, recommend_shixuns, current_user, watched,
aboutFocus, user, match
} = this.props;
return (
<React.Fragment>
<div className="educontent clearfix mt30 mb80">
<div className="with65 fl edu-back-white" >
<TPMNav
match={match}
user={user}
shixun={shixun}
{...this.props}
is_jupyter={this.props.is_jupyter}
></TPMNav>
<Collaborators
{...this.props}
/>
</div>
<div className="with35 fr pl20">
<TPMRightSection
{...this.props}
/>
</div>
</div>
</React.Fragment>
);
}
}
export default TPMCollaborators;

@ -1,47 +1,47 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import TPMCollaborators from './TPMCollaborators'
import axios from 'axios';
class TPMChallengeContainer extends Component {
constructor(props) {
super(props)
this.state = {
}
}
componentWillReceiveProps(newProps, newContext) {
}
componentDidMount() {
// this.props.showShixun();
}
render() {
const { tpmLoading } = this.props;
const user = this.props.current_user;
return (
<React.Fragment>
{ tpmLoading ? <div style={{ minHeight: '886px'}}></div> :
<TPMCollaborators
{...this.props}
{...this.state}
user={user}
aboutFocus={this.props.aboutFocus}
>
</TPMCollaborators>
}
</React.Fragment>
);
}
}
export default TPMChallengeContainer;
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import TPMCollaborators from './TPMCollaborators'
import axios from 'axios';
class TPMChallengeContainer extends Component {
constructor(props) {
super(props)
this.state = {
}
}
componentWillReceiveProps(newProps, newContext) {
}
componentDidMount() {
// this.props.showShixun();
}
render() {
const { tpmLoading } = this.props;
const user = this.props.current_user;
return (
<React.Fragment>
{ tpmLoading ? <div style={{ minHeight: '886px'}}></div> :
<TPMCollaborators
{...this.props}
{...this.state}
user={user}
aboutFocus={this.props.aboutFocus}
is_jupyter={this.props.is_jupyter}
>
</TPMCollaborators>
}
</React.Fragment>
);
}
}
export default TPMChallengeContainer;

@ -0,0 +1,593 @@
import React, {Component} from 'react';
import {Redirect} from 'react-router';
import {List, Typography, Tag, Modal, Radio, Checkbox, Table, Pagination,Upload,notification} from 'antd';
import { NoneData } from 'educoder'
import TPMRightSection from './component/TPMRightSection';
import TPMNav from './component/TPMNav';
import axios from 'axios';
import './tpmmodel/tpmmodel.css'
import {getUploadActionUrltwo,appendFileSizeToUploadFileAll} from 'educoder';
import moment from 'moment';
const confirm = Modal.confirm;
class TPMDataset extends Component {
constructor(props) {
super(props)
this.state = {
value: undefined,
columns: [
{
title: '文件',
dataIndex: 'title',
key: 'title',
align: 'left',
className: " font-14 wenjiantit",
width: '220px',
render: (text, record) => (
<div>
{record.title}
</div>
)
},
{
title: '最后修改时间',
dataIndex: 'timedata',
key: 'timedata',
align: 'center',
className: "edu-txt-center font-14 zuihoushijian",
width: '150px',
render: (text, record) => (
<div>
{record.timedata}
</div>
)
},
{
title: '最后修改人',
dataIndex: 'author',
key: 'author',
align: 'center',
className: "edu-txt-center font-14 ",
render: (text, record) => (
<div>
{record.author}
</div>
)
},
{
title: '文件大小',
dataIndex: 'filesize',
key: 'filesize',
align: 'center',
className: "edu-txt-center font-14 ",
render: (text, record) => (
<div>
{record.filesize}
</div>
)
},
],
page: 1,
limit: 10,
selectedRowKeys: [],
mylistansum:30,
collaboratorList:[],
fileList:[],
fileListimgs:[],
file:null,
datalist:[],
data_sets_count:0,
selectedRowKeysdata:[],
loadingstate:false,
checked: false,
}
}
componentDidMount() {
this.setState({
loadingstate:true,
})
this.getdatas()
}
mysonChange = (e) => {
// console.log(`全选checked = ${e.target.checked}`);
if (e.target.checked === true) {
let mydata=[];
let datas=[];
for(let i=0;i<this.state.collaboratorList.data_sets.length;i++){
mydata.push(this.state.collaboratorList.data_sets[i].id);
datas.push(i);
}
this.setState({
selectedRowKeysdata:mydata,
selectedRowKeys: datas,
checked:true,
})
// console.log(mydata);
// console.log(datas);
} else {
this.setState({
selectedRowKeysdata:[],
selectedRowKeys: [],
checked:false,
})
}
}
getdatas = () => {
let id=this.props.match.params.shixunId;
let collaborators=`/shixuns/${id}/get_data_sets.json`;
axios.get(collaborators,{params:{
page:1,
limit:10,
}}).then((response)=> {
if(response.status===200){
if (response.data.status === 403||response.data.status === 401||response.data.status === 500) {
}else{
let datalists=[];
for(let i=0;i<response.data.data_sets.length;i++){
const datas=response.data.data_sets;
var timedata = moment(datas[i].created_on).format('YYYY-MM-DD HH:mm');
datalists.push({
timedata:timedata,
author:datas[i].author,
filesize:datas[i].filesize,
id:datas[i].id,
title:datas[i].title,
})
}
this.setState({
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)
});
}
getdatastwo = (page,limit) => {
let id=this.props.match.params.shixunId;
let collaborators=`/shixuns/${id}/jupyter_data_sets.json`;
axios.get(collaborators,{params:{
page:page,
limit:limit,
}}).then((response)=> {
if(response.status===200){
if (response.data.status === 403||response.data.status === 401||response.data.status === 500) {
}else{
let datalists=[];
for(let i=0;i<response.data.data_sets.length;i++){
const datas=response.data.data_sets;
var timedata = moment(datas[i].created_on).format('YYYY-MM-DD HH:mm');
datalists.push({
timedata:timedata,
author:datas[i].author,
filesize:datas[i].filesize,
id:datas[i].id,
title:datas[i].title,
})
}
this.setState({
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)
});
}
getdatasthree = (page,limit) => {
let id=this.props.match.params.shixunId;
let collaborators=`/shixuns/${id}/jupyter_data_sets.json`;
axios.get(collaborators,{params:{
page:page,
limit:limit,
}}).then((response)=> {
if(response.status===200){
if (response.data.status === 403||response.data.status === 401||response.data.status === 500) {
}else{
}
}
}).catch((error)=>{
});
}
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(
{
selectedRowKeys
}
);
let mydata=[];
for(let i=0;i<selectedRows.length;i++){
mydata.push(selectedRows[i].id);
}
this.setState({
selectedRowKeysdata:mydata,
})
console.log(mydata);
}
rowClassName = (record, index) => {
let className = 'light-row';
if (index % 2 === 1) className = 'dark-row';
return className;
}
handleChange = (info) => {
if(info.file.status == "done" || info.file.status == "uploading" || info.file.status === 'removed'){
let fileList = info.fileList;
this.setState({
fileList: appendFileSizeToUploadFileAll(fileList),
});
if(info.file.status === 'done'){
//done 成功就会调用这个方法
this.getdatas();
}
}
}
onAttachmentRemove = (file) => {
// debugger
if(!file.percent || file.percent == 100){
confirm({
title: '确定要删除这个附件吗?',
okText: '确定',
cancelText: '取消',
// content: 'Some descriptions',
onOk: () => {
console.log("665")
this.deleteAttachment(file)
},
onCancel() {
console.log('Cancel');
},
});
return false;
}
}
deleteRemovedata(){
if(this.state.selectedRowKeysdata===undefined || this.state.selectedRowKeysdata===null ||this.state.selectedRowKeysdata.length===0){
this.props.showNotification(`请选择要删除的文件`);
return
}
let id=this.props.match.params.shixunId;
confirm({
title: '确定要删除文件吗?',
okText: '确定',
cancelText: '取消',
// content: 'Some descriptions',
onOk: () => {
const url = `/shixuns/${id}/destroy_data_sets.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);
let id=file.response ==undefined ? file.id : file.response.id
const url = `/attachements/destroy_files.json`
axios.delete(url, {
id:[id],
})
.then((response) => {
if (response.data) {
const { status } = response.data;
if (status == 0) {
// console.log('--- success')
this.setState((state) => {
const index = state.fileList.indexOf(file);
const newFileList = state.fileList.slice();
newFileList.splice(index, 1);
return {
fileList: newFileList,
deleteisnot:true
};
});
}
}
})
.catch(function (error) {
console.log(error);
});
}
render() {
const {tpmLoading, shixun, user, match} = this.props;
const {columns, page, limit, selectedRowKeys,mylistansum,fileList,datalist,data_sets_count,loadingstate} = this.state;
const rowSelection = {
selectedRowKeys,
onChange: this.onSelectChange,
};
// getCheckboxProps: record => ({
// disabled: record.name === 'Disabled User', // Column configuration not to be checked
// name: record.name,
// }),
let id=this.props.match.params.shixunId;
const uploadProps = {
width: 600,
fileList,
data:{
attachtype: 2,
container_id:this.props.match.params.shixunId,
container_type: "Shixun",
},
multiple: false,
//multiple 是否支持多选 查重的时候不能多选 不然弹许多框出来
// https://github.com/ant-design/ant-design/issues/15505
// showUploadList={false},然后外部拿到 fileList 数组自行渲染列表。
// showUploadList: false,
action: `${getUploadActionUrltwo(id)}`,
showUploadList:false,
onChange: this.handleChange,
onRemove: this.onAttachmentRemove,
beforeUpload: (file) => {
//上传前的操作
console.log('beforeUpload', file.name);
const isLt150M = file.size / 1024 / 1024 < 150;
if (!isLt150M) {
this.props.showNotification('文件大小必须小于150MB!');
}
return isLt150M;
},
};
return (
<React.Fragment>
<div className="tpmComment educontent clearfix mt30 mb80">
<div className="with65 fl edu-back-white commentsDelegateParent">
<TPMNav
match={match}
user={user}
shixun={shixun}
{...this.props}
is_jupyter={this.props.is_jupyter}
></TPMNav>
<div className="padding20 edu-back-white mt20 " style={{minHeight: '463px'}}>
<div className="sortinxdirection">
<div className="tpmwidth">
<Checkbox checked={this.state.checked} onChange={this.mysonChange}>全选</Checkbox>
</div>
<div className="tpmwidth xaxisreverseorder">
<style>
{
`
.ant-upload-list{
display:none
}
`
}
</style>
<div className="deletebuttom intermediatecenter "> <Upload {...uploadProps}><p className="deletebuttomtest" type="upload">
上传文件</p> </Upload></div>
{
data_sets_count>0?
<div
className={selectedRowKeys.length > 0 ? "deletebutomtextcode intermediatecenter mr21" : "deletebutom intermediatecenter mr21"} onClick={()=>this.deleteRemovedata()}>
<p className="deletebutomtext" >删除</p></div>
:""
}
</div>
</div>
<div className="mt24">
<style>{`
.ant-spin-nested-loading > div > .ant-spin .ant-spin-dot {
top: 72%;}
}
.edu-table .ant-table-tbody > tr > td {
height: 42px;
}
.edu-table .ant-table-thead > tr > th{
height: 42px;
}
.ysltableowss .ant-table-thead > tr > th{
height: 42px;
}
.ysltableowss .ant-table-tbody > tr > td{
height: 42px;
}
.ysltableowss .ant-table-thead > tr > th, .ant-table-tbody > tr > td {
padding: 9px;
}
.mysjysltable4 .ant-table-thead > tr > th, .ant-table-tbody > tr > td {
padding: 0px;
}
.ant-table-thead .ant-table-selection-column span{
visibility:hidden;
}
.ant-table-thead > tr > th {
background:#FFFFFF !important;
}
.ant-table table {
width: 100%;
text-align: left;
border-radius: 4px 4px 0 0;
border-collapse: separate;
border-spacing: 0;
border-left: 1px solid #eeeeee;
border-top: 1px solid #eeeeee;
border-right: 1px solid #eeeeee;
}
`}</style>
{data_sets_count===0?
<div className="edu-table edu-back-white ysltableowss">
<style>
{
`
.ant-table-tbody{
display:none;
}
.ant-table-placeholder{
display:none;
}
.ant-table table {
border-bottom: 1px solid #eeeeee !important;
}
`
}
</style>
<Table
columns={columns}
pagination={false}
className="mysjysltable4"
rowSelection={rowSelection}
rowClassName={this.rowClassName}
/>
</div>
:
<div className="edu-table edu-back-white ysltableowss">
<Table
dataSource={datalist}
columns={columns}
pagination={false}
className="mysjysltable4"
rowSelection={rowSelection}
rowClassName={this.rowClassName}
loading={loadingstate}
/>
</div>
}
{
data_sets_count>=11?
<div className="edu-txt-center mt40 mb20">
<Pagination showQuickJumper current={page}
onChange={this.paginationonChanges} pageSize={limit}
total={data_sets_count}
></Pagination>
</div>
:""
}
{ data_sets_count===0?
<NoneData style={{width: '100%'}}></NoneData>:""
}
</div>
</div>
</div>
<div className="with35 fr pl20">
<TPMRightSection
{...this.props}
/>
</div>
</div>
</React.Fragment>
);
}
}
export default TPMDataset;

@ -1,50 +1,50 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import TPMForklist from './TPMForklist'
import axios from 'axios';
class TPMRanking_listContainer extends Component {
constructor(props) {
super(props)
this.state = {
tpmLoading: true,
creator: {
owner_id: ''
}
}
}
componentWillReceiveProps(newProps, newContext) {
}
componentDidMount() {
this.props.showShixun();
}
render() {
const { tpmLoading } = this.props;
const user = this.props.current_user;
return (
<React.Fragment>
{ tpmLoading ? <div style={{ minHeight: '886px'}}></div> :
<TPMForklist
{...this.props}
{...this.state}
user={user}
aboutFocus={this.props.aboutFocus}
>
</TPMForklist>
}
</React.Fragment>
);
}
}
export default TPMRanking_listContainer;
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import TPMForklist from './TPMForklist'
import axios from 'axios';
class TPMRanking_listContainer extends Component {
constructor(props) {
super(props)
this.state = {
tpmLoading: true,
creator: {
owner_id: ''
}
}
}
componentWillReceiveProps(newProps, newContext) {
}
componentDidMount() {
this.props.showShixun();
}
render() {
const { tpmLoading } = this.props;
const user = this.props.current_user;
return (
<React.Fragment>
{ tpmLoading ? <div style={{ minHeight: '886px'}}></div> :
<TPMForklist
{...this.props}
{...this.state}
user={user}
aboutFocus={this.props.aboutFocus}
is_jupyter={this.props.is_jupyter}
>
</TPMForklist>
}
</React.Fragment>
);
}
}
export default TPMRanking_listContainer;

@ -1,63 +1,64 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import { CircularProgress } from 'material-ui/Progress';
import './TPMShixunDiscuss.css'
import Shixunfork_list from './shixunchild/Shixunfork_list'
import TPMRightSection from './component/TPMRightSection'
import TPMNav from './component/TPMNav'
class TPMForklist extends Component {
constructor(props) {
super(props)
}
componentWillReceiveProps(newProps, newContext) {
}
componentDidMount() {
}
render() {
const { loadingContent, creator, shixun, myshixun, recommend_shixuns, current_user, watched,
aboutFocus, user, match
} = this.props;
return (
<React.Fragment>
<div className="tpmComment educontent clearfix mt30 mb80">
<div className="with65 fl edu-back-white commentsDelegateParent" >
<TPMNav
match={match}
user={user}
shixun={shixun}
{...this.props}
></TPMNav>
{ loadingContent ?
<CircularProgress size={40} thickness={3} style={{ marginLeft: 'auto', marginRight: 'auto', marginTop: '200px', display: 'block' }}/> :
<Shixunfork_list/>
}
</div>
<div className="with35 fr pl20">
<TPMRightSection {...this.props}></TPMRightSection>
</div>
</div>
</React.Fragment>
);
}
}
export default TPMForklist;
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import { CircularProgress } from 'material-ui/Progress';
import './TPMShixunDiscuss.css'
import Shixunfork_list from './shixunchild/Shixunfork_list'
import TPMRightSection from './component/TPMRightSection'
import TPMNav from './component/TPMNav'
class TPMForklist extends Component {
constructor(props) {
super(props)
}
componentWillReceiveProps(newProps, newContext) {
}
componentDidMount() {
}
render() {
const { loadingContent, creator, shixun, myshixun, recommend_shixuns, current_user, watched,
aboutFocus, user, match
} = this.props;
return (
<React.Fragment>
<div className="tpmComment educontent clearfix mt30 mb80">
<div className="with65 fl edu-back-white commentsDelegateParent" >
<TPMNav
match={match}
user={user}
shixun={shixun}
{...this.props}
is_jupyter={this.props.is_jupyter}
></TPMNav>
{ loadingContent ?
<CircularProgress size={40} thickness={3} style={{ marginLeft: 'auto', marginRight: 'auto', marginTop: '200px', display: 'block' }}/> :
<Shixunfork_list/>
}
</div>
<div className="with35 fr pl20">
<TPMRightSection {...this.props}></TPMRightSection>
</div>
</div>
</React.Fragment>
);
}
}
export default TPMForklist;

@ -114,7 +114,7 @@ body>.-task-title {
/*-------------------个人主页:右侧提示区域--------------------------*/
.-task-sidebar{position:fixed;width:40px;height:180px;right:0;bottom:30px;z-index: 10;}
.-task-sidebar{position:fixed;width:40px;height:180px;right:0;bottom:80px !important;z-index: 10;}
.-task-sidebar>div{height: 40px;line-height: 40px;box-sizing: border-box;width:40px;background:#4CACFF;color:#fff;font-size:20px;text-align:center;margin-bottom:5px;border-radius: 4px;}
.-task-sidebar>div i{ color:#fff;}
.-task-sidebar>div i:hover{color: #fff!important;}

@ -22,13 +22,16 @@ import TPMRepositoryCommits from './shixunchild/Repository/TPMRepositoryCommits'
import TPMsettings from './TPMsettings/TPMsettings';
//import TPMsettings from './TPMsettings/oldTPMsettings';
import TPMChallengeComponent from './TPMChallengeContainer';
import TPMPropaedeuticsComponent from './TPMPropaedeuticsComponent';
import TPMRanking_listComponent from './TPMRanking_listContainer';
import TPMCollaboratorsComponent from './TPMCollaboratorsContainer';
import Audit_situationComponent from './Audit_situationComponent';
import TPMDataset from './TPMDataset';
import '../page/tpiPage.css'
import TPMNav from "./component/TPMNav";
const $ = window.$
//任务
@ -142,13 +145,15 @@ class TPMIndex extends Component {
identity:undefined,
TPMRightSectionData:undefined,
PropaedeuticsList: undefined,
tpmindexjupyterbool:false,
is_jupyter:false,
}
}
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) => {
@ -192,7 +197,7 @@ class TPMIndex extends Component {
propaedeutics:response.data.propaedeutics,
status: response.data.shixun_status,
secret_repository: response.data.secret_repository,
is_jupyter:response.data.is_jupyter=== undefined||response.data.is_jupyter===null?false:response.data.is_jupyter,
});
}
}).catch((error) => {
@ -204,7 +209,8 @@ class TPMIndex extends Component {
power: undefined,
identity: undefined,
status: undefined,
propaedeutics:undefined
propaedeutics:undefined,
is_jupyter:false,
});
});
@ -259,8 +265,8 @@ class TPMIndex extends Component {
axios.interceptors.request.eject(this.tpmContentResponseInterceptor);
this.tpmContentResponseInterceptor = null;
}
setLoadingContent = (isLoadingContent) => {
this.setState({ loadingContent: isLoadingContent })
}
@ -270,31 +276,42 @@ class TPMIndex extends Component {
// }
render() {
let url = window.location.href;
let flag = url.indexOf("add_file")>-1;
// console.log(this.state.is_jupyter);
return (
<div className="newMain clearfix">
{/*头部*/}
{
!flag &&
!flag &&
<TPMBanner
{...this.props}
{...this.state}
is_jupyter={this.state. is_jupyter}
></TPMBanner>
}
{/*筛选*/}
{/*{*/}
{/* tpmindexjupyterbool===false?*/}
{/* :""*/}
{/*}*/}
{/* */}
<Switch {...this.props}>
<Route path="/shixuns/:shixunId/repository/:repoId/commits" render={
(props) => (<TPMRepositoryCommits {...this.props} {...this.state} {...props}
(props) => (<TPMRepositoryCommits {...this.props} {...this.state} {...props} is_jupyter={this.state.is_jupyter}
/>)
}></Route>
<Route path="/shixuns/:shixunId/secret_repository/:repoId/commits" render={
(props) => (<TPMRepositoryCommits {...this.props} {...this.state} {...props} secret_repository_tab={true}
(props) => (<TPMRepositoryCommits {...this.props} {...this.state} {...props} secret_repository_tab={true} is_jupyter={this.state.is_jupyter}
/>)
}></Route>
{/*任务*/}
<Route exact path="/shixuns/:shixunId/challenges" render={
(props) => (<TPMChallengeComponent {...this.props} {...this.state} {...props}
(props) => (<TPMChallengeComponent {...this.props} {...this.state} {...props} is_jupyter={this.state.is_jupyter}
/>)
}></Route>
@ -304,33 +321,33 @@ class TPMIndex extends Component {
}></Route>
<Route path="/shixuns/:shixunId/repository" render={
(props) => (<TPMRepositoryComponent {...this.props} {...this.state} {...props}
(props) => (<TPMRepositoryComponent {...this.props} {...this.state} {...props} is_jupyter={this.state.is_jupyter}
/>)
}></Route>
<Route path="/shixuns/:shixunId/secret_repository" render={
(props) => (<TPMRepositoryComponent {...this.props} {...this.state} {...props} secret_repository_tab={true}
(props) => (<TPMRepositoryComponent {...this.props} {...this.state} {...props} secret_repository_tab={true} is_jupyter={this.state.is_jupyter}
/>)
}></Route>
{/* <Route exact path="/shixuns/:shixunId/propaedeutics" component={TPMPropaedeuticsComponent}></Route> */}
<Route exact path="/shixuns/:shixunId/propaedeutics" render={
(props) => (<TPMPropaedeuticsComponent {...this.props} {...this.state} {...props}
(props) => (<TPMPropaedeuticsComponent {...this.props} {...this.state} {...props} is_jupyter={this.state.is_jupyter}
/>)
}></Route>
<Route exact path="/shixuns/:shixunId/collaborators" render={
(props) => (<TPMCollaboratorsComponent {...this.props} {...this.state} {...props}
(props) => (<TPMCollaboratorsComponent {...this.props} {...this.state} {...props} is_jupyter={this.state.is_jupyter}
/>)
}></Route>
{/* <Route exact path="/shixuns/:shixunId/repository/:shixunId/" component={TPMRepositoryComponent}></Route> */}
<Route path="/shixuns/:shixunId/shixun_discuss" render={
(props) => (<TPMShixunDiscussContainer {...this.props} {...this.state} {...props}
(props) => (<TPMShixunDiscussContainer {...this.props} {...this.state} {...props} is_jupyter={this.state.is_jupyter}
initForumState={(data)=>this.initForumState(data)}
setSearchValue={this.setSearchValue}
setHotLabelIndex={this.setHotLabelIndex}
@ -342,14 +359,19 @@ class TPMIndex extends Component {
(props) => (<TPMsettings {...this.props} {...this.state} {...props} />)
}></Route>
{/*实训项目条目塞选*/}
<Route exact path="/shixuns/:shixunId/ranking_list" render={
(props) => (<TPMRanking_listComponent {...this.props} {...this.state} {...props}
(props) => (<TPMRanking_listComponent {...this.props} {...this.state} {...props} is_jupyter={this.state.is_jupyter}
/>)
}></Route>
{/*合作者*/}
<Route exact path="/shixuns/:shixunId/dataset" render={
(props) => (<TPMDataset {...this.props} {...this.state} {...props} is_jupyter={this.state.is_jupyter}
/>)
}></Route>
<Route exact path="/shixuns/:shixunId/audit_situation" render={
(props) => (<Audit_situationComponent {...this.props} {...this.state} {...props}
(props) => (<Audit_situationComponent {...this.props} {...this.state} {...props} is_jupyter={this.state.is_jupyter}
/>)
}></Route>
@ -400,7 +422,7 @@ class TPMIndex extends Component {
}></Route>
<Route exact path="/shixuns/:shixunId" render={
(props) => (<TPMChallengeComponent {...this.props} {...this.state} {...props}
(props) => (<TPMChallengeComponent {...this.props} {...this.state} {...props} is_jupyter={this.state.is_jupyter}
/>)
}></Route>

@ -23,7 +23,7 @@ const versionNum = '0001';
// let _url_origin = getUrl()
let _url_origin='';
if(window.location.port === "3007"){
_url_origin="http://pre-newweb.educoder.net";
_url_origin="https://test-newweb.educoder.net";
}
// let _url_origin=`https://www.educoder.net`;
@ -31,7 +31,7 @@ if(window.location.port === "3007"){
if (!window['indexHOCLoaded']) {
window.indexHOCLoaded = true;
//解决首屏加载问题
// $('head').append($('<link rel="stylesheet" type="text/css" />')
// .attr('href', `${_url_origin}/stylesheets/educoder/antd.min.css?1525440977`));
$('head').append($('<link rel="stylesheet" type="text/css" />')
@ -51,7 +51,7 @@ if (!window['indexHOCLoaded']) {
// setTimeout(() => {
// $('head').append( $('<link rel="stylesheet" type="text/css" />')
// .attr('href', `${_url_origin}/stylesheets/css/edu-common.css?1525440977`) );
// $('head').append( $('<link rel="stylesheet" type="text/css" />')
// .attr('href', `${_url_origin}/stylesheets/educoder/edu-all.css?1525440977`) );
// $('head').append( $('<link rel="stylesheet" type="text/css" />')
@ -60,7 +60,7 @@ if (!window['indexHOCLoaded']) {
$("script").append('<script></script>')
.attr('src', `${_url_origin}/javascripts/jquery-1.8.3-ui-1.9.2-ujs-2.0.3.js?_t=${versionNum}`);
}
// `${_url_origin}/javascripts/jquery-1.8.3-ui-1.9.2-ujs-2.0.3.js?_t=${versionNum}`
// TODO css加载完成后再打开页面行为和tpm其他页面一致
@ -146,7 +146,7 @@ export function TPMIndexHOC(WrappedComponent) {
componentWillUnmount() {
window.removeEventListener('keyup', this.keyupListener)
}
componentDidMount() {
// console.log("TPMIndexHOC========");
// console.log(this.props);
@ -216,7 +216,7 @@ export function TPMIndexHOC(WrappedComponent) {
this.getAppdata();
}
/**
课堂权限相关方法暂时写这里了 ----------------------------------------START
课堂权限相关方法暂时写这里了 ----------------------------------------START
ADMIN = 0 # 超级管理员
CREATOR = 1 # 课程创建者
PROFESSOR = 2 # 课程老师
@ -560,7 +560,7 @@ export function TPMIndexHOC(WrappedComponent) {
checkIfProfessionalCertification = () => {
return this.state.current_user && this.state.current_user.professional_certification
}
ShowOnlinePdf = (url) => {
return axios({
@ -642,14 +642,14 @@ export function TPMIndexHOC(WrappedComponent) {
isAdminOrCreator:this.isAdminOrCreator,
isClassManagement:this.isClassManagement,
isCourseAdmin:this.isCourseAdmin,
isAdmin: this.isAdmin,
isAdminOrTeacher: this.isAdminOrTeacher,
isStudent: this.isStudent,
isAdminOrStudent: this.isAdminOrStudent,
isNotMember: this.isNotMember,
isCourseEnd: this.isCourseEnd,
isUserid:this.state.coursedata&&this.state.coursedata.userid,
fetchUser: this.fetchUser,
@ -660,7 +660,7 @@ export function TPMIndexHOC(WrappedComponent) {
checkIfProfileCompleted: this.checkIfProfileCompleted,
checkIfProfessionalCertification: this.checkIfProfessionalCertification,
showProfessionalCertificationDialog: this.showProfessionalCertificationDialog,
ShowOnlinePdf:(url)=>this.ShowOnlinePdf(url),
DownloadFileA:(title,url)=>this.DownloadFileA(title,url),
DownloadOpenPdf:(type,url)=>this.DownloadOpenPdf(type,url),
@ -671,7 +671,7 @@ export function TPMIndexHOC(WrappedComponent) {
yslslowCheckresults:this.yslslowCheckresults,
yslslowCheckresultsNo:this.yslslowCheckresultsNo,
MdifHasAnchorJustScorll:this.MdifHasAnchorJustScorll
};
// console.log("this.props.mygetHelmetapi");
// console.log(this.props.mygetHelmetapi);
@ -742,7 +742,7 @@ export function TPMIndexHOC(WrappedComponent) {
}
`
}</style>
<NewHeader {...this.state} {...this.props}></NewHeader>
<Spin spinning={this.state.globalLoading} delay={0} className="globalSpin"
@ -765,9 +765,9 @@ export function TPMIndexHOC(WrappedComponent) {
{...this.state} {...this.props}
Footerdown={Footerdown}
/>
</div>
);
}
}
}
}

@ -1,74 +1,75 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import { CircularProgress } from 'material-ui/Progress';
import './TPMShixunDiscuss.css'
import Propaedeutics from './shixunchild/Propaedeutics/Propaedeu_tics'
import TPMRightSection from './component/TPMRightSection'
import TPMNav from './component/TPMNav'
import axios from 'axios';
class TPMPropaedeutics extends Component {
constructor(props) {
super(props)
this.state = {
shixunId: undefined
}
}
componentWillReceiveProps(newProps, newContext) {
}
componentDidMount() {
}
render() {
const { loadingContent, creator, shixun, myshixun, recommend_shixuns, current_user, watched,
aboutFocus, user, match
} = this.props;
// <Comments
// {...this.props}
// user={_user}
// onPaginationChange={this.onPaginationChange}
// ></Comments>
return (
<React.Fragment>
<div className="tpmComment educontent clearfix mt30 mb80">
<div className="with65 fl edu-back-white commentsDelegateParent" >
<TPMNav
match={match}
user={user}
shixun={shixun}
{...this.state}
{...this.props}
/>
<Propaedeutics
{...this.props}
{...this.state}
/>
</div>
<div className="with35 fr pl20">
<TPMRightSection {...this.props}></TPMRightSection>
</div>
</div>
</React.Fragment>
);
}
}
export default TPMPropaedeutics;
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import { CircularProgress } from 'material-ui/Progress';
import './TPMShixunDiscuss.css'
import Propaedeutics from './shixunchild/Propaedeutics/Propaedeu_tics'
import TPMRightSection from './component/TPMRightSection'
import TPMNav from './component/TPMNav'
import axios from 'axios';
class TPMPropaedeutics extends Component {
constructor(props) {
super(props)
this.state = {
shixunId: undefined
}
}
componentWillReceiveProps(newProps, newContext) {
}
componentDidMount() {
}
render() {
const { loadingContent, creator, shixun, myshixun, recommend_shixuns, current_user, watched,
aboutFocus, user, match
} = this.props;
// <Comments
// {...this.props}
// user={_user}
// onPaginationChange={this.onPaginationChange}
// ></Comments>
return (
<React.Fragment>
<div className="tpmComment educontent clearfix mt30 mb80">
<div className="with65 fl edu-back-white commentsDelegateParent" >
<TPMNav
match={match}
user={user}
shixun={shixun}
{...this.state}
{...this.props}
is_jupyter={this.props.is_jupyter}
/>
<Propaedeutics
{...this.props}
{...this.state}
/>
</div>
<div className="with35 fr pl20">
<TPMRightSection {...this.props}></TPMRightSection>
</div>
</div>
</React.Fragment>
);
}
}
export default TPMPropaedeutics;

@ -1,39 +1,40 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import TPMPropaedeutics from './TPMPropaedeutics'
import axios from 'axios';
class TPMPropaedeuticsComponent extends Component {
constructor(props) {
super(props)
this.state = {
// tpmLoading: true,
// creator: {
// owner_id: ''
// }
}
}
render() {
const { tpmLoading } = this.props;
return (
<React.Fragment>
{ tpmLoading ? <div style={{ minHeight: '886px'}}></div> :
<TPMPropaedeutics
{...this.props}
>
</TPMPropaedeutics>
}
</React.Fragment>
);
}
}
export default TPMPropaedeuticsComponent ;
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import TPMPropaedeutics from './TPMPropaedeutics'
import axios from 'axios';
class TPMPropaedeuticsComponent extends Component {
constructor(props) {
super(props)
this.state = {
// tpmLoading: true,
// creator: {
// owner_id: ''
// }
}
}
render() {
const { tpmLoading } = this.props;
return (
<React.Fragment>
{ tpmLoading ? <div style={{ minHeight: '886px'}}></div> :
<TPMPropaedeutics
{...this.props}
is_jupyter={this.props.is_jupyter}
>
</TPMPropaedeutics>
}
</React.Fragment>
);
}
}
export default TPMPropaedeuticsComponent ;

@ -1,59 +1,60 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import { CircularProgress } from 'material-ui/Progress';
import './TPMShixunDiscuss.css'
import Ranking_list from './shixunchild/Ranking_list/Ranking_list'
import TPMRightSection from './component/TPMRightSection'
import TPMNav from './component/TPMNav'
class TPMRanking_list extends Component {
constructor(props) {
super(props)
}
render() {
const { loadingContent, creator, shixun, myshixun, recommend_shixuns, current_user, watched,
aboutFocus, user, match
} = this.props;
// <Comments
// {...this.props}
// user={_user}
// onPaginationChange={this.onPaginationChange}
// ></Comments>
return (
<React.Fragment>
<div className="tpmComment educontent clearfix mt30 mb80">
<div className="with65 fl edu-back-white commentsDelegateParent" >
<TPMNav
match={match}
user={user}
shixun={shixun}
{...this.props}
></TPMNav>
<Ranking_list
{...this.props}
/>
</div>
<div className="with35 fr pl20">
<TPMRightSection {...this.props}></TPMRightSection>
</div>
</div>
</React.Fragment>
);
}
}
export default TPMRanking_list;
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import { CircularProgress } from 'material-ui/Progress';
import './TPMShixunDiscuss.css'
import Ranking_list from './shixunchild/Ranking_list/Ranking_list'
import TPMRightSection from './component/TPMRightSection'
import TPMNav from './component/TPMNav'
class TPMRanking_list extends Component {
constructor(props) {
super(props)
}
render() {
const { loadingContent, creator, shixun, myshixun, recommend_shixuns, current_user, watched,
aboutFocus, user, match
} = this.props;
// <Comments
// {...this.props}
// user={_user}
// onPaginationChange={this.onPaginationChange}
// ></Comments>
return (
<React.Fragment>
<div className="tpmComment educontent clearfix mt30 mb80">
<div className="with65 fl edu-back-white commentsDelegateParent" >
<TPMNav
match={match}
user={user}
shixun={shixun}
{...this.props}
is_jupyter={this.props.is_jupyter}
></TPMNav>
<Ranking_list
{...this.props}
/>
</div>
<div className="with35 fr pl20">
<TPMRightSection {...this.props}></TPMRightSection>
</div>
</div>
</React.Fragment>
);
}
}
export default TPMRanking_list;

@ -1,37 +1,40 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import TPMRanking_list from './TPMRanking_list'
import axios from 'axios';
class TPMRanking_listContainer extends Component {
constructor(props) {
super(props)
this.state = {
}
}
render() {
const { tpmLoading } = this.props;
const user = this.props.current_user;
return (
<React.Fragment>
{ tpmLoading ? <div style={{ minHeight: '886px'}}></div> :
<TPMRanking_list
{...this.props}
{...this.state}
user={user}
aboutFocus={this.props.aboutFocus}
>
</TPMRanking_list>
}
</React.Fragment>
);
}
}
export default TPMRanking_listContainer;
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import TPMRanking_list from './TPMRanking_list'
import axios from 'axios';
import TPMNav from "./component/TPMNav";
class TPMRanking_listContainer extends Component {
constructor(props) {
super(props)
this.state = {
}
}
render() {
const { tpmLoading } = this.props;
const user = this.props.current_user;
return (
<React.Fragment>
{ tpmLoading ? <div style={{ minHeight: '886px'}}></div> :
<TPMRanking_list
{...this.props}
{...this.state}
user={user}
aboutFocus={this.props.aboutFocus}
is_jupyter={this.props.is_jupyter}
>
</TPMRanking_list>
}
</React.Fragment>
);
}
}
export default TPMRanking_listContainer;

@ -1,58 +1,59 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import { CircularProgress } from 'material-ui/Progress';
import './TPMShixunDiscuss.css'
import Repository from './shixunchild/Repository/Repository'
import TPMRightSection from './component/TPMRightSection'
import TPMNav from './component/TPMNav'
// import RepositoryChooseModal from './component/modal/RepositoryChooseModal'
class TPMRepository extends Component {
constructor(props) {
super(props)
}
render() {
const { loadingContent, creator, shixun, myshixun, recommend_shixuns, current_user, watched,
aboutFocus, user, match, isContentWidth100
} = this.props;
return (
<React.Fragment>
<div className="tpmComment educontent clearfix mt30 mb80">
{/* 可能会影响到其他页面的样式,需要测试、协商 */}
<div className={`${isContentWidth100 ? 'width100': 'with65'} fl edu-back-white`}
style={{background: 'transparent'}}>
<TPMNav
match={match}
user={user}
shixun={shixun}
{...this.props}
></TPMNav>
{/* <RepositoryChooseModal {...this.props}></RepositoryChooseModal> */}
{ loadingContent ?
<CircularProgress size={40} thickness={3} style={{ marginLeft: 'auto', marginRight: 'auto', marginTop: '200px', display: 'block' }}/> :
<Repository
{...this.props}
/>
}
</div>
{ !isContentWidth100 && <div className="with35 fr pl20">
<TPMRightSection {...this.props}></TPMRightSection>
</div>}
</div>
</React.Fragment>
);
}
}
export default TPMRepository;
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import { CircularProgress } from 'material-ui/Progress';
import './TPMShixunDiscuss.css'
import Repository from './shixunchild/Repository/Repository'
import TPMRightSection from './component/TPMRightSection'
import TPMNav from './component/TPMNav'
// import RepositoryChooseModal from './component/modal/RepositoryChooseModal'
class TPMRepository extends Component {
constructor(props) {
super(props)
}
render() {
const { loadingContent, creator, shixun, myshixun, recommend_shixuns, current_user, watched,
aboutFocus, user, match, isContentWidth100
} = this.props;
return (
<React.Fragment>
<div className="tpmComment educontent clearfix mt30 mb80">
{/* 可能会影响到其他页面的样式,需要测试、协商 */}
<div className={`${isContentWidth100 ? 'width100': 'with65'} fl edu-back-white`}
style={{background: 'transparent'}}>
<TPMNav
match={match}
user={user}
shixun={shixun}
{...this.props}
is_jupyter={this.props.is_jupyter}
></TPMNav>
{/* <RepositoryChooseModal {...this.props}></RepositoryChooseModal> */}
{ loadingContent ?
<CircularProgress size={40} thickness={3} style={{ marginLeft: 'auto', marginRight: 'auto', marginTop: '200px', display: 'block' }}/> :
<Repository
{...this.props}
/>
}
</div>
{ !isContentWidth100 && <div className="with35 fr pl20">
<TPMRightSection {...this.props}></TPMRightSection>
</div>}
</div>
</React.Fragment>
);
}
}
export default TPMRepository;

@ -1,229 +1,230 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import TPMRepository from './TPMRepository'
import axios from 'axios';
import { trace_collapse, info } from 'educoder'
import RepositoryCodeEditor from './shixunchild/Repository/RepositoryCodeEditor'
class TPMRepositoryComponent extends Component {
constructor(props) {
super(props)
this.nameTypeMap = {}
let pathArray = []
var splitArray = window.location.pathname.split('shixun_show/');
if (splitArray[1]) {
pathArray = splitArray[1].split('/')
if (pathArray[pathArray.length - 1] == '') {
// 有可能是这么访问的: http://localhost:3007/shixuns/3ozvy5f8/repository/fsu7tkaw/master/shixun_show/src/
pathArray.length = pathArray.length - 1;
}
}
this.state = {
repositoryLoading: true,
pathArray: pathArray,
isContentWidth100: this._isFileInPathArray(pathArray)
}
}
componentDidUpdate(prevProps, prevState) {
if (this.props.secret_repository_tab != prevProps.secret_repository_tab) {
this.fetchRepo()
}
}
componentDidMount = () => {
this.fetchRepo()
}
setContentWidth100 = (flag) => {
const newFileContent = flag === false ? '' : this.state.fileContent
this.setState({
// isCodeFile
isContentWidth100: flag,
fileContent: newFileContent
})
}
saveCode = (content) => {
const path = this.state.pathArray.join('/')
let id = this.props.match.params.shixunId;
let url = `/shixuns/${id}/update_file.json`;
axios.post(url, {
path: path,
content
}).then((response) => {
if(response.status === 200){
this.setState({
fileContent: response.data.content,
repositoryLoading: false
});
}
trace_collapse('tpm save code res: ', response)
this.props.showSnackbar('文件保存成功')
}).catch((error)=>{
console.log(error)
});
}
fetchCode = (newPathArray) => {
const path = newPathArray.join('/')
// https://testeduplus2.educoder.net/shixuns/3ozvy5f8/file_content.json
this.setContentWidth100(true)
this.setState({ repositoryLoading: true, pathArray: newPathArray })
let id = this.props.match.params.shixunId;
let url = `/shixuns/${id}/file_content.json`;
axios.post(url, {
path: path,
secret_repository: this.props.secret_repository_tab
}).then((response) => {
trace_collapse('repository res: ', response)
if (response.data.status == -1) {
this.props.showSnackbar('无法找到对应的资源,请变更地址或联系管理员!')
return;
}
if(response.status === 200){
this.setState({
fileContent: response.data.content,
repositoryLoading: false
});
this.props.history
.replace(`${this.props.match.url}/master/shixun_show/${newPathArray.join('/')}`)
}
}).catch((error)=>{
this.props.showSnackbar('无法找到对应的资源,请变更地址或联系管理员!')
console.log(error)
});
}
_isFileName = (name) => {
return name.indexOf('.') !== -1
}
_isFileInPathArray = (array) => {
if (!array || array.length === 0) {
return false
}
return this.nameTypeMap[array[array.length - 1]] !== 'tree' && this._isFileName( array[array.length - 1] )
}
// listItem 如果是num则是通过面包屑点击过来的取pathArray的子集
fetchRepo = (listItem) => {
const { pathArray } = this.state;
let newPathArray = pathArray.slice(0)
if (listItem === 0 || listItem) {
this.setContentWidth100(false)
this.nameTypeMap[listItem.name] = listItem.type
if (typeof listItem == 'number') { // 参数是数字的话,做截取
// if (this._isFileName(newPathArray[listItem])) { // 面包屑中的文件不让点击了
// listItem--;
// }
newPathArray = newPathArray.slice(0, listItem)
} else if (listItem.type === 'tree') {
newPathArray.push(listItem.name)
} else if (listItem.type === 'blob') {
newPathArray.push(listItem.name)
this.setState({ pathArray: newPathArray })
this.fetchCode(newPathArray)
return;
}
}
// https://testeduplus2.educoder.net/shixuns/3ozvy5f8/repository.json
this.setState({ repositoryLoading: true, pathArray: newPathArray })
let urlNewPathArray = newPathArray;
let fileInPathArray = false;
if (newPathArray.length) {
fileInPathArray = this.nameTypeMap[newPathArray[newPathArray.length - 1]] ? this.nameTypeMap[newPathArray[newPathArray.length - 1]] !== 'tree'
: (listItem ? listItem.type !== 'tree' : this._isFileName( newPathArray[newPathArray.length - 1] ))
if ( fileInPathArray ) {
urlNewPathArray = newPathArray.slice(0, newPathArray.length - 1)
}
}
const path = urlNewPathArray.join('/')
let id = this.props.match.params.shixunId;
let url = `/shixuns/${id}/${this.props.secret_repository_tab ? 'secret_repository' : 'repository'}.json`;
// this.props.setLoadingContent(true)
axios.post(url, {
path: path ? path : ''
}).then((response) => {
// this.props.setLoadingContent(false)
const trees = response.data.trees
const treeIsFileMap = {}
if (!trees || !Array.isArray(trees)) {
// this.props.showSnackbar('无法找到对应的资源,请变更地址或联系管理员!')
// return;
} else {
trees.forEach(item => {
treeIsFileMap[item.name] = item.type == 'blob'
})
}
if(response.status === 200){
this.setState({
treeIsFileMap,
...response.data,
repositoryLoading: false
});
this.props.history
.replace(`${this.props.match.url}` +
(newPathArray.length ? `/master/shixun_show/${newPathArray.join('/')}` : ''))
}
// 初始化时repo接口完毕后需要看是否需要fetchCode
if (fileInPathArray) {
this.fetchCode(newPathArray)
}
// info(response)
trace_collapse('repository res: ', response)
}).catch((error)=>{
console.log(error)
});
}
render() {
const { isContentWidth100 } = this.state;
// 需要重构
return (
<React.Fragment>
{ !isContentWidth100 ? <TPMRepository
{...this.props}
{...this.state}
nameTypeMap={this.nameTypeMap}
fetchRepo={this.fetchRepo}
>
</TPMRepository>
:
<div className="tpmComment educontent clearfix mt30 mb80">
{/* 可能会影响到其他页面的样式,需要测试、协商 */}
<div className={`width100 fl edu-back-white`}
style={{background: 'transparent'}}>
<RepositoryCodeEditor
{...this.state}
{...this.props}
fetchRepo={this.fetchRepo}
saveCode={this.saveCode}
nameTypeMap={this.nameTypeMap}
></RepositoryCodeEditor>
</div>
</div>
}
</React.Fragment>
);
}
}
export default TPMRepositoryComponent ;
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import TPMRepository from './TPMRepository'
import axios from 'axios';
import { trace_collapse, info } from 'educoder'
import RepositoryCodeEditor from './shixunchild/Repository/RepositoryCodeEditor'
class TPMRepositoryComponent extends Component {
constructor(props) {
super(props)
this.nameTypeMap = {}
let pathArray = []
var splitArray = window.location.pathname.split('shixun_show/');
if (splitArray[1]) {
pathArray = splitArray[1].split('/')
if (pathArray[pathArray.length - 1] == '') {
// 有可能是这么访问的: http://localhost:3007/shixuns/3ozvy5f8/repository/fsu7tkaw/master/shixun_show/src/
pathArray.length = pathArray.length - 1;
}
}
this.state = {
repositoryLoading: true,
pathArray: pathArray,
isContentWidth100: this._isFileInPathArray(pathArray)
}
}
componentDidUpdate(prevProps, prevState) {
if (this.props.secret_repository_tab != prevProps.secret_repository_tab) {
this.fetchRepo()
}
}
componentDidMount = () => {
this.fetchRepo()
}
setContentWidth100 = (flag) => {
const newFileContent = flag === false ? '' : this.state.fileContent
this.setState({
// isCodeFile
isContentWidth100: flag,
fileContent: newFileContent
})
}
saveCode = (content) => {
const path = this.state.pathArray.join('/')
let id = this.props.match.params.shixunId;
let url = `/shixuns/${id}/update_file.json`;
axios.post(url, {
path: path,
content
}).then((response) => {
if(response.status === 200){
this.setState({
fileContent: response.data.content,
repositoryLoading: false
});
}
trace_collapse('tpm save code res: ', response)
this.props.showSnackbar('文件保存成功')
}).catch((error)=>{
console.log(error)
});
}
fetchCode = (newPathArray) => {
const path = newPathArray.join('/')
// https://testeduplus2.educoder.net/shixuns/3ozvy5f8/file_content.json
this.setContentWidth100(true)
this.setState({ repositoryLoading: true, pathArray: newPathArray })
let id = this.props.match.params.shixunId;
let url = `/shixuns/${id}/file_content.json`;
axios.post(url, {
path: path,
secret_repository: this.props.secret_repository_tab
}).then((response) => {
trace_collapse('repository res: ', response)
if (response.data.status == -1) {
this.props.showSnackbar('无法找到对应的资源,请变更地址或联系管理员!')
return;
}
if(response.status === 200){
this.setState({
fileContent: response.data.content,
repositoryLoading: false
});
this.props.history
.replace(`${this.props.match.url}/master/shixun_show/${newPathArray.join('/')}`)
}
}).catch((error)=>{
this.props.showSnackbar('无法找到对应的资源,请变更地址或联系管理员!')
console.log(error)
});
}
_isFileName = (name) => {
return name.indexOf('.') !== -1
}
_isFileInPathArray = (array) => {
if (!array || array.length === 0) {
return false
}
return this.nameTypeMap[array[array.length - 1]] !== 'tree' && this._isFileName( array[array.length - 1] )
}
// listItem 如果是num则是通过面包屑点击过来的取pathArray的子集
fetchRepo = (listItem) => {
const { pathArray } = this.state;
let newPathArray = pathArray.slice(0)
if (listItem === 0 || listItem) {
this.setContentWidth100(false)
this.nameTypeMap[listItem.name] = listItem.type
if (typeof listItem == 'number') { // 参数是数字的话,做截取
// if (this._isFileName(newPathArray[listItem])) { // 面包屑中的文件不让点击了
// listItem--;
// }
newPathArray = newPathArray.slice(0, listItem)
} else if (listItem.type === 'tree') {
newPathArray.push(listItem.name)
} else if (listItem.type === 'blob') {
newPathArray.push(listItem.name)
this.setState({ pathArray: newPathArray })
this.fetchCode(newPathArray)
return;
}
}
// https://testeduplus2.educoder.net/shixuns/3ozvy5f8/repository.json
this.setState({ repositoryLoading: true, pathArray: newPathArray })
let urlNewPathArray = newPathArray;
let fileInPathArray = false;
if (newPathArray.length) {
fileInPathArray = this.nameTypeMap[newPathArray[newPathArray.length - 1]] ? this.nameTypeMap[newPathArray[newPathArray.length - 1]] !== 'tree'
: (listItem ? listItem.type !== 'tree' : this._isFileName( newPathArray[newPathArray.length - 1] ))
if ( fileInPathArray ) {
urlNewPathArray = newPathArray.slice(0, newPathArray.length - 1)
}
}
const path = urlNewPathArray.join('/')
let id = this.props.match.params.shixunId;
let url = `/shixuns/${id}/${this.props.secret_repository_tab ? 'secret_repository' : 'repository'}.json`;
// this.props.setLoadingContent(true)
axios.post(url, {
path: path ? path : ''
}).then((response) => {
// this.props.setLoadingContent(false)
const trees = response.data.trees
const treeIsFileMap = {}
if (!trees || !Array.isArray(trees)) {
// this.props.showSnackbar('无法找到对应的资源,请变更地址或联系管理员!')
// return;
} else {
trees.forEach(item => {
treeIsFileMap[item.name] = item.type == 'blob'
})
}
if(response.status === 200){
this.setState({
treeIsFileMap,
...response.data,
repositoryLoading: false
});
this.props.history
.replace(`${this.props.match.url}` +
(newPathArray.length ? `/master/shixun_show/${newPathArray.join('/')}` : ''))
}
// 初始化时repo接口完毕后需要看是否需要fetchCode
if (fileInPathArray) {
this.fetchCode(newPathArray)
}
// info(response)
trace_collapse('repository res: ', response)
}).catch((error)=>{
console.log(error)
});
}
render() {
const { isContentWidth100 } = this.state;
// 需要重构
return (
<React.Fragment>
{ !isContentWidth100 ? <TPMRepository
{...this.props}
{...this.state}
nameTypeMap={this.nameTypeMap}
fetchRepo={this.fetchRepo}
is_jupyter={this.props.is_jupyter}
>
</TPMRepository>
:
<div className="tpmComment educontent clearfix mt30 mb80">
{/* 可能会影响到其他页面的样式,需要测试、协商 */}
<div className={`width100 fl edu-back-white`}
style={{background: 'transparent'}}>
<RepositoryCodeEditor
{...this.state}
{...this.props}
fetchRepo={this.fetchRepo}
saveCode={this.saveCode}
nameTypeMap={this.nameTypeMap}
></RepositoryCodeEditor>
</div>
</div>
}
</React.Fragment>
);
}
}
export default TPMRepositoryComponent ;

@ -1,72 +1,73 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import { CircularProgress } from 'material-ui/Progress';
import './TPMShixunDiscuss.css'
import ShixunDiscuss from './shixunchild/ShixunDiscuss/ShixunDiscuss'
import TPMRightSection from './component/TPMRightSection'
import TPMNav from './component/TPMNav'
import Comments from '../comment/Comments'
import { commentHOC } from '../comment/CommentsHOC'
class TPMShixunDiscuss extends Component {
constructor(props) {
super(props)
}
componentWillReceiveProps(newProps, newContext) {
}
componentDidMount() {
// TODO 加了HOC后 mount了两次
this.props.fetchCommentIfNotFetched &&
this.props.fetchCommentIfNotFetched();
}
render() {
const { loadingContent, creator, shixun, myshixun, recommend_shixuns, current_user, watched,
aboutFocus, user, match
} = this.props;
return (
<React.Fragment>
<div className="tpmComment educontent clearfix mt30 mb80">
<div className="with65 fl edu-back-white commentsDelegateParent" >
<TPMNav
match={match}
user={user}
shixun={shixun}
{...this.props}
></TPMNav>
{ loadingContent ?
<CircularProgress size={40} thickness={3} style={{ marginLeft: 'auto', marginRight: 'auto', marginTop: '200px', display: 'block' }}/> :
<Comments
{...this.props}
user={user}
showHiddenButton={true}
></Comments>
// onPaginationChange={this.onPaginationChange}
// <ShixunDiscuss
// {...this.props}
// />
}
</div>
<div className="with35 fr pl20">
<TPMRightSection {...this.props}></TPMRightSection>
</div>
</div>
</React.Fragment>
);
}
}
export default commentHOC ( TPMShixunDiscuss );
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import { CircularProgress } from 'material-ui/Progress';
import './TPMShixunDiscuss.css'
import ShixunDiscuss from './shixunchild/ShixunDiscuss/ShixunDiscuss'
import TPMRightSection from './component/TPMRightSection'
import TPMNav from './component/TPMNav'
import Comments from '../comment/Comments'
import { commentHOC } from '../comment/CommentsHOC'
class TPMShixunDiscuss extends Component {
constructor(props) {
super(props)
}
componentWillReceiveProps(newProps, newContext) {
}
componentDidMount() {
// TODO 加了HOC后 mount了两次
this.props.fetchCommentIfNotFetched &&
this.props.fetchCommentIfNotFetched();
}
render() {
const { loadingContent, creator, shixun, myshixun, recommend_shixuns, current_user, watched,
aboutFocus, user, match
} = this.props;
return (
<React.Fragment>
<div className="tpmComment educontent clearfix mt30 mb80">
<div className="with65 fl edu-back-white commentsDelegateParent" >
<TPMNav
match={match}
user={user}
shixun={shixun}
{...this.props}
is_jupyter={this.props.is_jupyter}
></TPMNav>
{ loadingContent ?
<CircularProgress size={40} thickness={3} style={{ marginLeft: 'auto', marginRight: 'auto', marginTop: '200px', display: 'block' }}/> :
<Comments
{...this.props}
user={user}
showHiddenButton={true}
></Comments>
// onPaginationChange={this.onPaginationChange}
// <ShixunDiscuss
// {...this.props}
// />
}
</div>
<div className="with35 fr pl20">
<TPMRightSection {...this.props}></TPMRightSection>
</div>
</div>
</React.Fragment>
);
}
}
export default commentHOC ( TPMShixunDiscuss );

@ -1,45 +1,45 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import TPMShixunDiscuss from './TPMShixunDiscuss'
import axios from 'axios';
class TPMShixunDiscussContainer extends Component {
constructor(props) {
super(props)
this.state = {
}
}
componentWillReceiveProps(newProps, newContext) {
}
componentDidMount() {
}
render() {
const { tpmLoading } = this.props;
const user = this.props.current_user;
return (
<React.Fragment>
{ tpmLoading ? <div style={{ minHeight: '886px'}}></div> :
<TPMShixunDiscuss
{...this.props}
{...this.state}
user={user}
aboutFocus={this.props.aboutFocus}
>
</TPMShixunDiscuss>
}
</React.Fragment>
);
}
}
export default TPMShixunDiscussContainer;
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
import TPMShixunDiscuss from './TPMShixunDiscuss'
import axios from 'axios';
class TPMShixunDiscussContainer extends Component {
constructor(props) {
super(props)
this.state = {
}
}
componentWillReceiveProps(newProps, newContext) {
}
componentDidMount() {
}
render() {
const { tpmLoading } = this.props;
const user = this.props.current_user;
return (
<React.Fragment>
{ tpmLoading ? <div style={{ minHeight: '886px'}}></div> :
<TPMShixunDiscuss
{...this.props}
{...this.state}
user={user}
aboutFocus={this.props.aboutFocus}
is_jupyter={this.props.is_jupyter}
>
</TPMShixunDiscuss>
}
</React.Fragment>
);
}
}
export default TPMShixunDiscussContainer;

@ -0,0 +1,385 @@
import React, {Component} from 'react';
//MonacoDiffEditor 对比模式
import {
Badge,
Select,
Radio,
Checkbox,
Modal,
DatePicker,
Button,
} from 'antd';
import locale from 'antd/lib/date-picker/locale/zh_CN';
import moment from 'moment';
import axios from 'axios';
import './css/TPMsettings.css';
import {handleDateStrings} from "./oldTPMsettings";
import Bottomsubmit from "../../modals/Bottomsubmit";
const $ = window.$;
let timeout;
let currentValue;
const Option = Select.Option;
const RadioGroup = Radio.Group;
function range(start, end) {
const result = [];
for (let i = start; i < end; i++) {
result.push(i);
}
return result;
}
function disabledDateTime() {
return {
// disabledHours: () => range(0, 24).splice(4, 20),
disabledMinutes: () => range(1, 30).concat(range(31, 60)),
// disabledSeconds: () => [0, 60],
};
}
function disabledDate(current) {
return current && current < moment().endOf('day').subtract(1, 'days');
}
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) => {
this.setState({
opening_time: dateString === "" ? "" : handleDateStrings(dateString)
})
}
onSubmits = () => {
this.setState({
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 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>
<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 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>
</div>
<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>
</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>
);
}
}

@ -0,0 +1,348 @@
import React, {Component} from 'react';
import {
Radio,
Checkbox,
} from 'antd';
import axios from 'axios';
import './css/TPMsettings.css';
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
// })
// }
}
}
}
}
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>
<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>
);
}
}

File diff suppressed because it is too large Load Diff

@ -111,3 +111,43 @@ a.newuse_scope-btn {
.ml82{
margin-left:82px;
}
.Permanentban{
color:#5091FF !important;
border-color: #5091FF !important;
}
/*tab*/
.ant-tabs-nav{
padding-bottom:18px;
padding-top: 18px;
}
.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

@ -344,7 +344,7 @@ export default class TPMMDEditor extends Component {
</div>
</div>
<div className={"fr rememberTip"}>
{noStorage == true ? ' ' : <p id={`e_tips_mdEditor_${mdID}`} className="edu-txt-right color-grey-cd font-12"> </p>}
{noStorage == true ? ' ' : <div id={`e_tips_mdEditor_${mdID}`} className="edu-txt-right color-grey-cd font-12"> </div>}
{/* {noStorage == true ? ' ' : <p id={`e_tips_mdEditor_${mdID}`} className="edu-txt-right color-grey-cd font-12"> </p>} */}
</div>
</React.Fragment>

@ -5,7 +5,11 @@ import { BrowserRouter as Router, Route, Link } from "react-router-dom";
class TPMNav extends Component {
render() {
const { user, match, shixun, secret_repository } = 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;
if (user) {
isAdminOrCreator = user.admin || user.manager
@ -15,6 +19,9 @@ class TPMNav extends Component {
// console.log(this.props.propaedeutics)
const challengesPath = `/shixuns/${shixunId}/challenges`;
// console.log(match.path)
// console.log("TPMNavTPMNavTPMNav");
// console.log(is_jupyter);
const is_teacher = this.props&&this.props.current_user&&this.props.current_user.is_teacher?this.props.current_user.is_teacher:"";
return (
<div className="bor-bottom-greyE clearfix pl20 pr20 pt40 pb20 edu-back-white challengeNav">
<Link
@ -28,27 +35,65 @@ class TPMNav extends Component {
>背景知识</Link>
}
{ this.props.identity >4||this.props.identity===undefined ?"":<Link to={`/shixuns/${shixunId}/repository`}
className={`${match.url.indexOf('/repository') != -1 ? 'active' : ''} fl mr40`}>版本库</Link>}
{ this.props.identity >4||this.props.identity===undefined ?"":
(this.props.is_jupyter===false?
<Link to={`/shixuns/${shixunId}/repository`}
className={`${match.url.indexOf('/repository') != -1 ? 'active' : ''} fl mr40`}>版本库</Link>
:"")
}
{this.props.identity >4||this.props.identity===undefined ?"": secret_repository && <Link to={`/shixuns/${shixunId}/secret_repository`}
className={`${match.url.indexOf('secret_repository') != -1 ? 'active' : ''} fl mr40`}>私密版本库</Link>}
<Link to={`/shixuns/${shixunId}/collaborators`}
className={`${match.url.indexOf('collaborators') != -1 ? 'active' : ''} fl mr40`}>合作者</Link>
<Link to={`/shixuns/${shixunId}/shixun_discuss`}
className={`${match.url.indexOf('shixun_discuss') != -1 ? 'active' : ''} fl mr40`}>评论</Link>
{/*jupyter*/}
{
this.props.is_jupyter===true?
(
is_teacher===true?
<Link to={`/shixuns/${shixunId}/dataset`}
className={`${match.url.indexOf('dataset') != -1 ? 'active' : ''} fl mr40`}>数据集</Link>
:""
)
:""
}
{
this.props.is_jupyter === false ?
<Link to={`/shixuns/${shixunId}/shixun_discuss`}
className={`${match.url.indexOf('shixun_discuss') != -1 ? 'active' : ''} fl mr40`}>评论</Link>
:""
}
{
this.props.is_jupyter === false ?
<Link to={`/shixuns/${shixunId}/ranking_list`}
className={`${match.url.indexOf('ranking_list') != -1 ? 'active' : ''} fl mr40`}>排行榜</Link>
className={`${match.url.indexOf('ranking_list') != -1 ? 'active' : ''} fl mr40`}>排行榜</Link>:""
}
{this.props.identity >2||this.props.identity===undefined?"":
(this.props.is_jupyter === false?
<Link to={`/shixuns/${shixunId}/audit_situation`}
className={`${match.url.indexOf('audit_situation') != -1 ? 'active' : ''} fl`}>审核情况</Link>
:
is_teacher===true?
<Link to={`/shixuns/${shixunId}/audit_situation`}
className={`${match.url.indexOf('audit_situation') != -1 ? 'active' : ''} fl`}>审核情况</Link>
:
""
)
{this.props.identity >2||this.props.identity===undefined?"":<Link to={`/shixuns/${shixunId}/audit_situation`}
className={`${match.url.indexOf('audit_situation') != -1 ? 'active' : ''} fl`}>审核情况</Link>}
}
<a
href={`/shixuns/${shixunId}/settings`} className="edu-default-btn edu-blueline-btn ml20 fr"
style={{display: this.props.identity >4||this.props.identity===undefined ? "none" : 'block'}}
>配置</a>
{this.props.identity >4||this.props.identity===undefined ? "":<Link to={`/shixuns/${shixunId}/settings`} className="edu-default-btn edu-blueline-btn ml20 fr"
>配置</Link>}
</div>
);
}

@ -0,0 +1,230 @@
/*
* @Description: jupyter tpi
* @Author: tangjiang
* @Github:
* @Date: 2019-12-11 08:35:23
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-13 15:25:50
*/
import './index.scss';
import React, { useEffect, useState } from 'react';
import SplitPane from 'react-split-pane';
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, // 保存按钮状态
total,
pagination,
dataSets, // 数据集
jupyter_info,
getJupyterInfo,
syncJupyterCode,
jupyter_tpi_url_state,
getJupyterTpiDataSet,
getJupyterTpiUrl,
saveJupyterTpi,
changeLoadingState,
changeGetJupyterUrlState,
jupyter_identifier,
changeCurrentPage
} = 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;
if (!identifier) return;
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();
}
// 分页信息改变时
const handlePageChange = (current) => {
// 改变当前页
changeCurrentPage(current);
// 分页查找数据
getJupyterTpiDataSet(jupyter_identifier);
}
return (
<div className="jupyter_area">
<div className="jupyter_header">
<UserInfo userInfo={userInfo} />
<p className="jupyter_title">
<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"
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}
total={total}
pagination={pagination}
onPageChange={handlePageChange}
/>
</div>
<SplitPane split="vertical" defaultSize="100%" allowResize={false}>
<RightPane
identifier={myIdentifier}
status={jupyter_tpi_url_state}
url={url}
loading={loading}
onReloadUrl={handleOnReloadUrl}
onSave={handleOnSave}
/>
<div />
</SplitPane>
</SplitPane>
</div>
</div>
);
}
const mapStateToProps = (state) => {
const {
jupyter_info,
jupyter_tpi_url,
jupyter_data_set,
jupyter_tpi_url_state,
jupyter_data_set_count,
jupyter_pagination,
jupyter_identifier
} = state.jupyterReducer;
const { loading } = state.commonReducer;
return {
loading,
jupyter_info,
url: jupyter_tpi_url,
dataSets: jupyter_data_set,
jupyter_tpi_url_state,
total: jupyter_data_set_count,
pagination: jupyter_pagination,
jupyter_identifier
};
}
const mapDispatchToProps = (dispatch) => ({
changeGetJupyterUrlState: (status) => dispatch(actions.changeGetJupyterUrlState(status)),
getJupyterInfo: (identifier) => dispatch(actions.getJupyterInfo(identifier)),
// 重置代码
syncJupyterCode: (identifier, msg) => dispatch(actions.syncJupyterCode(identifier, msg)),
getJupyterTpiDataSet: (identifier, current) => dispatch(actions.getJupyterTpiDataSet(identifier, current)),
getJupyterTpiUrl: (identifier) => dispatch(actions.getJupyterTpiUrl(identifier)),
saveJupyterTpi: () => dispatch(actions.saveJupyterTpi()),
changeLoadingState: (flag) => dispatch(actions.changeLoadingState(flag)),
changeCurrentPage: (current) => dispatch(actions.changeCurrentPage(current))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(JupyterTPI);

@ -0,0 +1,105 @@
.Resizer {
background: #000;
opacity: 0.2;
z-index: 1;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-moz-background-clip: padding;
-webkit-background-clip: padding;
background-clip: padding-box;
}
.Resizer:hover {
-webkit-transition: all 2s ease;
transition: all 2s ease;
}
.Resizer.horizontal {
height: 11px;
margin: -5px 0;
border-top: 5px solid rgba(255, 255, 255, 0);
border-bottom: 5px solid rgba(255, 255, 255, 0);
cursor: row-resize;
width: 100%;
}
.Resizer.horizontal:hover {
border-top: 5px solid rgba(0, 0, 0, 0.5);
border-bottom: 5px solid rgba(0, 0, 0, 0.5);
}
.Resizer.vertical {
width: 11px;
margin: 0 -5px;
border-left: 5px solid rgba(255, 255, 255, 0);
border-right: 5px solid rgba(255, 255, 255, 0);
cursor: col-resize;
}
.Resizer.vertical:hover {
border-left: 5px solid rgba(0, 0, 0, 0.5);
border-right: 5px solid rgba(0, 0, 0, 0.5);
}
.Resizer.disabled {
cursor: not-allowed;
}
.Resizer.disabled:hover {
border-color: transparent;
}
.jupyter_area{
.jupyter_header{
position: relative;
height: 60px;
line-height: 60px;
background-color: #070F1A;
padding-left: 30px;
.jupyter_title{
display: flex;
flex-direction: column;
// justify-content: space-around;
align-items: center;
height: 100%;
color: #fff;
.title_desc{
margin-top: 12px;
font-size: 16px;
}
.title_time{
font-size: 12px;
}
// text-align: center;
}
.jupyter_btn{
position: absolute;
right: 10px;
top: 14px;
.btn_common{
color: #888;
}
.btn_common:hover{
// background-color: #29BD8B;
// color: #29BD8B;
color: #1890ff;
}
}
}
.jupyter_ctx{
position: relative;
height: calc(100vh - 60px);
}
.update_notice{
text-align: center;
.update_txt{
line-height: 18px;
font-size: 14px;
}
}
}

@ -0,0 +1,88 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-12-12 10:34:03
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-13 16:28:33
*/
import './index.scss';
import React, { useState, useEffect } from 'react';
import {Icon, Empty, Pagination, Tooltip } from 'antd';
import MyIcon from '../../../../common/components/MyIcon';
function LeftPane (props) {
// 获取数据集
const {
dataSets = [],
total,
pagination,
onPageChange
} = 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}`}>
<Tooltip
placement="right"
title={item.file_path}
mouseLeaveDelay={0.3}
>
<Icon type="file-text" className="jupyter_icon"/>
<span className="jupyter_name">{item.title}</span>
</Tooltip>
</li>
);
});
const oUl = (
<ul className="jupyter_data_list">
{ oList }
</ul>
);
setRenderCtx(oUl);
}
}, [props]);
// 分页处理
const handleChangePage = (page) => {
// console.log(page, pageSize);
// setCurrent(page);
onPageChange && onPageChange(page);
}
return (
<div className="jupyter_data_sets_area">
<h2 className="jupyter_h2_title">
<MyIcon type="iconwenti" className="jupyter_data_icon"/> 数据集
{/* <span className="iconfont icon-java jupyter_data_icon"></span>数据集 */}
</h2>
{ renderCtx }
<div className='jupyter_pagination'>
<Pagination
simple
current={pagination.page}
pageSize={pagination.limit}
total={total}
onChange={handleChangePage}
/>
</div>
</div>
)
}
export default LeftPane;

@ -0,0 +1,72 @@
.jupyter_data_sets_area{
height: 100%;
background: #fff;
.jupyter_h2_title{
height: 44px;
line-height: 44px;
// background-color: #EEEEEE;
background: #fff;
padding: 0 30px;
font-size: 16px;
// box-size: border-box;
box-sizing: border-box;
border-bottom: 1px solid rgba(238,238,238,1);
.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 - 160px);
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%;
}
.jupyter_pagination{
display: flex;
justify-content: center;
align-items: center;
height: 56px;
box-sizing: border-box;
border-top: 1px solid rgba(238,238,238,1);
}
}

@ -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;
}
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,111 @@
import React, { Component } from 'react';
import {Button,Form,Input} from 'antd';
import axios from 'axios';
import TPMMDEditor from '../../tpm/challengesnew/TPMMDEditor';
class Osshackathonmd extends Component{
constructor(props) {
super(props)
this.contentMdRef = React.createRef();
this.state={
title_num: 0,
title_value: undefined
}
}
componentDidUpdate =(prevState)=>{
// if(prevState!=this.props){
// let url=`/osshackathon/edit_hackathon.json`;
// axios.get(url).then((result)=>{
// if(result.status==200){
// this.setState({
// title_value:result.data.name
// })
// this.contentMdRef.current.setValue(result.data.description);
// }
// })
// }
}
componentDidMount(){
let url=`/osshackathon/edit_hackathon.json`;
axios.get(url).then((result)=>{
if(result.status==200){
this.setState({
title_value:result.data.name
})
this.contentMdRef.current.setValue(result.data.description === null ? "" : result.data.description);
}
})
}
// 输入title
changeTitle = (e) => {
// title_num: 60 - parseInt(e.target.value.length),
this.setState({
title_num: e.target.value.length,
title_value: e.target.value
})
}
handleSubmit = () => {
let {title_value}=this.state;
const mdContnet = this.contentMdRef.current.getValue().trim();
// if(mdContnet.length>10000){
// this.props.showNotification("内容超过10000个字");
// return
// }
let url=`/osshackathon/update_hackathon.json`;
axios.post(url,{
name:title_value,
description:mdContnet,
}
).then((response) => {
if(response.data.status===0){
this.props.getosshackathon()
this.props.hidehackathonedit()
this.props.showNotification(`提交成功`);
}
}).catch((error) => {
console.log(error)
})
}
render() {
// console.log(this.props.tabkey)
// console.log(chart_rules)
return (
<div className={"mt20"}>
<Form>
<Form.Item label="标题">
<Input placeholder="请输入标题"
value={this.state.title_value}
onInput={this.changeTitle}
className="searchView searchViewAfter h45input" style={{"width": "100%"}} maxLength="60"
addonAfter={String(this.state.title_value === undefined || this.state.title_value === null ? 0 : this.state.title_value.length) + "/60"}
/>
</Form.Item>
<Form.Item label="描述">
<TPMMDEditor ref={this.contentMdRef} placeholder="请输入描述" mdID={'courseContentMD'} refreshTimeout={1500}
className="courseMessageMD"
initValue={this.state.description === null ? "" : this.state.description}></TPMMDEditor>
</Form.Item>
</Form>
<div className="clearfix mt30 mb30">
<div className={"fr"}>
<Button type="primary" onClick={this.handleSubmit} className="defalutSubmitbtn fl mr20">提交</Button>
<a className="defalutCancelbtn fl" onClick={() => this.props.hidehackathonedit()}>取消</ a>
</div>
</div>
</div>
)
}
}
export default Osshackathonmd;

@ -392,6 +392,442 @@ 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;
height: 40px !important;
box-shadow: none!important;
}
.width30{
width: 30%;
}
.newshixunheadersear{
display: -ms-flexbox;
display: flex;
-ms-flex-pack: center;
justify-content: center;
margin: 0 auto;
}
.packinput .ant-input{
height: 55px;
width:663px !important;
font-size: 14px;
/*color: #681616 !important;*/
border-color: #E1EDF8 !important;
padding-left: 20px;
}
.packinput .ant-input-group-addon .ant-btn{
width:137px !important;
font-size: 18px;
height: 53px;
background:rgba(76,172,255,1);
}
.tabtitle{
height: 62px !important;
-webkit-box-shadow: 3px 10px 21px 0px rgba(76, 76, 76, 0.15);
box-shadow: 3px 10px 21px 0px rgba(76, 76, 76, 0.15);
border-radius: 6px;
background: #fff;
display: -ms-flexbox;
display: flex;
-ms-flex-pack: center;
justify-content: center;
}
.tabtitles2{
background: #fff;
height: 62px !important;
width: 1200px;
}
.tabtitless{
height: 62px !important;
line-height: 62px !important;
}
.tabtitle1{
}
.tabtitle2{
margin-left: 30px !important;
}
.counttit{
display: -ms-flexbox;
display: flex;
-ms-flex-pack: center;
justify-content: center;
}
.counttittext{
text-align: left;
width: 1200px;
height: 18px;
color: #888888;
font-size: 13px;
margin-top: 24px;
}
.counttittexts{
color: #4CACFF !important;
font-size: 13px;
}
.mainx{
display: -ms-flexbox;
display: flex;
-ms-flex-pack: center;
justify-content: center;
margin-top: 17px;
}
.project-packages-list{
}
.project-package-item{
display: -ms-flexbox;
display: flex;
-ms-flex-direction:column;
flex-direction:column;
margin-bottom: 20px;
padding: 20px;
background: white;
/* box-shadow: 1px 3px 3px 1px rgba(156,156,156,0.16); */
}
.xuxianpro{
height: 20px;
border-bottom: 1px dashed;
border-color: #EAEAEA;
margin-bottom: 18px;
}
.magr11{
margin-top: 11px;
}
.highlight{
color: #4CACFF;
}
.fonttext{
font-size: 20px;
font-weight:bold;
}
.fontextcolor{
color: #777777;
}
.tzbq{
margin-left: 68px;
}
.tzbqx{
/* margin-left: 24px; */
}
.bjyss{
background: #F8F8F8;
}
.zj{
overflow:hidden;
-o-text-overflow:ellipsis;
text-overflow:ellipsis;
white-space:nowrap
}
.ziticor{
color: #777777;
font-size: 13px;
}
.foohter{
margin-top: 20px;
display: -ms-flexbox;
display: flex;
-ms-flex-direction:row;
flex-direction:row;
}
.maxwidth1100{
max-width: 1100px;
overflow:hidden;
-o-text-overflow:ellipsis;
text-overflow:ellipsis;
white-space:nowrap;
font-size: 18px !important;
font-weight: 500;
color: rgba(51,51,51,1) !important;
}
.newshixunmodelmidfont{
font-size: 14px;
font-weight: 400;
color: #999999;
margin-top: 15px;
margin-left: 30px;
max-width: 1100px;
overflow: hidden;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
}
.newshixunmodelbotfont{
font-size:12px;
font-weight:400;
color:rgba(102,102,102,1);
margin-top: 15px;
margin-left: 30px;
}
.newshixunlist{
max-height:227px;
width: 1200px;
}
.xuxianpro {
height: 20px;
border-bottom: 1px dashed;
border-color: #eaeaea;
margin-bottom: 18px;
}
.newshixunpd030{
padding: 0px 30px;
}
.pd303010{
padding: 30px 30px 10px;
}
.newshixunfont12{
font-size: 12px;
color: rgba(76,172,255,1);
line-height: 21px;
}
.newshixunmode{
width: 100px;
height: 38px;
border-radius: 3px;
/*border: 1px solid rgba(191,191,191,1);*/
}
.ntopsj {
position: absolute;
top: -4px;
}
.nyslbottomsj {
position: absolute;
bottom: -6px;
}
.inherits .ant-dropdown-menu-item{
cursor: inherit !important;
}
.menus{
width: 91px;
text-align: center;
}
.newshixunmodelbotfont span{
display: inline-block;
margin-right: 34px;
}
.minhegiht300{
min-height: 300px;
}
.newshixunlist:hover{
-webkit-box-shadow: 1px 6px 16px rgba(156,156,156,0.16);
box-shadow: 1px 6px 16px rgba(156,156,156,0.16);
opacity: 1;
border-radius: 2px;
}
.newshixun500{
max-width: 500px;
overflow: hidden;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
white-space: nowrap;
}
.mt3 {
margin-top: 3px !important;
}
.highlight{
color: #4CACFF;
}
.newshixunbottombtn{
position: fixed;
z-index: 1000;
bottom: 0px;
width: 100%;
height: 63px;
background: rgba(255,255,255,1);
-webkit-box-shadow: 0px -4px 4px 0px rgba(0,0,0,0.05);
box-shadow: 0px -4px 4px 0px rgba(0,0,0,0.05);
}
.mb60shixun{
margin-bottom: 60px !important;
}
.padding13-30 {
padding: 13px 30px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.displaymodulat {
display: -ms-flexbox;
display: flex;
display: -webkit-flex;
-ms-flex-direction: column;
flex-direction: column;
-ms-flex-align: center;
align-items: center;
}
.WordNumberTextarea {
outline: none; /* 去掉输入字符时的默认样式 */
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
background-color: white;
text-shadow: none;
-webkit-writing-mode: horizontal-tb !important;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
resize: none; /*禁止拉伸*/
border: none; /*去掉默认边框*/
width: 100%;
height: 130px;
border: none;
display: block;
}
.WordNumbernote {
padding: 0;
margin: 0;
list-style: none;
text-decoration: none;
-webkit-box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
height: auto;
border: 1px solid rgba(234, 234, 234, 1);
border-radius: 0.125rem;
margin: 10px 10px 0px 10px;
padding: 10px 10px 5px 10px;
backgroud: rgba(234, 234, 234, 1);
width: 530px;
margin-left: 10px;
margin-top: 25px;
height: 214px !important;
}
.WordNumbernote .WordNumberTextarea {
outline: none;
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
background-color: white;
text-shadow: none;
-webkit-writing-mode: horizontal-tb !important;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
resize: none;
border: none;
width: 100%;
height: 169px !important;
border: none;
display: block;
}
.WordNumberTextarea-count {
display: inline-block;
float: right;
font-size: 16px;
color: #adadad;
padding-right: 0.25rem;
}
.borerinput {
border: 1px solid #DD1717 !important;
}
.borerinputs {
border: 1px solid #eee !important;
}
.mexertwo {
display: -ms-flexbox;
display: flex;
-ms-flex-direction: initial;
flex-direction: initial;
}
.mexeheigth {
line-height: 40px;
}
.mexeheigth2 {
line-height: 40px;
width: 74px;
}
.minbuttionte {
/* display: flex; */
margin-top: 20px;
width: 100%;
/* align-items: center; */
margin-bottom: 17px;
display: -ms-flexbox;
display: flex;
-ms-flex-direction: column;
flex-direction: column;
-ms-flex-align: center;
align-items: center;
-ms-flex-pack: center;
justify-content: center;
-ms-flex-direction: initial;
flex-direction: initial;
}
.initialflex{
display: -ms-flexbox;
display: flex;
-ms-flex-direction:initial;
flex-direction:initial;
}
.newshixunheadersear{
margin: 0 auto;
}
.newshixunmodels{
margin: 0 auto;
}
.backgroundFFF{
background: #FFF !important;
}
.relative{
position: relative;
}
.pd40px{
padding-bottom: 40px;
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,329 @@
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 classNames from 'classnames';
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'
import ReactDOM from 'react-dom';
import axios from 'axios';
import AccountProfile from"../../../user/AccountProfile";
const $ = window.$;
class Challengesjupyter extends Component {
constructor(props) {
super(props)
this.state = {
ChallengesDataList: undefined,
operate: true,
startbtns: false,
iFrameHeight: '0px',
jupyter_port:0,
jupyter_url:null,
username:"",
booljupyterurls:false,
loading:false,
}
}
ChallengesList = () => {
let id = this.props.match.params.shixunId;
let ChallengesURL = `/shixuns/` + id + `/challenges.json`;
axios.get(ChallengesURL).then((response) => {
if (response.status === 200) {
if (response.data.status === 403||response.data.status === 401||response.data.status === 500) {
}else{
configShareForCustom(this.props.shixunsDetails.name, response.data.description)
this.setState({
ChallengesDataList: response.data,
sumidtype: false,
});
}
}
}).catch((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) => {
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)
}else{
setTimeout(() => {
this.setState({
booljupyterurls:true,
})
}, 600)
}
}
}).catch((error) => {
setTimeout(() => {
this.setState({
booljupyterurls:true,
})
}, 600)
});
}
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)
});
}
modifyjupyter=()=>{
let id=this.props.match.params.shixunId;
var jupyter_port="";
try{
jupyter_port= parseInt(this.state.jupyter_port);
}catch (e) {
jupyter_port=this.state.jupyter_port;
}
const url=`/jupyters/save_with_tpm.json`;
const data={
identifier:id,
jupyter_port:jupyter_port,
}
axios.get(url, {params: data})
.then((result) => {
if (result.data.status === 0) {
this.props.showNotification(`应用成功`);
}
}).catch((error) => {
})
}
render() {
let{ChallengesDataList,booljupyterurls}=this.state;
let id = this.props.match.params.shixunId;
const is_teacher = this.props&&this.props.current_user&&this.props.current_user.is_teacher?this.props.current_user.is_teacher:false;
return (
<React.Fragment>
<div className="mt30 pl20 pr20" >
<p className="clearfix mb20">
<span className="font-16 fl">简介</span>
<Tooltip placement="bottom" title={"编辑"}>
<a style={{ display: this.props.identity < 5 && ChallengesDataList&&ChallengesDataList.shixun_status < 3 ? "block" : 'none' }}
href={"/shixuns/" + id + "/settings?edit=1"} className="ring-green fr">
<img src={getImageUrl("images/educoder/icon/edit.svg")} className="fl mt3 ml2" />
</a>
</Tooltip>
</p>
<div className="justify break_full_word new_li "
id="challenge_editorMd_description">
<p id="ReactMarkdown" style={{overflow:'hidden'}}>
{ChallengesDataList === undefined ? "" :ChallengesDataList&&ChallengesDataList.description===null?"":
<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>
{
`
.intermediatecenter{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.sortinxdirection{
display: flex;
flex-direction:row;
}
.xaxisreverseorder{
display: flex;
flex-direction:row-reverse;
}
;
}
`
}
</style>
{
this.state.jupyter_url === null || this.state.jupyter_url === undefined ?
""
:
(
is_teacher===true?
<div className="sortinxdirection mt60">
<div className="renwuxiangssi sortinxdirection">
<div><p className="renwuxiangqdiv">任务详情</p></div>
<div><p className="renwuxiangqdivtest ml1 shixunbingbaocun">请将实训题目写在下方并保存</p></div>
</div>
<div className="renwuxiangssit xaxisreverseorder">
<div className="challenbaocun" onClick={() => this.modifyjupyter(this.state)}><p
className="challenbaocuntest">应用到实训</p></div>
</div>
</div>
:
""
)
}
<style>
{
`
iframe {
border-width: 0px;
border-style: inset;
border-color: initial;
border-image: initial;
}
iframe {
border-left: 1px solid #eeeeee;
border-top: 1px solid #eeeeee;
border-right: 1px solid #eeeeee;
border-bottom: 1px solid #eeeeee;
}
#header{
visibility:hidden;
}
`
}
</style>
{
is_teacher===true?
<div className="mt35">
<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"
name="framename" width="100%" height="700" frameBorder="0"
></iframe>
}
</div>
</div>
:""
}
</div>
</div>
</React.Fragment>
)
}
}
export default Challengesjupyter;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save