admins: shixun mirror manage feature

dev_unstable^2
p31729568 5 years ago
parent d92afe5282
commit 5e4bd66c56

@ -32,8 +32,11 @@ $(document).on('turbolinks:load', function(){
// flash alert提示框自动关闭
if($('.admin-alert-container .alert').length > 0){
setTimeout(function(){
$('.admin-alert-container .alert').alert('close');
$('.admin-alert-container .alert:not(.alert-danger)').alert('close');
}, 2000);
setTimeout(function(){
$('.admin-alert-container .alert.alert-danger').alert('close');
}, 5000);
}
});

@ -0,0 +1,19 @@
$(document).on('turbolinks:load', function() {
if ($('body.admins-mirror-repositories-edit-page, body.admins-mirror-repositories-update-page').length > 0) {
var $form = $('form.edit-mirror');
$form.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
rules: {
"mirror_repository[type_name]": {
required: true
}
}
});
$form.submit(function(e){
if(!$form.valid()){ e.preventDefault(); }
});
}
});

@ -0,0 +1,4 @@
$(document).on('turbolinks:load', function() {
if ($('body.admins-mirror-repositories-index-page').length > 0) {
}
});

@ -0,0 +1,32 @@
$(document).on('turbolinks:load', function() {
$('.admin-modal-container').on('show.bs.modal', '.modal.admin-choose-mirror-modal', function(){
var $modal = $('.modal.admin-choose-mirror-modal');
var $form = $modal.find('form.admin-choose-mirror-form');
var validateForm = function(){
var checkedValue = $form.find('input[name="mirror_number"]:checked').val();
if(checkedValue == undefined){
$modal.find('.error').html('必须选择一种镜像保存!');
return false;
}
return true;
}
$modal.on('click', '.submit-btn', function(){
$form.find('.error').html('');
var url = $form.attr('action');
if (validateForm()) {
$.ajax({
method: 'POST',
dataType: 'script',
url: url,
data: $form.serialize(),
}).done(function(){
$modal.modal('hide');
});
}
});
})
});

@ -0,0 +1,89 @@
$(document).on('turbolinks:load', function() {
var $modal = $('.modal.admin-replace-mirror-modal');
if ($modal.length > 0) {
var $form = $modal.find('form.admin-replace-mirror-form');
var $mirrorIdInput = $modal.find('.modal-body input[name="mirror_id"]');
var $mirrorSelect = $modal.find('.new-mirror-select');
var setMirror = function(id, name){
$mirrorIdInput.val(id);
$form.find('.mirror-id-container').html(id);
$form.find('.mirror-name-container').html(name);
}
$form.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
rules: {
new_mirror_id: {
required: true
},
},
messages: {
new_mirror_id: {
required: '请选择新镜像'
}
}
});
// modal ready fire
$modal.on('show.bs.modal', function (event) {
var $link = $(event.relatedTarget);
var mirrorId = $link.data('id');
var mirrorName = $link.data('name');
setMirror(mirrorId, mirrorName);
$mirrorSelect.select2('val', ' ');
});
$modal.on('hide.bs.modal', function () {
setMirror('', '');
$mirrorSelect.select2('val', ' ');
$('#new_mirror_id-error').remove();
});
$mirrorSelect.select2({
theme: 'bootstrap4',
placeholder: '输入要合并的镜像名',
minimumInputLength: 1,
ajax: {
url: '/admins/mirror_repositories/for_select',
dataType: 'json',
data: function(params){
return { keyword: params.term };
},
processResults: function(data){
return { results: data.mirrors }
}
},
templateResult: function (item) {
if(!item.id || item.id === '') return item.text;
return item.name;
},
templateSelection: function(item){
if (item.id) {
$('#new_mirror_id-error').remove();
$('#new_mirror_id').val(item.id);
}
return item.name || item.text;
}
});
$modal.on('click', '.submit-btn', function(){
$form.find('.error').html('');
if ($form.valid()) {
var url = $form.data('url');
$.ajax({
method: 'POST',
dataType: 'script',
url: url,
data: $form.serialize(),
}).done(function(){
$modal.modal('hide');
});
}
});
}
});

@ -47,4 +47,9 @@ label.error {
.flex-1 {
flex: 1;
}
}
.font-12 { font-size: 12px !important; }
.font-14 { font-size: 14px !important; }
.font-16 { font-size: 16px !important; }
.font-18 { font-size: 18px !important; }

@ -0,0 +1,11 @@
class Admins::ChooseMirrorRepositoriesController < Admins::BaseController
def new
@mirror = MirrorRepository.find(params[:mirror_id])
@new_mirror = MirrorOperationRecord.where(mirror_repository_id: @mirror.id, status: 1, user_id: -1).first
end
def create
mirror = MirrorRepository.find(params[:mirror_id])
Admins::ChooseMirrorService.call(mirror, current_user, params[:mirror_number])
end
end

@ -0,0 +1,96 @@
class Admins::MirrorRepositoriesController < Admins::BaseController
before_action :check_shixun_mirrors!, only: [:index]
def index
mirrors = MirrorRepository.all
mirrors = mirrors.reorder(status: :desc, main_type: :desc, type_name: :asc)
@mirrors = paginate mirrors.includes(:mirror_scripts)
@error_mirror_names = MirrorRepository.where(status: 5).pluck(:name)
end
def new
@mirror = MirrorRepository.new
end
def create
@mirror = MirrorRepository.new
Admins::SaveMirrorRepositoryService.call(@mirror, current_user, form_params)
flash[:success] = '保存成功'
redirect_to edit_admins_mirror_repository_path(@mirror)
rescue ActiveRecord::RecordInvalid
flash.now[:danger] = '保存失败'
render 'new'
rescue Admins::SaveMirrorRepositoryService::Error => ex
flash.now[:danger] = ex.message
render 'new'
end
def edit
@mirror = current_mirror
end
def update
@mirror = current_mirror
Admins::SaveMirrorRepositoryService.call(current_mirror, current_user, form_params)
flash[:success] = '保存成功'
redirect_to edit_admins_mirror_repository_path(current_mirror)
rescue ActiveRecord::RecordInvalid
flash.now[:danger] = '保存失败'
render 'edit'
rescue Admins::SaveMirrorRepositoryService::Error => ex
flash.now[:danger] = ex.message
render 'edit'
end
def destroy
return render_js_error('该状态下不允许删除') unless current_mirror.deletable?
current_mirror.destroy!
render_delete_success
end
def for_select
mirrors = MirrorRepository.all
keyword = params[:keyword].to_s.strip
mirrors = mirrors.where('name LIKE ?', "%#{keyword}%") if keyword.present?
@mirrors = paginate mirrors
render_ok(count: @mirrors.total_count, mirrors: @mirrors.as_json(only: %i[id name]))
end
def merge
origin_mirror = MirrorRepository.find(params[:mirror_id])
mirror = MirrorRepository.find(params[:new_mirror_id])
ActiveRecord::Base.transaction do
origin_mirror.update!(name: mirror.name, mirrorID: mirror.mirrorID)
mirror.destroy!
end
end
private
def current_mirror
@_current_mirror ||= MirrorRepository.find(params[:id])
end
def form_params
columns = %i[type_name main_type time_limit resource_limit cpu_limit memory_limit description status]
params.require(:mirror_repository).permit(*columns)
end
def check_shixun_mirrors!
return unless request.format.html?
Admins::CheckShixunMirrorsService.call
rescue Admins::CheckShixunMirrorsService::Error => e
internal_server_error(e.message)
end
end

@ -25,11 +25,11 @@ module Admins::RenderHelper
end
alias_method :render_error, :render_unprocessable_entity
def internal_server_error
def internal_server_error(message = '系统错误')
respond_to do |format|
format.html { render 'admins/shared/500' }
format.js { render_js_error('系统错误') }
format.json { render status: 500, json: { message: '系统错误' } }
format.html { render partail: 'admins/shared/500', locals: { message: message } }
format.js { render_js_error(message) }
format.json { render status: 500, json: { message: message } }
end
end

@ -0,0 +1,23 @@
module Admins::MirrorRepositoriesHelper
def mirror_type_tag(mirror)
case mirror.main_type
when '1' then '<i class="fa fa-star text-success font-16" aria-hidden="true" data-toggle="tooltip" data-title="主类别"></i>'.html_safe
when '0' then '<i class="fa fa-star text-secondary font-16" aria-hidden="true" data-toggle="tooltip" data-title="子类别"></i>'.html_safe
end
end
def mirror_status_tag(mirror)
case mirror.status
when 0
'<i class="fa fa-check-circle text-secondary font-16" data-toggle="tooltip" data-title="未发布"></i>'.html_safe
when 1
'<i class="fa fa-check-circle text-success font-16" data-toggle="tooltip" data-title="已发布"></i>'.html_safe
when 2, 3
'<i class="fa fa-exclamation-circle text-danger font-16" data-toggle="tooltip" data-title="被修改"></i>'.html_safe
when 4
'<i class="fa fa-times-circle text-danger font-18" data-toggle="tooltip" data-title="被删除"></i>'.html_safe
when 5
'<i class="fa fa-exclamation-circle text-warning font-16" data-toggle="tooltip" data-title="子节点异常"></i>'.html_safe
end
end
end

@ -0,0 +1,7 @@
# status: 0 创建镜像; 1 修改镜像ID 2 修改镜像name 3 删除镜像 4.从主节点同步镜像到子节点(子节点发生异常), 5. 修改镜像别名, 6. 修改镜像的状态
# user_id: -1时证明是非人为因素造成中间层异常导致
class MirrorOperationRecord < ActiveRecord::Base
default_scope { order(created_at: :desc) }
belongs_to :mirror_repository
end

@ -8,4 +8,8 @@ class MirrorRepository < ApplicationRecord
scope :published_mirror, -> { where(status: 1) }
scope :published_main_mirror, -> { published_mirror.where(main_type: 1) }
scope :published_small_mirror, -> { published_mirror.where(main_type: 0) }
def deletable?
status != 1 && !shixun_mirror_repositories.exists?
end
end

@ -0,0 +1,89 @@
class Admins::CheckShixunMirrorsService < ApplicationService
Error = Class.new(StandardError)
def call
bridge_images
ActiveRecord::Base.transaction do
check_sync_mirrors!
check_mirrors!
end
end
private
def mirrors
bridge_images['images']
end
def sync_mirrors
bridge_images['imagesNotSync']
end
def check_mirrors!
return if mirrors.blank?
image_names = []
mirrors.each do |data|
mirror = JSON.parse(data)
name_repository = MirrorRepository.find_by(name: mirror['imageName'])
id_repository = MirrorRepository.find_by(mirrorID: mirror['imageID'])
image_names << mirror['imageName']
if name_repository.blank? && id_repository.present? # 镜像名称被修改
id_repository.update_column(:status, 2)
MirrorOperationRecord.create!(mirror_repository_id: id_repository.id, mirror_id: mirror['imageID'],
mirror_name: mirror['imageName'], status: 2, user_id: -1)
elsif name_repository.blank? # 镜像不存在、创建镜像
new_repository = MirrorRepository.create!(mirrorID: mirror['imageID'], name: mirror['imageName'])
MirrorOperationRecord.create!(mirror_repository_id: new_repository.id, mirror_id: mirror['imageID'],
mirror_name: mirror['imageName'], status: 0, user_id: -1)
elsif name_repository.mirrorID != mirror['imageID'] # 镜像ID被修改
name_repository.update_column(:status, 2)
MirrorOperationRecord.create!(mirror_repository_id: name_repository.id, mirror_id: mirror['imageID'],
mirror_name: mirror['imageName'], status: 1, user_id: -1)
end
end
# 判断中间层镜像是否被删除
MirrorRepository.find_each do |mirror|
next if mirror&.name.blank? || image_names.index(mirror.name)
mirror.update_column(:status, 4)
MirrorOperationRecord.create!(mirror_repository_id: mirror.id, mirror_id: mirror&.mirrorID,
mirror_name: mirror.name, status: 3, user_id: -1)
end
end
def check_sync_mirrors!
return if sync_mirrors.blank?
sync_mirrors.each do |data|
mirror = JSON.parse(data)
repository = MirrorRepository.find_by(name: mirror['imageName'])
next if repository.blank? || repository.status != 1
repository.update_column(:status, 5)
MirrorOperationRecord.create!(mirror_repository_id: repository.id, mirror_id: mirror['imageID'],
mirror_name: mirror['imageName'], status: 4, user_id: -1)
end
end
def bridge_images
@_bridge_images ||= begin
url = EduSetting.get('cloud_bridge')
res = Faraday.get(url)
raise Error, '拉取镜像信息异常' if res && res['code'].nonzero?
res
rescue => e
Rails.logger.error("get response failed ! #{e.message}")
raise Error, '实训云平台繁忙繁忙等级84'
end
end
end

@ -0,0 +1,21 @@
class Admins::ChooseMirrorService < ApplicationService
attr_reader :mirror, :user, :number
def initialize(mirror, user, mirror_number)
@mirror = mirror
@user = user
@number = mirror_number
end
def call
if mirror.mirrorID == number
mirror.update_column(:status, 1)
return
end
old_number = mirror.mirrorID
mirror.update!(mirrorID: number, status: 1)
MirrorOperationRecord.create!(mirror_repository_id: mirror.id, mirror_id: number, mirror_name: mirror.name,
status: 1, user_id: user.id, old_tag: old_number, new_tag: mirror.mirrorID)
end
end

@ -0,0 +1,37 @@
class Admins::SaveMirrorRepositoryService < ApplicationService
Error = Class.new(StandardError)
attr_reader :mirror, :user, :params
def initialize(mirror, user, params)
@mirror = mirror
@user = user
@params = params
end
def call
mirror.assign_attributes(params)
raise Error, '镜像别名重复' if MirrorRepository.where.not(id: mirror.id).exists?(type_name: params[:type_name])
ActiveRecord::Base.transaction do
record_operation! if mirror.persisted?
mirror.save!
end
end
private
def record_operation!
if mirror.type_name_changed?
MirrorOperationRecord.create!(mirror_repository_id: mirror.id, status: 5,
user_id: user.id, old_tag: mirror.type_name_in_database,
new_tag: mirror.type_name)
elsif mirror.status_changed?
MirrorOperationRecord.create!(mirror_repository_id: mirror.id, status: 5,
user_id: user.id, old_tag: mirror.status_in_database,
new_tag: mirror.status)
end
end
end

@ -0,0 +1,5 @@
$.notify({ message: '操作成功' },{ type: 'success' });
setTimeout(function(){
window.location.reload();
}, 500)

@ -0,0 +1,2 @@
$('.admin-modal-container').html("<%= j( render partial: 'admins/mirror_repositories/shared/choose_mirror_modal', locals: { mirror: @mirror, new_mirror: @new_mirror } ) %>");
$('.modal.admin-choose-mirror-modal').modal('show');

@ -0,0 +1,8 @@
<%
define_admin_breadcrumbs do
add_admin_breadcrumb('镜像管理', admins_mirror_repositories_path)
add_admin_breadcrumb('镜像详情')
end
%>
<%= render partial: 'admins/mirror_repositories/shared/form', locals: { mirror: @mirror, form_action: 'update' } %>

@ -0,0 +1,23 @@
<% define_admin_breadcrumbs do %>
<% add_admin_breadcrumb('镜像管理') %>
<% end %>
<div class="box search-form-container mirror-repository-list-form">
<form class="flex-1"></form>
<%= link_to '新建', new_admins_mirror_repository_path, class: 'btn btn-primary' %>
</div>
<% if @error_mirror_names.present? %>
<div class="box pb-0">
以下镜像异常:
<% @error_mirror_names.each do |mirror_name| %>
<span class="ml-2 text-danger"><%= mirror_name %></span>
<% end %>
</div>
<% end %>
<div class="box mirror-repository-list-container">
<%= render partial: 'admins/mirror_repositories/shared/list', locals: { mirrors: @mirrors } %>
</div>
<%= render 'admins/mirror_repositories/shared/replace_mirror_modal' %>

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

@ -0,0 +1,5 @@
$.notify({ message: '操作成功' },{ type: 'success' });
setTimeout(function(){
window.location.reload();
}, 500)

@ -0,0 +1,8 @@
<%
define_admin_breadcrumbs do
add_admin_breadcrumb('镜像管理', admins_mirror_repositories_path)
add_admin_breadcrumb('新建镜像')
end
%>
<%= render partial: 'admins/mirror_repositories/shared/form', locals: { mirror: @mirror, form_action: 'create' } %>

@ -0,0 +1,42 @@
<div class="modal fade admin-choose-mirror-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">更新镜像仓库</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<%= form_tag(admins_choose_mirror_repositories_path(mirror_id: mirror.id), method: :post, class: 'admin-choose-mirror-form') do %>
<div class="form-group row">
<div class="col-md-2"></div>
<div class="col-md-5">ID</div>
<div class="col-md-5">名称</div>
</div>
<div class="form-group row">
<div class="col-md-2">旧镜像</div>
<div class="col-md-5 form-check">
<input class="form-check-input" type="radio" name="mirror_number" id="old-mirror-check" value="<%= mirror.mirrorID %>">
<label class="form-check-label" for="old-mirror-check"><%= mirror.mirrorID %></label>
</div>
<div class="col-md-5"><%= mirror.name %></div>
</div>
<div class="form-group row">
<div class="col-md-2">新镜像</div>
<div class="col-md-5 form-check">
<input class="form-check-input" type="radio" name="mirror_number" id="new-mirror-check" value="<%= new_mirror.mirror_id %>">
<label class="form-check-label" for="new-mirror-check"><%= new_mirror.mirror_id %></label>
</div>
<div class="col-md-5"><%= new_mirror.mirror_name %></div>
</div>
<div class="mt-2 error text-danger"></div>
<% end %>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary submit-btn">确认</button>
</div>
</div>
</div>
</div>

@ -0,0 +1,42 @@
<div class="box edit-mirror-repository-container">
<%= simple_form_for([:admins, mirror], url: { action: form_action }, html: { class: 'edit-mirror col-md-12' }, defaults: { wrapper_html: { class: 'col-md-4' } }) do |f| %>
<% unless mirror.new_record? %>
<div class="row">
<%= f.input :mirrorID, label: '镜像ID', input_html: { readonly: true, class: 'form-control-plaintext' } %>
<%= f.input :name, label: '镜像名称', input_html: { readonly: true, class: 'form-control-plaintext' } %>
</div>
<% end %>
<div class="row">
<%= f.input :type_name, as: :string, label: '镜像别名 *' %>
<div class="form-group select optional col-md-4">
<%= f.label :main_type, label: '类别' %>
<%= f.select :main_type, [['主类别', 1],['小类别', 0]], {}, class: 'form-control optional' %>
</div>
</div>
<div class="row">
<%= f.input :time_limit, as: :integer, label: '评测时限(S)' %>
<%= f.input :resource_limit, as: :integer, label: '磁盘限制(K)' %>
</div>
<div class="row">
<%= f.input :cpu_limit, as: :integer, label: 'CPU限制(核)' %>
<%= f.input :memory_limit, as: :integer, label: '内存限制(M)' %>
</div>
<div class="row">
<%= f.input :description, as: :text, label: '描述', wrapper_html: { class: 'col-md-8' } %>
</div>
<div class="row">
<%= f.input :status, as: :radio_buttons, label: '状态', collection: [%w(未发布 0), %w(已发布 1)], wrapper_html: { class: 'col-md-4' } %>
</div>
<div class="row">
<%= f.button :submit, value: '保存', class: 'btn-primary mr-3 px-4', 'data-disable-with': '保存中...' %>
<%= link_to '取消', admins_mirror_repositories_path, class: 'btn btn-secondary px-4' %>
</div>
<% end %>
</div>

@ -0,0 +1,54 @@
<table class="table table-hover text-center mirror-repository-list-table">
<thead class="thead-light">
<tr>
<th width="6%">ID</th>
<th width="6%">类别</th>
<th width="12%" class="text-left">镜像别名</th>
<th width="16%" class="text-left">镜像名称</th>
<th width="22%" class="text-left">镜像描述</th>
<th width="14%">修改时间</th>
<th width="6%">脚本</th>
<th width="6%">状态</th>
<th width="12%">操作</th>
</tr>
</thead>
<tbody>
<% if mirrors.present? %>
<% mirrors.each do |mirror| %>
<tr class="mirror-repository-item-<%= mirror.id %>">
<td><%= mirror.id %></td>
<td><%= mirror_type_tag(mirror) %></td>
<td class="text-left"><%= display_text(mirror.type_name) %></td>
<td class="text-left"><%= overflow_hidden_span mirror.name, width: 150 %></td>
<td class="text-left"><%= overflow_hidden_span mirror.description, width: 240 %></td>
<td><%= mirror.updated_at.strftime('%Y-%m-%d %H:%M') %></td>
<td>
<% if mirror.main_type == "1" %>
<%= link_to "/users/modify_script?mirror_id=#{mirror.id}", target: '_blank' do %>
<i class="fa fa-file-text <%= mirror.mirror_scripts.blank? ? 'text-danger' : 'text-success' %>" aria-hidden="true" data-toggle="tooltip" data-title="脚本模板"></i>
<% end %>
<% end %>
</td>
<td><%= mirror_status_tag mirror %></td>
<td class="action-container">
<%= link_to '编辑', edit_admins_mirror_repository_path(mirror), class: 'action edit-action' %>
<% if mirror.status == 2 %>
<%= link_to '同步', new_admins_choose_mirror_repository_path(mirror_id: mirror.id), remote: true, class: 'action sync-action' %>
<% end %>
<%= javascript_void_link '替换', class: 'action replace-action', data: { toggle: 'modal', target: '.admin-replace-mirror-modal', id: mirror.id, name: mirror.name } %>
<% if mirror.deletable? %>
<%= delete_link '删除', admins_mirror_repository_path(mirror, element: ".mirror-repository-item-#{mirror.id}"), class: 'delete-mirror-repository-action' %>
<% end %>
</td>
</tr>
<% end %>
<% else %>
<%= render 'admins/shared/no_data_for_table' %>
<% end %>
</tbody>
</table>
<%= render partial: 'admins/shared/paginate', locals: { objects: mirrors } %>

@ -0,0 +1,33 @@
<div class="modal fade admin-replace-mirror-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">镜像替换</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form class="admin-replace-mirror-form" data-url="<%= merge_admins_mirror_repositories_path %>">
<%= hidden_field_tag(:mirror_id, nil) %>
<div class="form-group d-flex">
<div>被替换镜像:</div>
<div class="ml-2">ID<span class="mx-1 text-info mirror-id-container"></span></div>
<div class="ml-2">名称:<span class="mx-1 text-info mirror-name-container"></span></div>
</div>
<div class="form-group d-flex">
<label for="new_mirror_id" class="col-form-label">选择新镜像:</label>
<div class="d-flex flex-column-reverse w-75">
<select id="new_mirror_id" name="new_mirror_id" class="form-control new-mirror-select"></select>
</div>
</div>
<div class="error text-danger"></div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary submit-btn">确认</button>
</div>
</div>
</div>
</div>

@ -2,5 +2,5 @@
<div class="global-error-code">
<span>500</span>
</div>
<div class="global-error-text">系统错误</div>
<div class="global-error-text"><%= message %></div>
</div>

@ -1,5 +1,5 @@
<div class="paginate-container">
<% if objects.size.nonzero? %>
<% if objects && objects.size.nonzero? %>
<div class="paginate-total"><%= page_entries_info objects %></div>
<% end %>
<%= paginate objects, views_prefix: 'admins', remote: true %>

@ -20,6 +20,12 @@
<% end %>
</li>
<li>
<%= sidebar_item_group('#shixun-submenu', '实训管理', icon: 'window-restore') do %>
<li><%= sidebar_item(admins_mirror_repositories_path, '镜像管理', icon: 'cubes', controller: 'admins-mirror_repositories') %></li>
<% end %>
</li>
<!-- <li>-->
<%#= sidebar_item_group('#course-submenu', '课堂+', icon: 'mortar-board') do %>
<!-- <li><%#= sidebar_item('#', '课程列表', icon: 'calendar', controller: '') %></li>-->

@ -131,7 +131,7 @@
<div class="form-row mt-4">
<%= f.button :submit, value: '保存', class: 'btn-primary mr-3 px-4' %>
<%= link_to '取消', 'javascript:history.go(-1)', class: 'btn btn-secondary px-4' %>
<%= link_to '取消', admins_users_path, class: 'btn btn-secondary px-4' %>
</div>
<% end %>
</div>

@ -786,6 +786,13 @@ Rails.application.routes.draw do
post :refuse
end
end
resources :mirror_repositories, only: [:index, :new, :create, :edit, :update, :destroy] do
collection do
post :merge
get :for_select
end
end
resources :choose_mirror_repositories, only: [:new, :create]
end
#git 认证回调

Loading…
Cancel
Save