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:
parent
27a0188949
commit
8057aa45c4
196 changed files with 3321 additions and 1316 deletions
|
@ -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>
|
||||
|
|
|
@ -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})"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 ">
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
angular.module('portainer.extensions', [
|
||||
'portainer.extensions.registrymanagement',
|
||||
'portainer.extensions.oauth'
|
||||
'portainer.extensions.oauth',
|
||||
'portainer.extensions.rbac'
|
||||
]);
|
||||
|
|
19
app/extensions/rbac/__module.js
Normal file
19
app/extensions/rbac/__module.js
Normal 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);
|
||||
}]);
|
|
@ -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>
|
|
@ -0,0 +1,5 @@
|
|||
angular.module('portainer.app').component('accessViewer', {
|
||||
templateUrl: './accessViewer.html',
|
||||
controller: 'AccessViewerController',
|
||||
controllerAs: 'ctrl'
|
||||
});
|
|
@ -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);
|
|
@ -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>
|
|
@ -0,0 +1,11 @@
|
|||
angular.module('portainer.app').component('accessViewerDatatable', {
|
||||
templateUrl: './accessViewerDatatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
bindings: {
|
||||
titleText: '@',
|
||||
titleIcon: '@',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
dataset: '<'
|
||||
}
|
||||
});
|
|
@ -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>
|
|
@ -0,0 +1,13 @@
|
|||
angular.module('portainer.extensions.rbac').component('rolesDatatable', {
|
||||
templateUrl: './rolesDatatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
bindings: {
|
||||
titleText: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
rbacEnabled: '<'
|
||||
}
|
||||
});
|
33
app/extensions/rbac/directives/authorization.js
Normal file
33
app/extensions/rbac/directives/authorization.js
Normal 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');
|
||||
}
|
||||
}
|
||||
}
|
||||
}]);
|
27
app/extensions/rbac/directives/disable-authorization.js
Normal file
27
app/extensions/rbac/directives/disable-authorization.js
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}]);
|
15
app/extensions/rbac/models/access.js
Normal file
15
app/extensions/rbac/models/access.js
Normal 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';
|
||||
}
|
6
app/extensions/rbac/models/role.js
Normal file
6
app/extensions/rbac/models/role.js
Normal 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;
|
||||
}
|
11
app/extensions/rbac/rest/role.js
Normal file
11
app/extensions/rbac/rest/role.js
Normal 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'} }
|
||||
});
|
||||
}]);
|
47
app/extensions/rbac/services/roleService.js
Normal file
47
app/extensions/rbac/services/roleService.js
Normal 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;
|
||||
}]);
|
36
app/extensions/rbac/views/roles/roles.html
Normal file
36
app/extensions/rbac/views/roles/roles.html
Normal 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>
|
25
app/extensions/rbac/views/roles/rolesController.js
Normal file
25
app/extensions/rbac/views/roles/rolesController.js
Normal 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);
|
|
@ -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)
|
||||
|
|
|
@ -104,7 +104,8 @@ angular.module('portainer.app', [])
|
|||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/endpoints/access/endpointAccess.html',
|
||||
controller: 'EndpointAccessController'
|
||||
controller: 'EndpointAccessController',
|
||||
controllerAs: 'ctrl'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
132
app/portainer/components/access-datatable/accessDatatable.html
Normal file
132
app/portainer/components/access-datatable/accessDatatable.html
Normal 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>
|
17
app/portainer/components/access-datatable/accessDatatable.js
Normal file
17
app/portainer/components/access-datatable/accessDatatable.js
Normal 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: '<'
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
]);
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
angular.module('portainer.app').component('porAccessManagement', {
|
||||
templateUrl: './porAccessManagement.html',
|
||||
controller: 'porAccessManagementController',
|
||||
controllerAs: 'ctrl',
|
||||
bindings: {
|
||||
accessControlledEntity: '<',
|
||||
inheritFrom: '<',
|
||||
entityType: '@',
|
||||
updateAccess: '&'
|
||||
updateAccess: '<',
|
||||
actionInProgress: '<'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>';
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,5 +4,4 @@ export function StatusViewModel(data) {
|
|||
this.EndpointManagement = data.EndpointManagement;
|
||||
this.Analytics = data.Analytics;
|
||||
this.Version = data.Version;
|
||||
this.EnabledExtensions = data.EnabledExtensions;
|
||||
}
|
||||
|
|
|
@ -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' } }
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}]);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = [];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
}]);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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> > <a ui-sref="portainer.endpoints.endpoint({id: endpoint.Id})">{{ endpoint.Name }}</a> > Access management
|
||||
<a ui-sref="portainer.endpoints">Endpoints</a> > <a
|
||||
ui-sref="portainer.endpoints.endpoint({id: ctrl.endpoint.Id})">{{ ctrl.endpoint.Name }}</a> > 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>
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
})
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue