mirror of
https://github.com/portainer/portainer.git
synced 2025-07-28 09:49:40 +02:00
Merge branch 'develop' into feat/EE-809/EE-466/kube-advanced-apps
This commit is contained in:
commit
0128d1bf96
54 changed files with 1634 additions and 1389 deletions
|
@ -29,6 +29,10 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||||
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.proxyRequestsToDockerAPI)))
|
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.proxyRequestsToDockerAPI)))
|
||||||
h.PathPrefix("/{id}/kubernetes").Handler(
|
h.PathPrefix("/{id}/kubernetes").Handler(
|
||||||
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.proxyRequestsToKubernetesAPI)))
|
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(
|
h.PathPrefix("/{id}/storidge").Handler(
|
||||||
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.proxyRequestsToStoridgeAPI)))
|
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.proxyRequestsToStoridgeAPI)))
|
||||||
return h
|
return h
|
||||||
|
|
|
@ -3,6 +3,7 @@ package endpointproxy
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
httperror "github.com/portainer/libhttp/error"
|
httperror "github.com/portainer/libhttp/error"
|
||||||
|
@ -65,6 +66,12 @@ func (handler *Handler) proxyRequestsToDockerAPI(w http.ResponseWriter, r *http.
|
||||||
}
|
}
|
||||||
|
|
||||||
id := strconv.Itoa(endpointID)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,17 +65,18 @@ func (handler *Handler) proxyRequestsToKubernetesAPI(w http.ResponseWriter, r *h
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For KubernetesLocalEnvironment
|
||||||
requestPrefix := fmt.Sprintf("/%d/kubernetes", endpointID)
|
requestPrefix := fmt.Sprintf("/%d/kubernetes", endpointID)
|
||||||
|
|
||||||
if endpoint.Type == portainer.AgentOnKubernetesEnvironment || endpoint.Type == portainer.EdgeAgentOnKubernetesEnvironment {
|
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)
|
http.StripPrefix(requestPrefix, proxy).ServeHTTP(w, r)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isKubernetesRequest(requestURL string) bool {
|
|
||||||
return strings.HasPrefix(requestURL, "/api") || strings.HasPrefix(requestURL, "/healthz")
|
|
||||||
}
|
|
|
@ -176,6 +176,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
http.StripPrefix("/api/endpoints", h.EndpointProxyHandler).ServeHTTP(w, r)
|
http.StripPrefix("/api/endpoints", h.EndpointProxyHandler).ServeHTTP(w, r)
|
||||||
case strings.Contains(r.URL.Path, "/azure/"):
|
case strings.Contains(r.URL.Path, "/azure/"):
|
||||||
http.StripPrefix("/api/endpoints", h.EndpointProxyHandler).ServeHTTP(w, r)
|
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/"):
|
case strings.Contains(r.URL.Path, "/edge/"):
|
||||||
http.StripPrefix("/api/endpoints", h.EndpointEdgeHandler).ServeHTTP(w, r)
|
http.StripPrefix("/api/endpoints", h.EndpointEdgeHandler).ServeHTTP(w, r)
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -34,14 +34,6 @@ import (
|
||||||
// @failure 500 "Server error"
|
// @failure 500 "Server error"
|
||||||
// @router /kubernetes/{id}/config [get]
|
// @router /kubernetes/{id}/config [get]
|
||||||
func (handler *Handler) getKubernetesConfig(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
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")
|
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid endpoint identifier route variable", err}
|
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}
|
return &httperror.HandlerError{http.StatusNotFound, "Unable to generate Kubeconfig", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filenameBase := fmt.Sprintf("%s-%s", tokenData.Username, endpoint.Name)
|
||||||
contentAcceptHeader := r.Header.Get("Accept")
|
contentAcceptHeader := r.Header.Get("Accept")
|
||||||
if contentAcceptHeader == "text/yaml" {
|
if contentAcceptHeader == "text/yaml" {
|
||||||
yaml, err := kcli.GenerateYAML(config)
|
yaml, err := kcli.GenerateYAML(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Failed to generate Kubeconfig", err}
|
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)
|
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)
|
return response.JSON(w, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ angular.module('portainer.agent').factory('AgentDockerhub', AgentDockerhub);
|
||||||
|
|
||||||
function AgentDockerhub($resource, API_ENDPOINT_ENDPOINTS) {
|
function AgentDockerhub($resource, API_ENDPOINT_ENDPOINTS) {
|
||||||
return $resource(
|
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' } },
|
limits: { method: 'GET', params: { registryId: '@registryId' } },
|
||||||
|
|
|
@ -31,10 +31,6 @@ angular.module('portainer').run([
|
||||||
HttpRequestHelper.resetAgentHeaders();
|
HttpRequestHelper.resetAgentHeaders();
|
||||||
});
|
});
|
||||||
|
|
||||||
$state.defaultErrorHandler(function () {
|
|
||||||
// Do not log transitionTo errors
|
|
||||||
});
|
|
||||||
|
|
||||||
// Keep-alive Edge endpoints by sending a ping request every minute
|
// Keep-alive Edge endpoints by sending a ping request every minute
|
||||||
$interval(function () {
|
$interval(function () {
|
||||||
ping(EndpointProvider, SystemService);
|
ping(EndpointProvider, SystemService);
|
||||||
|
|
|
@ -376,98 +376,6 @@ a[ng-click] {
|
||||||
margin: 0 auto;
|
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 {
|
#image-layers .btn {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
@ -481,86 +389,6 @@ ul.sidebar .sidebar-list a.active {
|
||||||
font-size: 90%;
|
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) {
|
@media (min-width: 768px) {
|
||||||
.margin-sm-top {
|
.margin-sm-top {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
|
|
|
@ -14,9 +14,6 @@
|
||||||
padding-left: 70px;
|
padding-left: 70px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#page-wrapper.open #sidebar-wrapper {
|
|
||||||
left: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hamburg Menu
|
* Hamburg Menu
|
||||||
|
@ -254,139 +251,6 @@ div.input-mask {
|
||||||
padding-top: 7px;
|
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
|
* Widgets
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
import angular from 'angular';
|
|
||||||
|
|
||||||
angular.module('portainer.azure').component('azureSidebarContent', {
|
|
||||||
templateUrl: './azureSidebarContent.html',
|
|
||||||
bindings: {
|
|
||||||
endpointId: '<',
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,6 +0,0 @@
|
||||||
<li class="sidebar-list">
|
|
||||||
<a ui-sref="azure.dashboard({endpointId: $ctrl.endpointId})" ui-sref-active="active">Dashboard <span class="menu-icon fa fa-tachometer-alt fa-fw"></span></a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list">
|
|
||||||
<a ui-sref="azure.containerinstances({endpointId: $ctrl.endpointId})" ui-sref-active="active">Container instances <span class="menu-icon fa fa-cubes fa-fw"></span></a>
|
|
||||||
</li>
|
|
7
app/azure/components/azure-sidebar/azure-sidebar.html
Normal file
7
app/azure/components/azure-sidebar/azure-sidebar.html
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<sidebar-menu-item path="azure.dashboard" path-params="{ endpointId: $ctrl.endpointId }" icon-class="fa-tachometer-alt fa-fw" class-name="sidebar-list">
|
||||||
|
Dashboard
|
||||||
|
</sidebar-menu-item>
|
||||||
|
|
||||||
|
<sidebar-menu-item path="azure.containerinstances" path-params="{ endpointId: $ctrl.endpointId }" icon-class="fa-cubes fa-fw" class-name="sidebar-list">
|
||||||
|
Container instances
|
||||||
|
</sidebar-menu-item>
|
8
app/azure/components/azure-sidebar/index.js
Normal file
8
app/azure/components/azure-sidebar/index.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import angular from 'angular';
|
||||||
|
|
||||||
|
angular.module('portainer.azure').component('azureSidebar', {
|
||||||
|
templateUrl: './azure-sidebar.html',
|
||||||
|
bindings: {
|
||||||
|
endpointId: '<',
|
||||||
|
},
|
||||||
|
});
|
121
app/docker/components/docker-sidebar/docker-sidebar.html
Normal file
121
app/docker/components/docker-sidebar/docker-sidebar.html
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
<sidebar-menu-item path="docker.dashboard" path-params="{ endpointId: $ctrl.endpointId }" icon-class="fa-tachometer-alt fa-fw" class-name="sidebar-list">
|
||||||
|
Dashboard
|
||||||
|
</sidebar-menu-item>
|
||||||
|
|
||||||
|
<sidebar-menu
|
||||||
|
ng-if="!$ctrl.offlineMode"
|
||||||
|
label="App Templates"
|
||||||
|
icon-class="fa-rocket fa-fw"
|
||||||
|
path="docker.templates"
|
||||||
|
path-params="{ endpointId: $ctrl.endpointId }"
|
||||||
|
is-sidebar-open="$ctrl.isSidebarOpen"
|
||||||
|
children-paths="[]"
|
||||||
|
>
|
||||||
|
<sidebar-menu-item path="docker.templates.custom" path-params="{ endpointId: $ctrl.endpointId }" class-name="sidebar-sublist">
|
||||||
|
Custom Templates
|
||||||
|
</sidebar-menu-item>
|
||||||
|
</sidebar-menu>
|
||||||
|
|
||||||
|
<sidebar-menu-item ng-if="$ctrl.showStacks" path="docker.stacks" path-params="{ endpointId: $ctrl.endpointId }" icon-class="fa-th-list fa-fw" class-name="sidebar-list">
|
||||||
|
Stacks
|
||||||
|
</sidebar-menu-item>
|
||||||
|
|
||||||
|
<sidebar-menu-item ng-if="$ctrl.swarmManagement" path="docker.services" path-params="{ endpointId: $ctrl.endpointId }" icon-class="fa-list-alt fa-fw" class-name="sidebar-list">
|
||||||
|
Services
|
||||||
|
</sidebar-menu-item>
|
||||||
|
|
||||||
|
<sidebar-menu-item path="docker.containers" path-params="{ endpointId: $ctrl.endpointId }" icon-class="fa-cubes fa-fw" class-name="sidebar-list">
|
||||||
|
Containers
|
||||||
|
</sidebar-menu-item>
|
||||||
|
|
||||||
|
<sidebar-menu-item path="docker.images" path-params="{ endpointId: $ctrl.endpointId }" icon-class="fa-clone fa-fw" class-name="sidebar-list">
|
||||||
|
Images
|
||||||
|
</sidebar-menu-item>
|
||||||
|
|
||||||
|
<sidebar-menu-item path="docker.networks" path-params="{ endpointId: $ctrl.endpointId }" icon-class="fa-sitemap fa-fw" class-name="sidebar-list">
|
||||||
|
Networks
|
||||||
|
</sidebar-menu-item>
|
||||||
|
|
||||||
|
<sidebar-menu-item path="docker.volumes" path-params="{ endpointId: $ctrl.endpointId }" icon-class="fa-hdd fa-fw" class-name="sidebar-list">
|
||||||
|
Volumes
|
||||||
|
</sidebar-menu-item>
|
||||||
|
|
||||||
|
<sidebar-menu-item
|
||||||
|
ng-if="$ctrl.endpointApiVersion >= 1.3 && $ctrl.swarmManagement"
|
||||||
|
path="docker.configs"
|
||||||
|
path-params="{ endpointId: $ctrl.endpointId }"
|
||||||
|
icon-class="fa-file-code fa-fw"
|
||||||
|
class-name="sidebar-list"
|
||||||
|
>
|
||||||
|
Configs
|
||||||
|
</sidebar-menu-item>
|
||||||
|
|
||||||
|
<sidebar-menu-item
|
||||||
|
ng-if="$ctrl.endpointApiVersion >= 1.25 && $ctrl.swarmManagement"
|
||||||
|
path="docker.secrets"
|
||||||
|
path-params="{ endpointId: $ctrl.endpointId }"
|
||||||
|
icon-class="fa-user-secret fa-fw"
|
||||||
|
class-name="sidebar-list"
|
||||||
|
>
|
||||||
|
Secrets
|
||||||
|
</sidebar-menu-item>
|
||||||
|
|
||||||
|
<sidebar-menu-item
|
||||||
|
ng-if="$ctrl.standaloneManagement && $ctrl.adminAccess && !$ctrl.offlineMode"
|
||||||
|
path="docker.events"
|
||||||
|
path-params="{ endpointId: $ctrl.endpointId }"
|
||||||
|
icon-class="fa-history fa-fw"
|
||||||
|
class-name="sidebar-list"
|
||||||
|
>
|
||||||
|
Events
|
||||||
|
</sidebar-menu-item>
|
||||||
|
|
||||||
|
<sidebar-menu
|
||||||
|
ng-if="$ctrl.standaloneManagement"
|
||||||
|
label="Host"
|
||||||
|
icon-class="fa-th fa-fw"
|
||||||
|
path="docker.host"
|
||||||
|
path-params="{ endpointId: $ctrl.endpointId }"
|
||||||
|
is-sidebar-open="$ctrl.isSidebarOpen"
|
||||||
|
children-paths="['docker.registries', 'docker.registries.access', 'docker.featuresConfiguration']"
|
||||||
|
>
|
||||||
|
<div ng-if="$ctrl.adminAccess">
|
||||||
|
<sidebar-menu-item
|
||||||
|
authorization="PortainerEndpointUpdateSettings"
|
||||||
|
path="docker.featuresConfiguration"
|
||||||
|
path-params="{ endpointId: $ctrl.endpointId }"
|
||||||
|
class-name="sidebar-sublist"
|
||||||
|
>
|
||||||
|
Setup
|
||||||
|
</sidebar-menu-item>
|
||||||
|
|
||||||
|
<sidebar-menu-item authorization="PortainerRegistryList" path="docker.registries" path-params="{ endpointId: $ctrl.endpointId }" class-name="sidebar-sublist">
|
||||||
|
Registries
|
||||||
|
</sidebar-menu-item>
|
||||||
|
</div>
|
||||||
|
</sidebar-menu>
|
||||||
|
|
||||||
|
<sidebar-menu
|
||||||
|
ng-if="$ctrl.swarmManagement"
|
||||||
|
label="Swarm"
|
||||||
|
icon-class="fa-object-group fa-fw"
|
||||||
|
path="docker.swarm"
|
||||||
|
path-params="{ endpointId: $ctrl.endpointId }"
|
||||||
|
is-sidebar-open="$ctrl.isSidebarOpen"
|
||||||
|
children-paths="['docker.registries', 'docker.registries.access', 'docker.featuresConfiguration']"
|
||||||
|
>
|
||||||
|
<div ng-if="$ctrl.adminAccess">
|
||||||
|
<sidebar-menu-item
|
||||||
|
authorization="PortainerEndpointUpdateSettings"
|
||||||
|
path="docker.featuresConfiguration"
|
||||||
|
path-params="{ endpointId: $ctrl.endpointId }"
|
||||||
|
class-name="sidebar-sublist"
|
||||||
|
>
|
||||||
|
Setup
|
||||||
|
</sidebar-menu-item>
|
||||||
|
|
||||||
|
<sidebar-menu-item authorization="PortainerRegistryList" path="docker.registries" path-params="{ endpointId: $ctrl.endpointId }" class-name="sidebar-sublist">
|
||||||
|
Registries
|
||||||
|
</sidebar-menu-item>
|
||||||
|
</div>
|
||||||
|
</sidebar-menu>
|
|
@ -1,12 +1,13 @@
|
||||||
angular.module('portainer.docker').component('dockerSidebarContent', {
|
angular.module('portainer.docker').component('dockerSidebar', {
|
||||||
templateUrl: './dockerSidebarContent.html',
|
templateUrl: './docker-sidebar.html',
|
||||||
bindings: {
|
bindings: {
|
||||||
|
isSidebarOpen: '<',
|
||||||
|
|
||||||
endpointApiVersion: '<',
|
endpointApiVersion: '<',
|
||||||
swarmManagement: '<',
|
swarmManagement: '<',
|
||||||
standaloneManagement: '<',
|
standaloneManagement: '<',
|
||||||
adminAccess: '<',
|
adminAccess: '<',
|
||||||
offlineMode: '<',
|
offlineMode: '<',
|
||||||
toggle: '<',
|
|
||||||
currentRouteName: '<',
|
currentRouteName: '<',
|
||||||
endpointId: '<',
|
endpointId: '<',
|
||||||
showStacks: '<',
|
showStacks: '<',
|
|
@ -1,53 +0,0 @@
|
||||||
<li class="sidebar-list">
|
|
||||||
<a ui-sref="docker.dashboard({endpointId: $ctrl.endpointId})" ui-sref-active="active">Dashboard <span class="menu-icon fa fa-tachometer-alt fa-fw"></span></a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list" ng-if="!$ctrl.offlineMode" authorization="DockerContainerCreate, PortainerStackCreate">
|
|
||||||
<a ui-sref="docker.templates({endpointId: $ctrl.endpointId})" ui-sref-active="active">App Templates <span class="menu-icon fa fa-rocket fa-fw"></span></a>
|
|
||||||
|
|
||||||
<div class="sidebar-sublist" ng-if="$ctrl.toggle && $ctrl.currentRouteName.includes('docker.templates')">
|
|
||||||
<a ui-sref="docker.templates.custom({endpointId: $ctrl.endpointId})" ui-sref-active="active">Custom Templates</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list" ng-if="$ctrl.showStacks">
|
|
||||||
<a ui-sref="docker.stacks({endpointId: $ctrl.endpointId})" ui-sref-active="active">Stacks <span class="menu-icon fa fa-th-list fa-fw"></span></a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list" ng-if="$ctrl.swarmManagement">
|
|
||||||
<a ui-sref="docker.services({endpointId: $ctrl.endpointId})" ui-sref-active="active">Services <span class="menu-icon fa fa-list-alt fa-fw"></span></a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list">
|
|
||||||
<a ui-sref="docker.containers({endpointId: $ctrl.endpointId})" ui-sref-active="active">Containers <span class="menu-icon fa fa-cubes fa-fw"></span></a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list">
|
|
||||||
<a ui-sref="docker.images({endpointId: $ctrl.endpointId})" ui-sref-active="active">Images <span class="menu-icon fa fa-clone fa-fw"></span></a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list">
|
|
||||||
<a ui-sref="docker.networks({endpointId: $ctrl.endpointId})" ui-sref-active="active">Networks <span class="menu-icon fa fa-sitemap fa-fw"></span></a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list">
|
|
||||||
<a ui-sref="docker.volumes({endpointId: $ctrl.endpointId})" ui-sref-active="active">Volumes <span class="menu-icon fa fa-hdd fa-fw"></span></a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list" ng-if="$ctrl.endpointApiVersion >= 1.3 && $ctrl.swarmManagement">
|
|
||||||
<a ui-sref="docker.configs({endpointId: $ctrl.endpointId})" ui-sref-active="active">Configs <span class="menu-icon fa fa-file-code fa-fw"></span></a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list" ng-if="$ctrl.endpointApiVersion >= 1.25 && $ctrl.swarmManagement">
|
|
||||||
<a ui-sref="docker.secrets({endpointId: $ctrl.endpointId})" ui-sref-active="active">Secrets <span class="menu-icon fa fa-user-secret fa-fw"></span></a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list" ng-if="$ctrl.standaloneManagement && $ctrl.adminAccess && !$ctrl.offlineMode">
|
|
||||||
<a ui-sref="docker.events({endpointId: $ctrl.endpointId})" ui-sref-active="active">Events <span class="menu-icon fa fa-history fa-fw"></span></a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list">
|
|
||||||
<a ng-if="$ctrl.standaloneManagement" ui-sref="docker.host({endpointId: $ctrl.endpointId})" ui-sref-active="active">Host <span class="menu-icon fa fa-th fa-fw"></span></a>
|
|
||||||
<a ng-if="$ctrl.swarmManagement" ui-sref="docker.swarm({endpointId: $ctrl.endpointId})" ui-sref-active="active">Swarm <span class="menu-icon fa fa-object-group fa-fw"></span></a>
|
|
||||||
|
|
||||||
<div
|
|
||||||
ng-if="$ctrl.adminAccess && ['docker.swarm', 'docker.host', 'docker.registries', 'docker.registries.access', 'docker.featuresConfiguration'].includes($ctrl.currentRouteName)"
|
|
||||||
>
|
|
||||||
<div class="sidebar-sublist">
|
|
||||||
<a ui-sref="docker.featuresConfiguration({endpointId: $ctrl.endpointId})" ui-sref-active="active">Setup</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="sidebar-sublist">
|
|
||||||
<a ui-sref="docker.registries({endpointId: $ctrl.endpointId})" ui-sref-active="active">Registries</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
|
@ -133,7 +133,7 @@ angular.module('portainer.docker').factory('ImageService', [
|
||||||
|
|
||||||
Image.create({}, imageConfiguration)
|
Image.create({}, imageConfiguration)
|
||||||
.$promise.then(function success(data) {
|
.$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) {
|
if (err) {
|
||||||
var detail = data[data.length - 1];
|
var detail = data[data.length - 1];
|
||||||
deferred.reject({ msg: detail.message });
|
deferred.reject({ msg: detail.message });
|
||||||
|
|
|
@ -16,7 +16,7 @@ angular.module('portainer.integrations.storidge').factory('StoridgeNodeService',
|
||||||
var nodes = [];
|
var nodes = [];
|
||||||
|
|
||||||
for (var key in nodeData) {
|
for (var key in nodeData) {
|
||||||
if (nodeData.hasOwnProperty(key)) {
|
if (Object.prototype.hasOwnProperty.call(nodeData, key)) {
|
||||||
nodes.push(new StoridgeNodeModel(key, nodeData[key]));
|
nodes.push(new StoridgeNodeModel(key, nodeData[key]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ angular.module('portainer.integrations.storidge').factory('StoridgeSnapshotServi
|
||||||
var snapshotsData = data.snapshots;
|
var snapshotsData = data.snapshots;
|
||||||
let snapshotsArray = [];
|
let snapshotsArray = [];
|
||||||
for (const key in snapshotsData) {
|
for (const key in snapshotsData) {
|
||||||
if (snapshotsData.hasOwnProperty(key)) {
|
if (Object.prototype.hasOwnProperty.call(snapshotsData, key)) {
|
||||||
snapshotsArray.push(snapshotsData[key]);
|
snapshotsArray.push(snapshotsData[key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ export default class KubectlShellController {
|
||||||
}
|
}
|
||||||
|
|
||||||
configureSocketAndTerminal(socket, term) {
|
configureSocketAndTerminal(socket, term) {
|
||||||
|
var vm = this;
|
||||||
socket.onopen = function () {
|
socket.onopen = function () {
|
||||||
const terminal_container = document.getElementById('terminal-container');
|
const terminal_container = document.getElementById('terminal-container');
|
||||||
term.open(terminal_container);
|
term.open(terminal_container);
|
||||||
|
@ -55,7 +56,7 @@ export default class KubectlShellController {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$window.onresize = function () {
|
this.$window.onresize = function () {
|
||||||
term.fit();
|
vm.TerminalWindow.terminalresize();
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.onmessage = function (msg) {
|
socket.onmessage = function (msg) {
|
||||||
|
|
|
@ -60,7 +60,7 @@ ul.sidebar li .shell-item-center a:hover {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 480px;
|
height: 495px;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
<li class="sidebar-list">
|
|
||||||
<a ui-sref="kubernetes.dashboard({endpointId: $ctrl.endpointId})" ui-sref-active="active">Dashboard <span class="menu-icon fa fa-tachometer-alt fa-fw"></span></a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list">
|
|
||||||
<a ui-sref="kubernetes.resourcePools({endpointId: $ctrl.endpointId})" ui-sref-active="active">Namespaces <span class="menu-icon fa fa-layer-group fa-fw"></span></a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list">
|
|
||||||
<a ui-sref="kubernetes.applications({endpointId: $ctrl.endpointId})" ui-sref-active="active">Applications <span class="menu-icon fa fa-laptop-code fa-fw"></span></a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list">
|
|
||||||
<a ui-sref="kubernetes.configurations({endpointId: $ctrl.endpointId})" ui-sref-active="active">Configurations <span class="menu-icon fa fa-file-code fa-fw"></span></a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list">
|
|
||||||
<a ui-sref="kubernetes.volumes({endpointId: $ctrl.endpointId})" ui-sref-active="active">Volumes <span class="menu-icon fa fa-database fa-fw"></span></a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list">
|
|
||||||
<a ui-sref="kubernetes.cluster({endpointId: $ctrl.endpointId})" ui-sref-active="active">Cluster <span class="menu-icon fa fa-server fa-fw"></span></a>
|
|
||||||
<div
|
|
||||||
ng-if="
|
|
||||||
$ctrl.adminAccess &&
|
|
||||||
['kubernetes.cluster', 'portainer.endpoints.endpoint.kubernetesConfig', 'kubernetes.registries', 'kubernetes.registries.access'].includes($ctrl.currentState)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div class="sidebar-sublist">
|
|
||||||
<a ui-sref="portainer.endpoints.endpoint.kubernetesConfig({id: $ctrl.endpointId})" ui-sref-active="active">Setup</a>
|
|
||||||
</div>
|
|
||||||
<div class="sidebar-sublist">
|
|
||||||
<a ui-sref="kubernetes.registries({endpointId: $ctrl.endpointId})" ui-sref-active="active">Registries</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
|
@ -1,8 +0,0 @@
|
||||||
angular.module('portainer.kubernetes').component('kubernetesSidebarContent', {
|
|
||||||
templateUrl: './kubernetesSidebarContent.html',
|
|
||||||
bindings: {
|
|
||||||
adminAccess: '<',
|
|
||||||
endpointId: '<',
|
|
||||||
currentState: '<',
|
|
||||||
},
|
|
||||||
});
|
|
9
app/kubernetes/components/kubernetes-sidebar/index.js
Normal file
9
app/kubernetes/components/kubernetes-sidebar/index.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import angular from 'angular';
|
||||||
|
|
||||||
|
angular.module('portainer.kubernetes').component('kubernetesSidebar', {
|
||||||
|
templateUrl: './kubernetes-sidebar.html',
|
||||||
|
bindings: {
|
||||||
|
endpointId: '<',
|
||||||
|
isSidebarOpen: '<',
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,38 @@
|
||||||
|
<sidebar-menu-item path="kubernetes.dashboard" path-params="{ endpointId: $ctrl.endpointId }" icon-class="fa-tachometer-alt fa-fw" class-name="sidebar-list">
|
||||||
|
Dashboard
|
||||||
|
</sidebar-menu-item>
|
||||||
|
|
||||||
|
<sidebar-menu-item path="kubernetes.resourcePools" path-params="{ endpointId: $ctrl.endpointId }" icon-class="fa-layer-group fa-fw" class-name="sidebar-list">
|
||||||
|
Namespaces
|
||||||
|
</sidebar-menu-item>
|
||||||
|
|
||||||
|
<sidebar-menu-item path="kubernetes.applications" path-params="{ endpointId: $ctrl.endpointId }" icon-class="fa-laptop-code fa-fw" class-name="sidebar-list">
|
||||||
|
Applications
|
||||||
|
</sidebar-menu-item>
|
||||||
|
|
||||||
|
<sidebar-menu-item path="kubernetes.configurations" path-params="{ endpointId: $ctrl.endpointId }" icon-class="fa-file-code fa-fw" class-name="sidebar-list">
|
||||||
|
Configurations
|
||||||
|
</sidebar-menu-item>
|
||||||
|
|
||||||
|
<sidebar-menu-item path="kubernetes.volumes" path-params="{ endpointId: $ctrl.endpointId }" icon-class="fa-database fa-fw" class-name="sidebar-list">
|
||||||
|
Volumes
|
||||||
|
</sidebar-menu-item>
|
||||||
|
|
||||||
|
<sidebar-menu
|
||||||
|
icon-class="fa-server fa-fw"
|
||||||
|
label="Cluster"
|
||||||
|
path="kubernetes.cluster"
|
||||||
|
path-params="{ endpointId: $ctrl.endpointId }"
|
||||||
|
is-sidebar-open="$ctrl.isSidebarOpen"
|
||||||
|
children-paths="['kubernetes.cluster', 'portainer.endpoints.endpoint.kubernetesConfig', 'kubernetes.registries', 'kubernetes.registries.access']"
|
||||||
|
>
|
||||||
|
<div ng-if="$ctrl.adminAccess">
|
||||||
|
<sidebar-menu-item authorization="K8sClusterSetupRW" path="portainer.endpoints.endpoint.kubernetesConfig" path-params="{ id: $ctrl.endpointId }" class-name="sidebar-sublist">
|
||||||
|
Setup
|
||||||
|
</sidebar-menu-item>
|
||||||
|
|
||||||
|
<sidebar-menu-item authorization="PortainerRegistryList" path="kubernetes.registries" path-params="{ endpointId: $ctrl.endpointId }" class-name="sidebar-sublist">
|
||||||
|
Registries
|
||||||
|
</sidebar-menu-item>
|
||||||
|
</div>
|
||||||
|
</sidebar-menu>
|
|
@ -89,122 +89,110 @@ class KubernetesApplicationService {
|
||||||
|
|
||||||
/* #region GET */
|
/* #region GET */
|
||||||
async getAsync(namespace, name) {
|
async getAsync(namespace, name) {
|
||||||
try {
|
const [deployment, daemonSet, statefulSet, pod, pods, autoScalers, ingresses] = await Promise.allSettled([
|
||||||
const [deployment, daemonSet, statefulSet, pod, pods, autoScalers, ingresses] = await Promise.allSettled([
|
this.KubernetesDeploymentService.get(namespace, name),
|
||||||
this.KubernetesDeploymentService.get(namespace, name),
|
this.KubernetesDaemonSetService.get(namespace, name),
|
||||||
this.KubernetesDaemonSetService.get(namespace, name),
|
this.KubernetesStatefulSetService.get(namespace, name),
|
||||||
this.KubernetesStatefulSetService.get(namespace, name),
|
this.KubernetesPodService.get(namespace, name),
|
||||||
this.KubernetesPodService.get(namespace, name),
|
this.KubernetesPodService.get(namespace),
|
||||||
this.KubernetesPodService.get(namespace),
|
this.KubernetesHorizontalPodAutoScalerService.get(namespace),
|
||||||
this.KubernetesHorizontalPodAutoScalerService.get(namespace),
|
this.KubernetesIngressService.get(namespace),
|
||||||
this.KubernetesIngressService.get(namespace),
|
]);
|
||||||
]);
|
|
||||||
|
|
||||||
// const pod = _.find(pods.value, ['metadata.namespace', namespace, 'metadata.name', name]);
|
let rootItem;
|
||||||
|
let converterFunc;
|
||||||
let rootItem;
|
if (deployment.status === 'fulfilled') {
|
||||||
let converterFunc;
|
rootItem = deployment;
|
||||||
if (deployment.status === 'fulfilled') {
|
converterFunc = KubernetesApplicationConverter.apiDeploymentToApplication;
|
||||||
rootItem = deployment;
|
} else if (daemonSet.status === 'fulfilled') {
|
||||||
converterFunc = KubernetesApplicationConverter.apiDeploymentToApplication;
|
rootItem = daemonSet;
|
||||||
} else if (daemonSet.status === 'fulfilled') {
|
converterFunc = KubernetesApplicationConverter.apiDaemonSetToApplication;
|
||||||
rootItem = daemonSet;
|
} else if (statefulSet.status === 'fulfilled') {
|
||||||
converterFunc = KubernetesApplicationConverter.apiDaemonSetToApplication;
|
rootItem = statefulSet;
|
||||||
} else if (statefulSet.status === 'fulfilled') {
|
converterFunc = KubernetesApplicationConverter.apiStatefulSetToapplication;
|
||||||
rootItem = statefulSet;
|
} else if (pod.status === 'fulfilled') {
|
||||||
converterFunc = KubernetesApplicationConverter.apiStatefulSetToapplication;
|
rootItem = pod;
|
||||||
} else if (pod.status === 'fulfilled') {
|
converterFunc = KubernetesApplicationConverter.apiPodToApplication;
|
||||||
rootItem = pod;
|
} else {
|
||||||
converterFunc = KubernetesApplicationConverter.apiPodToApplication;
|
throw new PortainerError('Unable to determine which association to use to convert application');
|
||||||
} 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
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 convertToApplication = (item, converterFunc, services, pods, ingresses) => {
|
||||||
const service = KubernetesServiceHelper.findApplicationBoundService(services, item);
|
const service = KubernetesServiceHelper.findApplicationBoundService(services, item);
|
||||||
const application = converterFunc(item, pods, service, ingresses);
|
const application = converterFunc(item, pods, service, ingresses);
|
||||||
application.Containers = KubernetesApplicationHelper.associateContainersAndApplication(application);
|
application.Containers = KubernetesApplicationHelper.associateContainersAndApplication(application);
|
||||||
return application;
|
return application;
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await Promise.all(
|
const res = await Promise.all(
|
||||||
_.map(namespaces, async (ns) => {
|
_.map(namespaces, async (ns) => {
|
||||||
const [deployments, daemonSets, statefulSets, services, pods, ingresses, autoScalers] = await Promise.all([
|
const [deployments, daemonSets, statefulSets, services, pods, ingresses, autoScalers] = await Promise.all([
|
||||||
this.KubernetesDeploymentService.get(ns),
|
this.KubernetesDeploymentService.get(ns),
|
||||||
this.KubernetesDaemonSetService.get(ns),
|
this.KubernetesDaemonSetService.get(ns),
|
||||||
this.KubernetesStatefulSetService.get(ns),
|
this.KubernetesStatefulSetService.get(ns),
|
||||||
this.KubernetesServiceService.get(ns),
|
this.KubernetesServiceService.get(ns),
|
||||||
this.KubernetesPodService.get(ns),
|
this.KubernetesPodService.get(ns),
|
||||||
this.KubernetesIngressService.get(ns),
|
this.KubernetesIngressService.get(ns),
|
||||||
this.KubernetesHorizontalPodAutoScalerService.get(ns),
|
this.KubernetesHorizontalPodAutoScalerService.get(ns),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const deploymentApplications = _.map(deployments, (item) =>
|
const deploymentApplications = _.map(deployments, (item) =>
|
||||||
convertToApplication(item, KubernetesApplicationConverter.apiDeploymentToApplication, services, pods, ingresses)
|
convertToApplication(item, KubernetesApplicationConverter.apiDeploymentToApplication, services, pods, ingresses)
|
||||||
);
|
);
|
||||||
const daemonSetApplications = _.map(daemonSets, (item) =>
|
const daemonSetApplications = _.map(daemonSets, (item) => convertToApplication(item, KubernetesApplicationConverter.apiDaemonSetToApplication, services, pods, ingresses));
|
||||||
convertToApplication(item, KubernetesApplicationConverter.apiDaemonSetToApplication, services, pods, ingresses)
|
const statefulSetApplications = _.map(statefulSets, (item) =>
|
||||||
);
|
convertToApplication(item, KubernetesApplicationConverter.apiStatefulSetToapplication, 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 boundPods = _.concat(_.flatMap(deploymentApplications, 'Pods'), _.flatMap(daemonSetApplications, 'Pods'), _.flatMap(statefulSetApplications, 'Pods'));
|
||||||
const unboundPods = _.without(pods, ...boundPods);
|
const unboundPods = _.without(pods, ...boundPods);
|
||||||
const nakedPodsApplications = _.map(unboundPods, (item) => convertToApplication(item, KubernetesApplicationConverter.apiPodToApplication, services, pods, ingresses));
|
const nakedPodsApplications = _.map(unboundPods, (item) => convertToApplication(item, KubernetesApplicationConverter.apiPodToApplication, services, pods, ingresses));
|
||||||
|
|
||||||
const applications = _.concat(deploymentApplications, daemonSetApplications, statefulSetApplications, nakedPodsApplications);
|
const applications = _.concat(deploymentApplications, daemonSetApplications, statefulSetApplications, nakedPodsApplications);
|
||||||
_.forEach(applications, (app) => {
|
_.forEach(applications, (app) => {
|
||||||
app.Pods = _.map(app.Pods, (item) => KubernetesPodConverter.apiToModel(item));
|
app.Pods = _.map(app.Pods, (item) => KubernetesPodConverter.apiToModel(item));
|
||||||
});
|
});
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
_.forEach(applications, async (application) => {
|
_.forEach(applications, async (application) => {
|
||||||
const boundScaler = KubernetesHorizontalPodAutoScalerHelper.findApplicationBoundScaler(autoScalers, application);
|
const boundScaler = KubernetesHorizontalPodAutoScalerHelper.findApplicationBoundScaler(autoScalers, application);
|
||||||
const scaler = boundScaler ? await this.KubernetesHorizontalPodAutoScalerService.get(ns, boundScaler.Name) : undefined;
|
const scaler = boundScaler ? await this.KubernetesHorizontalPodAutoScalerService.get(ns, boundScaler.Name) : undefined;
|
||||||
application.AutoScaler = scaler;
|
application.AutoScaler = scaler;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
return applications;
|
return applications;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
return _.flatten(res);
|
return _.flatten(res);
|
||||||
} catch (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get(namespace, name) {
|
get(namespace, name) {
|
||||||
|
@ -226,42 +214,38 @@ class KubernetesApplicationService {
|
||||||
* also be displayed in the summary output (getCreatedApplicationResources)
|
* also be displayed in the summary output (getCreatedApplicationResources)
|
||||||
*/
|
*/
|
||||||
async createAsync(formValues) {
|
async createAsync(formValues) {
|
||||||
try {
|
let [app, headlessService, service, claims] = KubernetesApplicationConverter.applicationFormValuesToApplication(formValues);
|
||||||
let [app, headlessService, service, claims] = KubernetesApplicationConverter.applicationFormValuesToApplication(formValues);
|
|
||||||
|
|
||||||
if (service) {
|
if (service) {
|
||||||
await this.KubernetesServiceService.create(service);
|
await this.KubernetesServiceService.create(service);
|
||||||
if (formValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
|
if (formValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
|
||||||
const ingresses = KubernetesIngressConverter.applicationFormValuesToIngresses(formValues, service.Name);
|
const ingresses = KubernetesIngressConverter.applicationFormValuesToIngresses(formValues, service.Name);
|
||||||
await Promise.all(this._generateIngressPatchPromises(formValues.OriginalIngresses, ingresses));
|
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) {
|
create(formValues) {
|
||||||
|
@ -277,97 +261,89 @@ class KubernetesApplicationService {
|
||||||
* in this method should also be displayed in the summary output (getUpdatedApplicationResources)
|
* in this method should also be displayed in the summary output (getUpdatedApplicationResources)
|
||||||
*/
|
*/
|
||||||
async patchAsync(oldFormValues, newFormValues) {
|
async patchAsync(oldFormValues, newFormValues) {
|
||||||
try {
|
const [oldApp, oldHeadlessService, oldService, oldClaims] = KubernetesApplicationConverter.applicationFormValuesToApplication(oldFormValues);
|
||||||
const [oldApp, oldHeadlessService, oldService, oldClaims] = KubernetesApplicationConverter.applicationFormValuesToApplication(oldFormValues);
|
const [newApp, newHeadlessService, newService, newClaims] = KubernetesApplicationConverter.applicationFormValuesToApplication(newFormValues);
|
||||||
const [newApp, newHeadlessService, newService, newClaims] = KubernetesApplicationConverter.applicationFormValuesToApplication(newFormValues);
|
const oldApiService = this._getApplicationApiService(oldApp);
|
||||||
const oldApiService = this._getApplicationApiService(oldApp);
|
const newApiService = this._getApplicationApiService(newApp);
|
||||||
const newApiService = this._getApplicationApiService(newApp);
|
|
||||||
|
|
||||||
if (oldApiService !== newApiService) {
|
if (oldApiService !== newApiService) {
|
||||||
await this.delete(oldApp);
|
await this.delete(oldApp);
|
||||||
if (oldService) {
|
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) {
|
|
||||||
await this.KubernetesServiceService.delete(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);
|
if (newApp instanceof KubernetesStatefulSet) {
|
||||||
const newAutoScaler = KubernetesHorizontalPodAutoScalerConverter.applicationFormValuesToModel(newFormValues, newKind);
|
await this.KubernetesServiceService.patch(oldHeadlessService, newHeadlessService);
|
||||||
if (!oldFormValues.AutoScaler.IsUsed) {
|
} else {
|
||||||
if (newFormValues.AutoScaler.IsUsed) {
|
const claimPromises = _.map(newClaims, (newClaim) => {
|
||||||
await this.KubernetesHorizontalPodAutoScalerService.create(newAutoScaler);
|
if (!newClaim.PreviousName && !newClaim.Id) {
|
||||||
}
|
return this.KubernetesPersistentVolumeClaimService.create(newClaim);
|
||||||
} else {
|
} else if (!newClaim.Id) {
|
||||||
const oldKind = KubernetesHorizontalPodAutoScalerHelper.getApplicationTypeString(oldApp);
|
const oldClaim = _.find(oldClaims, { Name: newClaim.PreviousName });
|
||||||
const oldAutoScaler = KubernetesHorizontalPodAutoScalerConverter.applicationFormValuesToModel(oldFormValues, oldKind);
|
return this.KubernetesPersistentVolumeClaimService.patch(oldClaim, newClaim);
|
||||||
if (newFormValues.AutoScaler.IsUsed) {
|
|
||||||
await this.KubernetesHorizontalPodAutoScalerService.patch(oldAutoScaler, newAutoScaler);
|
|
||||||
} else {
|
|
||||||
await this.KubernetesHorizontalPodAutoScalerService.delete(oldAutoScaler);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
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
|
// this function accepts KubernetesApplication as parameters
|
||||||
async patchPartialAsync(oldApp, newApp) {
|
async patchPartialAsync(oldApp, newApp) {
|
||||||
try {
|
const oldAppPayload = {
|
||||||
const oldAppPayload = {
|
Name: oldApp.Name,
|
||||||
Name: oldApp.Name,
|
Namespace: oldApp.ResourcePool,
|
||||||
Namespace: oldApp.ResourcePool,
|
StackName: oldApp.StackName,
|
||||||
StackName: oldApp.StackName,
|
Note: oldApp.Note,
|
||||||
Note: oldApp.Note,
|
};
|
||||||
};
|
const newAppPayload = {
|
||||||
const newAppPayload = {
|
Name: newApp.Name,
|
||||||
Name: newApp.Name,
|
Namespace: newApp.ResourcePool,
|
||||||
Namespace: newApp.ResourcePool,
|
StackName: newApp.StackName,
|
||||||
StackName: newApp.StackName,
|
Note: newApp.Note,
|
||||||
Note: newApp.Note,
|
};
|
||||||
};
|
const apiService = this._getApplicationApiService(oldApp);
|
||||||
const apiService = this._getApplicationApiService(oldApp);
|
await apiService.patch(oldAppPayload, newAppPayload);
|
||||||
await apiService.patch(oldAppPayload, newAppPayload);
|
|
||||||
} catch (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// accept either formValues or applications as parameters
|
// accept either formValues or applications as parameters
|
||||||
|
@ -384,42 +360,38 @@ class KubernetesApplicationService {
|
||||||
|
|
||||||
/* #region DELETE */
|
/* #region DELETE */
|
||||||
async deleteAsync(application) {
|
async deleteAsync(application) {
|
||||||
try {
|
const payload = {
|
||||||
const payload = {
|
Namespace: application.ResourcePool || application.Namespace,
|
||||||
Namespace: application.ResourcePool || application.Namespace,
|
Name: application.Name,
|
||||||
Name: application.Name,
|
};
|
||||||
};
|
const servicePayload = angular.copy(payload);
|
||||||
const servicePayload = angular.copy(payload);
|
servicePayload.Name = application.Name;
|
||||||
servicePayload.Name = application.Name;
|
|
||||||
|
|
||||||
const apiService = this._getApplicationApiService(application);
|
const apiService = this._getApplicationApiService(application);
|
||||||
await apiService.delete(payload);
|
await apiService.delete(payload);
|
||||||
|
|
||||||
if (apiService === this.KubernetesStatefulSetService) {
|
if (apiService === this.KubernetesStatefulSetService) {
|
||||||
const headlessServicePayload = angular.copy(payload);
|
const headlessServicePayload = angular.copy(payload);
|
||||||
headlessServicePayload.Name = application instanceof KubernetesStatefulSet ? application.ServiceName : application.HeadlessServiceName;
|
headlessServicePayload.Name = application instanceof KubernetesStatefulSet ? application.ServiceName : application.HeadlessServiceName;
|
||||||
await this.KubernetesServiceService.delete(headlessServicePayload);
|
await this.KubernetesServiceService.delete(headlessServicePayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (application.ServiceType) {
|
if (application.ServiceType) {
|
||||||
await this.KubernetesServiceService.delete(servicePayload);
|
await this.KubernetesServiceService.delete(servicePayload);
|
||||||
const isIngress = _.filter(application.PublishedPorts, (p) => p.IngressRules.length).length;
|
const isIngress = _.filter(application.PublishedPorts, (p) => p.IngressRules.length).length;
|
||||||
if (isIngress) {
|
if (isIngress) {
|
||||||
const originalIngresses = await this.KubernetesIngressService.get(payload.Namespace);
|
const originalIngresses = await this.KubernetesIngressService.get(payload.Namespace);
|
||||||
const formValues = {
|
const formValues = {
|
||||||
OriginalIngresses: originalIngresses,
|
OriginalIngresses: originalIngresses,
|
||||||
PublishedPorts: KubernetesApplicationHelper.generatePublishedPortsFormValuesFromPublishedPorts(application.ServiceType, application.PublishedPorts),
|
PublishedPorts: KubernetesApplicationHelper.generatePublishedPortsFormValuesFromPublishedPorts(application.ServiceType, application.PublishedPorts),
|
||||||
};
|
};
|
||||||
_.forEach(formValues.PublishedPorts, (p) => (p.NeedsDeletion = true));
|
_.forEach(formValues.PublishedPorts, (p) => (p.NeedsDeletion = true));
|
||||||
const ingresses = KubernetesIngressConverter.applicationFormValuesToIngresses(formValues, servicePayload.Name);
|
const ingresses = KubernetesIngressConverter.applicationFormValuesToIngresses(formValues, servicePayload.Name);
|
||||||
await Promise.all(this._generateIngressPatchPromises(formValues.OriginalIngresses, ingresses));
|
await Promise.all(this._generateIngressPatchPromises(formValues.OriginalIngresses, ingresses));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!_.isEmpty(application.AutoScaler)) {
|
}
|
||||||
await this.KubernetesHorizontalPodAutoScalerService.delete(application.AutoScaler);
|
if (!_.isEmpty(application.AutoScaler)) {
|
||||||
}
|
await this.KubernetesHorizontalPodAutoScalerService.delete(application.AutoScaler);
|
||||||
} catch (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,13 +402,9 @@ class KubernetesApplicationService {
|
||||||
|
|
||||||
/* #region ROLLBACK */
|
/* #region ROLLBACK */
|
||||||
async rollbackAsync(application, targetRevision) {
|
async rollbackAsync(application, targetRevision) {
|
||||||
try {
|
const payload = KubernetesApplicationRollbackHelper.getPatchPayload(application, targetRevision);
|
||||||
const payload = KubernetesApplicationRollbackHelper.getPatchPayload(application, targetRevision);
|
const apiService = this._getApplicationApiService(application);
|
||||||
const apiService = this._getApplicationApiService(application);
|
await apiService.rollback(application.ResourcePool, application.Name, payload);
|
||||||
await apiService.rollback(application.ResourcePool, application.Name, payload);
|
|
||||||
} catch (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rollback(application, targetRevision) {
|
rollback(application, targetRevision) {
|
||||||
|
|
|
@ -26,35 +26,27 @@ class KubernetesConfigurationService {
|
||||||
* GET
|
* GET
|
||||||
*/
|
*/
|
||||||
async getAsync(namespace, name) {
|
async getAsync(namespace, name) {
|
||||||
try {
|
const [configMap, secret] = await Promise.allSettled([this.KubernetesConfigMapService.get(namespace, name), this.KubernetesSecretService.get(namespace, name)]);
|
||||||
const [configMap, secret] = await Promise.allSettled([this.KubernetesConfigMapService.get(namespace, name), this.KubernetesSecretService.get(namespace, name)]);
|
let configuration;
|
||||||
let configuration;
|
if (secret.status === 'fulfilled') {
|
||||||
if (secret.status === 'fulfilled') {
|
configuration = KubernetesConfigurationConverter.secretToConfiguration(secret.value);
|
||||||
configuration = KubernetesConfigurationConverter.secretToConfiguration(secret.value);
|
|
||||||
return configuration;
|
|
||||||
}
|
|
||||||
configuration = KubernetesConfigurationConverter.configMapToConfiguration(configMap.value);
|
|
||||||
return configuration;
|
return configuration;
|
||||||
} catch (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
|
configuration = KubernetesConfigurationConverter.configMapToConfiguration(configMap.value);
|
||||||
|
return configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllAsync(namespace) {
|
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 res = await Promise.all(
|
||||||
const res = await Promise.all(
|
_.map(namespaces, async (ns) => {
|
||||||
_.map(namespaces, async (ns) => {
|
const [configMaps, secrets] = await Promise.all([this.KubernetesConfigMapService.get(ns), this.KubernetesSecretService.get(ns)]);
|
||||||
const [configMaps, secrets] = await Promise.all([this.KubernetesConfigMapService.get(ns), this.KubernetesSecretService.get(ns)]);
|
const secretsConfigurations = _.map(secrets, (secret) => KubernetesConfigurationConverter.secretToConfiguration(secret));
|
||||||
const secretsConfigurations = _.map(secrets, (secret) => KubernetesConfigurationConverter.secretToConfiguration(secret));
|
const configMapsConfigurations = _.map(configMaps, (configMap) => KubernetesConfigurationConverter.configMapToConfiguration(configMap));
|
||||||
const configMapsConfigurations = _.map(configMaps, (configMap) => KubernetesConfigurationConverter.configMapToConfiguration(configMap));
|
return _.concat(configMapsConfigurations, secretsConfigurations);
|
||||||
return _.concat(configMapsConfigurations, secretsConfigurations);
|
})
|
||||||
})
|
);
|
||||||
);
|
return _.flatten(res);
|
||||||
return _.flatten(res);
|
|
||||||
} catch (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get(namespace, name) {
|
get(namespace, name) {
|
||||||
|
@ -70,16 +62,12 @@ class KubernetesConfigurationService {
|
||||||
async createAsync(formValues) {
|
async createAsync(formValues) {
|
||||||
formValues.ConfigurationOwner = KubernetesCommonHelper.ownerToLabel(formValues.ConfigurationOwner);
|
formValues.ConfigurationOwner = KubernetesCommonHelper.ownerToLabel(formValues.ConfigurationOwner);
|
||||||
|
|
||||||
try {
|
if (formValues.Type === KubernetesConfigurationTypes.CONFIGMAP) {
|
||||||
if (formValues.Type === KubernetesConfigurationTypes.CONFIGMAP) {
|
const configMap = KubernetesConfigMapConverter.configurationFormValuesToConfigMap(formValues);
|
||||||
const configMap = KubernetesConfigMapConverter.configurationFormValuesToConfigMap(formValues);
|
await this.KubernetesConfigMapService.create(configMap);
|
||||||
await this.KubernetesConfigMapService.create(configMap);
|
} else {
|
||||||
} else {
|
const secret = KubernetesSecretConverter.configurationFormValuesToSecret(formValues);
|
||||||
const secret = KubernetesSecretConverter.configurationFormValuesToSecret(formValues);
|
await this.KubernetesSecretService.create(secret);
|
||||||
await this.KubernetesSecretService.create(secret);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,18 +79,14 @@ class KubernetesConfigurationService {
|
||||||
* UPDATE
|
* UPDATE
|
||||||
*/
|
*/
|
||||||
async updateAsync(formValues, configuration) {
|
async updateAsync(formValues, configuration) {
|
||||||
try {
|
if (formValues.Type === KubernetesConfigurationTypes.CONFIGMAP) {
|
||||||
if (formValues.Type === KubernetesConfigurationTypes.CONFIGMAP) {
|
const configMap = KubernetesConfigMapConverter.configurationFormValuesToConfigMap(formValues);
|
||||||
const configMap = KubernetesConfigMapConverter.configurationFormValuesToConfigMap(formValues);
|
configMap.ConfigurationOwner = configuration.ConfigurationOwner;
|
||||||
configMap.ConfigurationOwner = configuration.ConfigurationOwner;
|
await this.KubernetesConfigMapService.update(configMap);
|
||||||
await this.KubernetesConfigMapService.update(configMap);
|
} else {
|
||||||
} else {
|
const secret = KubernetesSecretConverter.configurationFormValuesToSecret(formValues);
|
||||||
const secret = KubernetesSecretConverter.configurationFormValuesToSecret(formValues);
|
secret.ConfigurationOwner = configuration.ConfigurationOwner;
|
||||||
secret.ConfigurationOwner = configuration.ConfigurationOwner;
|
await this.KubernetesSecretService.update(secret);
|
||||||
await this.KubernetesSecretService.update(secret);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,14 +98,10 @@ class KubernetesConfigurationService {
|
||||||
* DELETE
|
* DELETE
|
||||||
*/
|
*/
|
||||||
async deleteAsync(config) {
|
async deleteAsync(config) {
|
||||||
try {
|
if (config.Type === KubernetesConfigurationTypes.CONFIGMAP) {
|
||||||
if (config.Type === KubernetesConfigurationTypes.CONFIGMAP) {
|
await this.KubernetesConfigMapService.delete(config);
|
||||||
await this.KubernetesConfigMapService.delete(config);
|
} else {
|
||||||
} else {
|
await this.KubernetesSecretService.delete(config);
|
||||||
await this.KubernetesSecretService.delete(config);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,11 @@ class KubernetesConfigService {
|
||||||
|
|
||||||
async downloadConfig() {
|
async downloadConfig() {
|
||||||
const response = await this.KubernetesConfig.get();
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,39 +14,31 @@ export function KubernetesResourcePoolService($async, EndpointService, Kubernete
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getOne(name) {
|
async function getOne(name) {
|
||||||
try {
|
const namespace = await KubernetesNamespaceService.get(name);
|
||||||
const namespace = await KubernetesNamespaceService.get(name);
|
const [quotaAttempt] = await Promise.allSettled([KubernetesResourceQuotaService.get(name, KubernetesResourceQuotaHelper.generateResourceQuotaName(name))]);
|
||||||
const [quotaAttempt] = await Promise.allSettled([KubernetesResourceQuotaService.get(name, KubernetesResourceQuotaHelper.generateResourceQuotaName(name))]);
|
const pool = KubernetesResourcePoolConverter.apiToResourcePool(namespace);
|
||||||
const pool = KubernetesResourcePoolConverter.apiToResourcePool(namespace);
|
if (quotaAttempt.status === 'fulfilled') {
|
||||||
if (quotaAttempt.status === 'fulfilled') {
|
pool.Quota = quotaAttempt.value;
|
||||||
pool.Quota = quotaAttempt.value;
|
pool.Yaml += '---\n' + quotaAttempt.value.Yaml;
|
||||||
pool.Yaml += '---\n' + quotaAttempt.value.Yaml;
|
|
||||||
}
|
|
||||||
return pool;
|
|
||||||
} catch (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
|
return pool;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getAll() {
|
async function getAll() {
|
||||||
try {
|
const namespaces = await KubernetesNamespaceService.get();
|
||||||
const namespaces = await KubernetesNamespaceService.get();
|
const pools = await Promise.all(
|
||||||
const pools = await Promise.all(
|
_.map(namespaces, async (namespace) => {
|
||||||
_.map(namespaces, async (namespace) => {
|
const name = namespace.Name;
|
||||||
const name = namespace.Name;
|
const [quotaAttempt] = await Promise.allSettled([KubernetesResourceQuotaService.get(name, KubernetesResourceQuotaHelper.generateResourceQuotaName(name))]);
|
||||||
const [quotaAttempt] = await Promise.allSettled([KubernetesResourceQuotaService.get(name, KubernetesResourceQuotaHelper.generateResourceQuotaName(name))]);
|
const pool = KubernetesResourcePoolConverter.apiToResourcePool(namespace);
|
||||||
const pool = KubernetesResourcePoolConverter.apiToResourcePool(namespace);
|
if (quotaAttempt.status === 'fulfilled') {
|
||||||
if (quotaAttempt.status === 'fulfilled') {
|
pool.Quota = quotaAttempt.value;
|
||||||
pool.Quota = quotaAttempt.value;
|
pool.Yaml += '---\n' + quotaAttempt.value.Yaml;
|
||||||
pool.Yaml += '---\n' + quotaAttempt.value.Yaml;
|
}
|
||||||
}
|
return pool;
|
||||||
return pool;
|
})
|
||||||
})
|
);
|
||||||
);
|
return pools;
|
||||||
return pools;
|
|
||||||
} catch (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function get(name) {
|
function get(name) {
|
||||||
|
@ -58,80 +50,68 @@ export function KubernetesResourcePoolService($async, EndpointService, Kubernete
|
||||||
|
|
||||||
function create(formValues) {
|
function create(formValues) {
|
||||||
return $async(async () => {
|
return $async(async () => {
|
||||||
try {
|
const [namespace, quota, ingresses, registries] = KubernetesResourcePoolConverter.formValuesToResourcePool(formValues);
|
||||||
const [namespace, quota, ingresses, registries] = KubernetesResourcePoolConverter.formValuesToResourcePool(formValues);
|
await KubernetesNamespaceService.create(namespace);
|
||||||
await KubernetesNamespaceService.create(namespace);
|
|
||||||
|
|
||||||
if (quota) {
|
if (quota) {
|
||||||
await KubernetesResourceQuotaService.create(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;
|
|
||||||
}
|
}
|
||||||
|
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) {
|
function patch(oldFormValues, newFormValues) {
|
||||||
return $async(async () => {
|
return $async(async () => {
|
||||||
try {
|
const [oldNamespace, oldQuota, oldIngresses, oldRegistries] = KubernetesResourcePoolConverter.formValuesToResourcePool(oldFormValues);
|
||||||
const [oldNamespace, oldQuota, oldIngresses, oldRegistries] = KubernetesResourcePoolConverter.formValuesToResourcePool(oldFormValues);
|
const [newNamespace, newQuota, newIngresses, newRegistries] = KubernetesResourcePoolConverter.formValuesToResourcePool(newFormValues);
|
||||||
const [newNamespace, newQuota, newIngresses, newRegistries] = KubernetesResourcePoolConverter.formValuesToResourcePool(newFormValues);
|
void oldNamespace, newNamespace;
|
||||||
void oldNamespace, newNamespace;
|
|
||||||
|
|
||||||
if (oldQuota && newQuota) {
|
if (oldQuota && newQuota) {
|
||||||
await KubernetesResourceQuotaService.patch(oldQuota, newQuota);
|
await KubernetesResourceQuotaService.patch(oldQuota, newQuota);
|
||||||
} else if (!oldQuota && newQuota) {
|
} else if (!oldQuota && newQuota) {
|
||||||
await KubernetesResourceQuotaService.create(newQuota);
|
await KubernetesResourceQuotaService.create(newQuota);
|
||||||
} else if (oldQuota && !newQuota) {
|
} else if (oldQuota && !newQuota) {
|
||||||
await KubernetesResourceQuotaService.delete(oldQuota);
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
function _delete(pool) {
|
||||||
return $async(async () => {
|
return $async(async () => {
|
||||||
try {
|
await KubernetesNamespaceService.delete(pool.Namespace);
|
||||||
await KubernetesNamespaceService.delete(pool.Namespace);
|
|
||||||
} catch (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,13 +14,9 @@ class KubernetesStackService {
|
||||||
* GET
|
* GET
|
||||||
*/
|
*/
|
||||||
async getAllAsync(namespace) {
|
async getAllAsync(namespace) {
|
||||||
try {
|
const applications = await this.KubernetesApplicationService.get(namespace);
|
||||||
const applications = await this.KubernetesApplicationService.get(namespace);
|
const stacks = _.map(applications, (item) => item.StackName);
|
||||||
const stacks = _.map(applications, (item) => item.StackName);
|
return _.uniq(_.without(stacks, '-'));
|
||||||
return _.uniq(_.without(stacks, '-'));
|
|
||||||
} catch (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get(namespace) {
|
get(namespace) {
|
||||||
|
|
|
@ -20,28 +20,20 @@ class KubernetesVolumeService {
|
||||||
* GET
|
* GET
|
||||||
*/
|
*/
|
||||||
async getAsync(namespace, name) {
|
async getAsync(namespace, name) {
|
||||||
try {
|
const [pvc, pool] = await Promise.all([this.KubernetesPersistentVolumeClaimService.get(namespace, name), this.KubernetesResourcePoolService.get(namespace)]);
|
||||||
const [pvc, pool] = await Promise.all([this.KubernetesPersistentVolumeClaimService.get(namespace, name), this.KubernetesResourcePoolService.get(namespace)]);
|
return KubernetesVolumeConverter.pvcToVolume(pvc, pool);
|
||||||
return KubernetesVolumeConverter.pvcToVolume(pvc, pool);
|
|
||||||
} catch (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllAsync(namespace) {
|
async getAllAsync(namespace) {
|
||||||
try {
|
const data = await this.KubernetesResourcePoolService.get(namespace);
|
||||||
const data = await this.KubernetesResourcePoolService.get(namespace);
|
const pools = data instanceof Array ? data : [data];
|
||||||
const pools = data instanceof Array ? data : [data];
|
const res = await Promise.all(
|
||||||
const res = await Promise.all(
|
_.map(pools, async (pool) => {
|
||||||
_.map(pools, async (pool) => {
|
const pvcs = await this.KubernetesPersistentVolumeClaimService.get(pool.Namespace.Name);
|
||||||
const pvcs = await this.KubernetesPersistentVolumeClaimService.get(pool.Namespace.Name);
|
return _.map(pvcs, (pvc) => KubernetesVolumeConverter.pvcToVolume(pvc, pool));
|
||||||
return _.map(pvcs, (pvc) => KubernetesVolumeConverter.pvcToVolume(pvc, pool));
|
})
|
||||||
})
|
);
|
||||||
);
|
return _.flatten(res);
|
||||||
return _.flatten(res);
|
|
||||||
} catch (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get(namespace, name) {
|
get(namespace, name) {
|
||||||
|
@ -55,11 +47,7 @@ class KubernetesVolumeService {
|
||||||
* DELETE
|
* DELETE
|
||||||
*/
|
*/
|
||||||
async deleteAsync(volume) {
|
async deleteAsync(volume) {
|
||||||
try {
|
await this.KubernetesPersistentVolumeClaimService.delete(volume.PersistentVolumeClaim);
|
||||||
await this.KubernetesPersistentVolumeClaimService.delete(volume.PersistentVolumeClaim);
|
|
||||||
} catch (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(volume) {
|
delete(volume) {
|
||||||
|
|
|
@ -2,7 +2,7 @@ angular.module('portainer.app').controller('CodeEditorController', function Code
|
||||||
var ctrl = this;
|
var ctrl = this;
|
||||||
|
|
||||||
this.$onChanges = function $onChanges({ value }) {
|
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);
|
ctrl.editor.setValue(value.currentValue);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -68,7 +68,7 @@ class StackRedeployGitFormController {
|
||||||
this.state.redeployInProgress = true;
|
this.state.redeployInProgress = true;
|
||||||
|
|
||||||
await this.StackService.updateGit(this.stack.Id, this.stack.EndpointId, this.FormHelper.removeInvalidEnvVars(this.formValues.Env), false, this.formValues);
|
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();
|
await this.$state.reload();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.Notifications.error('Failure', err, 'Failed redeploying stack');
|
this.Notifications.error('Failure', err, 'Failed redeploying stack');
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
|
|
||||||
|
import sidebarModule from './sidebar';
|
||||||
import gitFormModule from './forms/git-form';
|
import gitFormModule from './forms/git-form';
|
||||||
import porAccessManagementModule from './accessManagement';
|
import porAccessManagementModule from './accessManagement';
|
||||||
import formComponentsModule from './form-components';
|
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;
|
||||||
|
|
12
app/portainer/components/sidebar/index.js
Normal file
12
app/portainer/components/sidebar/index.js
Normal file
|
@ -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;
|
12
app/portainer/components/sidebar/sidebar-menu-item/index.js
Normal file
12
app/portainer/components/sidebar/sidebar-menu-item/index.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import './sidebar-menu-item.css';
|
||||||
|
|
||||||
|
export const sidebarMenuItem = {
|
||||||
|
templateUrl: './sidebar-menu-item.html',
|
||||||
|
bindings: {
|
||||||
|
path: '@',
|
||||||
|
pathParams: '<',
|
||||||
|
iconClass: '@',
|
||||||
|
className: '@',
|
||||||
|
},
|
||||||
|
transclude: true,
|
||||||
|
};
|
|
@ -0,0 +1,4 @@
|
||||||
|
.sidebar-menu-item > a {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
<li class="sidebar-menu-item" ng-class="$ctrl.className">
|
||||||
|
<a ui-state="$ctrl.path" ui-state-params="$ctrl.pathParams" ui-sref-active="active">
|
||||||
|
<ng-transclude></ng-transclude>
|
||||||
|
<span ng-if="$ctrl.iconClass" class="menu-icon fa" ng-class="$ctrl.iconClass"></span>
|
||||||
|
</a>
|
||||||
|
</li>
|
18
app/portainer/components/sidebar/sidebar-menu/index.js
Normal file
18
app/portainer/components/sidebar/sidebar-menu/index.js
Normal file
|
@ -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,
|
||||||
|
};
|
|
@ -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;
|
|
@ -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;
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
<li class="sidebar-list sidebar-menu">
|
||||||
|
<sidebar-menu-item path="{{::$ctrl.path }}" path-params="$ctrl.pathParams" icon-class="{{::$ctrl.iconClass}}">
|
||||||
|
<div class="sidebar-menu-head">
|
||||||
|
<button ng-click="$ctrl.onClickArrow($event)" class="small sidebar-menu-indicator">
|
||||||
|
<i class="fas" ng-class="$ctrl.isOpen() ? 'fa-chevron-down' : 'fa-chevron-right'"></i>
|
||||||
|
</button>
|
||||||
|
{{ ::$ctrl.label }}
|
||||||
|
</div>
|
||||||
|
</sidebar-menu-item>
|
||||||
|
|
||||||
|
<div class="sidebar-list-items" ng-if="$ctrl.isOpen()">
|
||||||
|
<ng-transclude></ng-transclude>
|
||||||
|
</div>
|
||||||
|
</li>
|
|
@ -0,0 +1,7 @@
|
||||||
|
export const sidebarSection = {
|
||||||
|
templateUrl: './sidebar-section.html',
|
||||||
|
transclude: true,
|
||||||
|
bindings: {
|
||||||
|
title: '@',
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,7 @@
|
||||||
|
<div class="sidebar-section">
|
||||||
|
<li class="sidebar-title">
|
||||||
|
<span>{{ ::$ctrl.title }}</span>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<div class="sidebar-section-items" ng-transclude> </div>
|
||||||
|
</div>
|
326
app/portainer/components/sidebar/sidebar.css
Normal file
326
app/portainer/components/sidebar/sidebar.css
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -77,26 +77,22 @@ angular.module('portainer.app').factory('AccessService', [
|
||||||
}
|
}
|
||||||
|
|
||||||
async function accessesAsync(entity, parent) {
|
async function accessesAsync(entity, parent) {
|
||||||
try {
|
if (!entity) {
|
||||||
if (!entity) {
|
throw new Error('Unable to retrieve accesses');
|
||||||
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.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) {
|
function accesses(entity, parent) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
angular.module('portainer').service('TerminalWindow', function ($window) {
|
angular.module('portainer').service('TerminalWindow', function ($window) {
|
||||||
|
const terminalHeight = 495;
|
||||||
this.terminalopen = function () {
|
this.terminalopen = function () {
|
||||||
const terminalHeight = 480;
|
|
||||||
const contentWrapperHeight = $window.innerHeight;
|
const contentWrapperHeight = $window.innerHeight;
|
||||||
const newContentWrapperHeight = contentWrapperHeight - terminalHeight;
|
const newContentWrapperHeight = contentWrapperHeight - terminalHeight;
|
||||||
document.getElementById('content-wrapper').style.height = newContentWrapperHeight + 'px';
|
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('content-wrapper').style.overflowY = wrapperCSS.overflowY;
|
||||||
document.getElementById('sidebar-wrapper').style.height = wrapperCSS.height;
|
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';
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,6 +7,12 @@
|
||||||
<rd-header-content>Registry management</rd-header-content>
|
<rd-header-content>Registry management</rd-header-content>
|
||||||
</rd-header>
|
</rd-header>
|
||||||
|
|
||||||
|
<information-panel title-text="Information">
|
||||||
|
<span class="small text-muted">
|
||||||
|
View registries via an endpoint to manage access for user(s) and/or team(s)
|
||||||
|
</span>
|
||||||
|
</information-panel>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<registries-datatable
|
<registries-datatable
|
||||||
|
|
|
@ -9,193 +9,92 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="sidebar-content">
|
<div class="sidebar-content">
|
||||||
<ul class="sidebar">
|
<ul class="sidebar">
|
||||||
<li class="sidebar-list">
|
<sidebar-menu-item path="portainer.home" icon-class="fa-home fa-fw" class-name="sidebar-list">Home</sidebar-menu-item>
|
||||||
<a ui-sref="portainer.home" ui-sref-active="active">Home <span class="menu-icon fa fa-home fa-fw"></span></a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-title endpoint-name" ng-if="applicationState.endpoint.name">
|
<li class="sidebar-title endpoint-name" ng-if="applicationState.endpoint.name">
|
||||||
<span class="fa fa-plug space-right"></span>{{ applicationState.endpoint.name }}
|
<span class="fa fa-plug space-right"></span>{{ applicationState.endpoint.name }}
|
||||||
<kubectl-shell ng-if="applicationState.endpoint.mode && applicationState.endpoint.mode.provider === 'KUBERNETES'" class="kubectl-shell"></kubectl-shell>
|
<kubectl-shell ng-if="applicationState.endpoint.mode && applicationState.endpoint.mode.provider === 'KUBERNETES'" class="kubectl-shell"></kubectl-shell>
|
||||||
</li>
|
</li>
|
||||||
<kubernetes-sidebar-content
|
<div ng-if="applicationState.endpoint.mode">
|
||||||
ng-if="applicationState.endpoint.mode && applicationState.endpoint.mode.provider === 'KUBERNETES'"
|
<kubernetes-sidebar ng-if="applicationState.endpoint.mode.provider === 'KUBERNETES'" is-sidebar-open="toggle" endpoint-id="endpointId"></kubernetes-sidebar>
|
||||||
admin-access="isAdmin"
|
|
||||||
endpoint-id="endpointId"
|
<azure-sidebar ng-if="applicationState.endpoint.mode.provider === 'AZURE'" endpoint-id="endpointId"> </azure-sidebar>
|
||||||
current-state="$state.current.name"
|
|
||||||
>
|
<docker-sidebar
|
||||||
</kubernetes-sidebar-content>
|
ng-if="applicationState.endpoint.mode.provider !== 'AZURE' && applicationState.endpoint.mode.provider !== 'KUBERNETES'"
|
||||||
<azure-sidebar-content ng-if="applicationState.endpoint.mode && applicationState.endpoint.mode.provider === 'AZURE'" endpoint-id="endpointId"> </azure-sidebar-content>
|
current-route-name="$state.current.name"
|
||||||
<docker-sidebar-content
|
is-sidebar-open="toggle"
|
||||||
ng-if="applicationState.endpoint.mode && applicationState.endpoint.mode.provider !== 'AZURE' && applicationState.endpoint.mode.provider !== 'KUBERNETES'"
|
show-stacks="showStacks"
|
||||||
current-route-name="$state.current.name"
|
endpoint-api-version="applicationState.endpoint.apiVersion"
|
||||||
toggle="toggle"
|
swarm-management="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'"
|
||||||
show-stacks="showStacks"
|
standalone-management="applicationState.endpoint.mode.provider === 'DOCKER_STANDALONE'"
|
||||||
endpoint-api-version="applicationState.endpoint.apiVersion"
|
admin-access="isAdmin || isEndpointAdmin"
|
||||||
swarm-management="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'"
|
offline-mode="endpointState.OfflineMode"
|
||||||
standalone-management="applicationState.endpoint.mode.provider === 'DOCKER_STANDALONE'"
|
endpoint-id="endpointId"
|
||||||
admin-access="isAdmin"
|
></docker-sidebar>
|
||||||
offline-mode="endpointState.OfflineMode"
|
</div>
|
||||||
endpoint-id="endpointId"
|
|
||||||
></docker-sidebar-content>
|
<sidebar-section title="Integrations" ng-if="applicationState.endpoint.mode && applicationState.endpoint.extensions.length > 0" authorization="IntegrationStoridgeAdmin">
|
||||||
<li class="sidebar-title" authorization="IntegrationStoridgeAdmin" ng-if="applicationState.endpoint.mode && applicationState.endpoint.extensions.length > 0">
|
<sidebar-menu
|
||||||
<span>Integrations</span>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
authorization="IntegrationStoridgeAdmin"
|
|
||||||
class="sidebar-list"
|
|
||||||
ng-if="
|
|
||||||
applicationState.endpoint.mode &&
|
|
||||||
applicationState.endpoint.extensions.indexOf('storidge') !== -1 &&
|
|
||||||
applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' &&
|
|
||||||
applicationState.endpoint.mode.role === 'MANAGER'
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<a ui-sref="storidge.cluster" ui-sref-active="active">Storidge <span class="menu-icon fa fa-bolt fa-fw"></span></a>
|
|
||||||
<div
|
|
||||||
class="sidebar-sublist"
|
|
||||||
ng-if="
|
ng-if="
|
||||||
toggle &&
|
applicationState.endpoint.mode &&
|
||||||
($state.current.name === 'storidge.cluster' ||
|
applicationState.endpoint.extensions.indexOf('storidge') !== -1 &&
|
||||||
$state.current.name === 'storidge.profiles' ||
|
applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' &&
|
||||||
$state.current.name === 'storidge.monitor' ||
|
applicationState.endpoint.mode.role === 'MANAGER'
|
||||||
$state.current.name === 'storidge.profiles.new' ||
|
|
||||||
$state.current.name === 'storidge.profiles.profile' ||
|
|
||||||
$state.current.name === 'storidge.drives' ||
|
|
||||||
$state.current.name === 'storidge.drives.drive' ||
|
|
||||||
$state.current.name === 'storidge.cluster.node')
|
|
||||||
"
|
"
|
||||||
|
icon-class="fa-bolt fa-fw"
|
||||||
|
label="Storidge"
|
||||||
|
path="storidge.cluster"
|
||||||
|
is-sidebar-open="toggle"
|
||||||
|
children-paths="['storidge.cluster', 'storidge.profiles', 'storidge.monitor', 'storidge.profiles.new', 'storidge.profiles.profile', 'storidge.drives', 'storidge.drives.drive', 'storidge.cluster.node']"
|
||||||
>
|
>
|
||||||
<a ui-sref="storidge.monitor" ui-sref-active="active">Monitor</a>
|
<sidebar-menu-item path="storidge.monitor" class-name="sidebar-sublist">Monitor</sidebar-menu-item>
|
||||||
</div>
|
<sidebar-menu-item path="storidge.profiles" class-name="sidebar-sublist">Profiles</sidebar-menu-item>
|
||||||
<div
|
<sidebar-menu-item path="storidge.drives" class-name="sidebar-sublist">Drives</sidebar-menu-item>
|
||||||
class="sidebar-sublist"
|
</sidebar-menu>
|
||||||
ng-if="
|
</sidebar-section>
|
||||||
toggle &&
|
|
||||||
($state.current.name === 'storidge.cluster' ||
|
<sidebar-section title="Edge compute" ng-if="isAdmin && applicationState.application.enableEdgeComputeFeatures">
|
||||||
$state.current.name === 'storidge.profiles' ||
|
<sidebar-menu-item path="edge.groups" icon-class="fa-object-group fa-fw" class-name="sidebar-list">Edge Groups</sidebar-menu-item>
|
||||||
$state.current.name === 'storidge.monitor' ||
|
<sidebar-menu-item path="edge.stacks" icon-class="fa-layer-group fa-fw" class-name="sidebar-list">Edge Stacks</sidebar-menu-item>
|
||||||
$state.current.name === 'storidge.profiles.new' ||
|
<sidebar-menu-item path="edge.jobs" icon-class="fa-clock fa-fw" class-name="sidebar-list">Edge Jobs</sidebar-menu-item>
|
||||||
$state.current.name === 'storidge.profiles.profile' ||
|
</sidebar-section>
|
||||||
$state.current.name === 'storidge.drives' ||
|
|
||||||
$state.current.name === 'storidge.drives.drive' ||
|
<sidebar-section ng-if="isAdmin || isTeamLeader" title="Settings">
|
||||||
$state.current.name === 'storidge.cluster.node')
|
<sidebar-menu
|
||||||
"
|
icon-class="fa-users fa-fw"
|
||||||
|
label="Users"
|
||||||
|
path="portainer.users"
|
||||||
|
is-sidebar-open="toggle"
|
||||||
|
children-paths="['portainer.users.user' ,'portainer.teams' ,'portainer.teams.team' ,'portainer.roles' ,'portainer.roles.role' ,'portainer.roles.new']"
|
||||||
>
|
>
|
||||||
<a ui-sref="storidge.profiles" ui-sref-active="active">Profiles</a>
|
<sidebar-menu-item path="portainer.teams" class-name="sidebar-sublist">Teams</sidebar-menu-item>
|
||||||
|
<sidebar-menu-item path="portainer.roles" class-name="sidebar-sublist">Roles</sidebar-menu-item>
|
||||||
|
</sidebar-menu>
|
||||||
|
|
||||||
|
<div ng-if="isAdmin">
|
||||||
|
<sidebar-menu
|
||||||
|
icon-class="fa-plug fa-fw"
|
||||||
|
label="Endpoints"
|
||||||
|
path="portainer.endpoints"
|
||||||
|
is-sidebar-open="toggle"
|
||||||
|
children-paths="['portainer.endpoints.endpoint', 'portainer.endpoints.new', 'portainer.endpoints.endpoint.access', 'portainer.groups', 'portainer.groups.group', 'portainer.groups.group.access', 'portainer.groups.new', 'portainer.tags']"
|
||||||
|
>
|
||||||
|
<sidebar-menu-item path="portainer.groups" class-name="sidebar-sublist">Groups</sidebar-menu-item>
|
||||||
|
<sidebar-menu-item path="portainer.tags" class-name="sidebar-sublist">Tags</sidebar-menu-item>
|
||||||
|
</sidebar-menu>
|
||||||
|
|
||||||
|
<sidebar-menu-item path="portainer.registries" icon-class="fa-database fa-fw" class-name="sidebar-list">Registries</sidebar-menu-item>
|
||||||
|
|
||||||
|
<sidebar-menu label="Settings" icon-class="fa-cogs fa-fw" path="portainer.settings" is-sidebar-open="toggle" children-paths="['portainer.settings.authentication']">
|
||||||
|
<sidebar-menu-item path="portainer.settings.authentication" class-name="sidebar-sublist">Authentication</sidebar-menu-item>
|
||||||
|
|
||||||
|
<div class="sidebar-sublist">
|
||||||
|
<a href="http://www.portainer.io/help_about" target="_blank">Help / About</a>
|
||||||
|
</div>
|
||||||
|
</sidebar-menu>
|
||||||
</div>
|
</div>
|
||||||
<div
|
</sidebar-section>
|
||||||
class="sidebar-sublist"
|
|
||||||
ng-if="
|
|
||||||
toggle &&
|
|
||||||
($state.current.name === 'storidge.cluster' ||
|
|
||||||
$state.current.name === 'storidge.profiles' ||
|
|
||||||
$state.current.name === 'storidge.monitor' ||
|
|
||||||
$state.current.name === 'storidge.profiles.new' ||
|
|
||||||
$state.current.name === 'storidge.profiles.profile' ||
|
|
||||||
$state.current.name === 'storidge.drives' ||
|
|
||||||
$state.current.name === 'storidge.drives.drive' ||
|
|
||||||
$state.current.name === 'storidge.cluster.node')
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<a ui-sref="storidge.drives" ui-sref-active="active">Drives</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-title" ng-if="isAdmin && applicationState.application.enableEdgeComputeFeatures">
|
|
||||||
<span>Edge Compute</span>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list" ng-if="isAdmin && applicationState.application.enableEdgeComputeFeatures">
|
|
||||||
<a ui-sref="edge.groups" ui-sref-active="active">Edge Groups <span class="menu-icon fa fa-object-group fa-fw"></span></a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list" ng-if="isAdmin && applicationState.application.enableEdgeComputeFeatures">
|
|
||||||
<a ui-sref="edge.stacks" ui-sref-active="active">Edge Stacks <span class="menu-icon fa fa-layer-group fa-fw"></span></a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list" ng-if="isAdmin && applicationState.application.enableEdgeComputeFeatures">
|
|
||||||
<a ui-sref="edge.jobs" ui-sref-active="active">Edge Jobs <span class="menu-icon fa fa-clock fa-fw"></span></a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-title" ng-if="isAdmin || isTeamLeader">
|
|
||||||
<span>Settings</span>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list" ng-if="isAdmin || isTeamLeader">
|
|
||||||
<a ui-sref="portainer.users" ui-sref-active="active">Users <span class="menu-icon fa fa-users fa-fw"></span></a>
|
|
||||||
<div
|
|
||||||
class="sidebar-sublist"
|
|
||||||
ng-if="
|
|
||||||
toggle &&
|
|
||||||
($state.current.name === 'portainer.users' ||
|
|
||||||
$state.current.name === 'portainer.users.user' ||
|
|
||||||
$state.current.name === 'portainer.teams' ||
|
|
||||||
$state.current.name === 'portainer.teams.team' ||
|
|
||||||
$state.current.name === 'portainer.roles')
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<a ui-sref="portainer.teams" ui-sref-active="active">Teams</a>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="sidebar-sublist"
|
|
||||||
ng-if="
|
|
||||||
toggle &&
|
|
||||||
($state.current.name === 'portainer.users' ||
|
|
||||||
$state.current.name === 'portainer.users.user' ||
|
|
||||||
$state.current.name === 'portainer.teams' ||
|
|
||||||
$state.current.name === 'portainer.teams.team' ||
|
|
||||||
$state.current.name === 'portainer.roles')
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<a ui-sref="portainer.roles" ui-sref-active="active">Roles</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list" ng-if="isAdmin">
|
|
||||||
<a
|
|
||||||
ui-sref="portainer.endpoints"
|
|
||||||
ng-class="{ active: $state.current.name.includes('portainer.endpoints') && $state.current.name !== 'portainer.endpoints.endpoint.kubernetesConfig' }"
|
|
||||||
>Endpoints <span class="menu-icon fa fa-plug fa-fw"></span
|
|
||||||
></a>
|
|
||||||
<div
|
|
||||||
class="sidebar-sublist"
|
|
||||||
ng-if="
|
|
||||||
toggle &&
|
|
||||||
($state.current.name === 'portainer.endpoints' ||
|
|
||||||
$state.current.name === 'portainer.endpoints.endpoint' ||
|
|
||||||
$state.current.name === 'portainer.endpoints.new' ||
|
|
||||||
$state.current.name === 'portainer.endpoints.endpoint.access' ||
|
|
||||||
$state.current.name === 'portainer.groups' ||
|
|
||||||
$state.current.name === 'portainer.groups.group' ||
|
|
||||||
$state.current.name === 'portainer.groups.group.access' ||
|
|
||||||
$state.current.name === 'portainer.groups.new' ||
|
|
||||||
$state.current.name === 'portainer.tags')
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<a ui-sref="portainer.groups" ui-sref-active="active">Groups</a>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="sidebar-sublist"
|
|
||||||
ng-if="
|
|
||||||
toggle &&
|
|
||||||
($state.current.name === 'portainer.endpoints' ||
|
|
||||||
$state.current.name === 'portainer.endpoints.endpoint' ||
|
|
||||||
$state.current.name === 'portainer.endpoints.new' ||
|
|
||||||
$state.current.name === 'portainer.endpoints.endpoint.access' ||
|
|
||||||
$state.current.name === 'portainer.groups' ||
|
|
||||||
$state.current.name === 'portainer.groups.group' ||
|
|
||||||
$state.current.name === 'portainer.groups.group.access' ||
|
|
||||||
$state.current.name === 'portainer.groups.new' ||
|
|
||||||
$state.current.name === 'portainer.tags')
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<a ui-sref="portainer.tags" ui-sref-active="active">Tags</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list" ng-if="isAdmin">
|
|
||||||
<a ui-sref="portainer.registries" ui-sref-active="active">Registries <span class="menu-icon fa fa-database fa-fw"></span></a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list" ng-if="isAdmin">
|
|
||||||
<a ui-sref="portainer.settings" ui-sref-active="active">Settings <span class="menu-icon fa fa-cogs fa-fw"></span></a>
|
|
||||||
<div class="sidebar-sublist" ng-if="toggle && $state.current.name.startsWith('portainer.settings') && isAdmin">
|
|
||||||
<a ui-sref="portainer.settings.authentication" ui-sref-active="active">Authentication</a>
|
|
||||||
</div>
|
|
||||||
<div class="sidebar-sublist" ng-if="toggle && $state.current.name.startsWith('portainer.settings')">
|
|
||||||
<a href="http://www.portainer.io/help_about" target="_blank">Help / About</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
<div class="sidebar-footer-content">
|
<div class="sidebar-footer-content">
|
||||||
<div class="update-notification" ng-if="applicationState.application.versionStatus.UpdateAvailable">
|
<div class="update-notification" ng-if="applicationState.application.versionStatus.UpdateAvailable">
|
||||||
|
|
|
@ -1,71 +1,66 @@
|
||||||
angular.module('portainer.app').controller('SidebarController', [
|
angular.module('portainer.app').controller('SidebarController', SidebarController);
|
||||||
'$rootScope',
|
|
||||||
'$q',
|
|
||||||
'$scope',
|
|
||||||
'$transitions',
|
|
||||||
'StateManager',
|
|
||||||
'Notifications',
|
|
||||||
'Authentication',
|
|
||||||
'UserService',
|
|
||||||
'EndpointProvider',
|
|
||||||
function ($rootScope, $q, $scope, $transitions, StateManager, Notifications, Authentication, UserService, EndpointProvider) {
|
|
||||||
function checkPermissions(memberships) {
|
|
||||||
var isLeader = false;
|
|
||||||
angular.forEach(memberships, function (membership) {
|
|
||||||
if (membership.Role === 1) {
|
|
||||||
isLeader = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$scope.isTeamLeader = isLeader;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isClusterAdmin() {
|
function SidebarController($rootScope, $scope, $transitions, StateManager, Notifications, Authentication, UserService, EndpointProvider) {
|
||||||
return Authentication.isAdmin();
|
$scope.applicationState = StateManager.getState();
|
||||||
}
|
$scope.endpointState = EndpointProvider.endpoint();
|
||||||
|
|
||||||
async function initView() {
|
function checkPermissions(memberships) {
|
||||||
$scope.uiVersion = StateManager.getState().application.version;
|
var isLeader = false;
|
||||||
$scope.logo = StateManager.getState().application.logo;
|
angular.forEach(memberships, function (membership) {
|
||||||
|
if (membership.Role === 1) {
|
||||||
$scope.endpointId = EndpointProvider.endpointID();
|
isLeader = true;
|
||||||
$scope.showStacks = shouldShowStacks();
|
|
||||||
|
|
||||||
let userDetails = Authentication.getUserDetails();
|
|
||||||
const isAdmin = isClusterAdmin();
|
|
||||||
$scope.isAdmin = isAdmin;
|
|
||||||
|
|
||||||
$q.when(!isAdmin ? UserService.userMemberships(userDetails.ID) : [])
|
|
||||||
.then(function success(data) {
|
|
||||||
checkPermissions(data);
|
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
Notifications.error('Failure', err, 'Unable to retrieve user memberships');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
initView();
|
|
||||||
|
|
||||||
function shouldShowStacks() {
|
|
||||||
if (isClusterAdmin()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const endpoint = EndpointProvider.currentEndpoint();
|
|
||||||
if (!endpoint || !endpoint.SecuritySettings) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return endpoint.SecuritySettings.allowStackManagementForRegularUsers;
|
|
||||||
}
|
|
||||||
|
|
||||||
$transitions.onEnter({}, async () => {
|
|
||||||
$scope.endpointId = EndpointProvider.endpointID();
|
|
||||||
$scope.showStacks = shouldShowStacks();
|
|
||||||
$scope.isAdmin = isClusterAdmin();
|
|
||||||
|
|
||||||
if ($scope.applicationState.endpoint.name) {
|
|
||||||
document.title = `${$rootScope.defaultTitle} | ${$scope.applicationState.endpoint.name}`;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
$scope.isTeamLeader = isLeader;
|
||||||
]);
|
}
|
||||||
|
|
||||||
|
function isClusterAdmin() {
|
||||||
|
return Authentication.isAdmin();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initView() {
|
||||||
|
$scope.uiVersion = StateManager.getState().application.version;
|
||||||
|
$scope.logo = StateManager.getState().application.logo;
|
||||||
|
|
||||||
|
$scope.endpointId = EndpointProvider.endpointID();
|
||||||
|
$scope.showStacks = shouldShowStacks();
|
||||||
|
|
||||||
|
const userDetails = Authentication.getUserDetails();
|
||||||
|
const isAdmin = isClusterAdmin();
|
||||||
|
$scope.isAdmin = isAdmin;
|
||||||
|
|
||||||
|
if (!isAdmin) {
|
||||||
|
try {
|
||||||
|
const memberships = await UserService.userMemberships(userDetails.ID);
|
||||||
|
checkPermissions(memberships);
|
||||||
|
} catch (err) {
|
||||||
|
Notifications.error('Failure', err, 'Unable to retrieve user memberships');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initView();
|
||||||
|
|
||||||
|
function shouldShowStacks() {
|
||||||
|
if (isClusterAdmin()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const endpoint = EndpointProvider.currentEndpoint();
|
||||||
|
if (!endpoint || !endpoint.SecuritySettings) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return endpoint.SecuritySettings.allowStackManagementForRegularUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
$transitions.onEnter({}, async () => {
|
||||||
|
$scope.endpointId = EndpointProvider.endpointID();
|
||||||
|
$scope.showStacks = shouldShowStacks();
|
||||||
|
$scope.isAdmin = isClusterAdmin();
|
||||||
|
|
||||||
|
if ($scope.applicationState.endpoint.name) {
|
||||||
|
document.title = `${$rootScope.defaultTitle} | ${$scope.applicationState.endpoint.name}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -68,14 +68,6 @@ angular
|
||||||
|
|
||||||
$scope.onChangeFormValues = onChangeFormValues;
|
$scope.onChangeFormValues = onChangeFormValues;
|
||||||
|
|
||||||
$scope.addEnvironmentVariable = function () {
|
|
||||||
$scope.formValues.Env.push({ name: '', value: '' });
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.removeEnvironmentVariable = function (index) {
|
|
||||||
$scope.formValues.Env.splice(index, 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.addAdditionalFiles = function () {
|
$scope.addAdditionalFiles = function () {
|
||||||
$scope.formValues.AdditionalFiles.push('');
|
$scope.formValues.AdditionalFiles.push('');
|
||||||
};
|
};
|
||||||
|
|
10
package.json
10
package.json
|
@ -119,10 +119,10 @@
|
||||||
"cssnano": "^3.10.0",
|
"cssnano": "^3.10.0",
|
||||||
"cypress": "^5.2.0",
|
"cypress": "^5.2.0",
|
||||||
"cypress-wait-until": "^1.7.1",
|
"cypress-wait-until": "^1.7.1",
|
||||||
"eslint": "5.16.0",
|
"eslint": "^7.24.0",
|
||||||
"eslint-config-prettier": "^6.10.1",
|
"eslint-config-prettier": "^8.2.0",
|
||||||
"eslint-loader": "^2.1.2",
|
"eslint-plugin-import": "^2.22.1",
|
||||||
"eslint-plugin-import": "^2.20.2",
|
"eslint-webpack-plugin": "^2.5.3",
|
||||||
"file-loader": "^1.1.11",
|
"file-loader": "^1.1.11",
|
||||||
"grunt": "^1.1.0",
|
"grunt": "^1.1.0",
|
||||||
"grunt-cli": "^1.3.2",
|
"grunt-cli": "^1.3.2",
|
||||||
|
@ -175,4 +175,4 @@
|
||||||
"*.js": "eslint --cache --fix",
|
"*.js": "eslint --cache --fix",
|
||||||
"*.{js,css,md,html}": "prettier --write"
|
"*.{js,css,md,html}": "prettier --write"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ const CleanTerminalPlugin = require('clean-terminal-webpack-plugin');
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
||||||
const LodashModuleReplacementPlugin = require('lodash-webpack-plugin');
|
const LodashModuleReplacementPlugin = require('lodash-webpack-plugin');
|
||||||
|
const ESLintPlugin = require('eslint-webpack-plugin');
|
||||||
|
|
||||||
const pkg = require('../package.json');
|
const pkg = require('../package.json');
|
||||||
const projectRoot = path.resolve(__dirname, '..');
|
const projectRoot = path.resolve(__dirname, '..');
|
||||||
|
|
||||||
|
@ -37,14 +39,7 @@ module.exports = {
|
||||||
{
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
use: [
|
use: ['babel-loader', 'auto-ngtemplate-loader'],
|
||||||
'babel-loader',
|
|
||||||
'auto-ngtemplate-loader',
|
|
||||||
{
|
|
||||||
// enforce: 'pre',
|
|
||||||
loader: 'eslint-loader',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.html$/,
|
test: /\.html$/,
|
||||||
|
@ -81,6 +76,7 @@ module.exports = {
|
||||||
writeToDisk: true,
|
writeToDisk: true,
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
new ESLintPlugin(),
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
template: './app/index.html',
|
template: './app/index.html',
|
||||||
templateParameters: {
|
templateParameters: {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue