mirror of https://github.com/portainer/portainer
feat(auth): add custom user timeout (#3871)
* feat(auth): introduce new timeout constant * feat(auth): pass timeout from handler * feat(auth): add timeout selector to auth settings view * feat(settings): add user session timeout property * feat(auth): load user session timeout from settings * fix(settings): use correct time format * feat(auth): remove no-auth flag * refactor(auth): move timeout mgmt to jwt service * refactor(client): remove no-auth checks from client * refactor(cli): remove defaultNoAuth * feat(settings): create settings with default user timeout value * refactor(db): save user session timeout always * refactor(jwt): return error * feat(auth): set session timeout in jwt service on update * feat(auth): add description and time settings * feat(auth): parse duration * feat(settings): validate user timeout format * refactor(settings): remove unneccesary importpull/3914/head
parent
b58c2facfe
commit
b02749f877
|
@ -27,6 +27,7 @@ func (store *Store) Init() error {
|
||||||
EnableHostManagementFeatures: false,
|
EnableHostManagementFeatures: false,
|
||||||
EdgeAgentCheckinInterval: portainer.DefaultEdgeAgentCheckinIntervalInSeconds,
|
EdgeAgentCheckinInterval: portainer.DefaultEdgeAgentCheckinIntervalInSeconds,
|
||||||
TemplatesURL: portainer.DefaultTemplatesURL,
|
TemplatesURL: portainer.DefaultTemplatesURL,
|
||||||
|
UserSessionTimeout: portainer.DefaultUserSessionTimeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = store.SettingsService.UpdateSettings(defaultSettings)
|
err = store.SettingsService.UpdateSettings(defaultSettings)
|
||||||
|
|
|
@ -10,9 +10,9 @@ func (m *Migrator) updateSettingsToDB24() error {
|
||||||
|
|
||||||
if legacySettings.TemplatesURL == "" {
|
if legacySettings.TemplatesURL == "" {
|
||||||
legacySettings.TemplatesURL = portainer.DefaultTemplatesURL
|
legacySettings.TemplatesURL = portainer.DefaultTemplatesURL
|
||||||
|
|
||||||
return m.settingsService.UpdateSettings(legacySettings)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
legacySettings.UserSessionTimeout = portainer.DefaultUserSessionTimeout
|
||||||
|
|
||||||
|
return m.settingsService.UpdateSettings(legacySettings)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/portainer/portainer/api"
|
"github.com/portainer/portainer/api"
|
||||||
|
@ -20,7 +19,6 @@ const (
|
||||||
errInvalidEndpointProtocol = portainer.Error("Invalid endpoint protocol: Portainer only supports unix://, npipe:// or tcp://")
|
errInvalidEndpointProtocol = portainer.Error("Invalid endpoint protocol: Portainer only supports unix://, npipe:// or tcp://")
|
||||||
errSocketOrNamedPipeNotFound = portainer.Error("Unable to locate Unix socket or named pipe")
|
errSocketOrNamedPipeNotFound = portainer.Error("Unable to locate Unix socket or named pipe")
|
||||||
errInvalidSnapshotInterval = portainer.Error("Invalid snapshot interval")
|
errInvalidSnapshotInterval = portainer.Error("Invalid snapshot interval")
|
||||||
errNoAuthExcludeAdminPassword = portainer.Error("Cannot use --no-auth with --admin-password or --admin-password-file")
|
|
||||||
errAdminPassExcludeAdminPassFile = portainer.Error("Cannot use --admin-password with --admin-password-file")
|
errAdminPassExcludeAdminPassFile = portainer.Error("Cannot use --admin-password with --admin-password-file")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -35,7 +33,6 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
||||||
Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(),
|
Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(),
|
||||||
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
|
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
|
||||||
EndpointURL: kingpin.Flag("host", "Endpoint URL").Short('H').String(),
|
EndpointURL: kingpin.Flag("host", "Endpoint URL").Short('H').String(),
|
||||||
NoAuth: kingpin.Flag("no-auth", "Disable authentication (deprecated)").Default(defaultNoAuth).Bool(),
|
|
||||||
NoAnalytics: kingpin.Flag("no-analytics", "Disable Analytics in app").Default(defaultNoAnalytics).Bool(),
|
NoAnalytics: kingpin.Flag("no-analytics", "Disable Analytics in app").Default(defaultNoAnalytics).Bool(),
|
||||||
TLS: kingpin.Flag("tlsverify", "TLS support").Default(defaultTLS).Bool(),
|
TLS: kingpin.Flag("tlsverify", "TLS support").Default(defaultTLS).Bool(),
|
||||||
TLSSkipVerify: kingpin.Flag("tlsskipverify", "Disable TLS server verification").Default(defaultTLSSkipVerify).Bool(),
|
TLSSkipVerify: kingpin.Flag("tlsskipverify", "Disable TLS server verification").Default(defaultTLSSkipVerify).Bool(),
|
||||||
|
@ -81,10 +78,6 @@ func (*Service) ValidateFlags(flags *portainer.CLIFlags) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if *flags.NoAuth && (*flags.AdminPassword != "" || *flags.AdminPasswordFile != "") {
|
|
||||||
return errNoAuthExcludeAdminPassword
|
|
||||||
}
|
|
||||||
|
|
||||||
if *flags.AdminPassword != "" && *flags.AdminPasswordFile != "" {
|
if *flags.AdminPassword != "" && *flags.AdminPasswordFile != "" {
|
||||||
return errAdminPassExcludeAdminPassFile
|
return errAdminPassExcludeAdminPassFile
|
||||||
}
|
}
|
||||||
|
@ -93,9 +86,7 @@ func (*Service) ValidateFlags(flags *portainer.CLIFlags) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func displayDeprecationWarnings(flags *portainer.CLIFlags) {
|
func displayDeprecationWarnings(flags *portainer.CLIFlags) {
|
||||||
if *flags.NoAuth {
|
|
||||||
log.Println("Warning: the --no-auth flag is deprecated and will likely be removed in a future version of Portainer.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateEndpointURL(endpointURL string) error {
|
func validateEndpointURL(endpointURL string) error {
|
||||||
|
|
|
@ -8,7 +8,6 @@ const (
|
||||||
defaultTunnelServerPort = "8000"
|
defaultTunnelServerPort = "8000"
|
||||||
defaultDataDirectory = "/data"
|
defaultDataDirectory = "/data"
|
||||||
defaultAssetsDirectory = "./"
|
defaultAssetsDirectory = "./"
|
||||||
defaultNoAuth = "false"
|
|
||||||
defaultNoAnalytics = "false"
|
defaultNoAnalytics = "false"
|
||||||
defaultTLS = "false"
|
defaultTLS = "false"
|
||||||
defaultTLSSkipVerify = "false"
|
defaultTLSSkipVerify = "false"
|
||||||
|
|
|
@ -6,7 +6,6 @@ const (
|
||||||
defaultTunnelServerPort = "8000"
|
defaultTunnelServerPort = "8000"
|
||||||
defaultDataDirectory = "C:\\data"
|
defaultDataDirectory = "C:\\data"
|
||||||
defaultAssetsDirectory = "./"
|
defaultAssetsDirectory = "./"
|
||||||
defaultNoAuth = "false"
|
|
||||||
defaultNoAnalytics = "false"
|
defaultNoAnalytics = "false"
|
||||||
defaultTLS = "false"
|
defaultTLS = "false"
|
||||||
defaultTLSSkipVerify = "false"
|
defaultTLSSkipVerify = "false"
|
||||||
|
|
|
@ -77,15 +77,17 @@ func initSwarmStackManager(assetsPath string, dataStorePath string, signatureSer
|
||||||
return exec.NewSwarmStackManager(assetsPath, dataStorePath, signatureService, fileService, reverseTunnelService)
|
return exec.NewSwarmStackManager(assetsPath, dataStorePath, signatureService, fileService, reverseTunnelService)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initJWTService(authenticationEnabled bool) portainer.JWTService {
|
func initJWTService(dataStore portainer.DataStore) (portainer.JWTService, error) {
|
||||||
if authenticationEnabled {
|
settings, err := dataStore.Settings().Settings()
|
||||||
jwtService, err := jwt.NewService()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
return jwtService
|
|
||||||
|
jwtService, err := jwt.NewService(settings.UserSessionTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil
|
return jwtService, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func initDigitalSignatureService() portainer.DigitalSignatureService {
|
func initDigitalSignatureService() portainer.DigitalSignatureService {
|
||||||
|
@ -189,7 +191,6 @@ func loadSchedulesFromDatabase(jobScheduler portainer.JobScheduler, jobService p
|
||||||
func initStatus(flags *portainer.CLIFlags) *portainer.Status {
|
func initStatus(flags *portainer.CLIFlags) *portainer.Status {
|
||||||
return &portainer.Status{
|
return &portainer.Status{
|
||||||
Analytics: !*flags.NoAnalytics,
|
Analytics: !*flags.NoAnalytics,
|
||||||
Authentication: !*flags.NoAuth,
|
|
||||||
Version: portainer.APIVersion,
|
Version: portainer.APIVersion,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -392,7 +393,10 @@ func main() {
|
||||||
dataStore := initDataStore(*flags.Data, fileService)
|
dataStore := initDataStore(*flags.Data, fileService)
|
||||||
defer dataStore.Close()
|
defer dataStore.Close()
|
||||||
|
|
||||||
jwtService := initJWTService(!*flags.NoAuth)
|
jwtService, err := initJWTService(dataStore)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
ldapService := initLDAPService()
|
ldapService := initLDAPService()
|
||||||
|
|
||||||
|
@ -402,7 +406,7 @@ func main() {
|
||||||
|
|
||||||
digitalSignatureService := initDigitalSignatureService()
|
digitalSignatureService := initDigitalSignatureService()
|
||||||
|
|
||||||
err := initKeyPair(fileService, digitalSignatureService)
|
err = initKeyPair(fileService, digitalSignatureService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -492,9 +496,7 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !*flags.NoAuth {
|
|
||||||
go terminateIfNoAdminCreated(dataStore)
|
go terminateIfNoAdminCreated(dataStore)
|
||||||
}
|
|
||||||
|
|
||||||
err = reverseTunnelService.StartTunnelServer(*flags.TunnelAddr, *flags.TunnelPort, snapshotter)
|
err = reverseTunnelService.StartTunnelServer(*flags.TunnelAddr, *flags.TunnelPort, snapshotter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -506,7 +508,6 @@ func main() {
|
||||||
Status: applicationStatus,
|
Status: applicationStatus,
|
||||||
BindAddress: *flags.Addr,
|
BindAddress: *flags.Addr,
|
||||||
AssetsPath: *flags.Assets,
|
AssetsPath: *flags.Assets,
|
||||||
AuthDisabled: *flags.NoAuth,
|
|
||||||
DataStore: dataStore,
|
DataStore: dataStore,
|
||||||
SwarmStackManager: swarmStackManager,
|
SwarmStackManager: swarmStackManager,
|
||||||
ComposeStackManager: composeStackManager,
|
ComposeStackManager: composeStackManager,
|
||||||
|
|
|
@ -32,10 +32,6 @@ func (payload *authenticatePayload) Validate(r *http.Request) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *Handler) authenticate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
func (handler *Handler) authenticate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||||
if handler.authDisabled {
|
|
||||||
return &httperror.HandlerError{http.StatusServiceUnavailable, "Cannot authenticate user. Portainer was started with the --no-auth flag", ErrAuthDisabled}
|
|
||||||
}
|
|
||||||
|
|
||||||
var payload authenticatePayload
|
var payload authenticatePayload
|
||||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -10,16 +10,9 @@ import (
|
||||||
"github.com/portainer/portainer/api/http/security"
|
"github.com/portainer/portainer/api/http/security"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// ErrAuthDisabled is an error raised when trying to access the authentication endpoints
|
|
||||||
// when the server has been started with the --no-auth flag
|
|
||||||
ErrAuthDisabled = portainer.Error("Authentication is disabled")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handler is the HTTP handler used to handle authentication operations.
|
// Handler is the HTTP handler used to handle authentication operations.
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
*mux.Router
|
*mux.Router
|
||||||
authDisabled bool
|
|
||||||
DataStore portainer.DataStore
|
DataStore portainer.DataStore
|
||||||
CryptoService portainer.CryptoService
|
CryptoService portainer.CryptoService
|
||||||
JWTService portainer.JWTService
|
JWTService portainer.JWTService
|
||||||
|
@ -29,10 +22,9 @@ type Handler struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHandler creates a handler to manage authentication operations.
|
// NewHandler creates a handler to manage authentication operations.
|
||||||
func NewHandler(bouncer *security.RequestBouncer, rateLimiter *security.RateLimiter, authDisabled bool) *Handler {
|
func NewHandler(bouncer *security.RequestBouncer, rateLimiter *security.RateLimiter) *Handler {
|
||||||
h := &Handler{
|
h := &Handler{
|
||||||
Router: mux.NewRouter(),
|
Router: mux.NewRouter(),
|
||||||
authDisabled: authDisabled,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h.Handle("/auth/oauth/validate",
|
h.Handle("/auth/oauth/validate",
|
||||||
|
|
|
@ -17,11 +17,12 @@ func hideFields(settings *portainer.Settings) {
|
||||||
// Handler is the HTTP handler used to handle settings operations.
|
// Handler is the HTTP handler used to handle settings operations.
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
*mux.Router
|
*mux.Router
|
||||||
|
AuthorizationService *portainer.AuthorizationService
|
||||||
DataStore portainer.DataStore
|
DataStore portainer.DataStore
|
||||||
LDAPService portainer.LDAPService
|
|
||||||
FileService portainer.FileService
|
FileService portainer.FileService
|
||||||
JobScheduler portainer.JobScheduler
|
JobScheduler portainer.JobScheduler
|
||||||
AuthorizationService *portainer.AuthorizationService
|
JWTService portainer.JWTService
|
||||||
|
LDAPService portainer.LDAPService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHandler creates a handler to manage settings operations.
|
// NewHandler creates a handler to manage settings operations.
|
||||||
|
|
|
@ -2,6 +2,7 @@ package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/asaskevich/govalidator"
|
"github.com/asaskevich/govalidator"
|
||||||
httperror "github.com/portainer/libhttp/error"
|
httperror "github.com/portainer/libhttp/error"
|
||||||
|
@ -25,6 +26,7 @@ type settingsUpdatePayload struct {
|
||||||
TemplatesURL *string
|
TemplatesURL *string
|
||||||
EdgeAgentCheckinInterval *int
|
EdgeAgentCheckinInterval *int
|
||||||
EnableEdgeComputeFeatures *bool
|
EnableEdgeComputeFeatures *bool
|
||||||
|
UserSessionTimeout *string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
|
func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
|
||||||
|
@ -37,6 +39,13 @@ func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
|
||||||
if payload.TemplatesURL != nil && *payload.TemplatesURL != "" && !govalidator.IsURL(*payload.TemplatesURL) {
|
if payload.TemplatesURL != nil && *payload.TemplatesURL != "" && !govalidator.IsURL(*payload.TemplatesURL) {
|
||||||
return portainer.Error("Invalid external templates URL. Must correspond to a valid URL format")
|
return portainer.Error("Invalid external templates URL. Must correspond to a valid URL format")
|
||||||
}
|
}
|
||||||
|
if payload.UserSessionTimeout != nil {
|
||||||
|
_, err := time.ParseDuration(*payload.UserSessionTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return portainer.Error("Invalid user session timeout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,6 +134,14 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
|
||||||
settings.EdgeAgentCheckinInterval = *payload.EdgeAgentCheckinInterval
|
settings.EdgeAgentCheckinInterval = *payload.EdgeAgentCheckinInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if payload.UserSessionTimeout != nil {
|
||||||
|
settings.UserSessionTimeout = *payload.UserSessionTimeout
|
||||||
|
|
||||||
|
userSessionDuration, _ := time.ParseDuration(*payload.UserSessionTimeout)
|
||||||
|
|
||||||
|
handler.JWTService.SetUserSessionDuration(userSessionDuration)
|
||||||
|
}
|
||||||
|
|
||||||
tlsError := handler.updateTLS(settings)
|
tlsError := handler.updateTLS(settings)
|
||||||
if tlsError != nil {
|
if tlsError != nil {
|
||||||
return tlsError
|
return tlsError
|
||||||
|
|
|
@ -16,7 +16,6 @@ type (
|
||||||
dataStore portainer.DataStore
|
dataStore portainer.DataStore
|
||||||
jwtService portainer.JWTService
|
jwtService portainer.JWTService
|
||||||
rbacExtensionClient *rbacExtensionClient
|
rbacExtensionClient *rbacExtensionClient
|
||||||
authDisabled bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestrictedRequestContext is a data structure containing information
|
// RestrictedRequestContext is a data structure containing information
|
||||||
|
@ -30,12 +29,11 @@ type (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewRequestBouncer initializes a new RequestBouncer
|
// NewRequestBouncer initializes a new RequestBouncer
|
||||||
func NewRequestBouncer(dataStore portainer.DataStore, jwtService portainer.JWTService, authenticationDisabled bool, rbacExtensionURL string) *RequestBouncer {
|
func NewRequestBouncer(dataStore portainer.DataStore, jwtService portainer.JWTService, rbacExtensionURL string) *RequestBouncer {
|
||||||
return &RequestBouncer{
|
return &RequestBouncer{
|
||||||
dataStore: dataStore,
|
dataStore: dataStore,
|
||||||
jwtService: jwtService,
|
jwtService: jwtService,
|
||||||
rbacExtensionClient: newRBACExtensionClient(rbacExtensionURL),
|
rbacExtensionClient: newRBACExtensionClient(rbacExtensionURL),
|
||||||
authDisabled: authenticationDisabled,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,7 +287,6 @@ func (bouncer *RequestBouncer) mwUpgradeToRestrictedRequest(next http.Handler) h
|
||||||
func (bouncer *RequestBouncer) mwCheckAuthentication(next http.Handler) http.Handler {
|
func (bouncer *RequestBouncer) mwCheckAuthentication(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
var tokenData *portainer.TokenData
|
var tokenData *portainer.TokenData
|
||||||
if !bouncer.authDisabled {
|
|
||||||
var token string
|
var token string
|
||||||
|
|
||||||
// Optionally, token might be set via the "token" query parameter.
|
// Optionally, token might be set via the "token" query parameter.
|
||||||
|
@ -323,11 +320,6 @@ func (bouncer *RequestBouncer) mwCheckAuthentication(next http.Handler) http.Han
|
||||||
httperror.WriteError(w, http.StatusInternalServerError, "Unable to retrieve user details from the database", err)
|
httperror.WriteError(w, http.StatusInternalServerError, "Unable to retrieve user details from the database", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
tokenData = &portainer.TokenData{
|
|
||||||
Role: portainer.AdministratorRole,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := storeTokenData(r, tokenData)
|
ctx := storeTokenData(r, tokenData)
|
||||||
next.ServeHTTP(w, r.WithContext(ctx))
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
|
|
@ -47,7 +47,6 @@ import (
|
||||||
type Server struct {
|
type Server struct {
|
||||||
BindAddress string
|
BindAddress string
|
||||||
AssetsPath string
|
AssetsPath string
|
||||||
AuthDisabled bool
|
|
||||||
Status *portainer.Status
|
Status *portainer.Status
|
||||||
ReverseTunnelService portainer.ReverseTunnelService
|
ReverseTunnelService portainer.ReverseTunnelService
|
||||||
ExtensionManager portainer.ExtensionManager
|
ExtensionManager portainer.ExtensionManager
|
||||||
|
@ -77,11 +76,11 @@ func (server *Server) Start() error {
|
||||||
authorizationService := portainer.NewAuthorizationService(server.DataStore)
|
authorizationService := portainer.NewAuthorizationService(server.DataStore)
|
||||||
|
|
||||||
rbacExtensionURL := proxyManager.GetExtensionURL(portainer.RBACExtension)
|
rbacExtensionURL := proxyManager.GetExtensionURL(portainer.RBACExtension)
|
||||||
requestBouncer := security.NewRequestBouncer(server.DataStore, server.JWTService, server.AuthDisabled, rbacExtensionURL)
|
requestBouncer := security.NewRequestBouncer(server.DataStore, server.JWTService, rbacExtensionURL)
|
||||||
|
|
||||||
rateLimiter := security.NewRateLimiter(10, 1*time.Second, 1*time.Hour)
|
rateLimiter := security.NewRateLimiter(10, 1*time.Second, 1*time.Hour)
|
||||||
|
|
||||||
var authHandler = auth.NewHandler(requestBouncer, rateLimiter, server.AuthDisabled)
|
var authHandler = auth.NewHandler(requestBouncer, rateLimiter)
|
||||||
authHandler.DataStore = server.DataStore
|
authHandler.DataStore = server.DataStore
|
||||||
authHandler.CryptoService = server.CryptoService
|
authHandler.CryptoService = server.CryptoService
|
||||||
authHandler.JWTService = server.JWTService
|
authHandler.JWTService = server.JWTService
|
||||||
|
@ -153,11 +152,12 @@ func (server *Server) Start() error {
|
||||||
schedulesHandler.ReverseTunnelService = server.ReverseTunnelService
|
schedulesHandler.ReverseTunnelService = server.ReverseTunnelService
|
||||||
|
|
||||||
var settingsHandler = settings.NewHandler(requestBouncer)
|
var settingsHandler = settings.NewHandler(requestBouncer)
|
||||||
|
settingsHandler.AuthorizationService = authorizationService
|
||||||
settingsHandler.DataStore = server.DataStore
|
settingsHandler.DataStore = server.DataStore
|
||||||
settingsHandler.LDAPService = server.LDAPService
|
|
||||||
settingsHandler.FileService = server.FileService
|
settingsHandler.FileService = server.FileService
|
||||||
settingsHandler.JobScheduler = server.JobScheduler
|
settingsHandler.JobScheduler = server.JobScheduler
|
||||||
settingsHandler.AuthorizationService = authorizationService
|
settingsHandler.JWTService = server.JWTService
|
||||||
|
settingsHandler.LDAPService = server.LDAPService
|
||||||
|
|
||||||
var stackHandler = stacks.NewHandler(requestBouncer)
|
var stackHandler = stacks.NewHandler(requestBouncer)
|
||||||
stackHandler.DataStore = server.DataStore
|
stackHandler.DataStore = server.DataStore
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
// Service represents a service for managing JWT tokens.
|
// Service represents a service for managing JWT tokens.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
secret []byte
|
secret []byte
|
||||||
|
userSessionTimeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
type claims struct {
|
type claims struct {
|
||||||
|
@ -23,20 +24,27 @@ type claims struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService initializes a new service. It will generate a random key that will be used to sign JWT tokens.
|
// NewService initializes a new service. It will generate a random key that will be used to sign JWT tokens.
|
||||||
func NewService() (*Service, error) {
|
func NewService(userSessionDuration string) (*Service, error) {
|
||||||
|
userSessionTimeout, err := time.ParseDuration(userSessionDuration)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
secret := securecookie.GenerateRandomKey(32)
|
secret := securecookie.GenerateRandomKey(32)
|
||||||
if secret == nil {
|
if secret == nil {
|
||||||
return nil, portainer.ErrSecretGeneration
|
return nil, portainer.ErrSecretGeneration
|
||||||
}
|
}
|
||||||
|
|
||||||
service := &Service{
|
service := &Service{
|
||||||
secret,
|
secret,
|
||||||
|
userSessionTimeout,
|
||||||
}
|
}
|
||||||
return service, nil
|
return service, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateToken generates a new JWT token.
|
// GenerateToken generates a new JWT token.
|
||||||
func (service *Service) GenerateToken(data *portainer.TokenData) (string, error) {
|
func (service *Service) GenerateToken(data *portainer.TokenData) (string, error) {
|
||||||
expireToken := time.Now().Add(time.Hour * 8).Unix()
|
expireToken := time.Now().Add(service.userSessionTimeout).Unix()
|
||||||
cl := claims{
|
cl := claims{
|
||||||
UserID: int(data.ID),
|
UserID: int(data.ID),
|
||||||
Username: data.Username,
|
Username: data.Username,
|
||||||
|
@ -77,3 +85,8 @@ func (service *Service) ParseAndVerifyToken(token string) (*portainer.TokenData,
|
||||||
|
|
||||||
return nil, portainer.ErrInvalidJWTToken
|
return nil, portainer.ErrInvalidJWTToken
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetUserSessionDuration sets the user session duration
|
||||||
|
func (service *Service) SetUserSessionDuration(userSessionDuration time.Duration) {
|
||||||
|
service.userSessionTimeout = userSessionDuration
|
||||||
|
}
|
||||||
|
|
|
@ -46,7 +46,6 @@ type (
|
||||||
EndpointURL *string
|
EndpointURL *string
|
||||||
Labels *[]Pair
|
Labels *[]Pair
|
||||||
Logo *string
|
Logo *string
|
||||||
NoAuth *bool
|
|
||||||
NoAnalytics *bool
|
NoAnalytics *bool
|
||||||
Templates *string
|
Templates *string
|
||||||
TLS *bool
|
TLS *bool
|
||||||
|
@ -459,6 +458,7 @@ type (
|
||||||
EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
|
EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
|
||||||
EdgeAgentCheckinInterval int `json:"EdgeAgentCheckinInterval"`
|
EdgeAgentCheckinInterval int `json:"EdgeAgentCheckinInterval"`
|
||||||
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
|
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
|
||||||
|
UserSessionTimeout string `json:"UserSessionTimeout"`
|
||||||
|
|
||||||
// Deprecated fields
|
// Deprecated fields
|
||||||
DisplayDonationHeader bool
|
DisplayDonationHeader bool
|
||||||
|
@ -517,7 +517,6 @@ type (
|
||||||
|
|
||||||
// Status represents the application status
|
// Status represents the application status
|
||||||
Status struct {
|
Status struct {
|
||||||
Authentication bool `json:"Authentication"`
|
|
||||||
Analytics bool `json:"Analytics"`
|
Analytics bool `json:"Analytics"`
|
||||||
Version string `json:"Version"`
|
Version string `json:"Version"`
|
||||||
}
|
}
|
||||||
|
@ -842,6 +841,7 @@ type (
|
||||||
JWTService interface {
|
JWTService interface {
|
||||||
GenerateToken(data *TokenData) (string, error)
|
GenerateToken(data *TokenData) (string, error)
|
||||||
ParseAndVerifyToken(token string) (*TokenData, error)
|
ParseAndVerifyToken(token string) (*TokenData, error)
|
||||||
|
SetUserSessionDuration(userSessionDuration time.Duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LDAPService represents a service used to authenticate users against a LDAP/AD
|
// LDAPService represents a service used to authenticate users against a LDAP/AD
|
||||||
|
@ -1057,6 +1057,8 @@ const (
|
||||||
LocalExtensionManifestFile = "/extensions.json"
|
LocalExtensionManifestFile = "/extensions.json"
|
||||||
// DefaultTemplatesURL represents the URL to the official templates supported by Portainer
|
// DefaultTemplatesURL represents the URL to the official templates supported by Portainer
|
||||||
DefaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates-2.0.json"
|
DefaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates-2.0.json"
|
||||||
|
// DefaultUserSessionTimeout represents the default timeout after which the user session is cleared
|
||||||
|
DefaultUserSessionTimeout = "8h"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -90,7 +90,7 @@
|
||||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && $ctrl.state.reverseOrder"></i>
|
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && $ctrl.state.reverseOrder"></i>
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th ng-if="$ctrl.showOwnershipColumn">
|
<th>
|
||||||
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
|
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
|
||||||
Ownership
|
Ownership
|
||||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
|
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
|
||||||
|
@ -112,7 +112,7 @@
|
||||||
<a ui-sref="docker.configs.config({id: item.Id})">{{ item.Name }}</a>
|
<a ui-sref="docker.configs.config({id: item.Id})">{{ item.Name }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ item.CreatedAt | getisodate }}</td>
|
<td>{{ item.CreatedAt | getisodate }}</td>
|
||||||
<td ng-if="$ctrl.showOwnershipColumn">
|
<td>
|
||||||
<span>
|
<span>
|
||||||
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||||
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = $ctrl.RCO.ADMINISTRATORS }}
|
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = $ctrl.RCO.ADMINISTRATORS }}
|
||||||
|
|
|
@ -8,7 +8,6 @@ angular.module('portainer.docker').component('configsDatatable', {
|
||||||
tableKey: '@',
|
tableKey: '@',
|
||||||
orderBy: '@',
|
orderBy: '@',
|
||||||
reverseOrder: '<',
|
reverseOrder: '<',
|
||||||
showOwnershipColumn: '<',
|
|
||||||
removeAction: '<',
|
removeAction: '<',
|
||||||
refreshCallback: '<',
|
refreshCallback: '<',
|
||||||
},
|
},
|
||||||
|
|
|
@ -246,7 +246,7 @@
|
||||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Ports' && $ctrl.state.reverseOrder"></i>
|
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Ports' && $ctrl.state.reverseOrder"></i>
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th ng-if="$ctrl.showOwnershipColumn" ng-show="$ctrl.columnVisibility.columns.ownership.display">
|
<th ng-show="$ctrl.columnVisibility.columns.ownership.display">
|
||||||
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
|
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
|
||||||
Ownership
|
Ownership
|
||||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
|
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
|
||||||
|
@ -319,7 +319,7 @@
|
||||||
</a>
|
</a>
|
||||||
<span ng-if="item.Ports.length == 0">-</span>
|
<span ng-if="item.Ports.length == 0">-</span>
|
||||||
</td>
|
</td>
|
||||||
<td ng-if="$ctrl.showOwnershipColumn" ng-show="$ctrl.columnVisibility.columns.ownership.display">
|
<td ng-show="$ctrl.columnVisibility.columns.ownership.display">
|
||||||
<span>
|
<span>
|
||||||
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||||
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = $ctrl.RCO.ADMINISTRATORS }}
|
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = $ctrl.RCO.ADMINISTRATORS }}
|
||||||
|
|
|
@ -8,7 +8,6 @@ angular.module('portainer.docker').component('containersDatatable', {
|
||||||
tableKey: '@',
|
tableKey: '@',
|
||||||
orderBy: '@',
|
orderBy: '@',
|
||||||
reverseOrder: '<',
|
reverseOrder: '<',
|
||||||
showOwnershipColumn: '<',
|
|
||||||
showHostColumn: '<',
|
showHostColumn: '<',
|
||||||
showAddAction: '<',
|
showAddAction: '<',
|
||||||
offlineMode: '<',
|
offlineMode: '<',
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
<td>{{ item.IPAM.IPV6Configs[0].Subnet ? item.IPAM.IPV6Configs[0].Subnet : '-' }}</td>
|
<td>{{ item.IPAM.IPV6Configs[0].Subnet ? item.IPAM.IPV6Configs[0].Subnet : '-' }}</td>
|
||||||
<td>{{ item.IPAM.IPV6Configs[0].Gateway ? item.IPAM.IPV6Configs[0].Gateway : '-' }}</td>
|
<td>{{ item.IPAM.IPV6Configs[0].Gateway ? item.IPAM.IPV6Configs[0].Gateway : '-' }}</td>
|
||||||
<td ng-if="parentCtrl.showHostColumn">{{ item.NodeName ? item.NodeName : '-' }}</td>
|
<td ng-if="parentCtrl.showHostColumn">{{ item.NodeName ? item.NodeName : '-' }}</td>
|
||||||
<td ng-if="parentCtrl.showOwnershipColumn">
|
<td>
|
||||||
<span>
|
<span>
|
||||||
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||||
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = RCO.ADMINISTRATORS }}
|
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = RCO.ADMINISTRATORS }}
|
||||||
|
|
|
@ -151,7 +151,7 @@
|
||||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'NodeName' && $ctrl.state.reverseOrder"></i>
|
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'NodeName' && $ctrl.state.reverseOrder"></i>
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th ng-if="$ctrl.showOwnershipColumn">
|
<th>
|
||||||
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
|
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
|
||||||
Ownership
|
Ownership
|
||||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
|
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
|
||||||
|
|
|
@ -8,7 +8,6 @@ angular.module('portainer.docker').component('networksDatatable', {
|
||||||
tableKey: '@',
|
tableKey: '@',
|
||||||
orderBy: '@',
|
orderBy: '@',
|
||||||
reverseOrder: '<',
|
reverseOrder: '<',
|
||||||
showOwnershipColumn: '<',
|
|
||||||
showHostColumn: '<',
|
showHostColumn: '<',
|
||||||
removeAction: '<',
|
removeAction: '<',
|
||||||
offlineMode: '<',
|
offlineMode: '<',
|
||||||
|
|
|
@ -90,7 +90,7 @@
|
||||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && $ctrl.state.reverseOrder"></i>
|
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && $ctrl.state.reverseOrder"></i>
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th ng-if="$ctrl.showOwnershipColumn">
|
<th>
|
||||||
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
|
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
|
||||||
Ownership
|
Ownership
|
||||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
|
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
|
||||||
|
@ -112,7 +112,7 @@
|
||||||
<a ui-sref="docker.secrets.secret({id: item.Id})">{{ item.Name }}</a>
|
<a ui-sref="docker.secrets.secret({id: item.Id})">{{ item.Name }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ item.CreatedAt | getisodate }}</td>
|
<td>{{ item.CreatedAt | getisodate }}</td>
|
||||||
<td ng-if="$ctrl.showOwnershipColumn">
|
<td>
|
||||||
<span>
|
<span>
|
||||||
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||||
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = $ctrl.RCO.ADMINISTRATORS }}
|
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = $ctrl.RCO.ADMINISTRATORS }}
|
||||||
|
|
|
@ -8,7 +8,6 @@ angular.module('portainer.docker').component('secretsDatatable', {
|
||||||
tableKey: '@',
|
tableKey: '@',
|
||||||
orderBy: '@',
|
orderBy: '@',
|
||||||
reverseOrder: '<',
|
reverseOrder: '<',
|
||||||
showOwnershipColumn: '<',
|
|
||||||
removeAction: '<',
|
removeAction: '<',
|
||||||
refreshCallback: '<',
|
refreshCallback: '<',
|
||||||
},
|
},
|
||||||
|
|
|
@ -115,7 +115,7 @@
|
||||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'UpdatedAt' && $ctrl.state.reverseOrder"></i>
|
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'UpdatedAt' && $ctrl.state.reverseOrder"></i>
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th ng-if="$ctrl.showOwnershipColumn">
|
<th>
|
||||||
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
|
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
|
||||||
Ownership
|
Ownership
|
||||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
|
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
|
||||||
|
@ -180,7 +180,7 @@
|
||||||
<span ng-if="!item.Ports || item.Ports.length === 0">-</span>
|
<span ng-if="!item.Ports || item.Ports.length === 0">-</span>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ item.UpdatedAt | getisodate }}</td>
|
<td>{{ item.UpdatedAt | getisodate }}</td>
|
||||||
<td ng-if="$ctrl.showOwnershipColumn">
|
<td>
|
||||||
<span>
|
<span>
|
||||||
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||||
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = $ctrl.RCO.ADMINISTRATORS }}
|
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = $ctrl.RCO.ADMINISTRATORS }}
|
||||||
|
|
|
@ -10,7 +10,6 @@ angular.module('portainer.docker').component('servicesDatatable', {
|
||||||
reverseOrder: '<',
|
reverseOrder: '<',
|
||||||
nodes: '<',
|
nodes: '<',
|
||||||
agentProxy: '<',
|
agentProxy: '<',
|
||||||
showOwnershipColumn: '<',
|
|
||||||
showUpdateAction: '<',
|
showUpdateAction: '<',
|
||||||
showAddAction: '<',
|
showAddAction: '<',
|
||||||
showStackColumn: '<',
|
showStackColumn: '<',
|
||||||
|
|
|
@ -142,7 +142,7 @@
|
||||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'NodeName' && $ctrl.state.reverseOrder"></i>
|
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'NodeName' && $ctrl.state.reverseOrder"></i>
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th ng-if="$ctrl.showOwnershipColumn">
|
<th>
|
||||||
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
|
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
|
||||||
Ownership
|
Ownership
|
||||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
|
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
|
||||||
|
@ -177,7 +177,7 @@
|
||||||
<td>{{ item.Mountpoint | truncatelr }}</td>
|
<td>{{ item.Mountpoint | truncatelr }}</td>
|
||||||
<td>{{ item.CreatedAt | getisodate }}</td>
|
<td>{{ item.CreatedAt | getisodate }}</td>
|
||||||
<td ng-if="$ctrl.showHostColumn">{{ item.NodeName ? item.NodeName : '-' }}</td>
|
<td ng-if="$ctrl.showHostColumn">{{ item.NodeName ? item.NodeName : '-' }}</td>
|
||||||
<td ng-if="$ctrl.showOwnershipColumn">
|
<td>
|
||||||
<span>
|
<span>
|
||||||
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||||
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = $ctrl.RCO.ADMINISTRATORS }}
|
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = $ctrl.RCO.ADMINISTRATORS }}
|
||||||
|
|
|
@ -8,7 +8,6 @@ angular.module('portainer.docker').component('volumesDatatable', {
|
||||||
tableKey: '@',
|
tableKey: '@',
|
||||||
orderBy: '@',
|
orderBy: '@',
|
||||||
reverseOrder: '<',
|
reverseOrder: '<',
|
||||||
showOwnershipColumn: '<',
|
|
||||||
showHostColumn: '<',
|
showHostColumn: '<',
|
||||||
removeAction: '<',
|
removeAction: '<',
|
||||||
showBrowseAction: '<',
|
showBrowseAction: '<',
|
||||||
|
|
|
@ -74,7 +74,7 @@
|
||||||
state="$ctrl.data.DatatableState"
|
state="$ctrl.data.DatatableState"
|
||||||
order-by="Hostname"
|
order-by="Hostname"
|
||||||
show-ip-address-column="$ctrl.applicationState.endpoint.apiVersion >= 1.25"
|
show-ip-address-column="$ctrl.applicationState.endpoint.apiVersion >= 1.25"
|
||||||
access-to-node-details="!$ctrl.applicationState.application.authentication || $ctrl.isAdmin"
|
access-to-node-details="$ctrl.isAdmin"
|
||||||
name="node_selector"
|
name="node_selector"
|
||||||
ng-model="tmp"
|
ng-model="tmp"
|
||||||
ng-required="$ctrl.requiredNodeSelection()"
|
ng-required="$ctrl.requiredNodeSelection()"
|
||||||
|
|
|
@ -23,10 +23,9 @@ angular.module('portainer.docker').controller('NetworkMacvlanFormController', [
|
||||||
};
|
};
|
||||||
|
|
||||||
function initComponent() {
|
function initComponent() {
|
||||||
if (StateManager.getState().application.authentication) {
|
|
||||||
var isAdmin = Authentication.isAdmin();
|
var isAdmin = Authentication.isAdmin();
|
||||||
ctrl.isAdmin = isAdmin;
|
ctrl.isAdmin = isAdmin;
|
||||||
}
|
|
||||||
var provider = ctrl.applicationState.endpoint.mode.provider;
|
var provider = ctrl.applicationState.endpoint.mode.provider;
|
||||||
var apiVersion = ctrl.applicationState.endpoint.apiVersion;
|
var apiVersion = ctrl.applicationState.endpoint.apiVersion;
|
||||||
$q.all({
|
$q.all({
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
dataset="ctrl.configs"
|
dataset="ctrl.configs"
|
||||||
table-key="configs"
|
table-key="configs"
|
||||||
order-by="Name"
|
order-by="Name"
|
||||||
show-ownership-column="applicationState.application.authentication"
|
|
||||||
remove-action="ctrl.removeAction"
|
remove-action="ctrl.removeAction"
|
||||||
refresh-callback="ctrl.getConfigs"
|
refresh-callback="ctrl.getConfigs"
|
||||||
></configs-datatable>
|
></configs-datatable>
|
||||||
|
|
|
@ -57,7 +57,7 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- !labels-->
|
<!-- !labels-->
|
||||||
<!-- access-control -->
|
<!-- access-control -->
|
||||||
<por-access-control-form form-data="ctrl.formValues.AccessControlData" ng-if="applicationState.application.authentication"></por-access-control-form>
|
<por-access-control-form form-data="ctrl.formValues.AccessControlData"></por-access-control-form>
|
||||||
<!-- !access-control -->
|
<!-- !access-control -->
|
||||||
<!-- actions -->
|
<!-- actions -->
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
|
|
|
@ -59,8 +59,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- access-control-panel -->
|
<!-- access-control-panel -->
|
||||||
<por-access-control-panel ng-if="config && applicationState.application.authentication" resource-id="config.Id" resource-control="config.ResourceControl" resource-type="'config'">
|
<por-access-control-panel ng-if="config" resource-id="config.Id" resource-control="config.ResourceControl" resource-type="'config'"> </por-access-control-panel>
|
||||||
</por-access-control-panel>
|
|
||||||
<!-- !access-control-panel -->
|
<!-- !access-control-panel -->
|
||||||
|
|
||||||
<div class="row" ng-if="config">
|
<div class="row" ng-if="config">
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
dataset="containers"
|
dataset="containers"
|
||||||
table-key="containers"
|
table-key="containers"
|
||||||
order-by="Status"
|
order-by="Status"
|
||||||
show-ownership-column="applicationState.application.authentication"
|
|
||||||
show-host-column="applicationState.endpoint.mode.agentProxy && applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'"
|
show-host-column="applicationState.endpoint.mode.agentProxy && applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'"
|
||||||
show-add-action="true"
|
show-add-action="true"
|
||||||
offline-mode="offlineMode"
|
offline-mode="offlineMode"
|
||||||
|
|
|
@ -126,11 +126,7 @@
|
||||||
<!-- !node-selection -->
|
<!-- !node-selection -->
|
||||||
</div>
|
</div>
|
||||||
<!-- access-control -->
|
<!-- access-control -->
|
||||||
<por-access-control-form
|
<por-access-control-form form-data="formValues.AccessControlData" resource-control="fromContainer.ResourceControl" ng-if="fromContainer"></por-access-control-form>
|
||||||
form-data="formValues.AccessControlData"
|
|
||||||
resource-control="fromContainer.ResourceControl"
|
|
||||||
ng-if="applicationState.application.authentication && fromContainer"
|
|
||||||
></por-access-control-form>
|
|
||||||
<!-- !access-control -->
|
<!-- !access-control -->
|
||||||
<!-- actions -->
|
<!-- actions -->
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
|
|
|
@ -135,13 +135,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- access-control-panel -->
|
<!-- access-control-panel -->
|
||||||
<por-access-control-panel
|
<por-access-control-panel ng-if="container" resource-id="container.Id" resource-control="container.ResourceControl" resource-type="'container'"> </por-access-control-panel>
|
||||||
ng-if="container && applicationState.application.authentication"
|
|
||||||
resource-id="container.Id"
|
|
||||||
resource-control="container.ResourceControl"
|
|
||||||
resource-type="'container'"
|
|
||||||
>
|
|
||||||
</por-access-control-panel>
|
|
||||||
<!-- !access-control-panel -->
|
<!-- !access-control-panel -->
|
||||||
|
|
||||||
<div ng-if="container.State.Health" class="row">
|
<div ng-if="container.State.Health" class="row">
|
||||||
|
|
|
@ -194,7 +194,7 @@
|
||||||
<!-- !node-selection -->
|
<!-- !node-selection -->
|
||||||
</div>
|
</div>
|
||||||
<!-- access-control -->
|
<!-- access-control -->
|
||||||
<por-access-control-form form-data="formValues.AccessControlData" ng-if="applicationState.application.authentication"></por-access-control-form>
|
<por-access-control-form form-data="formValues.AccessControlData"></por-access-control-form>
|
||||||
<!-- !access-control -->
|
<!-- !access-control -->
|
||||||
<!-- actions -->
|
<!-- actions -->
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
|
|
|
@ -66,7 +66,7 @@
|
||||||
|
|
||||||
<!-- access-control-panel -->
|
<!-- access-control-panel -->
|
||||||
<por-access-control-panel
|
<por-access-control-panel
|
||||||
ng-if="network && applicationState.application.authentication"
|
ng-if="network"
|
||||||
resource-id="network.Id"
|
resource-id="network.Id"
|
||||||
resource-control="network.ResourceControl"
|
resource-control="network.ResourceControl"
|
||||||
resource-type="'network'"
|
resource-type="'network'"
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
table-key="networks"
|
table-key="networks"
|
||||||
order-by="Name"
|
order-by="Name"
|
||||||
remove-action="removeAction"
|
remove-action="removeAction"
|
||||||
show-ownership-column="applicationState.application.authentication"
|
|
||||||
show-host-column="applicationState.endpoint.mode.agentProxy && applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'"
|
show-host-column="applicationState.endpoint.mode.agentProxy && applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'"
|
||||||
offline-mode="offlineMode"
|
offline-mode="offlineMode"
|
||||||
refresh-callback="getNetworks"
|
refresh-callback="getNetworks"
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- !labels-->
|
<!-- !labels-->
|
||||||
<!-- access-control -->
|
<!-- access-control -->
|
||||||
<por-access-control-form form-data="formValues.AccessControlData" ng-if="applicationState.application.authentication"></por-access-control-form>
|
<por-access-control-form form-data="formValues.AccessControlData"></por-access-control-form>
|
||||||
<!-- !access-control -->
|
<!-- !access-control -->
|
||||||
<!-- actions -->
|
<!-- actions -->
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
|
|
|
@ -56,6 +56,5 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- access-control-panel -->
|
<!-- access-control-panel -->
|
||||||
<por-access-control-panel ng-if="secret && applicationState.application.authentication" resource-id="secret.Id" resource-control="secret.ResourceControl" resource-type="'secret'">
|
<por-access-control-panel ng-if="secret" resource-id="secret.Id" resource-control="secret.ResourceControl" resource-type="'secret'"> </por-access-control-panel>
|
||||||
</por-access-control-panel>
|
|
||||||
<!-- !access-control-panel -->
|
<!-- !access-control-panel -->
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
dataset="secrets"
|
dataset="secrets"
|
||||||
table-key="secrets"
|
table-key="secrets"
|
||||||
order-by="Name"
|
order-by="Name"
|
||||||
show-ownership-column="applicationState.application.authentication"
|
|
||||||
remove-action="removeAction"
|
remove-action="removeAction"
|
||||||
refresh-callback="getSecrets"
|
refresh-callback="getSecrets"
|
||||||
></secrets-datatable>
|
></secrets-datatable>
|
||||||
|
|
|
@ -112,7 +112,7 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- !create-webhook -->
|
<!-- !create-webhook -->
|
||||||
<!-- access-control -->
|
<!-- access-control -->
|
||||||
<por-access-control-form form-data="formValues.AccessControlData" ng-if="applicationState.application.authentication"></por-access-control-form>
|
<por-access-control-form form-data="formValues.AccessControlData"></por-access-control-form>
|
||||||
<!-- !access-control -->
|
<!-- !access-control -->
|
||||||
<!-- actions -->
|
<!-- actions -->
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
|
|
|
@ -99,7 +99,6 @@
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<p class="small text-muted" authorization="DockerServiceUpdate">
|
<p class="small text-muted" authorization="DockerServiceUpdate">
|
||||||
Note: you can only rollback one level of changes. Clicking the rollback button without making a new change will undo your previous rollback </p
|
Note: you can only rollback one level of changes. Clicking the rollback button without making a new change will undo your previous rollback </p
|
||||||
|
|
||||||
><p>
|
><p>
|
||||||
<a
|
<a
|
||||||
authorization="DockerServiceLogs"
|
authorization="DockerServiceLogs"
|
||||||
|
@ -199,13 +198,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- access-control-panel -->
|
<!-- access-control-panel -->
|
||||||
<por-access-control-panel
|
<por-access-control-panel ng-if="service" resource-id="service.Id" resource-control="service.ResourceControl" resource-type="'service'"> </por-access-control-panel>
|
||||||
ng-if="service && applicationState.application.authentication"
|
|
||||||
resource-id="service.Id"
|
|
||||||
resource-control="service.ResourceControl"
|
|
||||||
resource-type="'service'"
|
|
||||||
>
|
|
||||||
</por-access-control-panel>
|
|
||||||
<!-- !access-control-panel -->
|
<!-- !access-control-panel -->
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
order-by="Name"
|
order-by="Name"
|
||||||
nodes="nodes"
|
nodes="nodes"
|
||||||
agent-proxy="applicationState.endpoint.mode.agentProxy"
|
agent-proxy="applicationState.endpoint.mode.agentProxy"
|
||||||
show-ownership-column="applicationState.application.authentication"
|
|
||||||
show-update-action="applicationState.endpoint.apiVersion >= 1.25"
|
show-update-action="applicationState.endpoint.apiVersion >= 1.25"
|
||||||
show-task-logs-button="applicationState.endpoint.apiVersion >= 1.30"
|
show-task-logs-button="applicationState.endpoint.apiVersion >= 1.30"
|
||||||
show-add-action="true"
|
show-add-action="true"
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
table-key="nodes"
|
table-key="nodes"
|
||||||
order-by="Hostname"
|
order-by="Hostname"
|
||||||
show-ip-address-column="applicationState.endpoint.apiVersion >= 1.25"
|
show-ip-address-column="applicationState.endpoint.apiVersion >= 1.25"
|
||||||
access-to-node-details="!applicationState.application.authentication || isAdmin"
|
access-to-node-details="isAdmin"
|
||||||
refresh-callback="getNodes"
|
refresh-callback="getNodes"
|
||||||
></nodes-datatable>
|
></nodes-datatable>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -82,9 +82,7 @@ angular.module('portainer.docker').controller('SwarmController', [
|
||||||
}
|
}
|
||||||
|
|
||||||
function initView() {
|
function initView() {
|
||||||
if (StateManager.getState().application.authentication) {
|
|
||||||
$scope.isAdmin = Authentication.isAdmin();
|
$scope.isAdmin = Authentication.isAdmin();
|
||||||
}
|
|
||||||
|
|
||||||
var provider = $scope.applicationState.endpoint.mode.provider;
|
var provider = $scope.applicationState.endpoint.mode.provider;
|
||||||
$q.all({
|
$q.all({
|
||||||
|
|
|
@ -72,9 +72,7 @@
|
||||||
<input type="checkbox" name="useNFS" ng-model="formValues.NFSData.useNFS" ng-click="formValues.CIFSData.useCIFS = false" />
|
<input type="checkbox" name="useNFS" ng-model="formValues.NFSData.useNFS" ng-click="formValues.CIFSData.useCIFS = false" />
|
||||||
<i></i>
|
<i></i>
|
||||||
</label>
|
</label>
|
||||||
<div ng-if="formValues.NFSData.useNFS" class="small text-muted" style="margin-top: 10px;">
|
<div ng-if="formValues.NFSData.useNFS" class="small text-muted" style="margin-top: 10px;"> Ensure <code>nfs-utils</code> are installed on your hosts. </div>
|
||||||
Ensure <code>nfs-utils</code> are installed on your hosts.
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<volumes-nfs-form data="formValues.NFSData" ng-show="formValues.Driver === 'local'"></volumes-nfs-form>
|
<volumes-nfs-form data="formValues.NFSData" ng-show="formValues.Driver === 'local'"></volumes-nfs-form>
|
||||||
<!-- !nfs-management -->
|
<!-- !nfs-management -->
|
||||||
|
@ -87,9 +85,7 @@
|
||||||
<input type="checkbox" name="useCIFS" ng-model="formValues.CIFSData.useCIFS" ng-click="formValues.NFSData.useNFS = false" />
|
<input type="checkbox" name="useCIFS" ng-model="formValues.CIFSData.useCIFS" ng-click="formValues.NFSData.useNFS = false" />
|
||||||
<i></i>
|
<i></i>
|
||||||
</label>
|
</label>
|
||||||
<div ng-if="formValues.CIFSData.useCIFS" class="small text-muted" style="margin-top: 10px;">
|
<div ng-if="formValues.CIFSData.useCIFS" class="small text-muted" style="margin-top: 10px;"> Ensure <code>cifs-utils</code> are installed on your hosts. </div>
|
||||||
Ensure <code>cifs-utils</code> are installed on your hosts.
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<volumes-cifs-form data="formValues.CIFSData" ng-show="formValues.Driver === 'local'"></volumes-cifs-form>
|
<volumes-cifs-form data="formValues.CIFSData" ng-show="formValues.Driver === 'local'"></volumes-cifs-form>
|
||||||
<!-- !cifs-management -->
|
<!-- !cifs-management -->
|
||||||
|
@ -110,7 +106,7 @@
|
||||||
<!-- !node-selection -->
|
<!-- !node-selection -->
|
||||||
</div>
|
</div>
|
||||||
<!-- access-control -->
|
<!-- access-control -->
|
||||||
<por-access-control-form form-data="formValues.AccessControlData" ng-if="applicationState.application.authentication"></por-access-control-form>
|
<por-access-control-form form-data="formValues.AccessControlData"></por-access-control-form>
|
||||||
<!-- !access-control -->
|
<!-- !access-control -->
|
||||||
<!-- actions -->
|
<!-- actions -->
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
|
|
|
@ -78,8 +78,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- access-control-panel -->
|
<!-- access-control-panel -->
|
||||||
<por-access-control-panel ng-if="volume && applicationState.application.authentication" resource-id="volume.Id" resource-control="volume.ResourceControl" resource-type="'volume'">
|
<por-access-control-panel ng-if="volume" resource-id="volume.Id" resource-control="volume.ResourceControl" resource-type="'volume'"> </por-access-control-panel>
|
||||||
</por-access-control-panel>
|
|
||||||
<!-- !access-control-panel -->
|
<!-- !access-control-panel -->
|
||||||
|
|
||||||
<div class="row" ng-if="!(volume.Options | emptyobject)">
|
<div class="row" ng-if="!(volume.Options | emptyobject)">
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
table-key="volumes"
|
table-key="volumes"
|
||||||
order-by="Id"
|
order-by="Id"
|
||||||
remove-action="removeAction"
|
remove-action="removeAction"
|
||||||
show-ownership-column="applicationState.application.authentication"
|
|
||||||
show-host-column="applicationState.endpoint.mode.agentProxy && applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'"
|
show-host-column="applicationState.endpoint.mode.agentProxy && applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'"
|
||||||
show-browse-action="showBrowseAction"
|
show-browse-action="showBrowseAction"
|
||||||
offline-mode="offlineMode"
|
offline-mode="offlineMode"
|
||||||
|
|
|
@ -42,10 +42,7 @@ angular.module('portainer.extensions.registrymanagement').controller('RegistryRe
|
||||||
function initView() {
|
function initView() {
|
||||||
const registryId = $transition$.params().id;
|
const registryId = $transition$.params().id;
|
||||||
|
|
||||||
var authenticationEnabled = $scope.applicationState.application.authentication;
|
|
||||||
if (authenticationEnabled) {
|
|
||||||
$scope.isAdmin = Authentication.isAdmin();
|
$scope.isAdmin = Authentication.isAdmin();
|
||||||
}
|
|
||||||
|
|
||||||
RegistryService.registry(registryId)
|
RegistryService.registry(registryId)
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
|
|
|
@ -55,9 +55,7 @@ angular.module('portainer.app', []).config([
|
||||||
if (state.application.analytics) {
|
if (state.application.analytics) {
|
||||||
initAnalytics(Analytics, $rootScope);
|
initAnalytics(Analytics, $rootScope);
|
||||||
}
|
}
|
||||||
if (state.application.authentication) {
|
|
||||||
return $async(initAuthentication, authManager, Authentication, $rootScope, $state);
|
return $async(initAuthentication, authManager, Authentication, $rootScope, $state);
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.then(() => deferred.resolve())
|
.then(() => deferred.resolve())
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
|
|
|
@ -83,7 +83,7 @@
|
||||||
<td>{{ item.URL | stripprotocol }}</td>
|
<td>{{ item.URL | stripprotocol }}</td>
|
||||||
<td>{{ item.GroupName }}</td>
|
<td>{{ item.GroupName }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a ui-sref="portainer.endpoints.endpoint.access({id: item.Id})" ng-if="$ctrl.accessManagement"> <i class="fa fa-users" aria-hidden="true"></i> Manage access </a>
|
<a ui-sref="portainer.endpoints.endpoint.access({id: item.Id})"> <i class="fa fa-users" aria-hidden="true"></i> Manage access </a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr ng-if="$ctrl.state.loading">
|
<tr ng-if="$ctrl.state.loading">
|
||||||
|
|
|
@ -7,7 +7,6 @@ angular.module('portainer.app').component('endpointsDatatable', {
|
||||||
tableKey: '@',
|
tableKey: '@',
|
||||||
orderBy: '@',
|
orderBy: '@',
|
||||||
reverseOrder: '<',
|
reverseOrder: '<',
|
||||||
accessManagement: '<',
|
|
||||||
removeAction: '<',
|
removeAction: '<',
|
||||||
retrievePage: '<',
|
retrievePage: '<',
|
||||||
},
|
},
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
<a ui-sref="portainer.groups.group({id: item.Id})">{{ item.Name }}</a>
|
<a ui-sref="portainer.groups.group({id: item.Id})">{{ item.Name }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a ui-sref="portainer.groups.group.access({id: item.Id})" ng-if="$ctrl.accessManagement"> <i class="fa fa-users" aria-hidden="true"></i> Manage access </a>
|
<a ui-sref="portainer.groups.group.access({id: item.Id})"> <i class="fa fa-users" aria-hidden="true"></i> Manage access </a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr ng-if="!$ctrl.dataset">
|
<tr ng-if="!$ctrl.dataset">
|
||||||
|
|
|
@ -8,7 +8,6 @@ angular.module('portainer.app').component('groupsDatatable', {
|
||||||
tableKey: '@',
|
tableKey: '@',
|
||||||
orderBy: '@',
|
orderBy: '@',
|
||||||
reverseOrder: '<',
|
reverseOrder: '<',
|
||||||
accessManagement: '<',
|
|
||||||
removeAction: '<',
|
removeAction: '<',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th>Control</th>
|
<th>Control</th>
|
||||||
<th ng-if="$ctrl.showOwnershipColumn">
|
<th>
|
||||||
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
|
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
|
||||||
Ownership
|
Ownership
|
||||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
|
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
|
||||||
|
@ -127,7 +127,7 @@
|
||||||
</span>
|
</span>
|
||||||
<span ng-if="!item.External">Total</span>
|
<span ng-if="!item.External">Total</span>
|
||||||
</td>
|
</td>
|
||||||
<td ng-if="$ctrl.showOwnershipColumn">
|
<td>
|
||||||
<span>
|
<span>
|
||||||
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||||
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = $ctrl.RCO.ADMINISTRATORS }}
|
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = $ctrl.RCO.ADMINISTRATORS }}
|
||||||
|
|
|
@ -8,7 +8,6 @@ angular.module('portainer.app').component('stacksDatatable', {
|
||||||
tableKey: '@',
|
tableKey: '@',
|
||||||
orderBy: '@',
|
orderBy: '@',
|
||||||
reverseOrder: '<',
|
reverseOrder: '<',
|
||||||
showOwnershipColumn: '<',
|
|
||||||
removeAction: '<',
|
removeAction: '<',
|
||||||
offlineMode: '<',
|
offlineMode: '<',
|
||||||
refreshCallback: '<',
|
refreshCallback: '<',
|
||||||
|
|
|
@ -12,6 +12,7 @@ export function SettingsViewModel(data) {
|
||||||
this.EnableHostManagementFeatures = data.EnableHostManagementFeatures;
|
this.EnableHostManagementFeatures = data.EnableHostManagementFeatures;
|
||||||
this.EdgeAgentCheckinInterval = data.EdgeAgentCheckinInterval;
|
this.EdgeAgentCheckinInterval = data.EdgeAgentCheckinInterval;
|
||||||
this.EnableEdgeComputeFeatures = data.EnableEdgeComputeFeatures;
|
this.EnableEdgeComputeFeatures = data.EnableEdgeComputeFeatures;
|
||||||
|
this.UserSessionTimeout = data.UserSessionTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PublicSettingsViewModel(settings) {
|
export function PublicSettingsViewModel(settings) {
|
||||||
|
|
|
@ -77,7 +77,6 @@ angular.module('portainer.app').factory('StateManager', [
|
||||||
};
|
};
|
||||||
|
|
||||||
function assignStateFromStatusAndSettings(status, settings) {
|
function assignStateFromStatusAndSettings(status, settings) {
|
||||||
state.application.authentication = status.Authentication;
|
|
||||||
state.application.analytics = status.Analytics;
|
state.application.analytics = status.Analytics;
|
||||||
state.application.version = status.Version;
|
state.application.version = status.Version;
|
||||||
state.application.logo = settings.LogoURL;
|
state.application.logo = settings.LogoURL;
|
||||||
|
|
|
@ -128,10 +128,10 @@ class AuthenticationController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkForEndpointsAsync(noAuth) {
|
async checkForEndpointsAsync() {
|
||||||
try {
|
try {
|
||||||
const endpoints = await this.EndpointService.endpoints(0, 1);
|
const endpoints = await this.EndpointService.endpoints(0, 1);
|
||||||
const isAdmin = noAuth || this.Authentication.isAdmin();
|
const isAdmin = this.Authentication.isAdmin();
|
||||||
|
|
||||||
if (endpoints.value.length === 0 && isAdmin) {
|
if (endpoints.value.length === 0 && isAdmin) {
|
||||||
return this.$state.go('portainer.init.endpoint');
|
return this.$state.go('portainer.init.endpoint');
|
||||||
|
@ -162,7 +162,7 @@ class AuthenticationController {
|
||||||
|
|
||||||
async postLoginSteps() {
|
async postLoginSteps() {
|
||||||
await this.retrieveAndSaveEnabledExtensionsAsync();
|
await this.retrieveAndSaveEnabledExtensionsAsync();
|
||||||
await this.checkForEndpointsAsync(false);
|
await this.checkForEndpointsAsync();
|
||||||
await this.checkForLatestVersionAsync();
|
await this.checkForLatestVersionAsync();
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
@ -282,12 +282,7 @@ class AuthenticationController {
|
||||||
}
|
}
|
||||||
this.state.loginInProgress = false;
|
this.state.loginInProgress = false;
|
||||||
|
|
||||||
const authenticationEnabled = this.$scope.applicationState.application.authentication;
|
|
||||||
if (!authenticationEnabled) {
|
|
||||||
await this.checkForEndpointsAsync(true);
|
|
||||||
} else {
|
|
||||||
await this.authEnabledFlowAsync();
|
await this.authEnabledFlowAsync();
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.Notifications.error('Failure', err, 'Unable to retrieve public settings');
|
this.Notifications.error('Failure', err, 'Unable to retrieve public settings');
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
title-icon="fa-plug"
|
title-icon="fa-plug"
|
||||||
table-key="endpoints"
|
table-key="endpoints"
|
||||||
order-by="Name"
|
order-by="Name"
|
||||||
access-management="applicationState.application.authentication"
|
|
||||||
remove-action="removeAction"
|
remove-action="removeAction"
|
||||||
retrieve-page="getPaginatedEndpoints"
|
retrieve-page="getPaginatedEndpoints"
|
||||||
></endpoints-datatable>
|
></endpoints-datatable>
|
||||||
|
|
|
@ -9,14 +9,6 @@
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<groups-datatable
|
<groups-datatable title-text="Endpoint groups" title-icon="fa-object-group" dataset="groups" table-key="groups" order-by="Name" remove-action="removeAction"></groups-datatable>
|
||||||
title-text="Endpoint groups"
|
|
||||||
title-icon="fa-object-group"
|
|
||||||
dataset="groups"
|
|
||||||
table-key="groups"
|
|
||||||
order-by="Name"
|
|
||||||
access-management="applicationState.application.authentication"
|
|
||||||
remove-action="removeAction"
|
|
||||||
></groups-datatable>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
table-key="home_endpoints"
|
table-key="home_endpoints"
|
||||||
tags="tags"
|
tags="tags"
|
||||||
dashboard-action="goToDashboard"
|
dashboard-action="goToDashboard"
|
||||||
show-snapshot-action="!applicationState.application.authentication || isAdmin"
|
show-snapshot-action="isAdmin"
|
||||||
snapshot-action="triggerSnapshot"
|
snapshot-action="triggerSnapshot"
|
||||||
edit-action="goToEdit"
|
edit-action="goToEdit"
|
||||||
is-admin="isAdmin"
|
is-admin="isAdmin"
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<rd-header-content>Registry management</rd-header-content>
|
<rd-header-content>Registry management</rd-header-content>
|
||||||
</rd-header>
|
</rd-header>
|
||||||
|
|
||||||
<div class="row" ng-if="dockerhub && (!applicationState.application.authentication || isAdmin)">
|
<div class="row" ng-if="dockerhub && isAdmin">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<rd-widget>
|
<rd-widget>
|
||||||
<rd-widget-header icon="fa-database" title-text="DockerHub"> </rd-widget-header>
|
<rd-widget-header icon="fa-database" title-text="DockerHub"> </rd-widget-header>
|
||||||
|
@ -79,7 +79,7 @@
|
||||||
dataset="registries"
|
dataset="registries"
|
||||||
table-key="registries"
|
table-key="registries"
|
||||||
order-by="Name"
|
order-by="Name"
|
||||||
access-management="!applicationState.application.authentication || isAdmin"
|
access-management="isAdmin"
|
||||||
remove-action="removeAction"
|
remove-action="removeAction"
|
||||||
registry-management="registryManagementAvailable"
|
registry-management="registryManagementAvailable"
|
||||||
can-browse="canBrowse"
|
can-browse="canBrowse"
|
||||||
|
|
|
@ -81,10 +81,7 @@ angular.module('portainer.app').controller('RegistriesController', [
|
||||||
$scope.registries = data.registries;
|
$scope.registries = data.registries;
|
||||||
$scope.dockerhub = data.dockerhub;
|
$scope.dockerhub = data.dockerhub;
|
||||||
$scope.registryManagementAvailable = data.registryManagement;
|
$scope.registryManagementAvailable = data.registryManagement;
|
||||||
var authenticationEnabled = $scope.applicationState.application.authentication;
|
|
||||||
if (authenticationEnabled) {
|
|
||||||
$scope.isAdmin = Authentication.isAdmin();
|
$scope.isAdmin = Authentication.isAdmin();
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
$scope.registries = [];
|
$scope.registries = [];
|
||||||
|
|
|
@ -9,6 +9,28 @@
|
||||||
<rd-widget-header icon="fa-users" title-text="Authentication"></rd-widget-header>
|
<rd-widget-header icon="fa-users" title-text="Authentication"></rd-widget-header>
|
||||||
<rd-widget-body>
|
<rd-widget-body>
|
||||||
<form class="form-horizontal">
|
<form class="form-horizontal">
|
||||||
|
<div class="col-sm-12 form-section-title">
|
||||||
|
Configuration
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="user_timeout" class="col-sm-2 control-label text-left">
|
||||||
|
Session lifetime
|
||||||
|
<portainer-tooltip message="Time before users are forced to relogin."></portainer-tooltip>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<select
|
||||||
|
id="user_timeout"
|
||||||
|
class="form-control"
|
||||||
|
ng-model="settings.UserSessionTimeout"
|
||||||
|
ng-options="opt.value as opt.key for opt in state.availableUserSessionTimeoutOptions"
|
||||||
|
></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<span class="col-sm-12 text-muted small">
|
||||||
|
Changing from default is only recommended if you have additional layers of authentication in front of Portainer.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
Authentication method
|
Authentication method
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -14,9 +14,32 @@ angular.module('portainer.app').controller('SettingsAuthenticationController', [
|
||||||
uploadInProgress: false,
|
uploadInProgress: false,
|
||||||
connectivityCheckInProgress: false,
|
connectivityCheckInProgress: false,
|
||||||
actionInProgress: false,
|
actionInProgress: false,
|
||||||
|
availableUserSessionTimeoutOptions: [
|
||||||
|
{
|
||||||
|
key: '1 hour',
|
||||||
|
value: '1h',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '4 hours',
|
||||||
|
value: '4h',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '8 hours',
|
||||||
|
value: '8h',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '24 hours',
|
||||||
|
value: '24h',
|
||||||
|
},
|
||||||
|
{ key: '1 week', value: `${24 * 7}h` },
|
||||||
|
{ key: '1 month', value: `${24 * 30}h` },
|
||||||
|
{ key: '6 months', value: `${24 * 30 * 6}h` },
|
||||||
|
{ key: '1 year', value: `${24 * 30 * 12}h` },
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.formValues = {
|
$scope.formValues = {
|
||||||
|
UserSessionTimeout: $scope.state.availableUserSessionTimeoutOptions[0],
|
||||||
TLSCACert: '',
|
TLSCACert: '',
|
||||||
LDAPSettings: {
|
LDAPSettings: {
|
||||||
AnonymousMode: true,
|
AnonymousMode: true,
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
endpoint-api-version="applicationState.endpoint.apiVersion"
|
endpoint-api-version="applicationState.endpoint.apiVersion"
|
||||||
swarm-management="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'"
|
swarm-management="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'"
|
||||||
standalone-management="applicationState.endpoint.mode.provider === 'DOCKER_STANDALONE' || applicationState.endpoint.mode.provider === 'VMWARE_VIC'"
|
standalone-management="applicationState.endpoint.mode.provider === 'DOCKER_STANDALONE' || applicationState.endpoint.mode.provider === 'VMWARE_VIC'"
|
||||||
admin-access="!applicationState.application.authentication || isAdmin"
|
admin-access="isAdmin"
|
||||||
offline-mode="endpointState.OfflineMode"
|
offline-mode="endpointState.OfflineMode"
|
||||||
></docker-sidebar-content>
|
></docker-sidebar-content>
|
||||||
<li class="sidebar-title" authorization="IntegrationStoridgeAdmin" ng-if="applicationState.endpoint.mode && applicationState.endpoint.extensions.length > 0">
|
<li class="sidebar-title" authorization="IntegrationStoridgeAdmin" ng-if="applicationState.endpoint.mode && applicationState.endpoint.extensions.length > 0">
|
||||||
|
@ -85,10 +85,10 @@
|
||||||
<a ui-sref="storidge.drives" ui-sref-active="active">Drives</a>
|
<a ui-sref="storidge.drives" ui-sref-active="active">Drives</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar-title" ng-if="(!applicationState.application.authentication || isAdmin) && applicationState.application.enableHostManagementFeatures">
|
<li class="sidebar-title" ng-if="isAdmin && applicationState.application.enableHostManagementFeatures">
|
||||||
<span>Scheduler</span>
|
<span>Scheduler</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar-list" ng-if="(!applicationState.application.authentication || isAdmin) && applicationState.application.enableHostManagementFeatures">
|
<li class="sidebar-list" ng-if="isAdmin && applicationState.application.enableHostManagementFeatures">
|
||||||
<a ui-sref="portainer.schedules" ui-sref-active="active">Host jobs <span class="menu-icon fa fa-clock fa-fw"></span></a>
|
<a ui-sref="portainer.schedules" ui-sref-active="active">Host jobs <span class="menu-icon fa fa-clock fa-fw"></span></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar-title" ng-if="isAdmin && applicationState.application.enableEdgeComputeFeatures">
|
<li class="sidebar-title" ng-if="isAdmin && applicationState.application.enableEdgeComputeFeatures">
|
||||||
|
@ -103,10 +103,10 @@
|
||||||
<li class="sidebar-title">
|
<li class="sidebar-title">
|
||||||
<span>Settings</span>
|
<span>Settings</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar-list" ng-if="!applicationState.application.authentication || isAdmin">
|
<li class="sidebar-list" ng-if="isAdmin">
|
||||||
<a ui-sref="portainer.extensions" ui-sref-active="active">Extensions <span class="menu-icon fa fa-bolt fa-fw"></span></a>
|
<a ui-sref="portainer.extensions" ui-sref-active="active">Extensions <span class="menu-icon fa fa-bolt fa-fw"></span></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar-list" ng-if="applicationState.application.authentication && (isAdmin || isTeamLeader)">
|
<li class="sidebar-list" ng-if="isAdmin || isTeamLeader">
|
||||||
<a ui-sref="portainer.users" ui-sref-active="active">Users <span class="menu-icon fa fa-users fa-fw"></span></a>
|
<a ui-sref="portainer.users" ui-sref-active="active">Users <span class="menu-icon fa fa-users fa-fw"></span></a>
|
||||||
<div
|
<div
|
||||||
class="sidebar-sublist"
|
class="sidebar-sublist"
|
||||||
|
@ -139,7 +139,7 @@
|
||||||
<a ui-sref="portainer.roles" ui-sref-active="active">Roles</a>
|
<a ui-sref="portainer.roles" ui-sref-active="active">Roles</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar-list" ng-if="!applicationState.application.authentication || isAdmin">
|
<li class="sidebar-list" ng-if="isAdmin">
|
||||||
<a ui-sref="portainer.endpoints" ui-sref-active="active">Endpoints <span class="menu-icon fa fa-plug fa-fw"></span></a>
|
<a ui-sref="portainer.endpoints" ui-sref-active="active">Endpoints <span class="menu-icon fa fa-plug fa-fw"></span></a>
|
||||||
<div
|
<div
|
||||||
class="sidebar-sublist"
|
class="sidebar-sublist"
|
||||||
|
@ -179,14 +179,13 @@
|
||||||
<li class="sidebar-list">
|
<li class="sidebar-list">
|
||||||
<a ui-sref="portainer.registries" ui-sref-active="active">Registries <span class="menu-icon fa fa-database fa-fw"></span></a>
|
<a ui-sref="portainer.registries" ui-sref-active="active">Registries <span class="menu-icon fa fa-database fa-fw"></span></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar-list" ng-if="!applicationState.application.authentication || isAdmin">
|
<li class="sidebar-list" ng-if="isAdmin">
|
||||||
<a ui-sref="portainer.settings" ui-sref-active="active">Settings <span class="menu-icon fa fa-cogs fa-fw"></span></a>
|
<a ui-sref="portainer.settings" ui-sref-active="active">Settings <span class="menu-icon fa fa-cogs fa-fw"></span></a>
|
||||||
<div
|
<div
|
||||||
class="sidebar-sublist"
|
class="sidebar-sublist"
|
||||||
ng-if="
|
ng-if="
|
||||||
toggle &&
|
toggle &&
|
||||||
($state.current.name === 'portainer.settings' || $state.current.name === 'portainer.settings.authentication' || $state.current.name === 'portainer.about') &&
|
($state.current.name === 'portainer.settings' || $state.current.name === 'portainer.settings.authentication' || $state.current.name === 'portainer.about') &&
|
||||||
applicationState.application.authentication &&
|
|
||||||
isAdmin
|
isAdmin
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
|
|
@ -20,8 +20,6 @@ angular.module('portainer.app').controller('SidebarController', [
|
||||||
$scope.uiVersion = StateManager.getState().application.version;
|
$scope.uiVersion = StateManager.getState().application.version;
|
||||||
$scope.logo = StateManager.getState().application.logo;
|
$scope.logo = StateManager.getState().application.logo;
|
||||||
|
|
||||||
var authenticationEnabled = $scope.applicationState.application.authentication;
|
|
||||||
if (authenticationEnabled) {
|
|
||||||
let userDetails = Authentication.getUserDetails();
|
let userDetails = Authentication.getUserDetails();
|
||||||
let isAdmin = Authentication.isAdmin();
|
let isAdmin = Authentication.isAdmin();
|
||||||
$scope.isAdmin = isAdmin;
|
$scope.isAdmin = isAdmin;
|
||||||
|
@ -34,7 +32,6 @@ angular.module('portainer.app').controller('SidebarController', [
|
||||||
Notifications.error('Failure', err, 'Unable to retrieve user memberships');
|
Notifications.error('Failure', err, 'Unable to retrieve user memberships');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
initView();
|
initView();
|
||||||
},
|
},
|
||||||
|
|
|
@ -214,7 +214,7 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- !environment-variables -->
|
<!-- !environment-variables -->
|
||||||
<!-- !repository -->
|
<!-- !repository -->
|
||||||
<por-access-control-form form-data="formValues.AccessControlData" ng-if="applicationState.application.authentication"></por-access-control-form>
|
<por-access-control-form form-data="formValues.AccessControlData"></por-access-control-form>
|
||||||
<!-- actions -->
|
<!-- actions -->
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
Actions
|
Actions
|
||||||
|
|
|
@ -154,7 +154,6 @@
|
||||||
dataset="containers"
|
dataset="containers"
|
||||||
table-key="stack-containers"
|
table-key="stack-containers"
|
||||||
order-by="Status"
|
order-by="Status"
|
||||||
show-ownership-column="applicationState.application.authentication"
|
|
||||||
show-host-column="false"
|
show-host-column="false"
|
||||||
show-add-action="false"
|
show-add-action="false"
|
||||||
></containers-datatable>
|
></containers-datatable>
|
||||||
|
@ -181,6 +180,5 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- access-control-panel -->
|
<!-- access-control-panel -->
|
||||||
<por-access-control-panel ng-if="stack && applicationState.application.authentication" resource-id="stack.Name" resource-control="stack.ResourceControl" resource-type="'stack'">
|
<por-access-control-panel ng-if="stack" resource-id="stack.Name" resource-control="stack.ResourceControl" resource-type="'stack'"> </por-access-control-panel>
|
||||||
</por-access-control-panel>
|
|
||||||
<!-- !access-control-panel -->
|
<!-- !access-control-panel -->
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
table-key="stacks"
|
table-key="stacks"
|
||||||
order-by="Name"
|
order-by="Name"
|
||||||
remove-action="removeAction"
|
remove-action="removeAction"
|
||||||
show-ownership-column="applicationState.application.authentication"
|
|
||||||
offline-mode="offlineMode"
|
offline-mode="offlineMode"
|
||||||
refresh-callback="getStacks"
|
refresh-callback="getStacks"
|
||||||
></stacks-datatable>
|
></stacks-datatable>
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- !env -->
|
<!-- !env -->
|
||||||
<!-- access-control -->
|
<!-- access-control -->
|
||||||
<por-access-control-form form-data="formValues.AccessControlData" ng-if="applicationState.application.authentication"></por-access-control-form>
|
<por-access-control-form form-data="formValues.AccessControlData"></por-access-control-form>
|
||||||
<!-- !access-control -->
|
<!-- !access-control -->
|
||||||
<!-- actions -->
|
<!-- actions -->
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
|
@ -136,7 +136,7 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- !env -->
|
<!-- !env -->
|
||||||
<!-- access-control -->
|
<!-- access-control -->
|
||||||
<por-access-control-form form-data="formValues.AccessControlData" ng-if="applicationState.application.authentication"></por-access-control-form>
|
<por-access-control-form form-data="formValues.AccessControlData"></por-access-control-form>
|
||||||
<!-- !access-control -->
|
<!-- !access-control -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
|
|
Loading…
Reference in New Issue