Skip to content

Commit

Permalink
Merge pull request #16377 from opf/feature/saml-ui
Browse files Browse the repository at this point in the history
User interface for SAML configuration
  • Loading branch information
machisuji authored Sep 26, 2024
2 parents 1b3019e + 7b58928 commit a73e90b
Show file tree
Hide file tree
Showing 109 changed files with 5,387 additions and 167 deletions.
4 changes: 4 additions & 0 deletions app/components/op_primer/border_box_table_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ See COPYRIGHT and LICENSE files for more details.
end
end

if rows.empty?
component.with_row(scheme: :default) { render_blank_slate }
end

rows.each do |row|
component.with_row(scheme: :default) do
render(row_class.new(row:, table: self))
Expand Down
20 changes: 20 additions & 0 deletions app/components/op_primer/border_box_table_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,25 @@ def has_actions?
def sortable?
false
end

def render_blank_slate
render(Primer::Beta::Blankslate.new(border: false)) do |component|
component.with_visual_icon(icon: blank_icon, size: :medium) if blank_icon
component.with_heading(tag: :h2) { blank_title }
component.with_description { blank_description }
end
end

def blank_title
I18n.t(:label_nothing_display)
end

def blank_description
I18n.t(:no_results_title_text)
end

def blank_icon
nil
end
end
end
21 changes: 21 additions & 0 deletions app/components/op_primer/copy_to_clipboard_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<%=
flex_layout(align_items: :center, **@system_arguments) do |flex|
if @scheme == :link
flex.with_column(classes: "ellipsis") do
render(Primer::Beta::Link.new(
id: @id,
href: value,
title: value,
target: :_blank
)) { value }
end
else
flex.with_column(classes: "ellipsis") do
render(Primer::Beta::Text.new(title: value)) { value }
end
end
flex.with_column(ml: 1) do
render(Primer::Beta::ClipboardCopy.new(value:, "aria-label": t(:button_copy_to_clipboard)))
end
end
%>
43 changes: 43 additions & 0 deletions app/components/op_primer/copy_to_clipboard_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2024 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

module OpPrimer
class CopyToClipboardComponent < ApplicationComponent
include OpPrimer::ComponentHelpers

alias_method :value, :model

def initialize(value = nil, scheme: :value, **system_arguments)
super(value)

@scheme = scheme
@system_arguments = system_arguments
@id = SecureRandom.hex(8)
end
end
end
5 changes: 2 additions & 3 deletions app/components/op_turbo/stream_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<turbo-stream action="<%=@action%>" target="<%=@target%>">
<%= content_tag("turbo-stream", action: @action, target: @target, **@turbo_stream_args) do %>
<% if @template %>
<template>
<%= @template %>
</template>
<% end %>
</turbo-stream>

<% end %>
3 changes: 2 additions & 1 deletion app/components/op_turbo/stream_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@

module OpTurbo
class StreamComponent < ApplicationComponent
def initialize(template:, action:, target:)
def initialize(action:, target:, template: nil, **turbo_stream_args)
super()

@turbo_stream_args = turbo_stream_args
@template = template
@action = action
@target = target
Expand Down
7 changes: 7 additions & 0 deletions app/controllers/concerns/op_turbo/component_stream.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def respond_to_with_turbo_streams(status: turbo_status, &format_block)
yield(format) if format_block
end
end

alias_method :respond_with_turbo_streams, :respond_to_with_turbo_streams

def update_via_turbo_stream(component:, status: :ok)
Expand Down Expand Up @@ -82,6 +83,12 @@ def update_flash_message_via_turbo_stream(message:, component: OpPrimer::FlashCo
turbo_streams << instance.render_as_turbo_stream(view_context:, action: :flash)
end

def scroll_into_view_via_turbo_stream(target, behavior: :auto, block: :start)
turbo_streams << OpTurbo::StreamComponent
.new(action: :scroll_into_view, target:, behavior:, block:)
.render_in(view_context)
end

def turbo_streams
@turbo_streams ||= []
end
Expand Down
2 changes: 1 addition & 1 deletion app/services/authorization/enterprise_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ class Authorization::EnterpriseService
grid_widget_wp_graph
ldap_groups
one_drive_sharepoint_file_storage
openid_providers
placeholder_users
project_list_sharing
readonly_work_packages
sso_auth_providers
team_planner_view
two_factor_authentication
virus_scanning
Expand Down
1 change: 1 addition & 0 deletions app/services/service_result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class ServiceResult
attr_accessor :success,
:result,
:errors,
:message,
:dependent_results

attr_writer :state
Expand Down
4 changes: 2 additions & 2 deletions app/validators/url_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ def validate_each(record, attribute, value)
end

def parse(value)
url = URI.parse(value)
rescue StandardError => e
URI.parse(value.to_s.strip)
rescue StandardError
nil
end

Expand Down
4 changes: 4 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1450,6 +1450,7 @@ en:
button_expand_all: "Expand all"
button_favorite: "Add to favorites"
button_filter: "Filter"
button_finish_setup: "Finish setup"
button_generate: "Generate"
button_list: "List"
button_lock: "Lock"
Expand Down Expand Up @@ -2118,6 +2119,7 @@ en:
label_calendars_and_dates: "Calendars and dates"
label_calendar_show: "Show Calendar"
label_category: "Category"
label_completed: Completed
label_consent_settings: "User Consent"
label_wiki_menu_item: Wiki menu item
label_select_main_menu_item: Select new main menu item
Expand Down Expand Up @@ -2277,6 +2279,7 @@ en:
label_inactive: "Inactive"
label_incoming_emails: "Incoming emails"
label_includes: "includes"
label_incomplete: Incomplete
label_include_sub_projects: Include sub-projects
label_index_by_date: "Index by date"
label_index_by_title: "Index by title"
Expand Down Expand Up @@ -2390,6 +2393,7 @@ en:
label_no_parent_page: "No parent page"
label_nothing_display: "Nothing to display"
label_nobody: "nobody"
label_not_configured: "Not configured"
label_not_found: "not found"
label_none: "none"
label_none_parentheses: "(none)"
Expand Down
1 change: 1 addition & 0 deletions frontend/src/global_styles/openproject.sass
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
@import "../../../modules/meeting/app/components/_index.sass"
@import "../../../modules/overviews/app/components/_index.sass"
@import "../../../modules/storages/app/components/_index.sass"
@import "../../../modules/auth_saml/app/components/_index.sass"

// Component specific Styles
@import "../../../app/components/_index.sass"
3 changes: 3 additions & 0 deletions frontend/src/global_styles/primer/_overrides.sass
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ action-menu
@media screen and (min-width: $breakpoint-sm)
scroll-behavior: smooth

ul.SegmentedControl
margin-left: 0

/* Remove margin-left: 2rem from Breadcrumbs */
#breadcrumb,
page-header,
Expand Down
30 changes: 30 additions & 0 deletions frontend/src/stimulus/controllers/show-when-checked.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ApplicationController } from 'stimulus-use';

export default class OpShowWhenCheckedController extends ApplicationController {
static targets = ['cause', 'effect'];

static values = {
reversed: Boolean,
};

declare reversedValue:boolean;

declare readonly hasReversedValue:boolean;

declare readonly effectTargets:HTMLInputElement[];

causeTargetConnected(target:HTMLElement) {
target.addEventListener('change', this.toggleDisabled.bind(this));
}

causeTargetDisconnected(target:HTMLElement) {
target.removeEventListener('change', this.toggleDisabled.bind(this));
}

private toggleDisabled(evt:InputEvent):void {
const checked = (evt.target as HTMLInputElement).checked;
this.effectTargets.forEach((el) => {
el.hidden = (this.hasReversedValue && this.reversedValue) ? checked : !checked;
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ApplicationController } from 'stimulus-use';

export default class OpShowWhenValueSelectedController extends ApplicationController {
static targets = ['cause', 'effect'];

declare readonly effectTargets:HTMLInputElement[];

causeTargetConnected(target:HTMLElement) {
target.addEventListener('change', this.toggleDisabled.bind(this));
}

causeTargetDisconnected(target:HTMLElement) {
target.removeEventListener('change', this.toggleDisabled.bind(this));
}

private toggleDisabled(evt:InputEvent):void {
const value = (evt.target as HTMLInputElement).value;
this.effectTargets.forEach((el) => {
el.hidden = !(el.dataset.value === value);
});
}
}
4 changes: 4 additions & 0 deletions frontend/src/stimulus/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import RefreshOnFormChangesController from './controllers/refresh-on-form-change
import AsyncDialogController from './controllers/async-dialog.controller';
import PollForChangesController from './controllers/poll-for-changes.controller';
import TableHighlightingController from './controllers/table-highlighting.controller';
import OpShowWhenCheckedController from './controllers/show-when-checked.controller';
import OpShowWhenValueSelectedController from './controllers/show-when-value-selected.controller';

declare global {
interface Window {
Expand All @@ -26,7 +28,9 @@ instance.handleError = (error, message, detail) => {
instance.register('application', OpApplicationController);
instance.register('menus--main', MainMenuController);

instance.register('show-when-checked', OpShowWhenCheckedController);
instance.register('disable-when-checked', OpDisableWhenCheckedController);
instance.register('show-when-value-selected', OpShowWhenValueSelectedController);
instance.register('print', PrintController);
instance.register('refresh-on-form-changes', RefreshOnFormChangesController);
instance.register('async-dialog', AsyncDialogController);
Expand Down
5 changes: 5 additions & 0 deletions lib/open_project/static/links.rb
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,11 @@ def static_links
href: "https://www.openproject.org/docs/system-admin-guide/manage-work-packages/work-package-status/#create-a-new-work-package-status"
}
},
sysadmin_docs: {
saml: {
href: "https://www.openproject.org/docs/system-admin-guide/authentication/saml/"
}
},
storage_docs: {
setup: {
href: "https://www.openproject.org/docs/system-admin-guide/integrations/storage/"
Expand Down
6 changes: 4 additions & 2 deletions lib_static/redmine/i18n.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,15 @@ def format_date(date)
#
# @param i18n_key [String] The I18n key to translate.
# @param links [Hash] Link names mapped to URLs.
def link_translate(i18n_key, links: {}, locale: ::I18n.locale)
# @param target [String] optional HTML target attribute for the links.
def link_translate(i18n_key, links: {}, locale: ::I18n.locale, target: nil)
translation = ::I18n.t(i18n_key.to_s, locale:)
result = translation.scan(link_regex).inject(translation) do |t, matches|
link, text, key = matches
href = String(links[key.to_sym])
link_tag = content_tag(:a, text, href:, target:)

t.sub(link, "<a href=\"#{href}\">#{text}</a>")
t.sub(link, link_tag)
end

result.html_safe
Expand Down
16 changes: 16 additions & 0 deletions lookbook/previews/op_primer/copy_to_clipboard_component_preview.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

module OpPrimer
# @logical_path OpenProject/Primer
class CopyToClipboardComponentPreview < Lookbook::Preview
# @param value text
def default(value: "Copy me!")
render(OpPrimer::CopyToClipboardComponent.new(value))
end

# @param url text
def as_link(url: "http://example.org")
render(OpPrimer::CopyToClipboardComponent.new(url, scheme: :link))
end
end
end
2 changes: 1 addition & 1 deletion modules/auth_plugins/lib/omni_auth/flexible_strategy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def match_provider!
return false unless providers

@provider = providers.find do |p|
(current_path =~ /#{path_for_provider(p.to_hash[:name])}/) == 0
current_path.match?(/#{path_for_provider(p.to_hash[:name])}(\/|\s*$)/)
end

if @provider
Expand Down
1 change: 1 addition & 0 deletions modules/auth_saml/app/components/_index.sass
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "saml/providers/view_component"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<%= render(Primer::Beta::Heading.new(tag: :h4)) { I18n.t('saml.info.title') } %>

<%= render(Primer::Beta::Text.new) { I18n.t('saml.info.description') }%>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Saml
module Providers
class InfoComponent < ApplicationComponent
alias_method :provider, :model
end
end
end
Loading

0 comments on commit a73e90b

Please sign in to comment.