diff --git a/api/bolt/migrator/migrate_dbversion14.go b/api/bolt/migrator/migrate_dbversion14.go
new file mode 100644
index 000000000..f74e4ae05
--- /dev/null
+++ b/api/bolt/migrator/migrate_dbversion14.go
@@ -0,0 +1,11 @@
+package migrator
+
+func (m *Migrator) updateSettingsToDBVersion15() error {
+ legacySettings, err := m.settingsService.Settings()
+ if err != nil {
+ return err
+ }
+
+ legacySettings.EnableHostManagementFeatures = false
+ return m.settingsService.UpdateSettings(legacySettings)
+}
diff --git a/api/bolt/migrator/migrator.go b/api/bolt/migrator/migrator.go
index 4d05820aa..1fc16c930 100644
--- a/api/bolt/migrator/migrator.go
+++ b/api/bolt/migrator/migrator.go
@@ -186,5 +186,13 @@ func (m *Migrator) Migrate() error {
}
}
+ // Portainer 1.20-dev
+ if m.currentDBVersion < 15 {
+ err := m.updateSettingsToDBVersion15()
+ if err != nil {
+ return err
+ }
+ }
+
return m.versionService.StoreDBVersion(portainer.DBVersion)
}
diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go
index 5cf99b3d7..7e5b4f581 100644
--- a/api/cmd/portainer/main.go
+++ b/api/cmd/portainer/main.go
@@ -256,6 +256,7 @@ func initSettings(settingsService portainer.SettingsService, flags *portainer.CL
},
AllowBindMountsForRegularUsers: true,
AllowPrivilegedModeForRegularUsers: true,
+ EnableHostManagementFeatures: false,
SnapshotInterval: *flags.SnapshotInterval,
}
diff --git a/api/errors.go b/api/errors.go
index da6c4edfe..6aeec542e 100644
--- a/api/errors.go
+++ b/api/errors.go
@@ -93,6 +93,11 @@ const (
ErrUnableToPingEndpoint = Error("Unable to communicate with the endpoint")
)
+// Schedule errors.
+const (
+ ErrHostManagementFeaturesDisabled = Error("Host management features are disabled")
+)
+
// Error represents an application error.
type Error string
diff --git a/api/http/handler/schedules/handler.go b/api/http/handler/schedules/handler.go
index 4c5b64bbd..c2c091209 100644
--- a/api/http/handler/schedules/handler.go
+++ b/api/http/handler/schedules/handler.go
@@ -14,6 +14,7 @@ type Handler struct {
*mux.Router
ScheduleService portainer.ScheduleService
EndpointService portainer.EndpointService
+ SettingsService portainer.SettingsService
FileService portainer.FileService
JobService portainer.JobService
JobScheduler portainer.JobScheduler
diff --git a/api/http/handler/schedules/schedule_create.go b/api/http/handler/schedules/schedule_create.go
index 4dca4c3f3..893ec49f3 100644
--- a/api/http/handler/schedules/schedule_create.go
+++ b/api/http/handler/schedules/schedule_create.go
@@ -113,6 +113,14 @@ func (payload *scheduleCreateFromFileContentPayload) Validate(r *http.Request) e
// POST /api/schedules?method=file/string
func (handler *Handler) scheduleCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
+ settings, err := handler.SettingsService.Settings()
+ if err != nil {
+ return &httperror.HandlerError{http.StatusServiceUnavailable, "Unable to retrieve settings", err}
+ }
+ if !settings.EnableHostManagementFeatures {
+ return &httperror.HandlerError{http.StatusServiceUnavailable, "Host management features are disabled", portainer.ErrHostManagementFeaturesDisabled}
+ }
+
method, err := request.RetrieveQueryParameter(r, "method", false)
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid query parameter: method. Valid values are: file or string", err}
diff --git a/api/http/handler/schedules/schedule_delete.go b/api/http/handler/schedules/schedule_delete.go
index 7597fb6c0..67a02010a 100644
--- a/api/http/handler/schedules/schedule_delete.go
+++ b/api/http/handler/schedules/schedule_delete.go
@@ -12,6 +12,14 @@ import (
)
func (handler *Handler) scheduleDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
+ settings, err := handler.SettingsService.Settings()
+ if err != nil {
+ return &httperror.HandlerError{http.StatusServiceUnavailable, "Unable to retrieve settings", err}
+ }
+ if !settings.EnableHostManagementFeatures {
+ return &httperror.HandlerError{http.StatusServiceUnavailable, "Host management features are disabled", portainer.ErrHostManagementFeaturesDisabled}
+ }
+
scheduleID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid schedule identifier route variable", err}
diff --git a/api/http/handler/schedules/schedule_file.go b/api/http/handler/schedules/schedule_file.go
index 790f4d2e4..f24e10867 100644
--- a/api/http/handler/schedules/schedule_file.go
+++ b/api/http/handler/schedules/schedule_file.go
@@ -16,6 +16,14 @@ type scheduleFileResponse struct {
// GET request on /api/schedules/:id/file
func (handler *Handler) scheduleFile(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
+ settings, err := handler.SettingsService.Settings()
+ if err != nil {
+ return &httperror.HandlerError{http.StatusServiceUnavailable, "Unable to retrieve settings", err}
+ }
+ if !settings.EnableHostManagementFeatures {
+ return &httperror.HandlerError{http.StatusServiceUnavailable, "Host management features are disabled", portainer.ErrHostManagementFeaturesDisabled}
+ }
+
scheduleID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid schedule identifier route variable", err}
diff --git a/api/http/handler/schedules/schedule_inspect.go b/api/http/handler/schedules/schedule_inspect.go
index 9b721801e..bcd74b4b9 100644
--- a/api/http/handler/schedules/schedule_inspect.go
+++ b/api/http/handler/schedules/schedule_inspect.go
@@ -11,6 +11,14 @@ import (
)
func (handler *Handler) scheduleInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
+ settings, err := handler.SettingsService.Settings()
+ if err != nil {
+ return &httperror.HandlerError{http.StatusServiceUnavailable, "Unable to retrieve settings", err}
+ }
+ if !settings.EnableHostManagementFeatures {
+ return &httperror.HandlerError{http.StatusServiceUnavailable, "Host management features are disabled", portainer.ErrHostManagementFeaturesDisabled}
+ }
+
scheduleID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid schedule identifier route variable", err}
diff --git a/api/http/handler/schedules/schedule_list.go b/api/http/handler/schedules/schedule_list.go
index 4bc658b91..f67eee452 100644
--- a/api/http/handler/schedules/schedule_list.go
+++ b/api/http/handler/schedules/schedule_list.go
@@ -5,10 +5,19 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/response"
+ "github.com/portainer/portainer"
)
// GET request on /api/schedules
func (handler *Handler) scheduleList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
+ settings, err := handler.SettingsService.Settings()
+ if err != nil {
+ return &httperror.HandlerError{http.StatusServiceUnavailable, "Unable to retrieve settings", err}
+ }
+ if !settings.EnableHostManagementFeatures {
+ return &httperror.HandlerError{http.StatusServiceUnavailable, "Host management features are disabled", portainer.ErrHostManagementFeaturesDisabled}
+ }
+
schedules, err := handler.ScheduleService.Schedules()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve schedules from the database", err}
diff --git a/api/http/handler/schedules/schedule_tasks.go b/api/http/handler/schedules/schedule_tasks.go
index da88fdac7..0cbf24372 100644
--- a/api/http/handler/schedules/schedule_tasks.go
+++ b/api/http/handler/schedules/schedule_tasks.go
@@ -22,6 +22,14 @@ type taskContainer struct {
// GET request on /api/schedules/:id/tasks
func (handler *Handler) scheduleTasks(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
+ settings, err := handler.SettingsService.Settings()
+ if err != nil {
+ return &httperror.HandlerError{http.StatusServiceUnavailable, "Unable to retrieve settings", err}
+ }
+ if !settings.EnableHostManagementFeatures {
+ return &httperror.HandlerError{http.StatusServiceUnavailable, "Host management features are disabled", portainer.ErrHostManagementFeaturesDisabled}
+ }
+
scheduleID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid schedule identifier route variable", err}
diff --git a/api/http/handler/schedules/schedule_update.go b/api/http/handler/schedules/schedule_update.go
index ed680dfa1..7e741631d 100644
--- a/api/http/handler/schedules/schedule_update.go
+++ b/api/http/handler/schedules/schedule_update.go
@@ -31,6 +31,14 @@ func (payload *scheduleUpdatePayload) Validate(r *http.Request) error {
}
func (handler *Handler) scheduleUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
+ settings, err := handler.SettingsService.Settings()
+ if err != nil {
+ return &httperror.HandlerError{http.StatusServiceUnavailable, "Unable to retrieve settings", err}
+ }
+ if !settings.EnableHostManagementFeatures {
+ return &httperror.HandlerError{http.StatusServiceUnavailable, "Host management features are disabled", portainer.ErrHostManagementFeaturesDisabled}
+ }
+
scheduleID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid schedule identifier route variable", err}
diff --git a/api/http/handler/settings/settings_public.go b/api/http/handler/settings/settings_public.go
index 549cf999e..9744b319a 100644
--- a/api/http/handler/settings/settings_public.go
+++ b/api/http/handler/settings/settings_public.go
@@ -13,6 +13,7 @@ type publicSettingsResponse struct {
AuthenticationMethod portainer.AuthenticationMethod `json:"AuthenticationMethod"`
AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"`
AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"`
+ EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
ExternalTemplates bool `json:"ExternalTemplates"`
}
@@ -28,6 +29,7 @@ func (handler *Handler) settingsPublic(w http.ResponseWriter, r *http.Request) *
AuthenticationMethod: settings.AuthenticationMethod,
AllowBindMountsForRegularUsers: settings.AllowBindMountsForRegularUsers,
AllowPrivilegedModeForRegularUsers: settings.AllowPrivilegedModeForRegularUsers,
+ EnableHostManagementFeatures: settings.EnableHostManagementFeatures,
ExternalTemplates: false,
}
diff --git a/api/http/handler/settings/settings_update.go b/api/http/handler/settings/settings_update.go
index bef9cee9e..5b4d33ae3 100644
--- a/api/http/handler/settings/settings_update.go
+++ b/api/http/handler/settings/settings_update.go
@@ -18,6 +18,7 @@ type settingsUpdatePayload struct {
LDAPSettings *portainer.LDAPSettings
AllowBindMountsForRegularUsers *bool
AllowPrivilegedModeForRegularUsers *bool
+ EnableHostManagementFeatures *bool
SnapshotInterval *string
TemplatesURL *string
}
@@ -76,6 +77,10 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
settings.AllowPrivilegedModeForRegularUsers = *payload.AllowPrivilegedModeForRegularUsers
}
+ if payload.EnableHostManagementFeatures != nil {
+ settings.EnableHostManagementFeatures = *payload.EnableHostManagementFeatures
+ }
+
if payload.SnapshotInterval != nil && *payload.SnapshotInterval != settings.SnapshotInterval {
err := handler.updateSnapshotInterval(settings, *payload.SnapshotInterval)
if err != nil {
diff --git a/api/http/server.go b/api/http/server.go
index 8bcfb6921..461a1f4ee 100644
--- a/api/http/server.go
+++ b/api/http/server.go
@@ -140,6 +140,7 @@ func (server *Server) Start() error {
schedulesHandler.FileService = server.FileService
schedulesHandler.JobService = server.JobService
schedulesHandler.JobScheduler = server.JobScheduler
+ schedulesHandler.SettingsService = server.SettingsService
var settingsHandler = settings.NewHandler(requestBouncer)
settingsHandler.SettingsService = server.SettingsService
diff --git a/api/portainer.go b/api/portainer.go
index 634d8635c..1c6d103c6 100644
--- a/api/portainer.go
+++ b/api/portainer.go
@@ -89,6 +89,7 @@ type (
AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"`
SnapshotInterval string `json:"SnapshotInterval"`
TemplatesURL string `json:"TemplatesURL"`
+ EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
// Deprecated fields
DisplayDonationHeader bool
@@ -712,7 +713,7 @@ const (
// APIVersion is the version number of the Portainer API
APIVersion = "1.20-dev"
// DBVersion is the version number of the Portainer database
- DBVersion = 14
+ DBVersion = 15
// MessageOfTheDayURL represents the URL where Portainer MOTD message can be retrieved
MessageOfTheDayURL = "https://portainer-io-assets.sfo2.digitaloceanspaces.com/motd.html"
// PortainerAgentHeader represents the name of the header available in any agent response
diff --git a/app/docker/components/host-overview/host-overview.html b/app/docker/components/host-overview/host-overview.html
index c54e0b052..968c09708 100644
--- a/app/docker/components/host-overview/host-overview.html
+++ b/app/docker/components/host-overview/host-overview.html
@@ -12,23 +12,23 @@