1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-06 14:25:31 +02:00

feat(extensions): introduce RBAC extension (#2900)

This commit is contained in:
Anthony Lapenna 2019-05-24 18:04:58 +12:00 committed by GitHub
parent 27a0188949
commit 8057aa45c4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
196 changed files with 3321 additions and 1316 deletions

View file

@ -1,7 +1,7 @@
<div class="datatable">
<rd-widget>
<rd-widget-header icon="{{$ctrl.titleIcon}}" title-text="{{ $ctrl.titleText }}">
<file-uploader ng-if="$ctrl.isUploadAllowed" on-file-selected="$ctrl.onFileSelectedForUpload">
<file-uploader authorization="DockerAgentBrowsePut" ng-if="$ctrl.isUploadAllowed" on-file-selected="$ctrl.onFileSelectedForUpload">
</file-uploader>
</rd-widget-header>
<rd-widget-body classes="no-padding">
@ -71,14 +71,14 @@
{{ item.ModTime | getisodatefromtimestamp }}
</td>
<td>
<btn class="btn btn-xs btn-primary space-right" ng-click="$ctrl.download({ name: item.Name })"
<btn authorization="DockerAgentBrowseGet" class="btn btn-xs btn-primary space-right" ng-click="$ctrl.download({ name: item.Name })"
ng-if="!item.Dir">
<i class="fa fa-download" aria-hidden="true"></i> Download
</btn>
<btn class="btn btn-xs btn-primary space-right" ng-click="item.newName = item.Name; item.edit = true">
<btn authorization="DockerAgentBrowseRename" class="btn btn-xs btn-primary space-right" ng-click="item.newName = item.Name; item.edit = true">
<i class="fa fa-edit" aria-hidden="true"></i> Rename
</btn>
<btn class="btn btn-xs btn-danger" ng-click="$ctrl.delete({ name: item.Name })">
<btn authorization="DockerAgentBrowseDelete" class="btn btn-xs btn-danger" ng-click="$ctrl.delete({ name: item.Name })">
<i class="fa fa-trash" aria-hidden="true"></i> Delete
</btn>
</td>

View file

@ -1,40 +1,46 @@
<div class="btn-group btn-group-xs" role="group" aria-label="..." style="display:inline-flex;">
<a
ng-if="$ctrl.state.showQuickActionLogs && $ctrl.taskId === undefined"
style="margin: 0 2.5px;"
ui-sref="docker.containers.container.logs({id: $ctrl.containerId, nodeName: $ctrl.nodeName})"
<a
authorization="DockerContainerLogs"
ng-if="$ctrl.state.showQuickActionLogs && $ctrl.taskId === undefined"
style="margin: 0 2.5px;"
ui-sref="docker.containers.container.logs({id: $ctrl.containerId, nodeName: $ctrl.nodeName})"
title="Logs">
<i class="fa fa-file-alt space-right" aria-hidden="true"></i>
</a>
<a
ng-if="$ctrl.state.showQuickActionLogs && $ctrl.taskId !== undefined"
style="margin: 0 2.5px;"
ui-sref="docker.tasks.task.logs({id: $ctrl.taskId})"
<a
authorization="DockerTaskLogs"
ng-if="$ctrl.state.showQuickActionLogs && $ctrl.taskId !== undefined"
style="margin: 0 2.5px;"
ui-sref="docker.tasks.task.logs({id: $ctrl.taskId})"
title="Logs">
<i class="fa fa-file-alt space-right" aria-hidden="true"></i>
</a>
<a
ng-if="$ctrl.state.showQuickActionInspect && $ctrl.taskId === undefined"
style="margin: 0 2.5px;"
ui-sref="docker.containers.container.inspect({id: $ctrl.containerId, nodeName: $ctrl.nodeName})"
<a
authorization="DockerContainerInspect"
ng-if="$ctrl.state.showQuickActionInspect && $ctrl.taskId === undefined"
style="margin: 0 2.5px;"
ui-sref="docker.containers.container.inspect({id: $ctrl.containerId, nodeName: $ctrl.nodeName})"
title="Inspect">
<i class="fa fa-info-circle space-right" aria-hidden="true"></i>
</a>
<a
ng-if="$ctrl.state.showQuickActionInspect && $ctrl.taskId !== undefined"
style="margin: 0 2.5px;"
ui-sref="docker.tasks.task({id: $ctrl.taskId})"
<a
authorization="DockerTaskInspect"
ng-if="$ctrl.state.showQuickActionInspect && $ctrl.taskId !== undefined"
style="margin: 0 2.5px;"
ui-sref="docker.tasks.task({id: $ctrl.taskId})"
title="Inspect">
<i class="fa fa-info-circle space-right" aria-hidden="true"></i>
</a>
<a
ng-if="$ctrl.state.showQuickActionStats && ['starting', 'running', 'healthy', 'unhealthy'].indexOf($ctrl.status) !== -1 && $ctrl.taskId === undefined"
style="margin: 0 2.5px;"
ui-sref="docker.containers.container.stats({id: $ctrl.containerId, nodeName: $ctrl.nodeName})"
<a
authorization="DockerContainerStats"
ng-if="$ctrl.state.showQuickActionStats && ['starting', 'running', 'healthy', 'unhealthy'].indexOf($ctrl.status) !== -1 && $ctrl.taskId === undefined"
style="margin: 0 2.5px;"
ui-sref="docker.containers.container.stats({id: $ctrl.containerId, nodeName: $ctrl.nodeName})"
title="Stats">
<i class="fa fa-chart-area space-right" aria-hidden="true"></i>
</a>
<a
authorization="DockerExecStart"
ng-if="$ctrl.state.showQuickActionConsole && ['starting', 'running', 'healthy', 'unhealthy'].indexOf($ctrl.status) !== -1 && $ctrl.taskId === undefined"
style="margin: 0 2.5px;"
ui-sref="docker.containers.container.exec({id: $ctrl.containerId, nodeName: $ctrl.nodeName})"
@ -42,6 +48,7 @@
<i class="fa fa-terminal space-right" aria-hidden="true"></i>
</a>
<a
authorization="DockerContainerAttach"
ng-if="$ctrl.state.showQuickActionConsole && ['starting', 'running', 'healthy', 'unhealthy'].indexOf($ctrl.status) !== -1 && $ctrl.taskId === undefined"
style="margin: 0 2.5px;"
ui-sref="docker.containers.container.attach({id: $ctrl.containerId, nodeName: $ctrl.nodeName})"

View file

@ -5,14 +5,14 @@
<span>Name</span>
</td>
<td>
<select class="form-control" ng-model="$ctrl.state.editModel.name">
<select class="form-control" ng-model="$ctrl.state.editModel.name" disable-authorization="DockerContainerUpdate">
<option value="no">None</option>
<option value="on-failure">On Failure</option>
<option value="always">Always</option>
<option value="unless-stopped">Unless Stopped</option>
</select>
</td>
<td class="col-md-2">
<td class="col-md-2" authorization="DockerContainerUpdate">
<button class="btn btn-sm btn-primary" ng-click="$ctrl.save()">Update</button>
</td>
</tr>

View file

@ -6,12 +6,12 @@
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }}
</div>
</div>
<div class="actionBar">
<button type="button" class="btn btn-sm btn-danger"
<div class="actionBar" authorization="DockerConfigDelete, DockerConfigCreate">
<button type="button" class="btn btn-sm btn-danger" authorization="DockerConfigDelete"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button>
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.configs.new">
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.configs.new" authorization="DockerConfigCreate">
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add config
</button>
</div>
@ -24,7 +24,7 @@
<thead>
<tr>
<th>
<span class="md-checkbox">
<span class="md-checkbox" authorization="DockerConfigDelete, DockerConfigCreate">
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
<label for="select_all"></label>
</span>
@ -53,7 +53,7 @@
<tbody>
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
<td>
<span class="md-checkbox">
<span class="md-checkbox" authorization="DockerConfigDelete, DockerConfigCreate">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-change="$ctrl.selectItem(item)"/>
<label for="select_{{ $index }}"></label>
</span>

View file

@ -8,7 +8,7 @@
</div>
<div class="actionBar">
<form class="form-horizontal">
<div class="row">
<div class="row" authorization="DockerNetworkConnect">
<label for="container_network" class="col-sm-3 col-lg-2 control-label text-left">Join a network</label>
<div class="col-sm-5 col-lg-4">
<select class="form-control" ng-model="$ctrl.selectedNetwork" id="container_network">
@ -42,7 +42,7 @@
<td>{{ value.IPAddress || '-' }}</td>
<td>{{ value.Gateway || '-' }}</td>
<td>{{ value.MacAddress || '-' }}</td>
<td>
<td authorization="DockerNetworkDisconnect">
<button type="button" class="btn btn-xs btn-danger" ng-disabled="$ctrl.leaveNetworkActionInProgress" button-spinner="$ctrl.leaveNetworkActionInProgress" ng-click="$ctrl.leaveNetworkAction($ctrl.container, key)">
<span ng-hide="$ctrl.leaveNetworkActionInProgress"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i> Leave network</span>
<span ng-show="$ctrl.leaveNetworkActionInProgress">Leaving network...</span>

View file

@ -1,35 +1,35 @@
<div class="actionBar">
<div class="actionBar" authorization="DockerContainerStart, DockerContainerStop, DockerContainerKill, DockerContainerRestart, DockerContainerPause, DockerContainerUnpause, DockerContainerDelete, DockerContainerCreate">
<div class="btn-group" role="group" aria-label="...">
<button type="button" class="btn btn-sm btn-success" ng-click="$ctrl.startAction($ctrl.selectedItems)"
<button authorization="DockerContainerStart" type="button" class="btn btn-sm btn-success" ng-click="$ctrl.startAction($ctrl.selectedItems)"
ng-disabled="$ctrl.selectedItemCount === 0 || $ctrl.noStoppedItemsSelected">
<i class="fa fa-play space-right" aria-hidden="true"></i>Start
</button>
<button type="button" class="btn btn-sm btn-danger" ng-click="$ctrl.stopAction($ctrl.selectedItems)"
<button authorization="DockerContainerStop" type="button" class="btn btn-sm btn-danger" ng-click="$ctrl.stopAction($ctrl.selectedItems)"
ng-disabled="$ctrl.selectedItemCount === 0 || $ctrl.noRunningItemsSelected">
<i class="fa fa-stop space-right" aria-hidden="true"></i>Stop
</button>
<button type="button" class="btn btn-sm btn-danger" ng-click="$ctrl.killAction($ctrl.selectedItems)"
<button authorization="DockerContainerKill" type="button" class="btn btn-sm btn-danger" ng-click="$ctrl.killAction($ctrl.selectedItems)"
ng-disabled="$ctrl.selectedItemCount === 0">
<i class="fa fa-bomb space-right" aria-hidden="true"></i>Kill
</button>
<button type="button" class="btn btn-sm btn-primary" ng-click="$ctrl.restartAction($ctrl.selectedItems)"
<button authorization="DockerContainerRestart" type="button" class="btn btn-sm btn-primary" ng-click="$ctrl.restartAction($ctrl.selectedItems)"
ng-disabled="$ctrl.selectedItemCount === 0">
<i class="fa fa-sync space-right" aria-hidden="true"></i>Restart
</button>
<button type="button" class="btn btn-sm btn-primary" ng-click="$ctrl.pauseAction($ctrl.selectedItems)"
<button authorization="DockerContainerPause" type="button" class="btn btn-sm btn-primary" ng-click="$ctrl.pauseAction($ctrl.selectedItems)"
ng-disabled="$ctrl.selectedItemCount === 0 || $ctrl.noRunningItemsSelected">
<i class="fa fa-pause space-right" aria-hidden="true"></i>Pause
</button>
<button type="button" class="btn btn-sm btn-primary" ng-click="$ctrl.resumeAction($ctrl.selectedItems)"
<button authorization="DockerContainerUnpause" type="button" class="btn btn-sm btn-primary" ng-click="$ctrl.resumeAction($ctrl.selectedItems)"
ng-disabled="$ctrl.selectedItemCount === 0 || $ctrl.noPausedItemsSelected">
<i class="fa fa-play space-right" aria-hidden="true"></i>Resume
</button>
<button type="button" class="btn btn-sm btn-danger"
<button authorization="DockerContainerDelete" type="button" class="btn btn-sm btn-danger"
ng-disabled="$ctrl.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.selectedItems)">
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button>
</div>
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.containers.new" ng-if="$ctrl.showAddAction">
<button authorization="DockerContainerCreate" type="button" class="btn btn-sm btn-primary" ui-sref="docker.containers.new" ng-if="$ctrl.showAddAction">
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add container
</button>
</div>

View file

@ -14,7 +14,7 @@
Show / Hide Columns
</div>
<div class="menuContent">
<div class="md-checkbox">
<div class="md-checkbox" >
<input id="col_vis_state" ng-click="$ctrl.onColumnVisibilityChange()" type="checkbox" ng-model="$ctrl.columnVisibility.columns.state.display"/>
<label for="col_vis_state" ng-bind="$ctrl.columnVisibility.columns.state.label"></label>
</div>
@ -70,25 +70,27 @@
<label for="setting_container_trunc">Truncate container name</label>
</div>
</div>
<div class="menuHeader">
Quick actions
</div>
<div class="menuContent">
<div class="md-checkbox">
<input id="setting_show_stats" type="checkbox" ng-model="$ctrl.settings.showQuickActionStats" ng-change="$ctrl.onSettingsQuickActionChange()"/>
<label for="setting_show_stats">Stats</label>
<div authorization="DockerContainerStats, DockerContainerLogs, DockerExecStart, DockerContainerInspect, DockerTaskInspect, DockerTaskLogs">
<div class="menuHeader">
Quick actions
</div>
<div class="md-checkbox">
<input id="setting_show_logs" type="checkbox" ng-model="$ctrl.settings.showQuickActionLogs" ng-change="$ctrl.onSettingsQuickActionChange()"/>
<label for="setting_show_logs">Logs</label>
</div>
<div class="md-checkbox">
<input id="setting_show_console" type="checkbox" ng-model="$ctrl.settings.showQuickActionConsole" ng-change="$ctrl.onSettingsQuickActionChange()"/>
<label for="setting_show_console">Console</label>
</div>
<div class="md-checkbox">
<input id="setting_show_inspect" type="checkbox" ng-model="$ctrl.settings.showQuickActionInspect" ng-change="$ctrl.onSettingsQuickActionChange()"/>
<label for="setting_show_inspect">Inspect</label>
<div class="menuContent">
<div class="md-checkbox" authorization="DockerContainerStats">
<input id="setting_show_stats" type="checkbox" ng-model="$ctrl.settings.showQuickActionStats" ng-change="$ctrl.onSettingsQuickActionChange()"/>
<label for="setting_show_stats">Stats</label>
</div>
<div class="md-checkbox" authorization="DockerContainerLogs">
<input id="setting_show_logs" type="checkbox" ng-model="$ctrl.settings.showQuickActionLogs" ng-change="$ctrl.onSettingsQuickActionChange()"/>
<label for="setting_show_logs">Logs</label>
</div>
<div class="md-checkbox" authorization="DockerExecStart">
<input id="setting_show_console" type="checkbox" ng-model="$ctrl.settings.showQuickActionConsole" ng-change="$ctrl.onSettingsQuickActionChange()"/>
<label for="setting_show_console">Console</label>
</div>
<div class="md-checkbox" authorization="DockerContainerInspect">
<input id="setting_show_inspect" type="checkbox" ng-model="$ctrl.settings.showQuickActionInspect" ng-change="$ctrl.onSettingsQuickActionChange()"/>
<label for="setting_show_inspect">Inspect</label>
</div>
</div>
</div>
<div>
@ -116,7 +118,7 @@
<thead>
<tr>
<th>
<span class="md-checkbox" ng-if="!$ctrl.offlineMode">
<span class="md-checkbox" ng-if="!$ctrl.offlineMode" authorization="DockerContainerStart, DockerContainerStop, DockerContainerKill, DockerContainerRestart, DockerContainerPause, DockerContainerUnpause, DockerContainerDelete, DockerContainerCreate">
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
<label for="select_all"></label>
</span>
@ -153,7 +155,7 @@
</div>
</div>
</th>
<th ng-if="$ctrl.settings.showQuickActionStats || $ctrl.settings.showQuickActionLogs || $ctrl.settings.showQuickActionConsole || $ctrl.settings.showQuickActionInspect" ng-show="$ctrl.columnVisibility.columns.actions.display">
<th ng-if="$ctrl.settings.showQuickActionStats || $ctrl.settings.showQuickActionLogs || $ctrl.settings.showQuickActionConsole || $ctrl.settings.showQuickActionInspect" ng-show="$ctrl.columnVisibility.columns.actions.display" authorization="DockerContainerStats, DockerContainerLogs, DockerExecStart, DockerContainerInspect, DockerTaskInspect, DockerTaskLogs">
Quick actions
</th>
<th ng-show="$ctrl.columnVisibility.columns.stack.display">
@ -210,7 +212,7 @@
<tbody>
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter: $ctrl.applyFilters | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
<td>
<span class="md-checkbox" ng-if="!$ctrl.offlineMode">
<span class="md-checkbox" ng-if="!$ctrl.offlineMode" authorization="DockerContainerStart, DockerContainerStop, DockerContainerKill, DockerContainerRestart, DockerContainerPause, DockerContainerUnpause, DockerContainerDelete, DockerContainerCreate">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-change="$ctrl.selectItem(item)"/>
<label for="select_{{ $index }}"></label>
</span>
@ -221,7 +223,7 @@
<span ng-if="['starting','healthy','unhealthy'].indexOf(item.Status) !== -1" class="label label-{{ item.Status|containerstatusbadge }} interactive" uib-tooltip="This container has a health check">{{ item.Status }}</span>
<span ng-if="['starting','healthy','unhealthy'].indexOf(item.Status) === -1" class="label label-{{ item.Status|containerstatusbadge }}">{{ item.Status }}</span>
</td>
<td ng-if="!$ctrl.offlineMode && ($ctrl.settings.showQuickActionStats || $ctrl.settings.showQuickActionLogs || $ctrl.settings.showQuickActionConsole || $ctrl.settings.showQuickActionInspect)" ng-show="$ctrl.columnVisibility.columns.actions.display">
<td ng-if="!$ctrl.offlineMode && ($ctrl.settings.showQuickActionStats || $ctrl.settings.showQuickActionLogs || $ctrl.settings.showQuickActionConsole || $ctrl.settings.showQuickActionInspect)" ng-show="$ctrl.columnVisibility.columns.actions.display" authorization="DockerContainerStats, DockerContainerLogs, DockerExecStart, DockerContainerInspect, DockerTaskInspect, DockerTaskLogs">
<container-quick-actions container-id="item.Id" node-name="item.NodeName" status="item.Status" state="$ctrl.settings"></container-quick-actions>
</td>
<td ng-if="$ctrl.offlineMode">

View file

@ -6,8 +6,8 @@
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }}
</div>
</div>
<div class="actionBar" ng-if="!$ctrl.offlineMode">
<div class="btn-group">
<div class="actionBar" ng-if="!$ctrl.offlineMode" authorization="DockerImageDelete, DockerImageBuild, DockerImageLoad, DockerImageGet">
<div class="btn-group" authorization="DockerImageDelete">
<button type="button" class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems, false)">
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
@ -20,15 +20,15 @@
<li><a ng-click="$ctrl.forceRemoveAction($ctrl.state.selectedItems, true)" ng-disabled="$ctrl.state.selectedItemCount === 0">Force Remove</a></li>
</ul>
</div>
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.images.build">
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.images.build" authorization="DockerImageBuild">
<i class="fa fa-plus space-right" aria-hidden="true"></i>Build a new image
</button>
<div class="btn-group">
<button type="button" class="btn btn-sm btn-primary" ng-disabled="$ctrl.exportInProgress" ui-sref="docker.images.import">
<button type="button" class="btn btn-sm btn-primary" ng-disabled="$ctrl.exportInProgress" ui-sref="docker.images.import" authorization="DockerImageLoad">
<i class="fa fa-upload space-right" aria-hidden="true"></i>Import
</button>
<button type="button" class="btn btn-sm btn-primary" ng-disabled="$ctrl.state.selectedItemCount === 0 || $ctrl.exportInProgress"
ng-click="$ctrl.downloadAction($ctrl.state.selectedItems)" button-spinner="$ctrl.exportInProgress">
ng-click="$ctrl.downloadAction($ctrl.state.selectedItems)" button-spinner="$ctrl.exportInProgress" authorization="DockerImageGet">
<i class="fa fa-download space-right" aria-hidden="true"></i>
<span ng-hide="$ctrl.exportInProgress">Export</span>
<span ng-show="$ctrl.exportInProgress">Export in progress...</span>

View file

@ -6,12 +6,12 @@
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }}
</div>
</div>
<div class="actionBar" ng-if="!$ctrl.offlineMode">
<button type="button" class="btn btn-sm btn-danger"
<div class="actionBar" ng-if="!$ctrl.offlineMode" authorization="DockerNetworkDelete, DockerNetworkCreate">
<button type="button" class="btn btn-sm btn-danger" authorization="DockerNetworkDelete"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button>
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.networks.new">
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.networks.new" authorization="DockerNetworkCreate">
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add network
</button>
</div>

View file

@ -6,12 +6,12 @@
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }}
</div>
</div>
<div class="actionBar">
<button type="button" class="btn btn-sm btn-danger"
<div class="actionBar" authorization="DockerSecretDelete, DockerSecretCreate">
<button type="button" class="btn btn-sm btn-danger" authorization="DockerSecretDelete"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button>
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.secrets.new">
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.secrets.new" authorization="DockerSecretCreate">
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add secret
</button>
</div>
@ -24,7 +24,7 @@
<thead>
<tr>
<th>
<span class="md-checkbox">
<span class="md-checkbox" authorization="DockerSecretDelete, DockerSecretCreate">
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
<label for="select_all"></label>
</span>
@ -53,7 +53,7 @@
<tbody>
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
<td>
<span class="md-checkbox">
<span class="md-checkbox" authorization="DockerSecretDelete, DockerSecretCreate">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-change="$ctrl.selectItem(item)"/>
<label for="select_{{ $index }}"></label>
</span>

View file

@ -1,15 +1,15 @@
<div class="actionBar">
<div class="actionBar" authorization="DockerServiceUpdate, DockerServiceDelete, DockerServiceCreate">
<div class="btn-group" role="group" aria-label="...">
<button ng-if="$ctrl.showUpdateAction" type="button" class="btn btn-sm btn-primary"
<button ng-if="$ctrl.showUpdateAction" type="button" class="btn btn-sm btn-primary" authorization="DockerServiceUpdate"
ng-disabled="$ctrl.selectedItemCount === 0" ng-click="$ctrl.updateAction($ctrl.selectedItems)">
<i class="fa fa-sync space-right" aria-hidden="true"></i>Update
</button>
<button type="button" class="btn btn-sm btn-danger"
<button type="button" class="btn btn-sm btn-danger" authorization="DockerServiceDelete"
ng-disabled="$ctrl.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.selectedItems)">
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button>
</div>
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.services.new" ng-if="$ctrl.showAddAction">
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.services.new" ng-if="$ctrl.showAddAction" authorization="DockerServiceCreate">
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add service
</button>
</div>

View file

@ -21,7 +21,7 @@
<thead>
<tr>
<th style="width:55px;">
<span class="md-checkbox">
<span class="md-checkbox" authorization="DockerServiceUpdate, DockerServiceDelete, DockerServiceCreate">
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
<label for="select_all"></label>
</span>
@ -83,7 +83,7 @@
<tbody>
<tr ng-click="$ctrl.expandItem(item, !item.Expanded)" dir-paginate-start="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}" class="interactive">
<td>
<span class="md-checkbox">
<span class="md-checkbox" authorization="DockerServiceUpdate, DockerServiceDelete, DockerServiceCreate">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-change="$ctrl.selectItem(item)"/>
<label for="select_{{ $index }}"></label>
</span>
@ -97,7 +97,7 @@
<td ng-controller="ServicesDatatableActionsController as actionCtrl">
{{ item.Mode }}
<code>{{ item.Tasks | runningtaskscount }}</code> / <code>{{ item.Mode === 'replicated' ? item.Replicas : ($ctrl.nodes | availablenodecount:item) }}</code>
<span ng-if="item.Mode === 'replicated' && !item.Scale">
<span ng-if="item.Mode === 'replicated' && !item.Scale" authorization="DockerServiceUpdate">
<a class="interactive" ng-click="item.Scale = true; item.ReplicaCount = item.Replicas; $event.stopPropagation();">
<i class="fa fa-arrows-alt-v" aria-hidden="true"></i> Scale
</a>

View file

@ -6,12 +6,12 @@
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }}
</div>
</div>
<div class="actionBar" ng-if="!$ctrl.offlineMode">
<button type="button" class="btn btn-sm btn-danger"
<div class="actionBar" ng-if="!$ctrl.offlineMode" authorization="DockerVolumeDelete, DockerVolumeCreate">
<button type="button" class="btn btn-sm btn-danger" authorization="DockerVolumeDelete"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button>
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.volumes.new">
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.volumes.new" authorization="DockerVolumeCreate">
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add volume
</button>
</div>
@ -24,7 +24,7 @@
<thead>
<tr>
<th uib-dropdown dropdown-append-to-body auto-close="disabled" is-open="$ctrl.filters.usage.open">
<span class="md-checkbox" ng-if="!$ctrl.offlineMode">
<span class="md-checkbox" ng-if="!$ctrl.offlineMode" authorization="DockerVolumeDelete, DockerVolumeCreate">
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
<label for="select_all"></label>
</span>
@ -105,13 +105,13 @@
<tbody>
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter: $ctrl.applyFilters | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
<td>
<span class="md-checkbox" ng-if="!$ctrl.offlineMode">
<span class="md-checkbox" ng-if="!$ctrl.offlineMode" authorization="DockerVolumeDelete, DockerVolumeCreate">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-change="$ctrl.selectItem(item)"/>
<label for="select_{{ $index }}"></label>
</span>
<a ng-if="!$ctrl.offlineMode" ui-sref="docker.volumes.volume({ id: item.Id, nodeName: item.NodeName })" class="monospaced" title="{{ item.Id }}">{{ item.Id | truncate:40 }}</a>
<span ng-if="$ctrl.offlineMode">{{ item.Id | truncate:40 }}</span>
<a ui-sref="docker.volumes.volume.browse({ id: item.Id, nodeName: item.NodeName })" class="btn btn-xs btn-primary space-left" ng-if="$ctrl.showBrowseAction && !$ctrl.offlineMode">
<a authorization="DockerAgentBrowseList" ui-sref="docker.volumes.volume.browse({ id: item.Id, nodeName: item.NodeName })" class="btn btn-xs btn-primary space-left" ng-if="$ctrl.showBrowseAction && !$ctrl.offlineMode">
<i class="fa fa-search"></i> browse</a>
</a>
<span style="margin-left: 10px;" class="label label-warning image-tag space-left" ng-if="item.dangling">Unused</span>

View file

@ -1,7 +1,7 @@
<li class="sidebar-list">
<a ui-sref="docker.dashboard" ui-sref-active="active">Dashboard <span class="menu-icon fa fa-tachometer-alt fa-fw"></span></a>
</li>
<li class="sidebar-list" ng-if="!$ctrl.offlineMode">
<li class="sidebar-list" ng-if="!$ctrl.offlineMode" authorization="DockerContainerCreate, PortainerStackCreate">
<a ui-sref="portainer.templates" ui-sref-active="active">App Templates <span class="menu-icon fa fa-rocket fa-fw"></span></a>
</li>
<li class="sidebar-list">

View file

@ -19,8 +19,7 @@ angular.module('portainer.docker')
function initComponent() {
if (StateManager.getState().application.authentication) {
var userDetails = Authentication.getUserDetails();
var isAdmin = userDetails.role === 1 ? true : false;
var isAdmin = Authentication.isAdmin();
ctrl.isAdmin = isAdmin;
}
var provider = ctrl.applicationState.endpoint.mode.provider;

View file

@ -77,7 +77,7 @@ class CreateConfigController {
async create() {
let accessControlData = this.formValues.AccessControlData;
let userDetails = this.Authentication.getUserDetails();
let isAdmin = userDetails.role === 1;
let isAdmin = this.Authentication.isAdmin();
if (this.formValues.ConfigContent === "") {
this.state.formValidationError = "Config content must not be empty";

View file

@ -24,8 +24,8 @@
<td>ID</td>
<td>
{{ config.Id }}
<button class="btn btn-xs btn-danger" ng-click="removeConfig(config.Id)"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Delete this config</button>
<button class="btn btn-xs btn-primary" ui-sref="docker.configs.new({id: config.Id})"><i class="fa fa-copy space-right" aria-hidden="true"></i>Clone config</button>
<button authorization="DockerConfigDelete" class="btn btn-xs btn-danger" ng-click="removeConfig(config.Id)"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Delete this config</button>
<button authorization="DockerConfigCreate"class="btn btn-xs btn-primary" ui-sref="docker.configs.new({id: config.Id})"><i class="fa fa-copy space-right" aria-hidden="true"></i>Clone config</button>
</td>
</tr>
<tr>

View file

@ -633,8 +633,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai
$scope.availableLoggingDrivers = loggingDrivers;
});
var userDetails = Authentication.getUserDetails();
$scope.isAdmin = userDetails.role === 1;
$scope.isAdmin = Authentication.isAdmin();
}
function validateForm(accessControlData, isAdmin) {
@ -845,10 +844,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai
function validateAccessControl() {
var accessControlData = $scope.formValues.AccessControlData;
var userDetails = Authentication.getUserDetails();
var isAdmin = userDetails.role === 1;
return validateForm(accessControlData, isAdmin);
return validateForm(accessControlData, $scope.isAdmin);
}
function onSuccess() {

View file

@ -6,21 +6,21 @@
</rd-header-content>
</rd-header>
<div class="row">
<div class="row" authorization="DockerContainerStart, DockerContainerStop, DockerContainerKill, DockerContainerRestart, DockerContainerPause, DockerContainerUnpause, DockerContainerDelete, DockerContainerCreate">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-cogs" title-text="Actions"></rd-widget-header>
<rd-widget-body classes="padding">
<div class="btn-group" role="group" aria-label="...">
<button class="btn btn-success btn-sm" ng-click="start()" ng-disabled="container.State.Running"><i class="fa fa-play space-right" aria-hidden="true"></i>Start</button>
<button class="btn btn-danger btn-sm" ng-click="stop()" ng-disabled="!container.State.Running"><i class="fa fa-stop space-right" aria-hidden="true"></i>Stop</button>
<button class="btn btn-danger btn-sm" ng-click="kill()" ng-disabled="!container.State.Running"><i class="fa fa-bomb space-right" aria-hidden="true"></i>Kill</button>
<button class="btn btn-primary btn-sm" ng-click="restart()" ng-disabled="!container.State.Running"><i class="fa fa-sync space-right" aria-hidden="true"></i>Restart</button>
<button class="btn btn-primary btn-sm" ng-click="pause()" ng-disabled="!container.State.Running || container.State.Paused"><i class="fa fa-pause space-right" aria-hidden="true"></i>Pause</button>
<button class="btn btn-primary btn-sm" ng-click="unpause()" ng-disabled="!container.State.Paused"><i class="fa fa-play space-right" aria-hidden="true"></i>Resume</button>
<button class="btn btn-danger btn-sm" ng-click="confirmRemove()"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove</button>
<button authorization="DockerContainerStart" class="btn btn-success btn-sm" ng-click="start()" ng-disabled="container.State.Running"><i class="fa fa-play space-right" aria-hidden="true"></i>Start</button>
<button authorization="DockerContainerStop" class="btn btn-danger btn-sm" ng-click="stop()" ng-disabled="!container.State.Running"><i class="fa fa-stop space-right" aria-hidden="true"></i>Stop</button>
<button authorization=DockerContainerKill" class="btn btn-danger btn-sm" ng-click="kill()" ng-disabled="!container.State.Running"><i class="fa fa-bomb space-right" aria-hidden="true"></i>Kill</button>
<button authorization="DockerContainerRestart" class="btn btn-primary btn-sm" ng-click="restart()" ng-disabled="!container.State.Running"><i class="fa fa-sync space-right" aria-hidden="true"></i>Restart</button>
<button authorization="DockerContainerPause" class="btn btn-primary btn-sm" ng-click="pause()" ng-disabled="!container.State.Running || container.State.Paused"><i class="fa fa-pause space-right" aria-hidden="true"></i>Pause</button>
<button authorization="DockerContainerUnpause" class="btn btn-primary btn-sm" ng-click="unpause()" ng-disabled="!container.State.Paused"><i class="fa fa-play space-right" aria-hidden="true"></i>Resume</button>
<button authorization="DockerContainerDelete" class="btn btn-danger btn-sm" ng-click="confirmRemove()"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove</button>
</div>
<div class="btn-group" role="group" aria-label="..." ng-if="!container.Config.Labels['com.docker.swarm.service.id']">
<div class="btn-group" role="group" aria-label="..." ng-if="!container.Config.Labels['com.docker.swarm.service.id']" authorization="DockerContainerCreate">
<button type="button" class="btn btn-danger btn-sm" ng-disabled="state.recreateContainerInProgress" ng-click="recreate()" button-spinner="state.recreateContainerInProgress">
<span ng-hide="state.recreateContainerInProgress"><i class="fa fa-sync space-right" aria-hidden="true"></i>Recreate</span>
<span ng-show="state.recreateContainerInProgress">Recreation in progress...</span>
@ -47,7 +47,7 @@
<td>Name</td>
<td ng-if="!container.edit">
{{ container.Name|trimcontainername }}
<a href="" data-toggle="tooltip" title="Edit container name" ng-click="container.edit = true;"><i class="fa fa-edit"></i></a>
<a authorization="DockerContainerRename" href="" data-toggle="tooltip" title="Edit container name" ng-click="container.edit = true;"><i class="fa fa-edit"></i></a>
</td>
<td ng-if="container.edit">
<form ng-submit="renameContainer()">
@ -81,14 +81,14 @@
<td>Finished</td>
<td>{{ container.State.FinishedAt|getisodate }}</td>
</tr>
<tr>
<tr authorization="DockerContainerLogs, DockerContainerInspect, DockerContainerStats, DockerExecStart">
<td colspan="2">
<div class="btn-group" role="group" aria-label="...">
<a class="btn" type="button" ui-sref="docker.containers.container.logs({ id: container.Id })"><i class="fa fa-file-alt space-right" aria-hidden="true"></i>Logs</a>
<a class="btn" type="button" ui-sref="docker.containers.container.inspect({ id: container.Id })"><i class="fa fa-info-circle space-right" aria-hidden="true"></i>Inspect</a>
<a class="btn" type="button" ui-sref="docker.containers.container.stats({ id: container.Id })"><i class="fa fa-chart-area space-right" aria-hidden="true"></i>Stats</a>
<a class="btn" type="button" ui-sref="docker.containers.container.exec({ id: container.Id })"><i class="fa fa-terminal space-right" aria-hidden="true"></i>Exec</a>
<a class="btn" type="button" ui-sref="docker.containers.container.attach({ id: container.Id })"><i class="fa fa-plug space-right" aria-hidden="true"></i>Attach</a>
<a authorization="DockerContainerLogs" class="btn" type="button" ui-sref="docker.containers.container.logs({ id: container.Id })"><i class="fa fa-file-alt space-right" aria-hidden="true"></i>Logs</a>
<a authorization="DockerContainerInspect" class="btn" type="button" ui-sref="docker.containers.container.inspect({ id: container.Id })"><i class="fa fa-info-circle space-right" aria-hidden="true"></i>Inspect</a>
<a authorization="DockerContainerStats" class="btn" type="button" ui-sref="docker.containers.container.stats({ id: container.Id })"><i class="fa fa-chart-area space-right" aria-hidden="true"></i>Stats</a>
<a authorization="DockerExecStart" class="btn" type="button" ui-sref="docker.containers.container.exec({ id: container.Id })"><i class="fa fa-terminal space-right" aria-hidden="true"></i>Console</a>
<a authorization="DockerContainerAttach" class="btn" type="button" ui-sref="docker.containers.container.attach({ id: container.Id })"><i class="fa fa-plug space-right" aria-hidden="true"></i>Attach</a>
</div>
</td>
</tr>
@ -137,7 +137,7 @@
</div>
</div>
<div class="row">
<div class="row" authorization="DockerImageCreate">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-clone" title-text="Create image"></rd-widget-header>

View file

@ -19,7 +19,7 @@ angular.module('portainer.docker').controller('HostViewController', [
function initView() {
var applicationState = StateManager.getState();
ctrl.state.isAgent = applicationState.endpoint.mode.agentProxy;
ctrl.state.isAdmin = Authentication.getUserDetails().role === 1;
ctrl.state.isAdmin = Authentication.isAdmin();
var agentApiVersion = applicationState.endpoint.agentApiVersion;
ctrl.state.agentApiVersion = agentApiVersion;
ctrl.state.enableHostManagementFeatures = applicationState.application.enableHostManagementFeatures;

View file

@ -16,14 +16,14 @@
<div class="pull-left" ng-repeat="tag in image.RepoTags" style="display:table">
<div class="input-group col-md-1" style="padding:0 15px">
<span class="input-group-addon">{{ tag }}</span>
<span class="input-group-btn">
<a data-toggle="tooltip" class="btn btn-primary interactive" title="Push to registry" ng-click="pushTag(tag)">
<span class="input-group-btn" authorization="DockerImagePush, DockerImageCreate, DockerImageDelete">
<a data-toggle="tooltip" class="btn btn-primary interactive" title="Push to registry" ng-click="pushTag(tag)" authorization="DockerImagePush">
<span class="fa fa-upload white-icon" aria-hidden="true"></span>
</a>
<a data-toggle="tooltip" class="btn btn-primary interactive" title="Pull from registry" ng-click="pullTag(tag)">
<a data-toggle="tooltip" class="btn btn-primary interactive" title="Pull from registry" ng-click="pullTag(tag)" authorization="DockerImageCreate">
<span class="fa fa-download white-icon" aria-hidden="true"></span>
</a>
<a data-toggle="tooltip" class="btn btn-primary interactive" title="Remove tag" ng-click="removeTag(tag)">
<a data-toggle="tooltip" class="btn btn-primary interactive" title="Remove tag" ng-click="removeTag(tag)" authorization="DockerImageDelete">
<span class="fa fa-trash-alt white-icon" aria-hidden="true"></span>
</a>
</span>
@ -56,7 +56,7 @@
</div>
</div>
<div class="row">
<div class="row" authorization="DockerImageCreate">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-tag" title-text="Tag the image"></rd-widget-header>
@ -98,8 +98,8 @@
<td>ID</td>
<td>
{{ image.Id }}
<button class="btn btn-xs btn-danger" ng-click="removeImage(image.Id)"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Delete this image</button>
<button class="btn btn-xs btn-primary" ng-click="exportImage(image)" button-spinner="$ctrl.exportInProgress" ng-disabled="state.exportInProgress">
<button authorization="DockerImageDelete" class="btn btn-xs btn-danger" ng-click="removeImage(image.Id)"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Delete this image</button>
<button authorization="DockerImageGet" class="btn btn-xs btn-primary" ng-click="exportImage(image)" button-spinner="$ctrl.exportInProgress" ng-disabled="state.exportInProgress">
<i class="fa fa-download space-right" aria-hidden="true"></i>
<span ng-hide="state.exportInProgress">Export this image</span>
<span ng-show="state.exportInProgress">Export in progress...</span>

View file

@ -7,7 +7,7 @@
<rd-header-content>Images</rd-header-content>
</rd-header>
<div class="row" ng-if="!offlineMode">
<div class="row" ng-if="!offlineMode" authorization="DockerImageCreate">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-download" title-text="Pull image ">

View file

@ -158,7 +158,7 @@ angular.module('portainer.docker')
var networkConfiguration = prepareConfiguration();
var accessControlData = $scope.formValues.AccessControlData;
var userDetails = Authentication.getUserDetails();
var isAdmin = userDetails.role === 1;
var isAdmin = Authentication.isAdmin();
if (!validateForm(accessControlData, isAdmin)) {
return;

View file

@ -20,7 +20,7 @@
<td>ID</td>
<td>
{{ network.Id }}
<button ng-if="allowRemove(network)" class="btn btn-xs btn-danger" ng-click="removeNetwork(network.Id)"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Delete this network</button>
<button authorization="DockerNetworkDelete" ng-if="allowRemove(network)" class="btn btn-xs btn-danger" ng-click="removeNetwork(network.Id)"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Delete this network</button>
</td>
</tr>
<tr>
@ -89,7 +89,7 @@
<th>IPv4 Address</th>
<th>IPv6 Address</th>
<th>MacAddress</th>
<th>Actions</th>
<th authorization="DockerNetworkDisconnect">Actions</th>
</thead>
<tbody>
<tr ng-repeat="container in containersInNetwork">
@ -97,7 +97,7 @@
<td>{{ container.IPv4Address || '-' }}</td>
<td>{{ container.IPv6Address || '-' }}</td>
<td>{{ container.MacAddress || '-' }}</td>
<td>
<td authorization="DockerNetworkDisconnect">
<button type="button" class="btn btn-xs btn-danger" ng-click="containerLeaveNetwork(network, container)"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Leave Network</button>
</td>
</tr>

View file

@ -13,7 +13,7 @@ angular.module('portainer.docker').controller('NodeDetailsViewController', [
function initView() {
var applicationState = StateManager.getState();
ctrl.state.isAgent = applicationState.endpoint.mode.agentProxy;
ctrl.state.isAdmin = Authentication.getUserDetails().role === 1;
ctrl.state.isAdmin = Authentication.isAdmin();
ctrl.state.enableHostManagementFeatures = applicationState.application.enableHostManagementFeatures;
var fetchJobs = ctrl.state.isAdmin && ctrl.state.isAgent;

View file

@ -61,7 +61,7 @@ function ($scope, $state, Notifications, SecretService, LabelHelper, Authenticat
var accessControlData = $scope.formValues.AccessControlData;
var userDetails = Authentication.getUserDetails();
var isAdmin = userDetails.role === 1;
var isAdmin = Authentication.isAdmin();
if (!validateForm(accessControlData, isAdmin)) {
return;

View file

@ -24,7 +24,7 @@
<td>ID</td>
<td>
{{ secret.Id }}
<button class="btn btn-xs btn-danger" ng-click="removeSecret(secret.Id)"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Delete this secret</button>
<button authorization="DockerSecretDelete" class="btn btn-xs btn-danger" ng-click="removeSecret(secret.Id)"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Delete this secret</button>
</td>
</tr>
<tr>

View file

@ -467,12 +467,9 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C
}
$scope.create = function createService() {
var accessControlData = $scope.formValues.AccessControlData;
var userDetails = Authentication.getUserDetails();
var isAdmin = userDetails.role === 1;
if (!validateForm(accessControlData, isAdmin)) {
if (!validateForm(accessControlData, $scope.isAdmin)) {
return;
}
@ -524,8 +521,7 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C
$scope.availableLoggingDrivers = data.availableLoggingDrivers;
initSlidersMaxValuesBasedOnNodeData(data.nodes);
$scope.allowBindMounts = data.settings.AllowBindMountsForRegularUsers;
var userDetails = Authentication.getUserDetails();
$scope.isAdmin = userDetails.role === 1;
$scope.isAdmin = Authentication.isAdmin();
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to initialize view');

View file

@ -3,7 +3,7 @@
<rd-widget-header icon="fa-tasks" title-text="Configs">
</rd-widget-header>
<rd-widget-body classes="no-padding">
<div class="form-inline" style="padding: 10px;">
<div class="form-inline" style="padding: 10px;" authorization="DockerServiceUpdate">
Add a config:
<select class="form-control" ng-options="config.Name for config in configs | orderBy: 'Name'" ng-model="newConfig">
<option selected disabled hidden value="">Select a config</option>
@ -32,7 +32,7 @@
<td>{{ config.Uid }}</td>
<td>{{ config.Gid }}</td>
<td>{{ config.Mode }}</td>
<td>
<td authorization="DockerServiceUpdate">
<button class="btn btn-xs btn-danger pull-right" type="button" ng-click="removeConfig(service, $index)" ng-disabled="isUpdating">
<i class="fa fa-trash" aria-hidden="true"></i> Remove config
</button>
@ -44,7 +44,7 @@
</tbody>
</table>
</rd-widget-body>
<rd-widget-footer>
<rd-widget-footer authorization="DockerServiceUpdate">
<div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group">
<button type="submit" class="btn btn-primary btn-sm" ng-disabled="!hasChanges(service, ['ServiceConfigs'])">Apply changes</button>

View file

@ -1,7 +1,7 @@
<div ng-if="service.ServiceConstraints" id="service-placement-constraints">
<rd-widget>
<rd-widget-header icon="fa-tasks" title-text="Placement constraints">
<div class="nopadding">
<div class="nopadding" authorization="DockerServiceUpdate">
<a class="btn btn-default btn-sm pull-right" ng-click="isUpdating || addPlacementConstraint(service)" ng-disabled="isUpdating">
<i class="fa fa-plus-circle" aria-hidden="true"></i> placement constraint
</a>
@ -37,7 +37,7 @@
<td>
<div class="input-group input-group-sm">
<input type="text" class="form-control" ng-model="constraint.value" placeholder="e.g. manager" ng-change="updatePlacementConstraint(service, constraint)" ng-disabled="isUpdating">
<span class="input-group-btn">
<span class="input-group-btn" authorization="DockerServiceUpdate">
<button class="btn btn-sm btn-danger" type="button" ng-click="removePlacementConstraint(service, $index)" ng-disabled="isUpdating">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
@ -48,7 +48,7 @@
</tbody>
</table>
</rd-widget-body>
<rd-widget-footer>
<rd-widget-footer authorization="DockerServiceUpdate">
<div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!hasChanges(service, ['ServiceConstraints'])" ng-click="updateService(service)">Apply changes</button>

View file

@ -1,7 +1,7 @@
<div id="service-container-labels">
<rd-widget>
<rd-widget-header icon="fa-tasks" title-text="Container labels">
<div class="nopadding">
<div class="nopadding" authorization="DockerServiceUpdate">
<a class="btn btn-default btn-sm pull-right" ng-click="isUpdating ||addContainerLabel(service)" ng-disabled="isUpdating">
<i class="fa fa-plus-circle" aria-hidden="true"></i> container label
</a>
@ -30,7 +30,7 @@
<div class="input-group input-group-sm">
<span class="input-group-addon fit-text-size">value</span>
<input type="text" class="form-control" ng-model="label.value" placeholder="e.g. bar" ng-change="updateContainerLabel(service, label)" ng-disabled="isUpdating">
<span class="input-group-btn">
<span class="input-group-btn" authorization="DockerServiceUpdate">
<button class="btn btn-sm btn-danger" type="button" ng-click="removeContainerLabel(service, $index)" ng-disabled="isUpdating">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
@ -41,7 +41,7 @@
</tbody>
</table>
</rd-widget-body>
<rd-widget-footer>
<rd-widget-footer authorization="DockerServiceUpdate">
<div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!hasChanges(service, ['ServiceContainerLabels'])" ng-click="updateService(service)">Apply changes</button>

View file

@ -1,7 +1,7 @@
<div ng-if="service.EnvironmentVariables" id="service-env-variables">
<rd-widget>
<rd-widget-header icon="fa-tasks" title-text="Environment variables">
<div class="nopadding">
<div class="nopadding" authorization="DockerServiceUpdate">
<a class="btn btn-default btn-sm pull-right" ng-click="isUpdating ||addEnvironmentVariable(service)" ng-disabled="isUpdating">
<i class="fa fa-plus-circle" aria-hidden="true"></i> environment variable
</a>
@ -30,7 +30,7 @@
<div class="input-group input-group-sm">
<span class="input-group-addon fit-text-size">value</span>
<input type="text" class="form-control" ng-model="var.value" ng-change="updateEnvironmentVariable(service, var)" placeholder="e.g. bar" ng-disabled="isUpdating">
<span class="input-group-btn">
<span class="input-group-btn" authorization="DockerServiceUpdate">
<button class="btn btn-sm btn-danger" type="button" ng-click="removeEnvironmentVariable(service, $index)" ng-disabled="isUpdating">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
@ -41,7 +41,7 @@
</tbody>
</table>
</rd-widget-body>
<rd-widget-footer>
<rd-widget-footer authorization="DockerServiceUpdate">
<div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!hasChanges(service, ['EnvironmentVariables'])" ng-click="updateService(service)">Apply changes</button>

View file

@ -1,7 +1,7 @@
<div>
<rd-widget>
<rd-widget-header icon="fa-tasks" title-text="Hosts file entries">
<div class="nopadding">
<div class="nopadding" authorization="DockerServiceUpdate">
<a class="btn btn-default btn-sm pull-right" ng-click="isUpdating ||addHostsEntry(service)" ng-disabled="isUpdating">
<i class="fa fa-plus-circle" aria-hidden="true"></i> add host entry
</a>
@ -9,7 +9,7 @@
</rd-widget-header>
<rd-widget-body ng-if="!service.Hosts || service.Hosts.length === 0">
<p>The Hosts file has no extra entries.</p>
</rd-widget-body>
</rd-widget-body>
<rd-widget-body ng-if="service.Hosts.length > 0" classes="no-padding">
<table class="table" >
<thead>
@ -28,7 +28,7 @@
<td>
<div class="input-group input-group-sm">
<input type="text" class="form-control" ng-model="entry.ip" placeholder="e.g. 10.0.1.1" ng-change="updateHostsEntry(service, entry)" ng-disabled="isUpdating">
<span class="input-group-btn">
<span class="input-group-btn" authorization="DockerServiceUpdate">
<button class="btn btn-sm btn-danger" type="button" ng-click="removeHostsEntry(service, $index)" ng-disabled="isUpdating">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
@ -39,7 +39,7 @@
</tbody>
</table>
</rd-widget-body>
<rd-widget-footer>
<rd-widget-footer authorization="DockerServiceUpdate">
<div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!hasChanges(service, ['Hosts'])" ng-click="updateService(service)">Apply changes</button>

View file

@ -1,13 +1,13 @@
<div id="service-logging-driver">
<rd-widget>
<rd-widget-header icon="fa-tasks" title-text="Logging driver">
</rd-widget-header>
<rd-widget-header icon="fa-tasks" title-text="Logging driver">
</rd-widget-header>
<rd-widget-body classes="no-padding">
<div class="form-inline" style="padding: 10px;">
<div class="form-inline" style="padding: 10px;" authorization="DockerServiceUpdate">
Driver:
<select class="form-control" ng-model="service.LogDriverName" ng-change="updateLogDriverName(service)" ng-disabled="isUpdating">
<option selected value="">Default logging driver</option>
<option ng-repeat="driver in availableLoggingDrivers" ng-value="driver">{{ driver }}</option>
<option selected value="">Default logging driver</option>
<option ng-repeat="driver in availableLoggingDrivers" ng-value="driver">{{ driver }}</option>
<option value="none">none</option>
</select>
<a class="btn btn-default btn-sm" ng-click="!service.LogDriverName || service.LogDriverName === 'none' || addLogDriverOpt(service)">
@ -33,7 +33,7 @@
<div class="input-group input-group-sm">
<span class="input-group-addon fit-text-size">value</span>
<input type="text" class="form-control" ng-model="option.value" ng-change="updateLogDriverOpt(service, option)" placeholder="e.g. bar" ng-disabled="isUpdating">
<span class="input-group-btn">
<span class="input-group-btn" authorization="DockerServiceUpdate">
<button class="btn btn-sm btn-danger" type="button" ng-click="removeLogDriverOpt(service, $index)" ng-disabled="isUpdating">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
@ -47,7 +47,7 @@
</tbody>
</table>
</rd-widget-body>
<rd-widget-footer>
<rd-widget-footer authorization="DockerServiceUpdate">
<div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!hasChanges(service, ['LogDriverName', 'LogDriverOpts'])" ng-click="updateService(service)">Apply changes</button>
@ -62,4 +62,4 @@
</div>
</rd-widget-footer>
</rd-widget>
</div>
</div>

View file

@ -1,7 +1,7 @@
<div ng-if="service.ServiceMounts" id="service-mounts">
<rd-widget>
<rd-widget-header icon="fa-tasks" title-text="Mounts">
<div class="nopadding">
<div class="nopadding" authorization="DockerServiceUpdate">
<a class="btn btn-default btn-sm pull-right" ng-click="isUpdating ||addMount(service)" ng-disabled="isUpdating">
<i class="fa fa-plus-circle" aria-hidden="true"></i> mount
</a>
@ -41,7 +41,7 @@
<td>
<input type="checkbox" class="form-control" ng-model="mount.ReadOnly" ng-change="updateMount(service, mount)" ng-disabled="isUpdating">
</td>
<td>
<td authorization="DockerServiceUpdate">
<span class="input-group-btn">
<button class="btn btn-sm btn-danger" type="button" ng-click="removeMount(service, $index)" ng-disabled="isUpdating">
<i class="fa fa-trash" aria-hidden="true"></i>
@ -52,7 +52,7 @@
</tbody>
</table>
</rd-widget-body>
<rd-widget-footer>
<rd-widget-footer authorization="DockerServiceUpdate">
<div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!hasChanges(service, ['ServiceMounts'])" ng-click="updateService(service)">Apply changes</button>

View file

@ -1,7 +1,7 @@
<div ng-if="service.ServicePreferences" id="service-placement-preferences">
<rd-widget>
<rd-widget-header icon="fa-tasks" title-text="Placement preferences">
<div class="nopadding">
<div class="nopadding" authorization="DockerServiceUpdate">
<a class="btn btn-default btn-sm pull-right" ng-click="isUpdating || addPlacementPreference(service)" ng-disabled="isUpdating">
<i class="fa fa-plus-circle" aria-hidden="true"></i> placement preference
</a>
@ -28,7 +28,7 @@
<td>
<div class="input-group input-group-sm">
<input type="text" class="form-control" ng-model="preference.value" placeholder="e.g. manager" ng-change="updatePlacementPreference(service, preference)" ng-disabled="isUpdating">
<span class="input-group-btn">
<span class="input-group-btn" authorization="DockerServiceUpdate">
<button class="btn btn-sm btn-danger" type="button" ng-click="removePlacementPreference(service, $index)" ng-disabled="isUpdating">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
@ -39,7 +39,7 @@
</tbody>
</table>
</rd-widget-body>
<rd-widget-footer>
<rd-widget-footer authorization="DockerServiceUpdate">
<div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!hasChanges(service, ['ServicePreferences'])" ng-click="updateService(service)">Apply changes</button>

View file

@ -1,7 +1,7 @@
<div>
<rd-widget>
<rd-widget-header icon="fa-tasks" title-text="Published ports">
<div class="nopadding">
<div class="nopadding" authorization="DockerServiceUpdate">
<a class="btn btn-default btn-sm pull-right" ng-click="isUpdating ||addPublishedPort(service)" ng-disabled="isUpdating">
<i class="fa fa-plus-circle" aria-hidden="true"></i> port mapping
</a>
@ -51,7 +51,7 @@
</select>
</div>
</td>
<td>
<td authorization="DockerServiceUpdate">
<span class="input-group-btn">
<button class="btn btn-sm btn-danger" type="button" ng-click="removePortPublishedBinding(service, $index)" ng-disabled="isUpdating">
<i class="fa fa-trash" aria-hidden="true"></i>
@ -62,7 +62,7 @@
</tbody>
</table>
</rd-widget-body>
<rd-widget-footer>
<rd-widget-footer authorization="DockerServiceUpdate">
<div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!hasChanges(service, ['Ports'])" ng-click="updateService(service)">Apply changes</button>

View file

@ -10,7 +10,7 @@
Memory reservation (MB)
</td>
<td>
<input class="input-sm" type="number" step="0.125" min="0" ng-model="service.ReservationMemoryBytes" ng-change="updateServiceAttribute(service, 'ReservationMemoryBytes')" ng-disabled="isUpdating"/>
<input class="input-sm" type="number" step="0.125" min="0" ng-model="service.ReservationMemoryBytes" ng-change="updateServiceAttribute(service, 'ReservationMemoryBytes')" disable-authorization="DockerServiceUpdate"/>
</td>
<td style="vertical-align : middle;">
<p class="small text-muted">
@ -23,7 +23,7 @@
Memory limit (MB)
</td>
<td>
<input class="input-sm" type="number" step="0.125" min="0" ng-model="service.LimitMemoryBytes" ng-change="updateServiceAttribute(service, 'LimitMemoryBytes')" ng-disabled="isUpdating"/>
<input class="input-sm" type="number" step="0.125" min="0" ng-model="service.LimitMemoryBytes" ng-change="updateServiceAttribute(service, 'LimitMemoryBytes')" disable-authorization="DockerServiceUpdate"/>
</td>
<td style="vertical-align : middle;">
<p class="small text-muted">
@ -64,7 +64,7 @@
</tbody>
</table>
</rd-widget-body>
<rd-widget-footer>
<rd-widget-footer authorization="DockerServiceUpdate">
<div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!hasChanges(service, ['LimitNanoCPUs', 'LimitMemoryBytes', 'ReservationNanoCPUs', 'ReservationMemoryBytes'])" ng-click="updateService(service)">Apply changes</button>

View file

@ -9,7 +9,7 @@
<td>Restart condition</td>
<td>
<div class="input-group input-group-sm">
<select class="selectpicker form-control" ng-model="service.RestartCondition" ng-change="updateServiceAttribute(service, 'RestartCondition')" ng-disabled="isUpdating">
<select class="selectpicker form-control" ng-model="service.RestartCondition" ng-change="updateServiceAttribute(service, 'RestartCondition')" disable-authorization="DockerServiceUpdate">
<option value="none">None</option>
<option value="on-failure">On failure</option>
<option value="any">Any</option>
@ -25,7 +25,7 @@
<tr>
<td>Restart delay</td>
<td>
<input class="input-sm" type="text" ng-model="service.RestartDelay" ng-change="updateServiceAttribute(service, 'RestartDelay')" ng-pattern="/^([0-9]+)(h|m|s|ms|us|ns)$/i" ng-disabled="isUpdating"/>
<input class="input-sm" type="text" ng-model="service.RestartDelay" ng-change="updateServiceAttribute(service, 'RestartDelay')" ng-pattern="/^([0-9]+)(h|m|s|ms|us|ns)$/i" disable-authorization="DockerServiceUpdate"/>
</td>
<td>
<p class="small text-muted" style="margin-top: 10px;">
@ -36,7 +36,7 @@
<tr>
<td>Restart max attempts</td>
<td>
<input class="input-sm" type="number" ng-model="service.RestartMaxAttempts" ng-change="updateServiceAttribute(service, 'RestartMaxAttempts')" ng-disabled="isUpdating"/>
<input class="input-sm" type="number" ng-model="service.RestartMaxAttempts" ng-change="updateServiceAttribute(service, 'RestartMaxAttempts')" disable-authorization="DockerServiceUpdate"/>
</td>
<td>
<p class="small text-muted" style="margin-top: 10px;">
@ -47,7 +47,7 @@
<tr>
<td>Restart window</td>
<td>
<input class="input-sm" type="text" ng-model="service.RestartWindow" ng-change="updateServiceAttribute(service, 'RestartWindow')" ng-pattern="/^([0-9]+)(h|m|s|ms|us|ns)$/i" ng-disabled="isUpdating"/>
<input class="input-sm" type="text" ng-model="service.RestartWindow" ng-change="updateServiceAttribute(service, 'RestartWindow')" ng-pattern="/^([0-9]+)(h|m|s|ms|us|ns)$/i" disable-authorization="DockerServiceUpdate"/>
</td>
<td>
<p class="small text-muted" style="margin-top: 10px;">
@ -58,7 +58,7 @@
</tbody>
</table>
</rd-widget-body>
<rd-widget-footer>
<rd-widget-footer authorization="DockerServiceUpdate">
<div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!hasChanges(service, ['RestartCondition', 'RestartDelay', 'RestartMaxAttempts', 'RestartWindow'])" ng-click="updateService(service)">Apply changes</button>

View file

@ -3,7 +3,7 @@
<rd-widget-header icon="fa-tasks" title-text="Secrets">
</rd-widget-header>
<rd-widget-body classes="no-padding">
<div class="form-inline" style="padding: 10px;">
<div class="form-inline" style="padding: 10px;" authorization="DockerServiceUpdate">
Add a secret:
<select class="form-control" ng-options="secret.Name for secret in secrets | orderBy: 'Name'" ng-model="state.addSecret.secret">
<option selected disabled hidden value="">Select a secret</option>
@ -38,7 +38,7 @@
<td>{{ secret.Uid }}</td>
<td>{{ secret.Gid }}</td>
<td>{{ secret.Mode }}</td>
<td>
<td authorization="DockerServiceUpdate">
<button class="btn btn-xs btn-danger pull-right" type="button" ng-click="removeSecret(service, $index)" ng-disabled="isUpdating">
<i class="fa fa-trash" aria-hidden="true"></i> Remove secret
</button>
@ -50,7 +50,7 @@
</tbody>
</table>
</rd-widget-body>
<rd-widget-footer>
<rd-widget-footer authorization="DockerServiceUpdate">
<div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!hasChanges(service, ['ServiceSecrets'])" ng-click="updateService(service)">Apply changes</button>

View file

@ -1,7 +1,7 @@
<div id="service-labels">
<rd-widget>
<rd-widget-header icon="fa-tasks" title-text="Service labels">
<div class="nopadding">
<div class="nopadding" authorization="DockerServiceUpdate">
<a class="btn btn-default btn-sm pull-right" ng-click="isUpdating || addLabel(service)" ng-disabled="isUpdating">
<i class="fa fa-plus-circle" aria-hidden="true"></i> label
</a>
@ -34,7 +34,7 @@
<div class="input-group input-group-sm">
<span class="input-group-addon fit-text-size">value</span>
<input type="text" class="form-control" ng-model="label.value" placeholder="e.g. bar" ng-change="updateLabel(service, label)" ng-disabled="isUpdating">
<span class="input-group-btn">
<span class="input-group-btn" authorization="DockerServiceUpdate">
<button class="btn btn-sm btn-danger" type="button" ng-click="removeLabel(service, $index)" ng-disabled="isUpdating">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
@ -45,7 +45,7 @@
</tbody>
</table>
</rd-widget-body>
<rd-widget-footer>
<rd-widget-footer authorization="DockerServiceUpdate">
<div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!hasChanges(service, ['ServiceLabels'])" ng-click="updateService(service)">Apply changes</button>

View file

@ -8,7 +8,7 @@
<tr>
<td>Update Parallelism</td>
<td>
<input class="input-sm" type="number" ng-model="service.UpdateParallelism" ng-change="updateServiceAttribute(service, 'UpdateParallelism')" ng-disabled="isUpdating"/>
<input class="input-sm" type="number" ng-model="service.UpdateParallelism" ng-change="updateServiceAttribute(service, 'UpdateParallelism')" disable-authorization="DockerServiceUpdate"/>
</td>
<td>
<p class="small text-muted" style="margin-top: 10px;">
@ -19,7 +19,7 @@
<tr>
<td>Update Delay</td>
<td>
<input class="input-sm" type="text" ng-model="service.UpdateDelay" ng-change="updateServiceAttribute(service, 'UpdateDelay')" ng-pattern="/^([0-9]+)(h|m|s|ms|us|ns)$/i" ng-disabled="isUpdating"/>
<input class="input-sm" type="text" ng-model="service.UpdateDelay" ng-change="updateServiceAttribute(service, 'UpdateDelay')" ng-pattern="/^([0-9]+)(h|m|s|ms|us|ns)$/i" disable-authorization="DockerServiceUpdate"/>
</td>
<td>
<p class="small text-muted" style="margin-top: 10px;">
@ -32,11 +32,11 @@
<td>
<div class="form-group">
<label class="radio-inline">
<input type="radio" name="failure_action" ng-model="service.UpdateFailureAction" value="continue" ng-change="updateServiceAttribute(service, 'UpdateFailureAction')" ng-disabled="isUpdating">
<input type="radio" name="failure_action" ng-model="service.UpdateFailureAction" value="continue" ng-change="updateServiceAttribute(service, 'UpdateFailureAction')" disable-authorization="DockerServiceUpdate">
Continue
</label>
<label class="radio-inline">
<input type="radio" name="failure_action" ng-model="service.UpdateFailureAction" value="pause" ng-change="updateServiceAttribute(service, 'UpdateFailureAction')" ng-disabled="isUpdating">
<input type="radio" name="failure_action" ng-model="service.UpdateFailureAction" value="pause" ng-change="updateServiceAttribute(service, 'UpdateFailureAction')" disable-authorization="DockerServiceUpdate">
Pause
</label>
</div>
@ -52,11 +52,11 @@
<td>
<div class="form-group">
<label class="radio-inline">
<input type="radio" name="updateconfig_order" ng-model="service.UpdateOrder" value="start-first" ng-change="updateServiceAttribute(service, 'UpdateOrder')" ng-disabled="isUpdating">
<input type="radio" name="updateconfig_order" ng-model="service.UpdateOrder" value="start-first" ng-change="updateServiceAttribute(service, 'UpdateOrder')" disable-authorization="DockerServiceUpdate">
start-first
</label>
<label class="radio-inline">
<input type="radio" name="updateconfig_order" ng-model="service.UpdateOrder" value="stop-first" ng-change="updateServiceAttribute(service, 'UpdateOrder')" ng-disabled="isUpdating">
<input type="radio" name="updateconfig_order" ng-model="service.UpdateOrder" value="stop-first" ng-change="updateServiceAttribute(service, 'UpdateOrder')" disable-authorization="DockerServiceUpdate">
stop-first
</label>
</div>
@ -70,7 +70,7 @@
</tbody>
</table>
</rd-widget-body>
<rd-widget-footer>
<rd-widget-footer authorization="DockerServiceUpdate">
<div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!hasChanges(service, ['UpdateFailureAction', 'UpdateDelay', 'UpdateParallelism', 'UpdateOrder'])" ng-click="updateService(service)">Apply changes</button>

View file

@ -60,7 +60,7 @@
<td>Replicas</td>
<td>
<span ng-if="service.Mode === 'replicated'">
<input class="input-sm" type="number" ng-model="service.Replicas" ng-change="updateServiceAttribute(service, 'Replicas')" ng-disabled="isUpdating" />
<input class="input-sm" type="number" ng-model="service.Replicas" ng-change="updateServiceAttribute(service, 'Replicas')" disable-authorization="DockerServiceUpdate" />
</span>
</td>
</tr>
@ -68,7 +68,7 @@
<td>Image</td>
<td>
<input type="text" class="form-control" uib-typeahead="image for image in availableImages | filter:$viewValue | limitTo:5"
ng-model="service.Image" ng-change="updateServiceAttribute(service, 'Image')" id="image_name" ng-disabled="isUpdating">
ng-model="service.Image" ng-change="updateServiceAttribute(service, 'Image')" id="image_name" disable-authorization="DockerServiceUpdate">
</td>
</tr>
<tr>
@ -76,7 +76,7 @@
Service webhook
<portainer-tooltip position="top" message="Webhook (or callback URI) used to automate the update of this service. Sending a POST request to this callback URI (without requiring any authentication) will pull the most up-to-date version of the associated image and re-deploy this service."></portainer-tooltip>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" ng-model="WebhookExists" ng-click="updateWebhook(service)"><i></i>
<input disable-authorization="DockerServiceUpdate" type="checkbox" ng-model="WebhookExists" ng-click="updateWebhook(service)"><i></i>
</label>
</td>
<td ng-if="webhookURL">
@ -89,14 +89,14 @@
</span>
</td>
</tr>
<tr>
<tr authorization="DockerServiceLogs, DockerServiceUpdate, DockerServiceDelete">
<td colspan="2">
<a ng-if="applicationState.endpoint.apiVersion >= 1.30" class="btn btn-primary btn-sm" type="button" ui-sref="docker.services.service.logs({id: service.Id})"><i class="fa fa-file-alt space-right" aria-hidden="true"></i>Service logs</a>
<button type="button" class="btn btn-primary btn-sm" ng-disabled="state.updateInProgress || isUpdating" ng-click="forceUpdateService(service)" button-spinner="state.updateInProgress" ng-if="applicationState.endpoint.apiVersion >= 1.25">
<a authorization="DockerServiceLogs" ng-if="applicationState.endpoint.apiVersion >= 1.30" class="btn btn-primary btn-sm" type="button" ui-sref="docker.services.service.logs({id: service.Id})"><i class="fa fa-file-alt space-right" aria-hidden="true"></i>Service logs</a>
<button authorization="DockerServiceUpdate" type="button" class="btn btn-primary btn-sm" ng-disabled="state.updateInProgress || isUpdating" ng-click="forceUpdateService(service)" button-spinner="state.updateInProgress" ng-if="applicationState.endpoint.apiVersion >= 1.25">
<span ng-hide="state.updateInProgress"><i class="fa fa-sync space-right" aria-hidden="true"></i>Update the service</span>
<span ng-show="state.updateInProgress">Update in progress...</span>
</button>
<button type="button" class="btn btn-danger btn-sm" ng-disabled="state.deletionInProgress || isUpdating" ng-click="removeService()" button-spinner="state.deletionInProgress">
<button authorization="DockerServiceDelete" type="button" class="btn btn-danger btn-sm" ng-disabled="state.deletionInProgress || isUpdating" ng-click="removeService()" button-spinner="state.deletionInProgress">
<span ng-hide="state.deletionInProgress"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Delete the service</span>
<span ng-show="state.deletionInProgress">Deletion in progress...</span>
</button>
@ -105,7 +105,7 @@
</tbody>
</table>
</rd-widget-body>
<rd-widget-footer>
<rd-widget-footer authorization="DockerServiceUpdate">
<p class="small text-muted">
Do you need help? View the Docker Service documentation <a href="https://docs.docker.com/engine/reference/commandline/service_update/" target="self">here</a>.
</p>

View file

@ -508,8 +508,7 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
$scope.availableLoggingDrivers = data.availableLoggingDrivers;
$scope.availableVolumes = data.volumes;
$scope.allowBindMounts = data.settings.AllowBindMountsForRegularUsers;
var userDetails = Authentication.getUserDetails();
$scope.isAdmin = userDetails.role === 1;
$scope.isAdmin = Authentication.isAdmin();
if (data.webhooks.length > 0) {
var webhook = data.webhooks[0];

View file

@ -60,9 +60,7 @@ function ($q, $scope, SystemService, NodeService, Notifications, StateManager, A
function initView() {
if (StateManager.getState().application.authentication) {
var userDetails = Authentication.getUserDetails();
var isAdmin = userDetails.role === 1 ? true: false;
$scope.isAdmin = isAdmin;
$scope.isAdmin = Authentication.isAdmin();
}
var provider = $scope.applicationState.endpoint.mode.provider;

View file

@ -69,7 +69,7 @@ function ($q, $scope, $state, VolumeService, PluginService, ResourceControlServi
var volumeConfiguration = VolumeService.createVolumeConfiguration(name, driver, driverOptions);
var accessControlData = $scope.formValues.AccessControlData;
var userDetails = Authentication.getUserDetails();
var isAdmin = userDetails.role === 1;
var isAdmin = Authentication.isAdmin();
if (!validateForm(accessControlData, isAdmin)) {
return;

View file

@ -16,7 +16,7 @@
<td>ID</td>
<td>
{{ volume.Id }}
<button class="btn btn-xs btn-danger" ng-click="removeVolume()"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove this volume</button>
<button authorization="DockerVolumeDelete" class="btn btn-xs btn-danger" ng-click="removeVolume()"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove this volume</button>
</td>
</tr>
<tr>

View file

@ -1,4 +1,5 @@
angular.module('portainer.extensions', [
'portainer.extensions.registrymanagement',
'portainer.extensions.oauth'
'portainer.extensions.oauth',
'portainer.extensions.rbac'
]);

View file

@ -0,0 +1,19 @@
angular.module('portainer.extensions.rbac', ['ngResource'])
.constant('API_ENDPOINT_ROLES', 'api/roles')
.config(['$stateRegistryProvider', function ($stateRegistryProvider) {
'use strict';
var roles = {
name: 'portainer.roles',
url: '/roles',
views: {
'content@': {
templateUrl: './views/roles/roles.html',
controller: 'RolesController',
controllerAs: 'ctrl'
}
}
};
$stateRegistryProvider.register(roles);
}]);

View file

@ -0,0 +1,44 @@
<div class="col-sm-12" style="margin-bottom: 0px;">
<rd-widget ng-if="ctrl.users">
<rd-widget-header icon="fa-user-lock" title-text="Effective access viewer"></rd-widget-header>
<rd-widget-body>
<form class="form-horizontal">
<div class="col-sm-12 form-section-title">
User
</div>
<div class="form-group">
<div class="col-sm-12">
<span class="small text-muted" ng-if="ctrl.users.length === 0">
No user available
</span>
<ui-select ng-if="ctrl.users.length > 0" ng-model="ctrl.selectedUser" ng-change="ctrl.onUserSelect()">
<ui-select-match placeholder="Select a user">
<span>{{ $select.selected.Username }}</span>
</ui-select-match>
<ui-select-choices repeat="item in (ctrl.users | filter: $select.search)">
<span>{{ item.Username }}</span>
</ui-select-choices>
</ui-select>
</div>
</div>
<div class="col-sm-12 form-section-title">
Access
</div>
<div>
<div class="small text-muted" style="margin-bottom: 15px;">
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
Effective role for each endpoint will be displayed for the selected user
</div>
</div>
<access-viewer-datatable
ng-if="ctrl.users"
table-key="access_viewer"
dataset="ctrl.userRoles"
order-by="EndpointName"
>
</access-viewer-datatable>
</form>
</rd-widget-body>
</rd-widget>
</div>

View file

@ -0,0 +1,5 @@
angular.module('portainer.app').component('accessViewer', {
templateUrl: './accessViewer.html',
controller: 'AccessViewerController',
controllerAs: 'ctrl'
});

View file

@ -0,0 +1,128 @@
import _ from "lodash-es";
import angular from "angular";
import AccessViewerPolicyModel from '../../models/access'
class AccessViewerController {
/* @ngInject */
constructor(Notifications, ExtensionService, RoleService, UserService, EndpointService, GroupService, TeamService, TeamMembershipService) {
this.Notifications = Notifications;
this.ExtensionService = ExtensionService;
this.RoleService = RoleService;
this.UserService = UserService;
this.EndpointService = EndpointService;
this.GroupService = GroupService;
this.TeamService = TeamService;
this.TeamMembershipService = TeamMembershipService;
}
onUserSelect() {
this.userRoles = [];
const userRoles = {};
const user = this.selectedUser;
const userMemberships = _.filter(this.teamMemberships, {UserId: user.Id});
for (const [,endpoint] of _.entries(this.endpoints)) {
let role = this.getRoleFromUserEndpointPolicy(user, endpoint);
if (role) {
userRoles[endpoint.Id] = role;
continue;
}
role = this.getRoleFromUserEndpointGroupPolicy(user, endpoint);
if (role) {
userRoles[endpoint.Id] = role;
continue;
}
role = this.getRoleFromTeamEndpointPolicies(userMemberships, endpoint);
if (role) {
userRoles[endpoint.Id] = role;
continue;
}
role = this.getRoleFromTeamEndpointGroupPolicies(userMemberships, endpoint);
if (role) {
userRoles[endpoint.Id] = role;
}
}
this.userRoles = _.values(userRoles);
}
findLowestRole(policies) {
return _.first(_.orderBy(policies, 'RoleId', 'desc'));
}
getRoleFromUserEndpointPolicy(user, endpoint) {
const policyRoles = [];
const policy = endpoint.UserAccessPolicies[user.Id];
if (policy) {
const accessPolicy = new AccessViewerPolicyModel(policy, endpoint, this.roles, null, null);
policyRoles.push(accessPolicy);
}
return this.findLowestRole(policyRoles);
}
getRoleFromUserEndpointGroupPolicy(user, endpoint) {
const policyRoles = [];
const policy = this.groupUserAccessPolicies[endpoint.GroupId][user.Id];
if (policy) {
const accessPolicy = new AccessViewerPolicyModel(policy, endpoint, this.roles, this.groups[endpoint.GroupId], null);
policyRoles.push(accessPolicy);
}
return this.findLowestRole(policyRoles);
}
getRoleFromTeamEndpointPolicies(memberships, endpoint) {
const policyRoles = [];
for (const membership of memberships) {
const policy = endpoint.TeamAccessPolicies[membership.TeamId];
if (policy) {
const accessPolicy = new AccessViewerPolicyModel(policy, endpoint, this.roles, null, this.teams[membership.TeamId]);
policyRoles.push(accessPolicy);
}
}
return this.findLowestRole(policyRoles);
}
getRoleFromTeamEndpointGroupPolicies(memberships, endpoint) {
const policyRoles = [];
for (const membership of memberships) {
const policy = this.groupTeamAccessPolicies[endpoint.GroupId][membership.TeamId]
if (policy) {
const accessPolicy = new AccessViewerPolicyModel(policy, endpoint, this.roles, this.groups[endpoint.GroupId], this.teams[membership.TeamId]);
policyRoles.push(accessPolicy);
}
}
return this.findLowestRole(policyRoles);
}
async $onInit() {
try {
this.rbacEnabled = await this.ExtensionService.extensionEnabled(this.ExtensionService.EXTENSIONS.RBAC);
if (this.rbacEnabled) {
this.users = await this.UserService.users();
this.endpoints = _.keyBy(await this.EndpointService.endpoints(), 'Id');
const groups = await this.GroupService.groups();
this.groupUserAccessPolicies = {};
this.groupTeamAccessPolicies = {};
_.forEach(groups, group => {
this.groupUserAccessPolicies[group.Id] = group.UserAccessPolicies;
this.groupTeamAccessPolicies[group.Id] = group.TeamAccessPolicies;
});
this.groups = _.keyBy(groups, 'Id');
this.roles = _.keyBy(await this.RoleService.roles(), 'Id');
this.teams = _.keyBy(await this.TeamService.teams(), 'Id');
this.teamMemberships = await this.TeamMembershipService.memberships();
}
} catch (err) {
this.Notifications.error("Failure", err, "Unable to retrieve accesses");
}
}
}
export default AccessViewerController;
angular
.module("portainer.app")
.controller("AccessViewerController", AccessViewerController);

View file

@ -0,0 +1,74 @@
<div class="datatable">
<div class="searchBar">
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" ng-change="$ctrl.onTextFilterChange()"
placeholder="Search...">
</div>
<div class="table-responsive">
<table class="table table-hover nowrap-cells">
<thead>
<tr>
<th>
<a ng-click="$ctrl.changeOrderBy('EndpointName')">
Endpoint
<i class="fa fa-sort-alpha-down" aria-hidden="true"
ng-if="$ctrl.state.orderBy === 'EndpointName' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true"
ng-if="$ctrl.state.orderBy === 'EndpointName' && $ctrl.state.reverseOrder"></i>
</a>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('RoleName')">
Role
<i class="fa fa-sort-alpha-down" aria-hidden="true"
ng-if="$ctrl.state.orderBy === 'RoleName' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true"
ng-if="$ctrl.state.orderBy === 'RoleName' && $ctrl.state.reverseOrder"></i>
</a>
</th>
<th>Access origin</th>
</tr>
</thead>
<tbody>
<tr
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit)) track by $index">
<td>{{ item.EndpointName }}</td>
<td>{{ item.RoleName }}</td>
<td>{{ item.TeamName ? 'Team' : 'User'}} <code ng-if="item.TeamName">{{ item.TeamName }}</code> access defined on {{ item.AccessLocation }} <code ng-if="item.GroupName">{{item.GroupName}}</code>
<a ng-if="item.AccessLocation === 'endpoint'" ui-sref="portainer.endpoints.endpoint.access({id: item.EndpointId})"><i style="margin-left: 5px;" class="fa fa-users" aria-hidden="true"></i> Manage access </a>
<a ng-if="item.AccessLocation === 'endpoint group'" ui-sref="portainer.groups.group.access({id: item.GroupId})"><i style="margin-left: 5px;" class="fa fa-users" aria-hidden="true"></i> Manage access </a>
</td>
</tr>
<tr ng-if="!$ctrl.dataset">
<td colspan="3" class="text-center text-muted">Select a user to show associated access and role</td>
</tr>
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
<td colspan="3" class="text-center text-muted">The selected user do not have access to any endpoint</td>
</tr>
</tbody>
</table>
</div>
<div class="footer" ng-if="$ctrl.dataset">
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0">
{{ $ctrl.state.selectedItemCount }} item(s) selected
</div>
<div class="paginationControls">
<form class="form-inline">
<span class="limitSelector">
<span style="margin-right: 5px;">
Items per page
</span>
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit"
ng-change="$ctrl.changePaginationLimit()">
<option value="0">All</option>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</span>
<dir-pagination-controls max-size="5"></dir-pagination-controls>
</form>
</div>
</div>
</div>

View file

@ -0,0 +1,11 @@
angular.module('portainer.app').component('accessViewerDatatable', {
templateUrl: './accessViewerDatatable.html',
controller: 'GenericDatatableController',
bindings: {
titleText: '@',
titleIcon: '@',
tableKey: '@',
orderBy: '@',
dataset: '<'
}
});

View file

@ -0,0 +1,71 @@
<div class="datatable" ng-class="{'portainer-disabled-datatable': !$ctrl.rbacEnabled}">
<rd-widget>
<rd-widget-body classes="no-padding">
<div class="toolBar">
<div class="toolBarTitle">
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }}
</div>
</div>
<div class="searchBar">
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" ng-change="$ctrl.onTextFilterChange()" placeholder="Search..." auto-focus ng-disabled="!$ctrl.rbacEnabled">
</div>
<div class="table-responsive">
<table class="table table-hover nowrap-cells">
<thead>
<tr>
<th>
<a ng-click="$ctrl.changeOrderBy('Name')">
Name
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
</a>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('Description')">
Description
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Description' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Description' && $ctrl.state.reverseOrder"></i>
</a>
</th>
</tr>
</thead>
<tbody>
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
<td>{{ item.Name }}</td>
<td>{{ item.Description }}</td>
</tr>
<tr ng-if="!$ctrl.dataset">
<td colspan="2" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
<td colspan="2" class="text-center text-muted">No role available.</td>
</tr>
</tbody>
</table>
</div>
<div class="footer" ng-if="$ctrl.dataset">
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0">
{{ $ctrl.state.selectedItemCount }} item(s) selected
</div>
<div class="paginationControls">
<form class="form-inline">
<span class="limitSelector">
<span style="margin-right: 5px;">
Items per page
</span>
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
<option value="0">All</option>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</span>
<dir-pagination-controls max-size="5"></dir-pagination-controls>
</form>
</div>
</div>
</rd-widget-body>
</rd-widget>
</div>

View file

@ -0,0 +1,13 @@
angular.module('portainer.extensions.rbac').component('rolesDatatable', {
templateUrl: './rolesDatatable.html',
controller: 'GenericDatatableController',
bindings: {
titleText: '@',
titleIcon: '@',
dataset: '<',
tableKey: '@',
orderBy: '@',
reverseOrder: '<',
rbacEnabled: '<'
}
});

View file

@ -0,0 +1,33 @@
angular.module('portainer.extensions.rbac').directive('authorization', ['Authentication', 'ExtensionService',
function(Authentication, ExtensionService) {
return {
restrict: 'A',
link: async function(scope, elem, attrs) {
elem.hide();
try {
const rbacEnabled = await ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC);
if (!rbacEnabled) {
elem.show();
return;
}
} catch (err) {
elem.show();
return;
}
var authorizations = attrs.authorization.split(",");
for (var i = 0; i < authorizations.length; i++) {
authorizations[i] = authorizations[i].trim();
}
var hasAuthorizations = Authentication.hasAuthorizations(authorizations);
if (hasAuthorizations) {
elem.show();
} else if (!hasAuthorizations && elem[0].tagName === 'A') {
elem.show();
elem.addClass('portainer-disabled-link');
}
}
}
}]);

View file

@ -0,0 +1,27 @@
angular.module('portainer.extensions.rbac')
.directive('disableAuthorization', ['Authentication', 'ExtensionService', function(Authentication, ExtensionService) {
return {
restrict: 'A',
link: async function (scope, elem, attrs) {
try {
const rbacEnabled = await ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC);
if (!rbacEnabled) {
elem.show();
return;
}
} catch (err) {
elem.show();
return;
}
var authorizations = attrs.disableAuthorization.split(",");
for (var i = 0; i < authorizations.length; i++) {
authorizations[i] = authorizations[i].trim();
}
if (!Authentication.hasAuthorizations(authorizations)) {
elem.attr('disabled', true);
}
}
}
}]);

View file

@ -0,0 +1,15 @@
export default function AccessViewerPolicyModel(policy, endpoint, roles, group, team) {
this.EndpointId = endpoint.Id;
this.EndpointName = endpoint.Name;
this.RoleId = policy.RoleId;
this.RoleName = roles[policy.RoleId].Name;
if (group) {
this.GroupId = group.Id;
this.GroupName = group.Name
}
if (team) {
this.TeamId = team.Id;
this.TeamName = team.Name;
}
this.AccessLocation = group ? 'endpoint group' : 'endpoint';
}

View file

@ -0,0 +1,6 @@
export function RoleViewModel(data) {
this.ID = data.Id;
this.Name = data.Name;
this.Description = data.Description;
this.Authorizations = data.Authorizations;
}

View file

@ -0,0 +1,11 @@
angular.module('portainer.app')
.factory('Roles', ['$resource', 'API_ENDPOINT_ROLES', function RolesFactory($resource, API_ENDPOINT_ROLES) {
'use strict';
return $resource(API_ENDPOINT_ROLES + '/:id', {}, {
create: { method: 'POST', ignoreLoadingBar: true },
query: { method: 'GET', isArray: true },
get: { method: 'GET', params: { id: '@id' } },
update: { method: 'PUT', params: { id: '@id' } },
remove: { method: 'DELETE', params: { id: '@id'} }
});
}]);

View file

@ -0,0 +1,47 @@
import {
RoleViewModel,
// EndpointRoleCreateRequest,
// EndpointRoleUpdateRequest
} from '../models/role';
angular.module('portainer.extensions.rbac')
.factory('RoleService', ['$q', 'Roles',
function RoleService($q, Roles) {
'use strict';
var service = {};
service.role = function(roleId) {
var deferred = $q.defer();
Roles.get({ id: roleId }).$promise
.then(function success(data) {
var role = new RoleViewModel(data);
deferred.resolve(role);
})
.catch(function error(err) {
deferred.reject({ msg: 'Unable to retrieve role', err: err });
});
return deferred.promise;
};
service.roles = function() {
return Roles.query({}).$promise;
};
// service.createRole = function(model, endpoints) {
// var payload = new EndpointRoleCreateRequest(model, endpoints);
// return EndpointRoles.create(payload).$promise;
// };
//
// service.updateRole = function(model, endpoints) {
// var payload = new EndpointRoleUpdateRequest(model, endpoints);
// return EndpointRoles.update(payload).$promise;
// };
service.deleteRole = function(roleId) {
return Roles.remove({ id: roleId }).$promise;
};
return service;
}]);

View file

@ -0,0 +1,36 @@
<rd-header>
<rd-header-title title-text="Roles">
<a data-toggle="tooltip" title="Refresh" ui-sref="portainer.roles" ui-sref-opts="{reload: true}">
<i class="fa fa-sync" aria-hidden="true"></i>
</a>
</rd-header-title>
<rd-header-content>Role management</rd-header-content>
</rd-header>
<information-panel ng-if="!ctrl.rbacEnabled" title-text="Information">
<span class="small">
<p class="text-muted">
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i>
The <a ui-sref="portainer.extensions.extension({id: 3})"
tooltip-append-to-body="true" tooltip-placement="bottom" tooltip-class="portainer-tooltip"
uib-tooltip="Feature available via an extension">RBAC extension</a>
is required to use this feature.
</p>
</span>
</information-panel>
<div class="row">
<div class="col-sm-12">
<roles-datatable
title-text="Roles" title-icon="fa-file-code"
dataset="ctrl.roles" table-key="roles"
order-by="Name"
rbac-enabled="ctrl.rbacEnabled"
></roles-datatable>
</div>
</div>
<div class="row">
<access-viewer ng-if="ctrl.rbacEnabled">
</access-viewer>
</div>

View file

@ -0,0 +1,25 @@
import angular from 'angular';
class RolesController {
/* @ngInject */
constructor(Notifications, RoleService, ExtensionService) {
this.Notifications = Notifications;
this.RoleService = RoleService;
this.ExtensionService = ExtensionService;
}
async $onInit() {
this.roles = [];
this.rbacEnabled = false;
try {
this.rbacEnabled = await this.ExtensionService.extensionEnabled(this.ExtensionService.EXTENSIONS.RBAC);
this.roles = await this.RoleService.roles();
} catch (err) {
this.Notifications.error('Failure', err, 'Unable to retrieve roles');
}
}
}
export default RolesController;
angular.module('portainer.extensions.rbac').controller('RolesController', RolesController);

View file

@ -11,9 +11,7 @@ function ($transition$, $scope, RegistryService, RegistryV2Service, Notification
var authenticationEnabled = $scope.applicationState.application.authentication;
if (authenticationEnabled) {
var userDetails = Authentication.getUserDetails();
var isAdmin = userDetails.role === 1;
$scope.isAdmin = isAdmin;
$scope.isAdmin = Authentication.isAdmin();
}
RegistryService.registry(registryId)

View file

@ -104,7 +104,8 @@ angular.module('portainer.app', [])
views: {
'content@': {
templateUrl: './views/endpoints/access/endpointAccess.html',
controller: 'EndpointAccessController'
controller: 'EndpointAccessController',
controllerAs: 'ctrl'
}
}
};

View file

@ -0,0 +1,132 @@
<div class="datatable">
<rd-widget>
<rd-widget-header icon="{{$ctrl.titleIcon}}" title-text="{{ $ctrl.titleText }}">
</rd-widget-header>
<rd-widget-body classes="no-padding">
<div class="toolBar small" ng-if="$ctrl.inheritFrom">
Access tagged as <code>inherited</code> are inherited from the group access. They cannot be
removed nor modified at the endpoint level but they can be overriden.
</div>
<div class="toolBar small" ng-if="$ctrl.inheritFrom">
Access tagged as <code>override</code> are overriding the group access for the related users / teams.
</div>
<div class="actionBar">
<button type="button" class="btn btn-sm btn-danger" ng-disabled="$ctrl.state.selectedItemCount === 0"
ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button>
<button ng-if="$ctrl.rbacEnabled" type="button" class="btn btn-sm btn-primary" ng-disabled="($ctrl.dataset | filter:{ Updated: true}).length === 0 "
ng-click="$ctrl.updateAction()">
<i class="fa fa-check space-right" aria-hidden="true"></i>Update
</button>
</div>
<div class="searchBar">
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" ng-change="$ctrl.onTextFilterChange()"
placeholder="Search...">
</div>
<div class="table-responsive">
<table class="table table-hover nowrap-cells">
<thead>
<tr>
<th>
<span class="md-checkbox">
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll"
ng-change="$ctrl.selectAll()" />
<label for="select_all"></label>
</span>
<a ng-click="$ctrl.changeOrderBy('Name')">
Name
<i class="fa fa-sort-alpha-down" aria-hidden="true"
ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true"
ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
</a>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('Type')">
Type
<i class="fa fa-sort-alpha-down" aria-hidden="true"
ng-if="$ctrl.state.orderBy === 'Type' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true"
ng-if="$ctrl.state.orderBy === 'Type' && $ctrl.state.reverseOrder"></i>
</a>
</th>
<th ng-if="$ctrl.rbacEnabled">
<a ng-click="$ctrl.changeOrderBy('RoleName')">
Role
<i class="fa fa-sort-alpha-down" aria-hidden="true"
ng-if="$ctrl.state.orderBy === 'RoleName' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true"
ng-if="$ctrl.state.orderBy === 'RoleName' && $ctrl.state.reverseOrder"></i>
</a>
</th>
</tr>
</thead>
<tbody>
<tr
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit)) track by $index"
ng-class="{active: item.Checked}">
<td>
<span class="md-checkbox">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-disabled="item.Inherited"
ng-change="$ctrl.selectItem(item)" />
<label for="select_{{ $index }}"></label>
</span>
{{ item.Name }}
<span ng-if="item.Inherited" class="text-muted small" style="margin-left: 2px;"><code
style="font-size: 85% !important;">inherited</code></span>
<span ng-if="item.Override" class="text-muted small" style="margin-left: 2px;"><code
style="font-size: 85% !important;">override</code></span>
</td>
<td>{{ item.Type }}</td>
<td ng-if="$ctrl.rbacEnabled">
<span ng-if="!item.Updated">
{{ item.Role.Name }}
<a ng-if="!item.Inherited" class="interactive" ng-click="item.Updated = true; item.OldRole = item.Role; $event.stopPropagation();">
<i class="fa fa-edit" aria-hidden="true"></i> Edit
</a>
</span>
<span ng-if="item.Updated">
<select ng-model="item.Role"
ng-options="role.Name for role in $ctrl.roles">
</select>
<a class="interactive" ng-click="item.Updated = false; item.Role = item.OldRole; item.OldRole = null; $event.stopPropagation();"><i class="fa fa-times"></i></a>
</span>
</td>
</tr>
<tr ng-if="!$ctrl.dataset">
<td colspan="4" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
<td colspan="4" class="text-center text-muted">No authorized user or team.</td>
</tr>
</tbody>
</table>
</div>
<div class="footer" ng-if="$ctrl.dataset">
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0">
{{ $ctrl.state.selectedItemCount }} item(s) selected
</div>
<div class="paginationControls">
<form class="form-inline">
<span class="limitSelector">
<span style="margin-right: 5px;">
Items per page
</span>
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit"
ng-change="$ctrl.changePaginationLimit()">
<option value="0">All</option>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</span>
<dir-pagination-controls max-size="5"></dir-pagination-controls>
</form>
</div>
</div>
</rd-widget-body>
</rd-widget>
</div>

View file

@ -0,0 +1,17 @@
angular.module('portainer.app').component('accessDatatable', {
templateUrl: './accessDatatable.html',
controller: 'AccessDatatableController',
bindings: {
titleText: '@',
titleIcon: '@',
dataset: '<',
roles: '<',
tableKey: '@',
orderBy: '@',
removeAction: '<',
updateAction: '<',
reverseOrder: '<',
rbacEnabled: '<',
inheritFrom: '<'
}
});

View file

@ -0,0 +1,20 @@
angular.module('portainer.app')
.controller('AccessDatatableController', ['$scope', '$controller',
function ($scope, $controller) {
angular.extend(this, $controller('GenericDatatableController', {$scope: $scope}));
this.disableRemove = function(item) {
return item.Inherited;
};
this.selectAll = function() {
for (var i = 0; i < this.state.filteredDataSet.length; i++) {
var item = this.state.filteredDataSet[i];
if (!this.disableRemove(item) && item.Checked !== this.state.selectAll) {
item.Checked = this.state.selectAll;
this.selectItem(item);
}
}
};
}
]);

View file

@ -33,8 +33,7 @@ function ($q, UserService, TeamService, Notifications, Authentication, ResourceC
}
function initComponent() {
var userDetails = Authentication.getUserDetails();
var isAdmin = userDetails.role === 1 ? true: false;
var isAdmin = Authentication.isAdmin();
ctrl.isAdmin = isAdmin;
if (isAdmin) {

View file

@ -90,7 +90,7 @@ function ($q, $state, UserService, TeamService, ResourceControlService, Notifica
function initComponent() {
var userDetails = Authentication.getUserDetails();
var isAdmin = userDetails.role === 1 ? true: false;
var isAdmin = Authentication.isAdmin();
var userId = userDetails.ID;
ctrl.isAdmin = isAdmin;
var resourceControl = ctrl.resourceControl;

View file

@ -1,10 +1,12 @@
angular.module('portainer.app').component('porAccessManagement', {
templateUrl: './porAccessManagement.html',
controller: 'porAccessManagementController',
controllerAs: 'ctrl',
bindings: {
accessControlledEntity: '<',
inheritFrom: '<',
entityType: '@',
updateAccess: '&'
updateAccess: '<',
actionInProgress: '<'
}
});

View file

@ -1,46 +1,65 @@
<rd-widget>
<rd-widget-header icon="fa-users" title-text="Access management"></rd-widget-header>
<rd-widget-body>
<form class="form-horizontal">
<div class="small text-muted">
<p>You can select which user or team can access this {{ $ctrl.entityType }} by moving them to the authorized accesses table. Simply click
on a user or team entry to move it from one table to the other.</p>
<p ng-if="$ctrl.inheritFrom">
<b>Note</b>: accesses tagged as <code>inherited</code> are inherited from the group accesses and cannot be remove at the endpoint level.
</p>
</div>
<div class="form-group" style="margin-top: 20px;">
<!-- available-endpoints -->
<div class="col-sm-6">
<div class="text-center small text-muted">Users and teams</div>
<div class="text-center small text-muted" style="margin-top: 5px;">
<button class="btn btn-primary btn-sm" ng-click="$ctrl.authorizeAllAccesses()" ng-disabled="$ctrl.accesses.length === 0"><i class="fa fa-user-plus space-right" aria-hidden="true"></i>Authorize all</button>
<div class="row">
<div class="col-sm-12">
<rd-widget ng-if="ctrl.availableUsersAndTeams && ctrl.accessControlledEntity">
<rd-widget-header icon="fa-user-lock" title-text="Create access"></rd-widget-header>
<rd-widget-body>
<form class="form-horizontal">
<div class="form-group">
<label class="col-sm-3 col-lg-2 control-label text-left">
Select user(s) and/or team(s)
</label>
<div class="col-sm-9 col-lg-4">
<span class="small text-muted" ng-if="ctrl.availableUsersAndTeams.length === 0">
No user nor team available.
</span>
<span isteven-multi-select ng-if="ctrl.availableUsersAndTeams.length > 0" input-model="ctrl.availableUsersAndTeams"
output-model="ctrl.formValues.multiselectOutput" button-label="icon '-' Name" item-label="icon '-' Name"
tick-property="ticked" helper-elements="filter" search-property="Name"
translation="{nothingSelected: 'Select one or more users and/or teams', search: 'Search...'}">
</span>
</div>
</div>
<div style="margin-top: 10px;">
<access-table
dataset="$ctrl.accesses"
entry-click="$ctrl.authorizeAccess"
empty-dataset-message="No user or team available"
></access-table>
<div class="form-group" ng-if="ctrl.entityType !== 'registry'">
<label class="col-sm-3 col-lg-2 control-label text-left">
Role
</label>
<div class="col-sm-9 col-lg-4">
<select ng-if="ctrl.rbacEnabled" class="form-control" ng-model="ctrl.formValues.selectedRole"
ng-options="role.Name for role in ctrl.roles">
</select>
<span class="small text-muted" ng-if="!ctrl.rbacEnabled">
The <a ui-sref="portainer.extensions.extension({id: 3})">RBAC extension</a> is required to select a specific role.
</span>
</div>
</div>
</div>
<!-- !available-endpoints -->
<!-- associated-endpoints -->
<div class="col-sm-6">
<div class="text-center small text-muted">Authorized users and teams</div>
<div class="text-center small text-muted" style="margin-top: 5px;">
<button class="btn btn-primary btn-sm" ng-click="$ctrl.unauthorizeAllAccesses()" ng-disabled="$ctrl.authorizedAccesses.length === 0"><i class="fa fa-user-times space-right" aria-hidden="true"></i>Deny all</button>
<!-- actions -->
<div class="form-group">
<div class="col-sm-12">
<button type="submit" class="btn btn-primary btn-sm" ng-disabled="(ctrl.availableUsersAndTeams | filter:{ticked:true}).length === 0 || ctrl.actionInProgress"
ng-click="ctrl.authorizeAccess()" button-spinner="ctrl.actionInProgress">
<span ng-hide="ctrl.state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Create
access</span>
<span ng-show="ctrl.state.actionInProgress">Creating access...</span>
</button>
</div>
</div>
<div style="margin-top: 10px;">
<access-table
dataset="$ctrl.authorizedAccesses"
entry-click="$ctrl.unauthorizeAccess"
empty-dataset-message="No authorized user or team"
></access-table>
</div>
</div>
<!-- !associated-endpoints -->
</div>
</form>
</rd-widget-body>
</rd-widget>
<!-- !actions -->
</form>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<access-datatable ng-if="ctrl.authorizedUsersAndTeams"
title-text="Access" title-icon="fa-user-lock"
table-key="{{ 'access_' + ctrl.entityType}}" order-by="Name"
rbac-enabled="ctrl.rbacEnabled && ctrl.entityType !== 'registry'" inherit-from="ctrl.inheritFrom"
dataset="ctrl.authorizedUsersAndTeams" roles="ctrl.roles"
update-action="ctrl.updateAction" remove-action="ctrl.unauthorizeAccess"
>
</access-datatable>
</div>
</div>

View file

@ -1,143 +1,104 @@
import _ from 'lodash-es';
import _ from "lodash-es";
angular.module('portainer.app')
.controller('porAccessManagementController', ['AccessService', 'Notifications',
function (AccessService, Notifications) {
var ctrl = this;
import angular from "angular";
function dispatchUserAndTeamIDs(accesses, users, teams) {
angular.forEach(accesses, function (access) {
if (access.Type === 'user' && !access.Inherited) {
users.push(access.Id);
} else if (access.Type === 'team' && !access.Inherited) {
teams.push(access.Id);
class PorAccessManagementController {
/* @ngInject */
constructor(Notifications, ExtensionService, AccessService, RoleService) {
this.Notifications = Notifications;
this.ExtensionService = ExtensionService;
this.AccessService = AccessService;
this.RoleService = RoleService;
this.unauthorizeAccess = this.unauthorizeAccess.bind(this);
this.updateAction = this.updateAction.bind(this);
}
updateAction() {
const entity = this.accessControlledEntity;
const oldUserAccessPolicies = entity.UserAccessPolicies;
const oldTeamAccessPolicies = entity.TeamAccessPolicies;
const updatedUserAccesses = _.filter(this.authorizedUsersAndTeams, {Updated: true, Type: 'user', Inherited: false});
const updatedTeamAccesses = _.filter(this.authorizedUsersAndTeams, {Updated: true, Type: 'team', Inherited: false});
const accessPolicies = this.AccessService.generateAccessPolicies(oldUserAccessPolicies, oldTeamAccessPolicies, updatedUserAccesses, updatedTeamAccesses);
this.accessControlledEntity.UserAccessPolicies = accessPolicies.userAccessPolicies;
this.accessControlledEntity.TeamAccessPolicies = accessPolicies.teamAccessPolicies;
this.updateAccess();
}
authorizeAccess() {
const entity = this.accessControlledEntity;
const oldUserAccessPolicies = entity.UserAccessPolicies;
const oldTeamAccessPolicies = entity.TeamAccessPolicies;
const selectedRoleId = this.rbacEnabled ? this.formValues.selectedRole.Id : 0;
const selectedUserAccesses = _.filter(this.formValues.multiselectOutput, (access) => access.Type === "user");
const selectedTeamAccesses = _.filter(this.formValues.multiselectOutput, (access) => access.Type === "team");
const accessPolicies = this.AccessService.generateAccessPolicies(oldUserAccessPolicies, oldTeamAccessPolicies, selectedUserAccesses, selectedTeamAccesses, selectedRoleId);
this.accessControlledEntity.UserAccessPolicies = accessPolicies.userAccessPolicies;
this.accessControlledEntity.TeamAccessPolicies = accessPolicies.teamAccessPolicies;
this.updateAccess();
}
unauthorizeAccess(selectedAccesses) {
const entity = this.accessControlledEntity;
const userAccessPolicies = entity.UserAccessPolicies;
const teamAccessPolicies = entity.TeamAccessPolicies;
const selectedUserAccesses = _.filter(selectedAccesses, (access) => access.Type === "user");
const selectedTeamAccesses = _.filter(selectedAccesses, (access) => access.Type === "team");
_.forEach(selectedUserAccesses, (access) => delete userAccessPolicies[access.Id]);
_.forEach(selectedTeamAccesses, (access) => delete teamAccessPolicies[access.Id]);
this.updateAccess();
}
async $onInit() {
const entity = this.accessControlledEntity;
if (!entity) {
this.Notifications.error("Failure", "Unable to retrieve accesses");
return;
}
if (!entity.UserAccessPolicies) {
entity.UserAccessPolicies = {}
}
if (!entity.TeamAccessPolicies) {
entity.TeamAccessPolicies = {};
}
const parent = this.inheritFrom;
if (parent && !parent.UserAccessPolicies) {
parent.UserAccessPolicies = {}
}
if (parent && !parent.TeamAccessPolicies) {
parent.TeamAccessPolicies = {};
}
this.roles = [];
this.rbacEnabled = false;
try {
this.rbacEnabled = await this.ExtensionService.extensionEnabled(this.ExtensionService.EXTENSIONS.RBAC);
if (this.rbacEnabled) {
this.roles = await this.RoleService.roles();
this.formValues = {
selectedRole: this.roles[0]
};
}
});
}
function processAuthorizedIDs(accesses, authorizedAccesses) {
var authorizedUserIDs = [];
var authorizedTeamIDs = [];
if (accesses) {
dispatchUserAndTeamIDs(accesses, authorizedUserIDs, authorizedTeamIDs);
const data = await this.AccessService.accesses(
entity.UserAccessPolicies,
entity.TeamAccessPolicies,
parent ? parent.UserAccessPolicies : {},
parent ? parent.TeamAccessPolicies : {},
this.roles
);
this.availableUsersAndTeams = data.availableUsersAndTeams;
this.authorizedUsersAndTeams = data.authorizedUsersAndTeams;
} catch (err) {
this.availableUsersAndTeams = [];
this.authorizedUsersAndTeams = [];
this.Notifications.error("Failure", err, "Unable to retrieve accesses");
}
if (authorizedAccesses) {
dispatchUserAndTeamIDs(authorizedAccesses, authorizedUserIDs, authorizedTeamIDs);
}
return {
userIDs: authorizedUserIDs,
teamIDs: authorizedTeamIDs
};
}
}
function removeFromAccesses(access, accesses) {
_.remove(accesses, function(n) {
return n.Id === access.Id && n.Type === access.Type;
});
}
function removeFromAccessIDs(accessId, accessIDs) {
_.remove(accessIDs, function(n) {
return n === accessId;
});
}
ctrl.authorizeAccess = function(access) {
var accessData = processAuthorizedIDs(null, ctrl.authorizedAccesses);
var authorizedUserIDs = accessData.userIDs;
var authorizedTeamIDs = accessData.teamIDs;
if (access.Type === 'user') {
authorizedUserIDs.push(access.Id);
} else if (access.Type === 'team') {
authorizedTeamIDs.push(access.Id);
}
ctrl.updateAccess({ userAccesses: authorizedUserIDs, teamAccesses: authorizedTeamIDs })
.then(function success() {
removeFromAccesses(access, ctrl.accesses);
ctrl.authorizedAccesses.push(access);
Notifications.success('Accesses successfully updated');
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to update accesses');
});
};
ctrl.unauthorizeAccess = function(access) {
var accessData = processAuthorizedIDs(null, ctrl.authorizedAccesses);
var authorizedUserIDs = accessData.userIDs;
var authorizedTeamIDs = accessData.teamIDs;
if (access.Type === 'user') {
removeFromAccessIDs(access.Id, authorizedUserIDs);
} else if (access.Type === 'team') {
removeFromAccessIDs(access.Id, authorizedTeamIDs);
}
ctrl.updateAccess({ userAccesses: authorizedUserIDs, teamAccesses: authorizedTeamIDs })
.then(function success() {
removeFromAccesses(access, ctrl.authorizedAccesses);
ctrl.accesses.push(access);
Notifications.success('Accesses successfully updated');
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to update accesses');
});
};
function moveAccesses(source, target) {
for (var i = 0; i < source.length; i++) {
var access = source[i];
if (!access.Inherited) {
target.push(access);
}
}
_.remove(source, function(e){
return !e.Inherited
});
}
ctrl.unauthorizeAllAccesses = function() {
ctrl.updateAccess({ userAccesses: [], teamAccesses: [] })
.then(function success() {
moveAccesses(ctrl.authorizedAccesses, ctrl.accesses);
Notifications.success('Accesses successfully updated');
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to update accesses');
});
};
ctrl.authorizeAllAccesses = function() {
var accessData = processAuthorizedIDs(ctrl.accesses, ctrl.authorizedAccesses);
var authorizedUserIDs = accessData.userIDs;
var authorizedTeamIDs = accessData.teamIDs;
ctrl.updateAccess({ userAccesses: authorizedUserIDs, teamAccesses: authorizedTeamIDs })
.then(function success() {
moveAccesses(ctrl.accesses, ctrl.authorizedAccesses);
Notifications.success('Accesses successfully updated');
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to update accesses');
});
};
function initComponent() {
var entity = ctrl.accessControlledEntity;
var parent = ctrl.inheritFrom;
AccessService.accesses(entity.AuthorizedUsers, entity.AuthorizedTeams, parent ? parent.AuthorizedUsers: [], parent ? parent.AuthorizedTeams : [])
.then(function success(data) {
ctrl.accesses = data.accesses;
ctrl.authorizedAccesses = data.authorizedAccesses;
})
.catch(function error(err) {
ctrl.accesses = [];
ctrl.authorizedAccesses = [];
Notifications.error('Failure', err, 'Unable to retrieve accesses');
});
}
initComponent();
}]);
export default PorAccessManagementController;
angular
.module("portainer.app")
.controller("porAccessManagementController", PorAccessManagementController);

View file

@ -6,12 +6,12 @@
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }}
</div>
</div>
<div class="actionBar" ng-if="!$ctrl.offlineMode">
<button type="button" class="btn btn-sm btn-danger"
<div class="actionBar" ng-if="!$ctrl.offlineMode" authorization="PortainerStackCreate, PortainerStackDelete">
<button type="button" class="btn btn-sm btn-danger" authorization="PortainerStackDelete"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button>
<button type="button" class="btn btn-sm btn-primary" ui-sref="portainer.stacks.newstack">
<button type="button" class="btn btn-sm btn-primary" ui-sref="portainer.stacks.newstack" authorization="PortainerStackCreate">
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add stack
</button>
</div>
@ -24,7 +24,7 @@
<thead>
<tr>
<th>
<span class="md-checkbox" ng-if="!$ctrl.offlineMode">
<span class="md-checkbox" ng-if="!$ctrl.offlineMode" authorization="PortainerStackCreate, PortainerStackDelete">
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
<label for="select_all"></label>
</span>
@ -54,7 +54,7 @@
<tbody>
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
<td>
<span class="md-checkbox" ng-if="!$ctrl.offlineMode">
<span class="md-checkbox" ng-if="!$ctrl.offlineMode" authorization="PortainerStackCreate, PortainerStackDelete">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-change="$ctrl.selectItem(item)" ng-disabled="item.External && item.Type === 2"/>
<label for="select_{{ $index }}"></label>
</span>

View file

@ -61,7 +61,7 @@
<i class="fa fa-user-circle" aria-hidden="true" style="margin-right: 5px;" ng-if="item.Role === 1 && !item.isTeamLeader"></i>
<i class="fa fa-user-plus" aria-hidden="true" style="margin-right: 5px;" ng-if="item.Role !== 1 && item.isTeamLeader"></i>
<i class="fa fa-user" aria-hidden="true" style="margin-right: 5px;" ng-if="item.Role !== 1 && !item.isTeamLeader"></i>
{{ item.RoleName }}
{{ item.RoleName ? item.RoleName : '-' }}
</span>
</td>
<td>

View file

@ -19,7 +19,7 @@ function StackDuplicationFormController($state, EndpointProvider, EndpointServic
function onInit() {
var endpointId = EndpointProvider.endpointID();
ctrl.showRefreshButton = Authentication.getUserDetails().role === 1;
ctrl.showRefreshButton = Authentication.isAdmin();
EndpointService.endpoint(endpointId)

View file

@ -1,4 +1,4 @@
<div>
<div authorization="PortainerStackMigrate">
<div class="col-sm-12 form-section-title">
Stack duplication / migration
</div>
@ -10,7 +10,7 @@
</span>
<div>
<div class="form-group">
<input class="form-control" placeholder="Stack name (optional for migration)"
<input class="form-control" placeholder="Stack name (optional for migration)"
aria-placeholder="Stack name"
ng-model="$ctrl.formValues.newName" />
</div>
@ -18,7 +18,7 @@
endpoints="$ctrl.endpoints" groups="$ctrl.groups"></endpoint-selector>
<button class="btn btn-sm btn-primary" ng-click="$ctrl.migrateStack()"
ng-disabled="$ctrl.isMigrationButtonDisabled()"
style="margin-top: 7px; margin-left: 0;"
style="margin-top: 7px; margin-left: 0;"
button-spinner="$ctrl.state.migrationInProgress">
<span ng-hide="$ctrl.state.migrationInProgress">
<i class="fa fa-long-arrow-alt-right space-right"
@ -26,11 +26,11 @@
</span>
<span ng-show="$ctrl.state.migrationInProgress">Migration in progress...</span>
</button>
<button
class="btn btn-sm btn-primary"
<button
class="btn btn-sm btn-primary"
ng-click="$ctrl.duplicateStack()"
ng-disabled="!$ctrl.isFormValidForDuplication() || $ctrl.state.duplicationInProgress || $ctrl.state.migrationInProgress"
style="margin-top: 7px; margin-left: 0;"
style="margin-top: 7px; margin-left: 0;"
button-spinner="$ctrl.state.duplicationInProgress">
<span ng-hide="$ctrl.state.duplicationInProgress">
<i class="fa fa-clone space-right"
@ -40,4 +40,4 @@
</button>
</div>
</div>
</div>
</div>

View file

@ -1,13 +1,21 @@
// create UserAccessViewModel from UserViewModel
export function UserAccessViewModel(data) {
this.Id = data.Id;
this.Name = data.Username;
this.Type = 'user';
this.Inherited = false;
this.Override = false;
this.Role = { Id: 0, Name: "-" };
this.icon = '<i class="fa fa-user" aria-hidden="true"></i>';
}
// create TeamAccessViewModel from TeamViewModel
export function TeamAccessViewModel(data) {
this.Id = data.Id;
this.Name = data.Name;
this.Type = 'team';
this.Inherited = false;
this.Override = false;
this.Role = { Id: 0, Name: "-" };
this.icon = '<i class="fa fa-users" aria-hidden="true"></i>';
}

View file

@ -11,6 +11,8 @@ export function EndpointGroupModel(data) {
this.Tags = data.Tags;
this.AuthorizedUsers = data.AuthorizedUsers;
this.AuthorizedTeams = data.AuthorizedTeams;
this.UserAccessPolicies = data.UserAccessPolicies;
this.TeamAccessPolicies = data.TeamAccessPolicies;
}
export function EndpointGroupCreateRequest(model, endpoints) {
@ -26,4 +28,6 @@ export function EndpointGroupUpdateRequest(model, endpoints) {
this.Description = model.Description;
this.Tags = model.Tags;
this.AssociatedEndpoints = endpoints;
this.UserAccessPolicies = model.UserAccessPolicies;
this.TeamAccessPolicies = model.TeamAccessPolicies;
}

View file

@ -8,6 +8,8 @@ export function RegistryViewModel(data) {
this.Password = data.Password;
this.AuthorizedUsers = data.AuthorizedUsers;
this.AuthorizedTeams = data.AuthorizedTeams;
this.UserAccessPolicies = data.UserAccessPolicies;
this.TeamAccessPolicies = data.TeamAccessPolicies;
this.Checked = false;
}

View file

@ -4,5 +4,4 @@ export function StatusViewModel(data) {
this.EndpointManagement = data.EndpointManagement;
this.Analytics = data.Analytics;
this.Version = data.Version;
this.EnabledExtensions = data.EnabledExtensions;
}

View file

@ -5,7 +5,6 @@ angular.module('portainer.app')
create: { method: 'POST', ignoreLoadingBar: true },
query: { method: 'GET', isArray: true },
get: { method: 'GET', params: { id: '@id' } },
update: { method: 'PUT', params: { id: '@id' } },
remove: { method: 'DELETE', params: { id: '@id'} },
queryMemberships: { method: 'GET', isArray: true, params: { id: '@id', entity: 'memberships' } }
});

View file

@ -7,30 +7,47 @@ angular.module('portainer.app')
'use strict';
var service = {};
function mapAccessData(accesses, authorizedIDs, inheritedIDs) {
function _getRole(roles, roleId) {
if (roles.length) {
const role = _.find(roles, (role) => role.Id === roleId);
return role ? role : { Id: 0, Name: "-" };
}
}
function _mapAccessData(accesses, authorizedPolicies, inheritedPolicies, roles) {
var availableAccesses = [];
var authorizedAccesses = [];
for (var i = 0; i < accesses.length; i++) {
const access = accesses[i];
var access = accesses[i];
if (_.includes(inheritedIDs, access.Id)) {
const authorized = authorizedPolicies && authorizedPolicies[access.Id];
const inherited = inheritedPolicies && inheritedPolicies[access.Id];
if (authorized && inherited) {
access.Role = _getRole(roles, authorizedPolicies[access.Id].RoleId);
access.Override = true;
authorizedAccesses.push(access);
} else if (authorized && !inherited) {
access.Role = _getRole(roles, authorizedPolicies[access.Id].RoleId);
authorizedAccesses.push(access);
} else if (!authorized && inherited) {
access.Role = _getRole(roles, inheritedPolicies[access.Id].RoleId);
access.Inherited = true;
authorizedAccesses.push(access);
} else if (_.includes(authorizedIDs, access.Id)) {
authorizedAccesses.push(access);
availableAccesses.push(access);
} else {
availableAccesses.push(access);
}
}
return {
accesses: availableAccesses,
authorizedAccesses: authorizedAccesses
available: availableAccesses,
authorized: authorizedAccesses
};
}
service.accesses = function(authorizedUserIDs, authorizedTeamIDs, inheritedUserIDs, inheritedTeamIDs) {
service.accesses = function(authorizedUserPolicies, authorizedTeamPolicies, inheritedUserPolicies, inheritedTeamPolicies, roles) {
var deferred = $q.defer();
$q.all({
@ -45,12 +62,12 @@ angular.module('portainer.app')
return new TeamAccessViewModel(team);
});
var userAccessData = mapAccessData(userAccesses, authorizedUserIDs, inheritedUserIDs);
var teamAccessData = mapAccessData(teamAccesses, authorizedTeamIDs, inheritedTeamIDs);
var userAccessData = _mapAccessData(userAccesses, authorizedUserPolicies, inheritedUserPolicies, roles);
var teamAccessData = _mapAccessData(teamAccesses, authorizedTeamPolicies, inheritedTeamPolicies, roles);
var accessData = {
accesses: userAccessData.accesses.concat(teamAccessData.accesses),
authorizedAccesses: userAccessData.authorizedAccesses.concat(teamAccessData.authorizedAccesses)
availableUsersAndTeams: userAccessData.available.concat(teamAccessData.available),
authorizedUsersAndTeams: userAccessData.authorized.concat(teamAccessData.authorized)
};
deferred.resolve(accessData);
@ -62,5 +79,22 @@ angular.module('portainer.app')
return deferred.promise;
};
service.generateAccessPolicies = function(userAccessPolicies, teamAccessPolicies, selectedUserAccesses, selectedTeamAccesses, selectedRoleId) {
const newUserPolicies = _.clone(userAccessPolicies);
const newTeamPolicies = _.clone(teamAccessPolicies);
_.forEach(selectedUserAccesses, (access) => newUserPolicies[access.Id] = {RoleId: selectedRoleId ? selectedRoleId : access.Role.Id});
_.forEach(selectedTeamAccesses, (access) => newTeamPolicies[access.Id] = {RoleId: selectedRoleId ? selectedRoleId : access.Role.Id});
const accessPolicies = {
userAccessPolicies: newUserPolicies,
teamAccessPolicies: newTeamPolicies
};
return accessPolicies;
}
return service;
}]);

View file

@ -37,8 +37,8 @@ function EndpointServiceFactory($q, Endpoints, FileUploadService) {
return deferred.promise;
};
service.updateAccess = function(id, authorizedUserIDs, authorizedTeamIDs) {
return Endpoints.updateAccess({id: id}, {authorizedUsers: authorizedUserIDs, authorizedTeams: authorizedTeamIDs}).$promise;
service.updateAccess = function(id, userAccessPolicies, teamAccessPolicies) {
return Endpoints.updateAccess({id: id}, {UserAccessPolicies: userAccessPolicies, TeamAccessPolicies: teamAccessPolicies}).$promise;
};
service.updateEndpoint = function(id, payload) {

View file

@ -2,10 +2,16 @@ import _ from 'lodash-es';
import { ExtensionViewModel } from '../../models/extension';
angular.module('portainer.app')
.factory('ExtensionService', ['$q', 'Extension', function ExtensionServiceFactory($q, Extension) {
.factory('ExtensionService', ['$q', 'Extension', 'StateManager', function ExtensionServiceFactory($q, Extension, StateManager) {
'use strict';
var service = {};
service.EXTENSIONS = Object.freeze({
REGISTRY_MANAGEMENT: 1,
OAUTH_AUTHENTICATION: 2,
RBAC: 3
});
service.enable = function(license) {
return Extension.create({ license: license }).$promise;
};
@ -50,34 +56,14 @@ angular.module('portainer.app')
return deferred.promise;
};
service.registryManagementEnabled = function() {
var deferred = $q.defer();
service.extensions(false)
.then(function onSuccess(extensions) {
var extensionAvailable = _.find(extensions, { Id: 1, Enabled: true }) ? true : false;
deferred.resolve(extensionAvailable);
})
.catch(function onError(err) {
deferred.reject(err);
});
return deferred.promise;
service.extensionEnabled = function(extensionId) {
return StateManager.getExtension(extensionId) ? true : false;
};
service.OAuthAuthenticationEnabled = function() {
var deferred = $q.defer();
service.extensions(false)
.then(function onSuccess(extensions) {
var extensionAvailable = _.find(extensions, { Id: 2, Enabled: true }) ? true : false;
deferred.resolve(extensionAvailable);
})
.catch(function onError(err) {
deferred.reject(err);
});
return deferred.promise;
service.retrieveAndSaveEnabledExtensions = async function() {
const extensions = await service.extensions(false);
_.forEach(extensions, (ext) => delete ext.License);
StateManager.saveExtensions(extensions);
};
return service;

View file

@ -39,8 +39,8 @@ function GroupService($q, EndpointGroups) {
return EndpointGroups.update(payload).$promise;
};
service.updateAccess = function(groupId, authorizedUserIDs, authorizedTeamIDs) {
return EndpointGroups.updateAccess({ id: groupId }, { authorizedUsers: authorizedUserIDs, authorizedTeams: authorizedTeamIDs }).$promise;
service.updateAccess = function(groupId, userAccessPolicies, teamAccessPolicies) {
return EndpointGroups.updateAccess({ id: groupId }, {UserAccessPolicies: userAccessPolicies, TeamAccessPolicies: teamAccessPolicies}).$promise;
};
service.deleteGroup = function(groupId) {

View file

@ -44,8 +44,8 @@ angular.module('portainer.app')
return btoa(JSON.stringify(credentials));
};
service.updateAccess = function(id, authorizedUserIDs, authorizedTeamIDs) {
return Registries.updateAccess({id: id}, {authorizedUsers: authorizedUserIDs, authorizedTeams: authorizedTeamIDs}).$promise;
service.updateAccess = function(id, userAccessPolicies, teamAccessPolicies) {
return Registries.updateAccess({id: id}, {UserAccessPolicies: userAccessPolicies, TeamAccessPolicies: teamAccessPolicies}).$promise;
};
service.deleteRegistry = function(id) {

View file

@ -61,13 +61,6 @@ angular.module('portainer.app')
return Teams.remove({id: id}).$promise;
};
service.updateTeam = function(id, name) {
var payload = {
Name: name
};
return Teams.update({id: id}, payload).$promise;
};
service.userMemberships = function(id) {
var deferred = $q.defer();
Teams.queryMemberships({id: id}).$promise

View file

@ -45,7 +45,13 @@ angular.module('portainer.app')
service.createUser = function(username, password, role, teamIds) {
var deferred = $q.defer();
Users.create({}, {username: username, password: password, role: role}).$promise
var payload = {
username: username,
password: password,
role: role
};
Users.create({}, payload).$promise
.then(function success(data) {
var userId = data.Id;
var teamMembershipQueries = [];

View file

@ -1,6 +1,6 @@
angular.module('portainer.app')
.factory('Authentication', [
'Auth', 'OAuth', 'jwtHelper', 'LocalStorage', 'StateManager', 'EndpointProvider',
'Auth', 'OAuth', 'jwtHelper', 'LocalStorage', 'StateManager', 'EndpointProvider',
function AuthenticationFactory(Auth, OAuth, jwtHelper, LocalStorage, StateManager, EndpointProvider) {
'use strict';
@ -13,6 +13,8 @@ function AuthenticationFactory(Auth, OAuth, jwtHelper, LocalStorage, StateManage
service.logout = logout;
service.isAuthenticated = isAuthenticated;
service.getUserDetails = getUserDetails;
service.isAdmin = isAdmin;
service.hasAuthorizations = hasAuthorizations;
function init() {
var jwt = LocalStorage.getJWT();
@ -57,6 +59,32 @@ function AuthenticationFactory(Auth, OAuth, jwtHelper, LocalStorage, StateManage
user.username = tokenPayload.username;
user.ID = tokenPayload.id;
user.role = tokenPayload.role;
user.endpointAuthorizations = tokenPayload.endpointAuthorizations;
user.portainerAuthorizations = tokenPayload.portainerAuthorizations;
}
function isAdmin() {
if (user.role === 1) {
return true;
}
return false;
}
function hasAuthorizations(authorizations) {
const endpointId = EndpointProvider.endpointID();
if (isAdmin()) {
return true;
}
if (!user.endpointAuthorizations || (user.endpointAuthorizations && !user.endpointAuthorizations[endpointId])) {
return false;
}
for (var i = 0; i < authorizations.length; i++) {
var authorization = authorizations[i];
if (user.endpointAuthorizations[endpointId][authorization]) {
return true;
}
}
return false;
}
return service;

View file

@ -44,6 +44,12 @@ angular.module('portainer.app')
getUIState: function() {
return localStorageService.cookie.get('UI_STATE');
},
storeExtensionState: function(state) {
localStorageService.set('EXTENSION_STATE', state);
},
getExtensionState: function() {
return localStorageService.get('EXTENSION_STATE');
},
storeJWT: function(jwt) {
localStorageService.set('JWT', jwt);
},

View file

@ -1,3 +1,4 @@
import _ from 'lodash-es';
import moment from 'moment';
angular.module('portainer.app')
@ -14,7 +15,8 @@ function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, Settin
UI: {
dismissedInfoPanels: {},
dismissedInfoHash: ''
}
},
extensions: []
};
manager.dismissInformationPanel = function(id) {
@ -94,6 +96,11 @@ function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, Settin
state.UI = UIState;
}
const extensionState = LocalStorage.getExtensionState();
if (extensionState) {
state.extensions = extensionState;
}
var endpointState = LocalStorage.getEndpointState();
if (endpointState) {
state.endpoint = endpointState;
@ -190,5 +197,18 @@ function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, Settin
return state.endpoint.agentApiVersion;
};
manager.saveExtensions = function(extensions) {
state.extensions = extensions;
LocalStorage.storeExtensionState(state.extensions);
};
manager.getExtensions = function() {
return state.extensions;
};
manager.getExtension = function(extensionId) {
return _.find(state.extensions, { Id: extensionId, Enabled: true });
};
return manager;
}]);

View file

@ -1,6 +1,6 @@
angular.module('portainer.app')
.controller('AuthenticationController', ['$q', '$scope', '$state', '$stateParams', '$sanitize', 'Authentication', 'UserService', 'EndpointService', 'StateManager', 'Notifications', 'SettingsService', 'URLHelper',
function($q, $scope, $state, $stateParams, $sanitize, Authentication, UserService, EndpointService, StateManager, Notifications, SettingsService, URLHelper) {
.controller('AuthenticationController', ['$q', '$scope', '$state', '$stateParams', '$sanitize', 'Authentication', 'UserService', 'EndpointService', 'ExtensionService', 'StateManager', 'Notifications', 'SettingsService', 'URLHelper',
function($q, $scope, $state, $stateParams, $sanitize, Authentication, UserService, EndpointService, ExtensionService, StateManager, Notifications, SettingsService, URLHelper) {
$scope.logo = StateManager.getState().application.logo;
$scope.formValues = {
@ -14,12 +14,21 @@ function($q, $scope, $state, $stateParams, $sanitize, Authentication, UserServic
OAuthProvider: ''
};
function retrieveAndSaveEnabledExtensions() {
try {
ExtensionService.retrieveAndSaveEnabledExtensions();
} catch (err) {
Notifications.error('Failure', err, 'Unable to retrieve enabled extensions');
}
}
$scope.authenticateUser = function() {
var username = $scope.formValues.Username;
var password = $scope.formValues.Password;
Authentication.login(username, password)
.then(function success() {
retrieveAndSaveEnabledExtensions();
checkForEndpoints();
})
.catch(function error() {
@ -31,6 +40,7 @@ function($q, $scope, $state, $stateParams, $sanitize, Authentication, UserServic
return $q.reject();
})
.then(function success() {
retrieveAndSaveEnabledExtensions();
$state.go('portainer.updatePassword');
})
.catch(function error() {
@ -69,9 +79,8 @@ function($q, $scope, $state, $stateParams, $sanitize, Authentication, UserServic
EndpointService.endpoints()
.then(function success(data) {
var endpoints = data;
var userDetails = Authentication.getUserDetails();
if (endpoints.length === 0 && userDetails.role === 1) {
if (endpoints.length === 0 && Authentication.isAdmin()) {
$state.go('portainer.init.endpoint');
} else {
$state.go('portainer.home');
@ -132,6 +141,7 @@ function($q, $scope, $state, $stateParams, $sanitize, Authentication, UserServic
function oAuthLogin(code) {
return Authentication.OAuthLogin(code)
.then(function success() {
retrieveAndSaveEnabledExtensions();
URLHelper.cleanParameters();
})
.catch(function error() {

View file

@ -1,11 +1,12 @@
<rd-header>
<rd-header-title title-text="Endpoint access"></rd-header-title>
<rd-header-content>
<a ui-sref="portainer.endpoints">Endpoints</a> &gt; <a ui-sref="portainer.endpoints.endpoint({id: endpoint.Id})">{{ endpoint.Name }}</a> &gt; Access management
<a ui-sref="portainer.endpoints">Endpoints</a> &gt; <a
ui-sref="portainer.endpoints.endpoint({id: ctrl.endpoint.Id})">{{ ctrl.endpoint.Name }}</a> &gt; Access management
</rd-header-content>
</rd-header>
<div class="row" ng-if="endpoint">
<div class="row" ng-if="ctrl.endpoint">
<div class="col-sm-12">
<rd-widget>
<rd-widget-header icon="fa-plug" title-text="Endpoint"></rd-widget-header>
@ -15,19 +16,19 @@
<tr>
<td>Name</td>
<td>
{{ endpoint.Name }}
{{ ctrl.endpoint.Name }}
</td>
</tr>
<tr>
<td>URL</td>
<td>
{{ endpoint.URL | stripprotocol }}
{{ ctrl.endpoint.URL | stripprotocol }}
</td>
</tr>
<tr>
<td>Group</td>
<td>
<a ui-sref="portainer.groups.group({ id: group.Id })">{{ group.Name }}</a>
<a ui-sref="portainer.groups.group({ id: ctrl.group.Id })">{{ ctrl.group.Name }}</a>
</td>
</tr>
</tbody>
@ -37,10 +38,6 @@
</div>
</div>
<div class="row" ng-if="endpoint && group">
<div class="col-sm-12">
<por-access-management
access-controlled-entity="endpoint" entity-type="endpoint" inherit-from="group" update-access="updateAccess(userAccesses, teamAccesses)"
></por-access-management>
</div>
</div>
<por-access-management ng-if="ctrl.endpoint && ctrl.group" access-controlled-entity="ctrl.endpoint" action-in-progress="ctrl.state.actionInProgress"
entity-type="endpoint" inherit-from="ctrl.group" update-access="ctrl.updateAccess">
</por-access-management>

View file

@ -1,25 +1,42 @@
angular.module('portainer.app')
.controller('EndpointAccessController', ['$scope', '$transition$', 'EndpointService', 'GroupService', 'Notifications',
function ($scope, $transition$, EndpointService, GroupService, Notifications) {
import angular from "angular";
$scope.updateAccess = function(authorizedUsers, authorizedTeams) {
return EndpointService.updateAccess($transition$.params().id, authorizedUsers, authorizedTeams);
};
function initView() {
EndpointService.endpoint($transition$.params().id)
.then(function success(data) {
var endpoint = data;
$scope.endpoint = endpoint;
return GroupService.group(endpoint.GroupId);
})
.then(function success(data) {
$scope.group = data;
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve endpoint details');
});
class EndpointAccessController {
/* @ngInject */
constructor($state, $transition$, Notifications, EndpointService, GroupService) {
this.$state = $state;
this.$transition$ = $transition$;
this.Notifications = Notifications;
this.EndpointService = EndpointService;
this.GroupService = GroupService;
this.updateAccess = this.updateAccess.bind(this);
}
initView();
}]);
async $onInit() {
this.state = {actionInProgress: false};
try {
this.endpoint = await this.EndpointService.endpoint(
this.$transition$.params().id
);
this.group = await this.GroupService.group(this.endpoint.GroupId);
} catch (err) {
this.Notifications.error("Failure", err, "Unable to retrieve endpoint information");
}
}
async updateAccess() {
try {
this.state.actionInProgress = true;
await this.EndpointService.updateEndpoint(this.$transition$.params().id, this.endpoint);
this.Notifications.success("Access successfully updated");
this.$state.reload();
} catch (err) {
this.state.actionInProgress = false;
this.Notifications.error("Failure", err, "Unable to update accesses");
}
}
}
export default EndpointAccessController;
angular
.module("portainer.app")
.controller("EndpointAccessController", EndpointAccessController);

View file

@ -30,6 +30,7 @@ angular.module('portainer.app')
$scope.state.actionInProgress = true;
ExtensionService.enable(license)
.then(function onSuccess() {
ExtensionService.retrieveAndSaveEnabledExtensions();
Notifications.success('Extension successfully enabled');
$state.reload();
})

View file

@ -25,10 +25,6 @@
</div>
</div>
<div class="row" ng-if="group">
<div class="col-sm-12">
<por-access-management
ng-if="group" access-controlled-entity="group" entity-type="group" update-access="updateAccess(userAccesses, teamAccesses)"
></por-access-management>
</div>
</div>
<por-access-management ng-if="group" access-controlled-entity="group" entity-type="group" action-in-progress="state.actionInProgress"
update-access="updateAccess"
></por-access-management>

View file

@ -1,14 +1,24 @@
angular.module('portainer.app')
.controller('GroupAccessController', ['$scope', '$transition$', 'GroupService', 'Notifications',
function ($scope, $transition$, GroupService, Notifications) {
.controller('GroupAccessController', ['$scope', '$state', '$transition$', 'GroupService', 'Notifications',
function ($scope, $state, $transition$, GroupService, Notifications) {
$scope.updateAccess = function(authorizedUsers, authorizedTeams) {
return GroupService.updateAccess($transition$.params().id, authorizedUsers, authorizedTeams);
$scope.updateAccess = function() {
$scope.state.actionInProgress = true;
GroupService.updateGroup($scope.group, $scope.group.AssociatedEndpoints)
.then(() => {
Notifications.success("Access successfully updated");
$state.reload();
})
.catch((err) => {
$scope.state.actionInProgress = false;
Notifications.error("Failure", err, "Unable to update accesses");
});
};
function initView() {
var groupId = $transition$.params().id;
$scope.state = {actionInProgress: false};
GroupService.group(groupId)
.then(function success(data) {
$scope.group = data;

View file

@ -112,7 +112,7 @@ angular.module('portainer.app')
}
function initView() {
$scope.isAdmin = Authentication.getUserDetails().role === 1;
$scope.isAdmin = Authentication.isAdmin();
MotdService.motd()
.then(function success(data) {

Some files were not shown because too many files have changed in this diff Show more