mirror of https://github.com/portainer/portainer
feat(app): add the capability to enable/disable host management features (#2472)
* feat(settings): add the capability to enable/disable the host management features * feat(settings): remove the validation of EnableHostManagementFeatures in frontend * feat(api): disable schedules API when HostManagementFeatures is false + DB migration * style(settings): update host management settings tooltip * refacot(schedules): update DBVersion to 15pull/2449/head
parent
101bb41587
commit
a9b107dbb5
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -256,6 +256,7 @@ func initSettings(settingsService portainer.SettingsService, flags *portainer.CL
|
|||
},
|
||||
AllowBindMountsForRegularUsers: true,
|
||||
AllowPrivilegedModeForRegularUsers: true,
|
||||
EnableHostManagementFeatures: false,
|
||||
SnapshotInterval: *flags.SnapshotInterval,
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -12,23 +12,23 @@
|
|||
|
||||
<host-details-panel
|
||||
host="$ctrl.hostDetails"
|
||||
is-browse-enabled="$ctrl.isAgent && $ctrl.agentApiVersion > 1 && !$ctrl.offlineMode"
|
||||
is-browse-enabled="$ctrl.isAgent && $ctrl.agentApiVersion > 1 && !$ctrl.offlineMode && $ctrl.hostFeaturesEnabled"
|
||||
browse-url="{{$ctrl.browseUrl}}"
|
||||
is-job-enabled="$ctrl.isJobEnabled && !$ctrl.offlineMode"
|
||||
is-job-enabled="$ctrl.isJobEnabled && !$ctrl.offlineMode && $ctrl.hostFeaturesEnabled"
|
||||
job-url="{{$ctrl.jobUrl}}"
|
||||
></host-details-panel>
|
||||
|
||||
<engine-details-panel engine="$ctrl.engineDetails"></engine-details-panel>
|
||||
|
||||
<jobs-datatable
|
||||
ng-if="$ctrl.isJobEnabled && $ctrl.jobs && !$ctrl.offlineMode"
|
||||
ng-if="$ctrl.isJobEnabled && $ctrl.jobs && !$ctrl.offlineMode && $ctrl.hostFeaturesEnabled"
|
||||
title-text="Jobs" title-icon="fa-tasks"
|
||||
dataset="$ctrl.jobs"
|
||||
table-key="jobs"
|
||||
order-by="Created" reverse-order="true"
|
||||
></jobs-datatable>
|
||||
|
||||
<devices-panel ng-if="$ctrl.isAgent && $ctrl.agentApiVersion > 1 && !$ctrl.offlineMode" devices="$ctrl.devices"></devices-panel>
|
||||
<disks-panel ng-if="$ctrl.isAgent && $ctrl.agentApiVersion > 1 && !$ctrl.offlineMode" disks="$ctrl.disks"></disks-panel>
|
||||
<devices-panel ng-if="$ctrl.isAgent && $ctrl.agentApiVersion > 1 && !$ctrl.offlineMode && $ctrl.hostFeaturesEnabled" devices="$ctrl.devices"></devices-panel>
|
||||
<disks-panel ng-if="$ctrl.isAgent && $ctrl.agentApiVersion > 1 && !$ctrl.offlineMode && $ctrl.hostFeaturesEnabled" disks="$ctrl.disks"></disks-panel>
|
||||
|
||||
<ng-transclude></ng-transclude>
|
||||
|
|
|
@ -12,6 +12,7 @@ angular.module('portainer.docker').component('hostOverview', {
|
|||
browseUrl: '@',
|
||||
jobUrl: '@',
|
||||
isJobEnabled: '<',
|
||||
hostFeaturesEnabled: '<',
|
||||
jobs: '<'
|
||||
},
|
||||
transclude: true
|
||||
|
|
|
@ -22,6 +22,7 @@ angular.module('portainer.docker').controller('HostViewController', [
|
|||
ctrl.state.isAdmin = Authentication.getUserDetails().role === 1;
|
||||
var agentApiVersion = applicationState.endpoint.agentApiVersion;
|
||||
ctrl.state.agentApiVersion = agentApiVersion;
|
||||
ctrl.state.enableHostManagementFeatures = applicationState.application.enableHostManagementFeatures;
|
||||
|
||||
$q.all({
|
||||
version: SystemService.version(),
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
browse-url="docker.host.browser"
|
||||
offline-mode="$ctrl.state.offlineMode"
|
||||
is-job-enabled="$ctrl.state.isAdmin && !$ctrl.state.offlineMode"
|
||||
host-features-enabled="$ctrl.state.enableHostManagementFeatures"
|
||||
job-url="docker.host.job"
|
||||
jobs="$ctrl.jobs"
|
||||
></host-overview>
|
||||
|
|
|
@ -14,6 +14,7 @@ angular.module('portainer.docker').controller('NodeDetailsViewController', [
|
|||
var applicationState = StateManager.getState();
|
||||
ctrl.state.isAgent = applicationState.endpoint.mode.agentProxy;
|
||||
ctrl.state.isAdmin = Authentication.getUserDetails().role === 1;
|
||||
ctrl.state.enableHostManagementFeatures = applicationState.application.enableHostManagementFeatures;
|
||||
|
||||
var fetchJobs = ctrl.state.isAdmin && ctrl.state.isAgent;
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
refresh-url="docker.nodes.node"
|
||||
browse-url="docker.nodes.node.browse"
|
||||
is-job-enabled="$ctrl.state.isAdmin && $ctrl.state.isAgent"
|
||||
host-features-enabled="$ctrl.state.enableHostManagementFeatures"
|
||||
job-url="docker.nodes.node.job"
|
||||
jobs="$ctrl.jobs"
|
||||
>
|
||||
|
|
|
@ -8,6 +8,7 @@ function SettingsViewModel(data) {
|
|||
this.SnapshotInterval = data.SnapshotInterval;
|
||||
this.TemplatesURL = data.TemplatesURL;
|
||||
this.ExternalTemplates = data.ExternalTemplates;
|
||||
this.EnableHostManagementFeatures = data.EnableHostManagementFeatures;
|
||||
}
|
||||
|
||||
function LDAPSettingsViewModel(data) {
|
||||
|
|
|
@ -43,6 +43,11 @@ function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, Settin
|
|||
LocalStorage.storeApplicationState(state.application);
|
||||
};
|
||||
|
||||
manager.updateEnableHostManagementFeatures = function(enableHostManagementFeatures) {
|
||||
state.application.enableHostManagementFeatures = enableHostManagementFeatures;
|
||||
LocalStorage.storeApplicationState(state.application);
|
||||
};
|
||||
|
||||
function assignStateFromStatusAndSettings(status, settings) {
|
||||
state.application.authentication = status.Authentication;
|
||||
state.application.analytics = status.Analytics;
|
||||
|
@ -51,6 +56,7 @@ function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, Settin
|
|||
state.application.version = status.Version;
|
||||
state.application.logo = settings.LogoURL;
|
||||
state.application.snapshotInterval = settings.SnapshotInterval;
|
||||
state.application.enableHostManagementFeatures = settings.EnableHostManagementFeatures;
|
||||
state.application.validity = moment().unix();
|
||||
}
|
||||
|
||||
|
|
|
@ -101,6 +101,17 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label for="toggle_enableHostManagementFeatures" class="control-label text-left">
|
||||
Enable host management features
|
||||
<portainer-tooltip position="bottom" message="Enables host management features: host scheduler, host browsing and command execution. Requires an agent setup."></portainer-tooltip>
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;">
|
||||
<input type="checkbox" name="toggle_enableHostManagementFeatures" ng-model="formValues.enableHostManagementFeatures"><i></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- security -->
|
||||
<!-- actions -->
|
||||
<div class="form-group">
|
||||
|
|
|
@ -12,7 +12,8 @@ function ($scope, $state, Notifications, SettingsService, StateManager) {
|
|||
restrictBindMounts: false,
|
||||
restrictPrivilegedMode: false,
|
||||
labelName: '',
|
||||
labelValue: ''
|
||||
labelValue: '',
|
||||
enableHostManagementFeatures: false
|
||||
};
|
||||
|
||||
$scope.removeFilteredContainerLabel = function(index) {
|
||||
|
@ -46,6 +47,7 @@ function ($scope, $state, Notifications, SettingsService, StateManager) {
|
|||
|
||||
settings.AllowBindMountsForRegularUsers = !$scope.formValues.restrictBindMounts;
|
||||
settings.AllowPrivilegedModeForRegularUsers = !$scope.formValues.restrictPrivilegedMode;
|
||||
settings.EnableHostManagementFeatures = $scope.formValues.enableHostManagementFeatures;
|
||||
|
||||
$scope.state.actionInProgress = true;
|
||||
updateSettings(settings);
|
||||
|
@ -57,6 +59,7 @@ function ($scope, $state, Notifications, SettingsService, StateManager) {
|
|||
Notifications.success('Settings updated');
|
||||
StateManager.updateLogo(settings.LogoURL);
|
||||
StateManager.updateSnapshotInterval(settings.SnapshotInterval);
|
||||
StateManager.updateEnableHostManagementFeatures(settings.EnableHostManagementFeatures);
|
||||
$state.reload();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
|
@ -80,6 +83,7 @@ function ($scope, $state, Notifications, SettingsService, StateManager) {
|
|||
}
|
||||
$scope.formValues.restrictBindMounts = !settings.AllowBindMountsForRegularUsers;
|
||||
$scope.formValues.restrictPrivilegedMode = !settings.AllowPrivilegedModeForRegularUsers;
|
||||
$scope.formValues.enableHostManagementFeatures = settings.EnableHostManagementFeatures;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve application settings');
|
||||
|
|
|
@ -36,10 +36,10 @@
|
|||
<a ui-sref="storidge.profiles" ui-sref-active="active">Profiles</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="sidebar-title" ng-if="!applicationState.application.authentication || isAdmin">
|
||||
<li class="sidebar-title" ng-if="(!applicationState.application.authentication || isAdmin) && applicationState.application.enableHostManagementFeatures">
|
||||
<span>Scheduler</span>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="!applicationState.application.authentication || isAdmin">
|
||||
<li class="sidebar-list" ng-if="(!applicationState.application.authentication || isAdmin) && applicationState.application.enableHostManagementFeatures">
|
||||
<a ui-sref="portainer.schedules" ui-sref-active="active">Host jobs <span class="menu-icon fa fa-clock fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-title" ng-if="!applicationState.application.authentication || isAdmin || isTeamLeader">
|
||||
|
|
Loading…
Reference in New Issue