Compare commits

...

42 Commits

Author SHA1 Message Date
matias.spinarolli
05ac5ae577 UI fixes 2022-08-03 19:18:18 -03:00
matias.spinarolli
17865fa1a5 UI fixes 2022-08-03 19:17:53 -03:00
andres-portainer
1fcf523267 Add missing icons. 2022-08-03 19:17:46 -03:00
andres-portainer
4a73d46acb feat(ui): renovate the docker images edit page EE-3505 2022-08-03 19:17:31 -03:00
andres-portainer
1e78234f04 feat(ui): renovate the Docker volume list page EE-3513 (#7377) 2022-08-01 14:44:44 -03:00
Zhang Hao
d0a9c046b3 refactor(docker/stack): stack creation page [EE-3486] (#7316)
* reactor(docker/stack): stack creation page [EE-3486]

* feat(stack): some missing component on stack create page and edit page [EE-3486]
2022-08-01 23:07:41 +08:00
Zhang Hao
c54bb255ba feat(container): container detail page as well as some icon changes [EE-3493] (#7361) 2022-08-01 23:06:39 +08:00
matias-portainer
8843b7b0e8 feat(ui): renovate the docker images build page EE-3503 (#7387) 2022-08-01 10:51:20 -03:00
Rex Wang
a95d734c34 EE-3487 update ui of docker/configs (#7370) 2022-08-01 20:31:56 +08:00
Rex Wang
8262487401 fix(UI) update ui of swarm/node/item EE-3518 (#7392)
* EE-3502 update page docker/host/browse and docker/volume/browse

* EE-3518 update ui of swarm/node/item
2022-08-01 16:14:43 +08:00
Ali
57e53d1a21 feat(ui): ui-improvements-helm EE-3476 (#7344)
* feat(ui): helm views ui update EE-3476
2022-08-01 19:13:58 +12:00
Rex Wang
e28a1491d4 EE-3499 update UI endpoint/settings (#7385) 2022-08-01 14:44:02 +08:00
Rex Wang
9342ba9792 EE-3502 update page docker/host/browse and docker/volume/browse (#7388) 2022-08-01 14:13:58 +08:00
Matt Hook
2552eb5e25 feat(kube): create namespace from form view [EE-3479] (#7260)
Restyle create namespace from form view
2022-08-01 16:45:28 +12:00
Matt Hook
ddaf9dc885 feat(kube): create namespace from manifest view [EE-3479] (#7306)
Restyle create from manifest
2022-08-01 16:44:56 +12:00
Richard Wei
11c778cfeb import react2angular for used by icon, tooltip and tableheader (#7391) 2022-08-01 14:59:00 +12:00
Ali
11dffdee9a feat(ui): update dashboard table & items EE-3474 (#7351) 2022-08-01 13:29:49 +12:00
Richard Wei
d4d80ed8f7 feat(ui): ui change for create access token EE-3541 (#7366)
* ui change for create access token
2022-08-01 10:08:45 +12:00
Zhang Hao
0ba10b44ec feat(secret): secret item page [EE-3511] (#7356) 2022-07-31 20:15:12 +08:00
Richard Wei
0f617f7f87 fix js console error for access control and stack page (#7347) 2022-07-29 19:11:03 +12:00
Richard Wei
423dd5e394 feat(ui): portainer new ui for homepage EE-3554 (#7328)
* add icon to homepage
2022-07-29 16:13:02 +12:00
congs
44737029a9 fix(gpu): EE-3743 gpus null error (#7342) 2022-07-29 16:08:17 +12:00
Prabhat Khera
ce22544c60 feature(ui): UI security constraints screen EE-3706 (#7314) 2022-07-29 14:41:33 +12:00
Matt Hook
9106e74e61 restyle the web editor (#7333) 2022-07-29 12:54:17 +12:00
fhanportainer
6c57ddb563 feat(ui): EE-3574 css portainer users (#7295)
* feat(ui): EE-3574 css portainer users

* feat(users): updated UI based on PR feedback

* feat(user): updated admin toggle with <por-switch-field>

* feat(user): fixed alert circle position
2022-07-29 12:45:37 +12:00
Dakota Walsh
a2e1570162 feat(ui): volume detals UI improvements EE-3483 (#7329) 2022-07-29 11:43:37 +12:00
Chaim Lev-Ari
ea60740d48 fix(sidebar): save sidebar state to local storage (#7207) 2022-07-28 14:24:25 -03:00
Chaim Lev-Ari
762c664948 feat(edge): create edge device with wizard [EE-3096] (#7029) 2022-07-28 10:34:22 -03:00
Ali
d574a71cb1 feat(ui): allow-different-modal-icons EE-3751 (#7299)
* feat(ui): update modal icons EE-3751
2022-07-28 17:33:21 +12:00
Prabhat Khera
bb066cd58c fix(ui): certificate fields fixed EE-3692 (#7336) 2022-07-28 14:41:26 +12:00
Prabhat Khera
e779939ae1 feature(ui): ui improvements kube config add from EE-3471 (#7341) 2022-07-28 11:17:32 +12:00
Richard Wei
aa830a0e58 fix(ui): fix docker images page error on pageheader EE-3668 (#7212)
* fix docker images page error with link on page-header
2022-07-28 09:53:19 +12:00
matias-portainer
52ac54f15c feat(ui): renovate edge devices list page EE-3622 (#7210) 2022-07-27 17:09:44 -03:00
matias-portainer
cc0ab75aca feat(ui): renovate the edge devices create page EE-3620 (#7221) 2022-07-27 11:19:23 -03:00
matias-portainer
7e3347da2b feat(ui): renovate the FDO devices list EE-3669 (#7231) 2022-07-27 10:47:38 -03:00
matias-portainer
87e9d7f8d4 feat(ssl): use ECDSA instead of RSA to generate the self-signed certificates EE-3097 (#6891) 2022-07-27 10:46:21 -03:00
Rex Wang
6d3a33635d EE-3694 update UI of docker/custom template (#7345) 2022-07-27 21:04:31 +08:00
Rex Wang
090268d7b6 EE-3485 update ui of docker template (#7339) 2022-07-27 20:23:33 +08:00
Rex Wang
698a91596e EE-3498 update registry/endpoint registry/manage access (#7353) 2022-07-27 20:22:40 +08:00
Ali
bb447bb02a fix(ui): remove unwanted icon hover fill EE-3737 (#7284) 2022-07-27 14:11:54 +12:00
Zhang Hao
5ffcbe8677 refactor(service): docker service edition page [EE-3520] (#7327) 2022-07-27 09:55:16 +08:00
Richard Wei
ac6296b86d feat(ui): portainer settings page ui EE-3566 (#7259)
* settings page ui change
2022-07-27 13:05:25 +12:00
158 changed files with 2115 additions and 2247 deletions

View File

@@ -33,7 +33,7 @@ require (
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6
github.com/pkg/errors v0.9.1
github.com/portainer/docker-compose-wrapper v0.0.0-20220708023447-a69a4ebaa021
github.com/portainer/libcrypto v0.0.0-20210422035235-c652195c5c3a
github.com/portainer/libcrypto v0.0.0-20220506221303-1f4fb3b30f9a
github.com/portainer/libhelm v0.0.0-20210929000907-825e93d62108
github.com/portainer/libhttp v0.0.0-20211208103139-07a5f798eb3f
github.com/rkl-/digest v0.0.0-20180419075440-8316caa4a777

View File

@@ -355,8 +355,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/portainer/docker-compose-wrapper v0.0.0-20220708023447-a69a4ebaa021 h1:GFTn2e5AyIoBuK6hXbdVNkuV2m450DQnYmgQDZRU3x8=
github.com/portainer/docker-compose-wrapper v0.0.0-20220708023447-a69a4ebaa021/go.mod h1:WxDlJWZxCnicdLCPnLNEv7/gRhjeIVuCGmsv+iOPH3c=
github.com/portainer/libcrypto v0.0.0-20210422035235-c652195c5c3a h1:qY8TbocN75n5PDl16o0uVr5MevtM5IhdwSelXEd4nFM=
github.com/portainer/libcrypto v0.0.0-20210422035235-c652195c5c3a/go.mod h1:n54EEIq+MM0NNtqLeCby8ljL+l275VpolXO0ibHegLE=
github.com/portainer/libcrypto v0.0.0-20220506221303-1f4fb3b30f9a h1:B0z3skIMT+OwVNJPQhKp52X+9OWW6A9n5UWig3lHBJk=
github.com/portainer/libcrypto v0.0.0-20220506221303-1f4fb3b30f9a/go.mod h1:n54EEIq+MM0NNtqLeCby8ljL+l275VpolXO0ibHegLE=
github.com/portainer/libhelm v0.0.0-20210929000907-825e93d62108 h1:5e8KAnDa2G3cEHK7aV/ue8lOaoQwBZUzoALslwWkR04=
github.com/portainer/libhelm v0.0.0-20210929000907-825e93d62108/go.mod h1:YvYAk7krKTzB+rFwDr0jQ3sQu2BtiXK1AR0sZH7nhJA=
github.com/portainer/libhttp v0.0.0-20211208103139-07a5f798eb3f h1:GMIjRVV2LADpJprPG2+8MdRH6XvrFgC7wHm7dFUdOpc=

View File

@@ -1,20 +1,24 @@
<div class="datatable">
<rd-widget>
<rd-widget-header icon="{{ $ctrl.titleIcon }}" title-text="{{ $ctrl.titleText }}">
<file-uploader authorization="DockerAgentBrowsePut" ng-if="$ctrl.isUploadAllowed" on-file-selected="($ctrl.onFileSelectedForUpload)"> </file-uploader>
</rd-widget-header>
<rd-widget-body classes="no-padding">
<div class="searchBar">
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
<input
type="text"
class="searchInput"
ng-model="$ctrl.state.textFilter"
ng-model-options="{ debounce: 300 }"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search..."
auto-focus
/>
<div class="toolBar">
<div class="toolBarTitle vertical-center">
<pr-icon icon="$ctrl.titleIcon" feather="true" class-name="'icon-nested-blue'" mode="'primary'"></pr-icon>
{{ $ctrl.titleText }}
</div>
<div class="searchBar vertical-center">
<pr-icon icon="'search'" feather="true"></pr-icon>
<input
type="text"
class="searchInput"
ng-model="$ctrl.state.textFilter"
ng-model-options="{ debounce: 300 }"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search..."
auto-focus
/>
</div>
<file-uploader authorization="DockerAgentBrowsePut" ng-if="$ctrl.isUploadAllowed" on-file-selected="($ctrl.onFileSelectedForUpload)"> </file-uploader>
</div>
<div class="table-responsive">
<table class="table">
@@ -23,22 +27,22 @@
<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>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('Size')">
Size
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Size' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Size' && $ctrl.state.reverseOrder"></i>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'Size' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'Size' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('ModTime')">
Last modification
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ModTime' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ModTime' && $ctrl.state.reverseOrder"></i>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'ModTime' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'ModTime' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
<th> Actions </th>
@@ -47,7 +51,7 @@
<tbody>
<tr ng-if="!$ctrl.isRoot">
<td colspan="4">
<a ng-click="$ctrl.goToParent()"><i class="fa fa-level-up-alt space-right"></i>Go to parent</a>
<a ng-click="$ctrl.goToParent()"><pr-icon icon="'corner-left-up'" feather="true"></pr-icon>Go to parent</a>
</td>
</tr>
<tr ng-repeat="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder))">
@@ -60,27 +64,29 @@
on-enter-key="$ctrl.rename({ name: item.Name, newName: item.newName }); item.edit = false"
auto-focus
/>
<a class="interactive" ng-click="item.edit = false;"><i class="fa fa-times"></i></a>
<a class="interactive" ng-click="$ctrl.rename({name: item.Name, newName: item.newName}); item.edit = false;"><i class="fa fa-check-square"></i></a>
<a class="interactive" ng-click="item.edit = false;"><pr-icon icon="'x'" feather="true"></pr-icon></a>
<a class="interactive" ng-click="$ctrl.rename({name: item.Name, newName: item.newName}); item.edit = false;"
><pr-icon icon="'check-square'" feather="true"></pr-icon
></a>
</span>
<span ng-if="!item.edit && item.Dir">
<a ng-click="$ctrl.browse({name: item.Name})"><i class="fa fa-folder space-right" aria-hidden="true"></i>{{ item.Name }}</a>
<a ng-click="$ctrl.browse({name: item.Name})"><pr-icon icon="'folder'" feather="true" class-name="space-right"></pr-icon>{{ item.Name }}</a>
</span>
<span ng-if="!item.edit && !item.Dir"> <i class="fa fa-file space-right" aria-hidden="true"></i>{{ item.Name }} </span>
<span ng-if="!item.edit && !item.Dir"><pr-icon icon="'file'" feather="true" class-name="space-right"></pr-icon>{{ item.Name }} </span>
</td>
<td>{{ item.Size | humansize }}</td>
<td>
{{ item.ModTime | getisodatefromtimestamp }}
</td>
<td>
<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 authorization="DockerAgentBrowseGet" class="btn btn-xs btn-secondary space-right" ng-click="$ctrl.download({ name: item.Name })" ng-if="!item.Dir">
<pr-icon icon="'download'" feather="true"></pr-icon> Download
</btn>
<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 authorization="DockerAgentBrowseRename" class="btn btn-xs btn-secondary space-right" ng-click="item.newName = item.Name; item.edit = true">
<pr-icon icon="'edit'" feather="true"></pr-icon> Rename
</btn>
<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 authorization="DockerAgentBrowseDelete" class="btn btn-xs btn-dangerlight" ng-click="$ctrl.delete({ name: item.Name })">
<pr-icon icon="'trash-2'" feather="true"></pr-icon> Delete
</btn>
</td>
</tr>

View File

@@ -1,6 +1,6 @@
<files-datatable
title-text="Host browser - {{ $ctrl.getRelativePath() }}"
title-icon="fa-file"
title-icon="file"
dataset="$ctrl.files"
table-key="host_browser"
order-by="Dir"

View File

@@ -1,6 +1,6 @@
<files-datatable
title-text="Volume browser"
title-icon="fa-file"
title-icon="file"
dataset="$ctrl.files"
table-key="volume_browser"
order-by="Dir"

View File

@@ -87,7 +87,7 @@ body,
.form-section-title {
margin-top: 5px;
margin-bottom: 15px;
margin-bottom: 10px;
color: var(--text-form-section-title-color);
padding-left: 0;
font-weight: 500;
@@ -245,7 +245,6 @@ a[ng-click] {
cursor: pointer;
border: 1px solid var(--border-blocklist);
border-radius: 8px;
box-shadow: var(--shadow-box-color);
margin-right: 10px;
}
@@ -867,3 +866,18 @@ json-tree .branch-preview {
margin-left: 5px;
margin-right: 5px;
}
.web-editor {
background-color: var(--bg-webeditor-color);
border-radius: 8px;
padding: 10px;
}
.web-editor a {
color: var(--text-link-color);
}
.web-editor a:hover {
color: var(--text-link-hover-color);
text-decoration-line: underline;
}

View File

@@ -1,4 +1,15 @@
/* Label, Section Title */
.label {
border-radius: 5px;
}
.label-success {
background-color: var(--ui-success-7);
}
.label-danger {
background-color: var(--ui-error-6);
}
.control-label {
color: var(--ui-gray-7);
@@ -68,6 +79,12 @@
background-color: var(--ui-gray-3);
}
.switch-values {
font-style: normal;
font-weight: 500;
margin-left: 5px;
}
/* Toggle */
.slider {
@@ -247,19 +264,31 @@ input:checked + .slider:before {
}
.modal-content {
padding: 55px 20px 20px 20px;
padding: 20px;
}
.background-error {
padding-top: 55px;
background-image: url(../images/icon-error.svg);
background-repeat: no-repeat;
background-position: top left;
}
.background-warning {
padding-top: 55px;
background-image: url(../images/icon-warning.svg);
background-repeat: no-repeat;
background-position: top 10px left 10px;
background-position: top left;
}
.modal-header {
padding: 10px 0px 10px 0px;
margin-bottom: 10px;
padding: 0px;
border-bottom: none;
}
.modal-header .close {
margin-top: -40px;
margin-top: 0px;
}
.modal-header .modal-title {

View File

@@ -150,7 +150,7 @@
"3": "#f5f5f4",
"4": "#e7e5e4",
"5": "#d7d3d0",
"6": "#d7d3d0",
"6": "#a9a29d",
"7": "#79716b",
"8": "#57534e",
"9": "#44403c",

View File

@@ -6,7 +6,7 @@
}
pr-icon {
display: inline-block;
display: inline-flex;
}
.icon {
@@ -99,6 +99,9 @@ pr-icon {
}
.icon-nested-gray {
display: flex;
justify-content: center;
align-items: center;
height: 30px;
width: 30px;
padding: 5px;
@@ -109,6 +112,9 @@ pr-icon {
}
.icon-nested-blue {
display: flex;
justify-content: center;
align-items: center;
height: 30px;
width: 30px;
padding: 5px;

View File

@@ -96,7 +96,7 @@ div.input-mask {
}
.widget .widget-header {
color: var(--text-widget-header-color);
padding: 10px 15px;
padding: 20px 20px 10px 20px;
line-height: 30px;
font-weight: 500;
}

View File

@@ -161,6 +161,7 @@
--bg-searchbar: var(--ui-gray-2);
--bg-inputbox: var(--ui-gray-2);
--bg-dropdown-hover: var(--ui-gray-3);
--bg-webeditor-color: var(--ui-gray-3);
--text-main-color: var(--grey-7);
--text-body-color: var(--grey-6);
@@ -298,8 +299,9 @@
--bg-navtabs-color: var(--grey-3);
--bg-navtabs-hover-color: var(--grey-3);
--bg-table-selected-color: var(--grey-3);
--bg-codemirror-color: var(--grey-2);
--bg-codemirror-gutters-color: var(--grey-2);
--bg-codemirror-color: var(--ui-gray-warm-11);
--bg-codemirror-gutters-color: var(--ui-gray-warm-8);
--bg-codemirror-selected-color: var(--ui-gray-warm-7);
--bg-dropdown-menu-color: var(--grey-1);
--bg-log-viewer-color: var(--grey-2);
--bg-log-line-selected-color: var(--grey-3);
@@ -319,7 +321,6 @@
--bg-multiselect-checkbox-color: var(--grey-3);
--bg-panel-body-color: var(--grey-1);
--bg-boxselector-wrapper-disabled-color: var(--grey-39);
--bg-codemirror-selected-color: var(--grey-3);
--bg-sidebar-header-color: var(--grey-1);
--bg-multiselect-color: var(--grey-1);
--bg-daterangepicker-color: var(--grey-3);
@@ -344,6 +345,7 @@
--bg-searchbar: var(--grey-1);
--bg-inputbox: var(--grey-2);
--bg-dropdown-hover: var(--grey-3);
--bg-webeditor-color: var(--ui-gray-warm-9);
--text-main-color: var(--white-color);
--text-body-color: var(--white-color);
@@ -478,7 +480,7 @@
--bg-blocklist-item-selected-color: var(--black-color);
--bg-input-group-addon-color: var(--grey-3);
--bg-table-color: var(--black-color);
--bg-codemirror-gutters-color: var(--black-color);
--bg-codemirror-gutters-color: var(--ui-gray-warm-11);
--bg-codemirror-color: var(--black-color);
--bg-codemirror-selected-color: var(--grey-3);
--bg-log-viewer-color: var(--black-color);
@@ -526,6 +528,7 @@
--bg-inputbox: var(--black-color);
--bg-searchbar: var(--black-color);
--bg-dropdown-hover: var(--black-color);
--bg-webeditor-color: var(--ui-gray-warm-9);
--text-main-color: var(--white-color);
--text-body-color: var(--white-color);

View File

@@ -163,12 +163,22 @@ code {
.CodeMirror-gutters {
background: var(--bg-codemirror-gutters-color);
border-right: 1px solid var(--border-codemirror-gutters-color);
border-right: 0px;
}
.CodeMirror-linenumber {
text-align: left;
}
.CodeMirror pre.CodeMirror-line,
.CodeMirror pre.CodeMirror-line-like {
padding: 0 20px;
}
.CodeMirror {
background: var(--bg-codemirror-color);
color: var(--text-codemirror-color);
border-radius: 8px;
}
.CodeMirror-selected {
@@ -224,6 +234,27 @@ json-tree .branch-preview {
background-color: var(--bg-progress-color);
}
.ui-select-search,
.ui-select-toggle {
height: 30px;
min-width: 260px;
padding: 4px 12px;
}
.ui-select-toggle {
justify-content: flex-start !important;
}
.ui-select-match-text {
display: flex;
flex-direction: row-reverse;
align-items: center;
}
.ui-select-match-text > a {
verical-align: middle;
}
.ui-select-bootstrap .ui-select-choices-row > span {
color: var(--text-ui-select-color);
}
@@ -278,6 +309,14 @@ json-tree .branch-preview {
.rzslider .rz-bubble.rz-limit {
color: var(--text-rzslider-limit-color);
}
.rz-bubble.rz-limit.rz-ceil {
position: absolute;
right: 0;
left: auto !important;
top: -26px;
}
input,
button,
select,

View File

@@ -1,3 +1,3 @@
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M37.5 25.0002V43.7552C37.5 46.0507 37.5 47.1985 37.2138 48.2621C36.9603 49.2044 36.5432 50.095 35.9816 50.893C35.3477 51.7938 34.4659 52.5286 32.7025 53.9981L17.2975 66.8356C15.5341 68.3051 14.6523 69.0399 14.0184 69.9406C13.4568 70.7387 13.0397 71.6292 12.7862 72.5716C12.5 73.6352 12.5 74.783 12.5 77.0785V78.3335C12.5 83.0006 12.5 85.3342 13.4083 87.1168C14.2072 88.6848 15.4821 89.9596 17.0501 90.7586C18.8327 91.6668 21.1662 91.6668 25.8333 91.6668H74.1667C78.8338 91.6668 81.1673 91.6668 82.9499 90.7586C84.5179 89.9596 85.7928 88.6848 86.5917 87.1168C87.5 85.3342 87.5 83.0006 87.5 78.3335V77.0785C87.5 74.783 87.5 73.6352 87.2138 72.5716C86.9603 71.6292 86.5432 70.7387 85.9816 69.9406C85.3477 69.0399 84.4659 68.3051 82.7025 66.8356L67.2975 53.9981C65.5341 52.5286 64.6523 51.7938 64.0184 50.893C63.4568 50.095 63.0397 49.2044 62.7861 48.2621C62.5 47.1985 62.5 46.0507 62.5 43.7552V25.0002M34.5833 25.0002H65.4167C66.5834 25.0002 67.1668 25.0002 67.6125 24.7731C68.0045 24.5734 68.3232 24.2546 68.5229 23.8626C68.75 23.417 68.75 22.8336 68.75 21.6668V11.6668C68.75 10.5001 68.75 9.91667 68.5229 9.47102C68.3232 9.07901 68.0045 8.7603 67.6125 8.56057C67.1668 8.3335 66.5834 8.3335 65.4167 8.3335H34.5833C33.4166 8.3335 32.8332 8.3335 32.3875 8.56057C31.9955 8.7603 31.6768 9.07901 31.4771 9.47102C31.25 9.91667 31.25 10.5001 31.25 11.6668V21.6668C31.25 22.8336 31.25 23.417 31.4771 23.8626C31.6768 24.2546 31.9955 24.5734 32.3875 24.7731C32.8332 25.0002 33.4166 25.0002 34.5833 25.0002ZM22.9167 70.8335H77.0833C79.0194 70.8335 79.9874 70.8335 80.7924 70.9936C84.0982 71.6512 86.6823 74.2353 87.3399 77.5411C87.5 78.3461 87.5 79.3141 87.5 81.2502C87.5 83.1862 87.5 84.1543 87.3399 84.9593C86.6823 88.265 84.0982 90.8492 80.7924 91.5067C79.9874 91.6668 79.0194 91.6668 77.0833 91.6668H22.9167C20.9806 91.6668 20.0126 91.6668 19.2076 91.5067C15.9018 90.8492 13.3177 88.265 12.6601 84.9593C12.5 84.1543 12.5 83.1862 12.5 81.2502C12.5 79.3141 12.5 78.3461 12.6601 77.5411C13.3177 74.2353 15.9018 71.6512 19.2076 70.9936C20.0126 70.8335 20.9806 70.8335 22.9167 70.8335Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg width="auto" height="auto" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M37.5 25.0002V43.7552C37.5 46.0507 37.5 47.1985 37.2138 48.2621C36.9603 49.2044 36.5432 50.095 35.9816 50.893C35.3477 51.7938 34.4659 52.5286 32.7025 53.9981L17.2975 66.8356C15.5341 68.3051 14.6523 69.0399 14.0184 69.9406C13.4568 70.7387 13.0397 71.6292 12.7862 72.5716C12.5 73.6352 12.5 74.783 12.5 77.0785V78.3335C12.5 83.0006 12.5 85.3342 13.4083 87.1168C14.2072 88.6848 15.4821 89.9596 17.0501 90.7586C18.8327 91.6668 21.1662 91.6668 25.8333 91.6668H74.1667C78.8338 91.6668 81.1673 91.6668 82.9499 90.7586C84.5179 89.9596 85.7928 88.6848 86.5917 87.1168C87.5 85.3342 87.5 83.0006 87.5 78.3335V77.0785C87.5 74.783 87.5 73.6352 87.2138 72.5716C86.9603 71.6292 86.5432 70.7387 85.9816 69.9406C85.3477 69.0399 84.4659 68.3051 82.7025 66.8356L67.2975 53.9981C65.5341 52.5286 64.6523 51.7938 64.0184 50.893C63.4568 50.095 63.0397 49.2044 62.7861 48.2621C62.5 47.1985 62.5 46.0507 62.5 43.7552V25.0002M34.5833 25.0002H65.4167C66.5834 25.0002 67.1668 25.0002 67.6125 24.7731C68.0045 24.5734 68.3232 24.2546 68.5229 23.8626C68.75 23.417 68.75 22.8336 68.75 21.6668V11.6668C68.75 10.5001 68.75 9.91667 68.5229 9.47102C68.3232 9.07901 68.0045 8.7603 67.6125 8.56057C67.1668 8.3335 66.5834 8.3335 65.4167 8.3335H34.5833C33.4166 8.3335 32.8332 8.3335 32.3875 8.56057C31.9955 8.7603 31.6768 9.07901 31.4771 9.47102C31.25 9.91667 31.25 10.5001 31.25 11.6668V21.6668C31.25 22.8336 31.25 23.417 31.4771 23.8626C31.6768 24.2546 31.9955 24.5734 32.3875 24.7731C32.8332 25.0002 33.4166 25.0002 34.5833 25.0002ZM22.9167 70.8335H77.0833C79.0194 70.8335 79.9874 70.8335 80.7924 70.9936C84.0982 71.6512 86.6823 74.2353 87.3399 77.5411C87.5 78.3461 87.5 79.3141 87.5 81.2502C87.5 83.1862 87.5 84.1543 87.3399 84.9593C86.6823 88.265 84.0982 90.8492 80.7924 91.5067C79.9874 91.6668 79.0194 91.6668 77.0833 91.6668H22.9167C20.9806 91.6668 20.0126 91.6668 19.2076 91.5067C15.9018 90.8492 13.3177 88.265 12.6601 84.9593C12.5 84.1543 12.5 83.1862 12.5 81.2502C12.5 79.3141 12.5 78.3461 12.6601 77.5411C13.3177 74.2353 15.9018 71.6512 19.2076 70.9936C20.0126 70.8335 20.9806 70.8335 22.9167 70.8335Z" stroke="currentColor" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -1,3 +1 @@
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M60.4167 76.2503V87M70.8333 46V33.5M22.9167 76.2503V87M41.6667 76.2503V87M41.6667 76.2503H63.3333C70.334 76.2503 82.7428 76.2503 85.4167 74.8879C87.7687 73.6895 89.681 71.7772 90.8794 69.4252C91.8916 67.4386 92.1518 64.4957 92.2187 60.5M41.6667 76.2503H36.6667C29.666 76.2503 17.8324 76.2503 15.1585 74.8879C12.8064 73.6895 10.8942 71.7772 9.69576 69.4252C8.68352 67.4386 8.42335 64.4957 8.35648 60.5M79.1667 76.2503V87M50.5 46V33.5M29.5 46V33.5M8.35648 60.5C8.33334 59.1177 8.33334 58.0496 8.33334 56.2503V36.667C8.33334 29.6663 8.33334 26.166 9.69576 23.4921C10.8942 21.1401 12.8064 19.2278 15.1585 18.0294C17.8324 16.667 29.666 16.667 36.6667 16.667H63.3333C70.334 16.667 82.7428 16.667 85.4167 18.0294C87.7687 19.2278 89.681 21.1401 90.8794 23.4921C92.2418 26.166 92.2418 29.6663 92.2418 36.667V56.2503C92.2418 58.0496 92.2418 59.1177 92.2187 60.5M8.35648 60.5H92.2187" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg width="auto" height="auto" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M14.5 18.3V20.8799M17 11.0399V8.03992M5.5 18.3V20.8799M10 18.3V20.8799M10 18.3H15.2C16.8802 18.3 19.8583 18.3 20.5 17.973C21.0645 17.6854 21.5234 17.2265 21.811 16.662C22.054 16.1852 22.1164 15.4789 22.1325 14.5199M10 18.3H8.8C7.11984 18.3 4.27976 18.3 3.63803 17.973C3.07354 17.6854 2.6146 17.2265 2.32698 16.662C2.08404 16.1852 2.0216 15.4789 2.00555 14.5199M19 18.3V20.8799M12.12 11.0399V8.03992M7.08 11.0399V8.03992M2.00555 14.5199C2 14.1882 2 13.9318 2 13.5V8.8C2 7.11984 2 6.27976 2.32698 5.63803C2.6146 5.07354 3.07354 4.6146 3.63803 4.32698C4.27976 4 7.11984 4 8.8 4H15.2C16.8802 4 19.8583 4 20.5 4.32698C21.0645 4.6146 21.5234 5.07354 21.811 5.63803C22.138 6.27976 22.138 7.11984 22.138 8.8V13.5C22.138 13.9318 22.138 14.1882 22.1325 14.5199M2.00555 14.5199H22.1325" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 979 B

View File

@@ -1,3 +1 @@
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M79.1667 29.1667V70.8333M20.8333 29.1667V70.8333M70.8333 20.8333L29.1667 20.8333M70.8333 79.1667H29.1667M19.1667 29.1667H22.5C24.8336 29.1667 26.0003 29.1667 26.8916 28.7125C27.6756 28.3131 28.3131 27.6756 28.7125 26.8916C29.1667 26.0003 29.1667 24.8336 29.1667 22.5V19.1667C29.1667 16.8331 29.1667 15.6663 28.7125 14.775C28.3131 13.991 27.6756 13.3536 26.8916 12.9541C26.0003 12.5 24.8336 12.5 22.5 12.5H19.1667C16.8331 12.5 15.6663 12.5 14.775 12.9541C13.991 13.3536 13.3536 13.991 12.9541 14.775C12.5 15.6663 12.5 16.8331 12.5 19.1667V22.5C12.5 24.8336 12.5 26.0003 12.9541 26.8916C13.3536 27.6756 13.991 28.3131 14.775 28.7125C15.6663 29.1667 16.8331 29.1667 19.1667 29.1667ZM19.1667 87.5H22.5C24.8336 87.5 26.0003 87.5 26.8916 87.0459C27.6756 86.6464 28.3131 86.009 28.7125 85.225C29.1667 84.3337 29.1667 83.1669 29.1667 80.8333V77.5C29.1667 75.1664 29.1667 73.9997 28.7125 73.1084C28.3131 72.3244 27.6756 71.6869 26.8916 71.2875C26.0003 70.8333 24.8336 70.8333 22.5 70.8333H19.1667C16.8331 70.8333 15.6663 70.8333 14.775 71.2875C13.991 71.6869 13.3536 72.3244 12.9541 73.1084C12.5 73.9997 12.5 75.1664 12.5 77.5V80.8333C12.5 83.1669 12.5 84.3337 12.9541 85.225C13.3536 86.009 13.991 86.6464 14.775 87.0459C15.6663 87.5 16.8331 87.5 19.1667 87.5ZM77.5 29.1667H80.8333C83.1669 29.1667 84.3337 29.1667 85.225 28.7125C86.009 28.3131 86.6464 27.6756 87.0459 26.8916C87.5 26.0003 87.5 24.8336 87.5 22.5V19.1667C87.5 16.8331 87.5 15.6663 87.0459 14.775C86.6464 13.991 86.009 13.3536 85.225 12.9541C84.3337 12.5 83.1669 12.5 80.8333 12.5H77.5C75.1664 12.5 73.9997 12.5 73.1084 12.9541C72.3244 13.3536 71.6869 13.991 71.2875 14.775C70.8333 15.6663 70.8333 16.8331 70.8333 19.1667V22.5C70.8333 24.8336 70.8333 26.0003 71.2875 26.8916C71.6869 27.6756 72.3244 28.3131 73.1084 28.7125C73.9997 29.1667 75.1664 29.1667 77.5 29.1667ZM77.5 87.5H80.8333C83.1669 87.5 84.3337 87.5 85.225 87.0459C86.009 86.6464 86.6464 86.009 87.0459 85.225C87.5 84.3337 87.5 83.1669 87.5 80.8333V77.5C87.5 75.1664 87.5 73.9997 87.0459 73.1084C86.6464 72.3244 86.009 71.6869 85.225 71.2875C84.3337 70.8333 83.1669 70.8333 80.8333 70.8333H77.5C75.1664 70.8333 73.9997 70.8333 73.1084 71.2875C72.3244 71.6869 71.6869 72.3244 71.2875 73.1084C70.8333 73.9997 70.8333 75.1664 70.8333 77.5V80.8333C70.8333 83.1669 70.8333 84.3337 71.2875 85.225C71.6869 86.009 72.3244 86.6464 73.1084 87.0459C73.9997 87.5 75.1664 87.5 77.5 87.5Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg width="auto" height="auto" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M19 7V17M5 7V17M17 5L7 5M17 19H7M4.6 7H5.4C5.96005 7 6.24008 7 6.45399 6.89101C6.64215 6.79513 6.79513 6.64215 6.89101 6.45399C7 6.24008 7 5.96005 7 5.4V4.6C7 4.03995 7 3.75992 6.89101 3.54601C6.79513 3.35785 6.64215 3.20487 6.45399 3.10899C6.24008 3 5.96005 3 5.4 3H4.6C4.03995 3 3.75992 3 3.54601 3.10899C3.35785 3.20487 3.20487 3.35785 3.10899 3.54601C3 3.75992 3 4.03995 3 4.6V5.4C3 5.96005 3 6.24008 3.10899 6.45399C3.20487 6.64215 3.35785 6.79513 3.54601 6.89101C3.75992 7 4.03995 7 4.6 7ZM4.6 21H5.4C5.96005 21 6.24008 21 6.45399 20.891C6.64215 20.7951 6.79513 20.6422 6.89101 20.454C7 20.2401 7 19.9601 7 19.4V18.6C7 18.0399 7 17.7599 6.89101 17.546C6.79513 17.3578 6.64215 17.2049 6.45399 17.109C6.24008 17 5.96005 17 5.4 17H4.6C4.03995 17 3.75992 17 3.54601 17.109C3.35785 17.2049 3.20487 17.3578 3.10899 17.546C3 17.7599 3 18.0399 3 18.6V19.4C3 19.9601 3 20.2401 3.10899 20.454C3.20487 20.6422 3.35785 20.7951 3.54601 20.891C3.75992 21 4.03995 21 4.6 21ZM18.6 7H19.4C19.9601 7 20.2401 7 20.454 6.89101C20.6422 6.79513 20.7951 6.64215 20.891 6.45399C21 6.24008 21 5.96005 21 5.4V4.6C21 4.03995 21 3.75992 20.891 3.54601C20.7951 3.35785 20.6422 3.20487 20.454 3.10899C20.2401 3 19.9601 3 19.4 3H18.6C18.0399 3 17.7599 3 17.546 3.10899C17.3578 3.20487 17.2049 3.35785 17.109 3.54601C17 3.75992 17 4.03995 17 4.6V5.4C17 5.96005 17 6.24008 17.109 6.45399C17.2049 6.64215 17.3578 6.79513 17.546 6.89101C17.7599 7 18.0399 7 18.6 7ZM18.6 21H19.4C19.9601 21 20.2401 21 20.454 20.891C20.6422 20.7951 20.7951 20.6422 20.891 20.454C21 20.2401 21 19.9601 21 19.4V18.6C21 18.0399 21 17.7599 20.891 17.546C20.7951 17.3578 20.6422 17.2049 20.454 17.109C20.2401 17 19.9601 17 19.4 17H18.6C18.0399 17 17.7599 17 17.546 17.109C17.3578 17.2049 17.2049 17.3578 17.109 17.546C17 17.7599 17 18.0399 17 18.6V19.4C17 19.9601 17 20.2401 17.109 20.454C17.2049 20.6422 17.3578 20.7951 17.546 20.891C17.7599 21 18.0399 21 18.6 21Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,3 +1 @@
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M47.9166 20.8335H49.7266C62.4234 20.8335 68.7719 20.8335 71.1817 23.1139C73.2649 25.085 74.188 27.9888 73.6256 30.801C72.975 34.0544 67.792 37.7204 57.4261 45.0524L40.4904 57.0313C30.1245 64.3633 24.9415 68.0293 24.2909 71.2826C23.7285 74.0948 24.6517 76.9986 26.7348 78.9698C29.1447 81.2502 35.4931 81.2502 48.1899 81.2502H52.0833M33.3333 20.8335C33.3333 27.7371 27.7368 33.3335 20.8333 33.3335C13.9297 33.3335 8.33325 27.7371 8.33325 20.8335C8.33325 13.9299 13.9297 8.3335 20.8333 8.3335C27.7368 8.3335 33.3333 13.9299 33.3333 20.8335ZM91.6666 79.1668C91.6666 86.0704 86.0702 91.6668 79.1666 91.6668C72.263 91.6668 66.6666 86.0704 66.6666 79.1668C66.6666 72.2633 72.263 66.6668 79.1666 66.6668C86.0702 66.6668 91.6666 72.2633 91.6666 79.1668Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg width="auto" height="auto" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M11.5 5H11.9344C14.9816 5 16.5053 5 17.0836 5.54729C17.5836 6.02037 17.8051 6.71728 17.6702 7.39221C17.514 8.17302 16.2701 9.05285 13.7823 10.8125L9.71772 13.6875C7.2299 15.4471 5.98599 16.327 5.82984 17.1078C5.69486 17.7827 5.91642 18.4796 6.41636 18.9527C6.99474 19.5 8.51836 19.5 11.5656 19.5H12.5M8 5C8 6.65685 6.65685 8 5 8C3.34315 8 2 6.65685 2 5C2 3.34315 3.34315 2 5 2C6.65685 2 8 3.34315 8 5ZM22 19C22 20.6569 20.6569 22 19 22C17.3431 22 16 20.6569 16 19C16 17.3431 17.3431 16 19 16C20.6569 16 22 17.3431 22 19Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg>

Before

Width:  |  Height:  |  Size: 945 B

After

Width:  |  Height:  |  Size: 725 B

View File

@@ -1,3 +1,3 @@
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M50 8.33337V18.75M50 8.33337C26.9881 8.33337 8.33334 26.9882 8.33334 50M50 8.33337C73.0119 8.33337 91.6667 26.9882 91.6667 50M50 81.25V91.6667M50 91.6667C73.0119 91.6667 91.6667 73.0119 91.6667 50M50 91.6667C26.9881 91.6667 8.33334 73.0119 8.33334 50M18.75 50H8.33334M91.6667 50H81.25M79.4935 79.4935L72.1029 72.1029M20.5068 79.4935L27.9048 72.0955M20.5068 20.8334L27.742 28.0686M79.4935 20.8334L56.2496 43.75M58.3333 50C58.3333 54.6024 54.6024 58.3334 50 58.3334C45.3976 58.3334 41.6667 54.6024 41.6667 50C41.6667 45.3977 45.3976 41.6667 50 41.6667C54.6024 41.6667 58.3333 45.3977 58.3333 50Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg width="auto" height="auto" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.7453 16C18.5362 14.8661 19 13.4872 19 12C19 11.4851 18.9444 10.9832 18.8389 10.5M6.25469 16C5.46381 14.8662 5 13.4872 5 12C5 8.13401 8.13401 5 12 5C12.4221 5 12.8355 5.03737 13.2371 5.10897M16.4999 7.5L11.9999 12M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12ZM13 12C13 12.5523 12.5523 13 12 13C11.4477 13 11 12.5523 11 12C11 11.4477 11.4477 11 12 11C12.5523 11 13 11.4477 13 12Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 794 B

After

Width:  |  Height:  |  Size: 655 B

10
app/assets/ico/vendor/helm.svg vendored Normal file
View File

@@ -0,0 +1,10 @@
<svg width="auto" height="auto" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2026_8347)">
<path d="M2.03394 12.0799L21.9659 12.0799M4.05923 7.91314C4.44428 7.25611 4.90783 6.65064 5.437 6.10964M5.437 6.10964C7.12201 4.38695 9.4724 3.31777 12.0725 3.31777C14.6331 3.31777 16.9516 4.35473 18.6308 6.03161M5.437 6.10964L3.87003 4.54238M5.437 6.10964L5.44586 6.1185M18.6308 6.03161C19.1922 6.59221 19.6821 7.22434 20.0858 7.91314M18.6308 6.03161L20.1869 4.47547M18.6308 6.03161L18.6243 6.03807M11.9948 1.04492L11.9948 3.31804M19.969 16.069C19.584 16.726 19.1204 17.3315 18.5912 17.8725M18.5912 17.8725C16.9062 19.5952 14.5559 20.6643 11.9557 20.6643C9.39512 20.6643 7.07669 19.6274 5.39746 17.9505M18.5912 17.8725L20.1582 19.4397M18.5912 17.8725L18.5824 17.8636M5.39746 17.9505C4.83608 17.3899 4.34614 16.7578 3.94248 16.069M5.39746 17.9505L3.84132 19.5066M5.39746 17.9505L5.40392 17.944M12.0335 22.9372L12.0335 20.6641" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</g>
<defs>
<clipPath id="clip0_2026_8347">
<rect width="24" height="24" fill="white" transform="translate(24) rotate(90)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -19,7 +19,7 @@
data-cy="config-searchInput"
/>
</div>
<div class="actionBar !gap-3" ng-if="!$ctrl.offlineMode" authorization="DockerConfigDelete, DockerConfigCreate">
<div class="actionBar !gap-3" authorization="DockerConfigDelete, DockerConfigCreate">
<button
type="button"
class="btn btn-sm btn-dangerlight h-fit vertical-center !ml-0"
@@ -88,22 +88,22 @@
</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>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('CreatedAt')">
Creation Date
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && $ctrl.state.reverseOrder"></i>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
Ownership
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
</tr>

View File

@@ -61,12 +61,14 @@
<td authorization="DockerNetworkDisconnect">
<button
type="button"
class="btn btn-xs btn-danger"
class="btn btn-xs btn-dangerlight h-fit vertical-center !ml-0"
ng-disabled="$ctrl.leaveNetworkActionInProgress || $ctrl.container.IsPortainer"
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-hide="$ctrl.leaveNetworkActionInProgress"
><pr-icon icon="'trash-2'" feather="true" mode="'danger'" class-name="'icon-secondary icon-md'"></pr-icon> Leave network</span
>
<span ng-show="$ctrl.leaveNetworkActionInProgress">Leaving network...</span>
</button>
</td>

View File

@@ -63,57 +63,57 @@
<th>
<a ng-click="$ctrl.changeOrderBy('Hostname')">
Name
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Hostname' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Hostname' && $ctrl.state.reverseOrder"></i>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'Hostname' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'Hostname' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('Role')">
Role
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Role' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Role' && $ctrl.state.reverseOrder"></i>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'Role' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'Role' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('CPUs')">
CPU
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CPUs' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CPUs' && $ctrl.state.reverseOrder"></i>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'CPUs' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'CPUs' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('Memory')">
Memory
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Memory' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Memory' && $ctrl.state.reverseOrder"></i>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'Memory' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'Memory' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('EngineVersion')">
Engine
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'EngineVersion' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'EngineVersion' && $ctrl.state.reverseOrder"></i>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'EngineVersion' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'EngineVersion' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
<th ng-if="$ctrl.showIpAddressColumn">
<a ng-click="$ctrl.changeOrderBy('Addr')">
IP Address
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Addr' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Addr' && $ctrl.state.reverseOrder"></i>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'Addr' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'Addr' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('Status')">
Status
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && $ctrl.state.reverseOrder"></i>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'Status' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'Status' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('Availability')">
Availability
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Availability' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Availability' && $ctrl.state.reverseOrder"></i>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'Availability' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'Availability' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
</tr>

View File

@@ -19,7 +19,7 @@
data-cy="secret-searchInput"
/>
</div>
<div class="actionBar !gap-3" ng-if="!$ctrl.offlineMode" authorization="DockerSecretDelete, DockerSecretCreate">
<div class="actionBar !gap-3" authorization="DockerSecretDelete, DockerSecretCreate">
<button
type="button"
class="btn btn-sm btn-dangerlight h-fit vertical-center !ml-0"
@@ -88,22 +88,22 @@
</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>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('CreatedAt')">
Creation Date
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && $ctrl.state.reverseOrder"></i>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
Ownership
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
</tr>

View File

@@ -1,9 +1,9 @@
<div class="actionBar" authorization="DockerServiceUpdate, DockerServiceDelete, DockerServiceCreate">
<div class="actionBar !gap-3" 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 h-fit vertical-center !ml-0"
class="btn btn-sm btn-primary h-fit"
authorization="DockerServiceUpdate"
ng-disabled="$ctrl.selectedItemCount === 0"
ng-click="$ctrl.updateAction($ctrl.selectedItems)"
@@ -13,7 +13,7 @@
</button>
<button
type="button"
class="btn btn-sm btn-dangerlight h-fit vertical-center !ml-0"
class="btn btn-sm btn-dangerlight h-fit"
authorization="DockerServiceDelete"
ng-disabled="$ctrl.selectedItemCount === 0"
ng-click="$ctrl.removeAction($ctrl.selectedItems)"

View File

@@ -80,50 +80,50 @@
<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>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
<th ng-if="$ctrl.showStackColumn">
<a ng-click="$ctrl.changeOrderBy('StackName')">
Stack
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && $ctrl.state.reverseOrder"></i>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'StackName' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'StackName' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
<th ng-show="$ctrl.columnVisibility.columns.image.display">
<a ng-click="$ctrl.changeOrderBy('Image')">
Image
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Image' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Image' && $ctrl.state.reverseOrder"></i>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'Image' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'Image' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('Mode')">
Scheduling Mode
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Mode' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Mode' && $ctrl.state.reverseOrder"></i>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'Mode' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'Mode' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
<th ng-show="$ctrl.columnVisibility.columns.ports.display">
<a ng-click="$ctrl.changeOrderBy('Ports')">
Published Ports
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Ports' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Ports' && $ctrl.state.reverseOrder"></i>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'Ports' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'Ports' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
<th ng-show="$ctrl.columnVisibility.columns.updated.display">
<a ng-click="$ctrl.changeOrderBy('UpdatedAt')">
Last Update
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'UpdatedAt' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'UpdatedAt' && $ctrl.state.reverseOrder"></i>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'UpdatedAt' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'UpdatedAt' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
<th ng-show="$ctrl.columnVisibility.columns.ownership.display">
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
Ownership
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
</tr>

View File

@@ -7,7 +7,7 @@
{{ $ctrl.titleText }}
</div>
<div class="searchBar vertical-center">
<pr-icon icon="'search'" feather="true"></pr-icon>
<pr-icon icon="'search'" feather="true" class="leading-none"></pr-icon>
<input
type="text"
class="searchInput"
@@ -28,7 +28,7 @@
ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"
data-cy="volume-removeVolumeButton"
>
<pr-icon icon="'trash-2'" feather="true" mode="'danger'"></pr-icon>Remove
<pr-icon icon="'trash-2'" feather="true" mode="'danger'" class="leading-none"></pr-icon>Remove
</button>
<button
type="button"
@@ -37,7 +37,7 @@
authorization="DockerVolumeCreate"
data-cy="volume-addVolumeButton"
>
<pr-icon icon="'plus'" feather="true"></pr-icon>Add volume
<pr-icon icon="'plus'" feather="true" class="leading-none"></pr-icon>Add volume
</button>
</div>
<div class="settings">
@@ -63,8 +63,8 @@
<option value="120">2min</option>
<option value="300">5min</option>
</select>
<span>
<i id="refreshRateChange" class="fa fa-check green-icon" aria-hidden="true" style="margin-top: 7px; display: none"></i>
<span class="inline-block w-3">
<pr-icon id="refreshRateChange" icon="'check'" style="display: none" mode="'success'" feather="true"></pr-icon>
</span>
</div>
</div>
@@ -81,20 +81,24 @@
<table class="table table-hover table-filters nowrap-cells">
<thead>
<tr>
<th uib-dropdown dropdown-append-to-body auto-close="disabled" is-open="$ctrl.filters.state.open">
<th uib-dropdown dropdown-append-to-body auto-close="disabled" is-open="$ctrl.filters.state.open" class="flex gap-1">
<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>
<a ng-click="$ctrl.changeOrderBy('Id')">
Name
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && $ctrl.state.reverseOrder"></i>
</a>
<div>
<span uib-dropdown-toggle class="table-filter" ng-if="!$ctrl.filters.state.enabled">Filter <i class="fa fa-filter" aria-hidden="true"></i></span>
<span uib-dropdown-toggle class="table-filter filter-active" ng-if="$ctrl.filters.state.enabled">Filter <i class="fa fa-check" aria-hidden="true"></i></span>
</div>
<table-column-header
col-title="'Name'"
can-sort="true"
is-sorted="$ctrl.state.orderBy === 'Id'"
is-sorted-desc="$ctrl.state.orderBy === 'Id' && $ctrl.state.reverseOrder"
ng-click="$ctrl.changeOrderBy('Id')"
></table-column-header>
<span uib-dropdown-toggle class="table-filter flex gap-1 self-end" ng-if="!$ctrl.filters.state.enabled"
>Filter <pr-icon icon="'filter'" feather="true"></pr-icon
></span>
<span uib-dropdown-toggle class="table-filter filter-active flex gap-1 self-end" ng-if="$ctrl.filters.state.enabled"
>Filter <pr-icon icon="'check'" feather="true"></pr-icon
></span>
<div class="dropdown-menu" uib-dropdown-menu>
<div class="tableMenu">
<div class="menuHeader"> Filter by usage </div>
@@ -115,46 +119,58 @@
</div>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('StackName')">
Stack
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && $ctrl.state.reverseOrder"></i>
</a>
<table-column-header
col-title="'Stack'"
can-sort="true"
is-sorted="$ctrl.state.orderBy === 'StackName'"
is-sorted-desc="$ctrl.state.orderBy === 'StackName' && $ctrl.state.reverseOrder"
ng-click="$ctrl.changeOrderBy('StackName')"
></table-column-header>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('Driver')">
Driver
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Driver' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Driver' && $ctrl.state.reverseOrder"></i>
</a>
<table-column-header
col-title="'Driver'"
can-sort="true"
is-sorted="$ctrl.state.orderBy === 'Driver'"
is-sorted-desc="$ctrl.state.orderBy === 'Driver' && $ctrl.state.reverseOrder"
ng-click="$ctrl.changeOrderBy('Driver')"
></table-column-header>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('Mountpoint')">
Mount point
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Mountpoint' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Mountpoint' && $ctrl.state.reverseOrder"></i>
</a>
<table-column-header
col-title="'Mount point'"
can-sort="true"
is-sorted="$ctrl.state.orderBy === 'Mountpoint'"
is-sorted-desc="$ctrl.state.orderBy === 'Mountpoint' && $ctrl.state.reverseOrder"
ng-click="$ctrl.changeOrderBy('Mountpoint')"
></table-column-header>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('CreatedAt')">
Created
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && $ctrl.state.reverseOrder"></i>
</a>
<table-column-header
col-title="'Created'"
can-sort="true"
is-sorted="$ctrl.state.orderBy === 'CreatedAt'"
is-sorted-desc="$ctrl.state.orderBy === 'CreatedAt' && $ctrl.state.reverseOrder"
ng-click="$ctrl.changeOrderBy('CreatedAt')"
></table-column-header>
</th>
<th ng-if="$ctrl.showHostColumn">
<a ng-click="$ctrl.changeOrderBy('NodeName')">
Host
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'NodeName' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'NodeName' && $ctrl.state.reverseOrder"></i>
</a>
<table-column-header
col-title="'Host'"
can-sort="true"
is-sorted="$ctrl.state.orderBy === 'NodeName'"
is-sorted-desc="$ctrl.state.orderBy === 'NodeName' && $ctrl.state.reverseOrder"
ng-click="$ctrl.changeOrderBy('NodeName')"
></table-column-header>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
Ownership
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i>
</a>
<table-column-header
col-title="'Ownership'"
can-sort="true"
is-sorted="$ctrl.state.orderBy === 'ResourceControl.Ownership'"
is-sorted-desc="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"
ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')"
></table-column-header>
</th>
</tr>
</thead>
@@ -174,7 +190,7 @@
<span ng-if="$ctrl.offlineMode">{{ item.Id | truncate: 40 }}</span>
<btn authorization="DockerAgentBrowseList" ng-if="$ctrl.showBrowseAction && !$ctrl.offlineMode">
<a ui-sref="docker.volumes.volume.browse({ id: item.Id, nodeName: item.NodeName })" class="btn btn-xs btn-primary space-left">
<i class="fa fa-search"></i> browse
<pr-icon icon="'search'" feather="true"></pr-icon> browse
</a>
</btn>
<span style="margin-left: 10px" class="label label-warning image-tag space-left" ng-if="item.dangling">Unused</span>

View File

@@ -1,7 +1,7 @@
<div ng-if="!$ctrl.labels.length"> There are no labels for this node. </div>
<div class="col-sm-12 form-inline" style="padding: 0px" ng-if="$ctrl.labels.length">
<div ng-repeat="label in $ctrl.labels" style="margin-top: 2px">
<div class="col-sm-12 form-inline !p-0" ng-if="$ctrl.labels.length">
<div ng-repeat="label in $ctrl.labels" class="mt-1">
<div class="input-group col-sm-5 input-group-sm">
<span class="input-group-addon">name</span>
<input type="text" class="form-control" ng-model="label.key" placeholder="e.g. com.example.foo" ng-change="$ctrl.updateLabel(label)" />
@@ -11,7 +11,7 @@
<input type="text" class="form-control" ng-model="label.value" placeholder="e.g. bar" ng-change="$ctrl.updateLabel(label)" />
</div>
<button class="btn btn-light" type="button" ng-click="$ctrl.removeLabel($index)">
<pr-icon icon="'trash'" feather="true" class-name="'icon-secondary icon-md'"></pr-icon>
<pr-icon icon="'trash-2'" feather="true" class-name="'icon-secondary icon-md'"></pr-icon>
</button>
</div>
</div>

View File

@@ -33,7 +33,7 @@
<tr>
<td>
<div class="nopadding">
<a class="btn btn-default btn-sm pull-right" ng-click="$ctrl.addLabel(node)"> <pr-icon icon="'plus'" mode="'alt'" feather="true"></pr-icon> label </a>
<a class="btn btn-secondary btn-sm pull-right" ng-click="$ctrl.addLabel(node)"> <pr-icon icon="'plus'" mode="'alt'" feather="true"></pr-icon> label </a>
</div>
Node Labels
</td>

View File

@@ -84,12 +84,12 @@ function Option(props: OptionProps<GpuOption, true>) {
export function Gpu({
values,
onChange,
gpus,
gpus = [],
usedGpus = [],
usedAllGpus,
}: Props) {
const options = useMemo(() => {
const options = gpus.map((gpu) => ({
const options = (gpus || []).map((gpu) => ({
value: gpu.value,
label:
usedGpus.includes(gpu.value) || usedAllGpus

View File

@@ -147,8 +147,8 @@ class CreateConfigController {
}
}
editorUpdate(cm) {
this.formValues.ConfigContent = cm.getValue();
editorUpdate(value) {
this.formValues.ConfigContent = value;
this.state.isEditorDirty = true;
}
}

View File

@@ -16,13 +16,13 @@
<!-- config-data -->
<div class="form-group">
<div class="col-sm-12" ng-if="ctrl.formValues.displayCodeEditor">
<code-editor
<web-editor-form
identifier="config-creation-editor"
placeholder="Define or paste the content of your config here"
yml="false"
on-change="(ctrl.editorUpdate)"
value="ctrl.formValues.Data"
></code-editor>
></web-editor-form>
</div>
</div>
<!-- !config-data -->

View File

@@ -15,11 +15,11 @@
<td>ID</td>
<td>
{{ config.Id }}
<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="DockerConfigDelete" class="btn btn-xs btn-dangerlight" ng-click="removeConfig(config.Id)"
><pr-icon icon="'trash-2'" feather="true"></pr-icon>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
<button authorization="DockerConfigCreate" class="btn btn-xs btn-secondary" ui-sref="docker.configs.new({id: config.Id})"
><pr-icon icon="'copy'" feather="true"></pr-icon>Clone config</button
>
</td>
</tr>

View File

@@ -983,7 +983,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [
function showConfirmationModal() {
var deferred = $q.defer();
ModalService.confirm({
ModalService.confirmDestructive({
title: 'Are you sure ?',
message: 'A container with the same name already exists. Portainer can automatically remove it and re-create one. Do you want to replace it?',
buttons: {

View File

@@ -672,7 +672,7 @@
<div class="col-sm-12" style="margin-top: 5px">
<label class="control-label text-left">Sysctls</label>
<span class="label label-default interactive" style="margin-left: 10px" ng-click="addSysctl()">
<pr-icon icon="'plus'" mode="'alt'" feather="true"></pr-icon> add sysctl
<pr-icon icon="'plus'" feather="true"></pr-icon> add sysctl
</span>
</div>
<!-- sysctls-input-list -->

View File

@@ -72,7 +72,9 @@
<td>Name</td>
<td ng-if="!container.edit">
{{ container.Name | trimcontainername }}
<a authorization="DockerContainerRename" 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;"
><pr-icon icon="'edit'" feather="true" className="'space-right'"></pr-icon
></a>
</td>
<td ng-if="container.edit">
<form ng-submit="renameContainer()">
@@ -125,19 +127,19 @@
<td colspan="2">
<div class="btn-group" role="group" aria-label="...">
<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
><pr-icon icon="'file-text'" feather="true" className="'space-right'"></pr-icon>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
><pr-icon icon="'info'" feather="true" className="'space-right'"></pr-icon>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
><pr-icon icon="'bar-chart'" feather="true" className="'space-right'"></pr-icon>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
><pr-icon icon="'terminal'" feather="true" className="'space-right'"></pr-icon>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
><pr-icon icon="'paperclip'" feather="true" className="'space-right'"></pr-icon>Attach</a
>
</div>
</td>

View File

@@ -46,7 +46,7 @@ angular.module('portainer.docker').controller('DashboardController', [
$scope.buildGpusStr = function (gpuUseSet) {
var gpusAvailable = new Object();
for (let i = 0; i < $scope.endpoint.Gpus.length; i++) {
for (let i = 0; i < ($scope.endpoint.Gpus || []).length; i++) {
if (!gpuUseSet.has($scope.endpoint.Gpus[i].name)) {
var exist = false;
for (let gpuAvailable in gpusAvailable) {

View File

@@ -6,7 +6,7 @@
<rd-widget-body>
<uib-tabset active="state.activeTab">
<uib-tab index="0">
<uib-tab-heading> <i class="fa fa-wrench space-right" aria-hidden="true"></i> Builder </uib-tab-heading>
<uib-tab-heading class="vertical-center"> <pr-icon icon="'tool'" feather="true" class="leading-none"></pr-icon> Builder </uib-tab-heading>
<form class="form-horizontal">
<div class="col-sm-12 form-section-title"> Naming </div>
<!-- names -->
@@ -16,7 +16,7 @@
<div class="form-group">
<div class="col-sm-12">
<label class="control-label text-left">Names</label>
<span class="label label-default interactive" style="margin-left: 10px" ng-click="addImageName()">
<span class="label label-default interactive" class="ml-2.5" ng-click="addImageName()">
<pr-icon icon="'plus'" mode="'alt'" feather="true"></pr-icon> add additional name
</span>
</div>
@@ -24,7 +24,9 @@
<!-- !names -->
<div class="form-group" ng-if="formValues.ImageNames.length === 0">
<span class="col-sm-12 text-danger small">
<i class="fa fa-exclamation-triangle space-right" aria-hidden="true"></i>You must specify at least one name for the image.
<p class="vertical-center">
<pr-icon icon="'alert-triangle'" mode="'danger'" size="'sm'" feather="true"></pr-icon> You must specify at least one name for the image.
</p>
</span>
</div>
<!-- name-input-list -->
@@ -37,21 +39,24 @@
</div>
<div class="form-group">
<div class="col-sm-12">
<div class="col-sm-12 form-inline" style="margin-top: 10px">
<div ng-repeat="item in formValues.ImageNames track by $index" style="margin-top: 2px">
<div class="col-sm-12 form-inline" class="mt-2.5">
<div ng-repeat="item in formValues.ImageNames track by $index" class="mt-0.5">
<!-- name-input -->
<div class="input-group col-sm-5 input-group-sm">
<span class="input-group-addon">name</span>
<input type="text" class="form-control" ng-model="item.Name" placeholder="e.g. my-image:my-tag" auto-focus />
<span class="input-group-addon"
><i ng-class="{ true: 'fa fa-check green-icon', false: 'fa fa-times red-icon' }[checkName(item.Name)]" aria-hidden="true"></i
></span>
<span class="input-group-addon" ng-if="!checkName(item.Name)">
<pr-icon icon="'x'" mode="'danger'" feather="true"></pr-icon>
</span>
<span class="input-group-addon" ng-if="checkName(item.Name)">
<pr-icon icon="'check'" mode="'success'" feather="true"></pr-icon>
</span>
</div>
<!-- !name-input -->
<!-- actions -->
<div class="input-group col-sm-2 input-group-sm">
<button class="btn btn-sm btn-danger" type="button" ng-click="removeImageName($index)">
<pr-icon icon="'trash'" feather="true" class-name="'icon-secondary icon-md'"></pr-icon>
<pr-icon icon="'trash-2'" feather="true"></pr-icon>
</button>
</div>
<!-- !actions -->
@@ -64,12 +69,12 @@
<!-- build-method -->
<div class="col-sm-12 form-section-title"> Build method </div>
<div class="form-group"></div>
<div class="form-group" style="margin-bottom: 0">
<div class="form-group" class="mb-0">
<div class="boxselector_wrapper">
<div>
<input type="radio" id="method_editor" ng-model="state.BuildType" value="editor" ng-click="toggleEditor()" />
<label for="method_editor">
<div class="boxselector_header">
<div class="boxselector_header vertical-center">
<pr-icon icon="'edit'" feather="true"></pr-icon>
Web editor
</div>
@@ -79,7 +84,7 @@
<div>
<input type="radio" id="method_upload" ng-model="state.BuildType" value="upload" ng-click="saveEditorContent()" />
<label for="method_upload">
<div class="boxselector_header">
<div class="boxselector_header vertical-center">
<pr-icon icon="'upload'" feather="true"></pr-icon>
Upload
</div>
@@ -89,7 +94,7 @@
<div>
<input type="radio" id="method_url" ng-model="state.BuildType" value="url" ng-click="saveEditorContent()" />
<label for="method_url">
<div class="boxselector_header">
<div class="boxselector_header vertical-center">
<pr-icon icon="'globe'" feather="true"></pr-icon>
URL
</div>
@@ -130,11 +135,11 @@
</span>
</div>
<div class="form-group">
<div class="col-sm-12">
<div class="col-sm-12 vertical-center">
<button class="btn btn-sm btn-primary" ngf-select ngf-min-size="10" ng-model="formValues.UploadFile">Select file</button>
<span style="margin-left: 5px">
<span class="space-left">
{{ formValues.UploadFile.name }}
<i class="fa fa-times red-icon" ng-if="!formValues.UploadFile" aria-hidden="true"></i>
<span ng-if="!formValues.UploadFile"><pr-icon icon="'x'" mode="'danger'" feather="true" size="'md'"></pr-icon></span>
</span>
</div>
</div>
@@ -206,14 +211,14 @@
<span ng-hide="state.actionInProgress">Build the image</span>
<span ng-show="state.actionInProgress">Image building in progress...</span>
</button>
<span class="text-danger" ng-if="state.formValidationError" style="margin-left: 5px">{{ state.formValidationError }}</span>
<span class="text-danger" ng-if="state.formValidationError" class="space-left">{{ state.formValidationError }}</span>
</div>
</div>
<!-- !actions -->
</form>
</uib-tab>
<uib-tab index="1" disable="!buildLogs">
<uib-tab-heading> <i class="fa fa-file-alt space-right" aria-hidden="true"></i> Output </uib-tab-heading>
<uib-tab-heading class="vertical-center"> <pr-icon icon="'file-text'" feather="true" class="leading-none"></pr-icon> Output </uib-tab-heading>
<pre class="log_viewer">
<div ng-repeat="line in buildLogs track by $index" class="line"><p class="inner_line" ng-click="active=!active" ng-class="{'line_selected': active}">{{ line }}</p></div>
<div ng-if="!buildLogs.length" class="line"><p class="inner_line">No build output available.</p></div>

View File

@@ -9,18 +9,26 @@
<div class="form-group">
<div class="row">
<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" 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)" 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)" authorization="DockerImageDelete">
<span class="fa fa-trash-alt white-icon" aria-hidden="true"></span>
</a>
<div class="input-group col-md-1 !pr-3.5 !pl-3.5">
<span class="input-group-addon" style="border-right: 1px solid var(--border-input-group-addon-color); border-radius: 4px">{{ tag }}</span>
<span class="input-group-btn" style="padding: 0px 5px">
<span style="margin: 0px 5px" authorization="DockerImagePush">
<a data-toggle="tooltip" class="btn btn-primary interactive" title="Push to registry" ng-click="pushTag(tag)">
<pr-icon icon="'upload'" feather="true" class="text-white"></pr-icon>
</a>
</span>
<span class="my-0 mx-1" authorization="DockerImageCreate">
<a data-toggle="tooltip" class="btn btn-primary interactive" title="Pull from registry" ng-click="pullTag(tag)">
<pr-icon icon="'download'" feather="true" class="text-white"></pr-icon>
</a>
</span>
<span class="my-0 mx-1" authorization="DockerImageDelete">
<a data-toggle="tooltip" class="btn btn-primary interactive" title="Remove tag" ng-click="removeTag(tag)">
<pr-icon icon="'trash-2'" feather="true" class="text-white"></pr-icon>
</a>
</span>
</span>
</div>
</div>
@@ -28,20 +36,19 @@
</div>
<div class="form-group">
<div class="col-sm-12">
<span class="small text-muted">
Note: you can click on the upload icon <span class="fa fa-upload" aria-hidden="true"></span> to push an image or on the download icon
<span class="fa fa-download" aria-hidden="true"></span> to pull an image or on the trash icon <span class="fa fa-trash-alt" aria-hidden="true"></span> to delete a
tag.
<span class="small text-muted" authorization="DockerImageDelete">
Note: you can click on the upload icon <pr-icon icon="'upload'" feather="true"></pr-icon> to push an image or on the download icon
<pr-icon icon="'download'" feather="true"></pr-icon> to pull an image or on the trash icon <pr-icon icon="'trash-2'" feather="true"></pr-icon> to delete a tag.
</span>
</div>
<div class="col-sm-12">
<span id="downloadResourceHint" class="createResource" style="display: none; margin-left: 0">
<span id="downloadResourceHint" class="createResource ml-0" style="display: none">
Download in progress...
<i class="fa fa-circle-notch fa-spin" aria-hidden="true" style="margin-left: 2px"></i>
<i class="fa fa-circle-notch fa-spin ml-0.5" aria-hidden="true"></i>
</span>
<span id="uploadResourceHint" class="createResource" style="display: none; margin-left: 0">
<span id="uploadResourceHint" class="createResource ml-0.5" style="display: none">
Upload in progress...
<i class="fa fa-circle-notch fa-spin" aria-hidden="true" style="margin-left: 2px"></i>
<i class="fa fa-circle-notch fa-spin ml-0.5" aria-hidden="true"></i>
</span>
</div>
</div>
@@ -97,9 +104,9 @@
<td>ID</td>
<td>
{{ image.Id }}
<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="DockerImageDelete" class="btn btn-xs btn-danger" ng-click="removeImage(image.Id)">
<pr-icon icon="'trash-2'" feather="true"></pr-icon> Delete this image
</button>
<button
authorization="DockerImageGet"
class="btn btn-xs btn-primary"
@@ -107,7 +114,7 @@
button-spinner="$ctrl.exportInProgress"
ng-disabled="state.exportInProgress"
>
<i class="fa fa-download space-right" aria-hidden="true"></i>
<pr-icon icon="'download'" feather="true"></pr-icon>
<span ng-hide="state.exportInProgress">Export this image</span>
<span ng-show="state.exportInProgress">Export in progress...</span>
</button>
@@ -212,34 +219,40 @@
<rd-widget-body classes="no-padding">
<table id="image-layers" class="table">
<thead>
<th style="white-space: nowrap">
<a ng-click="order('Order')">
Order
<span ng-show="sortType == 'Order' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Order' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
<th class="whitespace-nowrap">
<table-column-header
col-title="'Order'"
can-sort="true"
is-sorted="$ctrl.state.orderBy === 'Order'"
is-sorted-desc="$ctrl.state.orderBy === 'Order' && $ctrl.state.reverseOrder"
ng-click="$ctrl.changeOrderBy('Order')"
></table-column-header>
</th>
<th>
<a ng-click="order('Size')">
Size
<span ng-show="sortType == 'Size' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Size' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
<table-column-header
col-title="'Size'"
can-sort="true"
is-sorted="$ctrl.state.orderBy === 'Size'"
is-sorted-desc="$ctrl.state.orderBy === 'Size' && $ctrl.state.reverseOrder"
ng-click="$ctrl.changeOrderBy('Size')"
></table-column-header>
</th>
<th>
<a ng-click="order('CreatedBy')">
Layer
<span ng-show="sortType == 'CreatedBy' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'CreatedBy' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
<table-column-header
col-title="'Layer'"
can-sort="true"
is-sorted="$ctrl.state.orderBy === 'Layer'"
is-sorted-desc="$ctrl.state.orderBy === 'Layer' && $ctrl.state.reverseOrder"
ng-click="$ctrl.changeOrderBy('Layer')"
></table-column-header>
</th>
</thead>
<tbody>
<tr ng-repeat="layer in history | orderBy:sortType:sortReverse">
<td style="white-space: nowrap">
<td class="whitespace-nowrap">
{{ layer.Order }}
</td>
<td style="white-space: nowrap">
<td class="whitespace-nowrap">
{{ layer.Size | humansize }}
</td>
<td class="expand">
@@ -249,7 +262,7 @@
</span>
<span id="layer-command-{{ $index }}-short">
{{ layer.CreatedBy | imagelayercommand | truncate: 130 }}
<span ng-if="layer.CreatedBy.length > 130" style="margin-left: 5px">
<span ng-if="layer.CreatedBy.length > 130" class="ml-1">
<a id="layer-command-expander{{ $index }}" class="btn" ng-click="toggleLayerCommand($index)">
<pr-icon icon="'plus'" mode="'alt'" feather="true"></pr-icon>
</a>

View File

@@ -15,8 +15,8 @@
<td>ID</td>
<td>
{{ secret.Id }}
<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
<button authorization="DockerSecretDelete" class="btn btn-xs btn-dangerlight" ng-click="removeSecret(secret.Id)"
><pr-icon icon="'trash-2'" feather="true" mode="'danger'"></pr-icon>Delete this secret</button
>
</td>
</tr>

View File

@@ -550,7 +550,7 @@ angular.module('portainer.docker').controller('ServiceController', [
}
$scope.rollbackService = function (service) {
ModalService.confirm({
ModalService.confirmWarn({
title: 'Rollback service',
message: 'Are you sure you want to rollback?',
buttons: {

View File

@@ -123,7 +123,17 @@ export function EdgeDevicesDatatable({
<div className="row">
<div className="col-sm-12">
<TableContainer>
<TableTitle icon="fa-plug" label="Edge Devices">
<TableTitle icon="box" featherIcon label="Edge Devices">
<SearchBar value={search} onChange={handleSearchBarChange} />
<TableActions>
<EdgeDevicesDatatableActions
selectedItems={selectedFlatRows.map((row) => row.original)}
isFDOEnabled={isFdoEnabled}
isOpenAMTEnabled={isOpenAmtEnabled}
setLoadingMessage={setLoadingMessage}
showWaitingRoomLink={showWaitingRoomLink}
/>
</TableActions>
<TableTitleActions>
<ColumnVisibilityMenu<Environment>
columns={columnsToHide}
@@ -135,15 +145,6 @@ export function EdgeDevicesDatatable({
</TableSettingsMenu>
</TableTitleActions>
</TableTitle>
<TableActions>
<EdgeDevicesDatatableActions
selectedItems={selectedFlatRows.map((row) => row.original)}
isFDOEnabled={isFdoEnabled}
isOpenAMTEnabled={isOpenAmtEnabled}
setLoadingMessage={setLoadingMessage}
showWaitingRoomLink={showWaitingRoomLink}
/>
</TableActions>
{isOpenAmtEnabled && someDeviceHasAMTActivated && (
<div className={styles.kvmTip}>
<TextTip color="blue">
@@ -161,7 +162,6 @@ export function EdgeDevicesDatatable({
</TextTip>
</div>
)}
<SearchBar value={search} onChange={handleSearchBarChange} />
<Table
className={tableProps.className}
role={tableProps.role}

View File

@@ -1,7 +1,10 @@
import { useRouter } from '@uirouter/react';
import type { Environment } from '@/portainer/environments/types';
import { confirmAsync } from '@/portainer/services/modal.service/confirm';
import {
confirmAsync,
confirmDestructiveAsync,
} from '@/portainer/services/modal.service/confirm';
import { promptAsync } from '@/portainer/services/modal.service/prompt';
import * as notifications from '@/portainer/services/notifications';
import { activateDevice } from '@/portainer/hostmanagement/open-amt/open-amt.service';
@@ -18,6 +21,11 @@ interface Props {
showWaitingRoomLink: boolean;
}
enum DeployType {
FDO = 'FDO',
MANUAL = 'MANUAL',
}
export function EdgeDevicesDatatableActions({
selectedItems,
isOpenAMTEnabled,
@@ -33,13 +41,13 @@ export function EdgeDevicesDatatableActions({
disabled={selectedItems.length < 1}
color="danger"
onClick={() => onDeleteEdgeDeviceClick()}
icon="trash-2"
featherIcon
>
<i className="fa fa-trash-alt space-right" aria-hidden="true" />
Remove
</Button>
<Button onClick={() => onAddNewDeviceClick()}>
<i className="fa fa-plus-circle space-right" aria-hidden="true" />
<Button onClick={() => onAddNewDeviceClick()} icon="plus" featherIcon>
Add Device
</Button>
@@ -47,8 +55,9 @@ export function EdgeDevicesDatatableActions({
<Button
disabled={selectedItems.length !== 1}
onClick={() => onAssociateOpenAMTClick(selectedItems)}
icon="link"
featherIcon
>
<i className="fa fa-link space-right" aria-hidden="true" />
Associate with OpenAMT
</Button>
)}
@@ -62,7 +71,7 @@ export function EdgeDevicesDatatableActions({
);
async function onDeleteEdgeDeviceClick() {
const confirmed = await confirmAsync({
const confirmed = await confirmDestructiveAsync({
title: 'Are you sure ?',
message:
'This action will remove all configurations associated to your environment(s). Continue?',
@@ -101,38 +110,37 @@ export function EdgeDevicesDatatableActions({
}
async function onAddNewDeviceClick() {
if (!isFDOEnabled) {
router.stateService.go('portainer.endpoints.newEdgeDevice');
return;
}
const result = await promptAsync({
title: 'How would you like to add an Edge Device?',
inputType: 'radio',
inputOptions: [
{
text: 'Provision bare-metal using Intel FDO',
value: 'FDO',
},
{
text: 'Deploy agent manually',
value: 'MANUAL',
},
],
buttons: {
confirm: {
label: 'Confirm',
className: 'btn-primary',
},
},
});
const result = isFDOEnabled
? await promptAsync({
title: 'How would you like to add an Edge Device?',
inputType: 'radio',
inputOptions: [
{
text: 'Provision bare-metal using Intel FDO',
value: DeployType.FDO,
},
{
text: 'Deploy agent manually',
value: DeployType.MANUAL,
},
],
buttons: {
confirm: {
label: 'Confirm',
className: 'btn-primary',
},
},
})
: DeployType.MANUAL;
switch (result) {
case 'FDO':
case DeployType.FDO:
router.stateService.go('portainer.endpoints.importDevice');
break;
case 'MANUAL':
router.stateService.go('portainer.endpoints.newEdgeDevice');
case DeployType.MANUAL:
router.stateService.go('portainer.wizard.endpoints', {
edgeDevice: true,
});
break;
default:
break;

View File

@@ -1,5 +1,5 @@
import _ from 'lodash-es';
import { confirmAsync } from '@/portainer/services/modal.service/confirm';
import { confirmDestructiveAsync } from '@/portainer/services/modal.service/confirm';
import { EdgeTypes } from '@/portainer/environments/types';
import { getEnvironments } from '@/portainer/environments/environment.service';
@@ -50,7 +50,7 @@ export class EdgeGroupFormController {
dissociateEndpoint(endpoint) {
return this.$async(async () => {
const confirmed = await confirmAsync({
const confirmed = await confirmDestructiveAsync({
title: 'Confirm action',
message: 'Removing the environment from this group will remove its corresponding edge stacks',
buttons: {

View File

@@ -2,13 +2,25 @@
<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>
<div class="toolBarTitle vertical-center">
<pr-icon icon="$ctrl.titleIcon" feather="true" mode="'primary'" class-name="'icon-nested-blue'"></pr-icon>
{{ $ctrl.titleText }}
</div>
<div class="searchBar">
<pr-icon icon="'search'" feather="true" class="vertical-center"></pr-icon>
<input
type="text"
class="searchInput ml-1"
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search for an application..."
auto-focus
ng-model-options="{ debounce: 300 }"
/>
</div>
<div ng-if="$ctrl.refreshCallback" class="settings">
<span class="setting" ng-class="{ 'setting-active': $ctrl.settings.open }" uib-dropdown dropdown-append-to-body auto-close="disabled" is-open="$ctrl.settings.open">
<span uib-dropdown-toggle> <i class="fa fa-cog" aria-hidden="true"></i> Table settings </span>
<span uib-dropdown-toggle><pr-icon icon="'more-vertical'" feather="true"></pr-icon></span>
<div class="dropdown-menu dropdown-menu-right" uib-dropdown-menu>
<div class="tableMenu">
<div class="menuHeader"> Table settings </div>
@@ -28,7 +40,7 @@
<option value="300">5min</option>
</select>
<span>
<i id="refreshRateChange" class="fa fa-check green-icon" aria-hidden="true" style="margin-top: 7px; display: none"></i>
<pr-icon id="refreshRateChange" style="display: none" icon="'check'" mode="'success'" feather="true"></pr-icon>
</span>
</div>
</div>
@@ -41,42 +53,36 @@
</span>
</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-model-options="{ debounce: 300 }"
/>
</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>
<table-column-header
col-title="'Name'"
can-sort="true"
is-sorted="$ctrl.state.orderBy === 'Name'"
is-sorted-desc="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"
ng-click="$ctrl.changeOrderBy('Name')"
></table-column-header>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('StackName')">
Stack
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && $ctrl.state.reverseOrder"></i>
</a>
<table-column-header
col-title="'Stack'"
can-sort="true"
is-sorted="$ctrl.state.orderBy === 'StackName'"
is-sorted-desc="$ctrl.state.orderBy === 'StackName' && $ctrl.state.reverseOrder"
ng-click="$ctrl.changeOrderBy('StackName')"
></table-column-header>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('Image')">
Image
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Image' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Image' && $ctrl.state.reverseOrder"></i>
</a>
<table-column-header
col-title="'Image'"
can-sort="true"
is-sorted="$ctrl.state.orderBy === 'Image'"
is-sorted-desc="$ctrl.state.orderBy === 'Image' && $ctrl.state.reverseOrder"
ng-click="$ctrl.changeOrderBy('Image')"
></table-column-header>
</th>
</tr>
</thead>

View File

@@ -1,5 +1,10 @@
<rd-widget>
<rd-widget-header icon="fa-dharmachakra" title-text="Additional repositories"></rd-widget-header>
<div class="toolBar px-5 pt-5">
<div class="toolBarTitle">
<pr-icon icon="'svg-helm'" class-name="'icon-nested-blue'" mode="'primary'"></pr-icon>
Additional repositories
</div>
</div>
<rd-widget-body>
<div class="actionBar">
<form class="form-horizontal" name="addUserHelmRepoForm">
@@ -7,7 +12,7 @@
<span class="col-sm-12 text-muted small"> Add a Helm repository. All Helm charts in the repository will be added to the list. </span>
</div>
<div class="form-group">
<div class="form-group mb-2">
<div class="col-sm-12">
<input
type="url"
@@ -22,16 +27,20 @@
</div>
<div class="form-group nomargin" ng-show="addUserHelmRepoForm.repo.$invalid">
<div class="col-sm-12 small text-warning">
<div class="small">
<div ng-messages="addUserHelmRepoForm.repo.$error">
<p ng-message="pattern"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> A valid URL beginning with http(s) is required.</p>
<p class="vertical-center" ng-message="pattern"
><pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> A valid URL beginning with http(s) is required.</p
>
</div>
</div>
</div>
<div class="form-group" ng-show="$ctrl.doesRepoExist()">
<div class="col-sm-12 small text-warning">
<div> <i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Helm repo already exists. </div>
<div class="form-group nomargin" ng-show="$ctrl.doesRepoExist()">
<div class="small">
<div ng-messages="addUserHelmRepoForm.repo.$error">
<p class="vertical-center"><pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> Helm repo already exists.</p>
</div>
</div>
</div>
@@ -39,7 +48,7 @@
<div class="col-sm-12">
<button
type="button"
class="btn btn-sm btn-default nomargin"
class="btn btn-primary btn-sm nomargin"
ng-click="$ctrl.addRepository()"
ng-disabled="$ctrl.state.isAddingRepo || addUserHelmRepoForm.repo.$invalid || $ctrl.doesRepoExist()"
analytics-on

View File

@@ -1,12 +1,19 @@
<!-- helm chart -->
<div ng-class="{ 'blocklist-item--selected': $ctrl.model.Selected }" class="blocklist-item template-item" ng-click="$ctrl.onSelect($ctrl.model)">
<div ng-class="{ 'blocklist-item--selected': $ctrl.model.Selected }" class="blocklist-item template-item mx-[10px]" ng-click="$ctrl.onSelect($ctrl.model)">
<div class="blocklist-item-box">
<!-- helmchart-image -->
<span ng-if="$ctrl.model.icon">
<img class="blocklist-item-logo" ng-src="{{ $ctrl.model.icon }}" />
<fallback-image
src="$ctrl.model.icon"
fallback-icon="'svg-helm'"
class-name="'blocklist-item-logo h-16 w-auto'"
fallback-class-name="'icon-nested-blue !h-12 !w-12'"
fallback-mode="'primary'"
size="'xl'"
></fallback-image>
</span>
<span class="blocklist-item-logo" ng-if="!$ctrl.model.icon">
<i class="fa fa-dharmachakra fa-4x blue-icon" aria-hidden="true"></i>
<span class="blocklist-item-logo vertical-center" ng-if="!$ctrl.model.icon">
<pr-icon icon="'svg-helm'" mode="'primary'" class-name="'icon-nested-blue'"></pr-icon>
</span>
<!-- !helmchart-image -->
<!-- helmchart-details -->
@@ -18,8 +25,8 @@
{{ $ctrl.model.name }}
</span>
<span class="space-left blocklist-item-subtitle">
<span>
<i class="fa fa-dharmachakra" aria-hidden="true"></i>
<span class="vertical-center">
<pr-icon icon="'svg-helm'" mode="'primary'"></pr-icon>
</span>
<span> Helm </span>
</span>

View File

@@ -1,37 +1,37 @@
<div class="datatable">
<rd-widget>
<rd-widget-body classes="no-padding">
<div class="toolBar">
<div class="toolBarTitle"> <pr-icon icon="$ctrl.titleIcon" class-name="space-right"></pr-icon> {{ $ctrl.titleText }} </div>
</div>
<div class="actionBar">
<div>
<span style="width: 25%">
<ui-select ng-model="$ctrl.state.selectedCategory" theme="bootstrap">
<ui-select-match placeholder="Select a category">
<a class="btn btn-xs btn-link pull-right" ng-click="$ctrl.clearCategory()"><i class="glyphicon glyphicon-remove"></i></a>
<span>{{ $select.selected }}</span>
</ui-select-match>
<ui-select-choices repeat="category in ($ctrl.state.categories | filter: $select.search)">
<span>{{ category }}</span>
</ui-select-choices>
</ui-select>
</span>
<div class="toolBar vertical-center !gap-x-5 !gap-y-1 flex-wrap w-full relative">
<div class="toolBarTitle vertical-center">
<pr-icon icon="$ctrl.titleIcon" feather="true" mode="'primary'" class-name="'icon-nested-blue space-right'"></pr-icon>
{{ $ctrl.titleText }}
</div>
</div>
<div class="searchBar" style="border-top: 2px solid #f6f6f6">
<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-model-options="{ debounce: 300 }"
/>
<div class="searchBar vertical-center !mr-0">
<pr-icon icon="'search'" feather="true" class="searchIcon"></pr-icon>
<input
type="text"
class="searchInput"
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search..."
auto-focus
ng-model-options="{ debounce: 300 }"
/>
</div>
<div>
<ui-select ng-model="$ctrl.state.selectedCategory" theme="bootstrap" append-to-body="true">
<ui-select-match placeholder="Select a category">
<a class="btn btn-xs btn-link pull-right vertical-center" ng-click="$ctrl.clearCategory()">
<pr-icon icon="'x'" feather="true"></pr-icon>
</a>
<span class="vertical-center">{{ $select.selected }}</span>
</ui-select-match>
<ui-select-choices repeat="category in ($ctrl.state.categories | filter: $select.search)">
<span>{{ category }}</span>
</ui-select-choices>
</ui-select>
</div>
</div>
<div class="blocklist">

View File

@@ -83,7 +83,7 @@ export default class HelmTemplatesController {
}
async selectHelmChart(chart) {
this.$anchorScroll('view-top');
window.scrollTo(0, 0);
this.state.showCustomValues = false;
this.state.chart = chart;
await this.getHelmValues();

View File

@@ -2,14 +2,19 @@
<information-panel title-text="Information" ng-if="!$ctrl.state.chart">
<span class="small text-muted">
<p>
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px"></i>
This is a first version for Helm charts, for more information see this <a href="https://www.portainer.io/blog/portainer-now-with-helm-support" target="_blank">blog post</a>.
</p>
<p ng-if="$ctrl.state.globalRepository === ''">
<i class="fa fa-exclamation-circle blue-icon" aria-hidden="true" style="margin-right: 2px"></i>
<p class="inline-flex flex-row items-center">
<pr-icon icon="'info'" feather="true" class="mr-1 vertical-center" mode="'primary'"></pr-icon>
This is a first version for Helm charts, for more information see this&nbsp;<a
class="text-blue-8 hover:underline hover:text-blue-8"
href="https://www.portainer.io/blog/portainer-now-with-helm-support"
target="_blank"
>blog post</a
>.</p
>
<p ng-if="$ctrl.state.globalRepository === ''" class="inline-flex items-center">
<pr-icon icon="'info'" feather="true"></pr-icon>
<span>The Global Helm Repository is not configured.</span>
<a ng-if="$ctrl.state.isAdmin" ui-sref="portainer.settings">Configure Global Helm Repository in Settings</a>
<a ng-if="$ctrl.state.isAdmin" ui-sref="portainer.settings">Configure Global Helm Repository in Settings</a>.
</p>
</span>
</information-panel>
@@ -18,7 +23,16 @@
<!-- helmchart-form -->
<div class="col-sm-12" ng-if="$ctrl.state.chart">
<rd-widget>
<rd-widget-custom-header icon="$ctrl.state.chart.icon" title-text="$ctrl.state.chart.name"></rd-widget-custom-header>
<div class="toolBarTitle vertical-center pt-5 px-5 text-muted">
<fallback-image
src="$ctrl.state.chart.icon"
fallback-icon="'svg-helm'"
class-name="'h-8 w-8'"
fallback-class-name="'icon-nested-blue'"
fallback-mode="'primary'"
></fallback-image>
{{ $ctrl.state.chart.name }}
</div>
<rd-widget-body classes="padding">
<form class="form-horizontal" name="$ctrl.helmTemplateCreationForm">
<!-- description -->
@@ -26,7 +40,7 @@
<div class="col-sm-12 form-section-title"> Description </div>
<div class="form-group">
<div class="col-sm-12">
<div class="template-note" ng-bind-html="$ctrl.state.chart.description"></div>
<div class="text-xs text-muted" ng-bind-html="$ctrl.state.chart.description"></div>
</div>
</div>
</div>
@@ -46,17 +60,16 @@
></select>
</div>
</div>
<div class="form-group" ng-if="!$ctrl.state.resourcePool">
<div class="col-sm-12 small text-muted">
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px"></i>
<div class="col-sm-12 small text-muted vertical-center">
<pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon>
You do not have access to any namespace. Contact your administrator to get access to a namespace.
</div>
</div>
<!-- !namespace-input -->
<!-- name-input -->
<div class="form-group">
<label for="release_name" class="col-sm-2 control-label text-left">Name</label>
<div class="form-group mb-2">
<label for="release_name" class="col-sm-2 control-label text-left required">Name</label>
<div class="col-sm-10">
<input
type="text"
@@ -70,12 +83,17 @@
</div>
</div>
<div class="form-group" ng-show="$ctrl.helmTemplateCreationForm.release_name.$invalid">
<div class="col-sm-12 small text-warning">
<div class="small">
<div ng-messages="$ctrl.helmTemplateCreationForm.release_name.$error">
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
<p ng-message="pattern">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field must consist of lower case alphanumeric characters or '-', start with an alphabetic
character, and end with an alphanumeric character (e.g. 'my-name', or 'abc-123').
<div class="col-sm-2"></div>
<p class="vertical-center col-sm-10 text-muted" ng-message="required">
<pr-icon icon="'alert-triangle'" feather="true" mode="'warning'" class="vertical-center"></pr-icon>
This field is required.
</p>
<p class="vertical-center col-sm-10 text-muted" ng-message="pattern">
<pr-icon icon="'alert-triangle'" feather="true" mode="'warning'" class="vertical-center"></pr-icon>
This field must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name',
or 'abc-123').
</p>
</div>
</div>
@@ -83,35 +101,44 @@
<!-- !name-input -->
<div class="form-group">
<div class="col-sm-12">
<a class="small interactive" ng-if="!$ctrl.state.showCustomValues && !$ctrl.state.loadingValues" ng-click="$ctrl.state.showCustomValues = true;">
<i class="fa fa-plus space-right" aria-hidden="true"></i> Show custom values
</a>
<span class="small interactive" ng-if="$ctrl.state.loadingValues"> <i class="fa fa-sync-alt space-right" aria-hidden="true"></i> Loading values.yaml... </span>
<a class="small interactive" ng-if="$ctrl.state.showCustomValues" ng-click="$ctrl.state.showCustomValues = false;">
<i class="fa fa-minus space-right" aria-hidden="true"></i> Hide custom values
</a>
<button
ng-if="!$ctrl.state.showCustomValues && !$ctrl.state.loadingValues"
class="btn btn-xs btn-default vertical-center mr-2 !ml-0"
ng-click="$ctrl.state.showCustomValues = true;"
>
<pr-icon icon="'plus'" feather="true" class="vertical-center"></pr-icon>
Show custom values
</button>
<span class="small interactive vertical-center" ng-if="$ctrl.state.loadingValues">
<pr-icon icon="'refresh-cw'" class="mr-1" feather="true"></pr-icon>
Loading values.yaml...
</span>
<button ng-if="$ctrl.state.showCustomValues" class="btn btn-xs btn-default vertical-center mr-2 !ml-0" ng-click="$ctrl.state.showCustomValues = false;">
<pr-icon icon="'minus'" feather="true" class="vertical-center"></pr-icon>
Hide custom values
</button>
</div>
</div>
<!-- values override -->
<div ng-if="$ctrl.state.showCustomValues">
<!-- web-editor -->
<div>
<div class="col-sm-12 form-section-title"> Web editor </div>
<div class="form-group">
<span class="col-sm-12 text-muted small">
You can get more information about Helm values file format in the
<a href="https://helm.sh/docs/chart_template_guide/values_files/" target="_blank">official documentation</a>.
</span>
</div>
<div class="form-group">
<div class="col-sm-12">
<code-editor
<web-editor-form
identifier="helm-app-creation-editor"
placeholder="# Define or paste the content of your values yaml file here"
yml="true"
on-change="($ctrl.editorUpdate)"
value="$ctrl.state.values"
></code-editor>
on-change="($ctrl.editorUpdate)"
yml="true"
placeholder="# Define or paste the content of your values yaml file here"
>
<editor-description>
You can get more information about Helm values file format in the
<a href="https://helm.sh/docs/chart_template_guide/values_files/" target="_blank" class="text-blue-8 hover:text-blue-8 hover:underline"
>official documentation</a
>.
</editor-description>
</web-editor-form>
</div>
</div>
</div>
@@ -124,7 +151,7 @@
<div class="col-sm-12">
<button
type="button"
class="btn btn-primary btn-sm"
class="btn btn-primary btn-sm !ml-0"
ng-disabled="!($ctrl.state.appName && $ctrl.state.resourcePool && !$ctrl.state.loadingValues && !$ctrl.state.actionInProgress)"
ng-click="$ctrl.installHelmchart()"
button-spinner="$ctrl.state.actionInProgress"
@@ -155,7 +182,7 @@
<div class="col-sm-12">
<helm-templates-list
title-text="Charts"
title-icon="fa-rocket"
title-icon="compass"
charts="$ctrl.state.charts"
table-key="$ctrl.state.charts"
select-action="$ctrl.selectHelmChart"

View File

@@ -4,20 +4,20 @@
<div class="form-group" ng-if="$ctrl.isCreation">
<div class="col-sm-12">
<p>
<a class="small interactive" ng-if="$ctrl.formValues.IsSimple" ng-click="$ctrl.showAdvancedMode()">
<i class="fa fa-list-ol space-right" aria-hidden="true"></i> Advanced mode
<a class="small interactive vertical-center" ng-if="$ctrl.formValues.IsSimple" ng-click="$ctrl.showAdvancedMode()">
<pr-icon icon="'list'" feather="true"></pr-icon> Advanced mode
</a>
<a class="small interactive" ng-if="!$ctrl.formValues.IsSimple" ng-click="$ctrl.showSimpleMode()">
<i class="fa fa-edit space-right" aria-hidden="true"></i> Simple mode
<a class="small interactive vertical-center" ng-if="!$ctrl.formValues.IsSimple" ng-click="$ctrl.showSimpleMode()">
<pr-icon icon="'edit'" feather="true"></pr-icon> Simple mode
</a>
</p>
</div>
<div class="col-sm-12 small text-muted" ng-if="$ctrl.formValues.IsSimple">
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px"></i>
<div class="col-sm-12 small text-muted vertical-center" ng-if="$ctrl.formValues.IsSimple">
<pr-icon icon="'alert-circle'" mode="'primary'" feather="true" class="vertical-center"></pr-icon>
Switch to advanced mode to copy and paste multiple key/values
</div>
<div class="col-sm-12 small text-muted" ng-if="!$ctrl.formValues.IsSimple">
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px"></i>
<div class="col-sm-12 small text-muted vertical-center" ng-if="!$ctrl.formValues.IsSimple">
<pr-icon icon="'alert-circle'" mode="'primary'" feather="true" class="vertical-center"></pr-icon>
Generate a configuration entry per line, use YAML format
</div>
</div>
@@ -25,18 +25,18 @@
<div class="form-group" ng-if="$ctrl.formValues.IsSimple">
<div class="col-sm-12">
<button type="button" class="btn btn-sm btn-default" style="margin-left: 0" ng-click="$ctrl.addEntry()" data-cy="k8sConfigCreate-createEntryButton">
<i class="fa fa-plus-circle" aria-hidden="true"></i> Create entry
<pr-icon icon="'plus'" feather="true" class="vertical-center"></pr-icon> Create entry
</button>
<button type="button" class="btn btn-sm btn-default" ngf-select="$ctrl.addEntryFromFile($file)" style="margin-left: 0" data-cy="k8sConfigCreate-createConfigsFromFileButton">
<i class="fa fa-file-upload" aria-hidden="true"></i> Create key/value from file
<pr-icon icon="'upload'" feather="true" class="vertical-center"></pr-icon> Create key/value from file
</button>
</div>
</div>
<div ng-repeat="(index, entry) in $ctrl.formValues.Data" ng-if="$ctrl.formValues.IsSimple">
<div class="form-group">
<label for="configuration_data_key_{{ index }}" class="col-sm-1 control-label text-left">Key</label>
<div class="col-sm-11">
<label for="configuration_data_key_{{ index }}" class="col-sm-3 col-lg-2 control-label text-left">Key</label>
<div class="col-sm-8">
<input
type="text"
class="form-control"
@@ -49,7 +49,7 @@
/>
</div>
<div
class="col-sm-11 small text-warning"
class="col-sm-11 small text-muted"
style="margin-top: 5px"
ng-show="
kubernetesConfigurationDataCreationForm['configuration_data_key_' + index].$invalid ||
@@ -58,18 +58,22 @@
"
>
<ng-messages for="kubernetesConfigurationDataCreationForm['configuration_data_key_' + index].$error">
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
<p ng-message="required" class="vertical-center"> <pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> This field is required. </p>
</ng-messages>
<p ng-if="$ctrl.state.duplicateKeys[index] !== undefined"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This key is already defined.</p>
<p ng-if="$ctrl.state.invalidKeys[index]"
><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This key is invalid. A valid key must consist of alphanumeric characters, '-', '_' or '.'</p
>
<div>
<p ng-if="$ctrl.state.duplicateKeys[index] !== undefined" class="vertical-center">
<pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> This key is already defined.
</p>
</div>
<p ng-if="$ctrl.state.invalidKeys[index]" class="vertical-center">
<pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> This key is invalid. A valid key must consist of alphanumeric characters, '-', '_' or '.'
</p>
</div>
</div>
<div class="form-group" ng-if="$ctrl.formValues.IsSimple && !entry.IsBinary">
<label for="configuration_data_value_{{ index }}" class="col-sm-1 control-label text-left">Value</label>
<div class="col-sm-11">
<label for="configuration_data_value_{{ index }}" class="col-sm-3 col-lg-2 control-label text-left">Value</label>
<div class="col-sm-8">
<textarea
class="form-control"
rows="5"
@@ -79,35 +83,35 @@
required
></textarea>
</div>
<div class="col-sm-11 small text-warning" style="margin-top: 5px" ng-show="kubernetesConfigurationDataCreationForm['configuration_data_value_' + index].$invalid">
<div class="col-sm-11 small text-muted" style="margin-top: 5px" ng-show="kubernetesConfigurationDataCreationForm['configuration_data_value_' + index].$invalid">
<ng-messages for="kubernetesConfigurationDataCreationForm['configuration_data_value_' + index].$error">
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
<p ng-message="required" class="vertical-center"><pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> This field is required.</p>
</ng-messages>
</div>
</div>
<div class="form-group" ng-if="$ctrl.formValues.IsSimple && entry.IsBinary">
<label for="configuration_data_value_{{ index }}" class="col-sm-1 control-label text-left">Value</label>
<div class="col-sm-11 control-label small text-muted text-left"
<label for="configuration_data_value_{{ index }}" class="col-sm-3 col-lg-2 control-label text-left">Value</label>
<div class="col-sm-8 control-label small text-muted text-left"
>Binary data <portainer-tooltip message="'This key holds binary data and cannot be displayed.'"></portainer-tooltip
></div>
</div>
<div class="form-group" ng-if="$ctrl.formValues.IsSimple">
<div class="col-sm-1"></div>
<div class="col-sm-11">
<div class="col-sm-3 col-lg-2"></div>
<div class="col-sm-8">
<button
type="button"
class="btn btn-sm btn-danger space-right"
class="btn btn-sm btn-dangerlight !ml-0"
style="margin-left: 0"
ng-disabled="entry.Used"
ng-click="$ctrl.removeEntry(index, entry)"
data-cy="k8sConfigDetail-removeEntryButton{{ index }}"
>
<i class="fa fa-trash-alt" aria-hidden="true"></i> Remove entry
<pr-icon icon="'trash-2'" feather="true"></pr-icon> Remove entry
</button>
<span class="small text-muted" ng-if="entry.Used">
<i class="fa fa-info-circle blue-icon space-right" aria-hidden="true"></i>
<pr-icon icon="'alert-circle'" feather="true" mode="'primary'"></pr-icon>
This key is currently used by one or more applications
</span>
</div>

View File

@@ -1,3 +1,4 @@
import { Buffer } from 'buffer';
import angular from 'angular';
import _ from 'lodash-es';
import chardet from 'chardet';

View File

@@ -21,11 +21,11 @@
placeholder="# Define or paste the content of your manifest file here"
>
<editor-description>
<p>Templates allow deploying any kind of Kubernetes resource (Deployment, Secret, ConfigMap...)</p>
<p>
<div>Templates allow deploying any kind of Kubernetes resource (Deployment, Secret, ConfigMap...)</div>
<div>
You can get more information about Kubernetes file format in the
<a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/" target="_blank">official documentation</a>.
</p>
</div>
</editor-description>
</web-editor-form>

View File

@@ -389,15 +389,17 @@
<!-- has-override -->
<div class="col-sm-12 form-inline" style="margin-top: 10px" ng-if="config.Overriden">
<div ng-repeat="(keyIndex, overridenKey) in config.OverridenKeys" style="margin-top: 2px">
<div style="margin-top: 2px">
<div class="col-sm-1 input-group input-group-sm" style="margin-left: 3px"></div>
<div class="col-sm-3 input-group input-group-sm">
<span class="input-group-addon">configuration key</span>
<input type="text" class="form-control" ng-value="overridenKey.Key" disabled />
<div class="row">
<div class="col-sm-3 col-lg-2 form-group !m-0"><span>&nbsp;</span></div>
<div class="col-sm-3 form-group" style="margin-left: -11px">
<div class="input-group input-group-sm">
<span class="input-group-addon">configuration key</span>
<input type="text" class="form-control" ng-value="overridenKey.Key" disabled />
</div>
</div>
<div class="col-sm-3 input-group input-group-sm" ng-if="overridenKey.Type === ctrl.ApplicationConfigurationFormValueOverridenKeyTypes.FILESYSTEM">
<div class="col-sm-12 input-group input-group-sm">
<div class="col-sm-3 form-group" ng-if="overridenKey.Type === ctrl.ApplicationConfigurationFormValueOverridenKeyTypes.FILESYSTEM">
<div class="input-group input-group-sm">
<span class="input-group-addon">path on disk</span>
<input
type="text"
@@ -411,44 +413,42 @@
data-cy="k8sAppCreate-pathOnDiskInput"
/>
</div>
</div>
<div class="input-group col-sm-4 btn-group btn-group-sm">
<label class="btn btn-primary" ng-model="overridenKey.Type" uib-btn-radio="ctrl.ApplicationConfigurationFormValueOverridenKeyTypes.ENVIRONMENT">
<pr-icon icon="'list'" feather="true"></pr-icon> Environment
</label>
<label class="btn btn-primary" ng-model="overridenKey.Type" uib-btn-radio="ctrl.ApplicationConfigurationFormValueOverridenKeyTypes.FILESYSTEM">
<pr-icon icon="'file-text'" feather="true"></pr-icon> Filesystem
</label>
</div>
</div>
<div
ng-show="
kubernetesApplicationCreationForm['overriden_key_path_' + index + '_' + keyIndex].$invalid ||
ctrl.state.duplicates.configurationPaths.refs[index + '_' + keyIndex] !== undefined
"
>
<div class="col-sm-1 input-group input-group-sm" style="margin-left: 3px"></div>
<div class="col-sm-3 input-group input-group-sm"></div>
<div class="col-sm-3 input-group input-group-sm" ng-if="overridenKey.Type === ctrl.ApplicationConfigurationFormValueOverridenKeyTypes.FILESYSTEM">
<div
class="small text-warning"
style="margin-top: 5px"
<span
ng-show="
kubernetesApplicationCreationForm['overriden_key_path_' + index + '_' + keyIndex].$invalid ||
ctrl.state.duplicates.configurationPaths.refs[index + '_' + keyIndex] !== undefined
"
>
<ng-messages for="kubernetesApplicationCreationForm['overriden_key_path_' + index + '_' + keyIndex].$error">
<p ng-message="required"><pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Path is required.</p>
</ng-messages>
<p ng-if="ctrl.state.duplicates.configurationPaths.refs[index + '_' + keyIndex] !== undefined"
><pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> This path is already used.</p
>
<div class="input-group input-group-sm text-muted" ng-if="overridenKey.Type === ctrl.ApplicationConfigurationFormValueOverridenKeyTypes.FILESYSTEM">
<div
class="small"
style="margin-top: 5px"
ng-show="
kubernetesApplicationCreationForm['overriden_key_path_' + index + '_' + keyIndex].$invalid ||
ctrl.state.duplicates.configurationPaths.refs[index + '_' + keyIndex] !== undefined
"
>
<ng-messages for="kubernetesApplicationCreationForm['overriden_key_path_' + index + '_' + keyIndex].$error">
<p ng-message="required"><pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Path is required.</p>
</ng-messages>
<p ng-if="ctrl.state.duplicates.configurationPaths.refs[index + '_' + keyIndex] !== undefined"
><pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> This path is already used.</p
>
</div>
</div>
</span>
</div>
<div class="col-sm-4 form-group">
<div class="input-group btn-group btn-group-sm">
<label class="btn btn-light" ng-model="overridenKey.Type" uib-btn-radio="ctrl.ApplicationConfigurationFormValueOverridenKeyTypes.ENVIRONMENT">
<pr-icon icon="'list'" feather="true"></pr-icon> Environment
</label>
<label class="btn btn-light" ng-model="overridenKey.Type" uib-btn-radio="ctrl.ApplicationConfigurationFormValueOverridenKeyTypes.FILESYSTEM">
<pr-icon icon="'file-text'" feather="true"></pr-icon> Filesystem
</label>
</div>
</div>
<div class="col-sm-4 input-group input-group-sm"></div>
</div>
</div>
</div>

View File

@@ -9,35 +9,37 @@
<div ng-if="$ctrl.state.viewReady">
<div class="row">
<div class="col-sm-12">
<div class="col-sm-12 form-section-title"> Information </div>
<p class="text-muted">
<i class="fa fa-flask orange-icon" aria-hidden="true" style="margin-right: 2px"></i>
This is a first version for Helm charts, for more information see this
<a href="https://www.portainer.io/blog/portainer-now-with-helm-support" target="_blank">blog post</a>.
</p>
</div>
<div class="col-sm-12">
<rd-widget>
<rd-widget-header icon="fa-dharmachakra" title-text="Release"></rd-widget-header>
<rd-widget-body classes="no-padding">
<div class="toolBar vertical-center !gap-x-5 !gap-y-1 p-5 flex-wrap w-full">
<div class="toolBarTitle vertical-center">
<pr-icon icon="'svg-helm'" class-name="'icon-nested-blue'" mode="'primary'"></pr-icon>
Release
</div>
</div>
<div class="toolBarTitle text-muted small vertical-center px-5 !gap-0">
<pr-icon icon="'info'" feather="true" mode="'primary'" class-name="'!mr-1'" class="vertical-center"></pr-icon>
This is a first version for Helm charts, for more information see this&nbsp;
<a href="https://www.portainer.io/blog/portainer-now-with-helm-support" target="_blank" class="text-blue-8 hover:text-blue-8 hover:underline">blog post</a>.
</div>
<rd-widget-body>
<table class="table">
<tbody class="release-table">
<tr>
<td>Name</td>
<td>
<td class="vertical-center !pl-0">Name</td>
<td class="vertical-center !p-2">
{{ $ctrl.state.release.name }}
</td>
</tr>
<tr>
<td>Chart</td>
<td>
<td class="vertical-center !pl-0">Chart</td>
<td class="vertical-center !p-2">
{{ $ctrl.state.release.chart }}
</td>
</tr>
<tr>
<td>App version</td>
<td>
<td class="vertical-center !pl-0">App version</td>
<td class="vertical-center !p-2">
{{ $ctrl.state.release.app_version }}
</td>
</tr>

View File

@@ -15,8 +15,8 @@
<form class="form-horizontal" name="kubernetesConfigurationCreationForm" autocomplete="off">
<!-- name -->
<div class="form-group">
<label for="configuration_name" class="col-sm-1 control-label text-left">Name</label>
<div class="col-sm-11">
<label for="configuration_name" class="col-sm-3 col-lg-2 control-label text-left">Name</label>
<div class="col-sm-8">
<input
type="text"
class="form-control"
@@ -32,16 +32,16 @@
</div>
</div>
<div class="form-group" ng-show="kubernetesConfigurationCreationForm.configuration_name.$invalid || ctrl.state.alreadyExist">
<div class="col-sm-12 small text-warning">
<div class="col-sm-12 small text-muted">
<div ng-messages="kubernetesConfigurationCreationForm.configuration_name.$error">
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
<p ng-message="pattern"
><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field must consist of lower case alphanumeric characters, '-' or '.', and contain at most 63
characters, and must start and end with an alphanumeric character.</p
<p ng-message="required" class="vertical-center"><pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> This field is required.</p>
<p ng-message="pattern" class="vertical-center"
><pr-icon icon="'alert-triangle'" feather="true" mode="'warning'" class="vertical-center"></pr-icon> This field must consist of lower case alphanumeric
characters, '-' or '.', and contain at most 63 characters, and must start and end with an alphanumeric character.</p
>
</div>
<p ng-if="ctrl.state.alreadyExist"
><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> A configuration with the same name already exists inside the selected namespace.</p
<p ng-if="ctrl.state.alreadyExist" class="vertical-center"
><pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> A configuration with the same name already exists inside the selected namespace.</p
>
</div>
</div>
@@ -51,8 +51,8 @@
<!-- resource-pool -->
<div class="form-group" ng-if="ctrl.formValues.ResourcePool">
<label for="resource-pool-selector" class="col-sm-1 control-label text-left">Namespace</label>
<div class="col-sm-11">
<label for="resource-pool-selector" class="col-sm-3 col-lg-2 control-label text-left">Namespace</label>
<div class="col-sm-8">
<select
class="form-control"
id="resource-pool-selector"
@@ -64,19 +64,20 @@
</div>
</div>
<div class="form-group" ng-if="ctrl.state.resourcePoolHasQuota && ctrl.resourceQuotaCapacityExceeded()">
<div class="col-sm-12 small text-warning">
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true" style="margin-right: 2px"></i>
<div class="col-sm-12 small text-muted vertical-center">
<pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon>
This namespace has exhausted its resource capacity and you will not be able to deploy the configuration. Contact your administrator to expand the capacity of the
namespace.
</div>
</div>
<div class="form-group" ng-if="!ctrl.formValues.ResourcePool">
<div class="col-sm-12 small text-muted">
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px"></i>
<div class="col-sm-12 small text-muted vertical-center">
<pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon>
You do not have access to any namespace. Contact your administrator to get access to a namespace.
</div>
</div>
<!-- !resource-pool -->
<div ng-if="ctrl.formValues.ResourcePool">
<div class="col-sm-12 form-section-title"> Configuration type </div>
@@ -126,21 +127,21 @@
is-creation="true"
is-editor-dirty="ctrl.state.isEditorDirty"
></kubernetes-configuration-data>
<!-- summary -->
<kubernetes-summary-view
ng-if="!(!kubernetesConfigurationCreationForm.$valid || !ctrl.isFormValid() || ctrl.state.actionInProgress)"
form-values="ctrl.formValues"
></kubernetes-summary-view>
</div>
<!-- summary -->
<kubernetes-summary-view
ng-if="!(!kubernetesConfigurationCreationForm.$valid || !ctrl.isFormValid() || ctrl.state.actionInProgress)"
form-values="ctrl.formValues"
></kubernetes-summary-view>
<!-- actions -->
<div class="col-sm-12 form-section-title" style="margin-top: 10px"> Actions </div>
<div class="form-group">
<div class="col-sm-12">
<button
type="button"
class="btn btn-primary btn-sm"
class="btn btn-primary btn-sm !ml-0"
ng-disabled="!kubernetesConfigurationCreationForm.$valid || !ctrl.isFormValid() || ctrl.state.actionInProgress || !ctrl.formValues.ResourcePool"
ng-click="ctrl.createConfiguration()"
button-spinner="ctrl.state.actionInProgress"

View File

@@ -6,22 +6,27 @@
<div class="row" ng-if="ctrl.endpoint">
<div class="col-sm-12">
<rd-widget>
<rd-widget-header icon="fa-tachometer-alt" title-text="Environment info"></rd-widget-header>
<rd-widget-body classes="no-padding">
<div class="toolBar vertical-center w-full">
<div class="toolBarTitle vertical-center p-5">
<pr-icon icon="'svg-tachometer'" class-name="'icon-nested-blue [&>svg]:!h-5 [&>svg]:!w-5'" mode="'primary'" class="vertical-center"></pr-icon>
Environment info
</div>
</div>
<rd-widget-body classes="!px-5 !py-0">
<table class="table">
<tbody>
<tr>
<td>Environment</td>
<td>
<td class="!pl-0 !border-none">Environment</td>
<td class="!border-none">
{{ ctrl.endpoint.Name }}
</td>
</tr>
<tr ng-if="ctrl.showEnvUrl">
<td>URL</td>
<td>{{ ctrl.endpoint.URL | stripprotocol }}</td>
<td class="!pl-0 !border-t">URL</td>
<td class="!border-t">{{ ctrl.endpoint.URL | stripprotocol }}</td>
</tr>
<tr>
<td>Tags</td>
<td class="!pl-0">Tags</td>
<td>{{ ctrl.endpointTags }}</td>
</tr>
</tbody>
@@ -34,23 +39,23 @@
<div class="dashboard-grid mx-4">
<div ng-if="ctrl.pools" data-cy="k8sDashboard-namespaces">
<a ui-sref="kubernetes.resourcePools">
<dashboard-item icon="'fa-layer-group'" type="'Namespace'" value="ctrl.pools.length"></dashboard-item>
<dashboard-item feather-icon="true" icon="'layers'" type="'Namespace'" value="ctrl.pools.length"></dashboard-item>
</a>
</div>
<div ng-if="ctrl.applications" data-cy="k8sDashboard-applications">
<a ui-sref="kubernetes.applications">
<dashboard-item icon="'fa-laptop-code'" type="'Application'" value="ctrl.applications.length"></dashboard-item>
<dashboard-item feather-icon="true" icon="'box'" type="'Application'" value="ctrl.applications.length"></dashboard-item>
</a>
</div>
<div ng-if="ctrl.configurations" data-cy="k8sDashboard-configurations">
<a ui-sref="kubernetes.configurations">
<dashboard-item icon="'fa-file-code'" type="'Configuration'" value="ctrl.configurations.length"></dashboard-item>
<dashboard-item feather-icon="true" icon="'lock'" type="'Configuration'" value="ctrl.configurations.length"></dashboard-item>
</a>
</div>
<div ng-if="ctrl.volumes" data-cy="k8sDashboard-volumes">
<a ui-sref="kubernetes.volumes">
<dashboard-item icon="'fa-database'" type="'Volume'" value="ctrl.volumes.length"></dashboard-item>
<dashboard-item feather-icon="true" icon="'database'" type="'Volume'" value="ctrl.volumes.length"></dashboard-item>
</a>
</div>
</div>

View File

@@ -13,7 +13,7 @@
<uib-tab index="0">
<uib-tab-heading> <pr-icon icon="'code'" feather="true"></pr-icon> Deploy </uib-tab-heading>
<div class="col-sm-12 form-section-title"> Namespace </div>
<form class="form-horizontal" style="margin-top: 20px" name="deploymentForm">
<form class="form-horizontal mt-3" name="deploymentForm">
<div class="form-group" ng-if="ctrl.formValues.Namespace">
<label for="target_node" class="col-lg-2 col-sm-3 control-label text-left">Namespace</label>
<div class="col-sm-8">
@@ -31,16 +31,18 @@
<portainer-tooltip message="'If you have defined namespaces in your deployment file turning this on will enforce the use of those only in the deployment'">
</portainer-tooltip>
</label>
<div class="col-sm-8">
<div class="col-sm-8 vertical-center pt-3">
<label class="switch">
<input type="checkbox" name="toggle_logo" ng-model="ctrl.formValues.namespace_toggle" />
<i></i>
<span class="slider round"></span>
</label>
<!-- <span class="ml-2 mb-1 switch-values" ng-if="ctrl.formValues.namespace_toggle">Yes</span>
<span class="ml-2 mb-1 switch-values" ng-if="!ctrl.formValues.namespace_toggle">No</span> -->
</div>
</div>
<div class="form-group" ng-if="!ctrl.formValues.Namespace">
<div class="col-sm-12 small text-muted">
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px"></i>
<pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon>
You do not have access to any namespace. Contact your administrator to get access to a namespace.
</div>
</div>
@@ -103,40 +105,41 @@
</div>
<!-- editor -->
<web-editor-form
ng-if="ctrl.state.BuildMethod === ctrl.BuildMethods.WEB_EDITOR || (ctrl.state.BuildMethod === ctrl.BuildMethods.CUSTOM_TEMPLATE && ctrl.state.templateId)"
identifier="kubernetes-deploy-editor"
value="ctrl.formValues.EditorContent"
on-change="(ctrl.onChangeFileContent)"
ng-required="true"
yml="true"
placeholder="# Define or paste the content of your manifest file here"
>
<editor-description>
<span class="col-sm-12 text-muted small" ng-show="ctrl.state.DeployType === ctrl.ManifestDeployTypes.COMPOSE">
<p>
<pr-icon icon="'alert-circle'" mode="'warning'" feather="true"></pr-icon>
Portainer uses <a href="https://kompose.io/" target="_blank">Kompose</a> to convert your Compose manifest to a Kubernetes compliant manifest. Be wary that
not all the Compose format options are supported by Kompose at the moment.
</p>
<p>
You can get more information about Compose file format in the
<a href="https://docs.docker.com/compose/compose-file/" target="_blank">official documentation</a>.
</p>
</span>
<span class="col-sm-12 text-muted small" ng-show="ctrl.state.DeployType === ctrl.ManifestDeployTypes.KUBERNETES">
<p>
<pr-icon icon="'alert-circle'" mode="'primary'" feather="true"></pr-icon>
This feature allows you to deploy any kind of Kubernetes resource in this environment (Deployment, Secret, ConfigMap...).
</p>
<p>
You can get more information about Kubernetes file format in the
<a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/" target="_blank">official documentation</a>.
</p>
</span>
</editor-description>
</web-editor-form>
<div class="mt-4">
<web-editor-form
ng-if="ctrl.state.BuildMethod === ctrl.BuildMethods.WEB_EDITOR || (ctrl.state.BuildMethod === ctrl.BuildMethods.CUSTOM_TEMPLATE && ctrl.state.templateId)"
identifier="kubernetes-deploy-editor"
value="ctrl.formValues.EditorContent"
on-change="(ctrl.onChangeFileContent)"
ng-required="true"
yml="true"
placeholder="# Define or paste the content of your manifest file here"
>
<editor-description>
<span class="col-sm-12 text-muted small" ng-show="ctrl.state.DeployType === ctrl.ManifestDeployTypes.COMPOSE">
<p>
<pr-icon icon="'alert-circle'" mode="'warning'" feather="true"></pr-icon>
Portainer uses <a href="https://kompose.io/" target="_blank">Kompose</a> to convert your Compose manifest to a Kubernetes compliant manifest. Be wary that
not all the Compose format options are supported by Kompose at the moment.
</p>
<p>
You can get more information about Compose file format in the
<a href="https://docs.docker.com/compose/compose-file/" target="_blank">official documentation</a>.
</p>
</span>
<span class="col-sm-12 text-muted small" ng-show="ctrl.state.DeployType === ctrl.ManifestDeployTypes.KUBERNETES">
<p>
<pr-icon icon="'alert-circle'" mode="'primary'" feather="true"></pr-icon>
This feature allows you to deploy any kind of Kubernetes resource in this environment (Deployment, Secret, ConfigMap...).
</p>
<p>
You can get more information about Kubernetes file format in the
<a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/" target="_blank">official documentation</a>.
</p>
</span>
</editor-description>
</web-editor-form>
</div>
<!-- !editor -->
<!-- url -->
@@ -188,7 +191,7 @@
<uib-tab index="1" disable="ctrl.state.tabLogsDisabled">
<uib-tab-heading> <pr-icon icon="'file-text'" feather="true"></pr-icon> Logs </uib-tab-heading>
<form class="form-horizontal" style="margin-top: 20px">
<form class="form-horizontal mt-3">
<div class="form-group" ng-if="ctrl.state.activeTab === 1">
<div class="col-sm-12">
<code-editor identifier="kubernetes-deploy-logs" read-only="true" yml="false" value="ctrl.errorLog"></code-editor>

View File

@@ -27,15 +27,15 @@ class KubernetesDeployController {
this.isTemplateVariablesEnabled = isBE;
this.deployOptions = [
buildOption('method_kubernetes', 'fa fa-cubes', 'Kubernetes', 'Kubernetes manifest format', KubernetesDeployManifestTypes.KUBERNETES),
buildOption('method_compose', 'fab fa-docker', 'Compose', 'Docker compose format', KubernetesDeployManifestTypes.COMPOSE),
buildOption('method_kubernetes', 'svg-kubernetes', 'Kubernetes', 'Kubernetes manifest format', KubernetesDeployManifestTypes.KUBERNETES),
buildOption('method_compose', 'svg-dockercompose', 'Compose', 'Docker compose format', KubernetesDeployManifestTypes.COMPOSE),
];
this.methodOptions = [
buildOption('method_repo', 'fab fa-github', 'Git Repository', 'Use a git repository', KubernetesDeployBuildMethods.GIT),
buildOption('method_editor', 'fa fa-edit', 'Web editor', 'Use our Web editor', KubernetesDeployBuildMethods.WEB_EDITOR),
buildOption('method_url', 'fa fa-globe', 'URL', 'Specify a URL to a file', KubernetesDeployBuildMethods.URL),
buildOption('method_template', 'fa fa-rocket', 'Custom Template', 'Use a custom template', KubernetesDeployBuildMethods.CUSTOM_TEMPLATE),
buildOption('method_repo', 'svg-git', 'Git Repository', 'Use a git repository', KubernetesDeployBuildMethods.GIT),
buildOption('method_editor', 'svg-custom', 'Web editor', 'Use our Web editor', KubernetesDeployBuildMethods.WEB_EDITOR),
buildOption('method_url', 'svg-url', 'URL', 'Specify a URL to a file', KubernetesDeployBuildMethods.URL),
buildOption('method_template', 'svg-template', 'Custom Template', 'Use a custom template', KubernetesDeployBuildMethods.CUSTOM_TEMPLATE),
];
this.state = {

View File

@@ -1,8 +1,9 @@
<div class="form-group">
<div class="col-sm-2">
<div class="col-sm-12">
<por-switch-field
data-cy="'k8sNamespaceCreate-enableQuotaToggle'"
label="'Enable quota'"
label-class="'col-sm-3 col-lg-2'"
name="'k8s-resourcepool-storagequota'"
feature-id="$ctrl.featureId"
checked="$ctrl.value"

View File

@@ -15,8 +15,8 @@
<form class="form-horizontal" autocomplete="off" name="resourcePoolCreationForm">
<!-- #region NAME INPUT -->
<div class="form-group">
<label for="pool_name" class="col-sm-1 control-label text-left">Name</label>
<div class="col-sm-11">
<label for="pool_name" class="col-sm-3 col-lg-2 control-label text-left required">Name</label>
<div class="col-sm-8">
<input
type="text"
class="form-control"
@@ -29,20 +29,25 @@
required
auto-focus
/>
<span class="help-block">
<div class="form-group" ng-show="resourcePoolCreationForm.pool_name.$invalid || $ctrl.state.isAlreadyExist || $ctrl.state.hasPrefixKube">
<div class="col-sm-12 small text-muted">
<div ng-messages="resourcePoolCreationForm.pool_name.$error">
<p class="vertical-center" ng-message="required"><pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon>This field is required.</p>
<p class="vertical-center" ng-message="pattern"
><pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> This field must consist of lower case alphanumeric characters or '-', and
contain at most 63 characters, and must start and end with an alphanumeric character.</p
>
</div>
<p class="vertical-center" ng-if="$ctrl.state.hasPrefixKube"
><pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> Prefix "kube-" is reserved for Kubernetes system namespaces.</p
>
</div>
</div>
</span>
</div>
</div>
<div class="form-group" ng-show="resourcePoolCreationForm.pool_name.$invalid || $ctrl.state.isAlreadyExist || $ctrl.state.hasPrefixKube">
<div class="col-sm-12 small text-warning">
<div ng-messages="resourcePoolCreationForm.pool_name.$error">
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
<p ng-message="pattern"
><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field must consist of lower case alphanumeric characters or '-', and contain at most 63
characters, and must start and end with an alphanumeric character.</p
>
</div>
<p ng-if="$ctrl.state.hasPrefixKube"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Prefix "kube-" is reserved for Kubernetes system namespaces.</p>
</div>
</div>
<!-- #endregion -->
<div class="col-sm-12 form-section-title"> Quota </div>
@@ -50,31 +55,39 @@
<!-- quotas-switch -->
<div class="form-group">
<div class="col-sm-12 small text-muted">
<p>
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px"></i>
<p class="vertical-center">
<pr-icon class="vertical-center" icon="'info'" feather="true" mode="'primary'"></pr-icon>
A namespace segments the underlying physical Kubernetes cluster into smaller virtual clusters. You should assign a capped limit of resources to this namespace or
disable for the safe operation of your platform.
</p>
</div>
<div class="col-sm-12">
<label class="control-label text-left"> Resource assignment </label>
<label class="switch" style="margin-left: 20px">
<input type="checkbox" ng-model="$ctrl.formValues.HasQuota" /><i data-cy="k8sNamespaceCreate-resourceAssignmentToggle"></i>
</label>
<por-switch-field
data-cy="'k8sNamespaceCreate-resourceAssignmentToggle'"
label="'Resource assignment'"
label-class="'col-sm-3 col-lg-2'"
name="'k8s-resourcepool-resourcequota'"
checked="$ctrl.formValues.HasQuota"
on-change="($ctrl.onToggleResourceQuota)"
></por-switch-field>
</div>
</div>
<div class="form-group" ng-if="$ctrl.formValues.HasQuota && !$ctrl.isQuotaValid()">
<span class="col-sm-12 text-warning small">
<p> <i class="fa fa-exclamation-triangle" aria-hidden="true" style="margin-right: 2px"></i> At least a single limit must be set for the quota to be valid. </p>
</span>
</div>
<!-- !quotas-switch -->
<div ng-if="$ctrl.formValues.HasQuota">
<div class="col-sm-12 form-section-title"> Resource limits </div>
<div>
<div class="form-group" ng-if="$ctrl.formValues.HasQuota && !$ctrl.isQuotaValid()">
<span class="col-sm-12 small text-muted">
<p class="vertical-center"
><pr-icon class="vertical-center" icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> At least a single limit must be set for the quota to be
valid.
</p>
</span>
</div>
<!-- memory-limit-input -->
<div class="form-group">
<label for="memory-limit" class="col-sm-3 col-lg-2 control-label text-left" style="margin-top: 20px"> Memory </label>
<label for="memory-limit" class="col-sm-3 col-lg-2 control-label text-left"> Memory </label>
<div class="col-sm-3">
<slider
model="$ctrl.formValues.MemoryLimit"
@@ -98,24 +111,28 @@
data-cy="k8sNamespaceCreate-memoryLimitInput"
required
/>
<span class="help-block">
<div class="form-group" ng-show="resourcePoolCreationForm.memory_limit.$invalid">
<div class="col-sm-12 small text-muted">
<div ng-messages="resourcePoolCreationForm.pool_name.$error">
<p class="vertical-center"
><pr-icon class="vertical-center" icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> Value must be between
{{ $ctrl.defaults.MemoryLimit }} and
{{ $ctrl.state.sliderMaxMemory }}
</p>
</div>
</div>
</div>
</span>
</div>
<div class="col-sm-4">
<p class="small text-muted" style="margin-top: 7px"> Maximum memory usage (<b>MB</b>) </p>
</div>
</div>
<div class="form-group" ng-show="resourcePoolCreationForm.memory_limit.$invalid">
<div class="col-sm-12 small text-warning">
<div ng-messages="resourcePoolCreationForm.pool_name.$error">
<p
><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Value must be between {{ $ctrl.defaults.MemoryLimit }} and {{ $ctrl.state.sliderMaxMemory }}
</p>
</div>
<p class="small text-muted"> Maximum memory usage (MB) </p>
</div>
</div>
<!-- !memory-limit-input -->
<!-- cpu-limit-input -->
<div class="form-group">
<label for="cpu-limit" class="col-sm-3 col-lg-2 control-label text-left" style="margin-top: 20px"> CPU </label>
<label for="cpu-limit" class="col-sm-3 col-lg-2 control-label text-left"> CPU </label>
<div class="col-sm-5">
<slider
model="$ctrl.formValues.CpuLimit"
@@ -128,7 +145,7 @@
>
</slider>
</div>
<div class="col-sm-4" style="margin-top: 20px">
<div class="col-sm-4">
<p class="small text-muted"> Maximum CPU usage </p>
</div>
</div>
@@ -141,8 +158,8 @@
<div class="col-sm-12 form-section-title"> Load balancers </div>
<div class="form-group">
<span class="col-sm-12 text-muted small">
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px"></i>
<span class="col-sm-12 text-muted small vertical-center">
<pr-icon icon="'info'" feather="true" mode="'primary'"></pr-icon>
You can set a quota on the amount of external load balancers that can be created inside this namespace. Set this quota to 0 to effectively disable the use of load
balancers in this namespace.
</span>
@@ -152,6 +169,7 @@
<por-switch-field
data-cy="'k8sNamespaceCreate-loadBalancerQuotaToggle'"
label="'Load Balancer quota'"
label-class="'col-sm-3 col-lg-2'"
name="'k8s-resourcepool-lbquota'"
feature-id="$ctrl.LBQuotaFeatureId"
checked="$ctrl.formValues.UseLoadBalancersQuota"
@@ -165,14 +183,14 @@
<div class="col-sm-12 form-section-title"> Storage </div>
<div class="form-group">
<span class="col-sm-12 text-muted small">
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px"></i>
<span class="col-sm-12 text-muted small vertical-center">
<pr-icon icon="'info'" feather="true" mode="'primary'"></pr-icon>
Quotas can be set on each storage option to prevent users from exceeding a specific threshold when deploying applications. You can set a quota to 0 to effectively
prevent the usage of a specific storage option inside this namespace.
</span>
</div>
<div class="col-sm-12 form-section-title">
<i class="fa fa-route" aria-hidden="true" style="margin-right: 2px"></i>
<div class="col-sm-12 form-section-title vertical-center">
<pr-icon icon="'svg-route'"></pr-icon>
standard
</div>
@@ -192,25 +210,30 @@
<div class="form-group" ng-if="$ctrl.formValues.IngressClasses.length > 0">
<div class="col-sm-12 small text-muted">
<p>
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px"></i>
<p class="vertical-center">
<pr-icon icon="'info'" feather="true" mode="'primary'"></pr-icon>
Enable and configure ingresses available to users when deploying applications.
</p>
</div>
</div>
<div class="form-group" ng-repeat-start="ic in $ctrl.formValues.IngressClasses track by ic.IngressClass.Name">
<div class="text-muted col-sm-12" style="width: 100%">
<div style="border-bottom: 1px solid #cdcdcd; padding-bottom: 5px">
<i class="fa fa-route" aria-hidden="true" style="margin-right: 2px"></i> {{ ic.IngressClass.Name }}
<div class="row">
<div class="col-sm-12">
<span class="text-muted vertical-center"><pr-icon icon="'svg-route'"></pr-icon> {{ ic.IngressClass.Name }}</span>
<hr class="mt-2 mb-0" />
</div>
</div>
<div class="col-sm-12" style="margin-top: 10px">
<label class="control-label text-left"> Allow users to use this ingress </label>
<label class="switch" style="margin-left: 20px">
<input type="checkbox" ng-model="ic.Selected" /><i data-cy="namespaceCreate-ingressToggle{{ ic.IngressClass.Name }}"></i>
</label>
<div class="row">
<div class="col-sm-3 col-lg-2">
<label class="control-label text-left"> Allow users to use this ingress </label>
</div>
<div class="col-sm-8 pt-2">
<label class="switch">
<input type="checkbox" ng-model="ic.Selected" /><span class="slider round" data-cy="namespaceCreate-ingressToggle{{ ic.IngressClass.Name }}"></span>
</label>
</div>
</div>
</div>
@@ -224,20 +247,15 @@
>
</portainer-tooltip>
</label>
<span
class="label label-default interactive"
style="margin-left: 10px"
ng-click="$ctrl.addHostname(ic)"
data-cy="namespaceCreate-addHostButton{{ ic.IngressClass.Name }}"
>
<i class="fa fa-plus-circle" aria-hidden="true"></i> add hostname
<span class="label label-default interactive" ng-click="$ctrl.addHostname(ic)" data-cy="namespaceCreate-addHostButton{{ ic.IngressClass.Name }}">
<pr-icon icon="'plus'" feather="true"></pr-icon> add hostname
</span>
</div>
<div class="col-sm-12" style="margin-top: 10px">
<div ng-repeat="item in ic.Hosts track by $index" style="margin-top: 2px">
<div class="col-sm-12 pt-4">
<div ng-repeat="item in ic.Hosts track by $index">
<div class="form-inline">
<div class="col-sm-10 input-group input-group-sm">
<span class="input-group-addon">Hostname</span>
<div class="col-sm-8 input-group input-group-sm pt-2">
<span class="input-group-addon required">Hostname</span>
<input
type="text"
class="form-control"
@@ -251,87 +269,85 @@
/>
</div>
<div class="col-sm-1 input-group input-group-sm" ng-if="$index > 0">
<button class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeHostname(ic, $index)">
<i class="fa fa-trash-alt" aria-hidden="true"></i>
<button class="btn btn-md btn-light btn-only-icon !h-[30px]" type="button" ng-click="$ctrl.removeHostname(ic, $index)">
<pr-icon icon="'trash-2'" size="'md'" feather="true"></pr-icon>
</button>
</div>
</div>
<div
class="small text-warning"
style="margin-top: 5px"
class="small text-muted pt-1"
ng-show="
resourcePoolCreationForm['hostname_' + ic.IngressClass.Name + '_' + $index].$invalid ||
$ctrl.state.duplicates.ingressHosts.refs[ic.IngressClass.Name][$index] !== undefined
"
>
<ng-messages for="resourcePoolCreationForm['hostname_' + ic.IngressClass.Name + '_' + $index].$error">
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Hostname is required.</p>
<p ng-message="required"><pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> Hostname is required.</p>
<p ng-message="pattern">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
<pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon>
This field must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com').
</p>
</ng-messages>
<p ng-if="$ctrl.state.duplicates.ingressHosts.refs[ic.IngressClass.Name][$index] !== undefined">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This hostname is already used.
<pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> This hostname is already used.
</p>
</div>
</div>
</div>
</div>
</div>
<div ng-repeat-end class="form-group" ng-if="ic.Selected" style="margin-bottom: 20px">
<div class="col-sm-12 small text-muted" style="margin-top: 5px">
<p>
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px"></i>
<div ng-repeat-end class="form-group" ng-if="ic.Selected">
<div class="col-sm-12 small text-muted">
<p class="vertical-center">
<pr-icon icon="'info'" feather="true" mode="'primary'"></pr-icon>
You can specify a list of annotations that will be associated to the ingress.
</p>
</div>
<div class="col-sm-12">
<label class="control-label text-left">Annotations</label>
<span
class="label label-default interactive"
style="margin-left: 10px"
ng-click="$ctrl.addAnnotation(ic)"
data-cy="namespaceCreate-addAnnotation{{ ic.IngressClass.Name }}"
>
<i class="fa fa-plus-circle" aria-hidden="true"></i> add annotation
</span>
<portainer-tooltip
message="'Use annotations to configure options for an ingress. Review Nginx or Traefik documentation to find the annotations supported by your choice of ingress type.'"
>
</portainer-tooltip>
<span
class="label label-default interactive"
ng-if="ic.IngressClass.Type === $ctrl.IngressClassTypes.NGINX"
style="margin-left: 10px"
ng-click="$ctrl.addRewriteAnnotation(ic)"
data-cy="namespaceCreate-addAnnotation{{ ic.IngressClass.Name }}"
>
<i class="fa fa-plus-circle" aria-hidden="true"></i> add rewrite annotation
</span>
<portainer-tooltip
ng-if="ic.IngressClass.Type === $ctrl.IngressClassTypes.NGINX"
message="'When the exposed URLs for your applications differ from the specified paths in the ingress, use the rewrite target annotation to denote the path to redirect to.'"
>
</portainer-tooltip>
<span
class="label label-default interactive"
ng-if="ic.IngressClass.Type === $ctrl.IngressClassTypes.NGINX"
style="margin-left: 10px"
ng-click="$ctrl.addUseregexAnnotation(ic)"
data-cy="namespaceCreate-addAnnotation{{ ic.IngressClass.Name }}"
>
<i class="fa fa-plus-circle" aria-hidden="true"></i> add regular expression annotation
</span>
<portainer-tooltip
ng-if="ic.IngressClass.Type === $ctrl.IngressClassTypes.NGINX"
message="'Enable use of regular expressions in ingress paths (set in the ingress details of an application). Use this along with rewrite-target to specify the regex capturing group to be replaced, e.g. path regex of ^/foo/(,*) and rewrite-target of /bar/$1 rewrites example.com/foo/account to example.com/bar/account.'"
>
</portainer-tooltip>
<label class="control-label text-left">Annotations </label>
<label class="control-label text-left">
<span class="label label-default interactive" ng-click="$ctrl.addAnnotation(ic)" data-cy="namespaceCreate-addAnnotation{{ ic.IngressClass.Name }}">
<pr-icon icon="'plus'" feather="true"></pr-icon> add annotation
</span>
<portainer-tooltip
message="'Use annotations to configure options for an ingress. Review Nginx or Traefik documentation to find the annotations supported by your choice of ingress type.'"
>
</portainer-tooltip>
</label>
<label class="control-label text-left">
<span
class="label label-default interactive"
ng-if="ic.IngressClass.Type === $ctrl.IngressClassTypes.NGINX"
ng-click="$ctrl.addRewriteAnnotation(ic)"
data-cy="namespaceCreate-addAnnotation{{ ic.IngressClass.Name }}"
>
<pr-icon icon="'plus'" feather="true"></pr-icon> add rewrite annotation
</span>
<portainer-tooltip
ng-if="ic.IngressClass.Type === $ctrl.IngressClassTypes.NGINX"
message="'When the exposed URLs for your applications differ from the specified paths in the ingress, use the rewrite target annotation to denote the path to redirect to.'"
>
</portainer-tooltip>
</label>
<label class="control-label text-left">
<span
class="label label-default interactive"
ng-if="ic.IngressClass.Type === $ctrl.IngressClassTypes.NGINX"
ng-click="$ctrl.addUseregexAnnotation(ic)"
data-cy="namespaceCreate-addAnnotation{{ ic.IngressClass.Name }}"
>
<pr-icon icon="'plus'" feather="true"></pr-icon> add regular expression annotation
</span>
<portainer-tooltip
ng-if="ic.IngressClass.Type === $ctrl.IngressClassTypes.NGINX"
message="'Enable use of regular expressions in ingress paths (set in the ingress details of an application). Use this along with rewrite-target to specify the regex capturing group to be replaced, e.g. path regex of ^/foo/(,*) and rewrite-target of /bar/$1 rewrites example.com/foo/account to example.com/bar/account.'"
>
</portainer-tooltip>
</label>
</div>
<div class="col-sm-12 form-inline" style="margin-top: 10px">
<div ng-repeat="annotation in ic.Annotations track by $index" style="margin-top: 2px">
<div class="col-sm-12 form-inline pt-4">
<div class="pt-2" ng-repeat="annotation in ic.Annotations track by $index">
<div class="input-group col-sm-5 input-group-sm">
<span class="input-group-addon">Key</span>
<input
@@ -347,7 +363,7 @@
data-cy="namespaceCreate-annotationKey{{ ic.IngressClass.Name }}"
/>
</div>
<div class="input-group col-sm-5 input-group-sm">
<div class="input-group input-group-sm col-sm-5">
<span class="input-group-addon">Value</span>
<input
type="text"
@@ -358,14 +374,14 @@
data-cy="namespaceCreate-annotationValue{{ ic.IngressClass.Name }}"
/>
</div>
<div class="col-sm-1 input-group input-group-sm">
<div class="input-group input-group-sm col-sm-1">
<button
class="btn btn-sm btn-danger"
class="btn btn-sm btn-light btn-only-icon !h-[30px]"
type="button"
ng-click="$ctrl.removeAnnotation(ic, $index)"
data-cy="namespaceCreate-deleteAnnotationButton{{ ic.IngressClass.Name }}"
>
<i class="fa fa-trash-alt" aria-hidden="true"></i>
<pr-icon icon="'trash-2'" size="'md'" feather="true"></pr-icon>
</button>
</div>
</div>
@@ -378,15 +394,15 @@
<div class="col-sm-12 form-section-title"> Registries </div>
<div class="form-group">
<div class="col-sm-12 small text-muted">
<p>
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px"></i>
Define which registry can be used by users who have access to this namespace.
<p class="vertical-center">
<pr-icon icon="'info'" feather="true" mode="'primary'"></pr-icon>
Define which registries can be used by users who have access to this namespace.
</p>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 col-lg-2 control-label text-left" style="padding-top: 0"> Select registries </label>
<label class="col-sm-3 col-lg-2 control-label text-left"> Select registries </label>
<div class="col-sm-9 col-lg-4">
<span class="small text-muted" ng-if="!$ctrl.registries.length && $ctrl.state.isAdmin">
No registries available. Head over <a ui-sref="portainer.registries">registry view</a> to define container registry.
@@ -404,7 +420,7 @@
tick-property="Checked"
helper-elements="filter"
search-property="Name"
translation="{nothingSelected: 'Select one or more registry', search: 'Search...'}"
translation="{nothingSelected: 'Select one or more registries', search: 'Search...'}"
data-cy="namespaceCreate-registrySelect"
>
</span>
@@ -422,7 +438,7 @@
<div class="col-sm-12">
<button
type="button"
class="btn btn-primary btn-sm"
class="btn btn-primary btn-sm !ml-0"
ng-disabled="!resourcePoolCreationForm.$valid || $ctrl.isCreateButtonDisabled()"
ng-click="$ctrl.createResourcePool()"
button-spinner="$ctrl.state.actionInProgress"

View File

@@ -38,6 +38,7 @@ class KubernetesCreateResourcePoolController {
this.onToggleStorageQuota = this.onToggleStorageQuota.bind(this);
this.onToggleLoadBalancerQuota = this.onToggleLoadBalancerQuota.bind(this);
this.onToggleResourceQuota = this.onToggleResourceQuota.bind(this);
}
/* #endregion */
@@ -53,6 +54,12 @@ class KubernetesCreateResourcePoolController {
});
}
onToggleResourceQuota(enabled) {
this.$scope.$evalAsync(() => {
this.formValues.HasQuota = enabled;
});
}
onChangeIngressHostname() {
const state = this.state.duplicates.ingressHosts;
const hosts = _.flatMap(this.formValues.IngressClasses, 'Hosts');

View File

@@ -13,12 +13,9 @@
<div ng-if="state.viewReady">
<div class="row">
<div class="col-sm-12 space-left">
<i class="fas fa-shield-alt"></i>
Pod security constraints</div
>
<div class="col-sm-12">
<rd-widget>
<rd-widget-header icon="shield" feather-icon="true" title-text="Pod security constraints"></rd-widget-header>
<rd-widget-body>
<form class="form-horizontal" name="kubernetesSecurityConstraintForm">
<!-- main toggle -->
@@ -29,6 +26,8 @@
name="'disableSysctlSettingForRegularUsers'"
label="'Enable pod security constraints'"
feature-id="limitedFeaturePodSecurityPolicy"
label-class="'col-sm-3 col-lg-2 px-0'"
switch-class="'col-sm-8'"
>
</por-switch-field>
</div>

View File

@@ -24,8 +24,10 @@
<rd-widget-body classes="no-padding">
<uib-tabset active="ctrl.state.activeTab" justified="true" type="pills">
<uib-tab index="0" classes="btn-sm" select="ctrl.selectTab(0)">
<uib-tab-heading data-cy="k8sVolDetail-volTab"> <i class="fa fa-database space-right" aria-hidden="true"></i> Volume </uib-tab-heading>
<uib-tab-heading class="vertical-center" data-cy="k8sVolDetail-volTab">
<pr-icon icon="'database'" feather="true"></pr-icon>
Volume
</uib-tab-heading>
<div style="padding: 20px">
<table class="table">
<tbody>
@@ -122,11 +124,11 @@
<div class="form-inline">
<div class="small text-warning" style="margin-top: 5px" ng-show="ctrl.state.errors.volumeSize || kubernetesVolumeUpdateForm.size.$invalid">
<div ng-messages="kubernetesVolumeUpdateForm.size.$error">
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
<div class="vertical-center" ng-messages="kubernetesVolumeUpdateForm.size.$error">
<p ng-message="required"><pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> This field is required.</p>
</div>
<p ng-show="ctrl.state.errors.volumeSize && !kubernetesVolumeUpdateForm.size.$invalid"
><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> The new size must be greater than the actual size.</p
<p class="vertical-center" ng-show="ctrl.state.errors.volumeSize"
><pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> The new size must be greater than the actual size.</p
>
</div>
</div>
@@ -139,16 +141,16 @@
</uib-tab>
<uib-tab index="1" classes="btn-sm" select="ctrl.selectTab(1)">
<uib-tab-heading data-cy="k8sVolDetail-volEventsTab">
<i class="fa fa-history space-right" aria-hidden="true"></i> Events
<uib-tab-heading class="vertical-center" data-cy="k8sVolDetail-volEventsTab">
<pr-icon icon="'file-text'" feather="true"></pr-icon> Events
<div ng-if="ctrl.hasEventWarnings()">
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px"></i>
<pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon>
{{ ctrl.state.eventWarningCount }} warning(s)
</div>
</uib-tab-heading>
<kubernetes-events-datatable
title-text="Events"
title-icon="fa-history"
title-icon="file-text"
dataset="ctrl.events"
table-key="kubernetes.volume.events"
order-by="Date"
@@ -160,8 +162,8 @@
</uib-tab>
<uib-tab index="2" ng-if="ctrl.volume.PersistentVolumeClaim.Yaml" select="ctrl.showEditor()" classes="btn-sm">
<uib-tab-heading data-cy="k8sVolDetail-volYamlTab"> <i class="fa fa-code space-right" aria-hidden="true"></i> YAML </uib-tab-heading>
<div style="padding-right: 25px" ng-if="ctrl.state.showEditorTab">
<uib-tab-heading class="vertical-center" data-cy="k8sVolDetail-volYamlTab"> <pr-icon icon="'code'" feather="true"></pr-icon> YAML </uib-tab-heading>
<div class="px-5" ng-if="ctrl.state.showEditorTab">
<kubernetes-yaml-inspector key="volume-yaml" data="ctrl.volume.PersistentVolumeClaim.Yaml"></kubernetes-yaml-inspector>
</div>
</uib-tab>
@@ -179,7 +181,7 @@
order-by="Name"
refresh-callback="ctrl.getVolume"
title-text="Applications using this volume"
title-icon="fa-laptop-code"
title-icon="code"
>
</kubernetes-integrated-applications-datatable>
</div>

View File

@@ -188,20 +188,6 @@ angular
},
};
var edgeDeviceCreation = {
name: 'portainer.endpoints.newEdgeDevice',
url: '/newEdgeDevice',
params: {
isEdgeDevice: true,
},
views: {
'content@': {
templateUrl: './views/endpoints/create/createendpoint.html',
controller: 'CreateEndpointController',
},
},
};
var deviceImport = {
name: 'portainer.endpoints.importDevice',
url: '/device',
@@ -471,7 +457,6 @@ angular
$stateRegistryProvider.register(endpoint);
$stateRegistryProvider.register(endpointAccess);
$stateRegistryProvider.register(endpointKVM);
$stateRegistryProvider.register(edgeDeviceCreation);
$stateRegistryProvider.register(deviceImport);
$stateRegistryProvider.register(addFDOProfile);
$stateRegistryProvider.register(editFDOProfile);

View File

@@ -6,7 +6,7 @@ import { r2a } from '@/react-tools/react2angular';
import { TeamMembership, Role } from '@/portainer/teams/types';
import { useUserMembership } from '@/portainer/users/queries';
import { Table, TableContainer, TableTitle } from '@@/datatables';
import { TableContainer, TableTitle } from '@@/datatables';
import { Button } from '@@/buttons';
import { ResourceControlType, ResourceId } from '../types';
@@ -48,33 +48,31 @@ export function AccessControlPanel({
<div className="col-sm-12">
<TableContainer>
<TableTitle label="Access control" icon="eye" featherIcon />
<Table className="no-padding">
<AccessControlPanelDetails
resourceType={resourceType}
resourceControl={resourceControl}
/>
<AccessControlPanelDetails
resourceType={resourceType}
resourceControl={resourceControl}
/>
{!isEditDisabled && !isEditMode && (
<div className="row">
<div>
<Button color="link" onClick={toggleEditMode}>
<Icon icon="edit" className="space-right" feather />
Change ownership
</Button>
</div>
{!isEditDisabled && !isEditMode && (
<div className="row">
<div>
<Button color="link" onClick={toggleEditMode}>
<Icon icon="edit" className="space-right" feather />
Change ownership
</Button>
</div>
)}
</div>
)}
{isEditMode && (
<AccessControlPanelForm
resourceControl={resourceControl}
onCancelClick={() => toggleEditMode()}
resourceId={resourceId}
resourceType={resourceType}
onUpdateSuccess={handleUpdateSuccess}
/>
)}
</Table>
{isEditMode && (
<AccessControlPanelForm
resourceControl={resourceControl}
onCancelClick={() => toggleEditMode()}
resourceId={resourceId}
resourceType={resourceType}
onUpdateSuccess={handleUpdateSuccess}
/>
)}
</TableContainer>
</div>
</div>

View File

@@ -1,26 +1,36 @@
<div class="datatable">
<rd-widget>
<rd-widget-header icon="{{ $ctrl.titleIcon }}" title-text="{{ $ctrl.titleText }}"> </rd-widget-header>
<rd-widget-header icon="{{ $ctrl.titleIcon }}" feather-icon="true" 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 or modified at the environment level but they can be overridden.
</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>
</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..."
ng-model-options="{ debounce: 300 }"
/>
<div class="toolBar vertical-center !gap-x-5 !gap-y-1 flex-wrap">
<div class="actionBar !gap-3">
<button
type="button"
class="btn btn-sm btn-dangerlight h-fit vertical-center !ml-0"
authorization="PortainerStackDelete"
ng-disabled="$ctrl.state.selectedItemCount === 0"
ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"
data-cy="access-removeButton"
>
<pr-icon icon="'trash-2'" feather="true" mode="'danger'"></pr-icon>Remove
</button>
</div>
<div class="searchBar vertical-center">
<pr-icon icon="'search'" feather="true"></pr-icon>
<input
type="text"
class="searchInput"
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search..."
ng-model-options="{ debounce: 300 }"
data-cy="access-searchInput"
/>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover nowrap-cells">

View File

@@ -1,13 +1,13 @@
<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-header icon="user-check" feather-icon="true" title-text="Create access"></rd-widget-header>
<rd-widget-body>
<form class="form-horizontal">
<div ng-if="ctrl.entityType !== 'registry'" class="form-group">
<span class="col-sm-12 small text-warning">
<p>
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px"></i>
<pr-icon icon="'alert-circle'" mode="'warning'" feather="true"></pr-icon>
Adding user access will require the affected user(s) to logout and login for the changes to be taken into account.
</p>
</span>
@@ -35,13 +35,13 @@
<div class="col-sm-12">
<button
type="submit"
class="btn btn-primary btn-sm"
class="btn btn-primary btn-sm vertical-center"
ng-disabled="(ctrl.availableUsersAndTeams | filter:{ticked:true}).length === 0 || ctrl.actionInProgress"
ng-click="ctrl.authorizeAccess()"
button-spinner="ctrl.actionInProgress"
data-cy="access-createAccess"
>
<span ng-hide="ctrl.state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Create access</span>
<span ng-hide="ctrl.state.actionInProgress"><pr-icon icon="'plus'" feather="true"></pr-icon> Create access</span>
<span ng-show="ctrl.state.actionInProgress">Creating access...</span>
</button>
</div>
@@ -57,7 +57,7 @@
<access-datatable
ng-if="ctrl.authorizedUsersAndTeams"
title-text="Access"
title-icon="fa-user-lock"
title-icon="user-x"
table-key="{{ 'access_' + ctrl.entityType }}"
order-by="Name"
show-warning="ctrl.entityType !== 'registry'"

View File

@@ -1,7 +1,7 @@
<div>
<div class="form-group">
<label for="stack_template" class="col-sm-1 control-label text-left"> Template </label>
<div class="col-sm-11">
<div class="form-group pt-3">
<label for="stack_template" class="col-sm-3 col-lg-2 control-label text-left"> Template </label>
<div class="col-sm-8 vertical-center">
<select
ng-if="$ctrl.templates.length"
class="form-control"
@@ -11,7 +11,10 @@
>
<option value="" label="Select a Custom template" disabled selected="selected"> </option>
</select>
<span ng-if="!$ctrl.templates.length"> No custom templates are available. Head over to the <a ui-state="$ctrl.newTemplatePath">custom template view</a> to create one. </span>
<span class="small text-muted pt-[7px]" ng-if="!$ctrl.templates.length">
No custom templates are available. Head over to the <a class="text-blue-8 hover:underline hover:text-blue-8" ui-state="$ctrl.newTemplatePath">custom template view</a> to
create one.
</span>
</div>
</div>

View File

@@ -334,13 +334,13 @@
width: 30px;
}
.table tr > th:first-child,
.table tr > td:first-child {
.table-responsive tr > th:first-child,
.table-responsive tr > td:first-child {
padding-left: 20px;
}
.table tr > th:last-child,
.table tr > last-child {
.table-responsive tr > th:last-child,
.table-responsive tr > last-child {
padding-right: 20px;
}

View File

@@ -19,10 +19,10 @@
data-cy="endpoint-searchInput"
/>
</div>
<div class="actionBar">
<div class="actionBar !gap-3">
<button
type="button"
class="btn btn-sm btn-dangerlight vertical-center"
class="btn btn-sm btn-dangerlight h-fit"
ng-disabled="$ctrl.state.selectedItemCount === 0"
ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"
data-cy="endpoint-removeEndpointButton"
@@ -30,13 +30,7 @@
<pr-icon icon="'trash-2'" feather="true" class-name="'icon-white'"></pr-icon>
<span>Remove</span>
</button>
<button
type="button"
class="btn btn-sm btn-primary vertical-center"
ng-click="$ctrl.setReferrer()"
ui-sref="portainer.wizard.endpoints"
data-cy="endpoint-addEndpointButton"
>
<button type="button" class="btn btn-sm btn-primary h-fit" ng-click="$ctrl.setReferrer()" ui-sref="portainer.wizard.endpoints" data-cy="endpoint-addEndpointButton">
<pr-icon icon="'plus'" feather="true" class-name="'icon-white'"></pr-icon>
<span>Add environment</span>
</button>
@@ -136,7 +130,7 @@
<div class="footer" ng-if="!$ctrl.state.loading">
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0"> {{ $ctrl.state.selectedItemCount }} item(s) selected </div>
<div class="paginationControls">
<form class="form-inline">
<form class="form-inline vertical-center">
<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()" data-cy="component-paginationSelect">

View File

@@ -2,34 +2,37 @@
<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="actionBar">
<button
type="button"
class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0"
ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"
data-cy="endpointGroup-removeGroupButton"
>
<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.groups.new" data-cy="endpointGroup-addGroupButton">
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add group
</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..."
auto-focus
ng-model-options="{ debounce: 300 }"
data-cy="endpointGroup-searchInput"
/>
<div class="toolBarTitle vertical-center">
<pr-icon icon="$ctrl.titleIcon" feather="true" class-name="'icon-white icon-primary icon-nested-blue'"></pr-icon>
{{ $ctrl.titleText }}
</div>
<div class="searchBar">
<pr-icon icon="'search'" feather="true"></pr-icon>
<input
type="text"
class="searchInput"
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search for a group..."
auto-focus
ng-model-options="{ debounce: 300 }"
data-cy="endpointGroup-searchInput"
/>
</div>
<div class="actionBar !gap-3">
<button
type="button"
class="btn btn-sm btn-dangerlight h-fit"
ng-disabled="$ctrl.state.selectedItemCount === 0"
ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"
data-cy="endpointGroup-removeGroupButton"
>
<pr-icon icon="'trash-2'" feather="true"></pr-icon>Remove
</button>
<button type="button" class="btn btn-sm btn-primary h-fit" ui-sref="portainer.groups.new" data-cy="endpointGroup-addGroupButton">
<pr-icon icon="'plus'" feather="true"></pr-icon>Add group
</button>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover nowrap-cells" data-cy="endpointGroup-endpointGroupTable">
@@ -42,8 +45,8 @@
</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>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
<th>Actions</th>
@@ -77,7 +80,7 @@
<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">
<form class="form-inline vertical-center">
<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()" data-cy="component-paginationSelect">

View File

@@ -114,7 +114,7 @@
<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">
<form class="form-inline vertical-center">
<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()" data-cy="component-paginationSelect">

View File

@@ -2,23 +2,26 @@
<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="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>
</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..."
ng-model-options="{ debounce: 300 }"
/>
<div class="toolBarTitle vertical-center">
<pr-icon icon="$ctrl.titleIcon" feather="true" class-name="'icon-white icon-primary icon-nested-blue'"></pr-icon>
{{ $ctrl.titleText }}
</div>
<div class="searchBar">
<pr-icon icon="'search'" feather="true"></pr-icon>
<input
type="text"
class="searchInput"
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search for a tag..."
ng-model-options="{ debounce: 300 }"
/>
</div>
<div class="actionBar !gap-3">
<button type="button" class="btn btn-sm btn-dangerlight h-fit" ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<pr-icon icon="'trash-2'" feather="true"></pr-icon>Remove
</button>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover nowrap-cells">
@@ -31,8 +34,8 @@
</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>
<pr-icon icon="'arrow-down'" feather="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></pr-icon>
<pr-icon icon="'arrow-up'" feather="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></pr-icon>
</a>
</th>
</tr>
@@ -62,7 +65,7 @@
<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">
<form class="form-inline vertical-center">
<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()" data-cy="component-paginationSelect">

View File

@@ -1,31 +1,37 @@
<div class="datatable">
<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="actionBar" ng-show="$ctrl.isAdmin">
<button
type="button"
class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0"
ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"
data-cy="user-removeUserButton"
>
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</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..."
ng-model-options="{ debounce: 300 }"
/>
<div class="toolBar vertical-center !gap-x-5 !gap-y-1 flex-wrap">
<div class="toolBarTitle vertical-center">
<pr-icon icon="$ctrl.titleIcon" feather="true" class-name="'icon-nested-blue'" mode="'primary'"></pr-icon>
{{ $ctrl.titleText }}
</div>
<div class="searchBar vertical-center">
<pr-icon icon="'search'" feather="true"></pr-icon>
<input
type="text"
class="searchInput"
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search..."
ng-model-options="{ debounce: 300 }"
/>
</div>
<div class="actionBar !gap-3" ng-show="$ctrl.isAdmin">
<button
type="button"
class="btn btn-sm btn-dangerlight h-fit vertical-center !ml-0"
ng-disabled="$ctrl.state.selectedItemCount === 0"
ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"
data-cy="user-removeUserButton"
>
<pr-icon icon="'trash-2'" feather="true" mode="'danger'"></pr-icon>Remove
</button>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover nowrap-cells" data-cy="user-userTable">
<thead>

View File

@@ -15,17 +15,17 @@
<div ng-if="$ctrl.mode == 'advanced'" class="environment-variables-panel--advanced">
<div class="col-sm-12">
<a class="small interactive" ng-click="$ctrl.switchEnvMode()"> <i class="fa fa-list-ol space-right" aria-hidden="true"></i> Simple mode </a>
<a class="small interactive" ng-click="$ctrl.switchEnvMode()"> <pr-icon icon="'list'" feather="true"></pr-icon> Simple mode </a>
</div>
<div class="col-sm-12 small text-muted">
<i class="fa fa-info-circle blue-icon space-right" aria-hidden="true"></i>
<pr-icon icon="'alert-circle'" mode="'primary'" feather="true"></pr-icon>
Switch to simple mode to define variables line by line, or load from .env file
</div>
<div class="form-group" style="margin-left: 1px">
<code-editor identifier="environment-variables-editor" placeholder="e.g. key=value" value="$ctrl.editorText" yml="false" on-change="($ctrl.editorUpdate)"></code-editor>
</div>
<div class="col-sm-12 small text-muted" ng-if="$ctrl.showHelpMessage">
<i class="fa fa-info-circle blue-icon space-right" aria-hidden="true"></i>
<pr-icon icon="'alert-circle'" mode="'primary'" feather="true"></pr-icon>
Environment changes will not take effect until redeployment occurs manually or via webhook.
</div>
</div>

View File

@@ -14,9 +14,9 @@
/>
</div>
<div class="form-group" style="margin-top: 5px" ng-show="$ctrl[$ctrl.formName].name.$invalid">
<div class="col-sm-12 small text-warning">
<div class="col-sm-12 small">
<div ng-messages="$ctrl[$ctrl.formName].name.$error">
<p ng-message="required"> <i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Name is required. </p>
<p ng-message="required"> <pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Name is required. </p>
<p ng-message="pattern">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
This field must consist alphanumeric characters, '-' or '_', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-var', or 'MY_VAR123').

View File

@@ -1,13 +1,13 @@
<div class="environment-variables-simple-mode">
<div class="col-sm-12">
<a class="small interactive" ng-click="$ctrl.onSwitchModeClick()"> <i class="fa fa-list-ol space-right" aria-hidden="true"></i> Advanced mode </a>
<a class="small interactive" ng-click="$ctrl.onSwitchModeClick()"> <pr-icon icon="'list'" feather="true"></pr-icon> Advanced mode </a>
</div>
<div class="col-sm-12 small text-muted">
<i class="fa fa-info-circle blue-icon space-right" aria-hidden="true"></i>
<pr-icon icon="'alert-circle'" mode="'primary'" feather="true"></pr-icon>
Switch to advanced mode to copy & paste multiple variables
</div>
<div class="col-sm-12 environment-variables-simple-mode--actions">
<button type="button" class="btn btn-sm btn-default" ng-click="$ctrl.add()"> <i class="fa fa-plus-circle" aria-hidden="true"></i> Add an environment variable </button>
<button type="button" class="btn btn-sm btn-default" ng-click="$ctrl.add()"> <pr-icon icon="'plus'" feather="true"></pr-icon> Add an environment variable </button>
<button
type="button"
class="btn btn-sm btn-default"
@@ -17,7 +17,7 @@
ngf-max-size="1MB"
ngf-model-invalid="errorFile"
>
<i class="fa fa-file-upload" aria-hidden="true"></i> Load variables from .env file
<pr-icon icon="'upload'" feather="true"></pr-icon> Load variables from .env file
</button>
<span class="space-left" ng-if="errorFile.$error == 'maxSize'">
<i class="fa fa-times red-icon space-right" aria-hidden="true"></i>
@@ -34,7 +34,7 @@
></environment-variables-simple-mode-item>
</div>
<div class="col-sm-12 small text-muted" ng-if="$ctrl.ngModel.length > 0 && $ctrl.showHelpMessage">
<i class="fa fa-info-circle blue-icon space-right" aria-hidden="true"></i>
<pr-icon icon="'alert-circle'" mode="'primary'" feather="true"></pr-icon>
Environment changes will not take effect until redeployment occurs manually or via webhook.
</div>
</div>

View File

@@ -9,6 +9,7 @@ export const webEditorForm = {
placeholder: '@',
yml: '<',
value: '<',
readOnly: '<',
onChange: '<',
},

View File

@@ -1,11 +1,20 @@
<ng-form name="$ctrl.webEditorForm">
<div class="col-sm-12 form-section-title"> Web editor </div>
<div class="form-group">
<span class="col-sm-12 text-muted small" ng-transclude="description"> </span>
</div>
<div class="form-group">
<div class="col-sm-12">
<code-editor identifier="{{ $ctrl.identifier }}" placeholder="{{ $ctrl.placeholder }}" yml="$ctrl.yml" value="$ctrl.value" on-change="($ctrl.editorUpdate)"></code-editor>
<div class="web-editor">
<div class="col-sm-12 form-section-title"> Web editor </div>
<div class="form-group col-sm-12 col-lg-12">
<div class="text-muted small" ng-transclude="description"> </div>
</div>
<div class="form-group">
<div class="col-sm-12 col-lg-12">
<code-editor
identifier="{{ $ctrl.identifier }}"
placeholder="{{ $ctrl.placeholder }}"
read-only="$ctrl.readOnly"
yml="$ctrl.yml"
value="$ctrl.value"
on-change="($ctrl.editorUpdate)"
></code-editor>
</div>
</div>
</div>
</ng-form>

View File

@@ -1,6 +1,6 @@
<ng-form class="env-item form-horizontal" name="$ctrl.{{ $ctrl.formName }}">
<div class="form-group col-sm-12">
<div class="form-inline" style="margin-top: 10px">
<div class="form-inline mt-3">
<div class="input-group col-sm-5 input-group-sm">
<span class="input-group-addon">path</span>
<input
@@ -18,10 +18,12 @@
</button>
</div>
<div ng-show="$ctrl[$ctrl.formName].name.$invalid">
<div class="small">
<div class="small text-muted">
<div ng-messages="$ctrl[$ctrl.formName].name.$error" class="mt-1">
<p ng-message="required"> <pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Path is required. </p>
<p ng-message="pattern"> <pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> File path must include yaml, yml, json, or hcl extension </p>
<p class="vertical-center" ng-message="required"> <pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Path is required. </p>
<p class="vertical-center" ng-message="pattern">
<pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> File path must include yaml, yml, json, or hcl extension
</p>
</div>
</div>
</div>

View File

@@ -1,11 +1,15 @@
<div class="form-group">
<div class="col-sm-12" style="margin-top: 5px">
<label class="control-label text-left">Additional paths</label>
<span class="label label-default interactive" style="margin-left: 10px" ng-click="$ctrl.add()">
<pr-icon icon="'plus'" size="'sm'" mode="'alt'" feather="true"></pr-icon> add file
</span>
<div class="col-sm-12 p-0">
<div class="col-sm-3 col-lg-2">
<label class="control-label text-left">Additional paths</label>
</div>
<div class="col-sm-9 pt-1">
<span class="label label-default interactive vertical-center" ng-click="$ctrl.add()">
<pr-icon icon="'plus'" size="'sm'" mode="'alt'" feather="true"></pr-icon> <span>add file</span>
</span>
</div>
</div>
<div class="col-sm-12 form-inline" style="margin-top: 10px">
<div class="col-sm-12 form-inline">
<git-form-additional-file-item
ng-repeat="variable in $ctrl.model.AdditionalFiles track by $index"
variable="variable"

View File

@@ -3,13 +3,15 @@
<por-switch-field
checked="$ctrl.model.RepositoryAuthentication"
label="'Authentication'"
label-class="'col-sm-3 col-lg-2'"
name="'authSwitch'"
on-change="($ctrl.onChangeAuth)"
data-cy="'component-gitAuthToggle'"
switch-values="{on:'Yes',off:'No'}"
></por-switch-field>
</div>
</div>
<div class="small" style="margin: 5px 0 15px 0" ng-if="$ctrl.model.RepositoryAuthentication && $ctrl.showAuthExplanation">
<div class="small mt-1 mb-3" ng-if="$ctrl.model.RepositoryAuthentication && $ctrl.showAuthExplanation">
<pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon>
<span class="text-muted">Enabling authentication will store the credentials and it is advisable to use a git service account</span>
</div>

View File

@@ -1,14 +1,21 @@
<ng-form name="autoUpdateForm">
<div class="form-group">
<div class="col-sm-12">
<por-switch-field name="'autoUpdate'" checked="$ctrl.model.RepositoryAutomaticUpdates" label="'Automatic Updates'" on-change="($ctrl.onChangeAutoUpdate)"></por-switch-field>
<por-switch-field
name="'autoUpdate'"
checked="$ctrl.model.RepositoryAutomaticUpdates"
label="'Automatic Updates'"
label-class="'col-sm-3 col-lg-2'"
on-change="($ctrl.onChangeAutoUpdate)"
switch-values="{on:'Yes',off:'No'}"
></por-switch-field>
</div>
</div>
<div class="small text-warning" style="margin: 5px 0 10px 0" ng-if="$ctrl.model.RepositoryAutomaticUpdates">
<pr-icon icon="'alert-circle'" mode="'warning'" feather="true"></pr-icon>
<div class="small" ng-if="$ctrl.model.RepositoryAutomaticUpdates">
<pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon>
<span class="text-muted">Any changes to this stack or application made locally in Portainer will be overridden, which may cause service interruption.</span>
</div>
<div class="form-group" ng-if="$ctrl.model.RepositoryAutomaticUpdates">
<div class="form-group mt-2" ng-if="$ctrl.model.RepositoryAutomaticUpdates">
<label for="repository_mechanism" class="col-lg-2 col-sm-3 control-label text-left"> Mechanism </label>
<div class="col-sm-8">
<div class="input-group col-sm-10 input-group-sm">
@@ -49,17 +56,20 @@
required
interval-format
/>
</div>
</div>
<div class="form-group col-md-12" ng-show="autoUpdateForm.repository_fetch_interval.$touched && autoUpdateForm.repository_fetch_interval.$invalid">
<div class="small">
<div ng-messages="autoUpdateForm.repository_fetch_interval.$error">
<p ng-message="required"> <pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> This field is required.</p>
<p ng-message="invalidIntervalFormat"> <pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Please enter a valid time interval.</p>
<p ng-message="minimumInterval"> <pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Minimum interval is 1m</p>
<div class="help-group">
<div class="form-group col-md-12 pt-1" ng-show="autoUpdateForm.repository_fetch_interval.$touched && autoUpdateForm.repository_fetch_interval.$invalid">
<div class="small text-muted">
<div ng-messages="autoUpdateForm.repository_fetch_interval.$error">
<p ng-message="required"> <pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> This field is required.</p>
<p ng-message="invalidIntervalFormat"> <pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Please enter a valid time interval.</p>
<p ng-message="minimumInterval"> <pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Minimum interval is 1m</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="form-group" ng-if="$ctrl.showForcePullImage && $ctrl.model.RepositoryAutomaticUpdates">
<div class="col-sm-12">
<por-switch-field
@@ -67,6 +77,7 @@
feature="$ctrl.limitedFeaturePullImage"
checked="$ctrl.model.ForcePullImage"
label="'Pull latest image'"
label-class="'col-sm-3 col-lg-2'"
on-change="($ctrl.onChangeForcePullImage)"
></por-switch-field>
</div>
@@ -79,16 +90,17 @@
feature-id="$ctrl.limitedFeature"
checked="$ctrl.model.RepositoryAutomaticUpdatesForce"
label="'Force Redeployment'"
label-class="'col-sm-3 col-lg-2'"
on-change="($ctrl.onChangeAutoUpdateForce)"
></por-switch-field>
</div>
</div>
<div class="small" style="margin: 5px 0 10px 0" ng-if="$ctrl.model.RepositoryAutomaticUpdates">
<pr-icon icon="'alert-circle'" mode="'primary'" feather="true"></pr-icon>
<div class="small" ng-if="$ctrl.model.RepositoryAutomaticUpdates">
<pr-icon icon="'info'" mode="'primary'" feather="true"></pr-icon>
<span class="text-muted">When enabled, enforces automatic deployment at each interval or webhook invocation.</span>
</div>
<div class="small" style="margin: 5px 0 10px 0" ng-if="!$ctrl.model.RepositoryAutomaticUpdates">
<pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon>
<div class="small" ng-if="!$ctrl.model.RepositoryAutomaticUpdates">
<pr-icon icon="'info'" mode="'primary'" feather="true"></pr-icon>
<span class="text-muted">When enabled, updates from the git repository will occur automatically at an interval or webhook.</span>
</div>
</ng-form>

View File

@@ -19,7 +19,9 @@
ng-pattern="/.+\.(yml|yaml|json|hcl)$/i"
required
/>
<p class="mt-2" ng-show="pathForm.repoPathField.$error.pattern"> <pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Invalid file path </p>
<p class="mt-2 text-muted small vertical-center" ng-show="pathForm.repoPathField.$error.pattern">
<pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Invalid file path
</p>
</div>
</div>
</ng-form>

View File

@@ -67,6 +67,7 @@
<div class="col-sm-12">
<por-switch-field
label="'Authentication'"
label-class="'col-sm-3 col-lg-2'"
tooltip="'Enable this option if you need to specify credentials to connect to a private registry.'"
name="'administrator'"
checked="$ctrl.model.Authentication"

View File

@@ -55,6 +55,7 @@
<div class="col-sm-12">
<por-switch-field
label="'Authentication'"
label-class="'col-sm-3 col-lg-2'"
tooltip="'Enable this option if you need to specify credentials to connect to this registry.'"
name="'administrator'"
checked="$ctrl.model.Authentication"

View File

@@ -59,10 +59,15 @@
</div>
<!-- !credentials-password -->
<!-- organisation-checkbox -->
<!-- organization-checkbox -->
<div class="form-group">
<div class="col-sm-12">
<por-switch-field label="'Use organisation registry'" checked="$ctrl.model.Quay.useOrganisation" on-change="($ctrl.toggleOrganisation)"></por-switch-field>
<por-switch-field
label="'Use organization registry'"
label-class="'col-sm-3 col-lg-2'"
checked="$ctrl.model.Quay.useOrganisation"
on-change="($ctrl.toggleOrganisation)"
></por-switch-field>
</div>
</div>
<!-- !organisation-checkbox -->

View File

@@ -1,7 +1,7 @@
<div class="row" ng-if="$ctrl.registry">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-plug" title-text="Registry"></rd-widget-header>
<rd-widget-header icon="radio" feather-icon="true" title-text="Registry"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>

View File

@@ -5,6 +5,8 @@ import dockerEdge from '@/assets/images/edge_endpoint.png';
import kube from '@/assets/images/kubernetes_endpoint.png';
import kubeEdge from '@/assets/images/kubernetes_edge_endpoint.png';
import { EnvironmentType } from '@/portainer/environments/types';
import azure from '@/assets/ico/vendor/azure.svg';
import docker from '@/assets/ico/vendor/docker.svg';
interface Props {
type: EnvironmentType;
@@ -12,6 +14,15 @@ interface Props {
export function EnvironmentIcon({ type }: Props) {
switch (type) {
case EnvironmentType.AgentOnDocker:
case EnvironmentType.Docker:
return (
<img src={docker} width="60" alt="azure endpoint" aria-hidden="true" />
);
case EnvironmentType.Azure:
return (
<img src={azure} width="60" alt="azure endpoint" aria-hidden="true" />
);
case EnvironmentType.EdgeAgentOnDocker:
return (
<img src={dockerEdge} alt="docker edge endpoint" aria-hidden="true" />

View File

@@ -1,5 +1,6 @@
import clsx from 'clsx';
import _ from 'lodash';
import { Edit2, Tag, Cpu } from 'react-feather';
import {
isoDateFromTimestamp,
@@ -16,8 +17,9 @@ import type { TagId } from '@/portainer/tags/types';
import { useIsAdmin } from '@/portainer/hooks/useUser';
import { useTags } from '@/portainer/tags/queries';
import { Button } from '@@/buttons';
import { Icon } from '@@/Icon';
import { Link } from '@@/Link';
import { Button } from '@@/buttons';
import { EnvironmentIcon } from './EnvironmentIcon';
import { EdgeIndicator } from './EdgeIndicator';
@@ -91,24 +93,35 @@ export function EnvironmentItem({ environment, onClick, groupName }: Props) {
</div>
<EnvironmentStats environment={environment} />
<div className="blocklist-item-line endpoint-item">
<span className="small text-muted">
<span className="small text-muted space-x-2">
{isDockerEnvironment(environment.Type) && (
<span>
{environment.Snapshots.length > 0 && (
<span className="small text-muted">
<i className="fa fa-microchip space-right" />
{environment.Snapshots[0].TotalCPU}
<i className="fa fa-memory space-left space-right" />
{humanize(environment.Snapshots[0].TotalMemory)}
<i className="fa fa-digital-tachograph space-left space-right" />
{environment.Gpus?.length}
<span className="small text-muted vertical-center">
<Cpu
className="icon icon-sm space-right"
aria-hidden="true"
/>
{environment.Snapshots[0].TotalCPU} CPU
<Icon
icon="svg-memory"
className="icon icon-sm space-right"
/>
{humanize(environment.Snapshots[0].TotalMemory)} RAM
<Cpu
className="icon icon-sm space-right"
aria-hidden="true"
/>
{environment.Gpus?.length} GPU
</span>
)}
<span className="space-left space-right">-</span>
</span>
)}
<span>
<i className="fa fa-tags space-right" aria-hidden="true" />
<span className="vertical-center">
<Tag
className="icon icon-sm space-right"
aria-hidden="true"
/>
{tags}
</span>
</span>
@@ -129,7 +142,7 @@ export function EnvironmentItem({ environment, onClick, groupName }: Props) {
className={styles.editButton}
>
<Button color="link">
<i className="fa fa-pencil-alt" />
<Edit2 className="icon icon-md" aria-hidden="true" />
</Button>
</Link>
)}

View File

@@ -1,3 +1,5 @@
import { Zap } from 'react-feather';
import {
DockerSnapshot,
EnvironmentType,
@@ -24,16 +26,18 @@ export function EnvironmentStatsDocker({ snapshots = [], type }: Props) {
return (
<div className="blocklist-item-line endpoint-item">
<span className="blocklist-item-desc space-x-4">
<span className="blocklist-item-desc">
<Stat
value={addPlural(snapshot.StackCount, 'stack')}
icon="fa-th-list"
icon="layers"
featherIcon
/>
{!!snapshot.Swarm && (
<Stat
value={addPlural(snapshot.ServiceCount, 'service')}
icon="fa-list-alt"
icon="shuffle"
featherIcon
/>
)}
@@ -43,21 +47,34 @@ export function EnvironmentStatsDocker({ snapshots = [], type }: Props) {
healthy={snapshot.HealthyContainerCount}
unhealthy={snapshot.UnhealthyContainerCount}
/>
<Stat value={addPlural(snapshot.VolumeCount, 'volume')} icon="fa-hdd" />
<Stat value={addPlural(snapshot.ImageCount, 'image')} icon="fa-clone" />
<Stat
value={addPlural(snapshot.VolumeCount, 'volume')}
icon="database"
featherIcon
/>
<Stat
value={addPlural(snapshot.ImageCount, 'image')}
icon="list"
featherIcon
/>
</span>
<span className="small text-muted space-x-3">
<span className="small text-muted space-x-2 vertical-center">
<span>{snapshot.Swarm ? 'Swarm' : 'Standalone'}</span>
<span>{snapshot.DockerVersion}</span>
{type === EnvironmentType.AgentOnDocker && (
<span>
+ <i className="fa fa-bolt" aria-hidden="true" /> Agent
+{' '}
<Zap className="icon icon-xs vertical-center" aria-hidden="true" />{' '}
Agent
</span>
)}
{snapshot.Swarm && (
<Stat value={addPlural(snapshot.NodeCount, 'node')} icon="fa-hdd" />
<Stat
value={addPlural(snapshot.NodeCount, 'node')}
icon="hard-drive"
featherIcon
/>
)}
</span>
</div>
@@ -80,15 +97,37 @@ function ContainerStats({
const containersCount = running + stopped;
return (
<Stat value={addPlural(containersCount, 'container')} icon="fa-cubes">
<Stat
value={addPlural(containersCount, 'container')}
icon="box"
featherIcon
>
{containersCount > 0 && (
<span className="space-x-2">
<span>-</span>
<Stat value={running} icon="fa-power-off green-icon" />
<Stat value={stopped} icon="fa-power-off red-icon" />
<span>/</span>
<Stat value={healthy} icon="fa-heartbeat green-icon" />
<Stat value={unhealthy} icon="fa-heartbeat orange-icon" />
<span className="space-x-2 space-right">
<Stat
value={running}
icon="power"
featherIcon
iconClass="icon-success"
/>
<Stat
value={stopped}
icon="power"
featherIcon
iconClass="icon-danger"
/>
<Stat
value={healthy}
icon="heart"
featherIcon
iconClass="icon-success"
/>
<Stat
value={unhealthy}
icon="heart"
featherIcon
iconClass="icon-warning"
/>
</span>
)}
</Stat>

View File

@@ -1,3 +1,4 @@
import clsx from 'clsx';
import { PropsWithChildren } from 'react';
import { Icon, IconProps } from '@/react/components/Icon';
@@ -5,6 +6,7 @@ import { Icon, IconProps } from '@/react/components/Icon';
interface Props extends IconProps {
value: string | number;
icon: string;
iconClass?: string;
}
export function Stat({
@@ -12,10 +14,15 @@ export function Stat({
icon,
children,
featherIcon,
iconClass,
}: PropsWithChildren<Props>) {
return (
<span>
<Icon className="space-right" icon={icon} feather={featherIcon} />
<span className="vertical-center space-right">
<Icon
className={clsx('icon icon-sm space-right', iconClass)}
icon={icon}
feather={featherIcon}
/>
<span>{value}</span>
{children && <span className="space-left">{children}</span>}
</span>

View File

@@ -21,17 +21,23 @@ export function EnvironmentStatsKubernetes({ snapshots = [] }: Props) {
return (
<div className="blocklist-item-line endpoint-item">
<span className="blocklist-item-desc space-x-4">
<Stat icon="fa-microchip" value={`${snapshot.TotalCPU} CPU`} />
<span className="blocklist-item-desc space-x-1">
<Stat icon="cpu" featherIcon value={`${snapshot.TotalCPU} CPU`} />
<Stat
icon="fa-memory"
icon="svg-memory"
featherIcon
value={`${humanize(snapshot.TotalMemory)} RAM`}
/>
</span>
<span className="small text-muted space-x-3">
<span className="small text-muted space-x-2 vertical-center">
<span>Kubernetes {snapshot.KubernetesVersion}</span>
<Stat value={addPlural(snapshot.NodeCount, 'node')} icon="fa-hdd" />
<Stat
value={addPlural(snapshot.NodeCount, 'node')}
icon="hard-drive"
featherIcon
/>
</span>
</div>
);

View File

@@ -277,7 +277,7 @@ export function EnvironmentList({ onClickItem, onRefresh }: Props) {
<div className="row">
<div className="col-sm-12">
<TableContainer>
<TableTitle icon="fa-plug" label="Environments" />
<TableTitle icon="hard-drive" featherIcon label="Environments" />
<TableActions className={styles.actionBar}>
<div className={styles.description}>

View File

@@ -10,6 +10,7 @@ import { Environment } from '../environments/types';
import { snapshotEndpoints } from '../environments/environment.service';
import { isEdgeEnvironment } from '../environments/utils';
import { confirmAsync } from '../services/modal.service/confirm';
import { buildTitle } from '../services/modal.service/utils';
import { EnvironmentList } from './EnvironmentList';
import { EdgeLoadingSpinner } from './EdgeLoadingSpinner';
@@ -76,7 +77,7 @@ export const HomeViewAngular = r2a(HomeView, []);
async function confirmEndpointSnapshot() {
return confirmAsync({
title: 'Are you sure?',
title: buildTitle('Are you sure?'),
message:
'Triggering a manual refresh will poll each environment to retrieve its information, this may take a few moments.',
buttons: {

View File

@@ -1,4 +1,5 @@
import angular from 'angular';
import { react2angular } from 'react2angular';
import { r2a } from '@/react-tools/react2angular';
import { Icon } from '@/react/components/Icon';
@@ -13,6 +14,7 @@ import { Tooltip } from '@@/Tip/Tooltip';
import { TableColumnHeaderAngular } from '@@/datatables/TableHeaderCell';
import { DashboardItem } from '@@/DashboardItem';
import { SearchBar } from '@@/datatables/SearchBar';
import { FallbackImage } from '@@/FallbackImage';
import { fileUploadField } from './file-upload-field';
import { switchField } from './switch-field';
@@ -24,7 +26,10 @@ export const componentsModule = angular
'tagSelector',
r2a(TagSelector, ['allowCreate', 'onChange', 'value'])
)
.component('portainerTooltip', r2a(Tooltip, ['message', 'position']))
.component(
'portainerTooltip',
react2angular(Tooltip, ['message', 'position'])
)
.component('fileUploadField', fileUploadField)
.component('porSwitchField', switchField)
.component(
@@ -34,7 +39,7 @@ export const componentsModule = angular
.component('rdLoading', r2a(Loading, []))
.component(
'tableColumnHeader',
r2a(TableColumnHeaderAngular, [
react2angular(TableColumnHeaderAngular, [
'colTitle',
'canSort',
'isSorted',
@@ -46,9 +51,22 @@ export const componentsModule = angular
'pageHeader',
r2a(PageHeader, ['title', 'breadcrumbs', 'loading', 'onReload', 'reload'])
)
.component(
'fallbackImage',
r2a(FallbackImage, [
'src',
'fallbackIcon',
'alt',
'size',
'className',
'fallbackMode',
'fallbackClassName',
'feather',
])
)
.component(
'prIcon',
r2a(Icon, ['className', 'feather', 'icon', 'mode', 'size'])
react2angular(Icon, ['className', 'feather', 'icon', 'mode', 'size'])
)
.component('reactQueryDevTools', r2a(ReactQueryDevtoolsWrapper, []))
.component(

View File

@@ -8,6 +8,7 @@ export const switchField = r2a(SwitchField, [
'label',
'name',
'labelClass',
'fieldClass',
'dataCy',
'disabled',
'onChange',

View File

@@ -29,6 +29,19 @@ function config($stateRegistryProvider: StateRegistry) {
},
});
$stateRegistryProvider.register({
name: 'portainer.wizard.endpoints',
url: '/endpoints?edgeDevice',
views: {
'content@': {
component: 'wizardEnvironmentTypeSelectView',
},
},
params: {
localEndpointId: 0,
},
});
$stateRegistryProvider.register({
name: 'portainer.wizard.endpoints.create',
url: '/create?envType',
@@ -41,17 +54,4 @@ function config($stateRegistryProvider: StateRegistry) {
envType: '',
},
});
$stateRegistryProvider.register({
name: 'portainer.wizard.endpoints',
url: '/endpoints',
views: {
'content@': {
component: 'wizardEnvironmentTypeSelectView',
},
},
params: {
localEndpointId: 0,
},
});
}

View File

@@ -1,7 +1,13 @@
import sanitize from 'sanitize-html';
import bootbox from 'bootbox';
import { applyBoxCSS, ButtonsOptions, confirmButtons } from './utils';
import {
applyBoxCSS,
ButtonsOptions,
confirmButtons,
buildTitle,
ModalTypeIcon,
} from './utils';
type ConfirmCallback = (confirmed: boolean) => void;
@@ -17,7 +23,7 @@ interface ConfirmOptions extends ConfirmAsyncOptions {
export function confirmWebEditorDiscard() {
const options = {
title: 'Are you sure ?',
title: buildTitle('Are you sure?'),
message:
'You currently have unsaved changes in the editor. Are you sure you want to leave?',
buttons: {
@@ -39,6 +45,17 @@ export function confirmAsync(options: ConfirmAsyncOptions) {
return new Promise((resolve) => {
confirm({
...options,
title: buildTitle(options.title),
callback: (confirmed) => resolve(confirmed),
});
});
}
export function confirmDestructiveAsync(options: ConfirmAsyncOptions) {
return new Promise((resolve) => {
confirm({
...options,
title: buildTitle(options.title, ModalTypeIcon.Destructive),
callback: (confirmed) => resolve(confirmed),
});
});
@@ -55,9 +72,20 @@ export function confirm(options: ConfirmOptions) {
applyBoxCSS(box);
}
export function confirmWarn(options: ConfirmOptions) {
confirm({ ...options, title: buildTitle(options.title, ModalTypeIcon.Warn) });
}
export function confirmDestructive(options: ConfirmOptions) {
confirm({
...options,
title: buildTitle(options.title, ModalTypeIcon.Destructive),
});
}
export function confirmImageForceRemoval(callback: ConfirmCallback) {
confirm({
title: 'Are you sure?',
title: buildTitle('Are you sure?', ModalTypeIcon.Destructive),
message:
'Forcing the removal of the image will remove the image even if it has multiple tags or if it is used by stopped containers.',
buttons: {
@@ -72,7 +100,7 @@ export function confirmImageForceRemoval(callback: ConfirmCallback) {
export function cancelRegistryRepositoryAction(callback: ConfirmCallback) {
confirm({
title: 'Are you sure?',
title: buildTitle('Are you sure?', ModalTypeIcon.Destructive),
message:
'WARNING: interrupting this operation before it has finished will result in the loss of all tags. Are you sure you want to do this?',
buttons: {
@@ -88,7 +116,7 @@ export function cancelRegistryRepositoryAction(callback: ConfirmCallback) {
export function confirmDeletion(message: string, callback: ConfirmCallback) {
const messageSanitized = sanitize(message);
confirm({
title: 'Are you sure ?',
title: buildTitle('Are you sure?', ModalTypeIcon.Destructive),
message: messageSanitized,
buttons: {
confirm: {
@@ -107,7 +135,7 @@ export function confirmWithTitle(
) {
const messageSanitized = sanitize(message);
confirm({
title: sanitize(title),
title: buildTitle(title, ModalTypeIcon.Destructive),
message: messageSanitized,
buttons: {
confirm: {
@@ -122,7 +150,7 @@ export function confirmWithTitle(
export function confirmDetachment(message: string, callback: ConfirmCallback) {
const messageSanitized = sanitize(message);
confirm({
title: 'Are you sure ?',
title: buildTitle('Are you sure?'),
message: messageSanitized,
buttons: {
confirm: {
@@ -140,7 +168,7 @@ export function confirmDisassociate(callback: ConfirmCallback) {
'<p>Any agent started with the Edge key associated to this environment will be able to re-associate with this environment.</p>' +
'<p>You can re-use the Edge ID and Edge key that you used to deploy the existing Edge agent to associate a new Edge device to this environment.</p>';
confirm({
title: 'About disassociating',
title: buildTitle('About disassociating'),
message: sanitize(message),
buttons: {
confirm: {
@@ -156,7 +184,7 @@ export function confirmUpdate(message: string, callback: ConfirmCallback) {
const messageSanitized = sanitize(message);
confirm({
title: 'Are you sure ?',
title: buildTitle('Are you sure?'),
message: messageSanitized,
buttons: {
confirm: {
@@ -195,7 +223,7 @@ export function confirmDeletionAsync(message: string) {
export function confirmImageExport(callback: ConfirmCallback) {
confirm({
title: 'Caution',
title: buildTitle('Caution'),
message:
'The export may take several minutes, do not navigate away whilst the export is in progress.',
buttons: {
@@ -210,7 +238,7 @@ export function confirmImageExport(callback: ConfirmCallback) {
export function confirmChangePassword() {
return confirmAsync({
title: 'Are you sure?',
title: buildTitle('Are you sure?'),
message:
'You will be logged out after the password change. Do you want to change your password?',
buttons: {

View File

@@ -4,6 +4,9 @@ import bootbox from 'bootbox';
import {
cancelRegistryRepositoryAction,
confirmAsync,
confirmWarn,
confirmDestructive,
confirmDestructiveAsync,
confirmDisassociate,
confirmDeletion,
confirmDetachment,
@@ -42,6 +45,9 @@ export function ModalServiceAngular() {
enlargeImage,
confirmWebEditorDiscard,
confirmAsync,
confirmWarn,
confirmDestructive,
confirmDestructiveAsync,
confirm,
confirmImageForceRemoval,
cancelRegistryRepositoryAction,

View File

@@ -1,8 +1,13 @@
import sanitize from 'sanitize-html';
import bootbox from 'bootbox';
import '@@/BoxSelector/BoxSelectorItem.css';
import { applyBoxCSS, ButtonsOptions, confirmButtons } from './utils';
import {
applyBoxCSS,
ButtonsOptions,
confirmButtons,
buildTitle,
ModalTypeIcon,
} from './utils';
type PromptCallback = ((value: string) => void) | ((value: string[]) => void);
@@ -60,10 +65,8 @@ export function confirmContainerDeletion(
title: string,
callback: PromptCallback
) {
const sanitizedTitle = sanitize(title);
prompt({
title: sanitizedTitle,
title: buildTitle(title, ModalTypeIcon.Destructive),
inputType: 'checkbox',
inputOptions: [
{
@@ -90,7 +93,7 @@ export function confirmContainerRecreation(
callback: PromptCallback
) {
const box = prompt({
title: 'Are you sure?',
title: buildTitle('Are you sure?', ModalTypeIcon.Destructive),
inputType: 'checkbox',
inputOptions: [
@@ -137,7 +140,7 @@ export function confirmServiceForceUpdate(
const sanitizedMessage = sanitize(message);
const box = prompt({
title: 'Are you sure?',
title: buildTitle('Are you sure?'),
inputType: 'checkbox',
inputOptions: [
{
@@ -164,8 +167,10 @@ export function confirmStackUpdate(
confirmButtonClassName: string | undefined,
callback: PromptCallback
) {
const sanitizedMessage = sanitize(message);
const box = prompt({
title: 'Are you sure?',
title: buildTitle('Are you sure?'),
inputType: 'checkbox',
inputOptions: [
{
@@ -181,7 +186,7 @@ export function confirmStackUpdate(
},
callback,
});
box.find('.bootbox-body').prepend(message);
box.find('.bootbox-body').prepend(sanitizedMessage);
const checkbox = box.find('.bootbox-input-checkbox');
checkbox.prop('checked', defaultToggle);
checkbox.prop('disabled', defaultDisabled);

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