Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df3886fd25 | ||
|
|
f2682d82b6 | ||
|
|
d3576fe8e6 | ||
|
|
f34d27df0f | ||
|
|
f228b28639 | ||
|
|
d7b4e4aba1 | ||
|
|
7234c443e8 | ||
|
|
5b844fd40a | ||
|
|
7f2ef8fb06 | ||
|
|
9f817749c1 | ||
|
|
36198b57a5 | ||
|
|
70f9e37eab | ||
|
|
24f69f0185 | ||
|
|
4a2e9a892d | ||
|
|
1e4bedde4b | ||
|
|
9db07e7f4e | ||
|
|
d4b8d9947d | ||
|
|
9194ddcd03 | ||
|
|
e1edf37770 | ||
|
|
82b73c06b4 | ||
|
|
ae286998ab | ||
|
|
ffc3ed67e2 | ||
|
|
6252be4a08 | ||
|
|
75481d928e | ||
|
|
d65d65803f | ||
|
|
6a8fc253bd | ||
|
|
1fa8921c11 | ||
|
|
ba3892aebf | ||
|
|
27fc700d6f | ||
|
|
3187cb0ada | ||
|
|
b462a15921 | ||
|
|
11a646a076 | ||
|
|
4c1edaf251 | ||
|
|
b45c4f8bea | ||
|
|
968f070b0b | ||
|
|
e679483ffd | ||
|
|
494ef6c392 | ||
|
|
7951c1d150 | ||
|
|
0083bdb6a2 | ||
|
|
e65881223d | ||
|
|
f0c9058568 | ||
|
|
afd654e4a8 | ||
|
|
0ef116d6b5 | ||
|
|
ee64a53782 | ||
|
|
4a0c899df5 | ||
|
|
840603bb8c | ||
|
|
5fe79621a6 | ||
|
|
d3b9822105 | ||
|
|
9163bcae25 | ||
|
|
17dad6e36f | ||
|
|
75b836453a | ||
|
|
b8866d487f | ||
|
|
dfb3b3aaa1 | ||
|
|
09fdc9781c |
@@ -6,10 +6,10 @@ Some basic conventions for contributing to this project.
|
||||
|
||||
Please make sure that there aren't existing pull requests attempting to address the issue mentioned. Likewise, please check for issues related to update, as someone else may be working on the issue in a branch or fork.
|
||||
|
||||
* Please open a discussion in a new issue / existing issue to talk about the changes you'd like to bring
|
||||
* Develop in a topic branch, not master/develop
|
||||
- Please open a discussion in a new issue / existing issue to talk about the changes you'd like to bring
|
||||
- Develop in a topic branch, not master/develop
|
||||
|
||||
When creating a new branch, prefix it with the *type* of the change (see section **Commit Message Format** below), the associated opened issue number, a dash and some text describing the issue (using dash as a separator).
|
||||
When creating a new branch, prefix it with the _type_ of the change (see section **Commit Message Format** below), the associated opened issue number, a dash and some text describing the issue (using dash as a separator).
|
||||
|
||||
For example, if you work on a bugfix for the issue #361, you could name the branch `fix361-template-selection`.
|
||||
|
||||
@@ -37,14 +37,14 @@ Lines should not exceed 100 characters. This allows the message to be easier to
|
||||
|
||||
Must be one of the following:
|
||||
|
||||
* **feat**: A new feature
|
||||
* **fix**: A bug fix
|
||||
* **docs**: Documentation only changes
|
||||
* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing
|
||||
- **feat**: A new feature
|
||||
- **fix**: A bug fix
|
||||
- **docs**: Documentation only changes
|
||||
- **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing
|
||||
semi-colons, etc)
|
||||
* **refactor**: A code change that neither fixes a bug or adds a feature
|
||||
* **test**: Adding missing tests
|
||||
* **chore**: Changes to the build process or auxiliary tools and libraries such as documentation
|
||||
- **refactor**: A code change that neither fixes a bug or adds a feature
|
||||
- **test**: Adding missing tests
|
||||
- **chore**: Changes to the build process or auxiliary tools and libraries such as documentation
|
||||
generation
|
||||
|
||||
### Scope
|
||||
@@ -57,9 +57,9 @@ You can use the **area** label tag associated on the issue here (for `area/conta
|
||||
|
||||
The subject contains succinct description of the change:
|
||||
|
||||
* use the imperative, present tense: "change" not "changed" nor "changes"
|
||||
* don't capitalize first letter
|
||||
* no dot (.) at the end
|
||||
- use the imperative, present tense: "change" not "changed" nor "changes"
|
||||
- don't capitalize first letter
|
||||
- no dot (.) at the end
|
||||
|
||||
## Contribution process
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package migrator
|
||||
|
||||
func (m *Migrator) updateSettingsToDBVersion25() error {
|
||||
legacySettings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
legacySettings.AllowContainerCapabilitiesForRegularUsers = true
|
||||
|
||||
return m.settingsService.UpdateSettings(legacySettings)
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package migrator
|
||||
|
||||
import (
|
||||
"github.com/boltdb/bolt"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/endpoint"
|
||||
"github.com/portainer/portainer/api/bolt/endpointgroup"
|
||||
"github.com/portainer/portainer/api/bolt/endpointrelation"
|
||||
@@ -330,13 +330,5 @@ func (m *Migrator) Migrate() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.24.2
|
||||
if m.currentDBVersion < 25 {
|
||||
err := m.updateSettingsToDBVersion25()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return m.versionService.StoreDBVersion(portainer.DBVersion)
|
||||
}
|
||||
|
||||
@@ -70,6 +70,45 @@ func initStore(dataStorePath string, fileService portainer.FileService) *bolt.St
|
||||
return store
|
||||
}
|
||||
|
||||
func initDemoData(store *bolt.Store, cryptoService portainer.CryptoService) error {
|
||||
password, err := cryptoService.Hash("tryportainer")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
admin := &portainer.User{
|
||||
Username: "admin",
|
||||
Password: password,
|
||||
Role: portainer.AdministratorRole,
|
||||
}
|
||||
|
||||
err = store.UserService.CreateUser(admin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
localEndpoint := &portainer.Endpoint{
|
||||
ID: portainer.EndpointID(1),
|
||||
Name: "local",
|
||||
URL: "unix:///var/run/docker.sock",
|
||||
PublicURL: "demo.portainer.io",
|
||||
TLSConfig: portainer.TLSConfiguration{
|
||||
TLS: false,
|
||||
},
|
||||
AuthorizedUsers: []portainer.UserID{},
|
||||
AuthorizedTeams: []portainer.TeamID{},
|
||||
Extensions: []portainer.EndpointExtension{},
|
||||
Tags: []string{},
|
||||
}
|
||||
|
||||
err = store.EndpointService.CreateEndpoint(localEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func initComposeStackManager(dataStorePath string, reverseTunnelService portainer.ReverseTunnelService) portainer.ComposeStackManager {
|
||||
return libcompose.NewComposeStackManager(dataStorePath, reverseTunnelService)
|
||||
}
|
||||
@@ -269,17 +308,16 @@ func initSettings(settingsService portainer.SettingsService, flags *portainer.CL
|
||||
portainer.LDAPGroupSearchSettings{},
|
||||
},
|
||||
},
|
||||
OAuthSettings: portainer.OAuthSettings{},
|
||||
AllowBindMountsForRegularUsers: true,
|
||||
AllowPrivilegedModeForRegularUsers: true,
|
||||
AllowVolumeBrowserForRegularUsers: false,
|
||||
AllowDeviceMappingForRegularUsers: true,
|
||||
AllowStackManagementForRegularUsers: true,
|
||||
AllowContainerCapabilitiesForRegularUsers: true,
|
||||
EnableHostManagementFeatures: false,
|
||||
AllowHostNamespaceForRegularUsers: true,
|
||||
SnapshotInterval: *flags.SnapshotInterval,
|
||||
EdgeAgentCheckinInterval: portainer.DefaultEdgeAgentCheckinIntervalInSeconds,
|
||||
OAuthSettings: portainer.OAuthSettings{},
|
||||
AllowBindMountsForRegularUsers: true,
|
||||
AllowPrivilegedModeForRegularUsers: true,
|
||||
AllowVolumeBrowserForRegularUsers: false,
|
||||
AllowDeviceMappingForRegularUsers: true,
|
||||
AllowStackManagementForRegularUsers: true,
|
||||
EnableHostManagementFeatures: false,
|
||||
AllowHostNamespaceForRegularUsers: true,
|
||||
SnapshotInterval: *flags.SnapshotInterval,
|
||||
EdgeAgentCheckinInterval: portainer.DefaultEdgeAgentCheckinIntervalInSeconds,
|
||||
}
|
||||
|
||||
if *flags.Templates != "" {
|
||||
@@ -533,6 +571,8 @@ func main() {
|
||||
|
||||
cryptoService := initCryptoService()
|
||||
|
||||
initDemoData(store, cryptoService)
|
||||
|
||||
digitalSignatureService := initDigitalSignatureService()
|
||||
|
||||
err := initKeyPair(fileService, digitalSignatureService)
|
||||
|
||||
@@ -89,6 +89,11 @@ const (
|
||||
ErrUndefinedTLSFileType = Error("Undefined TLS file type")
|
||||
)
|
||||
|
||||
// Demo errors.
|
||||
const (
|
||||
ErrNotAvailableInDemo = Error("This feature is not available in the demo version of Portainer")
|
||||
)
|
||||
|
||||
// Extension errors.
|
||||
const (
|
||||
ErrExtensionAlreadyEnabled = Error("This extension is already enabled")
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
"github.com/portainer/portainer/api/http/client"
|
||||
)
|
||||
@@ -261,13 +261,13 @@ func (handler *Handler) createEdgeAgentEndpoint(payload *endpointCreatePayload)
|
||||
TLSConfig: portainer.TLSConfiguration{
|
||||
TLS: false,
|
||||
},
|
||||
UserAccessPolicies: portainer.UserAccessPolicies{},
|
||||
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
||||
Extensions: []portainer.EndpointExtension{},
|
||||
TagIDs: payload.TagIDs,
|
||||
Status: portainer.EndpointStatusUp,
|
||||
Snapshots: []portainer.Snapshot{},
|
||||
EdgeKey: edgeKey,
|
||||
AuthorizedUsers: []portainer.UserID{},
|
||||
AuthorizedTeams: []portainer.TeamID{},
|
||||
Extensions: []portainer.EndpointExtension{},
|
||||
TagIDs: payload.TagIDs,
|
||||
Status: portainer.EndpointStatusUp,
|
||||
Snapshots: []portainer.Snapshot{},
|
||||
EdgeKey: edgeKey,
|
||||
}
|
||||
|
||||
err = handler.saveEndpointAndUpdateAuthorizations(endpoint)
|
||||
|
||||
@@ -21,6 +21,10 @@ func (handler *Handler) endpointDelete(w http.ResponseWriter, r *http.Request) *
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid endpoint identifier route variable", err}
|
||||
}
|
||||
|
||||
if endpointID == 1 {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "This feature is not available in the demo version of Portainer", portainer.ErrNotAvailableInDemo}
|
||||
}
|
||||
|
||||
endpoint, err := handler.EndpointService.Endpoint(portainer.EndpointID(endpointID))
|
||||
if err == portainer.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find an endpoint with the specified identifier inside the database", err}
|
||||
|
||||
@@ -10,19 +10,18 @@ import (
|
||||
)
|
||||
|
||||
type publicSettingsResponse struct {
|
||||
LogoURL string `json:"LogoURL"`
|
||||
AuthenticationMethod portainer.AuthenticationMethod `json:"AuthenticationMethod"`
|
||||
AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"`
|
||||
AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"`
|
||||
AllowVolumeBrowserForRegularUsers bool `json:"AllowVolumeBrowserForRegularUsers"`
|
||||
EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
|
||||
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
|
||||
ExternalTemplates bool `json:"ExternalTemplates"`
|
||||
OAuthLoginURI string `json:"OAuthLoginURI"`
|
||||
AllowStackManagementForRegularUsers bool `json:"AllowStackManagementForRegularUsers"`
|
||||
AllowHostNamespaceForRegularUsers bool `json:"AllowHostNamespaceForRegularUsers"`
|
||||
AllowDeviceMappingForRegularUsers bool `json:"AllowDeviceMappingForRegularUsers"`
|
||||
AllowContainerCapabilitiesForRegularUsers bool `json:"AllowContainerCapabilitiesForRegularUsers"`
|
||||
LogoURL string `json:"LogoURL"`
|
||||
AuthenticationMethod portainer.AuthenticationMethod `json:"AuthenticationMethod"`
|
||||
AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"`
|
||||
AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"`
|
||||
AllowVolumeBrowserForRegularUsers bool `json:"AllowVolumeBrowserForRegularUsers"`
|
||||
EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
|
||||
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
|
||||
ExternalTemplates bool `json:"ExternalTemplates"`
|
||||
OAuthLoginURI string `json:"OAuthLoginURI"`
|
||||
AllowStackManagementForRegularUsers bool `json:"AllowStackManagementForRegularUsers"`
|
||||
AllowHostNamespaceForRegularUsers bool `json:"AllowHostNamespaceForRegularUsers"`
|
||||
AllowDeviceMappingForRegularUsers bool `json:"AllowDeviceMappingForRegularUsers"`
|
||||
}
|
||||
|
||||
// GET request on /api/settings/public
|
||||
@@ -33,17 +32,16 @@ func (handler *Handler) settingsPublic(w http.ResponseWriter, r *http.Request) *
|
||||
}
|
||||
|
||||
publicSettings := &publicSettingsResponse{
|
||||
LogoURL: settings.LogoURL,
|
||||
AuthenticationMethod: settings.AuthenticationMethod,
|
||||
AllowBindMountsForRegularUsers: settings.AllowBindMountsForRegularUsers,
|
||||
AllowPrivilegedModeForRegularUsers: settings.AllowPrivilegedModeForRegularUsers,
|
||||
AllowVolumeBrowserForRegularUsers: settings.AllowVolumeBrowserForRegularUsers,
|
||||
EnableHostManagementFeatures: settings.EnableHostManagementFeatures,
|
||||
EnableEdgeComputeFeatures: settings.EnableEdgeComputeFeatures,
|
||||
AllowHostNamespaceForRegularUsers: settings.AllowHostNamespaceForRegularUsers,
|
||||
AllowStackManagementForRegularUsers: settings.AllowStackManagementForRegularUsers,
|
||||
ExternalTemplates: false,
|
||||
AllowContainerCapabilitiesForRegularUsers: settings.AllowContainerCapabilitiesForRegularUsers,
|
||||
LogoURL: settings.LogoURL,
|
||||
AuthenticationMethod: settings.AuthenticationMethod,
|
||||
AllowBindMountsForRegularUsers: settings.AllowBindMountsForRegularUsers,
|
||||
AllowPrivilegedModeForRegularUsers: settings.AllowPrivilegedModeForRegularUsers,
|
||||
AllowVolumeBrowserForRegularUsers: settings.AllowVolumeBrowserForRegularUsers,
|
||||
EnableHostManagementFeatures: settings.EnableHostManagementFeatures,
|
||||
EnableEdgeComputeFeatures: settings.EnableEdgeComputeFeatures,
|
||||
AllowHostNamespaceForRegularUsers: settings.AllowHostNamespaceForRegularUsers,
|
||||
AllowStackManagementForRegularUsers: settings.AllowStackManagementForRegularUsers,
|
||||
ExternalTemplates: false,
|
||||
OAuthLoginURI: fmt.Sprintf("%s?response_type=code&client_id=%s&redirect_uri=%s&scope=%s&prompt=login",
|
||||
settings.OAuthSettings.AuthorizationURI,
|
||||
settings.OAuthSettings.ClientID,
|
||||
|
||||
@@ -12,23 +12,22 @@ import (
|
||||
)
|
||||
|
||||
type settingsUpdatePayload struct {
|
||||
LogoURL *string
|
||||
BlackListedLabels []portainer.Pair
|
||||
AuthenticationMethod *int
|
||||
LDAPSettings *portainer.LDAPSettings
|
||||
OAuthSettings *portainer.OAuthSettings
|
||||
AllowBindMountsForRegularUsers *bool
|
||||
AllowPrivilegedModeForRegularUsers *bool
|
||||
AllowVolumeBrowserForRegularUsers *bool
|
||||
EnableHostManagementFeatures *bool
|
||||
SnapshotInterval *string
|
||||
TemplatesURL *string
|
||||
EdgeAgentCheckinInterval *int
|
||||
EnableEdgeComputeFeatures *bool
|
||||
AllowStackManagementForRegularUsers *bool
|
||||
AllowHostNamespaceForRegularUsers *bool
|
||||
AllowDeviceMappingForRegularUsers *bool
|
||||
AllowContainerCapabilitiesForRegularUsers *bool
|
||||
LogoURL *string
|
||||
BlackListedLabels []portainer.Pair
|
||||
AuthenticationMethod *int
|
||||
LDAPSettings *portainer.LDAPSettings
|
||||
OAuthSettings *portainer.OAuthSettings
|
||||
AllowBindMountsForRegularUsers *bool
|
||||
AllowPrivilegedModeForRegularUsers *bool
|
||||
AllowVolumeBrowserForRegularUsers *bool
|
||||
EnableHostManagementFeatures *bool
|
||||
SnapshotInterval *string
|
||||
TemplatesURL *string
|
||||
EdgeAgentCheckinInterval *int
|
||||
EnableEdgeComputeFeatures *bool
|
||||
AllowStackManagementForRegularUsers *bool
|
||||
AllowHostNamespaceForRegularUsers *bool
|
||||
AllowDeviceMappingForRegularUsers *bool
|
||||
}
|
||||
|
||||
func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
|
||||
@@ -126,10 +125,6 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
|
||||
settings.AllowHostNamespaceForRegularUsers = *payload.AllowHostNamespaceForRegularUsers
|
||||
}
|
||||
|
||||
if payload.AllowContainerCapabilitiesForRegularUsers != nil {
|
||||
settings.AllowContainerCapabilitiesForRegularUsers = *payload.AllowContainerCapabilitiesForRegularUsers
|
||||
}
|
||||
|
||||
if payload.SnapshotInterval != nil && *payload.SnapshotInterval != settings.SnapshotInterval {
|
||||
err := handler.updateSnapshotInterval(settings, *payload.SnapshotInterval)
|
||||
if err != nil {
|
||||
|
||||
@@ -302,12 +302,9 @@ func (handler *Handler) createComposeDeployConfig(r *http.Request, stack *portai
|
||||
}
|
||||
filteredRegistries := security.FilterRegistries(registries, securityContext)
|
||||
|
||||
var user *portainer.User
|
||||
if !handler.authDisabled {
|
||||
user, err = handler.UserService.User(securityContext.UserID)
|
||||
if err != nil {
|
||||
return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to load user information from the database", err}
|
||||
}
|
||||
user, err := handler.UserService.User(securityContext.UserID)
|
||||
if err != nil {
|
||||
return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to load user information from the database", err}
|
||||
}
|
||||
|
||||
config := &composeStackDeploymentConfig{
|
||||
@@ -333,29 +330,26 @@ func (handler *Handler) deployComposeStack(config *composeStackDeploymentConfig)
|
||||
return err
|
||||
}
|
||||
|
||||
if !handler.authDisabled {
|
||||
isAdminOrEndpointAdmin, err := handler.userIsAdminOrEndpointAdmin(config.user, config.endpoint.ID)
|
||||
isAdminOrEndpointAdmin, err := handler.userIsAdminOrEndpointAdmin(config.user, config.endpoint.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if (!settings.AllowBindMountsForRegularUsers ||
|
||||
!settings.AllowPrivilegedModeForRegularUsers ||
|
||||
!settings.AllowHostNamespaceForRegularUsers ||
|
||||
!settings.AllowDeviceMappingForRegularUsers) && !isAdminOrEndpointAdmin {
|
||||
|
||||
composeFilePath := path.Join(config.stack.ProjectPath, config.stack.EntryPoint)
|
||||
|
||||
stackContent, err := handler.FileService.GetFileContent(composeFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if (!settings.AllowBindMountsForRegularUsers ||
|
||||
!settings.AllowPrivilegedModeForRegularUsers ||
|
||||
!settings.AllowHostNamespaceForRegularUsers ||
|
||||
!settings.AllowDeviceMappingForRegularUsers ||
|
||||
!settings.AllowContainerCapabilitiesForRegularUsers) && !isAdminOrEndpointAdmin {
|
||||
|
||||
composeFilePath := path.Join(config.stack.ProjectPath, config.stack.EntryPoint)
|
||||
|
||||
stackContent, err := handler.FileService.GetFileContent(composeFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = handler.isValidStackFile(stackContent, settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = handler.isValidStackFile(stackContent, settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -311,12 +311,9 @@ func (handler *Handler) createSwarmDeployConfig(r *http.Request, stack *portaine
|
||||
}
|
||||
filteredRegistries := security.FilterRegistries(registries, securityContext)
|
||||
|
||||
var user *portainer.User
|
||||
if !handler.authDisabled {
|
||||
user, err = handler.UserService.User(securityContext.UserID)
|
||||
if err != nil {
|
||||
return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to load user information from the database", err}
|
||||
}
|
||||
user, err := handler.UserService.User(securityContext.UserID)
|
||||
if err != nil {
|
||||
return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to load user information from the database", err}
|
||||
}
|
||||
|
||||
config := &swarmStackDeploymentConfig{
|
||||
@@ -338,25 +335,23 @@ func (handler *Handler) deploySwarmStack(config *swarmStackDeploymentConfig) err
|
||||
return err
|
||||
}
|
||||
|
||||
if !handler.authDisabled {
|
||||
isAdminOrEndpointAdmin, err := handler.userIsAdminOrEndpointAdmin(config.user, config.endpoint.ID)
|
||||
isAdminOrEndpointAdmin, err := handler.userIsAdminOrEndpointAdmin(config.user, config.endpoint.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !settings.AllowBindMountsForRegularUsers && !isAdminOrEndpointAdmin {
|
||||
|
||||
composeFilePath := path.Join(config.stack.ProjectPath, config.stack.EntryPoint)
|
||||
|
||||
stackContent, err := handler.FileService.GetFileContent(composeFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !settings.AllowBindMountsForRegularUsers && !isAdminOrEndpointAdmin {
|
||||
|
||||
composeFilePath := path.Join(config.stack.ProjectPath, config.stack.EntryPoint)
|
||||
|
||||
stackContent, err := handler.FileService.GetFileContent(composeFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = handler.isValidStackFile(stackContent, settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = handler.isValidStackFile(stackContent, settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,6 @@ type Handler struct {
|
||||
stackCreationMutex *sync.Mutex
|
||||
stackDeletionMutex *sync.Mutex
|
||||
requestBouncer *security.RequestBouncer
|
||||
authDisabled bool
|
||||
|
||||
*mux.Router
|
||||
FileService portainer.FileService
|
||||
GitService portainer.GitService
|
||||
@@ -34,10 +32,9 @@ type Handler struct {
|
||||
}
|
||||
|
||||
// NewHandler creates a handler to manage stack operations.
|
||||
func NewHandler(bouncer *security.RequestBouncer, authDisabled bool) *Handler {
|
||||
func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||
h := &Handler{
|
||||
Router: mux.NewRouter(),
|
||||
authDisabled: authDisabled,
|
||||
stackCreationMutex: &sync.Mutex{},
|
||||
stackDeletionMutex: &sync.Mutex{},
|
||||
requestBouncer: bouncer,
|
||||
@@ -60,7 +57,7 @@ func NewHandler(bouncer *security.RequestBouncer, authDisabled bool) *Handler {
|
||||
}
|
||||
|
||||
func (handler *Handler) userCanAccessStack(securityContext *security.RestrictedRequestContext, endpointID portainer.EndpointID, resourceControl *portainer.ResourceControl) (bool, error) {
|
||||
if securityContext.IsAdmin || handler.authDisabled {
|
||||
if securityContext.IsAdmin {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -93,7 +90,7 @@ func (handler *Handler) userCanAccessStack(securityContext *security.RestrictedR
|
||||
}
|
||||
|
||||
func (handler *Handler) userCanCreateStack(securityContext *security.RestrictedRequestContext, endpointID portainer.EndpointID) (bool, error) {
|
||||
if securityContext.IsAdmin || handler.authDisabled {
|
||||
if securityContext.IsAdmin {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -164,10 +164,6 @@ func (handler *Handler) isValidStackFile(stackFileContent []byte, settings *port
|
||||
if !settings.AllowDeviceMappingForRegularUsers && service.Devices != nil && len(service.Devices) > 0 {
|
||||
return errors.New("device mapping disabled for non administrator users")
|
||||
}
|
||||
|
||||
if !settings.AllowContainerCapabilitiesForRegularUsers && (len(service.CapAdd) > 0 || len(service.CapDrop) > 0) {
|
||||
return errors.New("container capabilities disabled for non administrator users")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -22,6 +22,10 @@ func (handler *Handler) userDelete(w http.ResponseWriter, r *http.Request) *http
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve user authentication token", err}
|
||||
}
|
||||
|
||||
if userID == 1 {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "This feature is not available in the demo version of Portainer", portainer.ErrNotAvailableInDemo}
|
||||
}
|
||||
|
||||
if tokenData.ID == portainer.UserID(userID) {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Cannot remove your own user account. Contact another administrator", portainer.ErrAdminCannotRemoveSelf}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,10 @@ func (handler *Handler) userUpdate(w http.ResponseWriter, r *http.Request) *http
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid user identifier route variable", err}
|
||||
}
|
||||
|
||||
if userID == 1 {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "This feature is not available in the demo version of Portainer", portainer.ErrNotAvailableInDemo}
|
||||
}
|
||||
|
||||
tokenData, err := security.RetrieveTokenData(r)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve user authentication token", err}
|
||||
|
||||
@@ -33,6 +33,10 @@ func (handler *Handler) userUpdatePassword(w http.ResponseWriter, r *http.Reques
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid user identifier route variable", err}
|
||||
}
|
||||
|
||||
if userID == 1 {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "This feature is not available in the demo version of Portainer", portainer.ErrNotAvailableInDemo}
|
||||
}
|
||||
|
||||
tokenData, err := security.RetrieveTokenData(r)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve user authentication token", err}
|
||||
|
||||
@@ -68,7 +68,6 @@ func (factory *ProxyFactory) newDockerHTTPProxy(endpoint *portainer.Endpoint) (h
|
||||
ExtensionService: factory.extensionService,
|
||||
SignatureService: factory.signatureService,
|
||||
DockerClientFactory: factory.dockerClientFactory,
|
||||
AuthDisabled: factory.authDisabled,
|
||||
}
|
||||
|
||||
dockerTransport, err := docker.NewTransport(transportParameters, httpTransport)
|
||||
|
||||
@@ -159,9 +159,6 @@ func (transport *Transport) decorateContainerCreationOperation(request *http.Req
|
||||
Privileged bool `json:"Privileged"`
|
||||
PidMode string `json:"PidMode"`
|
||||
Devices []interface{} `json:"Devices"`
|
||||
CapAdd []string `json:"CapAdd"`
|
||||
CapDrop []string `json:"CapDrop"`
|
||||
Binds []string `json:"Binds"`
|
||||
} `json:"HostConfig"`
|
||||
}
|
||||
|
||||
@@ -174,12 +171,25 @@ func (transport *Transport) decorateContainerCreationOperation(request *http.Req
|
||||
return nil, err
|
||||
}
|
||||
|
||||
isAdminOrEndpointAdmin, err := transport.isAdminOrEndpointAdmin(request)
|
||||
user, err := transport.userService.User(tokenData.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !isAdminOrEndpointAdmin {
|
||||
rbacExtension, err := transport.extensionService.Extension(portainer.RBACExtension)
|
||||
if err != nil && err != portainer.ErrObjectNotFound {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
endpointResourceAccess := false
|
||||
_, ok := user.EndpointAuthorizations[portainer.EndpointID(transport.endpoint.ID)][portainer.EndpointResourcesAccess]
|
||||
if ok {
|
||||
endpointResourceAccess = true
|
||||
}
|
||||
|
||||
isAdmin := (rbacExtension != nil && endpointResourceAccess) || tokenData.Role == portainer.AdministratorRole
|
||||
|
||||
if !isAdmin {
|
||||
settings, err := transport.settingsService.Settings()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -187,9 +197,7 @@ func (transport *Transport) decorateContainerCreationOperation(request *http.Req
|
||||
|
||||
if !settings.AllowPrivilegedModeForRegularUsers ||
|
||||
!settings.AllowHostNamespaceForRegularUsers ||
|
||||
!settings.AllowDeviceMappingForRegularUsers ||
|
||||
!settings.AllowContainerCapabilitiesForRegularUsers ||
|
||||
!settings.AllowBindMountsForRegularUsers {
|
||||
!settings.AllowDeviceMappingForRegularUsers {
|
||||
|
||||
body, err := ioutil.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
@@ -202,26 +210,18 @@ func (transport *Transport) decorateContainerCreationOperation(request *http.Req
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !settings.AllowPrivilegedModeForRegularUsers && partialContainer.HostConfig.Privileged {
|
||||
if partialContainer.HostConfig.Privileged {
|
||||
return forbiddenResponse, errors.New("forbidden to use privileged mode")
|
||||
}
|
||||
|
||||
if !settings.AllowHostNamespaceForRegularUsers && partialContainer.HostConfig.PidMode == "host" {
|
||||
if partialContainer.HostConfig.PidMode == "host" {
|
||||
return forbiddenResponse, errors.New("forbidden to use pid host namespace")
|
||||
}
|
||||
|
||||
if !settings.AllowDeviceMappingForRegularUsers && len(partialContainer.HostConfig.Devices) > 0 {
|
||||
if len(partialContainer.HostConfig.Devices) > 0 {
|
||||
return nil, errors.New("forbidden to use device mapping")
|
||||
}
|
||||
|
||||
if !settings.AllowContainerCapabilitiesForRegularUsers && (len(partialContainer.HostConfig.CapAdd) > 0 || len(partialContainer.HostConfig.CapDrop) > 0) {
|
||||
return nil, errors.New("forbidden to use container capabilities")
|
||||
}
|
||||
|
||||
if !settings.AllowBindMountsForRegularUsers && (len(partialContainer.HostConfig.Binds) > 0) {
|
||||
return forbiddenResponse, errors.New("forbidden to use bind mounts")
|
||||
}
|
||||
|
||||
request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
@@ -88,54 +84,3 @@ func selectorServiceLabels(responseObject map[string]interface{}) map[string]int
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (transport *Transport) decorateServiceCreationOperation(request *http.Request) (*http.Response, error) {
|
||||
type PartialService struct {
|
||||
TaskTemplate struct {
|
||||
ContainerSpec struct {
|
||||
Mounts []struct {
|
||||
Type string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
forbiddenResponse := &http.Response{
|
||||
StatusCode: http.StatusForbidden,
|
||||
}
|
||||
|
||||
isAdminOrEndpointAdmin, err := transport.isAdminOrEndpointAdmin(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !isAdminOrEndpointAdmin {
|
||||
settings, err := transport.settingsService.Settings()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
partialService := &PartialService{}
|
||||
err = json.Unmarshal(body, partialService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !settings.AllowBindMountsForRegularUsers && (len(partialService.TaskTemplate.ContainerSpec.Mounts) > 0) {
|
||||
for _, mount := range partialService.TaskTemplate.ContainerSpec.Mounts {
|
||||
if mount.Type == "bind" {
|
||||
return forbiddenResponse, errors.New("forbidden to use bind mounts")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
|
||||
}
|
||||
|
||||
return transport.replaceRegistryAuthenticationHeader(request)
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ type (
|
||||
extensionService portainer.ExtensionService
|
||||
dockerClient *client.Client
|
||||
dockerClientFactory *docker.ClientFactory
|
||||
authDisabled bool
|
||||
}
|
||||
|
||||
// TransportParameters is used to create a new Transport
|
||||
@@ -55,7 +54,6 @@ type (
|
||||
ReverseTunnelService portainer.ReverseTunnelService
|
||||
ExtensionService portainer.ExtensionService
|
||||
DockerClientFactory *docker.ClientFactory
|
||||
AuthDisabled bool
|
||||
}
|
||||
|
||||
restrictedDockerOperationContext struct {
|
||||
@@ -96,7 +94,6 @@ func NewTransport(parameters *TransportParameters, httpTransport *http.Transport
|
||||
dockerClientFactory: parameters.DockerClientFactory,
|
||||
HTTPTransport: httpTransport,
|
||||
dockerClient: dockerClient,
|
||||
authDisabled: parameters.AuthDisabled,
|
||||
}
|
||||
|
||||
return transport, nil
|
||||
@@ -248,7 +245,7 @@ func (transport *Transport) proxyContainerRequest(request *http.Request) (*http.
|
||||
func (transport *Transport) proxyServiceRequest(request *http.Request) (*http.Response, error) {
|
||||
switch requestPath := request.URL.Path; requestPath {
|
||||
case "/services/create":
|
||||
return transport.decorateServiceCreationOperation(request)
|
||||
return transport.replaceRegistryAuthenticationHeader(request)
|
||||
|
||||
case "/services":
|
||||
return transport.rewriteOperation(request, transport.serviceListOperation)
|
||||
@@ -659,6 +656,7 @@ func (transport *Transport) createRegistryAccessContext(request *http.Request) (
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
accessContext := ®istryAccessContext{
|
||||
isAdmin: true,
|
||||
userID: tokenData.ID,
|
||||
@@ -736,36 +734,3 @@ func (transport *Transport) createOperationContext(request *http.Request) (*rest
|
||||
|
||||
return operationContext, nil
|
||||
}
|
||||
|
||||
func (transport *Transport) isAdminOrEndpointAdmin(request *http.Request) (bool, error) {
|
||||
if transport.authDisabled {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
tokenData, err := security.RetrieveTokenData(request)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if tokenData.Role == portainer.AdministratorRole {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
user, err := transport.userService.User(tokenData.ID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
rbacExtension, err := transport.extensionService.Extension(portainer.RBACExtension)
|
||||
if err != nil && err != portainer.ErrObjectNotFound {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if rbacExtension == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
_, endpointResourceAccess := user.EndpointAuthorizations[portainer.EndpointID(transport.endpoint.ID)][portainer.EndpointResourcesAccess]
|
||||
|
||||
return endpointResourceAccess, nil
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ func (factory ProxyFactory) newOSBasedLocalProxy(path string, endpoint *portaine
|
||||
ExtensionService: factory.extensionService,
|
||||
SignatureService: factory.signatureService,
|
||||
DockerClientFactory: factory.dockerClientFactory,
|
||||
AuthDisabled: factory.authDisabled,
|
||||
}
|
||||
|
||||
proxy := &dockerLocalProxy{}
|
||||
|
||||
@@ -25,7 +25,6 @@ func (factory ProxyFactory) newOSBasedLocalProxy(path string, endpoint *portaine
|
||||
ExtensionService: factory.extensionService,
|
||||
SignatureService: factory.signatureService,
|
||||
DockerClientFactory: factory.dockerClientFactory,
|
||||
AuthDisabled: factory.authDisabled,
|
||||
}
|
||||
|
||||
proxy := &dockerLocalProxy{}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/docker"
|
||||
)
|
||||
|
||||
@@ -32,7 +32,6 @@ type (
|
||||
reverseTunnelService portainer.ReverseTunnelService
|
||||
extensionService portainer.ExtensionService
|
||||
dockerClientFactory *docker.ClientFactory
|
||||
authDisabled bool
|
||||
}
|
||||
|
||||
// ProxyFactoryParameters is used to create a new ProxyFactory
|
||||
@@ -48,7 +47,6 @@ type (
|
||||
ReverseTunnelService portainer.ReverseTunnelService
|
||||
ExtensionService portainer.ExtensionService
|
||||
DockerClientFactory *docker.ClientFactory
|
||||
AuthDisabled bool
|
||||
}
|
||||
)
|
||||
|
||||
@@ -66,7 +64,6 @@ func NewProxyFactory(parameters *ProxyFactoryParameters) *ProxyFactory {
|
||||
reverseTunnelService: parameters.ReverseTunnelService,
|
||||
extensionService: parameters.ExtensionService,
|
||||
dockerClientFactory: parameters.DockerClientFactory,
|
||||
authDisabled: parameters.AuthDisabled,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
cmap "github.com/orcaman/concurrent-map"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/orcaman/concurrent-map"
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/docker"
|
||||
"github.com/portainer/portainer/api/http/proxy/factory"
|
||||
)
|
||||
@@ -34,7 +34,6 @@ type (
|
||||
ReverseTunnelService portainer.ReverseTunnelService
|
||||
ExtensionService portainer.ExtensionService
|
||||
DockerClientFactory *docker.ClientFactory
|
||||
AuthDisabled bool
|
||||
}
|
||||
)
|
||||
|
||||
@@ -52,7 +51,6 @@ func NewManager(parameters *ManagerParams) *Manager {
|
||||
ReverseTunnelService: parameters.ReverseTunnelService,
|
||||
ExtensionService: parameters.ExtensionService,
|
||||
DockerClientFactory: parameters.DockerClientFactory,
|
||||
AuthDisabled: parameters.AuthDisabled,
|
||||
}
|
||||
|
||||
return &Manager{
|
||||
|
||||
@@ -104,7 +104,6 @@ func (server *Server) Start() error {
|
||||
ReverseTunnelService: server.ReverseTunnelService,
|
||||
ExtensionService: server.ExtensionService,
|
||||
DockerClientFactory: server.DockerClientFactory,
|
||||
AuthDisabled: server.AuthDisabled,
|
||||
}
|
||||
proxyManager := proxy.NewManager(proxyManagerParameters)
|
||||
|
||||
@@ -248,7 +247,7 @@ func (server *Server) Start() error {
|
||||
settingsHandler.ExtensionService = server.ExtensionService
|
||||
settingsHandler.AuthorizationService = authorizationService
|
||||
|
||||
var stackHandler = stacks.NewHandler(requestBouncer, server.AuthDisabled)
|
||||
var stackHandler = stacks.NewHandler(requestBouncer)
|
||||
stackHandler.FileService = server.FileService
|
||||
stackHandler.StackService = server.StackService
|
||||
stackHandler.EndpointService = server.EndpointService
|
||||
|
||||
@@ -420,23 +420,22 @@ type (
|
||||
|
||||
// Settings represents the application settings
|
||||
Settings struct {
|
||||
LogoURL string `json:"LogoURL"`
|
||||
BlackListedLabels []Pair `json:"BlackListedLabels"`
|
||||
AuthenticationMethod AuthenticationMethod `json:"AuthenticationMethod"`
|
||||
LDAPSettings LDAPSettings `json:"LDAPSettings"`
|
||||
OAuthSettings OAuthSettings `json:"OAuthSettings"`
|
||||
AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"`
|
||||
AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"`
|
||||
AllowVolumeBrowserForRegularUsers bool `json:"AllowVolumeBrowserForRegularUsers"`
|
||||
SnapshotInterval string `json:"SnapshotInterval"`
|
||||
TemplatesURL string `json:"TemplatesURL"`
|
||||
EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
|
||||
EdgeAgentCheckinInterval int `json:"EdgeAgentCheckinInterval"`
|
||||
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
|
||||
AllowStackManagementForRegularUsers bool `json:"AllowStackManagementForRegularUsers"`
|
||||
AllowHostNamespaceForRegularUsers bool `json:"AllowHostNamespaceForRegularUsers"`
|
||||
AllowDeviceMappingForRegularUsers bool `json:"AllowDeviceMappingForRegularUsers"`
|
||||
AllowContainerCapabilitiesForRegularUsers bool `json:"AllowContainerCapabilitiesForRegularUsers"`
|
||||
LogoURL string `json:"LogoURL"`
|
||||
BlackListedLabels []Pair `json:"BlackListedLabels"`
|
||||
AuthenticationMethod AuthenticationMethod `json:"AuthenticationMethod"`
|
||||
LDAPSettings LDAPSettings `json:"LDAPSettings"`
|
||||
OAuthSettings OAuthSettings `json:"OAuthSettings"`
|
||||
AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"`
|
||||
AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"`
|
||||
AllowVolumeBrowserForRegularUsers bool `json:"AllowVolumeBrowserForRegularUsers"`
|
||||
SnapshotInterval string `json:"SnapshotInterval"`
|
||||
TemplatesURL string `json:"TemplatesURL"`
|
||||
EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
|
||||
EdgeAgentCheckinInterval int `json:"EdgeAgentCheckinInterval"`
|
||||
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
|
||||
AllowStackManagementForRegularUsers bool `json:"AllowStackManagementForRegularUsers"`
|
||||
AllowHostNamespaceForRegularUsers bool `json:"AllowHostNamespaceForRegularUsers"`
|
||||
AllowDeviceMappingForRegularUsers bool `json:"AllowDeviceMappingForRegularUsers"`
|
||||
|
||||
// Deprecated fields
|
||||
DisplayDonationHeader bool
|
||||
@@ -1011,7 +1010,7 @@ type (
|
||||
|
||||
const (
|
||||
// APIVersion is the version number of the Portainer API
|
||||
APIVersion = "1.24.2"
|
||||
APIVersion = "1.24.1"
|
||||
// DBVersion is the version number of the Portainer database
|
||||
DBVersion = 24
|
||||
// AssetsServerURL represents the URL of the Portainer asset server
|
||||
|
||||
@@ -1,81 +1,82 @@
|
||||
import _ from 'lodash-es';
|
||||
|
||||
angular.module('portainer.docker')
|
||||
.controller('ContainerNetworksDatatableController', ['$scope', '$controller', 'DatatableService',
|
||||
function ($scope, $controller, DatatableService) {
|
||||
angular.module('portainer.docker').controller('ContainerNetworksDatatableController', [
|
||||
'$scope',
|
||||
'$controller',
|
||||
'DatatableService',
|
||||
function ($scope, $controller, DatatableService) {
|
||||
angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
|
||||
this.state = Object.assign(this.state, {
|
||||
expandedItems: [],
|
||||
expandAll: true,
|
||||
});
|
||||
|
||||
angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
|
||||
this.state = Object.assign(this.state, {
|
||||
expandedItems: [],
|
||||
expandAll: true
|
||||
});
|
||||
|
||||
this.expandItem = function (item, expanded) {
|
||||
if (!this.itemCanExpand(item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
item.Expanded = expanded;
|
||||
if (!expanded) {
|
||||
item.Highlighted = false;
|
||||
}
|
||||
if (!item.Expanded) {
|
||||
this.state.expandAll = false;
|
||||
}
|
||||
};
|
||||
|
||||
this.itemCanExpand = function (item) {
|
||||
return item.GlobalIPv6Address !== '';
|
||||
this.expandItem = function (item, expanded) {
|
||||
if (!this.itemCanExpand(item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hasExpandableItems = function () {
|
||||
return _.filter(this.dataset, (item) => this.itemCanExpand(item)).length;
|
||||
};
|
||||
item.Expanded = expanded;
|
||||
if (!expanded) {
|
||||
item.Highlighted = false;
|
||||
}
|
||||
if (!item.Expanded) {
|
||||
this.state.expandAll = false;
|
||||
}
|
||||
};
|
||||
|
||||
this.expandAll = function () {
|
||||
this.state.expandAll = !this.state.expandAll;
|
||||
_.forEach(this.dataset, (item) => {
|
||||
if (this.itemCanExpand(item)) {
|
||||
this.expandItem(item, this.state.expandAll);
|
||||
}
|
||||
});
|
||||
};
|
||||
this.itemCanExpand = function (item) {
|
||||
return item.GlobalIPv6Address !== '';
|
||||
};
|
||||
|
||||
this.$onInit = function () {
|
||||
this.setDefaults();
|
||||
this.prepareTableFromDataset();
|
||||
this.hasExpandableItems = function () {
|
||||
return _.filter(this.dataset, (item) => this.itemCanExpand(item)).length;
|
||||
};
|
||||
|
||||
this.state.orderBy = this.orderBy;
|
||||
var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
|
||||
if (storedOrder !== null) {
|
||||
this.state.reverseOrder = storedOrder.reverse;
|
||||
this.state.orderBy = storedOrder.orderBy;
|
||||
this.expandAll = function () {
|
||||
this.state.expandAll = !this.state.expandAll;
|
||||
_.forEach(this.dataset, (item) => {
|
||||
if (this.itemCanExpand(item)) {
|
||||
this.expandItem(item, this.state.expandAll);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var textFilter = DatatableService.getDataTableTextFilters(this.tableKey);
|
||||
if (textFilter !== null) {
|
||||
this.state.textFilter = textFilter;
|
||||
this.onTextFilterChange();
|
||||
}
|
||||
this.$onInit = function () {
|
||||
this.setDefaults();
|
||||
this.prepareTableFromDataset();
|
||||
|
||||
var storedFilters = DatatableService.getDataTableFilters(this.tableKey);
|
||||
if (storedFilters !== null) {
|
||||
this.filters = storedFilters;
|
||||
}
|
||||
if (this.filters && this.filters.state) {
|
||||
this.filters.state.open = false;
|
||||
}
|
||||
this.state.orderBy = this.orderBy;
|
||||
var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
|
||||
if (storedOrder !== null) {
|
||||
this.state.reverseOrder = storedOrder.reverse;
|
||||
this.state.orderBy = storedOrder.orderBy;
|
||||
}
|
||||
|
||||
var storedSettings = DatatableService.getDataTableSettings(this.tableKey);
|
||||
if (storedSettings !== null) {
|
||||
this.settings = storedSettings;
|
||||
this.settings.open = false;
|
||||
}
|
||||
var textFilter = DatatableService.getDataTableTextFilters(this.tableKey);
|
||||
if (textFilter !== null) {
|
||||
this.state.textFilter = textFilter;
|
||||
this.onTextFilterChange();
|
||||
}
|
||||
|
||||
_.forEach(this.dataset, (item) => {
|
||||
item.Expanded = true;
|
||||
item.Highlighted = true;
|
||||
});
|
||||
};
|
||||
}
|
||||
]);
|
||||
var storedFilters = DatatableService.getDataTableFilters(this.tableKey);
|
||||
if (storedFilters !== null) {
|
||||
this.filters = storedFilters;
|
||||
}
|
||||
if (this.filters && this.filters.state) {
|
||||
this.filters.state.open = false;
|
||||
}
|
||||
|
||||
var storedSettings = DatatableService.getDataTableSettings(this.tableKey);
|
||||
if (storedSettings !== null) {
|
||||
this.settings = storedSettings;
|
||||
this.settings.open = false;
|
||||
}
|
||||
|
||||
_.forEach(this.dataset, (item) => {
|
||||
item.Expanded = true;
|
||||
item.Highlighted = true;
|
||||
});
|
||||
};
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -38,7 +38,14 @@
|
||||
<div class="form-group col-md-12">
|
||||
<label for="nfs_mountpoint" class="col-sm-2 col-md-1 control-label text-left">Mount point</label>
|
||||
<div class="col-sm-10 col-md-11">
|
||||
<input type="text" class="form-control" ng-model="$ctrl.data.mountPoint" name="nfs_mountpoint" placeholder="e.g. /export/share, :/export/share, /share or :/share" required />
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
ng-model="$ctrl.data.mountPoint"
|
||||
name="nfs_mountpoint"
|
||||
placeholder="e.g. /export/share, :/export/share, /share or :/share"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-12" ng-show="nfsInformationForm.nfs_mountpoint.$invalid">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ContainerDetailsViewModel, ContainerViewModel, ContainerStatsViewModel } from '../models/container';
|
||||
import { ContainerDetailsViewModel, ContainerStatsViewModel, ContainerViewModel } from '../models/container';
|
||||
|
||||
angular.module('portainer.docker').factory('ContainerService', [
|
||||
'$q',
|
||||
|
||||
@@ -610,11 +610,6 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||
$scope.formValues.NodeName = nodeName;
|
||||
HttpRequestHelper.setPortainerAgentTargetHeader(nodeName);
|
||||
|
||||
$scope.isAdmin = Authentication.isAdmin();
|
||||
$scope.isAdminOrEndpointAdmin = await checkIfAdminOrEndpointAdmin();
|
||||
$scope.showDeviceMapping = await shouldShowDevices($scope.isAdminOrEndpointAdmin);
|
||||
$scope.areContainerCapabilitiesEnabled = await checkIfContainerCapabilitiesEnabled($scope.isAdminOrEndpointAdmin);
|
||||
|
||||
Volume.query(
|
||||
{},
|
||||
function (d) {
|
||||
@@ -650,7 +645,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||
loadFromContainerSpec();
|
||||
} else {
|
||||
$scope.fromContainer = {};
|
||||
$scope.formValues.capabilities = $scope.areContainerCapabilitiesEnabled ? new ContainerCapabilities() : [];
|
||||
$scope.formValues.capabilities = new ContainerCapabilities();
|
||||
}
|
||||
},
|
||||
function (e) {
|
||||
@@ -677,7 +672,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||
|
||||
SettingsService.publicSettings()
|
||||
.then(function success(data) {
|
||||
$scope.allowBindMounts = $scope.isAdminOrEndpointAdmin || data.AllowBindMountsForRegularUsers;
|
||||
$scope.allowBindMounts = data.AllowBindMountsForRegularUsers;
|
||||
$scope.allowPrivilegedMode = data.AllowPrivilegedModeForRegularUsers;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
@@ -687,6 +682,9 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||
PluginService.loggingPlugins(apiVersion < 1.25).then(function success(loggingDrivers) {
|
||||
$scope.availableLoggingDrivers = loggingDrivers;
|
||||
});
|
||||
|
||||
$scope.isAdmin = Authentication.isAdmin();
|
||||
$scope.showDeviceMapping = await shouldShowDevices();
|
||||
}
|
||||
|
||||
function validateForm(accessControlData, isAdmin) {
|
||||
@@ -899,25 +897,17 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||
}
|
||||
}
|
||||
|
||||
async function shouldShowDevices(isAdminOrEndpointAdmin) {
|
||||
async function shouldShowDevices() {
|
||||
const isAdmin = !$scope.applicationState.application.authentication || Authentication.isAdmin();
|
||||
const { allowDeviceMappingForRegularUsers } = $scope.applicationState.application;
|
||||
|
||||
return allowDeviceMappingForRegularUsers || isAdminOrEndpointAdmin;
|
||||
}
|
||||
|
||||
async function checkIfContainerCapabilitiesEnabled(isAdminOrEndpointAdmin) {
|
||||
const { allowContainerCapabilitiesForRegularUsers } = $scope.applicationState.application;
|
||||
|
||||
return allowContainerCapabilitiesForRegularUsers || isAdminOrEndpointAdmin;
|
||||
}
|
||||
|
||||
async function checkIfAdminOrEndpointAdmin() {
|
||||
if (Authentication.isAdmin()) {
|
||||
if (isAdmin || allowDeviceMappingForRegularUsers) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const rbacEnabled = await ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC);
|
||||
return rbacEnabled ? Authentication.hasAuthorizations(['EndpointResourcesAccess']) : false;
|
||||
if (rbacEnabled) {
|
||||
return Authentication.hasAuthorizations(['EndpointResourcesAccess']);
|
||||
}
|
||||
}
|
||||
|
||||
initView();
|
||||
|
||||
@@ -185,7 +185,7 @@
|
||||
<li class="interactive"><a data-target="#labels" data-toggle="tab">Labels</a></li>
|
||||
<li class="interactive"><a data-target="#restart-policy" data-toggle="tab">Restart policy</a></li>
|
||||
<li class="interactive"><a data-target="#runtime-resources" ng-click="refreshSlider()" data-toggle="tab">Runtime & Resources</a></li>
|
||||
<li ng-if="areContainerCapabilitiesEnabled" class="interactive"><a data-target="#container-capabilities" data-toggle="tab">Capabilities</a></li>
|
||||
<li class="interactive"><a data-target="#container-capabilities" data-toggle="tab">Capabilities</a></li>
|
||||
</ul>
|
||||
<!-- tab-content -->
|
||||
<div class="tab-content">
|
||||
@@ -338,8 +338,8 @@
|
||||
</div>
|
||||
<!-- !container-path -->
|
||||
<!-- volume-type -->
|
||||
<div class="input-group col-sm-5" style="margin-left: 5px;">
|
||||
<div class="btn-group btn-group-sm" ng-if="allowBindMounts">
|
||||
<div class="input-group col-sm-5" style="margin-left: 5px;" ng-if="isAdmin || allowBindMounts">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<label class="btn btn-primary" ng-model="volume.type" uib-btn-radio="'volume'" ng-click="volume.name = ''">Volume</label>
|
||||
<label class="btn btn-primary" ng-model="volume.type" uib-btn-radio="'bind'" ng-click="volume.name = ''">Bind</label>
|
||||
</div>
|
||||
|
||||
@@ -33,7 +33,6 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
||||
'SettingsService',
|
||||
'WebhookService',
|
||||
'EndpointProvider',
|
||||
'ExtensionService',
|
||||
function (
|
||||
$q,
|
||||
$scope,
|
||||
@@ -59,8 +58,7 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
||||
NodeService,
|
||||
SettingsService,
|
||||
WebhookService,
|
||||
EndpointProvider,
|
||||
ExtensionService
|
||||
EndpointProvider
|
||||
) {
|
||||
$scope.formValues = {
|
||||
Name: '',
|
||||
@@ -108,8 +106,6 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
||||
actionInProgress: false,
|
||||
};
|
||||
|
||||
$scope.allowBindMounts = false;
|
||||
|
||||
$scope.refreshSlider = function () {
|
||||
$timeout(function () {
|
||||
$scope.$broadcast('rzSliderForceRender');
|
||||
@@ -566,8 +562,8 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
||||
secrets: apiVersion >= 1.25 ? SecretService.secrets() : [],
|
||||
configs: apiVersion >= 1.3 ? ConfigService.configs() : [],
|
||||
nodes: NodeService.nodes(),
|
||||
settings: SettingsService.publicSettings(),
|
||||
availableLoggingDrivers: PluginService.loggingPlugins(apiVersion < 1.25),
|
||||
allowBindMounts: checkIfAllowedBindMounts(),
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.availableVolumes = data.volumes;
|
||||
@@ -576,8 +572,8 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
||||
$scope.availableConfigs = data.configs;
|
||||
$scope.availableLoggingDrivers = data.availableLoggingDrivers;
|
||||
initSlidersMaxValuesBasedOnNodeData(data.nodes);
|
||||
$scope.allowBindMounts = data.settings.AllowBindMountsForRegularUsers;
|
||||
$scope.isAdmin = Authentication.isAdmin();
|
||||
$scope.allowBindMounts = data.allowBindMounts;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to initialize view');
|
||||
@@ -585,22 +581,5 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
||||
}
|
||||
|
||||
initView();
|
||||
|
||||
async function checkIfAllowedBindMounts() {
|
||||
const isAdmin = Authentication.isAdmin();
|
||||
|
||||
const settings = await SettingsService.publicSettings();
|
||||
const { AllowBindMountsForRegularUsers } = settings;
|
||||
|
||||
if (isAdmin || AllowBindMountsForRegularUsers) {
|
||||
return true;
|
||||
}
|
||||
const rbacEnabled = await ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC);
|
||||
if (rbacEnabled) {
|
||||
return Authentication.hasAuthorizations(['EndpointResourcesAccess']);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -305,7 +305,7 @@
|
||||
<!-- !container-path -->
|
||||
<!-- volume-type -->
|
||||
<div class="input-group col-sm-5" style="margin-left: 5px;">
|
||||
<div class="btn-group btn-group-sm" ng-if="allowBindMounts">
|
||||
<div class="btn-group btn-group-sm" ng-if="isAdmin || allowBindMounts">
|
||||
<label class="btn btn-primary" ng-model="volume.Type" uib-btn-radio="'volume'" ng-click="volume.name = ''">Volume</label>
|
||||
<label class="btn btn-primary" ng-model="volume.Type" uib-btn-radio="'bind'" ng-click="volume.Id = ''">Bind</label>
|
||||
</div>
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
<td colspan="2">
|
||||
<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
|
||||
|
||||
><p>
|
||||
<a
|
||||
authorization="DockerServiceLogs"
|
||||
|
||||
@@ -72,9 +72,7 @@
|
||||
<input type="checkbox" name="useNFS" ng-model="formValues.NFSData.useNFS" ng-click="formValues.CIFSData.useCIFS = false" />
|
||||
<i></i>
|
||||
</label>
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
<volumes-nfs-form data="formValues.NFSData" ng-show="formValues.Driver === 'local'"></volumes-nfs-form>
|
||||
<!-- !nfs-management -->
|
||||
@@ -87,9 +85,7 @@
|
||||
<input type="checkbox" name="useCIFS" ng-model="formValues.CIFSData.useCIFS" ng-click="formValues.NFSData.useNFS = false" />
|
||||
<i></i>
|
||||
</label>
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
<volumes-cifs-form data="formValues.CIFSData" ng-show="formValues.Driver === 'local'"></volumes-cifs-form>
|
||||
<!-- !cifs-management -->
|
||||
|
||||
@@ -73,16 +73,14 @@ angular.module('portainer.docker').controller('VolumesController', [
|
||||
|
||||
$scope.showBrowseAction = $scope.applicationState.endpoint.mode.agentProxy;
|
||||
|
||||
if ($scope.applicationState.application.authentication) {
|
||||
ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC).then(function success(extensionEnabled) {
|
||||
if (!extensionEnabled) {
|
||||
var isAdmin = Authentication.isAdmin();
|
||||
if (!$scope.applicationState.application.enableVolumeBrowserForNonAdminUsers && !isAdmin) {
|
||||
$scope.showBrowseAction = false;
|
||||
}
|
||||
ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC).then(function success(extensionEnabled) {
|
||||
if (!extensionEnabled) {
|
||||
var isAdmin = Authentication.isAdmin();
|
||||
if (!$scope.applicationState.application.enableVolumeBrowserForNonAdminUsers && !isAdmin) {
|
||||
$scope.showBrowseAction = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
<ui-select
|
||||
multiple
|
||||
ng-model="$ctrl.model"
|
||||
close-on-select="false"
|
||||
>
|
||||
<ui-select multiple ng-model="$ctrl.model" close-on-select="false">
|
||||
<ui-select-match placeholder="Select one or multiple group(s)">
|
||||
<span>
|
||||
{{ $item.Name }}
|
||||
</span>
|
||||
</ui-select-match>
|
||||
<ui-select-choices
|
||||
repeat="item.Id as item in $ctrl.items | filter: { Name: $select.search }"
|
||||
>
|
||||
<ui-select-choices repeat="item.Id as item in $ctrl.items | filter: { Name: $select.search }">
|
||||
<span>
|
||||
{{ item.Name }}
|
||||
</span>
|
||||
|
||||
@@ -2,6 +2,6 @@ angular.module('portainer.edge').component('edgeGroupsSelector', {
|
||||
templateUrl: './edge-groups-selector.html',
|
||||
bindings: {
|
||||
model: '=',
|
||||
items: '<'
|
||||
}
|
||||
items: '<',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
RoleViewModel,
|
||||
// EndpointRoleCreateRequest,
|
||||
// EndpointRoleUpdateRequest
|
||||
} from '../models/role';
|
||||
import { RoleViewModel } from '../models/role';
|
||||
|
||||
angular.module('portainer.extensions.rbac').factory('RoleService', [
|
||||
'$q',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import _ from 'lodash-es';
|
||||
|
||||
export function RegistryRepositoryViewModel(item) {
|
||||
if (item.name && item.tags) {
|
||||
this.Name = item.name;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import _ from 'lodash-es';
|
||||
import { RepositoryShortTag } from '../models/repositoryTag';
|
||||
import { RepositoryAddTagPayload } from '../models/repositoryTag';
|
||||
import { RepositoryAddTagPayload, RepositoryShortTag } from '../models/repositoryTag';
|
||||
import { RegistryRepositoryViewModel } from '../models/registryRepository';
|
||||
import genericAsyncGenerator from './genericAsyncGenerator';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import _ from 'lodash-es';
|
||||
import { RepositoryTagViewModel, RepositoryShortTag } from '../../../models/repositoryTag';
|
||||
import { RepositoryShortTag, RepositoryTagViewModel } from '../../../models/repositoryTag';
|
||||
|
||||
angular.module('portainer.app').controller('RegistryRepositoryController', [
|
||||
'$q',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { StoridgeNodeModel, StoridgeNodeDetailedModel } from '../models/node';
|
||||
import { StoridgeNodeDetailedModel, StoridgeNodeModel } from '../models/node';
|
||||
|
||||
angular.module('portainer.integrations.storidge').factory('StoridgeNodeService', [
|
||||
'$q',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import _ from 'lodash-es';
|
||||
import { ResourceControlOwnership as RCO } from 'Portainer/models/resourceControl/resourceControlOwnership';
|
||||
import { ResourceControlTypeString as RCTS, ResourceControlTypeInt as RCTI } from 'Portainer/models/resourceControl/resourceControlTypes';
|
||||
import { ResourceControlTypeInt as RCTI, ResourceControlTypeString as RCTS } from 'Portainer/models/resourceControl/resourceControlTypes';
|
||||
import { AccessControlPanelData } from './porAccessControlPanelModel';
|
||||
|
||||
angular.module('portainer.app').controller('porAccessControlPanelController', [
|
||||
|
||||
@@ -5,11 +5,7 @@
|
||||
<span ng-if="$item.TagIds.length"> - <i class="fa fa-tags"></i> {{ $ctrl.tagIdsToTagNames($item.TagIds) | arraytostr }}</span>
|
||||
</span>
|
||||
</ui-select-match>
|
||||
<ui-select-choices
|
||||
group-by="$ctrl.groupEndpoints"
|
||||
group-filter="$ctrl.sortGroups"
|
||||
repeat="endpoint.Id as endpoint in $ctrl.endpoints | filter: { Name: $select.search }"
|
||||
>
|
||||
<ui-select-choices group-by="$ctrl.groupEndpoints" group-filter="$ctrl.sortGroups" repeat="endpoint.Id as endpoint in $ctrl.endpoints | filter: { Name: $select.search }">
|
||||
<span>
|
||||
{{ endpoint.Name }}
|
||||
<span ng-if="endpoint.TagIds.length"> - <i class="fa fa-tags"></i> {{ $ctrl.tagIdsToTagNames(endpoint.TagIds) | arraytostr }}</span>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import _ from 'lodash-es';
|
||||
import { createStatus } from '../../docker/models/container';
|
||||
import {createStatus} from '../../docker/models/container';
|
||||
|
||||
export function ScheduleDefaultModel() {
|
||||
this.Name = '';
|
||||
|
||||
@@ -16,7 +16,6 @@ export function SettingsViewModel(data) {
|
||||
this.AllowStackManagementForRegularUsers = data.AllowStackManagementForRegularUsers;
|
||||
this.AllowHostNamespaceForRegularUsers = data.AllowHostNamespaceForRegularUsers;
|
||||
this.AllowDeviceMappingForRegularUsers = data.AllowDeviceMappingForRegularUsers;
|
||||
this.AllowContainerCapabilitiesForRegularUsers = data.AllowContainerCapabilitiesForRegularUsers;
|
||||
}
|
||||
|
||||
export function PublicSettingsViewModel(settings) {
|
||||
@@ -32,7 +31,6 @@ export function PublicSettingsViewModel(settings) {
|
||||
this.AllowStackManagementForRegularUsers = settings.AllowStackManagementForRegularUsers;
|
||||
this.AllowDeviceMappingForRegularUsers = settings.AllowDeviceMappingForRegularUsers;
|
||||
this.AllowHostNamespaceForRegularUsers = settings.AllowHostNamespaceForRegularUsers;
|
||||
this.AllowContainerCapabilitiesForRegularUsers = settings.AllowContainerCapabilitiesForRegularUsers;
|
||||
}
|
||||
|
||||
export function LDAPSettingsViewModel(data) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import _ from 'lodash-es';
|
||||
import { UserAccessViewModel } from '../../models/access';
|
||||
import { TeamAccessViewModel } from '../../models/access';
|
||||
import { TeamAccessViewModel, UserAccessViewModel } from '../../models/access';
|
||||
|
||||
angular.module('portainer.app').factory('AccessService', [
|
||||
'$q',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import _ from 'lodash-es';
|
||||
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||
import { RegistryTypes } from 'Extensions/registry-management/models/registryTypes';
|
||||
import { RegistryViewModel, RegistryCreateRequest } from '../../models/registry';
|
||||
import { RegistryCreateRequest, RegistryViewModel } from '../../models/registry';
|
||||
|
||||
angular.module('portainer.app').factory('RegistryService', [
|
||||
'$q',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ScheduleModel, ScheduleCreateRequest, ScheduleUpdateRequest, ScriptExecutionTaskModel } from '../../models/schedule';
|
||||
import { ScheduleCreateRequest, ScheduleModel, ScheduleUpdateRequest, ScriptExecutionTaskModel } from '../../models/schedule';
|
||||
|
||||
angular.module('portainer.app').factory('ScheduleService', [
|
||||
'$q',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SettingsViewModel, PublicSettingsViewModel } from '../../models/settings';
|
||||
import { PublicSettingsViewModel, SettingsViewModel } from '../../models/settings';
|
||||
|
||||
angular.module('portainer.app').factory('SettingsService', [
|
||||
'$q',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import _ from 'lodash-es';
|
||||
import { StackViewModel, ExternalStackViewModel } from '../../models/stack';
|
||||
import { ExternalStackViewModel, StackViewModel } from '../../models/stack';
|
||||
|
||||
angular.module('portainer.app').factory('StackService', [
|
||||
'$q',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TemplateViewModel, TemplateCreateRequest, TemplateUpdateRequest } from '../../models/template';
|
||||
import { TemplateCreateRequest, TemplateUpdateRequest, TemplateViewModel } from '../../models/template';
|
||||
|
||||
angular.module('portainer.app').factory('TemplateService', [
|
||||
'$q',
|
||||
|
||||
@@ -91,12 +91,11 @@ angular.module('portainer.app').factory('FileUploadService', [
|
||||
data: {
|
||||
file: file,
|
||||
Name: stackName,
|
||||
EdgeGroups: Upload.json(edgeGroups)
|
||||
EdgeGroups: Upload.json(edgeGroups),
|
||||
},
|
||||
ignoreLoadingBar: true
|
||||
ignoreLoadingBar: true,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
service.configureRegistry = function (registryId, registryManagementConfigurationModel) {
|
||||
return Upload.upload({
|
||||
|
||||
@@ -101,11 +101,6 @@ angular.module('portainer.app').factory('StateManager', [
|
||||
LocalStorage.storeApplicationState(state.application);
|
||||
};
|
||||
|
||||
manager.updateAllowContainerCapabilitiesForRegularUsers = function updateAllowContainerCapabilitiesForRegularUsers(allowContainerCapabilitiesForRegularUsers) {
|
||||
state.application.allowContainerCapabilitiesForRegularUsers = allowContainerCapabilitiesForRegularUsers;
|
||||
LocalStorage.storeApplicationState(state.application);
|
||||
};
|
||||
|
||||
function assignStateFromStatusAndSettings(status, settings) {
|
||||
state.application.authentication = status.Authentication;
|
||||
state.application.analytics = status.Analytics;
|
||||
@@ -122,7 +117,6 @@ angular.module('portainer.app').factory('StateManager', [
|
||||
state.application.allowPrivilegedModeForRegularUsers = settings.AllowPrivilegedModeForRegularUsers;
|
||||
state.application.allowDeviceMappingForRegularUsers = settings.AllowDeviceMappingForRegularUsers;
|
||||
state.application.allowHostNamespaceForRegularUsers = settings.AllowHostNamespaceForRegularUsers;
|
||||
state.application.allowContainerCapabilitiesForRegularUsers = settings.AllowContainerCapabilitiesForRegularUsers;
|
||||
state.application.validity = moment().unix();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
<rd-header-content>User settings</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row" ng-if="UserId === 1">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-exclamation-triangle" title="Feature not available"> </rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<span class="small text-muted">You cannot change the password of this account in the demo version of Portainer.</span>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
|
||||
@@ -36,10 +36,10 @@
|
||||
<div style="margin-top: 10px;">
|
||||
<uib-tabset active="state.deploymentTab">
|
||||
<uib-tab index="0" heading="Standalone">
|
||||
<code style="display: block; white-space: pre-wrap; padding: 16px 90px;">{{dockerCommands.standalone}}</code>
|
||||
<code style="display: block; white-space: pre-wrap; padding: 16px 90px;">{{ dockerCommands.standalone }}</code>
|
||||
</uib-tab>
|
||||
<uib-tab index="1" heading="Swarm">
|
||||
<code style="display: block; white-space: pre-wrap; padding: 16px 90px;">{{dockerCommands.swarm}}</code>
|
||||
<code style="display: block; white-space: pre-wrap; padding: 16px 90px;">{{ dockerCommands.swarm }}</code>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
<div style="margin-top: 10px;">
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
<rd-header-content>Settings</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row" ng-if="UserId === 1">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-exclamation-triangle" title="Feature not available"> </rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<span class="small text-muted">You cannot change the password of this account in the demo version of Portainer.</span>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
@@ -23,7 +34,10 @@
|
||||
<label for="toggle_logo" class="control-label text-left">
|
||||
Use custom logo
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" name="toggle_logo" ng-model="formValues.customLogo" /><i></i> </label>
|
||||
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" disabled name="toggle_logo" ng-model="formValues.customLogo" /><i></i> </label>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<span class="small text-muted">You cannot use this feature in the demo version of Portainer.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="formValues.customLogo">
|
||||
@@ -153,17 +167,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label for="toggle_disableContainerCapabilitiesForRegularUsers" class="control-label text-left">
|
||||
Disable container capabilities for non-administrators
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;">
|
||||
<input type="checkbox" name="toggle_disableContainerCapabilitiesForRegularUsers" ng-model="formValues.disableContainerCapabilitiesForRegularUsers" /><i></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="isContainerEditDisabled()">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
Note: The recreate/duplicate/edit feature is currently disabled (for non-admin users) by one or more security settings.
|
||||
|
||||
@@ -36,20 +36,11 @@ angular.module('portainer.app').controller('SettingsController', [
|
||||
allowStackManagementForRegularUsers: false,
|
||||
restrictHostNamespaceForRegularUsers: false,
|
||||
allowDeviceMappingForRegularUsers: false,
|
||||
disableContainerCapabilitiesForRegularUsers: false,
|
||||
};
|
||||
|
||||
$scope.isContainerEditDisabled = function isContainerEditDisabled() {
|
||||
const {
|
||||
restrictBindMounts,
|
||||
restrictHostNamespaceForRegularUsers,
|
||||
restrictPrivilegedMode,
|
||||
disableDeviceMappingForRegularUsers,
|
||||
disableContainerCapabilitiesForRegularUsers,
|
||||
} = this.formValues;
|
||||
return (
|
||||
restrictBindMounts || restrictHostNamespaceForRegularUsers || restrictPrivilegedMode || disableDeviceMappingForRegularUsers || disableContainerCapabilitiesForRegularUsers
|
||||
);
|
||||
const { restrictBindMounts, restrictHostNamespaceForRegularUsers, restrictPrivilegedMode, disableDeviceMappingForRegularUsers } = this.formValues;
|
||||
return restrictBindMounts || restrictHostNamespaceForRegularUsers || restrictPrivilegedMode || disableDeviceMappingForRegularUsers;
|
||||
};
|
||||
|
||||
$scope.removeFilteredContainerLabel = function (index) {
|
||||
@@ -89,7 +80,6 @@ angular.module('portainer.app').controller('SettingsController', [
|
||||
settings.AllowStackManagementForRegularUsers = !$scope.formValues.disableStackManagementForRegularUsers;
|
||||
settings.AllowHostNamespaceForRegularUsers = !$scope.formValues.restrictHostNamespaceForRegularUsers;
|
||||
settings.AllowDeviceMappingForRegularUsers = !$scope.formValues.disableDeviceMappingForRegularUsers;
|
||||
settings.AllowContainerCapabilitiesForRegularUsers = !$scope.formValues.disableContainerCapabilitiesForRegularUsers;
|
||||
|
||||
$scope.state.actionInProgress = true;
|
||||
updateSettings(settings);
|
||||
@@ -109,7 +99,6 @@ angular.module('portainer.app').controller('SettingsController', [
|
||||
StateManager.updateAllowDeviceMappingForRegularUsers(settings.AllowDeviceMappingForRegularUsers);
|
||||
StateManager.updateAllowPrivilegedModeForRegularUsers(settings.AllowPrivilegedModeForRegularUsers);
|
||||
StateManager.updateAllowBindMountsForRegularUsers(settings.AllowBindMountsForRegularUsers);
|
||||
StateManager.updateAllowContainerCapabilitiesForRegularUsers(settings.AllowContainerCapabilitiesForRegularUsers);
|
||||
$state.reload();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
@@ -139,7 +128,6 @@ angular.module('portainer.app').controller('SettingsController', [
|
||||
$scope.formValues.disableStackManagementForRegularUsers = !settings.AllowStackManagementForRegularUsers;
|
||||
$scope.formValues.restrictHostNamespaceForRegularUsers = !settings.AllowHostNamespaceForRegularUsers;
|
||||
$scope.formValues.disableDeviceMappingForRegularUsers = !settings.AllowDeviceMappingForRegularUsers;
|
||||
$scope.formValues.disableContainerCapabilitiesForRegularUsers = !settings.AllowContainerCapabilitiesForRegularUsers;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve application settings');
|
||||
|
||||
@@ -15,7 +15,6 @@ import 'angular-moment-picker/dist/angular-moment-picker.min.css';
|
||||
import 'angular-multiselect/isteven-multi-select.css';
|
||||
|
||||
import angular from 'angular';
|
||||
window.angular = angular;
|
||||
import 'moment';
|
||||
import '@uirouter/angularjs';
|
||||
import 'ui-select';
|
||||
@@ -39,3 +38,5 @@ import 'js-yaml/dist/js-yaml.js';
|
||||
import 'angular-ui-bootstrap';
|
||||
import 'angular-moment-picker';
|
||||
import 'angular-multiselect/isteven-multi-select.js';
|
||||
|
||||
window.angular = angular;
|
||||
|
||||
24
build/build_binary_azuredevops.ps1
Executable file
24
build/build_binary_azuredevops.ps1
Executable file
@@ -0,0 +1,24 @@
|
||||
param (
|
||||
[string]$platform,
|
||||
[string]$arch
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop";
|
||||
|
||||
$binary = "portainer.exe"
|
||||
$go_path = "$($(Get-ITEM -Path env:AGENT_TEMPDIRECTORY).Value)\go"
|
||||
|
||||
Set-Item env:GOPATH "$go_path"
|
||||
|
||||
New-Item -Name dist -Path "." -ItemType Directory -Force | Out-Null
|
||||
New-Item -Name portainer -Path "$go_path\src\github.com\portainer" -ItemType Directory -Force | Out-Null
|
||||
|
||||
Copy-Item -Path "api" -Destination "$go_path\src\github.com\portainer\portainer\api" -Recurse -Force
|
||||
|
||||
Set-Location -Path "api\cmd\portainer"
|
||||
|
||||
go get -t -d -v ./...
|
||||
## go build -v
|
||||
& cmd /c 'go build -v 2>&1'
|
||||
|
||||
Copy-Item -Path "portainer.exe" -Destination "$($env:BUILD_SOURCESDIRECTORY)\dist\portainer.exe" -Force
|
||||
@@ -1,8 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
PLATFORM=$1
|
||||
ARCH=$2
|
||||
|
||||
export GOPATH="/tmp/go"
|
||||
|
||||
binary="portainer"
|
||||
@@ -15,10 +10,6 @@ cp -R api ${GOPATH}/src/github.com/portainer/portainer/api
|
||||
cd 'api/cmd/portainer'
|
||||
|
||||
go get -t -d -v ./...
|
||||
GOOS=${PLATFORM} GOARCH=${ARCH} CGO_ENABLED=0 go build -a --installsuffix cgo --ldflags '-s'
|
||||
GOOS=$1 GOARCH=$2 CGO_ENABLED=0 go build -a --installsuffix cgo --ldflags '-s'
|
||||
|
||||
if [ "${PLATFORM}" == 'windows' ]; then
|
||||
mv "$BUILD_SOURCESDIRECTORY/api/cmd/portainer/${binary}.exe" "$BUILD_SOURCESDIRECTORY/dist/portainer.exe"
|
||||
else
|
||||
mv "$BUILD_SOURCESDIRECTORY/api/cmd/portainer/$binary" "$BUILD_SOURCESDIRECTORY/dist/portainer"
|
||||
fi
|
||||
mv "$BUILD_SOURCESDIRECTORY/api/cmd/portainer/$binary" "$BUILD_SOURCESDIRECTORY/dist/portainer"
|
||||
|
||||
13
build/download_docker_binary.ps1
Normal file
13
build/download_docker_binary.ps1
Normal file
@@ -0,0 +1,13 @@
|
||||
param (
|
||||
[string]$docker_version
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop";
|
||||
|
||||
New-Item -Path "docker-binary" -ItemType Directory | Out-Null
|
||||
|
||||
$download_folder = "docker-binary"
|
||||
|
||||
Invoke-WebRequest -O "$($download_folder)/docker-binaries.zip" "https://download.docker.com/win/static/stable/x86_64/docker-$($docker_version).zip"
|
||||
Expand-Archive -Path "$($download_folder)/docker-binaries.zip" -DestinationPath "$($download_folder)"
|
||||
Move-Item -Path "$($download_folder)/docker/docker.exe" -Destination "dist"
|
||||
@@ -1,14 +0,0 @@
|
||||
ARG OSVERSION
|
||||
FROM --platform=linux/amd64 gcr.io/k8s-staging-e2e-test-images/windows-servercore-cache:1.0-linux-amd64-${OSVERSION} as core
|
||||
FROM mcr.microsoft.com/windows/nanoserver:${OSVERSION}
|
||||
|
||||
COPY --from=core /Windows/System32/netapi32.dll /Windows/System32/netapi32.dll
|
||||
|
||||
USER ContainerAdministrator
|
||||
|
||||
COPY dist /
|
||||
|
||||
EXPOSE 9000
|
||||
EXPOSE 8000
|
||||
|
||||
ENTRYPOINT ["/portainer.exe"]
|
||||
9
build/windows2016/nanoserver/Dockerfile
Normal file
9
build/windows2016/nanoserver/Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
||||
FROM microsoft/nanoserver:sac2016
|
||||
|
||||
COPY dist /
|
||||
|
||||
WORKDIR /
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
ENTRYPOINT ["/portainer.exe"]
|
||||
25
gruntfile.js
25
gruntfile.js
@@ -142,7 +142,11 @@ function shell_build_binary(p, a) {
|
||||
}
|
||||
|
||||
function shell_build_binary_azuredevops(p, a) {
|
||||
return 'build/build_binary_azuredevops.sh ' + p + ' ' + a + ';';
|
||||
if (p === 'linux') {
|
||||
return 'build/build_binary_azuredevops.sh ' + p + ' ' + a + ';';
|
||||
} else {
|
||||
return 'powershell -Command ".\\build\\build_binary_azuredevops.ps1 -platform ' + p + ' -arch ' + a + '"';
|
||||
}
|
||||
}
|
||||
|
||||
function shell_run_container() {
|
||||
@@ -160,12 +164,15 @@ function shell_download_docker_binary(p, a) {
|
||||
var ip = ps[p] === undefined ? p : ps[p];
|
||||
var ia = as[a] === undefined ? a : as[a];
|
||||
var binaryVersion = p === 'windows' ? '<%= shippedDockerVersionWindows %>' : '<%= shippedDockerVersion %>';
|
||||
|
||||
return [
|
||||
'if [ -f dist/docker ] || [ -f dist/docker.exe ]; then',
|
||||
'echo "docker binary exists";',
|
||||
'else',
|
||||
'build/download_docker_binary.sh ' + ip + ' ' + ia + ' ' + binaryVersion + ';',
|
||||
'fi',
|
||||
].join(' ');
|
||||
if (p === 'linux' || p === 'mac') {
|
||||
return ['if [ -f dist/docker ]; then', 'echo "Docker binary exists";', 'else', 'build/download_docker_binary.sh ' + ip + ' ' + ia + ' ' + binaryVersion + ';', 'fi'].join(' ');
|
||||
} else {
|
||||
return [
|
||||
'powershell -Command "& {if (Get-Item -Path dist/docker.exe -ErrorAction:SilentlyContinue) {',
|
||||
'Write-Host "Docker binary exists"',
|
||||
'} else {',
|
||||
'& ".\\build\\download_docker_binary.ps1" -docker_version ' + binaryVersion + '',
|
||||
'}}"',
|
||||
].join(' ');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Portainer.io",
|
||||
"name": "portainer",
|
||||
"homepage": "http://portainer.io",
|
||||
"version": "1.24.2",
|
||||
"version": "1.24.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:portainer/portainer.git"
|
||||
|
||||
Reference in New Issue
Block a user