Merge branch 'dev_aliyun' into dev_cxt

chromesetting
cxt 5 years ago
commit 96f43439d2

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

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

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

@ -28,52 +28,56 @@ class AttachmentsController < ApplicationController
def create
# 1. 本地存储
# 2. 上传到云
upload_file = params["file"] || params["#{params[:file_param_name]}"] # 这里的file_param_name是为了方便其他插件名称
uid_logger("#########################file_params####{params["#{params[:file_param_name]}"]}")
raise "未上传文件" unless upload_file
folder = edu_setting('attachment_folder')
raise "存储目录未定义" unless folder.present?
month_folder = current_month_folder
save_path = File.join(folder, month_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'
# remote_path = file_save_to_ucloud(local_path[folder.size, local_path.size], local_path, content_type)
remote_path = nil # TODO 暂时本地上传,待域名配置后方可上传至云端
logger.info "local_path: #{local_path}"
logger.info "remote_path: #{remote_path}"
disk_filename = local_path[save_path.size + 1, local_path.size]
#存数据库
#
@attachment = Attachment.where(disk_filename: disk_filename,
author_id: current_user.id,
cloud_url: remote_path).first
begin
upload_file = params["file"] || params["#{params[:file_param_name]}"] # 这里的file_param_name是为了方便其他插件名称
uid_logger("#########################file_params####{params["#{params[:file_param_name]}"]}")
raise "未上传文件" unless upload_file
folder = edu_setting('attachment_folder')
raise "存储目录未定义" unless folder.present?
month_folder = current_month_folder
save_path = File.join(folder, month_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'
# remote_path = file_save_to_ucloud(local_path[folder.size, local_path.size], local_path, content_type)
remote_path = nil # TODO 暂时本地上传,待域名配置后方可上传至云端
logger.info "local_path: #{local_path}"
logger.info "remote_path: #{remote_path}"
disk_filename = local_path[save_path.size + 1, local_path.size]
#存数据库
#
@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
@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 = month_folder
@attachment.cloud_url = remote_path
@attachment.save!
else
logger.info "文件已存在id = #{@attachment.id}, filename = #{@attachment.filename}"
end
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 = month_folder
@attachment.cloud_url = remote_path
@attachment.save!
else
logger.info "文件已存在id = #{@attachment.id}, filename = #{@attachment.filename}"
render_json
rescue => e
uid_logger_error(e.message)
tip_exception(e.message)
end
render_json
end
def destroy
@ -196,4 +200,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

@ -23,7 +23,7 @@ class HackUserLastestCodesController < ApplicationController
# 同步代码
def sync_code
@my_hack.update_attributes(code: @hack.code, modify_time: @hack.modify_time)
@my_hack.update_attributes(code: @hack.code, modify_time: Time.now)
end
# 调试代码

@ -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,
@ -206,7 +207,7 @@ class ShixunsController < ApplicationController
@new_shixun = Shixun.new
@new_shixun.attributes = @shixun.attributes.dup.except("id","user_id","visits","gpid","status", "identifier", "averge_star",
"homepage_show","repo_name", "myshixuns_count", "challenges_count",
"can_copy", "created_at", "updated_at")
"can_copy", "created_at", "updated_at", "public")
@new_shixun.user_id = User.current.id
@new_shixun.averge_star = 5
@new_shixun.identifier = generate_identifier Shixun, 8
@ -364,73 +365,161 @@ 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 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
rescue => e
uid_logger_error(e.message)
tip_exception("基本信息更新失败:#{e.message}")
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_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
# 将实训标志为该云上实验室建立
Laboratory.current.laboratory_shixuns.create!(shixun: @shixun, ownership: true)
rescue Exception => e
uid_logger_error(e.message)
tip_exception("实训创建失败")
raise ActiveRecord::Rollback
# 实训学习页面设置
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
# 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.order("created_on desc").page(page).per(limit)
@absolute_folder = edu_setting('shixun_folder')
end
# 实训测试集附件
def upload_data_sets
begin
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.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
rescue => e
uid_logger_error(e.message)
tip_exception(e.message)
end
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 +551,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 +586,8 @@ class ShixunsController < ApplicationController
# @evaluate_scirpt = @shixun.evaluate_script || "无"
end
# 获取脚本内容
def get_script_contents
mirrir_script = MirrorScript.find(params[:script_id])
@ -708,112 +744,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("开启挑战前请先在Jupyter中填写内容") 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("#{e.message}")
end
end
def publish
@status = 0
@position = []
@ -849,7 +813,7 @@ class ShixunsController < ApplicationController
def apply_public
tip_exception(-1, "请先发布实训再申请公开") if @shixun.status != 2
ActiveRecord::Base.transaction do
@shixun.update_attributes!(pubic: 1)
@shixun.update_attributes!(public: 1)
apply = ApplyAction.where(:container_type => "ApplyShixun", :container_id => @shixun.id).order("created_at desc").first
if apply && apply.status == 0
@status = 0
@ -1035,10 +999,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 +1010,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 +1077,47 @@ 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
def delete_file(file_path)
File.delete(file_path) if File.exist?(file_path)
end
def md5_file(temp_file)
md5 = Digest::MD5.new
temp_file.rewind
while (buffer = temp_file.read(8192))
md5.update(buffer)
end
md5.hexdigest
end
end

@ -529,7 +529,7 @@ class StudentWorksController < ApplicationController
@echart_data = student_efficiency(@homework, @work)
@myself_eff = @echart_data[:efficiency_list].find { |item| item.last == @user.id }
@myself_consume = @echart_data[:consume_list].find { |item| item.last == @user.id }
filename_ = "#{@use&.student_id}_#{@use&.real_name}_#{@shixun&.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}.pdf"
filename_ = "#{@user&.student_id}_#{@user&.real_name}_#{@shixun&.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}.pdf"
filename = filename_.strip.tr("+/", "-_")
stylesheets = %w(shixun_work/shixun_work.css shared/codemirror.css)
if params[:export].present? && params[:export]

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

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

@ -67,6 +67,7 @@ class LaboratorySetting < ApplicationRecord
{ 'name' => '在线竞赛', 'link' => '/competitions', 'hidden' => false },
{ 'name' => '教学案例', 'link' => '/moop_cases', 'hidden' => false },
{ 'name' => '交流问答', 'link' => '/forums', 'hidden' => false },
{ 'name' => '开发者社区', 'link' => '/problems', 'hidden' => false },
],
footer: nil
}

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

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

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

@ -0,0 +1,219 @@
#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}"
mount = shixun.data_sets.present?
params = {tpiID: tpiID, identifier: shixun.identifier, needMount: mount,
: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']
jupyter_service = edu_setting('jupyter_service')
url =
if false
"https://#{jupyter_service}:#{res['port']}/notebooks/data/workspace/myshixun_#{tpiID}/01.ipynb"
else
"https://#{res['port']}.#{jupyter_service}/notebooks/data/workspace/myshixun_#{tpiID}/01.ipynb"
end
return url
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
mount = myshixun.shixun.data_sets.present?
params = {tpiID: tpiID, identifier: shixun.identifier, needMount: mount,
: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
jupyter_service = edu_setting('jupyter_service')
if false
"https://#{jupyter_service}:#{res['port']}/notebooks/data/workspace/myshixun_#{tpiID}/#{repo_save_path}/01.ipynb"
else
"https://#{res['port']}.#{jupyter_service}/notebooks/data/workspace/myshixun_#{tpiID}/#{repo_save_path}/01.ipynb"
end
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}"
jupyter_service = edu_setting('jupyter_service')
src_url =
if false
"https://#{jupyter_service}:#{jupyter_port}/nbconvert/notebook/data/workspace/myshixun_#{tpiID}/01.ipynb?download=true"
else
"https://#{jupyter_port}.#{jupyter_service}/nbconvert/notebook/data/workspace/myshixun_#{tpiID}/01.ipynb?download=true"
end
#https://47526.jupyter.educoder.net/nbconvert/notebook/data/workspace/myshixun_570461/f2ef5p798r20191210163135/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
jupyter_service = edu_setting('jupyter_service')
src_url =
if false
"https://#{jupyter_service}:#{jupyter_port}/nbconvert/notebook/data/workspace/myshixun_#{tpiID}/#{repo_save_path}/01.ipynb?download=true"
else
"https://#{jupyter_port}.#{jupyter_service}/nbconvert/notebook/data/workspace/myshixun_#{tpiID}/#{repo_save_path}/01.ipynb?download=true"
end
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

@ -25,7 +25,7 @@ class ShixunSearchService < ApplicationService
else
none_shixun_ids = ShixunSchool.where("school_id != #{User.current.school_id}").pluck(:shixun_id)
@shixuns = @shixuns.where.not(id: none_shixun_ids).where(hidden: 0, status: 2, public: 2).or(@shixuns.where(id: current_user.shixuns))
@shixuns = @shixuns.where.not(id: none_shixun_ids).where(hidden: 0, status: 2, public: 2).or(@shixuns.where(id: User.current.shixuns))
end
end
@ -38,6 +38,7 @@ class ShixunSearchService < ApplicationService
@shixuns = @shixuns.where(trainee: params[:diff])
end
Rails.logger.info("search_shixun_ids: #{@shixuns.pluck(:id)}")
Shixun.search(keyword, search_options)
end

@ -0,0 +1,115 @@
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
# 如果是jupyter先创建一个目录,为了挂载(因为后续数据集开启Pod后环境在没销毁前你上传数据集是挂载不上目录的因此要先创建目录方便中间层挂载)
if shixun.is_jupyter?
folder = edu_setting('shixun_folder')
path = "#{folder}/#{identifier}"
FileUtils.mkdir_p(path, :mode => 0777) unless File.directory?(dir)
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

@ -60,7 +60,7 @@ class Subjects::CopySubjectService < ApplicationService
shixun = stage_shixun.shixun
to_shixun = Shixun.new
to_shixun.attributes = shixun.attributes.dup.except('id', 'user_id', 'identifier', 'homepage_show',
'use_scope', 'averge_star', 'myshixuns_count', 'challenges_count')
'use_scope', 'averge_star', 'myshixuns_count', 'challenges_count', "public")
to_shixun.identifier = Util::UUID.generate_identifier(Shixun, 8)
to_shixun.user_id = user.id
if laboratory

@ -44,7 +44,7 @@ class Users::ShixunService
def user_policy_filter(relations)
# 只有自己或者管理员才有过滤筛选及查看全部状态下实训功能
if self_or_admin?
relations = relations.where.not(status: -1)
relations = relations.where.not(status: -1).where(hidden: false)
status_filter(relations)
else
relations.where(status: [2, 3], hidden: false)

@ -35,7 +35,7 @@ class Users::SubjectService
def user_policy_filter(relations)
# 只有自己或者管理员才有过滤筛选及查看全部状态下实训功能
if self_or_admin?
status_filter(relations)
status_filter(relations.unhidden)
else
relations.where(status: 2, hidden: false)
end

@ -557,7 +557,6 @@ a.user_orangebg_btn{background-color:#FF6800;color: #fff;}
a.user_greybg_btn{background-color:#747A7F;color: #fff;}
/*.user_white_btn{border: 1px solid #ffffff;color: #ffffff!important;}*/
.pointer{cursor: pointer}
.cdefault{cursor: default}

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

@ -12,4 +12,5 @@ json.homework_type homework.homework_type
if homework.homework_type == "practice"
json.shixun_identifier homework.shixuns.take.try(:identifier)
json.shixun_id homework.shixuns.take.try(:id)
json.shixun_status homework.shixuns.take.try(:status).to_i
end

@ -29,6 +29,7 @@ json.homeworks @homework_commons.each do |homework|
if @user_course_identity < Course::STUDENT
if homework.homework_type == "practice"
json.shixun_identifier homework.shixuns.take.try(:identifier)
json.shixun_status homework.shixuns.take.try(:status).to_i
end
elsif @user_course_identity == Course::STUDENT
if homework.homework_type == "practice"

@ -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 =""
@ -33,9 +33,9 @@ if (isDev) {
// 超管
// debugType="admin";
// 老师
// debugType="teacher";
//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()));

@ -37,6 +37,22 @@ export function getUrl(path, goTest) {
}
return `${path ? path: ''}`;
}
export function getUrlmys(path, goTest) {
// https://www.educoder.net
// https://testbdweb.trustie.net
// 如果想所有url定位到测试版可以反注释掉下面这行
//goTest = true
// 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-jupyterweb.educoder.net'
if (isDev) {
return `${local}${path?path:''}`
}
return `${path ? path: ''}`;
}
export function getStaticUrl() {
const local = TEST_HOST;
if (isDev) {
@ -55,6 +71,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 `${getUrlmys()}/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 +105,4 @@ export function htmlEncode(str) {
s = s.replace(/\'/g, "&#39;");//IE下不支持实体名称
s = s.replace(/\"/g, "&quot;");
return s;
}
}

@ -0,0 +1,30 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-12-13 10:28:15
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-13 10:37:17
*/
import { Modal } from 'antd';
export function ModalConfirm (
title,
content,
handleOk,
handleCancel
) {
Modal.confirm({
title,
content,
okText: '确定',
cancelText: '取消',
onOk () {
handleOk && handleOk();
},
onCancel () {
handleCancel && handleCancel();
}
});
}

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

@ -2,11 +2,11 @@
// 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
export { getImageUrl as getImageUrl, getUrl as getUrl, getUrlmys as getUrlmys, getUrl2 as getUrl2, setImagesUrl as setImagesUrl
, 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

@ -205,15 +205,16 @@ class CommonWorkItem extends Component{
{item.uncommit_count===undefined?"":<span className="mr20 fl">{item.uncommit_count} 未交</span>}
{
item.status_time!="" &&
<Tooltip placement="bottom" title={ item.status.indexOf('提交中') != -1 ? '提交剩余时间' :
item.status.indexOf('补交中') != -1 ? '补交剩余时间' :
item.status.indexOf('申诉中') != -1 ? '申诉剩余时间' :
item.status.indexOf('匿评中') != -1 ? '匿评剩余时间' :
item.status.indexOf('匿评申诉中') != -1 ? '匿评申诉剩余时间' : ''}>
<span className="mr20 fl">{item.status_time}</span>
</Tooltip>
}
}
{/*<Tooltip placement="bottom" title={ item.status.indexOf('提交中') != -1 ? '提交剩余时间' :*/}
{/* item.status.indexOf('补交中') != -1 ? '补交剩余时间' :*/}
{/* item.status.indexOf('申诉中') != -1 ? '申诉剩余时间' :*/}
{/* item.status.indexOf('匿评中') != -1 ? '匿评剩余时间' :*/}
{/* item.status.indexOf('匿评申诉中') != -1 ? '匿评申诉剩余时间' : ''}>*/}
{/*</Tooltip>*/}
{isAdmin && <div className="fr">
<WordsBtn style="blue" className="fl font-16 ml28" onClick={ () => { this.props.toEditPage(this.props.match.params, item.homework_id) }}>编辑</WordsBtn>
<WordsBtn style="blue" className="fl font-16 ml28" onClick={ () => { this.props.toWorkSettingPage(this.props.match.params, item.homework_id) }}>设置</WordsBtn>

@ -493,8 +493,8 @@ class NewShixunModel extends Component{
{this.props.type==='shixuns'?
<div className="clearfix sortinxdirection mt30 intermediatecenterysls">
<p className="nandu">筛选</p>
<p className={belongtoindex===0?"clickbutstwo ml13":"clickbutstwos ml13"} onClick={()=>this.belongto("all")}>全部实训</p>
<p className={belongtoindex===1?"clickbutstwo ml20":"clickbutstwos ml20"} onClick={()=>this.belongto("mine")}>普通实训</p>
<p className={type==="all"?"clickbutstwo ml13":"clickbutstwos ml13"} onClick={()=>this.belongto("all")}>全部实训</p>
<p className={type==="mine"?"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 intermediatecenter">
<p className="myysljupytertest">
Jupyter
</p>
</div>
:""
)
:""
}
<div className="cl"></div>
<style>
{

@ -385,6 +385,28 @@
.newshixunmodels{
margin: 0 auto;
}
.myysljupyter{
width:54px;
height:24px;
text-align: center;
border-radius:5px;
border:1px solid #FF6802;
}
.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{

@ -529,6 +529,7 @@ class YslDetailCards extends Component{
{
this.props.isAdmin()===true?
<DetailCardsEditAndEdit
{...this.props}
idsum={idsum}
keys={key}
pathCardsedittype={pathCardsedittype}

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

@ -232,9 +232,9 @@ class ShixunHomeworkPage extends Component {
typelist={teacherdatapage === undefined ? [""] : teacherdatapage.homework_status}
/>
<a className="color-grey-9 fr font-16 summaryname ml20 mr20" onClick={()=>this.gotohome()}>返回</a>
<a className="color-grey-9 fr font-16 mr20"
{teacherdatapage&&teacherdatapage.shixun_status>1?<a className="color-grey-9 fr font-16 mr20"
href={`/shixuns/${teacherdatapage === undefined ? "" : teacherdatapage.shixun_identifier}/challenges`}
target={"_blank"}>实训详情</a>
target={"_blank"}>实训详情</a>:""}
</div>
@ -336,10 +336,10 @@ class ShixunHomeworkPage extends Component {
<a className="fr color-blue font-16"
href={`/courses/${this.props.match.params.coursesId}/${jobsettingsdatapage === undefined ? "" : jobsettingsdatapage.data.category.main === 1 ? "shixun_homeworks" :"shixun_homework"}/${teacherdatapage === undefined ? "" : teacherdatapage.id}/commitsummary/${this.props.match.params.homeworkid}`}>{teacherdatapage.commit_des}</a>
}
{teacherdatapage === undefined ? "" : <Startshixuntask
{teacherdatapage === undefined ? "" :teacherdatapage&&teacherdatapage.shixun_status>1?<Startshixuntask
{...this.props}
data={teacherdatapage}
/>}
/>:""}
{this.props.isStudent() ?
(
teacherdatapage&&teacherdatapage.redo_work===true?

@ -315,6 +315,7 @@ class ShixunWorkReport extends Component {
// let showAppraiseModals=this.props&&this.props.isAdminOrTeacher()===true?work_comment===null||work_comment===undefined?false:true:work_comment===null||work_comment===undefined?false:true;
let showAppraiseModals=work_comment===null||work_comment===undefined?false:true;
document.title=data&&data.course_name;
return (
data===undefined?"":<Spin indicator={antIcon} spinning={this.state.spinning}>
@ -366,10 +367,15 @@ class ShixunWorkReport extends Component {
<p className=" fl color-black mt25 summaryname">{data&&data.shixun_name}</p>
{/*{this.props.isAdmin()?<a className=" fr font-14 ml30 mt10 mr20 color-grey-9 ">导出实训报告数据</a>:""}*/}
<a onClick={this.goback} className="color-grey-6 fr font-14 ml20 mt15">返回</a>
{this.props.isAdmin() ? <a
{this.props.isAdmin() ?<a
className=" color-blue font-14 fr ml20 mt15"
onClick={()=>this.confirmysl(`/student_works/${homeworkid}/export_shixun_work_report.pdf`)}
> <Spin size="small" spinning={this.state.isspinning}>导出实训报告数据</Spin></a> : ""}
> <Spin size="small" spinning={this.state.isspinning}>导出实训报告数据</Spin></a>:
parseInt(this.props&&this.props.user.user_id)===parseInt(data&&data.user_id)?<a
className=" color-blue font-14 fr ml20 mt15"
onClick={()=>this.confirmysl(`/student_works/${homeworkid}/export_shixun_work_report.pdf`)}
> <Spin size="small" spinning={this.state.isspinning}>导出实训报告数据</Spin></a>:""
}
{/*{this.props.isAdmin() ?work_comment_hidden===true? "":<a*/}
{/*className=" color-blue font-14 fr ml20 mt15"*/}
{/*onClick={()=>this.showAppraiseModal(1)}*/}

@ -355,18 +355,22 @@ class ShixunhomeWorkItem extends Component{
.homepagePostSettingbox{
width:139px !important;
}
.colorfff{
color:#fff !important;
}
`
}
</style>
{this.props.isAdmin?<span onClick={(event)=>this.stopPro(event)} className={this.props.isAdminOrCreator()?"homepagePostSetting homepagePostSettingname":"homepagePostSetting homepagePostSettingbox"} style={{"right":"-2px","top":"6px","display":"block"}}>
<Link className="btn colorblue font-16 fontweight400" to={"/shixuns/"+discussMessage.shixun_identifier+"/challenges"} target={"_blank"}>实训详情</Link>
{discussMessage&&discussMessage.shixun_status>1?<Link className="btn colorblue font-16 fontweight400" to={"/shixuns/"+discussMessage.shixun_identifier+"/challenges"} target={"_blank"}>实训详情</Link>:
<a className={"btn colorfff font-16 fontweight400"}>实训详情</a>}
{this.props.isAdminOrCreator()?<a onClick={(event)=>this.editname(discussMessage.name,discussMessage.homework_id,event)} className={"btn colorblue ml20 font-16 fontweight400"}>重命名</a>:""}
{/*<WordsBtn className="btn colorblue ml20 font-16" to={`/courses/${this.props.match.params.coursesId}/${this.state.shixuntypes}/${discussMessage.homework_id}/settings?tab=3`} > 设置</WordsBtn>*/}
<WordsBtn className="btn colorblue font-16 ml15 fontweight400" to={`/courses/${this.props.match.params.coursesId}/${this.state.shixuntypes}/${discussMessage.homework_id}/settings?tab=3`} > 设置</WordsBtn>
</span>:""}
{this.props.isStudent===true?this.props.course_identity===5?
{this.props.isStudent===true?this.props.course_identity===5?discussMessage&&discussMessage.shixun_status>1?
<WordsBtn style="blue" className="colorblue font-16 mr20 fr mt10">
{startbtn===false?
(discussMessage.task_operation[0] == '继续挑战' || discussMessage.task_operation[0] == '查看实战' ?
@ -377,7 +381,7 @@ class ShixunhomeWorkItem extends Component{
<a className="btn colorblue" onClick={()=>this.taskoperationId(discussMessage.task_operation[1])}>
{discussMessage.task_operation[0]}
</a>):<a className="btn colorblue" ></a>}
</WordsBtn>:"":""
</WordsBtn>:"":"":""
}
</h6>
@ -393,21 +397,13 @@ class ShixunhomeWorkItem extends Component{
{
discussMessage.time_status===1?
<Tooltip title={"提交剩余时间"} placement="bottom">
<span className="mr15 color-grey9">{discussMessage.status_time}</span>
</Tooltip>
:discussMessage.time_status===2?
<Tooltip title={"补交剩余时间"} placement="bottom">
<span className="mr15 color-grey9">{discussMessage.status_time}</span>
</Tooltip>
:discussMessage.time_status===3?
<Tooltip title={"匿评剩余时间"} placement="bottom">
<span className="mr15 color-grey9">{discussMessage.status_time}</span>
</Tooltip>
:discussMessage.time_status===4?
<Tooltip title={"申诉剩余时间"} placement="bottom">
<span className="mr15 color-grey9">{discussMessage.status_time}</span>
</Tooltip>
:
<span className="mr15 color-grey9">{discussMessage.status_time}</span>
}

@ -44,6 +44,13 @@ const maps = {
'value': '2'
}
],
'languageMenu': [
{
'key': 'c',
'name': 'C语言',
'value': 'c'
}
],
'difficultMenu': [
{
'key': '1',
@ -119,15 +126,14 @@ class DeveloperHome extends React.PureComponent {
>
{/* <Link to={`/problems/${record.identifier}/edit`}></Link> */}
</Button>
<Button
<Button
shape="circle"
type="danger"
icon="close"
size="small"
style={{ marginLeft: '10px' }}
style={{ marginLeft: '10px', display: record.open_or_not ? 'none' : 'inline-block' }}
onClick={() => this.handleClickDelete(record)}
>
{/* <Link to={`/problems/${record.identifier}/edit`}></Link> */}
</Button>
</React.Fragment>
),
@ -197,7 +203,6 @@ class DeveloperHome extends React.PureComponent {
componentDidMount() {
// 是否是我的,如果是我的 显示编辑按钮
const { isMySource } = this.props;
console.log(this.props);
if (isMySource) {
this.handleFilterSearch({come_from: 'mine'});
let _columns = this.columns.concat([this.options]);
@ -346,6 +351,14 @@ class DeveloperHome extends React.PureComponent {
});
this.handleFilterSearch({category: +item.key === 0 ? '' : +item.key});
}
// 下拉语言
handleLanguageMenuClick = (item) => {
this.addShowFilterCtx({
type: 'language',
key: item.key
});
this.handleFilterSearch({language: item.key})
}
// 难度下拉
handleHardMenuClick = (item) => {
this.addShowFilterCtx({
@ -421,6 +434,7 @@ class DeveloperHome extends React.PureComponent {
// const { testReducer, handleClick } = this.props;
const {
ojListReducer: {hacks_list, top_data, hacks_count},
user,
pagination
} = this.props;
const {passed_count = 0, simple_count = 0, medium_count = 0, diff_count = 0} = top_data;
@ -443,7 +457,11 @@ class DeveloperHome extends React.PureComponent {
>{ctx}</Tag>
)});
};
// console.log('=====>>>>>>>>>.', this.props);
const newBtnStyle = user && (user.admin || (user.is_teacher && user.professional_certification) || user.business)
? { display: 'block'}
: { display: 'none'};
return (
<div className="developer-list">
<div className="ant-spin-container">
@ -457,7 +475,8 @@ class DeveloperHome extends React.PureComponent {
<MultipTags type="warning" text="中等" numb={medium_count} style={{ marginRight: '20px' }}/>
<MultipTags type="error" text="困难" numb={diff_count}/>
</div>
<Button type="primary" onClick={this.handleClickNew}>新建
{/* 认证的老师, 超级管理员, 运营人员可见 */}
<Button style={ newBtnStyle } type="primary" onClick={this.handleClickNew}>新建
{/* <Link to="/problems/new">新建</Link> */}
</Button>
</div>
@ -468,6 +487,9 @@ class DeveloperHome extends React.PureComponent {
<Dropdown className={'dropdonw-style'} placement="bottomLeft" overlay={this.getMenuItems('categoryMenu', this.handleCategoryMenuClick)}>
<span className={'dropdown-span'}>分类 <Icon type="down"/></span>
</Dropdown>
<Dropdown className={'dropdonw-style'} placement="bottomLeft" overlay={this.getMenuItems('languageMenu', this.handleLanguageMenuClick)}>
<span className={'dropdown-span'}>语言 <Icon type="down"/></span>
</Dropdown>
<Dropdown className={'dropdonw-style'} placement="bottomLeft" overlay={this.getMenuItems('difficultMenu', this.handleHardMenuClick)}>
<span className={'dropdown-span'}>难度 <Icon type="down"/></span>
</Dropdown>

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-27 16:02:36
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-10 09:30:27
* @LastEditTime: 2019-12-13 17:32:33
*/
import './index.scss';
import React, { useState, useRef } from 'react';
@ -84,7 +84,7 @@ const ControlSetting = (props) => {
<Tabs
className={classNames}
activeKey={defaultActiveKey}
tabBarStyle={{ backgroundColor: '#000', color: '#fff' }}
tabBarStyle={{ backgroundColor: 'rgba(48,48,48,1)', color: '#fff' }}
onChange={handleTabChange}
>
<TabPane tab={'自定义测试用例'} key={'1'} style={{ height: '280px', overflowY: 'auto' }}>

@ -2,6 +2,7 @@
position: absolute;
bottom: 0;
width: 100%;
background:rgba(30,30,30,1);
// height: 56px;
.control_tab{
position: absolute;
@ -51,7 +52,8 @@
height: 56px;
padding-right: 30px;
padding-left: 10px;
background: #000;
// background: #000;
background:rgba(48,48,48,1);
}
.setting_drawer{

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-27 19:46:14
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-10 09:31:00
* @LastEditTime: 2019-12-13 17:38:42
*/
import './index.scss';
import React, { useState, useEffect, useRef, useImperativeHandle, forwardRef } from 'react';
@ -50,6 +50,7 @@ function InitTabCtx (props, ref) {
],
initialValue: inputValue
})(<TextArea
className="input_textarea_style"
rows={8}
placeholder="请填写测试用例的输入值,点击“调试代码”"
/>)

@ -47,4 +47,11 @@
.flex_r{
padding: 0 20px 0 10px;
}
.input_textarea_style{
background:rgba(30,30,30,1) !important;
color: #fff;
border-color: transparent;
outline: none;
}
}

@ -4,11 +4,11 @@
* @Github:
* @Date: 2019-11-27 15:02:52
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-10 09:20:42
* @LastEditTime: 2019-12-13 16:16:56
*/
import './index.scss';
import React, { useState, useRef, useEffect } from 'react';
import { Icon, Drawer, Modal } from 'antd';
import { Drawer, Modal } from 'antd';
import { fromStore } from 'educoder';
import { connect } from 'react-redux';
import MonacoEditor from '@monaco-editor/react';

@ -1,11 +1,13 @@
.monaco_editor_area{
height: 100%;
background-color: rgba(30,30,30,1);
.code_title{
display: flex;
align-items: center;
// justify-content: space-between;
// background: #000;
background: #333333;
// background: #333333;
background-color: rgba(48,48,48,1);
color: #fff;
height: 56px;
padding: 0 30px;

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

@ -9,7 +9,7 @@ import './index.scss';
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import SplitPane from 'react-split-pane';// import { Form } from 'antd';
import { Button } from 'antd';
import { Button, Modal } from 'antd';
import LeftPane from './leftpane';
import RightPane from './rightpane';
import { withRouter } from 'react-router';
@ -17,6 +17,7 @@ import { toStore } from 'educoder';
import UserInfo from '../components/userInfo';
// import RightPane from './rightpane/index';
import actions from '../../../redux/actions';
import {ModalConfirm} from '../../../common/components/ModalConfirm';
const NewOrEditTask = (props) => {
const {
@ -32,17 +33,18 @@ const NewOrEditTask = (props) => {
changePublishLoadingStatus,
startProgramQuestion,
getUserInfoForNew,
handleCancelPublish,
// updateTestAndValidate,
} = props;
// 表单提交
const handleSubmitForm = () => {
// 改变loading状态
changeSubmitLoadingStatus(true);
// 调用输入表单验证功能
if (props.identifier) {
props.handleUpdateOjForm(props);
} else {
// 改变loading状态
changeSubmitLoadingStatus(true);
props.handleFormSubmit(props); // 提交表单
}
};
@ -66,6 +68,9 @@ const NewOrEditTask = (props) => {
}, []);
// 模拟挑战
const imitationChallenge = () => {
}
// 开始挑战
const startChallenge = () => {
// 调用 start 接口, 成功后跳转到模拟页面
startProgramQuestion(identifier, props);
@ -84,9 +89,20 @@ const NewOrEditTask = (props) => {
// 发布
const handleClickPublish = () => {
// console.log('public has click');
changePublishLoadingStatus(true);
handlePublish(props, 'publish');
ModalConfirm('提示', (<p>发布后即可应用到自己管理的课堂<br /> 是否确认发布?</p>), () => {
changePublishLoadingStatus(true);
handlePublish(props, 'publish');
});
}
// 撤销发布
const handleClickCancelPublish = () => {
ModalConfirm('提示', (<p>是否确认撤销发布?</p>), () => {
changePublishLoadingStatus(true);
handleCancelPublish(props, identifier);
});
}
// 取消保存/取消按钮
@ -107,20 +123,43 @@ const NewOrEditTask = (props) => {
}
// 发布/模拟挑战
const renderPubOrFight = () => {
const pubButton = isPublish ? '' : (<Button
type="primary"
loading={publishLoading}
onClick={handleClickPublish}
>立即发布</Button>);
const pubButton = isPublish
? (<Button
type="primary"
loading={publishLoading}
onClick={handleClickCancelPublish}
>撤销发布</Button>)
: (<Button
type="primary"
loading={publishLoading}
onClick={handleClickPublish}
>立即发布</Button>);
// 未发布: 模拟挑战 已发布: 开始挑战
const challengeBtn = isPublish ? (
<Button type="primary" onClick={startChallenge}>开始挑战</Button>
) : (
<Button type="primary" onClick={imitationChallenge}>模拟挑战</Button>
);
// 更新
// const updateBtn = isPublish
// ? ''
// : (
// <Button
// type="primary"
// loading={submitLoading}
// onClick={handleSubmitForm}
// >更新</Button>
// );
return (
<React.Fragment>
<Button
type="primary"
loading={submitLoading}
onClick={handleSubmitForm}
>更新</Button>
<Button
type="primary"
loading={submitLoading}
onClick={handleSubmitForm}
>更新</Button>
{pubButton}
<Button type="primary" onClick={startChallenge}>模拟挑战</Button>
{challengeBtn}
</React.Fragment>
)
}
@ -142,24 +181,9 @@ const NewOrEditTask = (props) => {
return (
<div className={'new_add_task_wrap'}>
<div className={'task_header'}>
{/* <Link to="/problems" className={'header_btn'} >
<Icon type="left" style={{ marginRight: '5px'}}/>后退
</Link> */}
<UserInfo userInfo={userInfo}/>
<p className={'header_title'}>{props.name || ''}</p>
{ renderQuit() }
{/* <Link style={{
position: 'absolute',
right: '30px',
top: 0,
color: '#5091FF'
}} to="/problems">退出</Link> */}
{/* <Button
style={{ display: identifier ? 'none' : 'block'}}
loading={publishLoading}
className={`header_btn`}
type="primary"
onClick={handleClickPublish}>立即发布</Button> */}
</div>
<div className="split-pane-area">
<SplitPane split="vertical" minSize={350} maxSize={-350} defaultSize="40%">
@ -206,6 +230,8 @@ const mapDispatchToProps = (dispatch) => ({
handleFormSubmit: (props) => dispatch(actions.validateOjForm(props)),
// 发布表单
handlePublish: (props, type) => dispatch(actions.validateOjForm(props, type)),
// 撤销发布
handleCancelPublish: (props, identifier) => dispatch(actions.handleClickCancelPublish(props, identifier)),
// 更新OJForm
handleUpdateOjForm: (props) => dispatch(actions.validateOjForm(props)),
// 根据id号获取表单信息

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-21 09:19:38
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-10 19:37:35
* @LastEditTime: 2019-12-13 11:58:46
*/
import './index.scss';
import React, { useState } from 'react';
@ -16,7 +16,7 @@ const { TextArea } = Input;
const FormItem = Form.Item;
const AddTestDemo = (props) => {
const {
// key,
key,
onSubmitTest,
onDeleteTest,
testCase,
@ -138,7 +138,7 @@ const AddTestDemo = (props) => {
return (
<Collapse className={'collapse_area'} activeKey={isOpen?'1':''} onChange={() => handleChangeCollapse()}>
<Panel header={`测试用例${testCase.position}`} extra={genExtra()} key="1">
<Panel header={`测试用例${props.index + 1}`} extra={genExtra()} key="1">
<Form>
<FormItem
label={<span className={'label_text'}>输入</span>}

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-20 10:35:40
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-09 10:22:03
* @LastEditTime: 2019-12-13 11:39:52
*/
import 'quill/dist/quill.core.css';
import 'quill/dist/quill.bubble.css';
@ -185,6 +185,7 @@ class EditTab extends React.Component {
};
const renderTestCase = () => {
return this.props.testCases.map((item, i) => {
console.log(111);
return <AddTestDemo
key={`${i}`}
isOpen={openTestCodeIndex.includes(i)}

@ -4,10 +4,10 @@
* @Github:
* @Date: 2019-11-23 10:53:19
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-10 19:16:18
* @LastEditTime: 2019-12-13 17:19:15
*/
import './index.scss';
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import SplitPane from 'react-split-pane';
import LeftPane from './leftpane';
@ -15,33 +15,59 @@ import RightPane from './rightpane';
// import { Link } from 'react-router-dom';
// import { getImageUrl } from 'educoder'
// import RightPane from '../newOrEditTask/rightpane';
import { Icon } from 'antd';
import { Icon, Modal } from 'antd';
import UserInfo from '../components/userInfo';
import actions from '../../../redux/actions';
import { fromStore} from 'educoder';
import { withRouter } from 'react-router';
const StudentStudy = (props) => {
function StudentStudy (props) {
const [hasUpdate, setHasUpdate] = useState(true);
const {
// hack,
userInfo,
hack_identifier
hack_identifier,
// user_program_identifier,
restoreInitialCode
} = props;
const {
match: { params },
getUserProgramDetail,
saveUserProgramIdentifier
} = props;
useEffect(() => {
const {
match: { params },
getUserProgramDetail,
saveUserProgramIdentifier
} = props;
let { id } = params;
// console.log(id);
let { id } = params;
useEffect(() => {
// 保存当前的id
saveUserProgramIdentifier(id);
// startProgramQuestion(id);
getUserProgramDetail(id);
}, []);
useEffect(() => {
const { hack = {} } = props;
if (hack.modify_code && hasUpdate) { // 代码更改,提示是否需要更新代码
setHasUpdate(false);
Modal.confirm({
title: '提示',
content: (
<p>
代码文件有更新啦 <br />
还未提交的代码请自行保存
</p>
),
okText: '立即更新',
cancelText: '稍后再说',
onOk () {
restoreInitialCode(id, '更新成功');
}
});
}
}, [props, hasUpdate, setHasUpdate]);
const _hack_id = hack_identifier || fromStore('hack_identifier');
// 处理编辑
@ -66,11 +92,11 @@ const StudentStudy = (props) => {
</div> */}
<UserInfo userInfo={userInfo}/>
<div className={'study_name'}>
<span>乘积最大序列</span>
<span>乘积最大序列 {hasUpdate}</span>
</div>
<div className={'study_quit'}>
{/* to={`/problems/${_hack_id}/edit`} */}
<span onClick={handleClickEditor} className="quit-btn">
<span onClick={handleClickEditor} className={`quit-btn`}>
<Icon type="form" className="quit-icon"/> 编辑
</span>
{/* to="/problems" */}
@ -102,11 +128,13 @@ const StudentStudy = (props) => {
const mapStateToProps = (state) => {
const { userInfo } = state.userReducer;
const { hack_identifier } = state.ojForUserReducer;
const { hack_identifier, user_program_identifier, hack } = state.ojForUserReducer;
return {
hack,
userInfo,
user_program_identifier,
hack_identifier
};
};
};
const mapDispatchToProps = (dispatch) => ({
@ -115,7 +143,9 @@ const mapDispatchToProps = (dispatch) => ({
// 调用编程题详情
getUserProgramDetail: (id) => dispatch(actions.getUserProgramDetail(id)),
saveUserProgramIdentifier: (id) => dispatch(actions.saveUserProgramIdentifier(id)),
saveEditorCodeForDetail: (code) => dispatch(actions.saveEditorCodeForDetail(code))
saveEditorCodeForDetail: (code) => dispatch(actions.saveEditorCodeForDetail(code)),
// 恢复初始代码
restoreInitialCode: (identifier, msg) => dispatch(actions.restoreInitialCode(identifier, msg)),
});
export default withRouter(connect(

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-27 14:59:51
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-10 19:00:30
* @LastEditTime: 2019-12-13 16:50:40
*/
import React, { useState, useEffect } from 'react';
import {connect} from 'react-redux';
@ -66,7 +66,7 @@ const RightPane = (props) => {
}
// 恢复初始代码
const handleRestoreInitialCode = () => {
restoreInitialCode(identifier);
restoreInitialCode(identifier, '恢复初始代码成功');
}
return (
@ -110,7 +110,7 @@ const mapDispatchToProps = (dispatch) => ({
// 保存用户代码至后台
saveUserCodeForInterval: (identifier, code) => dispatch(actions.saveUserCodeForInterval(identifier, code)),
// 恢复初始代码
restoreInitialCode: (identifier) => dispatch(actions.restoreInitialCode(identifier)),
restoreInitialCode: (identifier, msg) => dispatch(actions.restoreInitialCode(identifier, msg)),
});
export default connect(

@ -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,14 +29,21 @@ render() {
body{
overflow: hidden !important;
}
.ant-modal-body {
padding: 20px 40px;
}
.color848282{
color:#848282;
}
`
}
</style>:""}
<Spin indicator={antIcons} spinning={this.props.antIcon===undefined?false:this.props.antIcon} >
<div className="task-popup-content">
<p className="task-popup-text-center font-16">{this.props.modalsTopval}</p>
{this.props.modalsMidval===undefined?"":<p className="task-popup-text-center font-16 mt5">{this.props.modalsMidval}</p>}
<p className="task-popup-text-center font-16 mt5">{this.props.modalsBottomval}</p>
{this.props.modalsMidval===undefined?"":<p className={this.props.modalstyles?"task-popup-text-center font-16 mt5 color848282":"task-popup-text-center font-16 mt5"}>{this.props.modalsMidval}</p>}
<p className={this.props.modalstyles?"task-popup-text-center font-16 mt5 color848282":"task-popup-text-center font-16 mt5"}
>{this.props.modalsBottomval}</p>
{this.props.loadtype===true?
<div className="clearfix edu-txt-center mt20">
<a className="task-btn task-btn-orange pop_close" onClick={this.props.modalSave}>知道啦</a>

@ -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,23 @@ 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 },
};
console.log(this.props)
return(
<Modal
className={this.props.className}
@ -81,6 +93,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,630 @@
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';
import Tpmdatasetmodel from "./tpmmodel/Tpmdatasetmodel";
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,
showmodel:false,
itemtypebool: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) => {
// console.log("handleChange123123");
// console.log(info);
// debugger
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();
}
if(info.file.response){
if(info.file.response.status===-1||info.file.response.status==="-1"){
// console.log("准备显示弹框了");
// console.log(info);false
let itemtype=-1;
try {
itemtype=info.file.response.message.indexOf('文件名已经存在'.toLowerCase());
}catch (e) {
}
this.setState({
showmodel:true,
tittest:info.file.response.message,
itemtypebool:itemtype>-1?true:itemtype<=-1?false:false,
})
}
}
}
}
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);
});
}
ModalCancel = () => {
this.setState({
showmodel: false,
})
}
ModalSave=()=>{
this.setState({
showmodel: false,
})
}
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,
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;
},
};
// console.log("showmodelshowmodel");
// console.log(this.state.showmodel);
return (
<React.Fragment>
<div className="tpmComment educontent clearfix mt30 mb80">
<div className="with65 fl edu-back-white commentsDelegateParent">
{
this.state.showmodel===true?
<Tpmdatasetmodel itemtypebool={this.state.itemtypebool} modalCancel={()=>this.ModalSave()} tittest={this.state.tittest} modalsType={this.state.showmodel}></Tpmdatasetmodel>
:""
}
<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;

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

Loading…
Cancel
Save