Merge pull request 'FIX 优化项目流程' (#146) from develop into dev_trustie_server

This commit is contained in:
jasder 2021-09-10 18:28:36 +08:00
commit 26c0d36227
59 changed files with 565 additions and 836 deletions

View File

@ -1,529 +0,0 @@
import React, {Component} from 'react';
import logo from './logo.svg';
import './App.css';
import {LocaleProvider} from 'antd'
import zhCN from 'antd/lib/locale-provider/zh_CN';
import {
BrowserRouter as Router,
Route,
Switch
} from 'react-router-dom';
import axios from 'axios';
import '@icedesign/base/dist/ICEDesignBase.css';
import '@icedesign/base/index.scss';
import LoginDialog from './modules/login/LoginDialog';
import Notcompletedysl from './modules/user/Notcompletedysl';
import Trialapplicationysl from './modules/login/Trialapplicationysl';
import Trialapplicationreview from './modules/user/Trialapplicationreview';
import Addcourses from "./modules/courses/coursesPublic/Addcourses";
import AccountProfile from"./modules/user/AccountProfile";
import Trialapplication from './modules/login/Trialapplication'
import NotFoundPage from './NotFoundPage'
import Loading from './Loading'
import Loadable from 'react-loadable';
import moment from 'moment'
import {MuiThemeProvider, createMuiTheme} from 'material-ui/styles';
// import './AppConfig'
import history from './history';
import {SnackbarHOC} from 'educoder'
import {initAxiosInterceptors} from './AppConfig'
// tpi需要这个来加载css
import {TPMIndexHOC} from './modules/tpm/TPMIndexHOC';
const theme = createMuiTheme({
palette: {
primary: {
main: '#4CACFF',
contrastText: 'rgba(255, 255, 255, 0.87)'
},
secondary: {main: '#4CACFF'}, // #11cb5f This is just green.A700 as hex.
},
});
//
// const Trialapplication= Loadable({
// loader: () =>import('./modules/login/Trialapplication'),
// loading:Loading,
// })
//登入
const EducoderLogin = Loadable({
loader: () => import('./modules/login/EducoderLogin'),
loading: Loading,
})
const TestIndex = Loadable({
loader: () => import('./modules/test'),
loading: Loading,
})
const IndexWrapperComponent = Loadable({
loader: () => import('./modules/page/IndexWrapper'),
loading: Loading,
})
const CommentComponent = Loadable({
loader: () => import('./modules/comment/CommentContainer'),
loading: Loading,
})
const TestMaterialDesignComponent = Loadable({
loader: () => import('./modules/test/md/TestMaterialDesign'),
loading: Loading,
})
const TestCodeMirrorComponent = Loadable({
loader: () => import('./modules/test/codemirror/TestCodeMirror'),
loading: Loading,
})
const TestComponent = Loadable({
loader: () => import('./modules/test/TestRC'),
loading: Loading,
})
const TestUrlQueryComponent = Loadable({
loader: () => import('./modules/test/urlquery/TestUrlQuery'),
loading: Loading,
})
const TPMIndexComponent = Loadable({
loader: () => import('./modules/tpm/TPMIndex'),
loading: Loading,
})
const TPMShixunsIndexComponent = Loadable({
loader: () => import('./modules/tpm/shixuns/ShixunsIndex'),
loading: Loading,
})
//实训课程(原实训路径)
const ShixunPaths = Loadable({
loader: () => import('./modules/paths/Index'),
loading: Loading,
})
//在线课堂
const CoursesIndex = Loadable({
loader: () => import('./modules/courses/Index'),
loading: Loading,
})
const SearchPage = Loadable({
loader: () => import('./search/SearchPage'),
loading: Loading,
})
//教学案例
const MoopCases = Loadable({
loader: () => import('./modules/moop_cases/index'),
loading: Loading,
})
// 课堂讨论
// const BoardIndex = Loadable({
// loader: () => import('./modules/courses/boards/BoardIndex'),
// loading:Loading,
// })
// //课堂普通作业&分组作业
// const CoursesWorkIndex = Loadable({
// loader: () => import('./modules/courses/busyWork/Index'),
// loading:Loading,
// })
//
// const TPMShixunchildIndexComponent = Loadable({
// loader: () => import('./modules/tpm/shixunchild/ShixunChildIndex'),
// loading: Loading,
// })
// const TPMshixunfork_listIndexComponent = Loadable({
// loader: () => import('./modules/tpm/shixunchild/Shixunfork_list'),
// loading: Loading,
// })
const ForumsIndexComponent = Loadable({
loader: () => import('./modules/forums/ForumsIndex'),
loading: Loading,
})
// trustie plus forum
// const TPForumsIndexComponent = Loadable({
// loader: () => import('./modules/tp-forums/TPForumsIndex'),
// loading: Loading,
// })
// const TestPageComponent = Loadable({
// loader: () => import('./modules/page/Index'),
// loading: Loading,
// })
//新建实训
const Newshixuns = Loadable({
loader: () => import('./modules/tpm/newshixuns/Newshixuns'),
loading: Loading,
})
//实训首页
const ShixunsHome = Loadable({
loader: () => import('./modules/home/shixunsHome'),
loading: Loading,
})
const CompatibilityPageLoadable = Loadable({
loader: () => import('./modules/common/CompatibilityPage'),
loading: Loading,
})
//403页面
const Shixunauthority = Loadable({
loader: () => import('./modules/403/Shixunauthority'),
loading: Loading,
})
//404页面
const Shixunnopage = Loadable({
loader: () => import('./modules/404/Shixunnopage'),
loading: Loading,
})
//500页面
const http500 = Loadable({
loader: () => import('./modules/500/http500'),
loading: Loading,
})
// 登录注册
const LoginRegisterPage = Loadable({
loader: () => import('./modules/user/LoginRegisterPage'),
loading: Loading,
})
const AccountPage = Loadable({
loader: () => import('./modules/user/AccountPage'),
loading: Loading,
})
// 个人主页
const UsersInfo = Loadable({
loader: () => import('./modules/user/usersInfo/Infos'),
loading: Loading,
})
// 兴趣页面
const Interestpage = Loadable({
loader: () => import('./modules/login/EducoderInteresse'),
loading: Loading,
})
//众包创新
const ProjectPackages=Loadable({
loader: () => import('./modules/projectPackages/ProjectPackageIndex'),
loading: Loading,
})
class App extends Component {
constructor(props) {
super(props)
// this.state = {
// isRenders:false,
// }
}
componentDidMount() {
// force an update if the URL changes
history.listen(() => {
this.forceUpdate()
const $ = window.$
// https://www.trustie.net/issues/21919 可能会有问题
$("html").animate({ scrollTop: $('html').scrollTop() - 0 })
});
initAxiosInterceptors(this.props)
//
// axios.interceptors.response.use((response) => {
// // console.log("response"+response);
// if(response!=undefined)
// // console.log("response"+response.data.statu);
// if (response&&response.data.status === 407) {
// this.setState({
// isRenders: true,
// })
// }
// return response;
// }, (error) => {
// //TODO 这里如果样式变了会出现css不加载的情况
// });
}
//修改登录方法
Modifyloginvalue=()=>{
this.setState({
isRender:false,
})
}
render() {
return (
<LocaleProvider locale={zhCN}>
<MuiThemeProvider theme={theme}>
<LoginDialog {...this.props} {...this.state} Modifyloginvalue={()=>this.Modifyloginvalue()}></LoginDialog>
<Notcompletedysl {...this.props} {...this.state}></Notcompletedysl>
<Trialapplicationysl {...this.props} {...this.state}></Trialapplicationysl>
<Trialapplicationreview {...this.props} {...this.state}></Trialapplicationreview>
<Addcourses {...this.props} {...this.state}/>
<AccountProfile {...this.props} {...this.state}/>
{/*{*/}
{/* isRender === true?*/}
{/* <LoginDialog></LoginDialog> : ""*/}
{/*}*/}
{/*{*/}
{/* isRenders === true?*/}
{/*<Trialapplication></Trialapplication>*/}
{/*:""*/}
{/*}*/}
<Router>
<Switch>
{/*<Route path="/login" component={LoginRegisterPage}/>*/}
{/*众包创新*/}
<Route path={"/crowdsourcings"} component={ProjectPackages}/>
{/*认证*/}
<Route path="/account" component={AccountPage}/>
{/*403*/}
<Route path="/403" component={Shixunauthority}/>
<Route path="/500" component={http500}/>
{/*404*/}
<Route path="/nopage" component={Shixunnopage}/>
<Route path="/compatibility" component={CompatibilityPageLoadable}/>
<Route
path="/login" component={EducoderLogin}
/>
<Route
path="/register" component={EducoderLogin}
/>
<Route path="/users/:username"
render={
(props) => (<UsersInfo {...this.props} {...props} {...this.state} />)
}></Route>
{/*<Route*/}
{/* path="/trialapplication" component={Trialapplication}*/}
{/*/>*/}
<Route
path="/changepassword" component={EducoderLogin}
/>
<Route
path="/interesse" component={Interestpage}
/>
<Route path="/shixuns/new" component={Newshixuns}>
</Route>
<Route path="/tasks/:stageId" component={IndexWrapperComponent}/>
<Route path="/shixuns/:shixunId" component={TPMIndexComponent}>
</Route>
{/*列表页*/}
<Route path="/shixuns" component={TPMShixunsIndexComponent}/>
{/* <Route path="/shixunchild" component={TPMShixunchildIndexComponent}>
</Route>
<Route path="/fork_list" component={TPMshixunfork_listIndexComponent}>
</Route> */}
{/*<Route path="/forums" component={ForumsIndexComponent}>*/}
{/*</Route>*/}
{/*实训课程(原实训路径)*/}
<Route path="/paths" component={ShixunPaths}></Route>
<Route path="/search"
render={
(props)=>(<SearchPage {...this.props} {...props} {...this.state}></SearchPage>)
}
></Route>
{/*课堂*/}
<Route path="/courses" component={CoursesIndex} {...this.props}></Route>
{/* 课堂讨论 */}
{/* <Route path="/board" component = {BoardIndex} {...this.props}></Route> */}
{/* <Route path="/tpforums" component={TPForumsIndexComponent}>
</Route> */}
{/* <Route path="/myshixuns/:shixunId/stages/:stageId" component={Index}/> */}
{/* 兴趣页面*/}
{/*<Route path="/interest" component={Interestpage}/>*/}
<Route path="/comment" component={CommentComponent}/>
<Route path="/testMaterial" component={TestMaterialDesignComponent}/>
<Route path="/test" component={TestIndex}/>
<Route path="/testCodeMirror" component={TestCodeMirrorComponent}/>
<Route path="/testRCComponent" component={TestComponent}/>
<Route path="/testUrlQuery" component={TestUrlQueryComponent}/>
{/* 教学案例 */}
<Route path="/moop_cases"render={
(props) => (<MoopCases {...this.props} {...props} {...this.state} />)
}/>
{/* <Route component={NotFoundPage}/> */}
{/*列表页*/}
{/*<Route component={TPMShixunsIndexComponent}/>*/}
{/*首页*/}
<Route exact path="/" component={ShixunsHome}/>
<Route component={Shixunnopage}/>
{/*<Route component={ShixunsHome}/>*/}
</Switch>
</Router>
</MuiThemeProvider>
</LocaleProvider>
);
}
}
// moment国际化设置为中文
moment.defineLocale('zh-cn', {
months: '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'),
monthsShort: '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'),
weekdays: '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'),
weekdaysShort: '周日_周一_周二_周三_周四_周五_周六'.split('_'),
weekdaysMin: '日_一_二_三_四_五_六'.split('_'),
longDateFormat: {
LT: 'Ah点mm分',
LTS: 'Ah点m分s秒',
L: 'YYYY-MM-DD',
LL: 'YYYY年MMMD日',
LLL: 'YYYY年MMMD日Ah点mm分',
LLLL: 'YYYY年MMMD日ddddAh点mm分',
l: 'YYYY-MM-DD',
ll: 'YYYY年MMMD日',
lll: 'YYYY年MMMD日Ah点mm分',
llll: 'YYYY年MMMD日ddddAh点mm分'
},
meridiemParse: /凌晨|早上|上午|中午|下午|晚上/,
meridiemHour: function (hour, meridiem) {
if (hour === 12) {
hour = 0;
}
if (meridiem === '凌晨' || meridiem === '早上' ||
meridiem === '上午') {
return hour;
} else if (meridiem === '下午' || meridiem === '晚上') {
return hour + 12;
} else {
// '中午'
return hour >= 11 ? hour : hour + 12;
}
},
meridiem: function (hour, minute, isLower) {
var hm = hour * 100 + minute;
if (hm < 600) {
return '凌晨';
} else if (hm < 900) {
return '早上';
} else if (hm < 1130) {
return '上午';
} else if (hm < 1230) {
return '中午';
} else if (hm < 1800) {
return '下午';
} else {
return '晚上';
}
},
calendar: {
sameDay: function () {
return this.minutes() === 0 ? '[今天]Ah[点整]' : '[今天]LT';
},
nextDay: function () {
return this.minutes() === 0 ? '[明天]Ah[点整]' : '[明天]LT';
},
lastDay: function () {
return this.minutes() === 0 ? '[昨天]Ah[点整]' : '[昨天]LT';
},
nextWeek: function () {
var startOfWeek, prefix;
startOfWeek = moment().startOf('week');
prefix = this.unix() - startOfWeek.unix() >= 7 * 24 * 3600 ? '[下]' : '[本]';
return this.minutes() === 0 ? prefix + 'dddAh点整' : prefix + 'dddAh点mm';
},
lastWeek: function () {
var startOfWeek, prefix;
startOfWeek = moment().startOf('week');
prefix = this.unix() < startOfWeek.unix() ? '[上]' : '[本]';
return this.minutes() === 0 ? prefix + 'dddAh点整' : prefix + 'dddAh点mm';
},
sameElse: 'LL'
},
ordinalParse: /\d{1,2}(日|月|周)/,
ordinal: function (number, period) {
switch (period) {
case 'd':
case 'D':
case 'DDD':
return number + '日';
case 'M':
return number + '月';
case 'w':
case 'W':
return number + '周';
default:
return number;
}
},
relativeTime: {
future: '%s内',
past: '%s前',
s: '几秒',
m: '1分钟',
mm: '%d分钟',
h: '1小时',
hh: '%d小时',
d: '1天',
dd: '%d天',
M: '1个月',
MM: '%d个月',
y: '1年',
yy: '%d年'
},
week: {
// GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效
dow: 1, // Monday is the first day of the week.
doy: 4 // The week that contains Jan 4th is the first week of the year.
}
});
export default SnackbarHOC()(App);

View File

@ -338,10 +338,10 @@ http://localhost:3000/api/projects/ | jq
|-|-|-|-| |-|-|-|-|
|user_id |是|int |用户id或者组织id | |user_id |是|int |用户id或者组织id |
|name |是|string |项目名称 | |name |是|string |项目名称 |
|description ||string |项目描述 | |description ||string |项目描述 |
|repository_name |是|string |仓库名称, 只含有数字、字母、下划线不能以下划线开头和结尾,且唯一 | |repository_name |是|string |仓库名称, 只含有数字、字母、下划线不能以下划线开头和结尾,且唯一 |
|project_category_id||int |项目类别id | |project_category_id||int |项目类别id |
|project_language_id||int |项目语言id | |project_language_id||int |项目语言id |
|ignore_id |否|int |gitignore相关id | |ignore_id |否|int |gitignore相关id |
|license_id |否|int |开源许可证id | |license_id |否|int |开源许可证id |
|private |否|boolean|项目是否私有, true为私有false: 公开,默认为公开 | |private |否|boolean|项目是否私有, true为私有false: 公开,默认为公开 |
@ -374,9 +374,7 @@ curl -X POST \
-d "user_id=36408" \ -d "user_id=36408" \
-d "clone_addr=https://gitea.com/mx8090alex/golden.git" \ -d "clone_addr=https://gitea.com/mx8090alex/golden.git" \
-d "name=golden_mirror1" \ -d "name=golden_mirror1" \
-d "description=golden_mirror" \ -d "repository_name=golden_mirror1" \
-d "project_category_id=1" \
-d "project_language_id=2" \
http://localhost:3000/api/projects/migrate.json | jq http://localhost:3000/api/projects/migrate.json | jq
``` ```
*请求参数说明:* *请求参数说明:*
@ -388,8 +386,8 @@ http://localhost:3000/api/projects/migrate.json | jq
|clone_addr |是|string |镜像项目clone地址 | |clone_addr |是|string |镜像项目clone地址 |
|description |否|string |项目描述 | |description |否|string |项目描述 |
|repository_name |是|string |仓库名称, 只含有数字、字母、下划线不能以下划线开头和结尾,且唯一 | |repository_name |是|string |仓库名称, 只含有数字、字母、下划线不能以下划线开头和结尾,且唯一 |
|project_category_id||int |项目类别id | |project_category_id||int |项目类别id |
|project_language_id||int |项目语言id | |project_language_id||int |项目语言id |
|is_mirror |否|boolean|是否设置为镜像, true false默认为否 | |is_mirror |否|boolean|是否设置为镜像, true false默认为否 |
|auth_username |否|string|镜像源仓库的登录用户名 | |auth_username |否|string|镜像源仓库的登录用户名 |
|auth_password |否|string|镜像源仓库的登录秘密 | |auth_password |否|string|镜像源仓库的登录秘密 |

View File

@ -0,0 +1,55 @@
class Admins::EduSettingsController < Admins::BaseController
before_action :find_setting, only: [:edit,:update, :destroy]
def index
default_sort('id', 'desc')
edu_settings = Admins::EduSettingQuery.call(params)
@edu_settings = paginate edu_settings
end
def new
@edu_setting = EduSetting.new
end
def edit
end
def create
@edu_setting = EduSetting.new(edu_setting_params)
if @edu_setting.save
redirect_to admins_edu_settings_path
flash[:success] = '创建成功'
else
redirect_to admins_edu_settings_path
flash[:danger] = @edu_setting.errors.full_messages.join(",")
end
end
def update
if @edu_setting.update!(edu_setting_params)
flash[:success] = '更新成功'
else
flash[:danger] = @edu_setting.errors.full_messages.join(",")
end
redirect_to admins_edu_settings_path
end
def destroy
if @edu_setting.destroy!
flash[:success] = '删除成功'
else
lash[:danger] = '删除失败'
end
redirect_to admins_edu_settings_path
end
private
def find_setting
@edu_setting ||= EduSetting.find(params[:id])
end
def edu_setting_params
params.require(:edu_setting).permit(:name, :value, :description)
end
end

View File

@ -0,0 +1,56 @@
class Admins::SitesController < Admins::BaseController
before_action :find_site, only: [:edit,:update, :destroy]
def index
default_sort('id', 'desc')
sites = Admins::SiteQuery.call(params)
@sites = paginate sites
end
def new
@site = Site.new
end
def edit
end
def create
@site = Site.new(site_params)
if @site.save
redirect_to admins_sites_path
flash[:success] = '创建成功'
else
redirect_to admins_sites_path
flash[:danger] = @site.errors.full_messages.join(",")
end
end
def update
if @site.update!(site_params)
flash[:success] = '更新成功'
else
flash[:danger] = @site.errors.full_messages.join(",")
end
redirect_to admins_sites_path
end
def destroy
if @site.destroy!
flash[:success] = '删除成功'
else
lash[:danger] = '删除失败'
end
redirect_to admins_sites_path
end
private
def find_site
@site ||= Site.find(params[:id])
end
def site_params
params.require(:site).permit(:name, :url, :key, :site_type)
end
end

View File

@ -41,7 +41,7 @@ module LaboratoryHelper
my_courses: "https://www.trustie.net/users/#{current_user.try(:login)}/user_courselist", my_courses: "https://www.trustie.net/users/#{current_user.try(:login)}/user_courselist",
my_projects: "/users/#{current_user.try(:login)}/projects", my_projects: "/users/#{current_user.try(:login)}/projects",
my_organ: "https://www.trustie.net/users/#{current_user.try(:login)}/user_organizations", my_organ: "https://www.trustie.net/users/#{current_user.try(:login)}/user_organizations",
default_url: "https://www.trustie.net/", default_url: Rails.application.config_for(:configuration)['platform_url'],
tiding_url: "https://www.trustie.net/users/#{current_user.try(:login)}/user_messages", tiding_url: "https://www.trustie.net/users/#{current_user.try(:login)}/user_messages",
register_url: "https://www.trustie.net/login?login=false" register_url: "https://www.trustie.net/login?login=false"
} }

View File

@ -29,10 +29,8 @@ class EduSettingsController < ApplicationController
respond_to do |format| respond_to do |format|
if @edu_setting.save if @edu_setting.save
format.html { redirect_to @edu_setting, notice: 'Edu setting was successfully created.' }
format.json { render :show, status: :created, location: @edu_setting } format.json { render :show, status: :created, location: @edu_setting }
else else
format.html { render :new }
format.json { render json: @edu_setting.errors, status: :unprocessable_entity } format.json { render json: @edu_setting.errors, status: :unprocessable_entity }
end end
end end
@ -43,10 +41,8 @@ class EduSettingsController < ApplicationController
def update def update
respond_to do |format| respond_to do |format|
if @edu_setting.update(edu_setting_params) if @edu_setting.update(edu_setting_params)
format.html { redirect_to @edu_setting, notice: 'Edu setting was successfully updated.' }
format.json { render :show, status: :ok, location: @edu_setting } format.json { render :show, status: :ok, location: @edu_setting }
else else
format.html { render :edit }
format.json { render json: @edu_setting.errors, status: :unprocessable_entity } format.json { render json: @edu_setting.errors, status: :unprocessable_entity }
end end
end end
@ -57,7 +53,6 @@ class EduSettingsController < ApplicationController
def destroy def destroy
@edu_setting.destroy @edu_setting.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to edu_settings_url, notice: 'Edu setting was successfully destroyed.' }
format.json { head :no_content } format.json { head :no_content }
end end
end end

View File

@ -46,7 +46,6 @@ class ProjectsController < ApplicationController
def create def create
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
tip_exception("无法使用以下关键词:#{project_params[:repository_name]},请重新命名") if ReversedKeyword.is_reversed(project_params[:repository_name]).present?
Projects::CreateForm.new(project_params).validate! Projects::CreateForm.new(project_params).validate!
@project = Projects::CreateService.new(current_user, project_params).call @project = Projects::CreateService.new(current_user, project_params).call
@ -57,7 +56,6 @@ class ProjectsController < ApplicationController
end end
def migrate def migrate
tip_exception("无法使用以下关键词:#{mirror_params[:repository_name]},请重新命名") if ReversedKeyword.is_reversed(mirror_params[:repository_name]).present?
Projects::MigrateForm.new(mirror_params).validate! Projects::MigrateForm.new(mirror_params).validate!
@project = @project =

View File

@ -1,7 +1,7 @@
class Users::HeadmapsController < Users::BaseController class Users::HeadmapsController < Users::BaseController
def index def index
result = Gitea::User::HeadmapService.call(observed_user.login, start_stamp, end_stamp) result = Gitea::User::HeadmapService.call(observed_user.login, start_stamp, end_stamp)
@headmaps = result[2] @headmaps = result[2].blank? ? [] : result[2]
rescue Exception => e rescue Exception => e
uid_logger_error(e.message) uid_logger_error(e.message)
tip_exception(e.message) tip_exception(e.message)

View File

@ -14,7 +14,7 @@ class Users::StatisticsController < Users::BaseController
@date_data << date.strftime("%Y.%m.%d") @date_data << date.strftime("%Y.%m.%d")
@issue_data << observed_user.issues.where("DATE(created_on) = ?", date).size @issue_data << observed_user.issues.where("DATE(created_on) = ?", date).size
@pull_request_data << observed_user.pull_requests.where("DATE(created_at) = ?", date).size @pull_request_data << observed_user.pull_requests.where("DATE(created_at) = ?", date).size
date_commit_data = commit_data.select{|item| item["timestamp"] == date.to_time.to_i} date_commit_data = commit_data.blank? ? nil : commit_data.select{|item| item["timestamp"] == date.to_time.to_i}
@commit_data << (date_commit_data.blank? ? 0 : date_commit_data[0]["contributions"].to_i) @commit_data << (date_commit_data.blank? ? 0 : date_commit_data[0]["contributions"].to_i)
end end
render :json => {dates: @date_data, issues_count: @issue_data, pull_requests_count: @pull_request_data, commits_count: @commit_data} render :json => {dates: @date_data, issues_count: @issue_data, pull_requests_count: @pull_request_data, commits_count: @commit_data}

View File

@ -2,18 +2,24 @@ class BaseForm
include ActiveModel::Model include ActiveModel::Model
def check_project_category(project_category_id) def check_project_category(project_category_id)
raise "project_category_id参数值无效." if (ProjectCategory.find_by_id project_category_id).blank? raise "project_category_id参数值无效." if project_category_id && !ProjectCategory.exists?(project_category_id)
end end
def check_project_language(project_language_id) def check_project_language(project_language_id)
raise "project_language_id参数值无效." if (ProjectLanguage.find_by_id project_language_id).blank? raise "project_language_id参数值无效." if project_language_id && !ProjectLanguage.exists?(project_language_id)
end end
def check_repository_name(user_id, repository_name) def check_repository_name(user_id, repository_name)
raise "仓库名称已被使用." if Repository.where(user_id: user_id, identifier: repository_name.strip).exists? check_reversed_keyword(repository_name)
raise "项目标识已被使用." if Repository.where(user_id: user_id, identifier: repository_name.strip).exists?
end end
def check_project_name(user_id, project_name) def check_project_name(user_id, project_name)
raise "项目名称已被使用." if Project.where(user_id: user_id, name: project_name.strip).exists? raise "项目名称已被使用." if Project.where(user_id: user_id, name: project_name.strip).exists?
end end
def check_reversed_keyword(repository_name)
raise "项目标识已被占用." if ReversedKeyword.is_reversed(repository_name).exists?
end
end end

View File

@ -1,11 +1,9 @@
class Projects::CreateForm < BaseForm class Projects::CreateForm < BaseForm
REPOSITORY_NAME_REGEX = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]+$/ #只含有数字、字母、下划线不能以下划线开头和结尾
attr_accessor :user_id, :name, :description, :repository_name, :project_category_id, attr_accessor :user_id, :name, :description, :repository_name, :project_category_id,
:project_language_id, :ignore_id, :license_id, :private, :owner :project_language_id, :ignore_id, :license_id, :private, :owner
validates :user_id, :name, :description,:repository_name, validates :user_id, :name, :repository_name, presence: true
:project_category_id, :project_language_id, presence: true validates :repository_name, format: { with: CustomRegexp::REPOSITORY_NAME_REGEX, multiline: true, message: "只能含有数字、字母、下划线且不能以下划线开头和结尾" }
validates :repository_name, format: { with: REPOSITORY_NAME_REGEX, multiline: true, message: "只能含有数字、字母、下划线且不能以下划线开头和结尾" }
validates :name, length: { maximum: 50 } validates :name, length: { maximum: 50 }
validates :repository_name, length: { maximum: 100 } validates :repository_name, length: { maximum: 100 }

View File

@ -1,12 +1,13 @@
class Projects::MigrateForm < BaseForm class Projects::MigrateForm < BaseForm
REPOSITORY_NAME_REGEX = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]+$/ #只含有数字、字母、下划线不能以下划线开头和结尾 attr_accessor :user_id, :name, :repository_name, :project_category_id, :description,
URL_REGEX = /\A(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?\z/i :project_language_id, :clone_addr, :private, :is_mirror, :auth_username, :auth_password, :owner
attr_accessor :user_id, :name, :description, :repository_name, :project_category_id, :project_language_id, :clone_addr, :private, :is_mirror, :auth_username, :auth_password, :owner validates :user_id, :name, :repository_name, :clone_addr, presence: true
validates :repository_name, format: { with: CustomRegexp::REPOSITORY_NAME_REGEX, multiline: true, message: "只能含有数字、字母、下划线且不能以下划线开头和结尾" }
validates :user_id, :name, :description,:repository_name, :project_category_id, :project_language_id, presence: true validates :clone_addr, format: { with: CustomRegexp::URL_REGEX, multiline: true, message: "地址格式不正确" }
validates :repository_name, format: { with: REPOSITORY_NAME_REGEX, multiline: true, message: "只能含有数字、字母、下划线且不能以下划线开头和结尾" } validates :name, length: { maximum: 50 }
validates :clone_addr, format: { with: URL_REGEX, multiline: true, message: "地址格式不正确" } validates :repository_name, length: { maximum: 100 }
validates :description, length: { maximum: 200 }
validate do validate do
check_project_name(user_id, name) unless name.blank? check_project_name(user_id, name) unless name.blank?
check_repository_name(user_id, repository_name) unless repository_name.blank? check_repository_name(user_id, repository_name) unless repository_name.blank?

View File

@ -6,4 +6,8 @@ module CustomRegexp
PASSWORD = /\A[a-z_A-Z0-9\-\.!@#\$%\\\^&\*\)\(\+=\{\}\[\]\/",'_<>~\·`\?:;|]{8,16}\z/ PASSWORD = /\A[a-z_A-Z0-9\-\.!@#\$%\\\^&\*\)\(\+=\{\}\[\]\/",'_<>~\·`\?:;|]{8,16}\z/
URL = /\Ahttps?:\/\/[-A-Za-z0-9+&@#\/%?=~_|!:,.;]+[-A-Za-z0-9+&@#\/%=~_|]\z/ URL = /\Ahttps?:\/\/[-A-Za-z0-9+&@#\/%?=~_|!:,.;]+[-A-Za-z0-9+&@#\/%=~_|]\z/
IP = /^((\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/ IP = /^((\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/
URL_REGEX = /\A(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?\z/i
REPOSITORY_NAME_REGEX = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]+$/ #只含有数字、字母、下划线不能以下划线开头和结尾
end end

View File

@ -24,4 +24,12 @@ class ApplicationRecord < ActiveRecord::Base
def reset_platform_cache_async_job def reset_platform_cache_async_job
ResetPlatformCacheJob.perform_later ResetPlatformCacheJob.perform_later
end end
def self.strip_param(key)
key.to_s.strip.presence
end
def strip_param(key)
key.to_s.strip.presence
end
end end

View File

@ -1,22 +1,24 @@
# == Schema Information # == Schema Information
# #
# Table name: edu_settings # Table name: edu_settings
# #
# id :integer not null, primary key # id :integer not null, primary key
# name :string(255) # name :string(255)
# value :string(255) # value :string(255)
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# description :string(255) # description :string(255)
# #
# Indexes # Indexes
# #
# index_edu_settings_on_name (name) UNIQUE # index_edu_settings_on_name (name) UNIQUE
# #
class EduSetting < ApplicationRecord class EduSetting < ApplicationRecord
after_commit :expire_value_cache after_commit :expire_value_cache
scope :by_search, -> (keyword){ where("name LIKE :keyword OR value LIKE :keyword", keyword: "%#{strip_param(keyword)}%") unless strip_param(keyword).blank? }
def value_cache_key def value_cache_key
self.class.value_cache_key(name) self.class.value_cache_key(name)
end end

View File

@ -17,6 +17,9 @@ class Site < ApplicationRecord
# common: 普通链接 # common: 普通链接
enum site_type: { add: 0, personal: 1, common: 2 } enum site_type: { add: 0, personal: 1, common: 2 }
scope :by_search, -> (keyword){ where("name LIKE :keyword OR url LIKE :keyword", keyword: "%#{strip_param(keyword)}%") unless strip_param(keyword).blank? }
scope :by_site_type, -> (site_type){ where(site_type: strip_param(site_type)) unless strip_param(site_type).blank? }
def self.set_default_menu def self.set_default_menu
set_add_menu! set_add_menu!
set_personal_menu! set_personal_menu!
@ -26,8 +29,8 @@ class Site < ApplicationRecord
private private
def self.set_add_menu! def self.set_add_menu!
adds= [ adds= [
{name: '新建镜像项目', key: 'add_mirror_project', url: '/projects/mirror/new'}, {name: '新建项目', key: 'add_mirror_project', url: '/projects/mirror/new'},
{name: '新建托管项目', key: 'add_common', url: '/projects/deposit/new'}, {name: '导入项目', key: 'add_common', url: '/projects/deposit/new'},
{name: '新建组织', key: 'add_r', url: '/organize/new'}] {name: '新建组织', key: 'add_r', url: '/organize/new'}]
adds.each { |ele| adds.each { |ele|

View File

@ -0,0 +1,28 @@
class Admins::EduSettingQuery < ApplicationQuery
include CustomSortable
attr_reader :params
sort_columns :id, default_by: :id, default_direction: :desc
def initialize(params)
@params = params
end
def call
collection = EduSetting.all
collection = filter_settings(collection)
custom_sort collection, params[:sort_by], params[:sort_direction]
end
def filter_settings(collection)
by_search(collection)
end
def by_search(collection)
keyword = strip_param(:search)
collection.by_search(keyword)
end
end

View File

@ -0,0 +1,35 @@
class Admins::SiteQuery < ApplicationQuery
include CustomSortable
attr_reader :params
sort_columns :id, default_by: :id, default_direction: :desc
def initialize(params)
@params = params
end
def call
collection = Site.all
collection = filter_sites(collection)
custom_sort collection, params[:sort_by], params[:sort_direction]
end
def filter_sites(collection)
collection = by_search(collection)
collection = by_stie_type(collection)
collection
end
def by_search(collection)
keyword = strip_param(:search)
collection.by_search(keyword)
end
def by_stie_type(collection)
site_type = strip_param(:site_type)
collection.by_site_type(site_type)
end
end

View File

@ -15,8 +15,8 @@ class Gitea::Organization::Repository::CreateService < Gitea::ClientService
private private
def request_params def request_params
create_params = params.merge(readme: "readme") # create_params = params.merge(readme: "readme")
Hash.new.merge(token: token, data: create_params) Hash.new.merge(token: token, data: params)
end end
def url def url

View File

@ -25,8 +25,8 @@ class Gitea::Repository::CreateService < Gitea::ClientService
private private
def request_params def request_params
create_params = params.merge(readme: "readme") # create_params = params.merge(readme: "readme")
Hash.new.merge(token: token, data: create_params) Hash.new.merge(token: token, data: params)
end end
def url def url

View File

@ -43,7 +43,7 @@ class Projects::CreateService < ApplicationService
ignore_id: params[:ignore_id], ignore_id: params[:ignore_id],
license_id: params[:license_id], license_id: params[:license_id],
website: params[:website], website: params[:website],
identifier: params[:repository_name] #新增,hs identifier: params[:repository_name]
} }
end end

View File

@ -32,7 +32,7 @@ class Repositories::MigrateService < ApplicationService
private: params[:hidden], private: params[:hidden],
mirror: wrapper_mirror || false, mirror: wrapper_mirror || false,
auth_username: params[:login], auth_username: params[:login],
auth_password: Base64.decode64(params[:password]) auth_password: Base64.decode64(params[:password] || "")
} }
end end

View File

@ -0,0 +1,38 @@
<div class="modal fade edu_setting-change-modal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><%= type == "create" ? "新增" : "编辑" %></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<%= form_for @edu_setting, url: {controller: "edu_settings", action: "#{type}"} do |p| %>
<div class="modal-body">
<div class="form-group">
<label>
变量名 <span class="ml10 color-orange mr20">*</span>
</label>
<%= p.text_field :name, class: "form-control input-lg",required: true%>
</div>
<div class="form-group">
<label>
变量值 <span class="ml10 color-orange mr20">*</span>
</label>
<%= p.text_field :value, class: "form-control input-lg",required: true%>
</div>
<div class="form-group">
<label>
备注说明
</label>
<%= p.text_area :description, class: "form-control", placeholder: ""%>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
<%= p.submit "确认", class: "btn btn-primary submit-btn" %>
</div>
<% end %>
</div>
</div>
</div>

View File

@ -0,0 +1,36 @@
<table class="table table-hover text-center subject-list-table">
<thead class="thead-light">
<tr>
<th width="5%">序号</th>
<th width="15%">变量名</th>
<th width="35%">变量值</th>
<th width="20%">备注说明</th>
<th width="10%"><%= sort_tag('创建时间', name: 'created_at', path: admins_edu_settings_path) %></th>
<th width="10%">操作</th>
</tr>
</thead>
<tbody>
<% if edu_settings.present? %>
<% edu_settings.each_with_index do |edu_setting, index| %>
<tr class="edu_setting-item-<%= edu_setting.id %>">
<td><%= list_index_no((params[:page] || 1).to_i, index) %></td>
<td>
<%= edu_setting.name %>
</td>
<td><%= edu_setting.value %></td>
<td><%= overflow_hidden_span display_text(edu_setting.description), width: 200 %></td>
<td><%= edu_setting.created_at&.strftime('%Y-%m-%d %H:%M') %></td>
<td class="action-container">
<%= link_to "编辑", edit_admins_edu_setting_path(edu_setting), remote: true, class: "action" %>
<%= link_to "删除", admins_edu_setting_path(edu_setting), method: :delete, data:{confirm: "确认删除的吗?"}, class: "action" %>
</td>
</tr>
<% end %>
<% else %>
<%= render 'admins/shared/no_data_for_table' %>
<% end %>
</tbody>
</table>
<%= render partial: 'admins/shared/paginate', locals: { objects: edu_settings } %>

View File

@ -0,0 +1,2 @@
$("#edu_setting-modals").html("<%= j render(partial: 'admins/edu_settings/form', locals: {type: 'update'}) %>")
$(".edu_setting-change-modal").modal('show');

View File

@ -0,0 +1,22 @@
<% define_admin_breadcrumbs do %>
<% add_admin_breadcrumb('全局变量配置') %>
<% end %>
<div class="box search-form-container edu_settings-list-form">
<%= form_tag(admins_edu_settings_path, method: :get, class: 'form-inline search-form flex-1', remote: true) do %>
<%= text_field_tag(:search, params[:search], class: 'form-control col-12 col-md-2 mr-3', placeholder: '关键字检索') %>
<%= submit_tag('搜索', class: 'btn btn-primary ml-3', 'data-disable-with': '搜索中...') %>
<input type="reset" class="btn btn-secondary clear-btn" value="清空"/>
<% end %>
<%= link_to "新增", new_admins_edu_setting_path, remote: true, class: "btn btn-primary pull-right", "data-disabled-with":"...新增" %>
</div>
<div class="box py-0 pt-4 pl-4 daily-school-statistic-title">
说明:该界面适用于存储全局变量.
</div>
<div class="box admin-list-container edu_settings-list-container">
<%= render partial: 'admins/edu_settings/list', locals: { edu_settings: @edu_settings } %>
</div>
<div id="edu_setting-modals">
</div>

View File

@ -0,0 +1 @@
$('.edu_settings-list-container').html("<%= j( render partial: 'admins/edu_settings/list', locals: { edu_settings: @edu_settings } ) %>");

View File

@ -0,0 +1,2 @@
$("#edu_setting-modals").html("<%= j render(partial: 'admins/edu_settings/form', locals: {type: 'create'}) %>")
$(".edu_setting-change-modal").modal('show');

View File

@ -4,7 +4,7 @@
<div class="box search-form-container laboratory-list-form"> <div class="box search-form-container laboratory-list-form">
<%= form_tag(admins_laboratories_path(unsafe_params), method: :get, class: 'form-inline search-form flex-1', remote: true) do %> <%= form_tag(admins_laboratories_path(unsafe_params), method: :get, class: 'form-inline search-form flex-1', remote: true) do %>
<%= text_field_tag(:keyword, params[:keyword], class: 'form-control col-6 col-md-4 ml-3', placeholder: '学校名称/二级域名前缀检索') %> <%= text_field_tag(:keyword, params[:keyword], class: 'form-control col-6 col-md-4 ml-3', placeholder: '单位名称/二级域名前缀检索') %>
<%= submit_tag('搜索', class: 'btn btn-primary ml-3', 'data-disable-with': '搜索中...') %> <%= submit_tag('搜索', class: 'btn btn-primary ml-3', 'data-disable-with': '搜索中...') %>
<% end %> <% end %>

View File

@ -1,6 +1,6 @@
<% school = laboratory&.school %> <% school = laboratory&.school %>
<td><%= list_index_no((params[:page] || 1).to_i, index) %></td> <td><%= list_index_no((params[:page] || 1).to_i, index) %></td>
<td class="text-left"><%= school&.name || 'Trustie主站' %></td> <td class="text-left"><%= school&.name || '主站' %></td>
<td class="text-left"> <td class="text-left">
<% if laboratory.identifier %> <% if laboratory.identifier %>
<%= link_to laboratory.site, "https://#{laboratory.site}", target: '_blank' %> <%= link_to laboratory.site, "https://#{laboratory.site}", target: '_blank' %>
@ -29,40 +29,10 @@
</div> </div>
</td> </td>
<td><%= laboratory.created_at.strftime('%Y-%m-%d %H:%M') %></td> <td><%= laboratory.created_at.strftime('%Y-%m-%d %H:%M') %></td>
<td>
<% if school.present? && laboratory.id != 1 %>
<%= check_box_tag :sync_course,!laboratory.sync_course,laboratory.sync_course,remote:true,data:{id:laboratory.id},class:"laboratory-sync-form" %>
<% end %>
</td>
<td>
<% if school.present? && laboratory.id != 1 %>
<%= check_box_tag :sync_subject,!laboratory.sync_subject,laboratory.sync_subject,remote:true,data:{id:laboratory.id},class:"laboratory-sync-form" %>
<% end %>
</td>
<td>
<% if school.present? && laboratory.id != 1 %>
<%= check_box_tag :sync_shixun,!laboratory.sync_shixun,laboratory.sync_shixun,remote:true,data:{id:laboratory.id},class:"laboratory-sync-form" %>
<% end %>
</td>
<td class="action-container"> <td class="action-container">
<%= link_to '定制', admins_laboratory_laboratory_setting_path(laboratory), class: 'action' %> <%= link_to '设置', admins_laboratory_laboratory_setting_path(laboratory), class: 'action' %>
<% if school.present? && laboratory.id != 1 %> <% if school.present? && laboratory.id != 1 %>
<%= javascript_void_link '添加管理员', class: 'action', data: { laboratory_id: laboratory.id, toggle: 'modal', target: '.admin-add-laboratory-user-modal' } %> <%= delete_link '删除', admins_laboratory_path(laboratory, element: ".laboratory-item-#{laboratory.id}"), class: 'action' %>
<%= link_to '同步用户', synchronize_user_admins_laboratory_path(laboratory), remote: true, data: { confirm: '确认同步该单位下的所有用户到云上实验室吗?' }, class: 'action' %> <% end %>
<% end %>
<div class="d-inline">
<%= javascript_void_link('更多', class: 'action dropdown-toggle', 'data-toggle': 'dropdown', 'aria-haspopup': true, 'aria-expanded': false) %>
<div class="dropdown-menu more-action-dropdown">
<%= link_to '轮播图', admins_laboratory_carousels_path(laboratory), class: 'dropdown-item' %>
<%= link_to '查看实训项目', admins_laboratory_laboratory_shixuns_path(laboratory), class: 'dropdown-item' %>
<%= link_to '查看实践课程', admins_laboratory_laboratory_subjects_path(laboratory), class: 'dropdown-item' %>
<% if school.present? && laboratory.id != 1 %>
<%= delete_link '删除', admins_laboratory_path(laboratory, element: ".laboratory-item-#{laboratory.id}"), class: 'dropdown-item delete-laboratory-action' %>
<% end %>
</div>
</div>
</td> </td>

View File

@ -7,9 +7,6 @@
<th width="6%">统计链接</th> <th width="6%">统计链接</th>
<th width="22%">管理员</th> <th width="22%">管理员</th>
<th width="10%"><%= sort_tag('创建时间', name: 'id', path: admins_laboratories_path) %></th> <th width="10%"><%= sort_tag('创建时间', name: 'id', path: admins_laboratories_path) %></th>
<th width="4%" title="同步显示主站下该单位的课堂">同步课堂</th>
<th width="4%" title="同步显示主站下该单位用户创建的实践课程">同步实践课程</th>
<th width="4%" title="同步显示主站下该单位用户创建的实训">同步实训</th>
<th width="16%">操作</th> <th width="16%">操作</th>
</tr> </tr>
</thead> </thead>

View File

@ -86,7 +86,9 @@
</div> </div>
</div> </div>
<div class="form-group px-2 setting-item"> <%
=begin%>
<div class="form-group px-2 setting-item">
<div class="setting-item-head"><h6>Banner设置</h6></div> <div class="setting-item-head"><h6>Banner设置</h6></div>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<div class="pl-0 py-3 row setting-item-body"> <div class="pl-0 py-3 row setting-item-body">
@ -140,7 +142,9 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<%
=end%>
<div class="form-group px-2 setting-item"> <div class="form-group px-2 setting-item">
<div class="setting-item-head"> <div class="setting-item-head">

View File

@ -11,6 +11,10 @@
<%= link_to "新增", new_admins_reversed_keyword_path, remote: true, class: "btn btn-primary pull-right", "data-disabled-with":"...新增" %> <%= link_to "新增", new_admins_reversed_keyword_path, remote: true, class: "btn btn-primary pull-right", "data-disabled-with":"...新增" %>
</div> </div>
<div class="box py-0 pt-4 pl-4 daily-school-statistic-title">
旨在为特殊用户群体、特殊场景、路由而保留的关键词Gitlink、Trustie等目前主要在用户注册、创建组织、创建项目等场景下做关键词的验证.
</div>
<div class="box admin-list-container reversed-keyword-list-container"> <div class="box admin-list-container reversed-keyword-list-container">
<%= render partial: 'admins/reversed_keywords/list', locals: { keywords: @keywords } %> <%= render partial: 'admins/reversed_keywords/list', locals: { keywords: @keywords } %>
</div> </div>

View File

@ -26,10 +26,11 @@
<li><%= sidebar_item(admins_project_categories_path, '分类列表', icon: 'sitemap', controller: 'admins-project_categories') %></li> <li><%= sidebar_item(admins_project_categories_path, '分类列表', icon: 'sitemap', controller: 'admins-project_categories') %></li>
<li><%= sidebar_item(admins_project_licenses_path, '开源许可证', icon: 'file-text-o', controller: 'admins-project_licenses') %></li> <li><%= sidebar_item(admins_project_licenses_path, '开源许可证', icon: 'file-text-o', controller: 'admins-project_licenses') %></li>
<li><%= sidebar_item(admins_project_ignores_path, '忽略文件', icon: 'git', controller: 'admins-project_ignores') %></li> <li><%= sidebar_item(admins_project_ignores_path, '忽略文件', icon: 'git', controller: 'admins-project_ignores') %></li>
<li><%= sidebar_item(admins_reversed_keywords_path, '系统保留关键词', icon: 'key', controller: 'admins-reversed_keywords') %></li>
<% end %> <% end %>
</li> </li>
<li><%= sidebar_item(admins_reversed_keywords_path, '系统保留关键词', icon: 'key', controller: 'admins-reversed_keywords') %></li>
<li><%= sidebar_item(admins_laboratories_path, '云上实验室', icon: 'cloud', controller: 'admins-laboratories') %></li> <li><%= sidebar_item(admins_laboratories_path, '云上实验室', icon: 'cloud', controller: 'admins-laboratories') %></li>
<li> <li>
@ -42,6 +43,12 @@
<li><%= sidebar_item(admins_faqs_path, 'FAQ', icon: 'question-circle', controller: 'admins-faqs') %></li> <li><%= sidebar_item(admins_faqs_path, 'FAQ', icon: 'question-circle', controller: 'admins-faqs') %></li>
<% end %> <% end %>
</li> </li>
<li>
<%= sidebar_item_group('#setting-system', '系统配置', icon: 'wrench') do %>
<li><%= sidebar_item(admins_sites_path, 'setting接口配置', icon: 'deaf', controller: 'admins-sites') %></li>
<li><%= sidebar_item(admins_edu_settings_path, '全局变量配置', icon: 'pencil-square', controller: 'admins-edu_settings') %></li>
<% end %>
</li>
<li> <li>
<%= sidebar_item('/admins/sidekiq', '定时任务', icon: 'bell', controller: 'root') %> <%= sidebar_item('/admins/sidekiq', '定时任务', icon: 'bell', controller: 'root') %>
</li> </li>

View File

@ -0,0 +1,42 @@
<div class="modal fade site-change-modal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><%= type == "create" ? "新增" : "编辑" %></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<%= form_for @site, url: {controller: "sites", action: "#{type}"} do |p| %>
<div class="modal-body">
<div class="form-group">
<label>
名称 <span class="ml10 color-orange mr20">*</span>
</label>
<%= p.text_field :name, class: "form-control input-lg",required: true%>
</div>
<div class="form-group">
<label>
路由 <span class="ml10 color-orange mr20">*</span>
</label>
<%= p.text_field :url, class: "form-control input-lg",required: true%>
</div>
<div class="form-group">
<label>
标识 <span class="ml10 color-orange mr20">*</span>
</label>
<%= p.text_field :key, class: "form-control input-lg",required: true%>
</div>
<div class="form-group ">
<label for="status">类型:</label>
<%= p.select :site_type, options_for_select(Site.site_types.map { |key, value| [key.humanize, key] }), {}, class: "form-control" %>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
<%= p.submit "确认", class: "btn btn-primary submit-btn" %>
</div>
<% end %>
</div>
</div>
</div>

View File

@ -0,0 +1,38 @@
<table class="table table-hover text-center subject-list-table">
<thead class="thead-light">
<tr>
<th width="5%">序号</th>
<th width="15%">名称</th>
<th width="30%">路由</th>
<th width="20%">标识</th>
<th width="10%">类型</th>
<th width="20%"><%= sort_tag('创建时间', name: 'created_at', path: admins_sites_path) %></th>
<th width="25%">操作</th>
</tr>
</thead>
<tbody>
<% if sites.present? %>
<% sites.each_with_index do |site, index| %>
<tr class="site-item-<%= site.id %>">
<td><%= list_index_no((params[:page] || 1).to_i, index) %></td>
<td>
<%= overflow_hidden_span display_text(site.name), width: 150 %>
</td>
<td><%= site.url %></td>
<td><%= overflow_hidden_span display_text(site.key), width: 150 %></td>
<td><%= site.site_type.humanize %></td>
<td><%= site.created_at&.strftime('%Y-%m-%d %H:%M') %></td>
<td class="action-container">
<%= link_to "编辑", edit_admins_site_path(site), remote: true, class: "action" %>
<%= link_to "删除", admins_site_path(site), method: :delete, data:{confirm: "确认删除的吗?"}, class: "action" %>
</td>
</tr>
<% end %>
<% else %>
<%= render 'admins/shared/no_data_for_table' %>
<% end %>
</tbody>
</table>
<%= render partial: 'admins/shared/paginate', locals: { objects: sites } %>

View File

@ -0,0 +1,2 @@
$("#site-modals").html("<%= j render(partial: 'admins/sites/form', locals: {type: 'update'}) %>")
$(".site-change-modal").modal('show');

View File

@ -0,0 +1,24 @@
<% define_admin_breadcrumbs do %>
<% add_admin_breadcrumb('Setting接口配置') %>
<% end %>
<div class="box search-form-container sites-list-form">
<%= form_tag(admins_sites_path, method: :get, class: 'form-inline search-form flex-1', remote: true) do %>
<div class="form-group mr-2">
<label for="status">类型:</label>
<% type_options = [['全部', ''], ['Add', Site.site_types[:add]], ['Personal', Site.site_types[:personal]], ['Common', Site.site_types[:common]]] %>
<%= select_tag(:site_type, options_for_select(type_options), class: 'form-control') %>
</div>
<%= text_field_tag(:search, params[:search], class: 'form-control col-12 col-md-2 mr-3', placeholder: '关键字检索') %>
<%= submit_tag('搜索', class: 'btn btn-primary ml-3', 'data-disable-with': '搜索中...') %>
<input type="reset" class="btn btn-secondary clear-btn" value="清空"/>
<% end %>
<%= link_to "新增", new_admins_site_path, remote: true, class: "btn btn-primary pull-right", "data-disabled-with":"...新增" %>
</div>
<div class="box admin-list-container sites-list-container">
<%= render partial: 'admins/sites/list', locals: { sites: @sites } %>
</div>
<div id="site-modals">
</div>

View File

@ -0,0 +1 @@
$('.sites-list-container').html("<%= j( render partial: 'admins/sites/list', locals: { sites: @sites } ) %>");

View File

@ -0,0 +1,2 @@
$("#site-modals").html("<%= j render(partial: 'admins/sites/form', locals: {type: 'create'}) %>")
$(".site-change-modal").modal('show');

View File

@ -1,27 +0,0 @@
<%= form_with(model: edu_setting, local: true) do |form| %>
<% if edu_setting.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(edu_setting.errors.count, "error") %> prohibited this edu_setting from being saved:</h2>
<ul>
<% edu_setting.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :name %>
<%= form.text_field :name %>
</div>
<div class="field">
<%= form.label :value %>
<%= form.text_field :value %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>

View File

@ -1,6 +0,0 @@
<h1>Editing Edu Setting</h1>
<%= render 'form', edu_setting: @edu_setting %>
<%= link_to 'Show', @edu_setting %> |
<%= link_to 'Back', edu_settings_path %>

View File

@ -1,30 +0,0 @@
<p id="notice"><%= notice %></p>
<h1>EduCoder公共配置</h1>
<p>说明:该界面适用于存储全局变量</p>
<table>
<thead>
<tr>
<th>变量名</th>
<th>变量值</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @edu_settings.each do |edu_setting| %>
<tr>
<td><%= edu_setting.name %></td>
<td><%= edu_setting.value %></td>
<td><%= link_to 'Show', edu_setting %></td>
<td><%= link_to 'Edit', edit_edu_setting_path(edu_setting) %></td>
<td><%= link_to 'Destroy', edu_setting, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Edu Setting', new_edu_setting_path %>

View File

@ -1,5 +0,0 @@
<h1>New Edu Setting</h1>
<%= render 'form', edu_setting: @edu_setting %>
<%= link_to 'Back', edu_settings_path %>

View File

@ -1,14 +0,0 @@
<p id="notice"><%= notice %></p>
<p>
<strong>Name:</strong>
<%= @edu_setting.name %>
</p>
<p>
<strong>Value:</strong>
<%= @edu_setting.value %>
</p>
<%= link_to 'Edit', edit_edu_setting_path(@edu_setting) %> |
<%= link_to 'Back', edu_settings_path %>

View File

@ -3,18 +3,18 @@ json.login @user.login
json.name @user.full_name json.name @user.full_name
json.location @user.location json.location @user.location
json.image_url url_to_avatar(@user) json.image_url url_to_avatar(@user)
json.url "#{request.base_url }/users/#{@user.login}" json.url "#{request.base_url }/#{@user.login}"
json.followers_count @user.followers_count json.followers_count @user.followers_count
json.followers_url "#{base_url}/users/#{@user.login}/fan_users" json.followers_url "#{base_url}/#{@user.login}/fan_users"
json.following_count @user.following_count json.following_count @user.following_count
json.following_url "#{base_url}/users/#{@user.login}/watchers" json.following_url "#{base_url}/#{@user.login}/watchers"
json.projects_count @user.projects_count json.projects_count @user.projects_count
json.projects_url "#{base_url}/users/#{@user.login}" json.projects_url "#{base_url}/#{@user.login}"
json.projects_count @user.projects_count json.projects_count @user.projects_count
json.is_watch current_user&.watched?(@user) json.is_watch current_user&.watched?(@user)
json.organizations @user.organizations do |organization| json.organizations @user.organizations do |organization|
json.login organization.login json.login organization.login
json.name organization.real_name json.name organization.real_name
json.image_url url_to_avatar(organization) json.image_url url_to_avatar(organization)
json.url "#{base_url}/organize/#{organization.login}" json.url "#{base_url}/#{organization.login}"
end end

View File

@ -651,6 +651,8 @@ Rails.application.routes.draw do
get :visits_static get :visits_static
end end
end end
resources :sites
resources :edu_settings
resources :project_languages resources :project_languages
resources :project_categories resources :project_categories
resources :project_licenses resources :project_licenses

View File

@ -1,51 +0,0 @@
# 新版Git测试说明
统一:
参考实训http://47.96.87.25:48080/shixuns/ca9fvobr/repository
请求方式POST
参数{repo_path: "educoder/ca9fvobr.git"}
公共方法:
['add_repository', 'fork_repository', 'delete_repository', 'file_tree', 'update_file',
'file_content', 'commits']
1、仓库目录接口
测试方法模拟1000个用户同时去访问接口访问方式
http://121.199.19.206:9000/api/file_tree
参数:
{repo_path: "educoder/ca9fvobr.git", path: ''} // 如:{path: 'step1'}
2、创建版本库
访问地址http://121.199.19.206:9000/api/add_repository
参数:
{repo_path: 比如:"Hjqreturn/aaass1.git"}
3、fork版本库
http://121.199.19.206:9000/api/fork_repository
参数:
{repo_path: 'Hjqreturn/aaass1.git', fork_repository_path: 'educoder/ca9fvobr.git'}
说明fork_repository_path是新项目的repo_path, repo_path是源项目的
4、更新文件
测试方法:
1、更新同一个文件并发量可以不用很大可以用同一个用户并发10-100
2、更新不同的文件可以依据创建的版本库去更新
访问地址http://121.199.19.206:9000/api/update_file
参数:
{repo_path: "educoder/ca9fvobr.git",
file_path: 'step1/main.py',
message: 'commit by test',
content: 'afdjadsjfj1111',
author_name: 'guange',
author_email: '8863824@gmil.com'}
5、获取文件内容
访问地址http://121.199.19.206:9000/api/file_content
参数:
{repo_path: "educoder/ca9fvobr.git", path: 'step1/main.py',}
6、获取提交记录
访问地址http://121.199.19.206:9000/api/commits
参数:
{repo_path: 比如:"educoder/ca9fvobr.git"}

View File

@ -1,72 +0,0 @@
mbtclufr
9op3hs4j
96ctv7yr
rtmzxfke
ofqxthrf
czu9w4gj
9fpzj6et
pwhc865b
maozpx4l
y5wh2ofx
b5rzhpf3
bs243nrl
47fn2yfb
kwotfxey
w5468sbp
fyekprio
q6ze5fih
b5hjq9zm
ky8pbqux
53phc7nq
b9j2yuix
9t3uphwk
iokm8ah2
qlsy6xb4
345bqhfi
v728fqia
4euftvf2
f23sef5m
nhqis8m9
qp72tb5x
gt3anszw
tng6heyf
nb9keawo
elgnbkp9
4neslomg
lh35s6ma
xmc4rpay
qrpaxi6b
9fla2zry
efuibzrm
fzp3iu4w
pligsyn8
glbksr29
kfm7ghyc
p6hk3svf
p539gjhm
am5o73er
4x3qwrbe
fqosyl8g
of5z3fci
tb7hw62n
ie6zxg7r
4q2bmy9h
fpm3u5yb
nikx3ojt
vt82s9bq
ma59fefo
lxa39tfq
4gnockxf
nxwg84ey
fmie8nzb
w5nsr24v
4hn3efwc
h9ljfbq7
nuv54t8b
2te9fmfq
vihnsayz
qhlyn82s
vw74kmfr
vcta36bz
henz425l
g529v38z

View File

@ -0,0 +1,47 @@
require 'rails_helper'
RSpec.describe Admins::EduSettingsController, type: :controller do
describe "GET #index" do
it "returns http success" do
get :index
expect(response).to have_http_status(:success)
end
end
describe "GET #new" do
it "returns http success" do
get :new
expect(response).to have_http_status(:success)
end
end
describe "GET #update" do
it "returns http success" do
get :update
expect(response).to have_http_status(:success)
end
end
describe "GET #edit" do
it "returns http success" do
get :edit
expect(response).to have_http_status(:success)
end
end
describe "GET #create" do
it "returns http success" do
get :create
expect(response).to have_http_status(:success)
end
end
describe "GET #destroy" do
it "returns http success" do
get :destroy
expect(response).to have_http_status(:success)
end
end
end

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe Admins::SitesController, type: :controller do
end

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe "edu_settings/create.html.erb", type: :view do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe "edu_settings/destroy.html.erb", type: :view do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe "edu_settings/edit.html.erb", type: :view do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe "edu_settings/index.html.erb", type: :view do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe "edu_settings/new.html.erb", type: :view do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe "edu_settings/update.html.erb", type: :view do
pending "add some examples to (or delete) #{__FILE__}"
end