diff --git a/api/http/handler/endpointproxy/handler.go b/api/http/handler/endpointproxy/handler.go
index 037cb4dfb..ec3da1e7b 100644
--- a/api/http/handler/endpointproxy/handler.go
+++ b/api/http/handler/endpointproxy/handler.go
@@ -29,6 +29,10 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.proxyRequestsToDockerAPI)))
h.PathPrefix("/{id}/kubernetes").Handler(
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.proxyRequestsToKubernetesAPI)))
+ h.PathPrefix("/{id}/agent/docker").Handler(
+ bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.proxyRequestsToDockerAPI)))
+ h.PathPrefix("/{id}/agent/kubernetes").Handler(
+ bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.proxyRequestsToKubernetesAPI)))
h.PathPrefix("/{id}/storidge").Handler(
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.proxyRequestsToStoridgeAPI)))
return h
diff --git a/api/http/handler/endpointproxy/proxy_docker.go b/api/http/handler/endpointproxy/proxy_docker.go
index c4671506d..9ac89edbc 100644
--- a/api/http/handler/endpointproxy/proxy_docker.go
+++ b/api/http/handler/endpointproxy/proxy_docker.go
@@ -3,6 +3,7 @@ package endpointproxy
import (
"errors"
"strconv"
+ "strings"
"time"
httperror "github.com/portainer/libhttp/error"
@@ -65,6 +66,12 @@ func (handler *Handler) proxyRequestsToDockerAPI(w http.ResponseWriter, r *http.
}
id := strconv.Itoa(endpointID)
- http.StripPrefix("/"+id+"/docker", proxy).ServeHTTP(w, r)
+
+ prefix := "/" + id + "/agent/docker";
+ if !strings.HasPrefix(r.URL.Path, prefix) {
+ prefix = "/" + id + "/docker";
+ }
+
+ http.StripPrefix(prefix, proxy).ServeHTTP(w, r)
return nil
}
diff --git a/api/http/handler/endpointproxy/proxy_kubernetes.go b/api/http/handler/endpointproxy/proxy_kubernetes.go
index 1f6ed3bfd..88a369486 100644
--- a/api/http/handler/endpointproxy/proxy_kubernetes.go
+++ b/api/http/handler/endpointproxy/proxy_kubernetes.go
@@ -65,17 +65,18 @@ func (handler *Handler) proxyRequestsToKubernetesAPI(w http.ResponseWriter, r *h
}
}
+ // For KubernetesLocalEnvironment
requestPrefix := fmt.Sprintf("/%d/kubernetes", endpointID)
+
if endpoint.Type == portainer.AgentOnKubernetesEnvironment || endpoint.Type == portainer.EdgeAgentOnKubernetesEnvironment {
- if isKubernetesRequest(strings.TrimPrefix(r.URL.String(), requestPrefix)) {
- requestPrefix = fmt.Sprintf("/%d", endpointID)
+ requestPrefix = fmt.Sprintf("/%d", endpointID)
+
+ agentPrefix := fmt.Sprintf("/%d/agent/kubernetes", endpointID)
+ if strings.HasPrefix(r.URL.Path, agentPrefix) {
+ requestPrefix = agentPrefix
}
}
http.StripPrefix(requestPrefix, proxy).ServeHTTP(w, r)
return nil
-}
-
-func isKubernetesRequest(requestURL string) bool {
- return strings.HasPrefix(requestURL, "/api") || strings.HasPrefix(requestURL, "/healthz")
-}
+}
\ No newline at end of file
diff --git a/api/http/handler/handler.go b/api/http/handler/handler.go
index b924ad660..a5c4e1dd5 100644
--- a/api/http/handler/handler.go
+++ b/api/http/handler/handler.go
@@ -176,6 +176,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.StripPrefix("/api/endpoints", h.EndpointProxyHandler).ServeHTTP(w, r)
case strings.Contains(r.URL.Path, "/azure/"):
http.StripPrefix("/api/endpoints", h.EndpointProxyHandler).ServeHTTP(w, r)
+ case strings.Contains(r.URL.Path, "/agent/"):
+ http.StripPrefix("/api/endpoints", h.EndpointProxyHandler).ServeHTTP(w, r)
case strings.Contains(r.URL.Path, "/edge/"):
http.StripPrefix("/api/endpoints", h.EndpointEdgeHandler).ServeHTTP(w, r)
default:
diff --git a/api/http/handler/kubernetes/kubernetes_config.go b/api/http/handler/kubernetes/kubernetes_config.go
index c9c4c21e4..f2b1ac5d3 100644
--- a/api/http/handler/kubernetes/kubernetes_config.go
+++ b/api/http/handler/kubernetes/kubernetes_config.go
@@ -34,14 +34,6 @@ import (
// @failure 500 "Server error"
// @router /kubernetes/{id}/config [get]
func (handler *Handler) getKubernetesConfig(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
- if r.TLS == nil {
- return &httperror.HandlerError{
- StatusCode: http.StatusInternalServerError,
- Message: "Kubernetes config generation only supported on portainer instances running with TLS",
- Err: errors.New("missing request TLS config"),
- }
- }
-
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid endpoint identifier route variable", err}
@@ -76,17 +68,19 @@ func (handler *Handler) getKubernetesConfig(w http.ResponseWriter, r *http.Reque
return &httperror.HandlerError{http.StatusNotFound, "Unable to generate Kubeconfig", err}
}
+ filenameBase := fmt.Sprintf("%s-%s", tokenData.Username, endpoint.Name)
contentAcceptHeader := r.Header.Get("Accept")
if contentAcceptHeader == "text/yaml" {
yaml, err := kcli.GenerateYAML(config)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Failed to generate Kubeconfig", err}
}
- w.Header().Set("Content-Disposition", `attachment; filename=config.yaml`)
+
+ w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; %s.yaml", filenameBase))
return YAML(w, yaml)
}
- w.Header().Set("Content-Disposition", `attachment; filename="config.json"`)
+ w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; %s.json", filenameBase))
return response.JSON(w, config)
}
diff --git a/app/agent/rest/dockerhub.js b/app/agent/rest/dockerhub.js
index eb901b494..369feda3e 100644
--- a/app/agent/rest/dockerhub.js
+++ b/app/agent/rest/dockerhub.js
@@ -4,7 +4,7 @@ angular.module('portainer.agent').factory('AgentDockerhub', AgentDockerhub);
function AgentDockerhub($resource, API_ENDPOINT_ENDPOINTS) {
return $resource(
- `${API_ENDPOINT_ENDPOINTS}/:endpointId/:endpointType/v2/dockerhub/:registryId`,
+ `${API_ENDPOINT_ENDPOINTS}/:endpointId/agent/:endpointType/v2/dockerhub/:registryId`,
{},
{
limits: { method: 'GET', params: { registryId: '@registryId' } },
diff --git a/app/app.js b/app/app.js
index bf795ccc6..5e94c74f0 100644
--- a/app/app.js
+++ b/app/app.js
@@ -31,10 +31,6 @@ angular.module('portainer').run([
HttpRequestHelper.resetAgentHeaders();
});
- $state.defaultErrorHandler(function () {
- // Do not log transitionTo errors
- });
-
// Keep-alive Edge endpoints by sending a ping request every minute
$interval(function () {
ping(EndpointProvider, SystemService);
diff --git a/app/assets/css/app.css b/app/assets/css/app.css
index 8d946337d..d88bfc110 100644
--- a/app/assets/css/app.css
+++ b/app/assets/css/app.css
@@ -376,98 +376,6 @@ a[ng-click] {
margin: 0 auto;
}
-ul.sidebar {
- position: relative;
- overflow: hidden;
- flex-shrink: 0;
-}
-
-ul.sidebar .sidebar-title {
- height: auto;
-}
-
-ul.sidebar .sidebar-title.endpoint-name {
- color: #fff;
- text-align: center;
- text-indent: 0;
-}
-
-ul.sidebar .sidebar-list a {
- font-size: 14px;
-}
-
-ul.sidebar .sidebar-list a.active {
- color: #fff;
- text-indent: 22px;
- border-left: 3px solid #fff;
- background: #2d3e63;
-}
-
-.sidebar-header {
- height: 60px;
- list-style: none;
- text-indent: 20px;
- font-size: 18px;
- background: #2d3e63;
-}
-
-.sidebar-header a {
- color: #fff;
-}
-.sidebar-header a:hover {
- text-decoration: none;
-}
-
-.sidebar-header .menu-icon {
- float: right;
- padding-right: 28px;
- line-height: 60px;
-}
-
-#page-wrapper:not(.open) .sidebar-footer-content {
- display: none;
-}
-
-.sidebar-footer-content {
- text-align: center;
-}
-
-.sidebar-footer-content .logo {
- width: 100%;
- max-width: 100px;
- height: 100%;
- max-height: 35px;
- margin: 2px 0 2px 20px;
-}
-
-.sidebar-footer-content .update-notification {
- font-size: 14px;
- padding: 12px;
- border-radius: 2px;
- background-color: #ff851b;
- margin-bottom: 5px;
-}
-
-.sidebar-footer-content .version {
- font-size: 11px;
- margin: 11px 20px 0 7px;
- color: #fff;
-}
-
-#sidebar-wrapper {
- display: flex;
- flex-flow: column;
-}
-
-.sidebar-content {
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- overflow-y: auto;
- overflow-x: hidden;
- height: 100%;
-}
-
#image-layers .btn {
padding: 0;
}
@@ -481,86 +389,6 @@ ul.sidebar .sidebar-list a.active {
font-size: 90%;
}
-ul.sidebar .sidebar-list a.active .menu-icon {
- text-indent: 25px;
-}
-
-ul.sidebar .sidebar-list .sidebar-sublist a {
- text-indent: 35px;
- font-size: 12px;
- color: #b2bfdc;
- line-height: 36px;
-}
-
-ul.sidebar .sidebar-title {
- line-height: 36px;
-}
-ul.sidebar .sidebar-title .form-control {
- height: 36px;
- padding: 6px 12px;
-}
-
-ul.sidebar .sidebar-list {
- height: 36px;
-}
-
-ul.sidebar .sidebar-list a,
-ul.sidebar .sidebar-list .sidebar-sublist a {
- line-height: 36px;
-}
-
-ul.sidebar .sidebar-list .menu-icon {
- line-height: 36px;
-}
-
-ul.sidebar .sidebar-list .sidebar-sublist a.active {
- color: #fff;
- border-left: 3px solid #fff;
- background: #2d3e63;
-}
-
-@media (max-height: 785px) {
- ul.sidebar .sidebar-title {
- line-height: 26px;
- }
- ul.sidebar .sidebar-title .form-control {
- height: 26px;
- padding: 3px 6px;
- }
- ul.sidebar .sidebar-list {
- height: 26px;
- }
- ul.sidebar .sidebar-list a,
- ul.sidebar .sidebar-list .sidebar-sublist a {
- font-size: 12px;
- line-height: 26px;
- }
- ul.sidebar .sidebar-list .menu-icon {
- line-height: 26px;
- }
-}
-
-@media (min-height: 786px) and (max-height: 924px) {
- ul.sidebar .sidebar-title {
- line-height: 30px;
- }
- ul.sidebar .sidebar-title .form-control {
- height: 30px;
- padding: 5px 10px;
- }
- ul.sidebar .sidebar-list {
- height: 30px;
- }
- ul.sidebar .sidebar-list a,
- ul.sidebar .sidebar-list .sidebar-sublist a {
- font-size: 12px;
- line-height: 30px;
- }
- ul.sidebar .sidebar-list .menu-icon {
- line-height: 30px;
- }
-}
-
@media (min-width: 768px) {
.margin-sm-top {
margin-top: 5px;
diff --git a/app/assets/css/rdash.css b/app/assets/css/rdash.css
index 950d8fd8d..a9dc4ff48 100644
--- a/app/assets/css/rdash.css
+++ b/app/assets/css/rdash.css
@@ -14,9 +14,6 @@
padding-left: 70px;
}
}
-#page-wrapper.open #sidebar-wrapper {
- left: 150px;
-}
/**
* Hamburg Menu
@@ -254,139 +251,6 @@ div.input-mask {
padding-top: 7px;
}
-/* #592727 RED */
-/* #2f5927 GREEN */
-/* #30426a BLUE (default)*/
-/* Sidebar background color */
-/* Sidebar header and footer color */
-/* Sidebar title text colour */
-/* Sidebar menu item hover color */
-/**
- * Sidebar
- */
-#sidebar-wrapper {
- background: #30426a;
-}
-ul.sidebar .sidebar-main a,
-.sidebar-footer,
-ul.sidebar .sidebar-list a:hover,
-#page-wrapper:not(.open) ul.sidebar .sidebar-title.separator {
- /* Sidebar header and footer color */
- background: #2d3e63;
-}
-ul.sidebar {
- position: absolute;
- top: 0;
- bottom: 0;
- padding: 0;
- margin: 0;
- list-style: none;
- text-indent: 20px;
- overflow-x: hidden;
- overflow-y: auto;
-}
-ul.sidebar li a {
- color: #fff;
- display: block;
- float: left;
- text-decoration: none;
- width: 250px;
-}
-ul.sidebar .sidebar-main {
- height: 65px;
-}
-ul.sidebar .sidebar-main a {
- font-size: 18px;
- line-height: 60px;
-}
-ul.sidebar .sidebar-main a:hover {
- cursor: pointer;
-}
-ul.sidebar .sidebar-main .menu-icon {
- float: right;
- font-size: 18px;
- padding-right: 28px;
- line-height: 60px;
-}
-ul.sidebar .sidebar-title {
- color: #738bc0;
- font-size: 12px;
- height: 35px;
- line-height: 40px;
- text-transform: uppercase;
- transition: all 0.6s ease 0s;
-}
-ul.sidebar .sidebar-list {
- height: 40px;
-}
-ul.sidebar .sidebar-list a {
- text-indent: 25px;
- font-size: 15px;
- color: #b2bfdc;
- line-height: 40px;
-}
-ul.sidebar .sidebar-list a:hover {
- color: #fff;
- border-left: 3px solid #e99d1a;
- text-indent: 22px;
-}
-ul.sidebar .sidebar-list a:hover .menu-icon {
- text-indent: 25px;
-}
-ul.sidebar .sidebar-list .menu-icon {
- float: right;
- padding-right: 29px;
- line-height: 40px;
- width: 70px;
-}
-#page-wrapper:not(.open) ul.sidebar {
- bottom: 0;
-}
-#page-wrapper:not(.open) ul.sidebar .sidebar-title {
- display: none;
- height: 0px;
- text-indent: -100px;
-}
-#page-wrapper:not(.open) ul.sidebar .sidebar-title.separator {
- display: block;
- height: 2px;
- margin: 13px 0;
-}
-#page-wrapper:not(.open) ul.sidebar .sidebar-list a:hover span {
- border-left: 3px solid #e99d1a;
- text-indent: 22px;
-}
-#page-wrapper:not(.open) .sidebar-footer {
- display: none;
-}
-.sidebar-footer {
- position: absolute;
- height: 40px;
- bottom: 0;
- width: 100%;
- padding: 0;
- margin: 0;
- transition: all 0.6s ease 0s;
- text-align: center;
-}
-.sidebar-footer div a {
- color: #b2bfdc;
- font-size: 12px;
- line-height: 43px;
-}
-.sidebar-footer div a:hover {
- color: #ffffff;
- text-decoration: none;
-}
-
-/* #592727 RED */
-/* #2f5927 GREEN */
-/* #30426a BLUE (default)*/
-/* Sidebar background color */
-/* Sidebar header and footer color */
-/* Sidebar title text colour */
-/* Sidebar menu item hover color */
-
/**
* Widgets
*/
diff --git a/app/azure/components/azure-sidebar-content/azure-sidebar-content.js b/app/azure/components/azure-sidebar-content/azure-sidebar-content.js
deleted file mode 100644
index 1cdf11ca3..000000000
--- a/app/azure/components/azure-sidebar-content/azure-sidebar-content.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import angular from 'angular';
-
-angular.module('portainer.azure').component('azureSidebarContent', {
- templateUrl: './azureSidebarContent.html',
- bindings: {
- endpointId: '<',
- },
-});
diff --git a/app/azure/components/azure-sidebar-content/azureSidebarContent.html b/app/azure/components/azure-sidebar-content/azureSidebarContent.html
deleted file mode 100644
index d6e68d12b..000000000
--- a/app/azure/components/azure-sidebar-content/azureSidebarContent.html
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
diff --git a/app/azure/components/azure-sidebar/azure-sidebar.html b/app/azure/components/azure-sidebar/azure-sidebar.html
new file mode 100644
index 000000000..7f6c5345a
--- /dev/null
+++ b/app/azure/components/azure-sidebar/azure-sidebar.html
@@ -0,0 +1,7 @@
+
+ Dashboard
+
+
+
+ Container instances
+
diff --git a/app/azure/components/azure-sidebar/index.js b/app/azure/components/azure-sidebar/index.js
new file mode 100644
index 000000000..5b5b13f8b
--- /dev/null
+++ b/app/azure/components/azure-sidebar/index.js
@@ -0,0 +1,8 @@
+import angular from 'angular';
+
+angular.module('portainer.azure').component('azureSidebar', {
+ templateUrl: './azure-sidebar.html',
+ bindings: {
+ endpointId: '<',
+ },
+});
diff --git a/app/docker/components/docker-sidebar/docker-sidebar.html b/app/docker/components/docker-sidebar/docker-sidebar.html
new file mode 100644
index 000000000..59866f008
--- /dev/null
+++ b/app/docker/components/docker-sidebar/docker-sidebar.html
@@ -0,0 +1,121 @@
+
+ Dashboard
+
+
+
+
+ Custom Templates
+
+
+
+
+ Stacks
+
+
+
+ Services
+
+
+
+ Containers
+
+
+
+ Images
+
+
+
+ Networks
+
+
+
+ Volumes
+
+
+
+ Configs
+
+
+
+ Secrets
+
+
+
+ Events
+
+
+
+
+
+ Setup
+
+
+
+ Registries
+
+
+
+
+
+
+
+ Setup
+
+
+
+ Registries
+
+
+
diff --git a/app/docker/components/dockerSidebarContent/docker-sidebar-content.js b/app/docker/components/docker-sidebar/docker-sidebar.js
similarity index 62%
rename from app/docker/components/dockerSidebarContent/docker-sidebar-content.js
rename to app/docker/components/docker-sidebar/docker-sidebar.js
index 088165ac3..65e679262 100644
--- a/app/docker/components/dockerSidebarContent/docker-sidebar-content.js
+++ b/app/docker/components/docker-sidebar/docker-sidebar.js
@@ -1,12 +1,13 @@
-angular.module('portainer.docker').component('dockerSidebarContent', {
- templateUrl: './dockerSidebarContent.html',
+angular.module('portainer.docker').component('dockerSidebar', {
+ templateUrl: './docker-sidebar.html',
bindings: {
+ isSidebarOpen: '<',
+
endpointApiVersion: '<',
swarmManagement: '<',
standaloneManagement: '<',
adminAccess: '<',
offlineMode: '<',
- toggle: '<',
currentRouteName: '<',
endpointId: '<',
showStacks: '<',
diff --git a/app/docker/components/dockerSidebarContent/dockerSidebarContent.html b/app/docker/components/dockerSidebarContent/dockerSidebarContent.html
deleted file mode 100644
index 086047756..000000000
--- a/app/docker/components/dockerSidebarContent/dockerSidebarContent.html
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/docker/services/imageService.js b/app/docker/services/imageService.js
index 2f04f8fb7..e22f0a3af 100644
--- a/app/docker/services/imageService.js
+++ b/app/docker/services/imageService.js
@@ -133,7 +133,7 @@ angular.module('portainer.docker').factory('ImageService', [
Image.create({}, imageConfiguration)
.$promise.then(function success(data) {
- var err = data.length > 0 && data[data.length - 1].hasOwnProperty('message');
+ var err = data.length > 0 && data[data.length - 1].message;
if (err) {
var detail = data[data.length - 1];
deferred.reject({ msg: detail.message });
diff --git a/app/integrations/storidge/services/nodeService.js b/app/integrations/storidge/services/nodeService.js
index 564b50881..1e506c17b 100644
--- a/app/integrations/storidge/services/nodeService.js
+++ b/app/integrations/storidge/services/nodeService.js
@@ -16,7 +16,7 @@ angular.module('portainer.integrations.storidge').factory('StoridgeNodeService',
var nodes = [];
for (var key in nodeData) {
- if (nodeData.hasOwnProperty(key)) {
+ if (Object.prototype.hasOwnProperty.call(nodeData, key)) {
nodes.push(new StoridgeNodeModel(key, nodeData[key]));
}
}
diff --git a/app/integrations/storidge/services/snapshotService.js b/app/integrations/storidge/services/snapshotService.js
index 8edb18aa4..ae964e12d 100644
--- a/app/integrations/storidge/services/snapshotService.js
+++ b/app/integrations/storidge/services/snapshotService.js
@@ -20,7 +20,7 @@ angular.module('portainer.integrations.storidge').factory('StoridgeSnapshotServi
var snapshotsData = data.snapshots;
let snapshotsArray = [];
for (const key in snapshotsData) {
- if (snapshotsData.hasOwnProperty(key)) {
+ if (Object.prototype.hasOwnProperty.call(snapshotsData, key)) {
snapshotsArray.push(snapshotsData[key]);
}
}
diff --git a/app/kubernetes/components/kubectl-shell/kubectl-shell.controller.js b/app/kubernetes/components/kubectl-shell/kubectl-shell.controller.js
index 15e31baef..6fa2a5f58 100644
--- a/app/kubernetes/components/kubectl-shell/kubectl-shell.controller.js
+++ b/app/kubernetes/components/kubectl-shell/kubectl-shell.controller.js
@@ -39,6 +39,7 @@ export default class KubectlShellController {
}
configureSocketAndTerminal(socket, term) {
+ var vm = this;
socket.onopen = function () {
const terminal_container = document.getElementById('terminal-container');
term.open(terminal_container);
@@ -55,7 +56,7 @@ export default class KubectlShellController {
});
this.$window.onresize = function () {
- term.fit();
+ vm.TerminalWindow.terminalresize();
};
socket.onmessage = function (msg) {
diff --git a/app/kubernetes/components/kubectl-shell/kubectl-shell.css b/app/kubernetes/components/kubectl-shell/kubectl-shell.css
index 969252f19..829baf691 100644
--- a/app/kubernetes/components/kubectl-shell/kubectl-shell.css
+++ b/app/kubernetes/components/kubectl-shell/kubectl-shell.css
@@ -60,7 +60,7 @@ ul.sidebar li .shell-item-center a:hover {
bottom: 0;
left: 0;
width: 100vw;
- height: 480px;
+ height: 495px;
z-index: 1000;
}
diff --git a/app/kubernetes/components/kubernetes-sidebar-content/kubernetesSidebarContent.html b/app/kubernetes/components/kubernetes-sidebar-content/kubernetesSidebarContent.html
deleted file mode 100644
index f7439c9a6..000000000
--- a/app/kubernetes/components/kubernetes-sidebar-content/kubernetesSidebarContent.html
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
diff --git a/app/kubernetes/components/kubernetes-sidebar-content/kubernetesSidebarContent.js b/app/kubernetes/components/kubernetes-sidebar-content/kubernetesSidebarContent.js
deleted file mode 100644
index 8a44e8a8d..000000000
--- a/app/kubernetes/components/kubernetes-sidebar-content/kubernetesSidebarContent.js
+++ /dev/null
@@ -1,8 +0,0 @@
-angular.module('portainer.kubernetes').component('kubernetesSidebarContent', {
- templateUrl: './kubernetesSidebarContent.html',
- bindings: {
- adminAccess: '<',
- endpointId: '<',
- currentState: '<',
- },
-});
diff --git a/app/kubernetes/components/kubernetes-sidebar/index.js b/app/kubernetes/components/kubernetes-sidebar/index.js
new file mode 100644
index 000000000..def39be7a
--- /dev/null
+++ b/app/kubernetes/components/kubernetes-sidebar/index.js
@@ -0,0 +1,9 @@
+import angular from 'angular';
+
+angular.module('portainer.kubernetes').component('kubernetesSidebar', {
+ templateUrl: './kubernetes-sidebar.html',
+ bindings: {
+ endpointId: '<',
+ isSidebarOpen: '<',
+ },
+});
diff --git a/app/kubernetes/components/kubernetes-sidebar/kubernetes-sidebar.html b/app/kubernetes/components/kubernetes-sidebar/kubernetes-sidebar.html
new file mode 100644
index 000000000..dc6901a06
--- /dev/null
+++ b/app/kubernetes/components/kubernetes-sidebar/kubernetes-sidebar.html
@@ -0,0 +1,38 @@
+
+ Dashboard
+
+
+
+ Namespaces
+
+
+
+ Applications
+
+
+
+ Configurations
+
+
+
+ Volumes
+
+
+
+
+
+ Setup
+
+
+
+ Registries
+
+
+
diff --git a/app/kubernetes/services/applicationService.js b/app/kubernetes/services/applicationService.js
index 0f13b5dd2..fa80b8e85 100644
--- a/app/kubernetes/services/applicationService.js
+++ b/app/kubernetes/services/applicationService.js
@@ -89,122 +89,110 @@ class KubernetesApplicationService {
/* #region GET */
async getAsync(namespace, name) {
- try {
- const [deployment, daemonSet, statefulSet, pod, pods, autoScalers, ingresses] = await Promise.allSettled([
- this.KubernetesDeploymentService.get(namespace, name),
- this.KubernetesDaemonSetService.get(namespace, name),
- this.KubernetesStatefulSetService.get(namespace, name),
- this.KubernetesPodService.get(namespace, name),
- this.KubernetesPodService.get(namespace),
- this.KubernetesHorizontalPodAutoScalerService.get(namespace),
- this.KubernetesIngressService.get(namespace),
- ]);
+ const [deployment, daemonSet, statefulSet, pod, pods, autoScalers, ingresses] = await Promise.allSettled([
+ this.KubernetesDeploymentService.get(namespace, name),
+ this.KubernetesDaemonSetService.get(namespace, name),
+ this.KubernetesStatefulSetService.get(namespace, name),
+ this.KubernetesPodService.get(namespace, name),
+ this.KubernetesPodService.get(namespace),
+ this.KubernetesHorizontalPodAutoScalerService.get(namespace),
+ this.KubernetesIngressService.get(namespace),
+ ]);
- // const pod = _.find(pods.value, ['metadata.namespace', namespace, 'metadata.name', name]);
-
- let rootItem;
- let converterFunc;
- if (deployment.status === 'fulfilled') {
- rootItem = deployment;
- converterFunc = KubernetesApplicationConverter.apiDeploymentToApplication;
- } else if (daemonSet.status === 'fulfilled') {
- rootItem = daemonSet;
- converterFunc = KubernetesApplicationConverter.apiDaemonSetToApplication;
- } else if (statefulSet.status === 'fulfilled') {
- rootItem = statefulSet;
- converterFunc = KubernetesApplicationConverter.apiStatefulSetToapplication;
- } else if (pod.status === 'fulfilled') {
- rootItem = pod;
- converterFunc = KubernetesApplicationConverter.apiPodToApplication;
- } else {
- throw new PortainerError('Unable to determine which association to use to convert application');
- }
-
- const services = await this.KubernetesServiceService.get(namespace);
- const boundService = KubernetesServiceHelper.findApplicationBoundService(services, rootItem.value.Raw);
- const service = boundService ? await this.KubernetesServiceService.get(namespace, boundService.metadata.name) : {};
-
- const application = converterFunc(rootItem.value.Raw, pods.value, service.Raw, ingresses.value);
- application.Yaml = rootItem.value.Yaml;
- application.Raw = rootItem.value.Raw;
- application.Pods = _.map(application.Pods, (item) => KubernetesPodConverter.apiToModel(item));
- application.Containers = KubernetesApplicationHelper.associateContainersAndApplication(application);
-
- const boundScaler = KubernetesHorizontalPodAutoScalerHelper.findApplicationBoundScaler(autoScalers.value, application);
- const scaler = boundScaler ? await this.KubernetesHorizontalPodAutoScalerService.get(namespace, boundScaler.Name) : undefined;
- application.AutoScaler = scaler;
-
- await this.KubernetesHistoryService.get(application);
-
- if (service.Yaml) {
- application.Yaml += '---\n' + service.Yaml;
- }
- if (scaler && scaler.Yaml) {
- application.Yaml += '---\n' + scaler.Yaml;
- }
- // TODO: refactor @LP
- // append ingress yaml ?
- return application;
- } catch (err) {
- throw err;
+ let rootItem;
+ let converterFunc;
+ if (deployment.status === 'fulfilled') {
+ rootItem = deployment;
+ converterFunc = KubernetesApplicationConverter.apiDeploymentToApplication;
+ } else if (daemonSet.status === 'fulfilled') {
+ rootItem = daemonSet;
+ converterFunc = KubernetesApplicationConverter.apiDaemonSetToApplication;
+ } else if (statefulSet.status === 'fulfilled') {
+ rootItem = statefulSet;
+ converterFunc = KubernetesApplicationConverter.apiStatefulSetToapplication;
+ } else if (pod.status === 'fulfilled') {
+ rootItem = pod;
+ converterFunc = KubernetesApplicationConverter.apiPodToApplication;
+ } else {
+ throw new PortainerError('Unable to determine which association to use to convert application');
}
+
+ const services = await this.KubernetesServiceService.get(namespace);
+ const boundService = KubernetesServiceHelper.findApplicationBoundService(services, rootItem.value.Raw);
+ const service = boundService ? await this.KubernetesServiceService.get(namespace, boundService.metadata.name) : {};
+
+ const application = converterFunc(rootItem.value.Raw, pods.value, service.Raw, ingresses.value);
+ application.Yaml = rootItem.value.Yaml;
+ application.Raw = rootItem.value.Raw;
+ application.Pods = _.map(application.Pods, (item) => KubernetesPodConverter.apiToModel(item));
+ application.Containers = KubernetesApplicationHelper.associateContainersAndApplication(application);
+
+ const boundScaler = KubernetesHorizontalPodAutoScalerHelper.findApplicationBoundScaler(autoScalers.value, application);
+ const scaler = boundScaler ? await this.KubernetesHorizontalPodAutoScalerService.get(namespace, boundScaler.Name) : undefined;
+ application.AutoScaler = scaler;
+
+ await this.KubernetesHistoryService.get(application);
+
+ if (service.Yaml) {
+ application.Yaml += '---\n' + service.Yaml;
+ }
+ if (scaler && scaler.Yaml) {
+ application.Yaml += '---\n' + scaler.Yaml;
+ }
+ // TODO: refactor @LP
+ // append ingress yaml ?
+ return application;
}
async getAllAsync(namespace) {
- try {
- const namespaces = namespace ? [namespace] : _.map(await this.KubernetesNamespaceService.get(), 'Name');
+ const namespaces = namespace ? [namespace] : _.map(await this.KubernetesNamespaceService.get(), 'Name');
- const convertToApplication = (item, converterFunc, services, pods, ingresses) => {
- const service = KubernetesServiceHelper.findApplicationBoundService(services, item);
- const application = converterFunc(item, pods, service, ingresses);
- application.Containers = KubernetesApplicationHelper.associateContainersAndApplication(application);
- return application;
- };
+ const convertToApplication = (item, converterFunc, services, pods, ingresses) => {
+ const service = KubernetesServiceHelper.findApplicationBoundService(services, item);
+ const application = converterFunc(item, pods, service, ingresses);
+ application.Containers = KubernetesApplicationHelper.associateContainersAndApplication(application);
+ return application;
+ };
- const res = await Promise.all(
- _.map(namespaces, async (ns) => {
- const [deployments, daemonSets, statefulSets, services, pods, ingresses, autoScalers] = await Promise.all([
- this.KubernetesDeploymentService.get(ns),
- this.KubernetesDaemonSetService.get(ns),
- this.KubernetesStatefulSetService.get(ns),
- this.KubernetesServiceService.get(ns),
- this.KubernetesPodService.get(ns),
- this.KubernetesIngressService.get(ns),
- this.KubernetesHorizontalPodAutoScalerService.get(ns),
- ]);
+ const res = await Promise.all(
+ _.map(namespaces, async (ns) => {
+ const [deployments, daemonSets, statefulSets, services, pods, ingresses, autoScalers] = await Promise.all([
+ this.KubernetesDeploymentService.get(ns),
+ this.KubernetesDaemonSetService.get(ns),
+ this.KubernetesStatefulSetService.get(ns),
+ this.KubernetesServiceService.get(ns),
+ this.KubernetesPodService.get(ns),
+ this.KubernetesIngressService.get(ns),
+ this.KubernetesHorizontalPodAutoScalerService.get(ns),
+ ]);
- const deploymentApplications = _.map(deployments, (item) =>
- convertToApplication(item, KubernetesApplicationConverter.apiDeploymentToApplication, services, pods, ingresses)
- );
- const daemonSetApplications = _.map(daemonSets, (item) =>
- convertToApplication(item, KubernetesApplicationConverter.apiDaemonSetToApplication, services, pods, ingresses)
- );
- const statefulSetApplications = _.map(statefulSets, (item) =>
- convertToApplication(item, KubernetesApplicationConverter.apiStatefulSetToapplication, services, pods, ingresses)
- );
+ const deploymentApplications = _.map(deployments, (item) =>
+ convertToApplication(item, KubernetesApplicationConverter.apiDeploymentToApplication, services, pods, ingresses)
+ );
+ const daemonSetApplications = _.map(daemonSets, (item) => convertToApplication(item, KubernetesApplicationConverter.apiDaemonSetToApplication, services, pods, ingresses));
+ const statefulSetApplications = _.map(statefulSets, (item) =>
+ convertToApplication(item, KubernetesApplicationConverter.apiStatefulSetToapplication, services, pods, ingresses)
+ );
- const boundPods = _.concat(_.flatMap(deploymentApplications, 'Pods'), _.flatMap(daemonSetApplications, 'Pods'), _.flatMap(statefulSetApplications, 'Pods'));
- const unboundPods = _.without(pods, ...boundPods);
- const nakedPodsApplications = _.map(unboundPods, (item) => convertToApplication(item, KubernetesApplicationConverter.apiPodToApplication, services, pods, ingresses));
+ const boundPods = _.concat(_.flatMap(deploymentApplications, 'Pods'), _.flatMap(daemonSetApplications, 'Pods'), _.flatMap(statefulSetApplications, 'Pods'));
+ const unboundPods = _.without(pods, ...boundPods);
+ const nakedPodsApplications = _.map(unboundPods, (item) => convertToApplication(item, KubernetesApplicationConverter.apiPodToApplication, services, pods, ingresses));
- const applications = _.concat(deploymentApplications, daemonSetApplications, statefulSetApplications, nakedPodsApplications);
- _.forEach(applications, (app) => {
- app.Pods = _.map(app.Pods, (item) => KubernetesPodConverter.apiToModel(item));
- });
- await Promise.all(
- _.forEach(applications, async (application) => {
- const boundScaler = KubernetesHorizontalPodAutoScalerHelper.findApplicationBoundScaler(autoScalers, application);
- const scaler = boundScaler ? await this.KubernetesHorizontalPodAutoScalerService.get(ns, boundScaler.Name) : undefined;
- application.AutoScaler = scaler;
- })
- );
- return applications;
- })
- );
- return _.flatten(res);
- } catch (err) {
- throw err;
- }
+ const applications = _.concat(deploymentApplications, daemonSetApplications, statefulSetApplications, nakedPodsApplications);
+ _.forEach(applications, (app) => {
+ app.Pods = _.map(app.Pods, (item) => KubernetesPodConverter.apiToModel(item));
+ });
+ await Promise.all(
+ _.forEach(applications, async (application) => {
+ const boundScaler = KubernetesHorizontalPodAutoScalerHelper.findApplicationBoundScaler(autoScalers, application);
+ const scaler = boundScaler ? await this.KubernetesHorizontalPodAutoScalerService.get(ns, boundScaler.Name) : undefined;
+ application.AutoScaler = scaler;
+ })
+ );
+ return applications;
+ })
+ );
+ return _.flatten(res);
}
get(namespace, name) {
@@ -226,42 +214,38 @@ class KubernetesApplicationService {
* also be displayed in the summary output (getCreatedApplicationResources)
*/
async createAsync(formValues) {
- try {
- let [app, headlessService, service, claims] = KubernetesApplicationConverter.applicationFormValuesToApplication(formValues);
+ let [app, headlessService, service, claims] = KubernetesApplicationConverter.applicationFormValuesToApplication(formValues);
- if (service) {
- await this.KubernetesServiceService.create(service);
- if (formValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
- const ingresses = KubernetesIngressConverter.applicationFormValuesToIngresses(formValues, service.Name);
- await Promise.all(this._generateIngressPatchPromises(formValues.OriginalIngresses, ingresses));
- }
+ if (service) {
+ await this.KubernetesServiceService.create(service);
+ if (formValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
+ const ingresses = KubernetesIngressConverter.applicationFormValuesToIngresses(formValues, service.Name);
+ await Promise.all(this._generateIngressPatchPromises(formValues.OriginalIngresses, ingresses));
}
-
- const apiService = this._getApplicationApiService(app);
-
- if (app instanceof KubernetesStatefulSet) {
- app.VolumeClaims = claims;
- headlessService = await this.KubernetesServiceService.create(headlessService);
- app.ServiceName = headlessService.metadata.name;
- } else {
- const claimPromises = _.map(claims, (item) => {
- if (!item.PreviousName && !item.Id) {
- return this.KubernetesPersistentVolumeClaimService.create(item);
- }
- });
- await Promise.all(_.without(claimPromises, undefined));
- }
-
- if (formValues.AutoScaler.IsUsed && formValues.DeploymentType !== KubernetesApplicationDeploymentTypes.GLOBAL) {
- const kind = KubernetesHorizontalPodAutoScalerHelper.getApplicationTypeString(app);
- const autoScaler = KubernetesHorizontalPodAutoScalerConverter.applicationFormValuesToModel(formValues, kind);
- await this.KubernetesHorizontalPodAutoScalerService.create(autoScaler);
- }
-
- await apiService.create(app);
- } catch (err) {
- throw err;
}
+
+ const apiService = this._getApplicationApiService(app);
+
+ if (app instanceof KubernetesStatefulSet) {
+ app.VolumeClaims = claims;
+ headlessService = await this.KubernetesServiceService.create(headlessService);
+ app.ServiceName = headlessService.metadata.name;
+ } else {
+ const claimPromises = _.map(claims, (item) => {
+ if (!item.PreviousName && !item.Id) {
+ return this.KubernetesPersistentVolumeClaimService.create(item);
+ }
+ });
+ await Promise.all(_.without(claimPromises, undefined));
+ }
+
+ if (formValues.AutoScaler.IsUsed && formValues.DeploymentType !== KubernetesApplicationDeploymentTypes.GLOBAL) {
+ const kind = KubernetesHorizontalPodAutoScalerHelper.getApplicationTypeString(app);
+ const autoScaler = KubernetesHorizontalPodAutoScalerConverter.applicationFormValuesToModel(formValues, kind);
+ await this.KubernetesHorizontalPodAutoScalerService.create(autoScaler);
+ }
+
+ await apiService.create(app);
}
create(formValues) {
@@ -277,97 +261,89 @@ class KubernetesApplicationService {
* in this method should also be displayed in the summary output (getUpdatedApplicationResources)
*/
async patchAsync(oldFormValues, newFormValues) {
- try {
- const [oldApp, oldHeadlessService, oldService, oldClaims] = KubernetesApplicationConverter.applicationFormValuesToApplication(oldFormValues);
- const [newApp, newHeadlessService, newService, newClaims] = KubernetesApplicationConverter.applicationFormValuesToApplication(newFormValues);
- const oldApiService = this._getApplicationApiService(oldApp);
- const newApiService = this._getApplicationApiService(newApp);
+ const [oldApp, oldHeadlessService, oldService, oldClaims] = KubernetesApplicationConverter.applicationFormValuesToApplication(oldFormValues);
+ const [newApp, newHeadlessService, newService, newClaims] = KubernetesApplicationConverter.applicationFormValuesToApplication(newFormValues);
+ const oldApiService = this._getApplicationApiService(oldApp);
+ const newApiService = this._getApplicationApiService(newApp);
- if (oldApiService !== newApiService) {
- await this.delete(oldApp);
- if (oldService) {
- await this.KubernetesServiceService.delete(oldService);
- }
- return await this.create(newFormValues);
- }
-
- if (newApp instanceof KubernetesStatefulSet) {
- await this.KubernetesServiceService.patch(oldHeadlessService, newHeadlessService);
- } else {
- const claimPromises = _.map(newClaims, (newClaim) => {
- if (!newClaim.PreviousName && !newClaim.Id) {
- return this.KubernetesPersistentVolumeClaimService.create(newClaim);
- } else if (!newClaim.Id) {
- const oldClaim = _.find(oldClaims, { Name: newClaim.PreviousName });
- return this.KubernetesPersistentVolumeClaimService.patch(oldClaim, newClaim);
- }
- });
- await Promise.all(claimPromises);
- }
-
- await newApiService.patch(oldApp, newApp);
-
- if (oldService && newService) {
- await this.KubernetesServiceService.patch(oldService, newService);
- if (newFormValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS || oldFormValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
- const oldIngresses = KubernetesIngressConverter.applicationFormValuesToIngresses(oldFormValues, oldService.Name);
- const newIngresses = KubernetesIngressConverter.applicationFormValuesToIngresses(newFormValues, newService.Name);
- await Promise.all(this._generateIngressPatchPromises(oldIngresses, newIngresses));
- }
- } else if (!oldService && newService) {
- await this.KubernetesServiceService.create(newService);
- if (newFormValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
- const ingresses = KubernetesIngressConverter.applicationFormValuesToIngresses(newFormValues, newService.Name);
- await Promise.all(this._generateIngressPatchPromises(newFormValues.OriginalIngresses, ingresses));
- }
- } else if (oldService && !newService) {
+ if (oldApiService !== newApiService) {
+ await this.delete(oldApp);
+ if (oldService) {
await this.KubernetesServiceService.delete(oldService);
- if (oldFormValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
- const ingresses = KubernetesIngressConverter.applicationFormValuesToIngresses(newFormValues, oldService.Name);
- await Promise.all(this._generateIngressPatchPromises(oldFormValues.OriginalIngresses, ingresses));
- }
}
+ return await this.create(newFormValues);
+ }
- const newKind = KubernetesHorizontalPodAutoScalerHelper.getApplicationTypeString(newApp);
- const newAutoScaler = KubernetesHorizontalPodAutoScalerConverter.applicationFormValuesToModel(newFormValues, newKind);
- if (!oldFormValues.AutoScaler.IsUsed) {
- if (newFormValues.AutoScaler.IsUsed) {
- await this.KubernetesHorizontalPodAutoScalerService.create(newAutoScaler);
- }
- } else {
- const oldKind = KubernetesHorizontalPodAutoScalerHelper.getApplicationTypeString(oldApp);
- const oldAutoScaler = KubernetesHorizontalPodAutoScalerConverter.applicationFormValuesToModel(oldFormValues, oldKind);
- if (newFormValues.AutoScaler.IsUsed) {
- await this.KubernetesHorizontalPodAutoScalerService.patch(oldAutoScaler, newAutoScaler);
- } else {
- await this.KubernetesHorizontalPodAutoScalerService.delete(oldAutoScaler);
+ if (newApp instanceof KubernetesStatefulSet) {
+ await this.KubernetesServiceService.patch(oldHeadlessService, newHeadlessService);
+ } else {
+ const claimPromises = _.map(newClaims, (newClaim) => {
+ if (!newClaim.PreviousName && !newClaim.Id) {
+ return this.KubernetesPersistentVolumeClaimService.create(newClaim);
+ } else if (!newClaim.Id) {
+ const oldClaim = _.find(oldClaims, { Name: newClaim.PreviousName });
+ return this.KubernetesPersistentVolumeClaimService.patch(oldClaim, newClaim);
}
+ });
+ await Promise.all(claimPromises);
+ }
+
+ await newApiService.patch(oldApp, newApp);
+
+ if (oldService && newService) {
+ await this.KubernetesServiceService.patch(oldService, newService);
+ if (newFormValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS || oldFormValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
+ const oldIngresses = KubernetesIngressConverter.applicationFormValuesToIngresses(oldFormValues, oldService.Name);
+ const newIngresses = KubernetesIngressConverter.applicationFormValuesToIngresses(newFormValues, newService.Name);
+ await Promise.all(this._generateIngressPatchPromises(oldIngresses, newIngresses));
+ }
+ } else if (!oldService && newService) {
+ await this.KubernetesServiceService.create(newService);
+ if (newFormValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
+ const ingresses = KubernetesIngressConverter.applicationFormValuesToIngresses(newFormValues, newService.Name);
+ await Promise.all(this._generateIngressPatchPromises(newFormValues.OriginalIngresses, ingresses));
+ }
+ } else if (oldService && !newService) {
+ await this.KubernetesServiceService.delete(oldService);
+ if (oldFormValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
+ const ingresses = KubernetesIngressConverter.applicationFormValuesToIngresses(newFormValues, oldService.Name);
+ await Promise.all(this._generateIngressPatchPromises(oldFormValues.OriginalIngresses, ingresses));
+ }
+ }
+
+ const newKind = KubernetesHorizontalPodAutoScalerHelper.getApplicationTypeString(newApp);
+ const newAutoScaler = KubernetesHorizontalPodAutoScalerConverter.applicationFormValuesToModel(newFormValues, newKind);
+ if (!oldFormValues.AutoScaler.IsUsed) {
+ if (newFormValues.AutoScaler.IsUsed) {
+ await this.KubernetesHorizontalPodAutoScalerService.create(newAutoScaler);
+ }
+ } else {
+ const oldKind = KubernetesHorizontalPodAutoScalerHelper.getApplicationTypeString(oldApp);
+ const oldAutoScaler = KubernetesHorizontalPodAutoScalerConverter.applicationFormValuesToModel(oldFormValues, oldKind);
+ if (newFormValues.AutoScaler.IsUsed) {
+ await this.KubernetesHorizontalPodAutoScalerService.patch(oldAutoScaler, newAutoScaler);
+ } else {
+ await this.KubernetesHorizontalPodAutoScalerService.delete(oldAutoScaler);
}
- } catch (err) {
- throw err;
}
}
// this function accepts KubernetesApplication as parameters
async patchPartialAsync(oldApp, newApp) {
- try {
- const oldAppPayload = {
- Name: oldApp.Name,
- Namespace: oldApp.ResourcePool,
- StackName: oldApp.StackName,
- Note: oldApp.Note,
- };
- const newAppPayload = {
- Name: newApp.Name,
- Namespace: newApp.ResourcePool,
- StackName: newApp.StackName,
- Note: newApp.Note,
- };
- const apiService = this._getApplicationApiService(oldApp);
- await apiService.patch(oldAppPayload, newAppPayload);
- } catch (err) {
- throw err;
- }
+ const oldAppPayload = {
+ Name: oldApp.Name,
+ Namespace: oldApp.ResourcePool,
+ StackName: oldApp.StackName,
+ Note: oldApp.Note,
+ };
+ const newAppPayload = {
+ Name: newApp.Name,
+ Namespace: newApp.ResourcePool,
+ StackName: newApp.StackName,
+ Note: newApp.Note,
+ };
+ const apiService = this._getApplicationApiService(oldApp);
+ await apiService.patch(oldAppPayload, newAppPayload);
}
// accept either formValues or applications as parameters
@@ -384,42 +360,38 @@ class KubernetesApplicationService {
/* #region DELETE */
async deleteAsync(application) {
- try {
- const payload = {
- Namespace: application.ResourcePool || application.Namespace,
- Name: application.Name,
- };
- const servicePayload = angular.copy(payload);
- servicePayload.Name = application.Name;
+ const payload = {
+ Namespace: application.ResourcePool || application.Namespace,
+ Name: application.Name,
+ };
+ const servicePayload = angular.copy(payload);
+ servicePayload.Name = application.Name;
- const apiService = this._getApplicationApiService(application);
- await apiService.delete(payload);
+ const apiService = this._getApplicationApiService(application);
+ await apiService.delete(payload);
- if (apiService === this.KubernetesStatefulSetService) {
- const headlessServicePayload = angular.copy(payload);
- headlessServicePayload.Name = application instanceof KubernetesStatefulSet ? application.ServiceName : application.HeadlessServiceName;
- await this.KubernetesServiceService.delete(headlessServicePayload);
- }
+ if (apiService === this.KubernetesStatefulSetService) {
+ const headlessServicePayload = angular.copy(payload);
+ headlessServicePayload.Name = application instanceof KubernetesStatefulSet ? application.ServiceName : application.HeadlessServiceName;
+ await this.KubernetesServiceService.delete(headlessServicePayload);
+ }
- if (application.ServiceType) {
- await this.KubernetesServiceService.delete(servicePayload);
- const isIngress = _.filter(application.PublishedPorts, (p) => p.IngressRules.length).length;
- if (isIngress) {
- const originalIngresses = await this.KubernetesIngressService.get(payload.Namespace);
- const formValues = {
- OriginalIngresses: originalIngresses,
- PublishedPorts: KubernetesApplicationHelper.generatePublishedPortsFormValuesFromPublishedPorts(application.ServiceType, application.PublishedPorts),
- };
- _.forEach(formValues.PublishedPorts, (p) => (p.NeedsDeletion = true));
- const ingresses = KubernetesIngressConverter.applicationFormValuesToIngresses(formValues, servicePayload.Name);
- await Promise.all(this._generateIngressPatchPromises(formValues.OriginalIngresses, ingresses));
- }
+ if (application.ServiceType) {
+ await this.KubernetesServiceService.delete(servicePayload);
+ const isIngress = _.filter(application.PublishedPorts, (p) => p.IngressRules.length).length;
+ if (isIngress) {
+ const originalIngresses = await this.KubernetesIngressService.get(payload.Namespace);
+ const formValues = {
+ OriginalIngresses: originalIngresses,
+ PublishedPorts: KubernetesApplicationHelper.generatePublishedPortsFormValuesFromPublishedPorts(application.ServiceType, application.PublishedPorts),
+ };
+ _.forEach(formValues.PublishedPorts, (p) => (p.NeedsDeletion = true));
+ const ingresses = KubernetesIngressConverter.applicationFormValuesToIngresses(formValues, servicePayload.Name);
+ await Promise.all(this._generateIngressPatchPromises(formValues.OriginalIngresses, ingresses));
}
- if (!_.isEmpty(application.AutoScaler)) {
- await this.KubernetesHorizontalPodAutoScalerService.delete(application.AutoScaler);
- }
- } catch (err) {
- throw err;
+ }
+ if (!_.isEmpty(application.AutoScaler)) {
+ await this.KubernetesHorizontalPodAutoScalerService.delete(application.AutoScaler);
}
}
@@ -430,13 +402,9 @@ class KubernetesApplicationService {
/* #region ROLLBACK */
async rollbackAsync(application, targetRevision) {
- try {
- const payload = KubernetesApplicationRollbackHelper.getPatchPayload(application, targetRevision);
- const apiService = this._getApplicationApiService(application);
- await apiService.rollback(application.ResourcePool, application.Name, payload);
- } catch (err) {
- throw err;
- }
+ const payload = KubernetesApplicationRollbackHelper.getPatchPayload(application, targetRevision);
+ const apiService = this._getApplicationApiService(application);
+ await apiService.rollback(application.ResourcePool, application.Name, payload);
}
rollback(application, targetRevision) {
diff --git a/app/kubernetes/services/configurationService.js b/app/kubernetes/services/configurationService.js
index e6600e6cd..fd410e424 100644
--- a/app/kubernetes/services/configurationService.js
+++ b/app/kubernetes/services/configurationService.js
@@ -26,35 +26,27 @@ class KubernetesConfigurationService {
* GET
*/
async getAsync(namespace, name) {
- try {
- const [configMap, secret] = await Promise.allSettled([this.KubernetesConfigMapService.get(namespace, name), this.KubernetesSecretService.get(namespace, name)]);
- let configuration;
- if (secret.status === 'fulfilled') {
- configuration = KubernetesConfigurationConverter.secretToConfiguration(secret.value);
- return configuration;
- }
- configuration = KubernetesConfigurationConverter.configMapToConfiguration(configMap.value);
+ const [configMap, secret] = await Promise.allSettled([this.KubernetesConfigMapService.get(namespace, name), this.KubernetesSecretService.get(namespace, name)]);
+ let configuration;
+ if (secret.status === 'fulfilled') {
+ configuration = KubernetesConfigurationConverter.secretToConfiguration(secret.value);
return configuration;
- } catch (err) {
- throw err;
}
+ configuration = KubernetesConfigurationConverter.configMapToConfiguration(configMap.value);
+ return configuration;
}
async getAllAsync(namespace) {
- try {
- const namespaces = namespace ? [namespace] : _.map(await this.KubernetesNamespaceService.get(), 'Name');
- const res = await Promise.all(
- _.map(namespaces, async (ns) => {
- const [configMaps, secrets] = await Promise.all([this.KubernetesConfigMapService.get(ns), this.KubernetesSecretService.get(ns)]);
- const secretsConfigurations = _.map(secrets, (secret) => KubernetesConfigurationConverter.secretToConfiguration(secret));
- const configMapsConfigurations = _.map(configMaps, (configMap) => KubernetesConfigurationConverter.configMapToConfiguration(configMap));
- return _.concat(configMapsConfigurations, secretsConfigurations);
- })
- );
- return _.flatten(res);
- } catch (err) {
- throw err;
- }
+ const namespaces = namespace ? [namespace] : _.map(await this.KubernetesNamespaceService.get(), 'Name');
+ const res = await Promise.all(
+ _.map(namespaces, async (ns) => {
+ const [configMaps, secrets] = await Promise.all([this.KubernetesConfigMapService.get(ns), this.KubernetesSecretService.get(ns)]);
+ const secretsConfigurations = _.map(secrets, (secret) => KubernetesConfigurationConverter.secretToConfiguration(secret));
+ const configMapsConfigurations = _.map(configMaps, (configMap) => KubernetesConfigurationConverter.configMapToConfiguration(configMap));
+ return _.concat(configMapsConfigurations, secretsConfigurations);
+ })
+ );
+ return _.flatten(res);
}
get(namespace, name) {
@@ -70,16 +62,12 @@ class KubernetesConfigurationService {
async createAsync(formValues) {
formValues.ConfigurationOwner = KubernetesCommonHelper.ownerToLabel(formValues.ConfigurationOwner);
- try {
- if (formValues.Type === KubernetesConfigurationTypes.CONFIGMAP) {
- const configMap = KubernetesConfigMapConverter.configurationFormValuesToConfigMap(formValues);
- await this.KubernetesConfigMapService.create(configMap);
- } else {
- const secret = KubernetesSecretConverter.configurationFormValuesToSecret(formValues);
- await this.KubernetesSecretService.create(secret);
- }
- } catch (err) {
- throw err;
+ if (formValues.Type === KubernetesConfigurationTypes.CONFIGMAP) {
+ const configMap = KubernetesConfigMapConverter.configurationFormValuesToConfigMap(formValues);
+ await this.KubernetesConfigMapService.create(configMap);
+ } else {
+ const secret = KubernetesSecretConverter.configurationFormValuesToSecret(formValues);
+ await this.KubernetesSecretService.create(secret);
}
}
@@ -91,18 +79,14 @@ class KubernetesConfigurationService {
* UPDATE
*/
async updateAsync(formValues, configuration) {
- try {
- if (formValues.Type === KubernetesConfigurationTypes.CONFIGMAP) {
- const configMap = KubernetesConfigMapConverter.configurationFormValuesToConfigMap(formValues);
- configMap.ConfigurationOwner = configuration.ConfigurationOwner;
- await this.KubernetesConfigMapService.update(configMap);
- } else {
- const secret = KubernetesSecretConverter.configurationFormValuesToSecret(formValues);
- secret.ConfigurationOwner = configuration.ConfigurationOwner;
- await this.KubernetesSecretService.update(secret);
- }
- } catch (err) {
- throw err;
+ if (formValues.Type === KubernetesConfigurationTypes.CONFIGMAP) {
+ const configMap = KubernetesConfigMapConverter.configurationFormValuesToConfigMap(formValues);
+ configMap.ConfigurationOwner = configuration.ConfigurationOwner;
+ await this.KubernetesConfigMapService.update(configMap);
+ } else {
+ const secret = KubernetesSecretConverter.configurationFormValuesToSecret(formValues);
+ secret.ConfigurationOwner = configuration.ConfigurationOwner;
+ await this.KubernetesSecretService.update(secret);
}
}
@@ -114,14 +98,10 @@ class KubernetesConfigurationService {
* DELETE
*/
async deleteAsync(config) {
- try {
- if (config.Type === KubernetesConfigurationTypes.CONFIGMAP) {
- await this.KubernetesConfigMapService.delete(config);
- } else {
- await this.KubernetesSecretService.delete(config);
- }
- } catch (err) {
- throw err;
+ if (config.Type === KubernetesConfigurationTypes.CONFIGMAP) {
+ await this.KubernetesConfigMapService.delete(config);
+ } else {
+ await this.KubernetesSecretService.delete(config);
}
}
diff --git a/app/kubernetes/services/kubeconfigService.js b/app/kubernetes/services/kubeconfigService.js
index 752dc1ef3..8b78ca9f7 100644
--- a/app/kubernetes/services/kubeconfigService.js
+++ b/app/kubernetes/services/kubeconfigService.js
@@ -9,7 +9,11 @@ class KubernetesConfigService {
async downloadConfig() {
const response = await this.KubernetesConfig.get();
- return this.FileSaver.saveAs(response.data, 'config');
+
+ const headers = response.headers();
+ const contentDispositionHeader = headers['content-disposition'];
+ const filename = contentDispositionHeader.replace('attachment;', '').trim();
+ return this.FileSaver.saveAs(response.data, filename);
}
}
diff --git a/app/kubernetes/services/resourcePoolService.js b/app/kubernetes/services/resourcePoolService.js
index e89069191..a8de30eb4 100644
--- a/app/kubernetes/services/resourcePoolService.js
+++ b/app/kubernetes/services/resourcePoolService.js
@@ -14,39 +14,31 @@ export function KubernetesResourcePoolService($async, EndpointService, Kubernete
};
async function getOne(name) {
- try {
- const namespace = await KubernetesNamespaceService.get(name);
- const [quotaAttempt] = await Promise.allSettled([KubernetesResourceQuotaService.get(name, KubernetesResourceQuotaHelper.generateResourceQuotaName(name))]);
- const pool = KubernetesResourcePoolConverter.apiToResourcePool(namespace);
- if (quotaAttempt.status === 'fulfilled') {
- pool.Quota = quotaAttempt.value;
- pool.Yaml += '---\n' + quotaAttempt.value.Yaml;
- }
- return pool;
- } catch (err) {
- throw err;
+ const namespace = await KubernetesNamespaceService.get(name);
+ const [quotaAttempt] = await Promise.allSettled([KubernetesResourceQuotaService.get(name, KubernetesResourceQuotaHelper.generateResourceQuotaName(name))]);
+ const pool = KubernetesResourcePoolConverter.apiToResourcePool(namespace);
+ if (quotaAttempt.status === 'fulfilled') {
+ pool.Quota = quotaAttempt.value;
+ pool.Yaml += '---\n' + quotaAttempt.value.Yaml;
}
+ return pool;
}
async function getAll() {
- try {
- const namespaces = await KubernetesNamespaceService.get();
- const pools = await Promise.all(
- _.map(namespaces, async (namespace) => {
- const name = namespace.Name;
- const [quotaAttempt] = await Promise.allSettled([KubernetesResourceQuotaService.get(name, KubernetesResourceQuotaHelper.generateResourceQuotaName(name))]);
- const pool = KubernetesResourcePoolConverter.apiToResourcePool(namespace);
- if (quotaAttempt.status === 'fulfilled') {
- pool.Quota = quotaAttempt.value;
- pool.Yaml += '---\n' + quotaAttempt.value.Yaml;
- }
- return pool;
- })
- );
- return pools;
- } catch (err) {
- throw err;
- }
+ const namespaces = await KubernetesNamespaceService.get();
+ const pools = await Promise.all(
+ _.map(namespaces, async (namespace) => {
+ const name = namespace.Name;
+ const [quotaAttempt] = await Promise.allSettled([KubernetesResourceQuotaService.get(name, KubernetesResourceQuotaHelper.generateResourceQuotaName(name))]);
+ const pool = KubernetesResourcePoolConverter.apiToResourcePool(namespace);
+ if (quotaAttempt.status === 'fulfilled') {
+ pool.Quota = quotaAttempt.value;
+ pool.Yaml += '---\n' + quotaAttempt.value.Yaml;
+ }
+ return pool;
+ })
+ );
+ return pools;
}
function get(name) {
@@ -58,80 +50,68 @@ export function KubernetesResourcePoolService($async, EndpointService, Kubernete
function create(formValues) {
return $async(async () => {
- try {
- const [namespace, quota, ingresses, registries] = KubernetesResourcePoolConverter.formValuesToResourcePool(formValues);
- await KubernetesNamespaceService.create(namespace);
+ const [namespace, quota, ingresses, registries] = KubernetesResourcePoolConverter.formValuesToResourcePool(formValues);
+ await KubernetesNamespaceService.create(namespace);
- if (quota) {
- await KubernetesResourceQuotaService.create(quota);
- }
- const ingressPromises = _.map(ingresses, (i) => KubernetesIngressService.create(i));
- await Promise.all(ingressPromises);
-
- const endpointId = formValues.EndpointId;
- const registriesPromises = _.map(registries, (r) => EndpointService.updateRegistryAccess(endpointId, r.Id, r.RegistryAccesses[endpointId]));
- await Promise.all(registriesPromises);
- } catch (err) {
- throw err;
+ if (quota) {
+ await KubernetesResourceQuotaService.create(quota);
}
+ const ingressPromises = _.map(ingresses, (i) => KubernetesIngressService.create(i));
+ await Promise.all(ingressPromises);
+
+ const endpointId = formValues.EndpointId;
+ const registriesPromises = _.map(registries, (r) => EndpointService.updateRegistryAccess(endpointId, r.Id, r.RegistryAccesses[endpointId]));
+ await Promise.all(registriesPromises);
});
}
function patch(oldFormValues, newFormValues) {
return $async(async () => {
- try {
- const [oldNamespace, oldQuota, oldIngresses, oldRegistries] = KubernetesResourcePoolConverter.formValuesToResourcePool(oldFormValues);
- const [newNamespace, newQuota, newIngresses, newRegistries] = KubernetesResourcePoolConverter.formValuesToResourcePool(newFormValues);
- void oldNamespace, newNamespace;
+ const [oldNamespace, oldQuota, oldIngresses, oldRegistries] = KubernetesResourcePoolConverter.formValuesToResourcePool(oldFormValues);
+ const [newNamespace, newQuota, newIngresses, newRegistries] = KubernetesResourcePoolConverter.formValuesToResourcePool(newFormValues);
+ void oldNamespace, newNamespace;
- if (oldQuota && newQuota) {
- await KubernetesResourceQuotaService.patch(oldQuota, newQuota);
- } else if (!oldQuota && newQuota) {
- await KubernetesResourceQuotaService.create(newQuota);
- } else if (oldQuota && !newQuota) {
- await KubernetesResourceQuotaService.delete(oldQuota);
- }
-
- const create = _.filter(newIngresses, (ing) => !_.find(oldIngresses, { Name: ing.Name }));
- const del = _.filter(oldIngresses, (ing) => !_.find(newIngresses, { Name: ing.Name }));
- const patch = _.without(newIngresses, ...create);
-
- const createPromises = _.map(create, (i) => KubernetesIngressService.create(i));
- const delPromises = _.map(del, (i) => KubernetesIngressService.delete(i.Namespace, i.Name));
- const patchPromises = _.map(patch, (ing) => {
- const old = _.find(oldIngresses, { Name: ing.Name });
- ing.Paths = angular.copy(old.Paths);
- ing.PreviousHost = old.Host;
- return KubernetesIngressService.patch(old, ing);
- });
-
- const promises = _.flatten([createPromises, delPromises, patchPromises]);
- await Promise.all(promises);
-
- const endpointId = newFormValues.EndpointId;
- const keptRegistries = _.intersectionBy(oldRegistries, newRegistries, 'Id');
- const removedRegistries = _.without(oldRegistries, ...keptRegistries);
-
- const newRegistriesPromises = _.map(newRegistries, (r) => EndpointService.updateRegistryAccess(endpointId, r.Id, r.RegistryAccesses[endpointId]));
- const removedRegistriesPromises = _.map(removedRegistries, (r) => {
- _.pull(r.RegistryAccesses[endpointId].Namespaces, newFormValues.Name);
- return EndpointService.updateRegistryAccess(endpointId, r.Id, r.RegistryAccesses[endpointId]);
- });
-
- await Promise.all(_.concat(newRegistriesPromises, removedRegistriesPromises));
- } catch (err) {
- throw err;
+ if (oldQuota && newQuota) {
+ await KubernetesResourceQuotaService.patch(oldQuota, newQuota);
+ } else if (!oldQuota && newQuota) {
+ await KubernetesResourceQuotaService.create(newQuota);
+ } else if (oldQuota && !newQuota) {
+ await KubernetesResourceQuotaService.delete(oldQuota);
}
+
+ const create = _.filter(newIngresses, (ing) => !_.find(oldIngresses, { Name: ing.Name }));
+ const del = _.filter(oldIngresses, (ing) => !_.find(newIngresses, { Name: ing.Name }));
+ const patch = _.without(newIngresses, ...create);
+
+ const createPromises = _.map(create, (i) => KubernetesIngressService.create(i));
+ const delPromises = _.map(del, (i) => KubernetesIngressService.delete(i.Namespace, i.Name));
+ const patchPromises = _.map(patch, (ing) => {
+ const old = _.find(oldIngresses, { Name: ing.Name });
+ ing.Paths = angular.copy(old.Paths);
+ ing.PreviousHost = old.Host;
+ return KubernetesIngressService.patch(old, ing);
+ });
+
+ const promises = _.flatten([createPromises, delPromises, patchPromises]);
+ await Promise.all(promises);
+
+ const endpointId = newFormValues.EndpointId;
+ const keptRegistries = _.intersectionBy(oldRegistries, newRegistries, 'Id');
+ const removedRegistries = _.without(oldRegistries, ...keptRegistries);
+
+ const newRegistriesPromises = _.map(newRegistries, (r) => EndpointService.updateRegistryAccess(endpointId, r.Id, r.RegistryAccesses[endpointId]));
+ const removedRegistriesPromises = _.map(removedRegistries, (r) => {
+ _.pull(r.RegistryAccesses[endpointId].Namespaces, newFormValues.Name);
+ return EndpointService.updateRegistryAccess(endpointId, r.Id, r.RegistryAccesses[endpointId]);
+ });
+
+ await Promise.all(_.concat(newRegistriesPromises, removedRegistriesPromises));
});
}
function _delete(pool) {
return $async(async () => {
- try {
- await KubernetesNamespaceService.delete(pool.Namespace);
- } catch (err) {
- throw err;
- }
+ await KubernetesNamespaceService.delete(pool.Namespace);
});
}
}
diff --git a/app/kubernetes/services/stackService.js b/app/kubernetes/services/stackService.js
index 23e09c01f..7caf17a08 100644
--- a/app/kubernetes/services/stackService.js
+++ b/app/kubernetes/services/stackService.js
@@ -14,13 +14,9 @@ class KubernetesStackService {
* GET
*/
async getAllAsync(namespace) {
- try {
- const applications = await this.KubernetesApplicationService.get(namespace);
- const stacks = _.map(applications, (item) => item.StackName);
- return _.uniq(_.without(stacks, '-'));
- } catch (err) {
- throw err;
- }
+ const applications = await this.KubernetesApplicationService.get(namespace);
+ const stacks = _.map(applications, (item) => item.StackName);
+ return _.uniq(_.without(stacks, '-'));
}
get(namespace) {
diff --git a/app/kubernetes/services/volumeService.js b/app/kubernetes/services/volumeService.js
index abbf0019d..7b00aa6e2 100644
--- a/app/kubernetes/services/volumeService.js
+++ b/app/kubernetes/services/volumeService.js
@@ -20,28 +20,20 @@ class KubernetesVolumeService {
* GET
*/
async getAsync(namespace, name) {
- try {
- const [pvc, pool] = await Promise.all([this.KubernetesPersistentVolumeClaimService.get(namespace, name), this.KubernetesResourcePoolService.get(namespace)]);
- return KubernetesVolumeConverter.pvcToVolume(pvc, pool);
- } catch (err) {
- throw err;
- }
+ const [pvc, pool] = await Promise.all([this.KubernetesPersistentVolumeClaimService.get(namespace, name), this.KubernetesResourcePoolService.get(namespace)]);
+ return KubernetesVolumeConverter.pvcToVolume(pvc, pool);
}
async getAllAsync(namespace) {
- try {
- const data = await this.KubernetesResourcePoolService.get(namespace);
- const pools = data instanceof Array ? data : [data];
- const res = await Promise.all(
- _.map(pools, async (pool) => {
- const pvcs = await this.KubernetesPersistentVolumeClaimService.get(pool.Namespace.Name);
- return _.map(pvcs, (pvc) => KubernetesVolumeConverter.pvcToVolume(pvc, pool));
- })
- );
- return _.flatten(res);
- } catch (err) {
- throw err;
- }
+ const data = await this.KubernetesResourcePoolService.get(namespace);
+ const pools = data instanceof Array ? data : [data];
+ const res = await Promise.all(
+ _.map(pools, async (pool) => {
+ const pvcs = await this.KubernetesPersistentVolumeClaimService.get(pool.Namespace.Name);
+ return _.map(pvcs, (pvc) => KubernetesVolumeConverter.pvcToVolume(pvc, pool));
+ })
+ );
+ return _.flatten(res);
}
get(namespace, name) {
@@ -55,11 +47,7 @@ class KubernetesVolumeService {
* DELETE
*/
async deleteAsync(volume) {
- try {
- await this.KubernetesPersistentVolumeClaimService.delete(volume.PersistentVolumeClaim);
- } catch (err) {
- throw err;
- }
+ await this.KubernetesPersistentVolumeClaimService.delete(volume.PersistentVolumeClaim);
}
delete(volume) {
diff --git a/app/portainer/components/code-editor/codeEditorController.js b/app/portainer/components/code-editor/codeEditorController.js
index 26363c108..2c7499b03 100644
--- a/app/portainer/components/code-editor/codeEditorController.js
+++ b/app/portainer/components/code-editor/codeEditorController.js
@@ -2,7 +2,7 @@ angular.module('portainer.app').controller('CodeEditorController', function Code
var ctrl = this;
this.$onChanges = function $onChanges({ value }) {
- if (value && value.currentValue && ctrl.editor) {
+ if (value && value.currentValue && ctrl.editor && ctrl.editor.getValue() !== value.currentValue) {
ctrl.editor.setValue(value.currentValue);
}
};
diff --git a/app/portainer/components/forms/stack-redeploy-git-form/stack-redeploy-git-form.controller.js b/app/portainer/components/forms/stack-redeploy-git-form/stack-redeploy-git-form.controller.js
index f4179817d..717c15333 100644
--- a/app/portainer/components/forms/stack-redeploy-git-form/stack-redeploy-git-form.controller.js
+++ b/app/portainer/components/forms/stack-redeploy-git-form/stack-redeploy-git-form.controller.js
@@ -68,7 +68,7 @@ class StackRedeployGitFormController {
this.state.redeployInProgress = true;
await this.StackService.updateGit(this.stack.Id, this.stack.EndpointId, this.FormHelper.removeInvalidEnvVars(this.formValues.Env), false, this.formValues);
-
+ this.Notifications.success('Pulled and redeployed stack successfully');
await this.$state.reload();
} catch (err) {
this.Notifications.error('Failure', err, 'Failed redeploying stack');
diff --git a/app/portainer/components/index.js b/app/portainer/components/index.js
index bef46f4d7..9a660facf 100644
--- a/app/portainer/components/index.js
+++ b/app/portainer/components/index.js
@@ -1,7 +1,8 @@
import angular from 'angular';
+import sidebarModule from './sidebar';
import gitFormModule from './forms/git-form';
import porAccessManagementModule from './accessManagement';
import formComponentsModule from './form-components';
-export default angular.module('portainer.app.components', [gitFormModule, porAccessManagementModule, formComponentsModule]).name;
+export default angular.module('portainer.app.components', [sidebarModule, gitFormModule, porAccessManagementModule, formComponentsModule]).name;
diff --git a/app/portainer/components/sidebar/index.js b/app/portainer/components/sidebar/index.js
new file mode 100644
index 000000000..1d2c18062
--- /dev/null
+++ b/app/portainer/components/sidebar/index.js
@@ -0,0 +1,12 @@
+import angular from 'angular';
+import './sidebar.css';
+
+import { sidebarMenu } from './sidebar-menu';
+import { sidebarSection } from './sidebar-section';
+import { sidebarMenuItem } from './sidebar-menu-item';
+
+export default angular
+ .module('portainer.app.components.sidebar', [])
+ .component('sidebarMenu', sidebarMenu)
+ .component('sidebarMenuItem', sidebarMenuItem)
+ .component('sidebarSection', sidebarSection).name;
diff --git a/app/portainer/components/sidebar/sidebar-menu-item/index.js b/app/portainer/components/sidebar/sidebar-menu-item/index.js
new file mode 100644
index 000000000..014d6262f
--- /dev/null
+++ b/app/portainer/components/sidebar/sidebar-menu-item/index.js
@@ -0,0 +1,12 @@
+import './sidebar-menu-item.css';
+
+export const sidebarMenuItem = {
+ templateUrl: './sidebar-menu-item.html',
+ bindings: {
+ path: '@',
+ pathParams: '<',
+ iconClass: '@',
+ className: '@',
+ },
+ transclude: true,
+};
diff --git a/app/portainer/components/sidebar/sidebar-menu-item/sidebar-menu-item.css b/app/portainer/components/sidebar/sidebar-menu-item/sidebar-menu-item.css
new file mode 100644
index 000000000..4f751ad36
--- /dev/null
+++ b/app/portainer/components/sidebar/sidebar-menu-item/sidebar-menu-item.css
@@ -0,0 +1,4 @@
+.sidebar-menu-item > a {
+ display: flex;
+ justify-content: space-between;
+}
diff --git a/app/portainer/components/sidebar/sidebar-menu-item/sidebar-menu-item.html b/app/portainer/components/sidebar/sidebar-menu-item/sidebar-menu-item.html
new file mode 100644
index 000000000..bfcb509c9
--- /dev/null
+++ b/app/portainer/components/sidebar/sidebar-menu-item/sidebar-menu-item.html
@@ -0,0 +1,6 @@
+
diff --git a/app/portainer/components/sidebar/sidebar-menu/index.js b/app/portainer/components/sidebar/sidebar-menu/index.js
new file mode 100644
index 000000000..8c9d24aea
--- /dev/null
+++ b/app/portainer/components/sidebar/sidebar-menu/index.js
@@ -0,0 +1,18 @@
+import './sidebar-menu.css';
+
+import controller from './sidebar-menu.controller.js';
+
+export const sidebarMenu = {
+ templateUrl: './sidebar-menu.html',
+ controller,
+ bindings: {
+ iconClass: '@',
+ path: '@', // string
+ pathParams: '<', //object, corresponds to https://ui-router.github.io/ng1/docs/latest/modules/directives.html#uistatedirective
+ childrenPaths: '<', // []string (globs)
+ label: '@', // string
+ isSidebarOpen: '<',
+ currentState: '@',
+ },
+ transclude: true,
+};
diff --git a/app/portainer/components/sidebar/sidebar-menu/sidebar-menu.controller.js b/app/portainer/components/sidebar/sidebar-menu/sidebar-menu.controller.js
new file mode 100644
index 000000000..8122f7ea9
--- /dev/null
+++ b/app/portainer/components/sidebar/sidebar-menu/sidebar-menu.controller.js
@@ -0,0 +1,45 @@
+class SidebarMenuController {
+ /* @ngInject */
+ constructor($state) {
+ this.$state = $state;
+
+ this.state = {
+ forceOpen: false,
+ };
+ }
+
+ isOpen() {
+ if (!this.isSidebarOpen) {
+ return false;
+ }
+
+ if (this.state.forceOpen) {
+ return true;
+ }
+
+ return this.isOpenByPathState();
+ }
+
+ isOpenByPathState() {
+ const currentName = this.$state.current.name;
+ return currentName.startsWith(this.path) || this.childrenPaths.includes(currentName);
+ }
+
+ onClickArrow(event) {
+ event.stopPropagation();
+ event.preventDefault();
+
+ // prevent toggling when menu is open by state
+ if (this.isOpenByPathState()) {
+ return;
+ }
+
+ this.state.forceOpen = !this.state.forceOpen;
+ }
+
+ $onInit() {
+ this.childrenPaths = this.childrenPaths || [];
+ }
+}
+
+export default SidebarMenuController;
diff --git a/app/portainer/components/sidebar/sidebar-menu/sidebar-menu.css b/app/portainer/components/sidebar/sidebar-menu/sidebar-menu.css
new file mode 100644
index 000000000..f28c4ae14
--- /dev/null
+++ b/app/portainer/components/sidebar/sidebar-menu/sidebar-menu.css
@@ -0,0 +1,15 @@
+.sidebar-menu .sidebar-menu-head {
+ position: relative;
+}
+
+.sidebar-menu .sidebar-menu-head .sidebar-menu-indicator {
+ background: none;
+ border: 0;
+ width: fit-content;
+ height: fit-content;
+ color: white;
+ margin: 0 10px;
+ padding: 0;
+ position: absolute;
+ left: -5px;
+}
diff --git a/app/portainer/components/sidebar/sidebar-menu/sidebar-menu.html b/app/portainer/components/sidebar/sidebar-menu/sidebar-menu.html
new file mode 100644
index 000000000..a9e6243d5
--- /dev/null
+++ b/app/portainer/components/sidebar/sidebar-menu/sidebar-menu.html
@@ -0,0 +1,14 @@
+
diff --git a/app/portainer/components/sidebar/sidebar-section/index.js b/app/portainer/components/sidebar/sidebar-section/index.js
new file mode 100644
index 000000000..b577b6660
--- /dev/null
+++ b/app/portainer/components/sidebar/sidebar-section/index.js
@@ -0,0 +1,7 @@
+export const sidebarSection = {
+ templateUrl: './sidebar-section.html',
+ transclude: true,
+ bindings: {
+ title: '@',
+ },
+};
diff --git a/app/portainer/components/sidebar/sidebar-section/sidebar-section.html b/app/portainer/components/sidebar/sidebar-section/sidebar-section.html
new file mode 100644
index 000000000..057db83f8
--- /dev/null
+++ b/app/portainer/components/sidebar/sidebar-section/sidebar-section.html
@@ -0,0 +1,7 @@
+
diff --git a/app/portainer/components/sidebar/sidebar.css b/app/portainer/components/sidebar/sidebar.css
new file mode 100644
index 000000000..e5d27df9b
--- /dev/null
+++ b/app/portainer/components/sidebar/sidebar.css
@@ -0,0 +1,326 @@
+#page-wrapper.open #sidebar-wrapper {
+ left: 150px;
+}
+
+/* #592727 RED */
+/* #2f5927 GREEN */
+/* #30426a BLUE (default)*/
+/* Sidebar background color */
+/* Sidebar header and footer color */
+/* Sidebar title text colour */
+/* Sidebar menu item hover color */
+/**
+ * Sidebar
+ */
+#sidebar-wrapper {
+ background: #30426a;
+}
+
+ul.sidebar .sidebar-main a,
+.sidebar-footer,
+ul.sidebar .sidebar-list a:hover,
+#page-wrapper:not(.open) ul.sidebar .sidebar-title.separator {
+ /* Sidebar header and footer color */
+ background: #2d3e63;
+}
+
+ul.sidebar {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ padding: 0;
+ margin: 0;
+ list-style: none;
+ text-indent: 20px;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+ul.sidebar li a {
+ color: #fff;
+ text-decoration: none;
+ width: 250px;
+}
+
+ul.sidebar .sidebar-main {
+ height: 65px;
+}
+
+ul.sidebar .sidebar-main a {
+ font-size: 18px;
+ line-height: 60px;
+}
+
+ul.sidebar .sidebar-main a:hover {
+ cursor: pointer;
+}
+
+ul.sidebar .sidebar-main .menu-icon {
+ float: right;
+ font-size: 18px;
+ padding-right: 28px;
+ line-height: 60px;
+}
+
+ul.sidebar .sidebar-title {
+ color: #738bc0;
+ font-size: 12px;
+ height: 35px;
+ line-height: 40px;
+ text-transform: uppercase;
+ transition: all 0.6s ease 0s;
+}
+
+ul.sidebar .sidebar-list a {
+ text-indent: 25px;
+ font-size: 15px;
+ color: #b2bfdc;
+ line-height: 40px;
+ padding-left: 5px;
+ border-left: 3px solid transparent;
+}
+
+ul.sidebar .sidebar-list a:hover {
+ color: #fff;
+ border-left-color: #e99d1a;
+}
+
+ul.sidebar .sidebar-list a:hover .menu-icon {
+ text-indent: 25px;
+}
+
+ul.sidebar .sidebar-list .menu-icon {
+ padding-right: 30px;
+ line-height: 40px;
+ width: fit-content;
+}
+
+#page-wrapper:not(.open) ul.sidebar {
+ bottom: 0;
+}
+
+#page-wrapper:not(.open) ul.sidebar .sidebar-title {
+ display: none;
+ height: 0px;
+ text-indent: -100px;
+}
+
+#page-wrapper:not(.open) ul.sidebar .sidebar-title.separator {
+ display: block;
+ height: 2px;
+ margin: 13px 0;
+}
+
+#page-wrapper:not(.open) ul.sidebar .sidebar-list a:hover span {
+ border-left: 3px solid #e99d1a;
+ text-indent: 22px;
+}
+
+#page-wrapper:not(.open) .sidebar-footer {
+ display: none;
+}
+
+.sidebar-footer {
+ position: absolute;
+ height: 40px;
+ bottom: 0;
+ width: 100%;
+ padding: 0;
+ margin: 0;
+ transition: all 0.6s ease 0s;
+ text-align: center;
+}
+
+.sidebar-footer div a {
+ color: #b2bfdc;
+ font-size: 12px;
+ line-height: 43px;
+}
+
+.sidebar-footer div a:hover {
+ color: #ffffff;
+ text-decoration: none;
+}
+
+/* #592727 RED */
+/* #2f5927 GREEN */
+/* #30426a BLUE (default)*/
+/* Sidebar background color */
+/* Sidebar header and footer color */
+/* Sidebar title text colour */
+/* Sidebar menu item hover color */
+
+ul.sidebar {
+ position: relative;
+ overflow: hidden;
+ flex-shrink: 0;
+}
+
+ul.sidebar .sidebar-title {
+ height: auto;
+}
+
+ul.sidebar .sidebar-title.endpoint-name {
+ color: #fff;
+ text-align: center;
+ text-indent: 0;
+}
+
+ul.sidebar .sidebar-list a {
+ font-size: 14px;
+}
+
+ul.sidebar .sidebar-list a.active {
+ color: #fff;
+ border-left-color: #fff;
+ background: #2d3e63;
+}
+
+.sidebar-header {
+ height: 60px;
+ list-style: none;
+ text-indent: 20px;
+ font-size: 18px;
+ background: #2d3e63;
+}
+
+.sidebar-header a {
+ color: #fff;
+}
+
+.sidebar-header a:hover {
+ text-decoration: none;
+}
+
+.sidebar-header .menu-icon {
+ float: right;
+ padding-right: 28px;
+ line-height: 60px;
+}
+
+#page-wrapper:not(.open) .sidebar-footer-content {
+ display: none;
+}
+
+.sidebar-footer-content {
+ text-align: center;
+}
+
+.sidebar-footer-content .logo {
+ width: 100%;
+ max-width: 100px;
+ height: 100%;
+ max-height: 35px;
+ margin: 2px 0 2px 20px;
+}
+
+.sidebar-footer-content .update-notification {
+ font-size: 14px;
+ padding: 12px;
+ border-radius: 2px;
+ background-color: #ff851b;
+ margin-bottom: 5px;
+}
+
+.sidebar-footer-content .version {
+ font-size: 11px;
+ margin: 11px 20px 0 7px;
+ color: #fff;
+}
+
+.sidebar-footer-content .edition-version {
+ font-size: 10px;
+ margin-bottom: 8px;
+ color: #fff;
+}
+
+#sidebar-wrapper {
+ display: flex;
+ flex-flow: column;
+}
+
+.sidebar-content {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow-y: auto;
+ overflow-x: hidden;
+ height: 100%;
+}
+
+ul.sidebar .sidebar-list a.active .menu-icon {
+ text-indent: 25px;
+}
+
+ul.sidebar .sidebar-list .sidebar-sublist a {
+ text-indent: 35px;
+ font-size: 12px;
+ color: #b2bfdc;
+ line-height: 36px;
+}
+
+ul.sidebar .sidebar-title {
+ line-height: 36px;
+}
+
+ul.sidebar .sidebar-title .form-control {
+ height: 36px;
+ padding: 6px 12px;
+}
+
+ul.sidebar .sidebar-list a,
+ul.sidebar .sidebar-list .sidebar-sublist a {
+ line-height: 36px;
+}
+
+ul.sidebar .sidebar-list .menu-icon {
+ line-height: 36px;
+}
+
+ul.sidebar .sidebar-list .sidebar-sublist a.active {
+ color: #fff;
+ border-left: 3px solid #fff;
+ background: #2d3e63;
+}
+
+@media (max-height: 785px) {
+ ul.sidebar .sidebar-title {
+ line-height: 26px;
+ }
+
+ ul.sidebar .sidebar-title .form-control {
+ height: 26px;
+ padding: 3px 6px;
+ }
+
+ ul.sidebar .sidebar-list a,
+ ul.sidebar .sidebar-list .sidebar-sublist a {
+ font-size: 12px;
+ line-height: 26px;
+ }
+
+ ul.sidebar .sidebar-list .menu-icon {
+ line-height: 26px;
+ }
+}
+
+@media (min-height: 786px) and (max-height: 924px) {
+ ul.sidebar .sidebar-title {
+ line-height: 30px;
+ }
+
+ ul.sidebar .sidebar-title .form-control {
+ height: 30px;
+ padding: 5px 10px;
+ }
+
+ ul.sidebar .sidebar-list a,
+ ul.sidebar .sidebar-list .sidebar-sublist a {
+ font-size: 12px;
+ line-height: 30px;
+ }
+
+ ul.sidebar .sidebar-list .menu-icon {
+ line-height: 30px;
+ }
+}
diff --git a/app/portainer/services/api/accessService.js b/app/portainer/services/api/accessService.js
index 40018afd3..711f052f5 100644
--- a/app/portainer/services/api/accessService.js
+++ b/app/portainer/services/api/accessService.js
@@ -77,26 +77,22 @@ angular.module('portainer.app').factory('AccessService', [
}
async function accessesAsync(entity, parent) {
- try {
- if (!entity) {
- throw new Error('Unable to retrieve accesses');
- }
- if (!entity.UserAccessPolicies) {
- entity.UserAccessPolicies = {};
- }
- if (!entity.TeamAccessPolicies) {
- entity.TeamAccessPolicies = {};
- }
- if (parent && !parent.UserAccessPolicies) {
- parent.UserAccessPolicies = {};
- }
- if (parent && !parent.TeamAccessPolicies) {
- parent.TeamAccessPolicies = {};
- }
- return await getAccesses(entity.UserAccessPolicies, entity.TeamAccessPolicies, parent ? parent.UserAccessPolicies : {}, parent ? parent.TeamAccessPolicies : {});
- } catch (err) {
- throw err;
+ if (!entity) {
+ throw new Error('Unable to retrieve accesses');
}
+ if (!entity.UserAccessPolicies) {
+ entity.UserAccessPolicies = {};
+ }
+ if (!entity.TeamAccessPolicies) {
+ entity.TeamAccessPolicies = {};
+ }
+ if (parent && !parent.UserAccessPolicies) {
+ parent.UserAccessPolicies = {};
+ }
+ if (parent && !parent.TeamAccessPolicies) {
+ parent.TeamAccessPolicies = {};
+ }
+ return await getAccesses(entity.UserAccessPolicies, entity.TeamAccessPolicies, parent ? parent.UserAccessPolicies : {}, parent ? parent.TeamAccessPolicies : {});
}
function accesses(entity, parent) {
diff --git a/app/portainer/services/terminal-window.js b/app/portainer/services/terminal-window.js
index 0464fa1d8..5bd10dbf7 100644
--- a/app/portainer/services/terminal-window.js
+++ b/app/portainer/services/terminal-window.js
@@ -1,6 +1,6 @@
angular.module('portainer').service('TerminalWindow', function ($window) {
+ const terminalHeight = 495;
this.terminalopen = function () {
- const terminalHeight = 480;
const contentWrapperHeight = $window.innerHeight;
const newContentWrapperHeight = contentWrapperHeight - terminalHeight;
document.getElementById('content-wrapper').style.height = newContentWrapperHeight + 'px';
@@ -16,4 +16,11 @@ angular.module('portainer').service('TerminalWindow', function ($window) {
document.getElementById('content-wrapper').style.overflowY = wrapperCSS.overflowY;
document.getElementById('sidebar-wrapper').style.height = wrapperCSS.height;
};
+ this.terminalresize = function () {
+ const contentWrapperHeight = $window.innerHeight;
+ const newContentWrapperHeight = contentWrapperHeight - terminalHeight;
+ document.getElementById('content-wrapper').style.height = newContentWrapperHeight + 'px';
+ document.getElementById('content-wrapper').style.overflowY = 'auto';
+ document.getElementById('sidebar-wrapper').style.height = newContentWrapperHeight + 'px';
+ };
});
diff --git a/app/portainer/views/registries/registries.html b/app/portainer/views/registries/registries.html
index 219a1886f..742b3122f 100644
--- a/app/portainer/views/registries/registries.html
+++ b/app/portainer/views/registries/registries.html
@@ -7,6 +7,12 @@
Registry management
+
+
+ View registries via an endpoint to manage access for user(s) and/or team(s)
+
+
+