Compare commits

..

23 Commits

Author SHA1 Message Date
Felix Han
fd0f6b8f47 fix(box-selector): removed unused media query 2021-10-05 23:07:59 +13:00
Felix Han
c28f315de1 fix(box-selector): fixed icon position in different view port. 2021-10-04 14:02:08 +13:00
Felix Han
f67230db3f fix(s3): using constant 2021-10-03 15:17:35 +03:00
Felix Han
5581613d38 fix(k8s): using constant in be feature 2021-10-03 15:17:35 +03:00
Felix Han
ad1756f08e fix(log): added limited feature property at page level 2021-10-03 15:17:35 +03:00
Felix Han
e825fcb608 fix(log): using constant in activity log title bar 2021-10-03 15:17:34 +03:00
Felix Han
5138920d2b fix(log): using constant in auth log title bar 2021-10-03 15:17:34 +03:00
Felix Han
0b8b3fc73f fix(oauth): removed border radius 2021-10-03 15:17:34 +03:00
Chaim Lev-Ari
8fc18b09ce feat(endpoints): show BE added value in access manager (#5783)
* feat(endpoints): show BE added value in selector

* fix(endpoints): give more space to field
2021-10-03 15:17:34 +03:00
Chaim Lev-Ari
24a40dc787 feat(oauth): highlight BE features [EE-1665] (#5787)
* refactor(oauth): backport ee changes

* feat(oauth): limit options to BE

* feat(oauth): limit oauth fields to BE

* refactor(oauth): remove unused membership toggle
2021-10-03 15:17:34 +03:00
fhanportainer
bfcaf3c02a feat(s3): backport S3 backup component and added BE highlight value (#5792) 2021-10-03 15:17:34 +03:00
Chaim Lev-Ari
14cc745128 chore(deps): upgrade lodash 2021-10-03 15:17:34 +03:00
Chaim Lev-Ari
318d8d0006 feat(settings/auth): highlight be added value in ad (#5736)
* feat(settings): limit AD to BE

* feat(settings/ad): highlight be added value
2021-10-03 15:17:34 +03:00
Chaim Lev-Ari
6b68e294c5 feat(settings/ldap): highlight be added value in openldap (#5735) 2021-10-03 15:17:34 +03:00
Chaim Lev-Ari
82f6a8a46f feat(ldap): highlight BE feature (#5706) 2021-10-03 15:17:34 +03:00
Chaim Lev-Ari
ab9e8b9ea0 feat(feature-flags): enhance framework (#5707) 2021-10-03 15:17:34 +03:00
fhanportainer
28c28182ad feat(registry): highlight BE added value in Registry view [EE-1672] (#5695)
* feat(app): introduce feature flags framework (#5622)

* feat(featureflags): expose is feature limited to BE

* feat(registry): highlight BE added value [EE-1672]

* feat(registry): highlight BE added value [EE-1672]

Co-authored-by: Chaim Lev-Ari <chiptus@users.noreply.github.com>
Co-authored-by: Chaim Lev-Ari <chiptus@gmail.com>
Co-authored-by: Sven Dowideit <SvenDowideit@home.org.au>
2021-10-03 15:17:34 +03:00
fhanportainer
bf491cab67 feat(activity-log): added activity log (#5760)
* feat(datatable): backport datatable children components to CE.

* feat(sidebar): added auth logs to the side bar

* feat(auth-log): added auth log component

* feat(auth-log): added BE feature only to datatable title bar

* feat(activity-log): added activity log

Co-authored-by: Sven Dowideit <SvenDowideit@home.org.au>
2021-10-03 15:17:34 +03:00
Chaim Lev-Ari
c14084d5b0 feat(roles): highlight BE added value (#5673)
* refactor(settings): backport auth views (#5705)

* feat(roles): highlight BE added value

* feat(roles): show default for standard user

* fix(feature-flags): replace feature id

* feat(roles): show no users when limited to be
2021-10-03 15:17:34 +03:00
Chaim Lev-Ari
204fad6535 refactor(settings): backport auth views (#5705) 2021-10-03 15:17:34 +03:00
Chaim Lev-Ari
ea12b87ccb feat(k8s): highlight BE added value [EE-1673] (#5674) 2021-10-03 15:17:34 +03:00
Chaim Lev-Ari
5bd5b111f7 feat(featureflags): expose is feature limited to BE 2021-10-03 15:17:34 +03:00
Chaim Lev-Ari
c17e9fd028 feat(app): introduce feature flags framework (#5622) 2021-10-03 15:17:34 +03:00
48 changed files with 165 additions and 685 deletions

View File

@@ -93,7 +93,7 @@ func (manager *ComposeStackManager) fetchEndpointProxy(endpoint *portainer.Endpo
return "", nil, err
}
return fmt.Sprintf("tcp://127.0.0.1:%d", proxy.Port), proxy, nil
return fmt.Sprintf("http://127.0.0.1:%d", proxy.Port), proxy, nil
}
func createEnvFile(stack *portainer.Stack) (string, error) {

View File

@@ -18,7 +18,6 @@ import (
"github.com/portainer/portainer/api/http/handler/file"
"github.com/portainer/portainer/api/http/handler/helm"
"github.com/portainer/portainer/api/http/handler/kubernetes"
"github.com/portainer/portainer/api/http/handler/ldap"
"github.com/portainer/portainer/api/http/handler/motd"
"github.com/portainer/portainer/api/http/handler/registries"
"github.com/portainer/portainer/api/http/handler/resourcecontrols"
@@ -54,7 +53,6 @@ type Handler struct {
HelmTemplatesHandler *helm.Handler
KubernetesHandler *kubernetes.Handler
FileHandler *file.Handler
LDAPHandler *ldap.Handler
MOTDHandler *motd.Handler
RegistryHandler *registries.Handler
ResourceControlHandler *resourcecontrols.Handler
@@ -191,8 +189,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
default:
http.StripPrefix("/api", h.EndpointHandler).ServeHTTP(w, r)
}
case strings.HasPrefix(r.URL.Path, "/api/ldap"):
http.StripPrefix("/api", h.LDAPHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/motd"):
http.StripPrefix("/api", h.MOTDHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/registries"):

View File

@@ -1,53 +0,0 @@
package ldap
import (
"net/http"
"github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/filesystem"
"github.com/portainer/portainer/api/http/security"
)
// Handler is the HTTP handler used to handle LDAP search Operations
type Handler struct {
*mux.Router
DataStore portainer.DataStore
FileService portainer.FileService
LDAPService portainer.LDAPService
}
// NewHandler returns a new Handler
func NewHandler(bouncer *security.RequestBouncer) *Handler {
h := &Handler{
Router: mux.NewRouter(),
}
h.Handle("/ldap/check",
bouncer.AdminAccess(httperror.LoggerHandler(h.ldapCheck))).Methods(http.MethodPost)
return h
}
func (handler *Handler) prefillSettings(ldapSettings *portainer.LDAPSettings) error {
if !ldapSettings.AnonymousMode && ldapSettings.Password == "" {
settings, err := handler.DataStore.Settings().Settings()
if err != nil {
return err
}
ldapSettings.Password = settings.LDAPSettings.Password
}
if (ldapSettings.TLSConfig.TLS || ldapSettings.StartTLS) && !ldapSettings.TLSConfig.TLSSkipVerify {
caCertPath, err := handler.FileService.GetPathForTLSFile(filesystem.LDAPStorePath, portainer.TLSFileCA)
if err != nil {
return err
}
ldapSettings.TLSConfig.TLSCACertPath = caCertPath
}
return nil
}

View File

@@ -5,7 +5,7 @@ import (
"github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/security"
)
@@ -35,6 +35,8 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
bouncer.AdminAccess(httperror.LoggerHandler(h.settingsUpdate))).Methods(http.MethodPut)
h.Handle("/settings/public",
bouncer.PublicAccess(httperror.LoggerHandler(h.settingsPublic))).Methods(http.MethodGet)
h.Handle("/settings/authentication/checkLDAP",
bouncer.AdminAccess(httperror.LoggerHandler(h.settingsLDAPCheck))).Methods(http.MethodPut)
return h
}

View File

@@ -1,4 +1,4 @@
package ldap
package settings
import (
"net/http"
@@ -7,43 +7,42 @@ import (
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/filesystem"
)
type checkPayload struct {
type settingsLDAPCheckPayload struct {
LDAPSettings portainer.LDAPSettings
}
func (payload *checkPayload) Validate(r *http.Request) error {
func (payload *settingsLDAPCheckPayload) Validate(r *http.Request) error {
return nil
}
// @id LDAPCheck
// @id SettingsLDAPCheck
// @summary Test LDAP connectivity
// @description Test LDAP connectivity using LDAP details
// @description **Access policy**: administrator
// @tags ldap
// @tags settings
// @security jwt
// @accept json
// @param body body checkPayload true "details"
// @param body body settingsLDAPCheckPayload true "details"
// @success 204 "Success"
// @failure 400 "Invalid request"
// @failure 500 "Server error"
// @router /ldap/check [post]
func (handler *Handler) ldapCheck(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
var payload checkPayload
// @router /settings/ldap/check [put]
func (handler *Handler) settingsLDAPCheck(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
var payload settingsLDAPCheckPayload
err := request.DecodeAndValidateJSONPayload(r, &payload)
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
}
settings := &payload.LDAPSettings
err = handler.prefillSettings(settings)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to fetch default settings", err}
if (payload.LDAPSettings.TLSConfig.TLS || payload.LDAPSettings.StartTLS) && !payload.LDAPSettings.TLSConfig.TLSSkipVerify {
caCertPath, _ := handler.FileService.GetPathForTLSFile(filesystem.LDAPStorePath, portainer.TLSFileCA)
payload.LDAPSettings.TLSConfig.TLSCACertPath = caCertPath
}
err = handler.LDAPService.TestConnectivity(settings)
err = handler.LDAPService.TestConnectivity(&payload.LDAPSettings)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to connect to LDAP server", err}
}

View File

@@ -10,21 +10,13 @@ import (
"github.com/portainer/portainer/api/http/security"
)
// @summary Execute a websocket on kubectl shell pod
// @description The request will be upgraded to the websocket protocol. The request will proxy input from the client to the pod via long-lived websocket connection.
// @description **Access policy**: authenticated
// @security jwt
// @tags websocket
// @accept json
// @produce json
// @param endpointId query int true "environment(endpoint) ID of the environment(endpoint) where the resource is located"
// @param token query string true "JWT token used for authentication against this environment(endpoint)"
// @success 200
// @failure 400
// @failure 403
// @failure 404
// @failure 500
// @router /websocket/kubernetes-shell [get]
// websocketShellPodExec handles GET requests on /websocket/pod?token=<token>&endpointId=<endpointID>
// The request will be upgraded to the websocket protocol.
// Authentication and access is controlled via the mandatory token query parameter.
// The request will proxy input from the client to the pod via long-lived websocket connection.
// The following query parameters are mandatory:
// * token: JWT token used for authentication against this environment(endpoint)
// * endpointId: environment(endpoint) ID of the environment(endpoint) where the resource is located
func (handler *Handler) websocketShellPodExec(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
endpointID, err := request.RetrieveNumericQueryParameter(r, "endpointId", false)
if err != nil {

View File

@@ -29,7 +29,6 @@ import (
"github.com/portainer/portainer/api/http/handler/file"
"github.com/portainer/portainer/api/http/handler/helm"
kubehandler "github.com/portainer/portainer/api/http/handler/kubernetes"
"github.com/portainer/portainer/api/http/handler/ldap"
"github.com/portainer/portainer/api/http/handler/motd"
"github.com/portainer/portainer/api/http/handler/registries"
"github.com/portainer/portainer/api/http/handler/resourcecontrols"
@@ -176,11 +175,6 @@ func (server *Server) Start() error {
var helmTemplatesHandler = helm.NewTemplateHandler(requestBouncer, server.HelmPackageManager)
var ldapHandler = ldap.NewHandler(requestBouncer)
ldapHandler.DataStore = server.DataStore
ldapHandler.FileService = server.FileService
ldapHandler.LDAPService = server.LDAPService
var motdHandler = motd.NewHandler(requestBouncer)
var registryHandler = registries.NewHandler(requestBouncer)
@@ -261,7 +255,6 @@ func (server *Server) Start() error {
EndpointEdgeHandler: endpointEdgeHandler,
EndpointProxyHandler: endpointProxyHandler,
FileHandler: fileHandler,
LDAPHandler: ldapHandler,
HelmTemplatesHandler: helmTemplatesHandler,
KubernetesHandler: kubernetesHandler,
MOTDHandler: motdHandler,

View File

@@ -1,11 +1,11 @@
package ldap
import (
"errors"
"fmt"
"strings"
ldap "github.com/go-ldap/ldap/v3"
"github.com/pkg/errors"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/crypto"
httperrors "github.com/portainer/portainer/api/http/errors"
@@ -20,28 +20,55 @@ var (
// Service represents a service used to authenticate users against a LDAP/AD.
type Service struct{}
func createConnection(settings *portainer.LDAPSettings) (*ldap.Conn, error) {
conn, err := createConnectionForURL(settings.URL, settings)
if err != nil {
return nil, errors.Wrap(err, "failed creating LDAP connection")
func searchUser(username string, conn *ldap.Conn, settings []portainer.LDAPSearchSettings) (string, error) {
var userDN string
found := false
usernameEscaped := ldap.EscapeFilter(username)
for _, searchSettings := range settings {
searchRequest := ldap.NewSearchRequest(
searchSettings.BaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&%s(%s=%s))", searchSettings.Filter, searchSettings.UserNameAttribute, usernameEscaped),
[]string{"dn"},
nil,
)
// Deliberately skip errors on the search request so that we can jump to other search settings
// if any issue arise with the current one.
sr, err := conn.Search(searchRequest)
if err != nil {
continue
}
if len(sr.Entries) == 1 {
found = true
userDN = sr.Entries[0].DN
break
}
}
return conn, nil
if !found {
return "", errUserNotFound
}
return userDN, nil
}
func createConnectionForURL(url string, settings *portainer.LDAPSettings) (*ldap.Conn, error) {
func createConnection(settings *portainer.LDAPSettings) (*ldap.Conn, error) {
if settings.TLSConfig.TLS || settings.StartTLS {
config, err := crypto.CreateTLSConfigurationFromDisk(settings.TLSConfig.TLSCACertPath, settings.TLSConfig.TLSCertPath, settings.TLSConfig.TLSKeyPath, settings.TLSConfig.TLSSkipVerify)
if err != nil {
return nil, err
}
config.ServerName = strings.Split(url, ":")[0]
config.ServerName = strings.Split(settings.URL, ":")[0]
if settings.TLSConfig.TLS {
return ldap.DialTLS("tcp", url, config)
return ldap.DialTLS("tcp", settings.URL, config)
}
conn, err := ldap.Dial("tcp", url)
conn, err := ldap.Dial("tcp", settings.URL)
if err != nil {
return nil, err
}
@@ -54,7 +81,7 @@ func createConnectionForURL(url string, settings *portainer.LDAPSettings) (*ldap
return conn, nil
}
return ldap.Dial("tcp", url)
return ldap.Dial("tcp", settings.URL)
}
// AuthenticateUser is used to authenticate a user against a LDAP/AD.
@@ -106,157 +133,13 @@ func (*Service) GetUserGroups(username string, settings *portainer.LDAPSettings)
return nil, err
}
userGroups := getGroupsByUser(userDN, connection, settings.GroupSearchSettings)
userGroups := getGroups(userDN, connection, settings.GroupSearchSettings)
return userGroups, nil
}
// SearchUsers searches for users with the specified settings
func (*Service) SearchUsers(settings *portainer.LDAPSettings) ([]string, error) {
connection, err := createConnection(settings)
if err != nil {
return nil, err
}
defer connection.Close()
if !settings.AnonymousMode {
err = connection.Bind(settings.ReaderDN, settings.Password)
if err != nil {
return nil, err
}
}
users := map[string]bool{}
for _, searchSettings := range settings.SearchSettings {
searchRequest := ldap.NewSearchRequest(
searchSettings.BaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
searchSettings.Filter,
[]string{"dn", searchSettings.UserNameAttribute},
nil,
)
sr, err := connection.Search(searchRequest)
if err != nil {
return nil, err
}
for _, user := range sr.Entries {
username := user.GetAttributeValue(searchSettings.UserNameAttribute)
if username != "" {
users[username] = true
}
}
}
usersList := []string{}
for user := range users {
usersList = append(usersList, user)
}
return usersList, nil
}
// SearchGroups searches for groups with the specified settings
func (*Service) SearchGroups(settings *portainer.LDAPSettings) ([]portainer.LDAPUser, error) {
type groupSet map[string]bool
connection, err := createConnection(settings)
if err != nil {
return nil, err
}
defer connection.Close()
if !settings.AnonymousMode {
err = connection.Bind(settings.ReaderDN, settings.Password)
if err != nil {
return nil, err
}
}
userGroups := map[string]groupSet{}
for _, searchSettings := range settings.GroupSearchSettings {
searchRequest := ldap.NewSearchRequest(
searchSettings.GroupBaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
searchSettings.GroupFilter,
[]string{"cn", searchSettings.GroupAttribute},
nil,
)
sr, err := connection.Search(searchRequest)
if err != nil {
return nil, err
}
for _, entry := range sr.Entries {
members := entry.GetAttributeValues(searchSettings.GroupAttribute)
for _, username := range members {
_, ok := userGroups[username]
if !ok {
userGroups[username] = groupSet{}
}
userGroups[username][entry.GetAttributeValue("cn")] = true
}
}
}
users := []portainer.LDAPUser{}
for username, groups := range userGroups {
groupList := []string{}
for group := range groups {
groupList = append(groupList, group)
}
user := portainer.LDAPUser{
Name: username,
Groups: groupList,
}
users = append(users, user)
}
return users, nil
}
func searchUser(username string, conn *ldap.Conn, settings []portainer.LDAPSearchSettings) (string, error) {
var userDN string
found := false
usernameEscaped := ldap.EscapeFilter(username)
for _, searchSettings := range settings {
searchRequest := ldap.NewSearchRequest(
searchSettings.BaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&%s(%s=%s))", searchSettings.Filter, searchSettings.UserNameAttribute, usernameEscaped),
[]string{"dn"},
nil,
)
// Deliberately skip errors on the search request so that we can jump to other search settings
// if any issue arise with the current one.
sr, err := conn.Search(searchRequest)
if err != nil {
continue
}
if len(sr.Entries) == 1 {
found = true
userDN = sr.Entries[0].DN
break
}
}
if !found {
return "", errUserNotFound
}
return userDN, nil
}
// Get a list of group names for specified user from LDAP/AD
func getGroupsByUser(userDN string, conn *ldap.Conn, settings []portainer.LDAPGroupSearchSettings) []string {
func getGroups(userDN string, conn *ldap.Conn, settings []portainer.LDAPGroupSearchSettings) []string {
groups := make([]string, 0)
userDNEscaped := ldap.EscapeFilter(userDN)
@@ -296,18 +179,9 @@ func (*Service) TestConnectivity(settings *portainer.LDAPSettings) error {
}
defer connection.Close()
if !settings.AnonymousMode {
err = connection.Bind(settings.ReaderDN, settings.Password)
if err != nil {
return err
}
} else {
err = connection.UnauthenticatedBind("")
if err != nil {
return err
}
err = connection.Bind(settings.ReaderDN, settings.Password)
if err != nil {
return err
}
return nil
}

View File

@@ -513,12 +513,6 @@ type (
AutoCreateUsers bool `json:"AutoCreateUsers" example:"true"`
}
// LDAPUser represents a LDAP user
LDAPUser struct {
Name string
Groups []string
}
// LicenseInformation represents information about an extension license
LicenseInformation struct {
LicenseKey string `json:"LicenseKey,omitempty"`
@@ -1301,8 +1295,6 @@ type (
AuthenticateUser(username, password string, settings *LDAPSettings) error
TestConnectivity(settings *LDAPSettings) error
GetUserGroups(username string, settings *LDAPSettings) ([]string, error)
SearchGroups(settings *LDAPSettings) ([]LDAPUser, error)
SearchUsers(settings *LDAPSettings) ([]string, error)
}
// OAuthService represents a service used to authenticate users using OAuth

View File

@@ -721,6 +721,10 @@ a[ng-click] {
.multiSelect .multiSelectItem:hover,
.multiSelect .multiSelectGroup:hover {
border-color: var(--grey-3);
}
.multiSelect .multiSelectItem:hover,
.multiSelect .multiSelectGroup:hover {
background-image: var(--bg-image-multiselect) !important;
color: var(--white-color) !important;
}

View File

@@ -246,6 +246,12 @@ json-tree .branch-preview {
.pagination > li > span:focus {
background-color: var(--bg-pagination-hover-color);
border-color: var(--border-pagination-hover-color);
}
.pagination > li > a:hover,
.pagination > li > span:hover,
.pagination > li > a:focus,
.pagination > li > span:focus {
color: var(--text-pagination-span-hover-color);
}

View File

@@ -63,7 +63,7 @@ class KubernetesDeployController {
RepositoryUsername: '',
RepositoryPassword: '',
AdditionalFiles: [],
ComposeFilePathInRepository: '',
ComposeFilePathInRepository: 'deployment.yml',
RepositoryAutomaticUpdates: true,
RepositoryMechanism: RepositoryMechanismTypes.INTERVAL,
RepositoryFetchInterval: '5m',

View File

@@ -146,13 +146,14 @@
</div>
<div class="form-group">
<div class="col-sm-12">
<por-switch-field
ng-data-cy="k8sNamespaceCreate-loadBalancerQuotaToggle"
label="Load Balancer quota"
name="k8s-resourcepool-Lbquota"
feature="ctrl.LBQuotaFeatureId"
ng-model="lbquota"
></por-switch-field>
<label class="control-label text-left">
Load Balancer quota
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" disabled /><i></i> </label>
<span class="text-muted small" style="margin-left: 15px;">
<i class="fa fa-user" aria-hidden="true"></i>
This feature is available in <a href="https://www.portainer.io/business-upsell?from=k8s-resourcepool-lbquota" target="_blank"> Portainer Business Edition</a>.
</span>
</div>
</div>
<!-- #endregion -->
@@ -388,13 +389,15 @@
</div>
<div class="form-group">
<div class="col-sm-12">
<por-switch-field
ng-data-cy="k8sNamespaceCreate-enableQuotaToggle"
label="Enable quota"
name="k8s-resourcepool-storagequota"
feature="ctrl.StorageQuotaFeatureId"
ng-model="storagequota"
></por-switch-field>
<label class="control-label text-left">
Enable quota
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" disabled /><i></i> </label>
<span class="text-muted small" style="margin-left: 15px;">
<i class="fa fa-user" aria-hidden="true"></i>
This feature is available in
<a href="https://www.portainer.io/business-upsell?from=k8s-resourcepool-storagequota" target="_blank"> Portainer Business Edition</a>.
</span>
</div>
</div>
<!-- #endregion -->

View File

@@ -16,7 +16,6 @@ import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHel
import { KubernetesIngressClassTypes } from 'Kubernetes/ingress/constants';
import KubernetesResourceQuotaConverter from 'Kubernetes/converters/resourceQuota';
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
import { K8S_RESOURCE_POOL_LB_QUOTA, K8S_RESOURCE_POOL_STORAGE_QUOTA } from '@/portainer/feature-flags/feature-ids';
class KubernetesResourcePoolController {
/* #region CONSTRUCTOR */
@@ -61,9 +60,6 @@ class KubernetesResourcePoolController {
this.IngressClassTypes = KubernetesIngressClassTypes;
this.ResourceQuotaDefaults = KubernetesResourceQuotaDefaults;
this.LBQuotaFeatureId = K8S_RESOURCE_POOL_LB_QUOTA;
this.StorageQuotaFeatureId = K8S_RESOURCE_POOL_STORAGE_QUOTA;
this.updateResourcePoolAsync = this.updateResourcePoolAsync.bind(this);
this.getEvents = this.getEvents.bind(this);
}

View File

@@ -6,18 +6,9 @@ export default class BoxSelectorItemController {
this.limitedToBE = false;
}
handleChange(value) {
this.formCtrl.$setValidity(this.radioName, !this.limitedToBE, this.formCtrl);
this.onChange(value);
}
$onInit() {
if (this.option.feature) {
this.limitedToBE = this.featureService.isLimitedToBE(this.option.feature);
}
}
$onDestroy() {
this.formCtrl.$setValidity(this.radioName, true, this.formCtrl);
}
}

View File

@@ -15,7 +15,7 @@
ng-value="$ctrl.option.value"
ng-disabled="$ctrl.disabled"
/>
<label for="{{ $ctrl.option.id }}" ng-click="$ctrl.handleChange($ctrl.option.value)">
<label for="{{ $ctrl.option.id }}" ng-click="$ctrl.onChange($ctrl.option.value, $ctrl.limitedToBE)">
<i class="fas fa-briefcase limited-icon" ng-if="$ctrl.limitedToBE"></i>
<div class="boxselector_header">

View File

@@ -7,9 +7,6 @@ import controller from './box-selector-item.controller';
angular.module('portainer.app').component('boxSelectorItem', {
templateUrl: './box-selector-item.html',
controller,
require: {
formCtrl: '^^form',
},
bindings: {
radioName: '@',
isChecked: '<',

View File

@@ -2,18 +2,13 @@
border-color: var(--BE-only);
}
.form-control.limited-be.no-border {
border-color: var(--border-form-control-color);
}
button.limited-be {
background-color: var(--BE-only);
border-color: var(--BE-only);
}
ng-form.limited-be,
form.limited-be,
div.limited-be {
form.limited-be {
border: solid 1px var(--BE-only);
padding: 10px;
pointer-events: none;

View File

@@ -19,7 +19,7 @@ export function limitedFeatureDirective(featureService) {
}
const limitedFeatureAttrs = Object.keys(attrs)
.filter((attr) => attr.startsWith(BASENAME) && attr !== `${BASENAME}Dir`)
.filter((attr) => attr.startsWith(BASENAME) && attr !== BASENAME)
.map((attr) => [_.kebabCase(attr.replace(BASENAME, '')), attrs[attr]]);
const state = featureService.selectShow(featureId);

View File

@@ -48,9 +48,6 @@ export default class OAuthSettingsController {
if (providerId === 'microsoft' && this.state.microsoftTenantID !== '') {
this.onMicrosoftTenantIDChange();
}
} else {
this.settings.ClientID = '';
this.settings.ClientSecret = '';
}
}

View File

@@ -184,7 +184,6 @@
limited-feature-dir="{{::$ctrl.limitedFeature}}"
limited-feature-class="limited-be"
limited-feature-disabled
limited-feature-tabindex="-1"
required
/>
</div>
@@ -203,7 +202,7 @@
placeholder="xxxxxxxxxxxxxxxxxxxx"
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
tabindex="{{ $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' ? -1 : 0 }}"
required
/>
</div>
</div>
@@ -222,7 +221,7 @@
autocomplete="new-password"
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
tabindex="{{ $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' ? -1 : 0 }}"
required
/>
</div>
</div>
@@ -237,15 +236,7 @@
></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input
type="text"
class="form-control"
id="oauth_authorization_uri"
ng-model="$ctrl.settings.AuthorizationURI"
placeholder="https://example.com/oauth/authorize"
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
/>
<input type="text" class="form-control" id="oauth_authorization_uri" ng-model="$ctrl.settings.AuthorizationURI" placeholder="https://example.com/oauth/authorize" />
</div>
</div>
@@ -255,15 +246,7 @@
<portainer-tooltip position="bottom" message="URL used by Portainer to exchange a valid OAuth authentication code for an access token"></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input
type="text"
class="form-control"
id="oauth_access_token_uri"
ng-model="$ctrl.settings.AccessTokenURI"
placeholder="https://example.com/oauth/token"
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
/>
<input type="text" class="form-control" id="oauth_access_token_uri" ng-model="$ctrl.settings.AccessTokenURI" placeholder="https://example.com/oauth/token" />
</div>
</div>
@@ -273,15 +256,7 @@
<portainer-tooltip position="bottom" message="URL used by Portainer to retrieve information about the authenticated user"></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input
type="text"
class="form-control"
id="oauth_resource_uri"
ng-model="$ctrl.settings.ResourceURI"
placeholder="https://example.com/user"
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
/>
<input type="text" class="form-control" id="oauth_resource_uri" ng-model="$ctrl.settings.ResourceURI" placeholder="https://example.com/user" />
</div>
</div>
@@ -294,15 +269,7 @@
></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input
type="text"
class="form-control"
id="oauth_redirect_uri"
ng-model="$ctrl.settings.RedirectURI"
placeholder="http://yourportainer.com/"
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
/>
<input type="text" class="form-control" id="oauth_redirect_uri" ng-model="$ctrl.settings.RedirectURI" placeholder="http://yourportainer.com/" />
</div>
</div>
@@ -315,14 +282,7 @@
></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input
type="text"
class="form-control"
id="oauth_logout_url"
ng-model="$ctrl.settings.LogoutURI"
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
/>
<input type="text" class="form-control" id="oauth_logout_url" ng-model="$ctrl.settings.LogoutURI" />
</div>
</div>
@@ -335,15 +295,7 @@
></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input
type="text"
class="form-control"
id="oauth_user_identifier"
ng-model="$ctrl.settings.UserIdentifier"
placeholder="id"
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
/>
<input type="text" class="form-control" id="oauth_user_identifier" ng-model="$ctrl.settings.UserIdentifier" placeholder="id" />
</div>
</div>
@@ -356,15 +308,7 @@
></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input
type="text"
class="form-control"
id="oauth_scopes"
ng-model="$ctrl.settings.Scopes"
placeholder="id,email,name"
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
/>
<input type="text" class="form-control" id="oauth_scopes" ng-model="$ctrl.settings.Scopes" placeholder="id,email,name" />
</div>
</div>
</div>

View File

@@ -35,36 +35,14 @@
<div class="form-group">
<label for="ldap_url" class="col-sm-3 col-lg-2 control-label text-left" style="display: flex; flex-wrap: wrap;">
AD Controller
<button
type="button"
class="label label-default interactive"
style="border: 0;"
ng-click="$ctrl.addLDAPUrl()"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>
<button type="button" class="label label-default interactive" style="border: 0;" ng-click="$ctrl.addLDAPUrl()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> Add additional server
</button>
</label>
<div class="col-sm-9 col-lg-10">
<div ng-repeat="url in $ctrl.settings.URLs track by $index" style="display: flex; margin-bottom: 10px;">
<input
type="text"
class="form-control"
id="ldap_url"
ng-model="$ctrl.settings.URLs[$index]"
placeholder="e.g. 10.0.0.10:389 or myldap.domain.tld:389"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
/>
<button
ng-if="$index > 0"
class="btn btn-sm btn-danger"
type="button"
ng-click="$ctrl.removeLDAPUrl($index)"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>
<input type="text" class="form-control" id="ldap_url" ng-model="$ctrl.settings.URLs[$index]" placeholder="e.g. 10.0.0.10:389 or myldap.domain.tld:389" />
<button ng-if="$index > 0" class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeLDAPUrl($index)">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</div>
@@ -84,8 +62,6 @@
ng-model="$ctrl.settings.ReaderDN"
placeholder="reader@domain.tld"
ng-change="$ctrl.onAccountChange($ctrl.settings.ReaderDN)"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
/>
</div>
</div>
@@ -96,16 +72,7 @@
<portainer-tooltip position="bottom" message="If you do not enter a password, Portainer will leave the current password unchanged."></portainer-tooltip>
</label>
<div class="col-sm-9">
<input
type="password"
class="form-control"
id="ldap_password"
ng-model="$ctrl.settings.Password"
placeholder="password"
autocomplete="new-password"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
/>
<input type="password" class="form-control" id="ldap_password" ng-model="$ctrl.settings.Password" placeholder="password" autocomplete="new-password" />
</div>
</div>
@@ -114,7 +81,6 @@
settings="$ctrl.settings"
state="$ctrl.state"
connectivity-check="$ctrl.connectivityCheck"
limited-feature-id="$ctrl.limitedFeatureId"
></ldap-connectivity-check>
<ldap-settings-security
@@ -123,7 +89,6 @@
tlsca-cert="$ctrl.tlscaCert"
upload-in-progress="$ctrl.state.uploadInProgress"
on-tlsca-cert-change="($ctrl.onTlscaCertChange)"
limited-feature-id="$ctrl.limitedFeatureId"
></ldap-settings-security>
<ldap-connectivity-check
@@ -131,7 +96,6 @@
settings="$ctrl.settings"
state="$ctrl.state"
connectivity-check="$ctrl.connectivityCheck"
limited-feature-id="$ctrl.limitedFeatureId"
></ldap-connectivity-check>
<ldap-user-search
@@ -141,7 +105,6 @@
domain-suffix="{{ $ctrl.domainSuffix }}"
base-filter="(objectClass=user)"
on-search-click="($ctrl.searchUsers)"
limited-feature-id="$ctrl.limitedFeatureId"
></ldap-user-search>
<ldap-group-search
@@ -150,8 +113,7 @@
domain-suffix="{{ $ctrl.domainSuffix }}"
base-filter="(objectClass=group)"
on-search-click="($ctrl.searchGroups)"
limited-feature-id="$ctrl.limitedFeatureId"
></ldap-group-search>
<ldap-settings-test-login settings="$ctrl.settings" limited-feature-id="$ctrl.limitedFeatureId"></ldap-settings-test-login>
<ldap-settings-test-login settings="$ctrl.settings"></ldap-settings-test-login>
</ng-form>

View File

@@ -4,6 +4,5 @@ export const ldapConnectivityCheck = {
settings: '<',
state: '<',
connectivityCheck: '<',
limitedFeatureId: '<',
},
};

View File

@@ -11,8 +11,6 @@
ng-disabled="($ctrl.state.connectivityCheckInProgress) || (!$ctrl.settings.URLs.length) || ((!$ctrl.settings.ReaderDN || !$ctrl.settings.Password) && !$ctrl.settings.AnonymousMode)"
ng-click="$ctrl.connectivityCheck()"
button-spinner="$ctrl.state.connectivityCheckInProgress"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>
<span ng-hide="$ctrl.state.connectivityCheckInProgress">Test connectivity</span>
<span ng-show="$ctrl.state.connectivityCheckInProgress">Testing connectivity...</span>

View File

@@ -6,6 +6,6 @@ export const ldapCustomGroupSearch = {
bindings: {
settings: '=',
onSearchClick: '<',
limitedFeatureId: '<',
limitedFeature: '<',
},
};

View File

@@ -1,5 +1,5 @@
<div class="col-sm-12 form-section-title" style="float: initial;">
Group search configurations
Teams auto-population configurations
</div>
<rd-widget ng-repeat="config in $ctrl.settings | limitTo: (1 - $ctrl.settings)" style="display: block; margin-bottom: 10px;">
@@ -55,14 +55,13 @@
class="btn btm-sm btn-primary"
type="button"
ng-click="$ctrl.search()"
limited-feature-dir="{{ $ctrl.limitedFeatureId }}"
limited-feature-tabindex="-1"
limited-feature-dir="{{ $ctrl.limitedFeature }}"
limited-feature-disabled
limited-feature-class="limited-be"
>
Display User/Group matching
</button>
<be-feature-indicator feature="$ctrl.limitedFeatureId" class="space-left"></be-feature-indicator>
<be-feature-indicator feature="$ctrl.limitedFeature" class="space-left"></be-feature-indicator>
</div>
</div>

View File

@@ -6,6 +6,6 @@ export const ldapCustomUserSearch = {
bindings: {
settings: '=',
onSearchClick: '<',
limitedFeatureId: '<',
limitedFeature: '<',
},
};

View File

@@ -55,14 +55,13 @@
class="btn btm-sm btn-primary"
type="button"
ng-click="$ctrl.search()"
limited-feature-dir="{{ $ctrl.limitedFeatureId }}"
limited-feature-dir="{{ $ctrl.limitedFeature }}"
limited-feature-disabled
limited-feature-class="limited-be"
limited-feature-tabindex="-1"
>
Display Users
</button>
<be-feature-indicator feature="$ctrl.limitedFeatureId" class="space-left"></be-feature-indicator>
<be-feature-indicator feature="$ctrl.limitedFeature" class="space-left"></be-feature-indicator>
</div>
</div>

View File

@@ -10,6 +10,5 @@ export const ldapGroupSearchItem = {
baseFilter: '@',
onRemoveClick: '<',
limitedFeatureId: '<',
},
};

View File

@@ -4,13 +4,7 @@
<span class="text-muted small">
Extra search configuration
</span>
<button
class="btn btn-sm btn-danger"
type="button"
ng-click="$ctrl.onRemoveClick($ctrl.index)"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>
<button class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.onRemoveClick($ctrl.index)">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</div>
@@ -20,7 +14,6 @@
suffix="{{ $ctrl.domainSuffix }}"
ng-model="$ctrl.config.GroupBaseDN"
on-change="($ctrl.onChangeBaseDN)"
limited-feature-id="$ctrl.limitedFeatureId"
></ldap-settings-dn-builder>
<div class="form-group">
@@ -44,34 +37,16 @@
<rd-widget-body>
<div class="form-group no-margin-last-child" ng-repeat="entry in $ctrl.groups">
<div class="col-sm-4">
<select
class="form-control"
ng-model="entry.type"
ng-change="$ctrl.onGroupsChange()"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>
<select class="form-control" ng-model="entry.type" ng-change="$ctrl.onGroupsChange()">
<option value="ou">OU Name</option>
<option value="cn">Folder Name</option>
</select>
</div>
<div class="col-sm-5">
<input
class="form-control"
ng-model="entry.value"
ng-change="$ctrl.onGroupsChange()"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
/>
<input class="form-control" ng-model="entry.value" ng-change="$ctrl.onGroupsChange()" />
</div>
<div class="col-sm-3 text-right">
<button
class="btn btn-sm btn-danger"
type="button"
ng-click="$ctrl.removeGroup($index)"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>
<button class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeGroup($index)">
<i class="fa fa-trash-alt" aria-hidden="true"></i>
</button>
</div>

View File

@@ -9,6 +9,5 @@ export const ldapGroupSearch = {
baseFilter: '@',
onSearchClick: '<',
limitedFeatureId: '<',
},
};

View File

@@ -1,5 +1,5 @@
<div class="col-sm-12 form-section-title" style="float: initial;">
Group search configurations
Teams auto-population configurations
</div>
<div style="margin-top: 10px;" ng-repeat="config in $ctrl.settings | limitTo: (1 - $ctrl.settings)">
@@ -9,24 +9,17 @@
index="$index"
base-filter="{{ $ctrl.baseFilter }}"
on-remove-click="($ctrl.onRemoveClick)"
limited-feature-id="$ctrl.limitedFeatureId"
></ldap-group-search-item>
</div>
<div class="form-group" style="margin-top: 10px;">
<div class="col-sm-12">
<button
class="label label-default interactive"
style="border: 0;"
ng-click="$ctrl.onAddClick()"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>
<button class="label label-default interactive" style="border: 0;" ng-click="$ctrl.onAddClick()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> add group search configuration
</button>
</div>
<div class="col-sm-12" style="margin-top: 10px;">
<button class="btn btm-sm btn-primary" type="button" ng-click="$ctrl.search()" limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-tabindex="-1">
<button class="btn btm-sm btn-primary" type="button" ng-click="$ctrl.search()">
Display User/Group matching
</button>
</div>

View File

@@ -2,7 +2,7 @@ import { EXTERNAL_AUTH_LDAP } from '@/portainer/feature-flags/feature-ids';
export default class LdapSettingsCustomController {
constructor() {
this.limitedFeatureId = EXTERNAL_AUTH_LDAP;
this.limitedFeature = EXTERNAL_AUTH_LDAP;
}
addLDAPUrl() {

View File

@@ -29,7 +29,7 @@
class="label label-default interactive"
style="border: 0;"
ng-click="$ctrl.addLDAPUrl()"
limited-feature-dir="{{ $ctrl.limitedFeatureId }}"
limited-feature-dir="{{ $ctrl.limitedFeature }}"
limited-feature-disabled
limited-feature-class="limited-be"
>
@@ -38,7 +38,7 @@
</label>
<div class="col-sm-9 col-lg-10">
<div ng-repeat="url in $ctrl.settings.URLs track by $index" style="display: flex; margin-bottom: 10px;">
<input type="text" class="form-control" id="ldap_url" ng-model="$ctrl.settings.URLs[$index]" placeholder="e.g. 10.0.0.10:389 or myldap.domain.tld:389" required />
<input type="text" class="form-control" id="ldap_url" ng-model="$ctrl.settings.URLs[$index]" placeholder="e.g. 10.0.0.10:389 or myldap.domain.tld:389" />
<button ng-if="$index > 0" class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeLDAPUrl($index)">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
@@ -105,15 +105,13 @@
style="margin-top: 5px;"
settings="$ctrl.settings.SearchSettings"
on-search-click="($ctrl.onSearchUsersClick)"
limited-feature-id="$ctrl.limitedFeatureId"
limited-feature="$ctrl.limitedFeature"
></ldap-custom-user-search>
<ldap-custom-group-search
style="margin-top: 5px;"
settings="$ctrl.settings.GroupSearchSettings"
on-search-click="($ctrl.onSearchGroupsClick)"
limited-feature-id="$ctrl.limitedFeatureId"
limited-feature="$ctrl.limitedFeature"
></ldap-custom-group-search>
<div limited-feature-dir="{{ $ctrl.limitedFeatureId }}" limited-feature-class="limited-be">
<ldap-settings-test-login settings="$ctrl.settings" limited-feature-id="$ctrl.limitedFeatureId" show-be-indicator-if-needed="true"></ldap-settings-test-login>
</div>
<ldap-settings-test-login settings="$ctrl.settings" limited-feature="$ctrl.limitedFeature"></ldap-settings-test-login>

View File

@@ -11,6 +11,5 @@ export const ldapSettingsDnBuilder = {
// suffix: string (dc=,dc=,)
suffix: '@',
label: '@',
limitedFeatureId: '<',
},
};

View File

@@ -1,14 +1,7 @@
<div class="form-group ldap-dn-builder">
<div class="col-sm-12" style="margin-bottom: 5px;">
<label class="control-label text-left">{{ $ctrl.label || 'DN entries' }}</label>
<button
type="button"
class="label label-default interactive"
style="margin-left: 10px; border: 0;"
ng-click="$ctrl.addEntry()"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>
<button type="button" class="label label-default interactive" style="margin-left: 10px; border: 0;" ng-click="$ctrl.addEntry()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> add another entry
</button>
</div>
@@ -17,48 +10,22 @@
<rd-widget-body>
<div class="form-group no-margin-last-child" ng-repeat="entry in $ctrl.entries">
<div class="col-sm-4">
<select class="form-control" ng-model="entry.type" ng-change="$ctrl.onEntriesChange()" limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-tabindex="-1">
<select class="form-control" ng-model="entry.type" ng-change="$ctrl.onEntriesChange()">
<option value="ou">OU Name</option>
<option value="cn">Folder Name</option>
</select>
</div>
<div class="col-sm-5">
<input
class="form-control"
ng-model="entry.value"
ng-change="$ctrl.onEntriesChange()"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
/>
<input class="form-control" ng-model="entry.value" ng-change="$ctrl.onEntriesChange()" />
</div>
<div class="col-sm-3 text-right">
<button
class="btn btn-sm btn-primary"
type="button"
ng-disabled="$first"
ng-click="$ctrl.moveUp($index)"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>
<button class="btn btn-sm btn-primary" type="button" ng-disabled="$first" ng-click="$ctrl.moveUp($index)">
<i class="fa fa-arrow-up" aria-hidden="true"></i>
</button>
<button
class="btn btn-sm btn-primary"
type="button"
ng-disabled="$last"
ng-click="$ctrl.moveDown($index)"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>
<button class="btn btn-sm btn-primary" type="button" ng-disabled="$last" ng-click="$ctrl.moveDown($index)">
<i class="fa fa-arrow-down" aria-hidden="true"></i>
</button>
<button
class="btn btn-sm btn-danger"
type="button"
ng-click="$ctrl.removeEntry($index)"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>
<button class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeEntry($index)">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</div>

View File

@@ -13,6 +13,5 @@ export const ldapSettingsGroupDnBuilder = {
// index: int >= 0
index: '<',
onRemoveClick: '<',
limitedFeatureId: '<',
},
};

View File

@@ -3,25 +3,10 @@
Group Name
</label>
<div class="col-sm-7" style="padding-left: 0;">
<input
type="text"
class="form-control"
id="group-name-input"
ng-model="$ctrl.groupName"
ng-change="$ctrl.onGroupNameChange()"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
/>
<input type="text" class="form-control" id="group-name-input" ng-model="$ctrl.groupName" ng-change="$ctrl.onGroupNameChange()" />
</div>
<div class="col-sm-1">
<button
type="button"
class="btn btn-danger btn-sm"
ng-if="$ctrl.onRemoveClick"
ng-click="$ctrl.onRemoveClick()"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>
<button type="button" class="btn btn-danger btn-sm" ng-if="$ctrl.onRemoveClick" ng-click="$ctrl.onRemoveClick()">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</div>
@@ -33,5 +18,4 @@
suffix="{{ $ctrl.suffix }}"
on-change="($ctrl.onEntriesChange)"
on-remove-click="($ctrl.removeGroup)"
limited-feature-id="$ctrl.limitedFeatureId"
></ldap-settings-dn-builder>

View File

@@ -27,36 +27,14 @@
<div class="form-group">
<label for="ldap_url" class="col-sm-3 col-lg-2 control-label text-left" style="display: flex; flex-wrap: wrap;">
LDAP Server
<button
type="button"
class="label label-default interactive"
style="border: 0;"
ng-click="$ctrl.addLDAPUrl()"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>
<button type="button" class="label label-default interactive" style="border: 0;" ng-click="$ctrl.addLDAPUrl()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> Add additional server
</button>
</label>
<div class="col-sm-9 col-lg-10">
<div ng-repeat="url in $ctrl.settings.URLs track by $index" style="display: flex; margin-bottom: 10px;">
<input
type="text"
class="form-control"
id="ldap_url"
ng-model="$ctrl.settings.URLs[$index]"
placeholder="e.g. 10.0.0.10:389 or myldap.domain.tld:389"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
/>
<button
ng-if="$index > 0"
class="btn btn-sm btn-danger"
type="button"
ng-click="$ctrl.removeLDAPUrl($index)"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>
<input type="text" class="form-control" id="ldap_url" ng-model="$ctrl.settings.URLs[$index]" placeholder="e.g. 10.0.0.10:389 or myldap.domain.tld:389" />
<button ng-if="$index > 0" class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeLDAPUrl($index)">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</div>
@@ -70,15 +48,7 @@
<portainer-tooltip position="bottom" message="Enable this option if the server is configured for Anonymous access."></portainer-tooltip>
</label>
<div class="col-sm-9">
<label class="switch">
<input
type="checkbox"
id="anonymous_mode"
ng-model="$ctrl.settings.AnonymousMode"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
/><i></i>
</label>
<label class="switch"> <input type="checkbox" id="anonymous_mode" ng-model="$ctrl.settings.AnonymousMode" /><i></i> </label>
</div>
</div>
<!-- !Anonymous mode-->
@@ -97,8 +67,6 @@
ng-model="$ctrl.settings.ReaderDN"
placeholder="cn=user,dc=domain,dc=tld"
ng-change="$ctrl.onAccountChange($ctrl.settings.ReaderDN)"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
/>
</div>
</div>
@@ -109,16 +77,7 @@
<portainer-tooltip position="bottom" message="If you do not enter a password, Portainer will leave the current password unchanged."></portainer-tooltip>
</label>
<div class="col-sm-9">
<input
type="password"
class="form-control"
id="ldap_password"
ng-model="$ctrl.settings.Password"
placeholder="password"
autocomplete="new-password"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
/>
<input type="password" class="form-control" id="ldap_password" ng-model="$ctrl.settings.Password" placeholder="password" autocomplete="new-password" />
</div>
</div>
</div>
@@ -128,15 +87,7 @@
Domain root
</label>
<div class="col-sm-9">
<input
type="text"
class="form-control"
id="ldap_domain_root"
ng-model="$ctrl.domainSuffix"
placeholder="dc=domain,dc=tld"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
/>
<input type="text" class="form-control" id="ldap_domain_root" ng-model="$ctrl.domainSuffix" placeholder="dc=domain,dc=tld" />
</div>
</div>
@@ -145,7 +96,6 @@
settings="$ctrl.settings"
state="$ctrl.state"
connectivity-check="$ctrl.connectivityCheck"
limited-feature-id="$ctrl.limitedFeatureId"
></ldap-connectivity-check>
<ldap-settings-security
@@ -154,7 +104,6 @@
tlsca-cert="$ctrl.tlscaCert"
upload-in-progress="$ctrl.state.uploadInProgress"
on-tlsca-cert-change="($ctrl.onTlscaCertChange)"
limited-feature-id="$ctrl.limitedFeatureId"
></ldap-settings-security>
<ldap-connectivity-check
@@ -162,7 +111,6 @@
settings="$ctrl.settings"
state="$ctrl.state"
connectivity-check="$ctrl.connectivityCheck"
limited-feature-id="$ctrl.limitedFeatureId"
></ldap-connectivity-check>
<ldap-user-search
@@ -171,7 +119,6 @@
domain-suffix="{{ $ctrl.domainSuffix }}"
base-filter="(objectClass=inetOrgPerson)"
on-search-click="($ctrl.onSearchUsersClick)"
limited-feature-id="$ctrl.limitedFeatureId"
></ldap-user-search>
<ldap-group-search
@@ -180,8 +127,7 @@
domain-suffix="{{ $ctrl.domainSuffix }}"
base-filter="(objectClass=groupOfNames)"
on-search-click="($ctrl.onSearchGroupsClick)"
limited-feature-id="$ctrl.limitedFeatureId"
></ldap-group-search>
<ldap-settings-test-login settings="$ctrl.settings" limited-feature-id="$ctrl.limitedFeatureId"></ldap-settings-test-login>
<ldap-settings-test-login settings="$ctrl.settings"></ldap-settings-test-login>
</ng-form>

View File

@@ -6,6 +6,5 @@ export const ldapSettingsSecurity = {
onTlscaCertChange: '<',
uploadInProgress: '<',
title: '@',
limitedFeatureId: '<',
},
};

View File

@@ -12,9 +12,7 @@
></portainer-tooltip>
</label>
<div class="col-sm-9">
<label class="switch">
<input type="checkbox" ng-model="$ctrl.settings.StartTLS" limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-tabindex="-1" /><i></i>
</label>
<label class="switch"> <input type="checkbox" ng-model="$ctrl.settings.StartTLS" /><i></i> </label>
</div>
</div>
<!-- !starttls -->
@@ -26,9 +24,7 @@
<portainer-tooltip position="bottom" message="Enable this option if you need to specify TLS certificates to connect to the LDAP server."></portainer-tooltip>
</label>
<div class="col-sm-9">
<label class="switch">
<input type="checkbox" ng-model="$ctrl.settings.TLSConfig.TLS" limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-tabindex="-1" /><i></i>
</label>
<label class="switch"> <input type="checkbox" ng-model="$ctrl.settings.TLSConfig.TLS" /><i></i> </label>
</div>
</div>
<!-- !tls-checkbox -->
@@ -40,9 +36,7 @@
<portainer-tooltip position="bottom" message="Skip the verification of the server TLS certificate. Not recommended on unsecured networks."></portainer-tooltip>
</label>
<div class="col-sm-9">
<label class="switch">
<input type="checkbox" ng-model="$ctrl.settings.TLSConfig.TLSSkipVerify" limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-tabindex="-1" /><i></i>
</label>
<label class="switch"> <input type="checkbox" ng-model="$ctrl.settings.TLSConfig.TLSSkipVerify" /><i></i> </label>
</div>
</div>
<!-- !tls-skip-verify -->
@@ -51,16 +45,7 @@
<div class="form-group" ng-if="$ctrl.settings.TLSConfig.TLS || ($ctrl.settings.StartTLS && !$ctrl.settings.TLSConfig.TLSSkipVerify)">
<label class="col-sm-3 control-label text-left">TLS CA certificate</label>
<div class="col-sm-9">
<button
type="button"
class="btn btn-sm btn-primary"
ngf-select="$ctrl.onTlscaCertChange($file)"
ng-model="$ctrl.tlscaCert"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>
Select file
</button>
<button type="button" class="btn btn-sm btn-primary" ngf-select="$ctrl.onTlscaCertChange($file)" ng-model="$ctrl.tlscaCert">Select file</button>
<span style="margin-left: 5px;">
{{ $ctrl.tlscaCert.name }}
<i class="fa fa-check green-icon" ng-if="$ctrl.tlscaCert && $ctrl.tlscaCert === $ctrl.settings.TLSConfig.TLSCACert" aria-hidden="true"></i>

View File

@@ -5,7 +5,6 @@ export const ldapSettingsTestLogin = {
controller,
bindings: {
settings: '=',
limitedFeatureId: '<',
showBeIndicatorIfNeeded: '<',
limitedFeature: '<',
},
};

View File

@@ -6,7 +6,7 @@
<label for="ldap_test_username" style="font-size: 0.9em; margin-right: 5px;">
Username
</label>
<input type="text" class="form-control" id="ldap_test_username" ng-model="$ctrl.username" limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-tabindex="-1" />
<input type="text" class="form-control" id="ldap_test_username" ng-model="$ctrl.username" limited-feature-dir="{{::$ctrl.limitedFeature}}" limited-feature-class="limited-be" />
</div>
<div class="form-group" style="margin: 0;">
@@ -19,8 +19,8 @@
id="ldap_test_password"
ng-model="$ctrl.password"
autocomplete="new-password"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
limited-feature-dir="{{::$ctrl.limitedFeature}}"
limited-feature-class="limited-be"
/>
</div>
@@ -30,9 +30,8 @@
class="btn btn-primary"
ng-disabled="$ctrl.state.testStatus === $ctrl.TEST_STATUS.LOADING || !$ctrl.username || !$ctrl.password"
ng-click="$ctrl.testLogin($ctrl.username, $ctrl.password)"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-dir="{{::$ctrl.limitedFeature}}"
limited-feature-disabled
limited-feature-tabindex="-1"
>
<span ng-if="$ctrl.state.testStatus !== $ctrl.TEST_STATUS.LOADING">Test</span>
<span ng-if="$ctrl.state.testStatus === $ctrl.TEST_STATUS.LOADING">Testing...</span>
@@ -41,5 +40,5 @@
<i ng-if="$ctrl.state.testStatus === $ctrl.TEST_STATUS.FAILURE" class="fa fa-times red-icon"></i>
</div>
<be-feature-indicator ng-if="$ctrl.showBeIndicatorIfNeeded" feature="$ctrl.limitedFeatureId" class="space-left"></be-feature-indicator>
<be-feature-indicator feature="$ctrl.limitedFeature" class="space-left"></be-feature-indicator>
</div>

View File

@@ -10,13 +10,7 @@
Server Type
</div>
<box-selector
style="margin-bottom: 0;"
radio-name="ldap-server-type-selector"
ng-model="$ctrl.settings.ServerType"
options="$ctrl.boxSelectorOptions"
on-change="($ctrl.onChangeServerType)"
></box-selector>
<box-selector style="margin-bottom: 0;" ng-model="$ctrl.settings.ServerType" options="$ctrl.boxSelectorOptions" on-change="($ctrl.onChangeServerType)"></box-selector>
<ldap-settings-custom
ng-if="$ctrl.settings.ServerType === $ctrl.SERVER_TYPES.CUSTOM"

View File

@@ -10,6 +10,5 @@ export const ldapUserSearchItem = {
domainSuffix: '@',
baseFilter: '@',
onRemoveClick: '<',
limitedFeatureId: '<',
},
};

View File

@@ -4,14 +4,7 @@
<span class="text-muted small">
Extra search configuration
</span>
<button
ng-if="$ctrl.index > 0"
class="btn btn-sm btn-danger"
type="button"
ng-click="$ctrl.onRemoveClick($ctrl.index)"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>
<button ng-if="$ctrl.index > 0" class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.onRemoveClick($ctrl.index)">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</div>
@@ -23,23 +16,8 @@
<div class="col-sm-8">
<div class="input-group">
<div class="input-group-btn">
<button
class="btn btn-primary"
ng-model="$ctrl.config.UserNameAttribute"
uib-btn-radio="'sAMAccountName'"
style="margin-left: 0px;"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>username</button
>
<button
class="btn btn-primary"
ng-model="$ctrl.config.UserNameAttribute"
uib-btn-radio="'userPrincipalName'"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>user@domainname</button
>
<button class="btn btn-primary" ng-model="$ctrl.config.UserNameAttribute" uib-btn-radio="'sAMAccountName'" style="margin-left: 0px;">username</button>
<button class="btn btn-primary" ng-model="$ctrl.config.UserNameAttribute" uib-btn-radio="'userPrincipalName'">user@domainname</button>
</div>
</div>
</div>
@@ -59,20 +37,12 @@
label="User Search Path (optional)"
suffix="{{ $ctrl.domainSuffix }}"
on-change="($ctrl.onBaseDNChange)"
limited-feature-id="$ctrl.limitedFeatureId"
></ldap-settings-dn-builder>
<div class="form-group no-margin-last-child">
<div class="col-sm-12" style="margin-bottom: 5px;">
<label class="control-label text-left">Allowed Groups (optional)</label>
<button
type="button"
class="label label-default interactive"
style="margin-left: 10px; border: 0;"
ng-click="$ctrl.addGroup()"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>
<button type="button" class="label label-default interactive" style="margin-left: 10px; border: 0;" ng-click="$ctrl.addGroup()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> add another group
</button>
</div>
@@ -86,7 +56,6 @@
suffix="{{ $ctrl.domainSuffix }}"
on-change="($ctrl.onGroupChange)"
on-remove-click="($ctrl.removeGroup)"
limited-feature-id="$ctrl.limitedFeatureId"
></ldap-settings-group-dn-builder>
</rd-widget-body>
</rd-widget>

View File

@@ -8,7 +8,6 @@ export const ldapUserSearch = {
domainSuffix: '@',
showUsernameFormat: '<',
baseFilter: '@',
limitedFeatureId: '<',
onSearchClick: '<',
},

View File

@@ -10,24 +10,17 @@
show-username-format="$ctrl.showUsernameFormat"
base-filter="{{ $ctrl.baseFilter }}"
on-remove-click="($ctrl.onRemoveClick)"
limited-feature-id="$ctrl.limitedFeatureId"
></ldap-user-search-item>
</div>
<div class="form-group" style="margin-top: 10px;">
<div class="col-sm-12">
<button
class="label label-default interactive"
style="border: 0;"
ng-click="$ctrl.onAddClick()"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>
<button class="label label-default interactive" style="border: 0;" ng-click="$ctrl.onAddClick()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> add user search configuration
</button>
</div>
<div class="col-sm-12" style="margin-top: 10px;">
<button class="btn btm-sm btn-primary" type="button" ng-click="$ctrl.search()" limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-tabindex="-1">
<button class="btn btm-sm btn-primary" type="button" ng-click="$ctrl.search()">
Display Users
</button>
</div>