Initial commit: Redmine LDAP Config Plugin
🤖 Generated with Claude Code
This commit is contained in:
commit
e7dd7b0f19
111
app/controllers/ldap_config_controller.rb
Normal file
111
app/controllers/ldap_config_controller.rb
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
class LdapConfigController < ApplicationController
|
||||||
|
layout 'admin'
|
||||||
|
before_action :require_admin
|
||||||
|
before_action :find_ldap, only: [:edit, :update, :destroy]
|
||||||
|
|
||||||
|
def index
|
||||||
|
@ldap_sources = AuthSourceLdap.all
|
||||||
|
@ldap = AuthSourceLdap.new
|
||||||
|
@presets = ldap_presets
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit
|
||||||
|
@presets = ldap_presets
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@ldap = AuthSourceLdap.new(ldap_params)
|
||||||
|
if @ldap.save
|
||||||
|
flash[:notice] = l(:notice_successful_create)
|
||||||
|
redirect_to ldap_config_path
|
||||||
|
else
|
||||||
|
@ldap_sources = AuthSourceLdap.all
|
||||||
|
@presets = ldap_presets
|
||||||
|
render :index
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
if @ldap.update(ldap_params)
|
||||||
|
flash[:notice] = l(:notice_successful_update)
|
||||||
|
else
|
||||||
|
flash[:error] = @ldap.errors.full_messages.join(', ')
|
||||||
|
end
|
||||||
|
redirect_to ldap_config_path
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@ldap.destroy
|
||||||
|
flash[:notice] = l(:notice_successful_delete)
|
||||||
|
redirect_to ldap_config_path
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_connection
|
||||||
|
@ldap = AuthSourceLdap.new(ldap_params)
|
||||||
|
begin
|
||||||
|
@ldap.test_connection
|
||||||
|
render json: { success: true, message: 'Connection successful!' }
|
||||||
|
rescue => e
|
||||||
|
render json: { success: false, message: e.message }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def find_ldap
|
||||||
|
@ldap = AuthSourceLdap.find(params[:id])
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render_404
|
||||||
|
end
|
||||||
|
|
||||||
|
def ldap_params
|
||||||
|
params.require(:auth_source_ldap).permit(
|
||||||
|
:name, :host, :port, :tls, :verify_peer,
|
||||||
|
:account, :account_password, :base_dn, :filter,
|
||||||
|
:onthefly_register, :attr_login, :attr_firstname,
|
||||||
|
:attr_lastname, :attr_mail, :timeout
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def ldap_presets
|
||||||
|
{
|
||||||
|
'freeipa' => {
|
||||||
|
name: 'FreeIPA',
|
||||||
|
port: 389,
|
||||||
|
tls: false,
|
||||||
|
base_dn: 'cn=users,cn=accounts,dc=example,dc=com',
|
||||||
|
filter: '(objectClass=person)',
|
||||||
|
attr_login: 'uid',
|
||||||
|
attr_firstname: 'givenName',
|
||||||
|
attr_lastname: 'sn',
|
||||||
|
attr_mail: 'mail'
|
||||||
|
},
|
||||||
|
'active_directory' => {
|
||||||
|
name: 'Active Directory',
|
||||||
|
port: 389,
|
||||||
|
tls: false,
|
||||||
|
base_dn: 'CN=Users,DC=example,DC=com',
|
||||||
|
filter: '(&(objectClass=user)(!(objectClass=computer)))',
|
||||||
|
attr_login: 'sAMAccountName',
|
||||||
|
attr_firstname: 'givenName',
|
||||||
|
attr_lastname: 'sn',
|
||||||
|
attr_mail: 'mail'
|
||||||
|
},
|
||||||
|
'openldap' => {
|
||||||
|
name: 'OpenLDAP',
|
||||||
|
port: 389,
|
||||||
|
tls: false,
|
||||||
|
base_dn: 'ou=users,dc=example,dc=com',
|
||||||
|
filter: '(objectClass=inetOrgPerson)',
|
||||||
|
attr_login: 'uid',
|
||||||
|
attr_firstname: 'givenName',
|
||||||
|
attr_lastname: 'sn',
|
||||||
|
attr_mail: 'mail'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def ldap_config_path
|
||||||
|
{ controller: 'ldap_config', action: 'index' }
|
||||||
|
end
|
||||||
|
end
|
||||||
110
app/views/ldap_config/_ldap_form.html.erb
Normal file
110
app/views/ldap_config/_ldap_form.html.erb
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
<fieldset class="box tabular">
|
||||||
|
<legend>Connection Settings</legend>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<%= f.label :name, 'Name' %> <span class="required">*</span>
|
||||||
|
<%= f.text_field :name, size: 30, required: true %>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<%= f.label :host, 'Host' %> <span class="required">*</span>
|
||||||
|
<%= f.text_field :host, size: 30, required: true, placeholder: 'ldap.example.com or IP' %>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<%= f.label :port, 'Port' %> <span class="required">*</span>
|
||||||
|
<%= f.number_field :port, value: (@ldap.port || 389), min: 1, max: 65535, required: true %>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<%= f.label :tls, 'LDAPS (TLS)' %>
|
||||||
|
<%= f.check_box :tls %>
|
||||||
|
<em class="info">(Use port 636 for LDAPS)</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<%= f.label :verify_peer, 'Verify SSL Certificate' %>
|
||||||
|
<%= f.check_box :verify_peer %>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<%= f.label :timeout, 'Timeout (seconds)' %>
|
||||||
|
<%= f.number_field :timeout, value: (@ldap.timeout || 20), min: 1, max: 300 %>
|
||||||
|
</p>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset class="box tabular" id="domain-section" style="display: none;">
|
||||||
|
<legend>Domain Settings (Auto-Configure)</legend>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label for="domain_input">Domain</label> <span class="required">*</span>
|
||||||
|
<input type="text" id="domain_input" size="30" placeholder="finnq.com" />
|
||||||
|
<em class="info">(Enter domain to auto-configure LDAP settings)</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label for="bind_user">Bind User</label>
|
||||||
|
<input type="text" id="bind_user" size="20" value="admin" />
|
||||||
|
<em class="info">(Default: admin)</em>
|
||||||
|
</p>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset class="box tabular" id="bind-section">
|
||||||
|
<legend>Bind Credentials</legend>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<%= f.label :account, 'Bind DN' %>
|
||||||
|
<%= f.text_field :account, size: 60, placeholder: 'uid=admin,cn=users,cn=accounts,dc=example,dc=com' %>
|
||||||
|
<em class="info">(Leave empty for anonymous bind)</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<%= f.label :account_password, 'Bind Password' %>
|
||||||
|
<%= f.password_field :account_password, size: 30, autocomplete: 'off' %>
|
||||||
|
</p>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset class="box tabular" id="search-section">
|
||||||
|
<legend>Search Settings</legend>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<%= f.label :base_dn, 'Base DN' %> <span class="required">*</span>
|
||||||
|
<%= f.text_field :base_dn, size: 60, placeholder: 'cn=users,cn=accounts,dc=example,dc=com' %>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<%= f.label :filter, 'LDAP Filter' %>
|
||||||
|
<%= f.text_field :filter, size: 60, placeholder: '(objectClass=person)' %>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<%= f.label :onthefly_register, 'On-the-fly user creation' %>
|
||||||
|
<%= f.check_box :onthefly_register, checked: true %>
|
||||||
|
<em class="info">(Automatically create Redmine user on first login)</em>
|
||||||
|
</p>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset class="box tabular" id="attr-section">
|
||||||
|
<legend>Attribute Mapping</legend>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<%= f.label :attr_login, 'Login attribute' %> <span class="required">*</span>
|
||||||
|
<%= f.text_field :attr_login, size: 30, placeholder: 'uid' %>
|
||||||
|
<em class="info">(uid for LDAP, sAMAccountName for AD)</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<%= f.label :attr_firstname, 'First name attribute' %> <span class="required">*</span>
|
||||||
|
<%= f.text_field :attr_firstname, size: 30, placeholder: 'givenName' %>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<%= f.label :attr_lastname, 'Last name attribute' %> <span class="required">*</span>
|
||||||
|
<%= f.text_field :attr_lastname, size: 30, placeholder: 'sn' %>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<%= f.label :attr_mail, 'Email attribute' %> <span class="required">*</span>
|
||||||
|
<%= f.text_field :attr_mail, size: 30, placeholder: 'mail' %>
|
||||||
|
</p>
|
||||||
|
</fieldset>
|
||||||
69
app/views/ldap_config/edit.html.erb
Normal file
69
app/views/ldap_config/edit.html.erb
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<h2>Edit LDAP: <%= @ldap.name %></h2>
|
||||||
|
|
||||||
|
<%= javascript_tag do %>
|
||||||
|
var ldapPresets = <%= raw @presets.to_json %>;
|
||||||
|
|
||||||
|
function applyPreset(selectElement) {
|
||||||
|
var preset = selectElement.value;
|
||||||
|
if (preset && ldapPresets[preset]) {
|
||||||
|
var config = ldapPresets[preset];
|
||||||
|
document.getElementById('auth_source_ldap_name').value = config.name;
|
||||||
|
document.getElementById('auth_source_ldap_port').value = config.port;
|
||||||
|
document.getElementById('auth_source_ldap_tls').checked = config.tls;
|
||||||
|
document.getElementById('auth_source_ldap_base_dn').value = config.base_dn;
|
||||||
|
document.getElementById('auth_source_ldap_filter').value = config.filter;
|
||||||
|
document.getElementById('auth_source_ldap_attr_login').value = config.attr_login;
|
||||||
|
document.getElementById('auth_source_ldap_attr_firstname').value = config.attr_firstname;
|
||||||
|
document.getElementById('auth_source_ldap_attr_lastname').value = config.attr_lastname;
|
||||||
|
document.getElementById('auth_source_ldap_attr_mail').value = config.attr_mail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testLdapConnection() {
|
||||||
|
var form = document.getElementById('ldap-form');
|
||||||
|
var formData = new FormData(form);
|
||||||
|
var testBtn = document.getElementById('test-btn');
|
||||||
|
var resultDiv = document.getElementById('test-result');
|
||||||
|
|
||||||
|
testBtn.disabled = true;
|
||||||
|
testBtn.value = 'Testing...';
|
||||||
|
resultDiv.innerHTML = '';
|
||||||
|
|
||||||
|
fetch('<%= url_for(controller: 'ldap_config', action: 'test_connection') %>', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
testBtn.disabled = false;
|
||||||
|
testBtn.value = 'Test Connection';
|
||||||
|
if (data.success) {
|
||||||
|
resultDiv.innerHTML = '<span style="color: green; font-weight: bold;">✔ ' + data.message + '</span>';
|
||||||
|
} else {
|
||||||
|
resultDiv.innerHTML = '<span style="color: red; font-weight: bold;">✘ ' + data.message + '</span>';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
testBtn.disabled = false;
|
||||||
|
testBtn.value = 'Test Connection';
|
||||||
|
resultDiv.innerHTML = '<span style="color: red;">Error: ' + error + '</span>';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= form_for @ldap, url: ldap_config_update_path(@ldap), method: :patch,
|
||||||
|
html: { id: 'ldap-form', class: 'tabular' } do |f| %>
|
||||||
|
|
||||||
|
<%= render partial: 'ldap_form', locals: { f: f } %>
|
||||||
|
|
||||||
|
<p style="margin-top: 15px;">
|
||||||
|
<%= f.submit 'Save', class: 'button-positive' %>
|
||||||
|
<input type="button" id="test-btn" value="Test Connection" onclick="testLdapConnection()" class="button" />
|
||||||
|
<%= link_to 'Cancel', { controller: 'ldap_config', action: 'index' }, class: 'button' %>
|
||||||
|
<span id="test-result" style="margin-left: 10px;"></span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<% end %>
|
||||||
192
app/views/ldap_config/index.html.erb
Normal file
192
app/views/ldap_config/index.html.erb
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
<h2>LDAP Configuration</h2>
|
||||||
|
|
||||||
|
<%= javascript_tag do %>
|
||||||
|
var currentPreset = null;
|
||||||
|
|
||||||
|
function domainToDC(domain) {
|
||||||
|
if (!domain) return '';
|
||||||
|
return domain.split('.').map(function(part) {
|
||||||
|
return 'dc=' + part;
|
||||||
|
}).join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFromDomain() {
|
||||||
|
var domain = document.getElementById('domain_input').value;
|
||||||
|
if (!currentPreset || !domain) return;
|
||||||
|
|
||||||
|
var dc = domainToDC(domain);
|
||||||
|
var bindUser = document.getElementById('bind_user').value || 'admin';
|
||||||
|
var baseDN, bindDN, filter, attrLogin;
|
||||||
|
|
||||||
|
if (currentPreset === 'freeipa') {
|
||||||
|
baseDN = 'cn=users,cn=accounts,' + dc;
|
||||||
|
bindDN = 'uid=' + bindUser + ',cn=users,cn=accounts,' + dc;
|
||||||
|
filter = '(objectClass=person)';
|
||||||
|
attrLogin = 'uid';
|
||||||
|
} else if (currentPreset === 'active_directory') {
|
||||||
|
baseDN = 'CN=Users,' + dc.toUpperCase().replace(/dc=/g, 'DC=');
|
||||||
|
bindDN = bindUser + '@' + domain;
|
||||||
|
filter = '(&(objectClass=user)(!(objectClass=computer)))';
|
||||||
|
attrLogin = 'sAMAccountName';
|
||||||
|
} else if (currentPreset === 'openldap') {
|
||||||
|
baseDN = 'ou=users,' + dc;
|
||||||
|
bindDN = 'cn=' + bindUser + ',' + dc;
|
||||||
|
filter = '(objectClass=inetOrgPerson)';
|
||||||
|
attrLogin = 'uid';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('auth_source_ldap_base_dn').value = baseDN;
|
||||||
|
document.getElementById('auth_source_ldap_account').value = bindDN;
|
||||||
|
document.getElementById('auth_source_ldap_filter').value = filter;
|
||||||
|
document.getElementById('auth_source_ldap_attr_login').value = attrLogin;
|
||||||
|
document.getElementById('auth_source_ldap_attr_firstname').value = 'givenName';
|
||||||
|
document.getElementById('auth_source_ldap_attr_lastname').value = 'sn';
|
||||||
|
document.getElementById('auth_source_ldap_attr_mail').value = 'mail';
|
||||||
|
document.getElementById('auth_source_ldap_onthefly_register').checked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFieldsReadonly(readonly) {
|
||||||
|
var fields = ['auth_source_ldap_base_dn', 'auth_source_ldap_account', 'auth_source_ldap_filter',
|
||||||
|
'auth_source_ldap_attr_login', 'auth_source_ldap_attr_firstname',
|
||||||
|
'auth_source_ldap_attr_lastname', 'auth_source_ldap_attr_mail'];
|
||||||
|
fields.forEach(function(id) {
|
||||||
|
var el = document.getElementById(id);
|
||||||
|
if (el) {
|
||||||
|
el.readOnly = readonly;
|
||||||
|
el.style.backgroundColor = readonly ? '#f0f0f0' : '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyPresetMode(preset) {
|
||||||
|
currentPreset = preset;
|
||||||
|
var domainSection = document.getElementById('domain-section');
|
||||||
|
|
||||||
|
if (preset && (preset === 'freeipa' || preset === 'active_directory' || preset === 'openldap')) {
|
||||||
|
domainSection.style.display = 'block';
|
||||||
|
var names = { 'freeipa': 'FreeIPA', 'active_directory': 'Active Directory', 'openldap': 'OpenLDAP' };
|
||||||
|
document.getElementById('auth_source_ldap_name').value = names[preset];
|
||||||
|
setFieldsReadonly(true);
|
||||||
|
document.getElementById('domain_input').value = '';
|
||||||
|
document.getElementById('bind_user').value = 'admin';
|
||||||
|
} else {
|
||||||
|
domainSection.style.display = 'none';
|
||||||
|
setFieldsReadonly(false);
|
||||||
|
document.getElementById('auth_source_ldap_name').value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testLdapConnection() {
|
||||||
|
var form = document.getElementById('ldap-form');
|
||||||
|
var formData = new FormData(form);
|
||||||
|
var testBtn = document.getElementById('test-btn');
|
||||||
|
var resultDiv = document.getElementById('test-result');
|
||||||
|
|
||||||
|
testBtn.disabled = true;
|
||||||
|
testBtn.value = 'Testing...';
|
||||||
|
resultDiv.innerHTML = '';
|
||||||
|
|
||||||
|
fetch('<%= url_for(controller: 'ldap_config', action: 'test_connection') %>', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(function(response) { return response.json(); })
|
||||||
|
.then(function(data) {
|
||||||
|
testBtn.disabled = false;
|
||||||
|
testBtn.value = 'Test Connection';
|
||||||
|
if (data.success) {
|
||||||
|
resultDiv.innerHTML = '<span style="color: green; font-weight: bold;">OK: ' + data.message + '</span>';
|
||||||
|
} else {
|
||||||
|
resultDiv.innerHTML = '<span style="color: red; font-weight: bold;">FAIL: ' + data.message + '</span>';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function(error) {
|
||||||
|
testBtn.disabled = false;
|
||||||
|
testBtn.value = 'Test Connection';
|
||||||
|
resultDiv.innerHTML = '<span style="color: red;">Error: ' + error + '</span>';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#domain_input').on('keyup change', function() {
|
||||||
|
updateFromDomain();
|
||||||
|
});
|
||||||
|
$('#bind_user').on('keyup change', function() {
|
||||||
|
updateFromDomain();
|
||||||
|
});
|
||||||
|
$('#preset').on('change', function() {
|
||||||
|
applyPresetMode(this.value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% if @ldap_sources.any? %>
|
||||||
|
<h3>Existing LDAP Sources</h3>
|
||||||
|
<table class="list">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Host</th>
|
||||||
|
<th>Port</th>
|
||||||
|
<th>TLS</th>
|
||||||
|
<th>Base DN</th>
|
||||||
|
<th>On-the-fly</th>
|
||||||
|
<th>Users</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% @ldap_sources.each do |ldap| %>
|
||||||
|
<tr>
|
||||||
|
<td><strong><%= ldap.name %></strong></td>
|
||||||
|
<td><%= ldap.host %></td>
|
||||||
|
<td><%= ldap.port %></td>
|
||||||
|
<td><%= ldap.tls ? 'Yes' : 'No' %></td>
|
||||||
|
<td><code style="font-size: 11px;"><%= truncate(ldap.base_dn, length: 40) %></code></td>
|
||||||
|
<td><%= ldap.onthefly_register ? 'Yes' : 'No' %></td>
|
||||||
|
<td><%= ldap.users.count %></td>
|
||||||
|
<td>
|
||||||
|
<%= link_to 'Edit', ldap_config_edit_path(ldap), class: 'icon icon-edit' %>
|
||||||
|
<%= link_to 'Delete', ldap_config_delete_path(ldap),
|
||||||
|
method: :delete,
|
||||||
|
data: { confirm: 'Are you sure?' },
|
||||||
|
class: 'icon icon-del' %>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<hr />
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<h3>Add New LDAP Source</h3>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<p>
|
||||||
|
<label for="preset">Quick Setup (Preset):</label>
|
||||||
|
<select id="preset">
|
||||||
|
<option value="">-- Manual Configuration --</option>
|
||||||
|
<option value="freeipa">FreeIPA</option>
|
||||||
|
<option value="active_directory">Active Directory</option>
|
||||||
|
<option value="openldap">OpenLDAP</option>
|
||||||
|
</select>
|
||||||
|
<em class="info">(Select preset for simplified setup)</em>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= form_for @ldap, url: { controller: 'ldap_config', action: 'create' },
|
||||||
|
html: { id: 'ldap-form', class: 'tabular' } do |f| %>
|
||||||
|
|
||||||
|
<%= render partial: 'ldap_form', locals: { f: f } %>
|
||||||
|
|
||||||
|
<p style="margin-top: 15px;">
|
||||||
|
<%= f.submit 'Create', class: 'button-positive' %>
|
||||||
|
<input type="button" id="test-btn" value="Test Connection" onclick="testLdapConnection()" class="button" />
|
||||||
|
<span id="test-result" style="margin-left: 10px;"></span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<% end %>
|
||||||
8
config/routes.rb
Normal file
8
config/routes.rb
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
Rails.application.routes.draw do
|
||||||
|
get 'ldap_config', to: 'ldap_config#index'
|
||||||
|
get 'ldap_config/:id/edit', to: 'ldap_config#edit', as: 'ldap_config_edit'
|
||||||
|
post 'ldap_config/create', to: 'ldap_config#create'
|
||||||
|
patch 'ldap_config/:id', to: 'ldap_config#update', as: 'ldap_config_update'
|
||||||
|
post 'ldap_config/test', to: 'ldap_config#test_connection'
|
||||||
|
delete 'ldap_config/:id', to: 'ldap_config#destroy', as: 'ldap_config_delete'
|
||||||
|
end
|
||||||
15
init.rb
Normal file
15
init.rb
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
Redmine::Plugin.register :ldap_config do
|
||||||
|
name 'LDAP Config'
|
||||||
|
author 'System Admin'
|
||||||
|
description 'Web-based LDAP configuration plugin for Redmine'
|
||||||
|
version '1.0.0'
|
||||||
|
url 'https://github.com/example/ldap_config'
|
||||||
|
author_url 'https://example.com'
|
||||||
|
|
||||||
|
# Admin menu
|
||||||
|
menu :admin_menu, :ldap_config,
|
||||||
|
{ controller: 'ldap_config', action: 'index' },
|
||||||
|
caption: 'LDAP Config',
|
||||||
|
html: { class: 'icon icon-server-authentication' },
|
||||||
|
after: :ldap_authentication
|
||||||
|
end
|
||||||
Loading…
Reference in New Issue
Block a user